diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
index 038faf6bc..fbdde88af 100644
--- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
+++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs
@@ -1,4 +1,6 @@
using System;
+using System.Collections.Generic;
+using System.IO;
using System.Threading.Tasks;
namespace Discord
@@ -10,5 +12,12 @@ namespace Discord
/// Modifies this text channel.
Task ModifyAsync(Action func, RequestOptions options = null);
+
+ /// Creates a webhook in this text channel.
+ Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null);
+ /// Gets the webhook in this text channel with the provided id, or null if not found.
+ Task GetWebhookAsync(ulong id, RequestOptions options = null);
+ /// Gets the webhooks for this text channel.
+ Task> GetWebhooksAsync(RequestOptions options = null);
}
}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
index 7874f5fd1..7d12d1cb0 100644
--- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
+++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs
@@ -114,5 +114,10 @@ namespace Discord
Task DownloadUsersAsync();
/// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed.
Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null);
+
+ /// Gets the webhook in this guild with the provided id, or null if not found.
+ Task GetWebhookAsync(ulong id, RequestOptions options = null);
+ /// Gets a collection of all webhooks for this guild.
+ Task> GetWebhooksAsync(RequestOptions options = null);
}
}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs b/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs
index 8f4d42187..be769b944 100644
--- a/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IWebhookUser.cs
@@ -1,6 +1,5 @@
namespace Discord
{
- //TODO: Add webhook endpoints
public interface IWebhookUser : IGuildUser
{
ulong WebhookId { get; }
diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs
new file mode 100644
index 000000000..4a8bb309b
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Threading.Tasks;
+
+namespace Discord
+{
+ public interface IWebhook : IDeletable, ISnowflakeEntity
+ {
+ /// Gets the token of this webhook.
+ string Token { get; }
+
+ /// Gets the default name of this webhook.
+ string Name { get; }
+ /// Gets the id of this webhook's default avatar.
+ string AvatarId { get; }
+ /// Gets the url to this webhook's default avatar.
+ string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128);
+
+ /// Gets the channel for this webhook.
+ ITextChannel Channel { get; }
+ /// Gets the id of the channel for this webhook.
+ ulong ChannelId { get; }
+
+ /// Gets the guild owning this webhook.
+ IGuild Guild { get; }
+ /// Gets the id of the guild owning this webhook.
+ ulong GuildId { get; }
+
+ /// Gets the user that created this webhook.
+ IUser Creator { get; }
+
+ /// Modifies this webhook.
+ Task ModifyAsync(Action func, string webhookToken = null, RequestOptions options = null);
+ }
+}
diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs
new file mode 100644
index 000000000..e00ba86be
--- /dev/null
+++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs
@@ -0,0 +1,27 @@
+namespace Discord
+{
+ ///
+ /// Modify an with the specified parameters.
+ ///
+ ///
+ ///
+ /// await webhook.ModifyAsync(x =>
+ /// {
+ /// x.Name = "Bob";
+ /// x.Avatar = new Image("avatar.jpg");
+ /// });
+ ///
+ ///
+ ///
+ public class WebhookProperties
+ {
+ ///
+ /// The default name of the webhook.
+ ///
+ public Optional Name { get; set; }
+ ///
+ /// The default avatar of the webhook.
+ ///
+ public Optional Image { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs
index 23e8e9c5b..b694ccf4e 100644
--- a/src/Discord.Net.Core/IDiscordClient.cs
+++ b/src/Discord.Net.Core/IDiscordClient.cs
@@ -34,5 +34,7 @@ namespace Discord
Task> GetVoiceRegionsAsync(RequestOptions options = null);
Task GetVoiceRegionAsync(string id, RequestOptions options = null);
+
+ Task GetWebhookAsync(ulong id, string webhookToken = null, RequestOptions options = null);
}
}
diff --git a/src/Discord.Net.Rest/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs
new file mode 100644
index 000000000..cbd5fdad5
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Common/Webhook.cs
@@ -0,0 +1,25 @@
+#pragma warning disable CS1591
+using Newtonsoft.Json;
+
+namespace Discord.API
+{
+ internal class Webhook
+ {
+ [JsonProperty("id")]
+ public ulong Id { get; set; }
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+ [JsonProperty("token")]
+ public string Token { get; set; }
+
+ [JsonProperty("name")]
+ public Optional Name { get; set; }
+ [JsonProperty("avatar")]
+ public Optional Avatar { get; set; }
+ [JsonProperty("guild_id")]
+ public Optional GuildId { get; set; }
+
+ [JsonProperty("user")]
+ public Optional Creator { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
index 970a30201..279f538bd 100644
--- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
+++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs
@@ -8,6 +8,8 @@ namespace Discord.API.Rest
{
[JsonProperty("content")]
public string Content { get; }
+ [JsonProperty("wait")]
+ public bool ReturnCreatedMessage { get; set; }
[JsonProperty("nonce")]
public Optional Nonce { get; set; }
diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs
new file mode 100644
index 000000000..0d1059fab
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs
@@ -0,0 +1,14 @@
+#pragma warning disable CS1591
+using Newtonsoft.Json;
+
+namespace Discord.API.Rest
+{
+ [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
+ internal class CreateWebhookParams
+ {
+ [JsonProperty("name")]
+ public string Name { get; set; }
+ [JsonProperty("avatar")]
+ public Optional Avatar { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs
new file mode 100644
index 000000000..1d385c328
--- /dev/null
+++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs
@@ -0,0 +1,14 @@
+#pragma warning disable CS1591
+using Newtonsoft.Json;
+
+namespace Discord.API.Rest
+{
+ [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
+ internal class ModifyWebhookParams
+ {
+ [JsonProperty("name")]
+ public Optional Name { get; set; }
+ [JsonProperty("avatar")]
+ public Optional Avatar { get; set; }
+ }
+}
diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs
index ed12ff383..9b88140ae 100644
--- a/src/Discord.Net.Rest/BaseDiscordClient.cs
+++ b/src/Discord.Net.Rest/BaseDiscordClient.cs
@@ -160,6 +160,9 @@ namespace Discord.Rest
Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> Task.FromResult(null);
+ Task IDiscordClient.GetWebhookAsync(ulong id, string webhookToken, RequestOptions options)
+ => Task.FromResult(null);
+
Task IDiscordClient.StartAsync()
=> Task.Delay(0);
Task IDiscordClient.StopAsync()
diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs
index 2f05d5d36..12f2f95e6 100644
--- a/src/Discord.Net.Rest/ClientHelper.cs
+++ b/src/Discord.Net.Rest/ClientHelper.cs
@@ -144,6 +144,14 @@ namespace Discord.Rest
return null;
}
+ public static async Task GetWebhookAsync(BaseDiscordClient client, ulong id, string webhookToken, RequestOptions options)
+ {
+ var model = await client.ApiClient.GetWebhookAsync(id, webhookToken);
+ if (model != null)
+ return RestWebhook.Create(client, (IGuild)null, model);
+ return null;
+ }
+
public static async Task> GetVoiceRegionsAsync(BaseDiscordClient client, RequestOptions options)
{
var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false);
diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs
index a6c42782a..e34ce2603 100644
--- a/src/Discord.Net.Rest/DiscordRestApiClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs
@@ -472,11 +472,13 @@ namespace Discord.API
var ids = new BucketIds(channelId: channelId);
return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}
- public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null)
+ public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, string webhookToken = null, RequestOptions options = null)
{
- if (AuthTokenType != TokenType.Webhook)
+ if (AuthTokenType != TokenType.Webhook && string.IsNullOrWhiteSpace(webhookToken))
throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token.");
+ webhookToken = webhookToken ?? AuthToken;
+
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
if (!args.Embeds.IsSpecified || args.Embeds.Value == null || args.Embeds.Value.Length == 0)
@@ -486,7 +488,11 @@ namespace Discord.API
throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content));
options = RequestOptions.CreateOrClone(options);
- await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ if (args.ReturnCreatedMessage)
+ return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{webhookToken}", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+
+ await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{webhookToken}", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
+ return null;
}
public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null)
{
@@ -1153,6 +1159,79 @@ namespace Discord.API
return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false);
}
+ //Webhooks
+ public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(channelId, 0, nameof(channelId));
+ Preconditions.NotNull(args, nameof(args));
+ Preconditions.NotNull(args.Name, nameof(args.Name));
+ options = RequestOptions.CreateOrClone(options);
+
+ return await SendJsonAsync("POST", () => $"channels/{channelId}/webhooks", args, new BucketIds(), options: options);
+ }
+ public async Task GetWebhookAsync(ulong webhookId, string webhookToken = null, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
+ options = RequestOptions.CreateOrClone(options);
+
+ if (!string.IsNullOrWhiteSpace(webhookToken))
+ {
+ webhookToken = "/" + webhookToken;
+ options.IgnoreState = true;
+ }
+
+ try
+ {
+ return await SendAsync("GET", () => $"webhooks/{webhookId}{webhookToken}", new BucketIds(), options: options).ConfigureAwait(false);
+ }
+ catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; }
+ }
+ public async Task ModifyWebhookAsync(ulong webhookId, ModifyWebhookParams args, string webhookToken = null, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
+ Preconditions.NotNull(args, nameof(args));
+ Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name));
+ options = RequestOptions.CreateOrClone(options);
+
+ if (!string.IsNullOrWhiteSpace(webhookToken))
+ {
+ webhookToken = "/" + webhookToken;
+ options.IgnoreState = true;
+ }
+
+ return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}{webhookToken}", args, new BucketIds(), options: options).ConfigureAwait(false);
+ }
+ public async Task DeleteWebhookAsync(ulong webhookId, string webhookToken = null, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(webhookId, 0, nameof(webhookId));
+ options = RequestOptions.CreateOrClone(options);
+ options.IgnoreState = true;
+
+ if (!string.IsNullOrWhiteSpace(webhookToken))
+ {
+ webhookToken = "/" + webhookToken;
+ options.IgnoreState = true;
+ }
+
+ await SendAsync("DELETE", () => $"webhooks/{webhookId}{webhookToken}", new BucketIds(), options: options).ConfigureAwait(false);
+ }
+ public async Task> GetGuildWebhooksAsync(ulong guildId, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(guildId, 0, nameof(guildId));
+ options = RequestOptions.CreateOrClone(options);
+
+ var ids = new BucketIds(guildId: guildId);
+ return await SendAsync>("GET", () => $"guilds/{guildId}/webhooks", ids, options: options).ConfigureAwait(false);
+ }
+ public async Task> GetChannelWebhooksAsync(ulong channelId, RequestOptions options = null)
+ {
+ Preconditions.NotEqual(channelId, 0, nameof(channelId));
+ options = RequestOptions.CreateOrClone(options);
+
+ var ids = new BucketIds(channelId: channelId);
+ return await SendAsync>("GET", () => $"channels/{channelId}/webhooks", ids, options: options).ConfigureAwait(false);
+ }
+
//Helpers
protected void CheckState()
{
diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs
index aa9937008..1169bca9e 100644
--- a/src/Discord.Net.Rest/DiscordRestClient.cs
+++ b/src/Discord.Net.Rest/DiscordRestClient.cs
@@ -91,6 +91,9 @@ namespace Discord.Rest
///
public Task GetVoiceRegionAsync(string id, RequestOptions options = null)
=> ClientHelper.GetVoiceRegionAsync(this, id, options);
+ ///
+ public Task GetWebhookAsync(ulong id, string webhookToken = null, RequestOptions options = null)
+ => ClientHelper.GetWebhookAsync(this, id, webhookToken, options);
//IDiscordClient
async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options)
@@ -160,5 +163,8 @@ namespace Discord.Rest
=> await GetVoiceRegionsAsync(options).ConfigureAwait(false);
async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options)
=> await GetVoiceRegionAsync(id, options).ConfigureAwait(false);
+
+ async Task IDiscordClient.GetWebhookAsync(ulong id, string webhookToken, RequestOptions options)
+ => await GetWebhookAsync(id, webhookToken, options);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
index 6b7dca3a9..543b755ca 100644
--- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Channel;
using UserModel = Discord.API.User;
+using WebhookModel = Discord.API.Webhook;
namespace Discord.Rest
{
@@ -279,6 +280,30 @@ namespace Discord.Rest
RequestOptions options)
=> new TypingNotifier(client, channel, options);
+ //Webhooks
+ public static async Task CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options)
+ {
+ var args = new CreateWebhookParams { Name = name };
+ if (avatar != null)
+ args.Avatar = new API.Image(avatar);
+
+ var model = await client.ApiClient.CreateWebhookAsync(channel.Id, args, options).ConfigureAwait(false);
+ return RestWebhook.Create(client, channel, model);
+ }
+ public static async Task GetWebhookAsync(ITextChannel channel, BaseDiscordClient client, ulong id, RequestOptions options)
+ {
+ var model = await client.ApiClient.GetWebhookAsync(id, options: options).ConfigureAwait(false);
+ if (model == null)
+ return null;
+ return RestWebhook.Create(client, channel, model);
+ }
+ public static async Task> GetWebhooksAsync(ITextChannel channel, BaseDiscordClient client, RequestOptions options)
+ {
+ var models = await client.ApiClient.GetChannelWebhooksAsync(channel.Id, options).ConfigureAwait(false);
+ return models.Select(x => RestWebhook.Create(client, channel, x))
+ .ToImmutableArray();
+ }
+
//Helpers
private static IUser GetAuthor(BaseDiscordClient client, IGuild guild, UserModel model, ulong? webhookId)
{
diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
index d7405fb4a..a13412065 100644
--- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
+++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs
@@ -73,8 +73,23 @@ namespace Discord.Rest
public IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
+ public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => ChannelHelper.GetWebhookAsync(this, Discord, id, options);
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ => ChannelHelper.GetWebhooksAsync(this, Discord, options);
+
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
+ //ITextChannel
+ async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
+ => await CreateWebhookAsync(name, avatar, options);
+ async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
+ => await GetWebhookAsync(id, options);
+ async Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
+ => await GetWebhooksAsync(options);
+
//IMessageChannel
async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
{
diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
index 5cfb1e566..9fe31f69e 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
@@ -247,5 +247,19 @@ namespace Discord.Rest
model = await client.ApiClient.BeginGuildPruneAsync(guild.Id, args, options).ConfigureAwait(false);
return model.Pruned;
}
+
+ //Webhooks
+ public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
+ {
+ var model = await client.ApiClient.GetWebhookAsync(id, options: options).ConfigureAwait(false);
+ if (model == null)
+ return null;
+ return RestWebhook.Create(client, guild, model);
+ }
+ public static async Task> GetWebhooksAsync(IGuild guild, BaseDiscordClient client, RequestOptions options)
+ {
+ var models = await client.ApiClient.GetGuildWebhooksAsync(guild.Id, options).ConfigureAwait(false);
+ return models.Select(x => RestWebhook.Create(client, guild, x)).ToImmutableArray();
+ }
}
}
diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
index 11971a5c1..715b7832f 100644
--- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
+++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs
@@ -239,6 +239,12 @@ namespace Discord.Rest
public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null)
=> GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options);
+ //Webhooks
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => GuildHelper.GetWebhookAsync(this, Discord, id, options);
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ => GuildHelper.GetWebhooksAsync(this, Discord, options);
+
public override string ToString() => Name;
private string DebuggerDisplay => $"{Name} ({Id})";
@@ -361,5 +367,10 @@ namespace Discord.Rest
return ImmutableArray.Create();
}
Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); }
+
+ async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options)
+ => await GetWebhookAsync(id, options);
+ async Task> IGuild.GetWebhooksAsync(RequestOptions options)
+ => await GetWebhooksAsync(options);
}
}
diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
new file mode 100644
index 000000000..c93479c89
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs
@@ -0,0 +1,89 @@
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Model = Discord.API.Webhook;
+
+namespace Discord.Rest
+{
+ [DebuggerDisplay(@"{DebuggerDisplay,nq}")]
+ public class RestWebhook : RestEntity, IWebhook, IUpdateable
+ {
+ internal IGuild Guild { get; }
+ internal ITextChannel Channel { get; }
+ public string Token { get; private set; }
+ public string Name { get; private set; }
+ public string AvatarId { get; private set; }
+ public ulong ChannelId { get; private set; }
+ public ulong GuildId { get; private set; }
+ public IUser Creator { get; private set; }
+
+ public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
+
+ internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id)
+ : base(discord, id)
+ {
+ Guild = guild;
+ }
+ internal RestWebhook(BaseDiscordClient discord, ITextChannel channel, ulong id)
+ : this(discord, channel.Guild, id)
+ {
+ Channel = channel;
+ }
+ internal static RestWebhook Create(BaseDiscordClient discord, IGuild guild, Model model)
+ {
+ var entity = new RestWebhook(discord, guild, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+ internal static RestWebhook Create(BaseDiscordClient discord, ITextChannel channel, Model model)
+ {
+ var entity = new RestWebhook(discord, channel, model.Id);
+ entity.Update(model);
+ return entity;
+ }
+ internal void Update(Model model)
+ {
+ Token = model.Token;
+ ChannelId = model.ChannelId;
+ if (model.Avatar.IsSpecified)
+ AvatarId = model.Avatar.Value;
+ if (model.Creator.IsSpecified)
+ Creator = RestUser.Create(Discord, model.Creator.Value);
+ if (model.GuildId.IsSpecified)
+ GuildId = model.GuildId.Value;
+ if (model.Name.IsSpecified)
+ Name = model.Name.Value;
+ }
+
+ public async Task UpdateAsync(RequestOptions options = null)
+ {
+ var model = await Discord.ApiClient.GetWebhookAsync(Id, Token, options).ConfigureAwait(false);
+ Update(model);
+ }
+
+ public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
+ => CDN.GetUserAvatarUrl(Id, AvatarId, size, format);
+
+ public async Task ModifyAsync(Action func, string webhookToken = null, RequestOptions options = null)
+ {
+ var model = await WebhookHelper.ModifyAsync(this, Discord, func, webhookToken, options).ConfigureAwait(false);
+ Update(model);
+ }
+
+ public Task DeleteAsync(string webhookToken = null, RequestOptions options = null)
+ => WebhookHelper.DeleteAsync(this, Discord, webhookToken, options);
+
+ public override string ToString() => Name;
+ private string DebuggerDisplay => $"{Name} ({Id})";
+
+ //IWebhook
+ IGuild IWebhook.Guild
+ => Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
+ ITextChannel IWebhook.Channel
+ => Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object.");
+ Task IWebhook.ModifyAsync(Action func, string webhookToken, RequestOptions options)
+ => ModifyAsync(func, webhookToken, options);
+ Task IDeletable.DeleteAsync(RequestOptions options)
+ => DeleteAsync(Token, options);
+ }
+}
diff --git a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs
new file mode 100644
index 000000000..0bd518dab
--- /dev/null
+++ b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs
@@ -0,0 +1,34 @@
+using Discord.API.Rest;
+using System;
+using System.Threading.Tasks;
+using ImageModel = Discord.API.Image;
+using Model = Discord.API.Webhook;
+
+namespace Discord.Rest
+{
+ internal static class WebhookHelper
+ {
+ public static async Task ModifyAsync(IWebhook webhook, BaseDiscordClient client,
+ Action func, string webhookToken, RequestOptions options)
+ {
+ var args = new WebhookProperties();
+ func(args);
+ var apiArgs = new ModifyWebhookParams
+ {
+ Avatar = args.Image.IsSpecified ? args.Image.Value?.ToModel() : Optional.Create(),
+ Name = args.Name
+ };
+
+ if (!apiArgs.Avatar.IsSpecified && webhook.AvatarId != null)
+ apiArgs.Avatar = new ImageModel(webhook.AvatarId);
+
+ return await client.ApiClient.ModifyWebhookAsync(webhook.Id, apiArgs, webhookToken, options).ConfigureAwait(false);
+ }
+ public static async Task DeleteAsync(IWebhook webhook, BaseDiscordClient client, string webhookToken,
+ RequestOptions options)
+ {
+ await client.ApiClient.DeleteWebhookAsync(webhook.Id, webhookToken, options).ConfigureAwait(false);
+ }
+
+ }
+}
diff --git a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs
index 72b45e466..fee63946c 100644
--- a/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs
+++ b/src/Discord.Net.Rpc/Entities/Channels/RpcTextChannel.cs
@@ -66,11 +66,25 @@ namespace Discord.Rpc
=> ChannelHelper.TriggerTypingAsync(this, Discord, options);
public IDisposable EnterTypingState(RequestOptions options = null)
=> ChannelHelper.EnterTypingState(this, Discord, options);
-
+
+ //Webhooks
+ public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => ChannelHelper.GetWebhookAsync(this, Discord, id, options);
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ => ChannelHelper.GetWebhooksAsync(this, Discord, options);
+
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
//ITextChannel
string ITextChannel.Topic { get { throw new NotSupportedException(); } }
+ async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
+ => await CreateWebhookAsync(name, avatar, options);
+ async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
+ => await GetWebhookAsync(id, options);
+ async Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
+ => await GetWebhooksAsync(options);
//IMessageChannel
async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options)
diff --git a/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs
new file mode 100644
index 000000000..e5c7afe41
--- /dev/null
+++ b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs
@@ -0,0 +1,13 @@
+#pragma warning disable CS1591
+using Newtonsoft.Json;
+
+namespace Discord.API.Gateway
+{
+ internal class WebhookUpdateEvent
+ {
+ [JsonProperty("guild_id")]
+ public ulong GuildId { get; set; }
+ [JsonProperty("channel_id")]
+ public ulong ChannelId { get; set; }
+ }
+}
diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
index c22523e00..48577d51e 100644
--- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
+++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs
@@ -108,10 +108,26 @@ namespace Discord.WebSocket
}
return null;
}
-
+
+ //Webhooks
+ public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null)
+ => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options);
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => ChannelHelper.GetWebhookAsync(this, Discord, id, options);
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ => ChannelHelper.GetWebhooksAsync(this, Discord, options);
+
private string DebuggerDisplay => $"{Name} ({Id}, Text)";
internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel;
+ //ITextChannel
+ async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options)
+ => await CreateWebhookAsync(name, avatar, options);
+ async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options)
+ => await GetWebhookAsync(id, options);
+ async Task> ITextChannel.GetWebhooksAsync(RequestOptions options)
+ => await GetWebhooksAsync(options);
+
//IGuildChannel
Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options)
=> Task.FromResult(GetUser(id));
diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
index aae18be36..579f04394 100644
--- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
+++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
@@ -423,6 +423,12 @@ namespace Discord.WebSocket
_downloaderPromise.TrySetResultAsync(true);
}
+ //Webhooks
+ public Task GetWebhookAsync(ulong id, RequestOptions options = null)
+ => GuildHelper.GetWebhookAsync(this, Discord, id, options);
+ public Task> GetWebhooksAsync(RequestOptions options = null)
+ => GuildHelper.GetWebhooksAsync(this, Discord, options);
+
//Voice States
internal async Task AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model)
{
@@ -659,5 +665,10 @@ namespace Discord.WebSocket
Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options)
=> Task.FromResult(Owner);
Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); }
+
+ async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options)
+ => await GetWebhookAsync(id, options);
+ async Task> IGuild.GetWebhooksAsync(RequestOptions options)
+ => await GetWebhooksAsync(options);
}
}
diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
index 9695099ee..beae73461 100644
--- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs
+++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs
@@ -56,7 +56,7 @@ namespace Discord.Webhook
args.Username = username;
if (avatarUrl != null)
args.AvatarUrl = avatarUrl;
- await ApiClient.CreateWebhookMessageAsync(_webhookId, args, options).ConfigureAwait(false);
+ await ApiClient.CreateWebhookMessageAsync(_webhookId, args, options: options).ConfigureAwait(false);
}
#if FILESYSTEM