Browse Source

Stickers pt1

pull/1923/head
quin lynch 3 years ago
parent
commit
ba69f15c9e
20 changed files with 772 additions and 4 deletions
  1. +28
    -0
      src/Discord.Net.Core/CDN.cs
  2. +53
    -0
      src/Discord.Net.Core/Entities/Guilds/IGuild.cs
  3. +62
    -0
      src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs
  4. +7
    -0
      src/Discord.Net.Core/Entities/Stickers/ISticker.cs
  5. +29
    -0
      src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs
  6. +2
    -0
      src/Discord.Net.Rest/API/Common/Guild.cs
  7. +2
    -2
      src/Discord.Net.Rest/API/Common/Message.cs
  8. +15
    -0
      src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs
  9. +2
    -0
      src/Discord.Net.Rest/API/Common/Sticker.cs
  10. +21
    -0
      src/Discord.Net.Rest/API/Common/StickerItem.cs
  11. +27
    -0
      src/Discord.Net.Rest/API/Common/StickerPack.cs
  12. +32
    -0
      src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs
  13. +19
    -0
      src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs
  14. +87
    -0
      src/Discord.Net.Rest/DiscordRestApiClient.cs
  15. +47
    -0
      src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs
  16. +2
    -1
      src/Discord.Net.Rest/Entities/Messages/Sticker.cs
  17. +46
    -0
      src/Discord.Net.Rest/Entities/Messages/StickerItem.cs
  18. +150
    -1
      src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs
  19. +73
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketCustomSticker.cs
  20. +68
    -0
      src/Discord.Net.WebSocket/Entities/Messages/SocketSticker.cs

+ 28
- 0
src/Discord.Net.Core/CDN.cs View File

@@ -177,6 +177,34 @@ namespace Discord
public static string GetSpotifyDirectUrl(string trackId)
=> $"https://open.spotify.com/track/{trackId}";

/// <summary>
/// Gets a stickers url based off the id and format.
/// </summary>
/// <param name="stickerId">The id of the sticker.</param>
/// <param name="format">The format of the sticker</param>
/// <returns>
/// A URL to the sticker.
/// </returns>
public static string GetStickerUrl(ulong stickerId, StickerFormatType format = StickerFormatType.Png)
=> $"{DiscordConfig.CDNUrl}stickers/{stickerId}.{FormatToExtension(format)}";

private static string FormatToExtension(StickerFormatType format)
{
switch (format)
{
case StickerFormatType.None:
case StickerFormatType.Png:
return "png";
case StickerFormatType.Lottie:
return "lottie";
case StickerFormatType.Apng:
return "apng";
default:
throw new ArgumentException(nameof(format));

}
}

private static string FormatToExtension(ImageFormat format, string imageId)
{
if (format == ImageFormat.Auto)


+ 53
- 0
src/Discord.Net.Core/Entities/Guilds/IGuild.cs View File

@@ -199,6 +199,13 @@ namespace Discord
/// </returns>
IReadOnlyCollection<GuildEmote> Emotes { get; }
/// <summary>
/// Gets a collection of all custom stickers for this guild.
/// </summary>
/// <returns>
/// A read-only collection of all custom stickers for this guild.
/// </returns>
IReadOnlyCollection<ICustomSticker> Stickers { get; }
/// <summary>
/// Gets a collection of all extra features added to this guild.
/// </summary>
/// <returns>
@@ -942,6 +949,52 @@ namespace Discord
/// </returns>
Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null);

/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="image">The image of the new emote.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
/// </returns>
Task<ICustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options = null);

/// <summary>
/// Gets a specific sticker within this guild.
/// </summary>
/// <param name="id">The id of the sticker to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the sticker found with the
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
Task<ICustomSticker> GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary>
/// Gets a collection of all stickers within this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of stickers found within the guild.
/// </returns>
Task<IReadOnlyCollection<ICustomSticker>> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null);

/// <summary>
/// Deletes a sticker within this guild.
/// </summary>
/// <param name="sticker">The sticker to delete.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous removal operation.
/// </returns>
Task DeleteStickerAsync(ICustomSticker sticker, RequestOptions options = null);

/// <summary>
/// Gets this guilds slash commands commands
/// </summary>


+ 62
- 0
src/Discord.Net.Core/Entities/Stickers/ICustomSticker.cs View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a custom sticker within a guild.
/// </summary>
public interface ICustomSticker : ISticker
{
/// <summary>
/// Gets the users id who uploaded the sticker.
/// </summary>
/// <remarks>
/// In order to get the author id, the bot needs the MANAGE_EMOJIS_AND_STICKERS permission.
/// </remarks>
ulong? AuthorId { get; }

/// <summary>
/// Gets the guild that this custom sticker is in.
/// </summary>
IGuild Guild { get; }

/// <summary>
/// Modifies this sticker.
/// </summary>
/// <remarks>
/// This method modifies this sticker with the specified properties. To see an example of this
/// method and what properties are available, please refer to <see cref="StickerProperties"/>.
/// <br/>
/// <br/>
/// The bot needs the MANAGE_EMOJIS_AND_STICKERS permission within the guild in order to modify stickers.
/// </remarks>
/// <example>
/// <para>The following example replaces the name of the sticker with <c>kekw</c>.</para>
/// <code language="cs">
/// await sticker.ModifyAsync(x =&gt; x.Name = "kekw");
/// </code>
/// </example>
/// <param name="func">A delegate containing the properties to modify the sticker with.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous modification operation.
/// </returns>
Task ModifyAsync(Action<StickerProperties> func, RequestOptions options = null);

/// <summary>
/// Deletes the current sticker.
/// </summary>
/// <remakrs>
/// The bot neeeds the MANAGE_EMOJIS_AND_STICKERS permission inside the guild in order to delete stickers.
/// </remakrs>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous deletion operation.
/// </returns>
Task DeleteAsync(RequestOptions options = null);
}
}

src/Discord.Net.Core/Entities/Messages/ISticker.cs → src/Discord.Net.Core/Entities/Stickers/ISticker.cs View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Discord
{
@@ -63,5 +65,10 @@ namespace Discord
/// A <see cref="StickerFormatType"/> with the format type of this sticker.
/// </returns>
StickerFormatType FormatType { get; }

/// <summary>
/// Gets the image url for this sticker.
/// </summary>
string GetStickerUrl();
}
}

+ 29
- 0
src/Discord.Net.Core/Entities/Stickers/StickerProperties.cs View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a class used to modify stickers.
/// </summary>
public class StickerProperties
{
/// <summary>
/// Gets or sets the name of the sticker.
/// </summary>
public Optional<string> Name { get; set; }

/// <summary>
/// Gets or sets the description of the sticker.
/// </summary>
public Optional<string> Description { get; set; }

/// <summary>
/// Gets or sets the tags of the sticker.
/// </summary>
public Optional<IEnumerable<string>> Tags { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/Guild.cs View File

@@ -80,5 +80,7 @@ namespace Discord.API
public Optional<Channel[]> Threads { get; set; }
[JsonProperty("nsfw_level")]
public NsfwLevel NsfwLevel { get; set; }
[JsonProperty("stickers")]
public Sticker[] Stickers { get; set; }
}
}

+ 2
- 2
src/Discord.Net.Rest/API/Common/Message.cs View File

@@ -60,7 +60,7 @@ namespace Discord.API
public Optional<Message> ReferencedMessage { get; set; }
[JsonProperty("components")]
public Optional<API.ActionRowComponent[]> Components { get; set; }
[JsonProperty("stickers")]
public Optional<Sticker[]> Stickers { get; set; }
[JsonProperty("sticker_items")]
public Optional<StickerItem[]> StickerItems { get; set; }
}
}

+ 15
- 0
src/Discord.Net.Rest/API/Common/NitroStickerPacks.cs View File

@@ -0,0 +1,15 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class NitroStickerPacks
{
[JsonProperty("sticker_packs")]
public List<StickerPack> StickerPacks { get; set; }
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/Sticker.cs View File

@@ -21,5 +21,7 @@ namespace Discord.API
public string PreviewAsset { get; set; }
[JsonProperty("format_type")]
public StickerFormatType FormatType { get; set; }
[JsonProperty("user")]
public Optional<User> User { get; set; }
}
}

+ 21
- 0
src/Discord.Net.Rest/API/Common/StickerItem.cs View File

@@ -0,0 +1,21 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class StickerItem
{
[JsonProperty("id")]
public ulong Id { get; set; }

[JsonProperty("name")]
public string Name { get; set; }

[JsonProperty("format_type")]
public StickerFormatType FormatType { get; set; }
}
}

+ 27
- 0
src/Discord.Net.Rest/API/Common/StickerPack.cs View File

@@ -0,0 +1,27 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API
{
internal class StickerPack
{
[JsonProperty("id")]
public ulong Id { get; set; }
[JsonProperty("stickers")]
public Sticker[] Stickers { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("sku_id")]
public ulong SkuId { get; set; }
[JsonProperty("cover_sticker_id")]
public Optional<ulong> CoverStickerId { get; set; }
[JsonProperty("description")]
public string Description { get; set; }
[JsonProperty("banner_asset_id")]
public ulong BannerAssetId { get; set; }
}
}

+ 32
- 0
src/Discord.Net.Rest/API/Rest/CreateStickerParams.cs View File

@@ -0,0 +1,32 @@
using Discord.Net.Rest;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class CreateStickerParams
{
public Stream File { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Tags { get; set; }

public IReadOnlyDictionary<string, object> ToDictionary()
{
var d = new Dictionary<string, object>();

d["file"] = new MultipartFile(File, Name);

d["name"] = Name;
d["description"] = Description;
d["tags"] = Tags;

return d;
}
}
}

+ 19
- 0
src/Discord.Net.Rest/API/Rest/ModifyStickerParams.cs View File

@@ -0,0 +1,19 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord.API.Rest
{
internal class ModifyStickerParams
{
[JsonProperty("name")]
public Optional<string> Name { get; set; }
[JsonProperty("description")]
public Optional<string> Description { get; set; }
[JsonProperty("tags")]
public Optional<string> Tags { get; set; }
}
}

+ 87
- 0
src/Discord.Net.Rest/DiscordRestApiClient.cs View File

@@ -886,6 +886,67 @@ namespace Discord.API
return await SendJsonAsync<Message>("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false);
}

// Stickers
public async Task<Sticker> GetStickerAsync(ulong id, RequestOptions options = null)
{
Preconditions.NotEqual(id, 0, nameof(id));

options = RequestOptions.CreateOrClone(options);

return await NullifyNotFound(SendAsync<Sticker>("GET", () => $"stickers/{id}", new BucketIds(), options: options)).ConfigureAwait(false);
}
public async Task<Sticker> GetGuildStickerAsync(ulong guildId, ulong id, RequestOptions options = null)
{
Preconditions.NotEqual(id, 0, nameof(id));
Preconditions.NotEqual(guildId, 0, nameof(guildId));

options = RequestOptions.CreateOrClone(options);

return await NullifyNotFound(SendAsync<Sticker>("GET", () => $"guilds/{guildId}/stickers/{id}", new BucketIds(guildId), options: options)).ConfigureAwait(false);
}
public async Task<Sticker[]> ListGuildStickersAsync(ulong guildId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));

options = RequestOptions.CreateOrClone(options);

return await SendAsync<Sticker[]>("GET", () => $"guilds/{guildId}/stickers", new BucketIds(guildId), options: options).ConfigureAwait(false);
}
public async Task<NitroStickerPacks> ListNitroStickerPacksAsync(RequestOptions options = null)
{
options = RequestOptions.CreateOrClone(options);

return await SendAsync<NitroStickerPacks>("GET", () => $"sticker-packs", new BucketIds(), options: options).ConfigureAwait(false);
}
public async Task<Sticker> CreateGuildStickerAsync(CreateStickerParams args, ulong guildId, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(guildId, 0, nameof(guildId));

options = RequestOptions.CreateOrClone(options);

return await SendMultipartAsync<Sticker>("POST", () => $"guilds/{guildId}/stickers", args.ToDictionary(), new BucketIds(guildId), options: options).ConfigureAwait(false);
}
public async Task<Sticker> ModifyStickerAsync(ModifyStickerParams args, ulong guildId, ulong stickerId, RequestOptions options = null)
{
Preconditions.NotNull(args, nameof(args));
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(stickerId, 0, nameof(stickerId));

options = RequestOptions.CreateOrClone(options);

return await SendJsonAsync<Sticker>("PATCH", () => $"guilds/{guildId}/stickers/{stickerId}", args, new BucketIds(guildId), options: options).ConfigureAwait(false);
}
public async Task DeleteStickerAsync(ulong guildId, ulong stickerId, RequestOptions options = null)
{
Preconditions.NotEqual(guildId, 0, nameof(guildId));
Preconditions.NotEqual(stickerId, 0, nameof(stickerId));

options = RequestOptions.CreateOrClone(options);

await SendAsync("DELETE", () => $"guilds/{guildId}/stickers/{stickerId}", new BucketIds(guildId), options: options).ConfigureAwait(false);
}

public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null)
{
Preconditions.NotEqual(channelId, 0, nameof(channelId));
@@ -2002,6 +2063,32 @@ namespace Discord.API
}
}

protected async Task<T> NullifyNotFound<T>(Task<T> sendTask) where T : class
{
try
{
var result = await sendTask.ConfigureAwait(false);

if (sendTask.Exception != null)
{
if (sendTask.Exception.InnerException is HttpException x)
{
if (x.HttpCode == HttpStatusCode.NotFound)
{
return null;
}
}

throw sendTask.Exception;
}
else
return result;
}
catch (HttpException x) when (x.HttpCode == HttpStatusCode.NotFound)
{
return null;
}
}
internal class BucketIds
{
public ulong GuildId { get; internal set; }


+ 47
- 0
src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs View File

@@ -535,5 +535,52 @@ namespace Discord.Rest
}
public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options)
=> client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options);

public static async Task<API.Sticker> CreateStickerAsync(BaseDiscordClient client, IGuild guild, string name, string description, IEnumerable<string> tags,
Image image, RequestOptions options = null)
{
Preconditions.NotNull(name, nameof(name));
Preconditions.NotNull(description, nameof(description));

Preconditions.AtLeast(name.Length, 2, nameof(name));
Preconditions.AtLeast(description.Length, 2, nameof(description));

Preconditions.AtMost(name.Length, 30, nameof(name));
Preconditions.AtMost(description.Length, 100, nameof(name));

var apiArgs = new CreateStickerParams()
{
Name = name,
Description = description,
File = image.Stream,
Tags = string.Join(", ", tags)
};

return await client.ApiClient.CreateGuildStickerAsync(apiArgs, guild.Id, options).ConfigureAwait(false);
}

public static async Task<API.Sticker> ModifyStickerAsync(BaseDiscordClient client, IGuild guild, ISticker sticker, Action<StickerProperties> func,
RequestOptions options = null)
{
if (func == null)
throw new ArgumentNullException(paramName: nameof(func));

var props = new StickerProperties();
func(props);

var apiArgs = new ModifyStickerParams()
{
Description = props.Description,
Name = props.Name,
Tags = props.Tags.IsSpecified ?
string.Join(", ", props.Tags.Value) :
Optional<string>.Unspecified
};

return await client.ApiClient.ModifyStickerAsync(apiArgs, guild.Id, sticker.Id, options).ConfigureAwait(false);
}

public static async Task DeleteStickerAsync(BaseDiscordClient client, IGuild guild, ISticker sticker, RequestOptions options = null)
=> await client.ApiClient.DeleteStickerAsync(guild.Id, sticker.Id, options).ConfigureAwait(false);
}
}

+ 2
- 1
src/Discord.Net.Rest/Entities/Messages/Sticker.cs View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Model = Discord.API.Sticker;

namespace Discord
@@ -39,7 +40,7 @@ namespace Discord
internal static Sticker Create(Model model)
{
return new Sticker(model.Id, model.PackId, model.Name, model.Desription,
model.Tags.IsSpecified ? model.Tags.Value.Split(',') : new string[0],
model.Tags.IsSpecified ? model.Tags.Value.Split(',').Select(x => x.Trim()).ToArray() : new string[0],
model.Asset, model.PreviewAsset, model.FormatType);
}



+ 46
- 0
src/Discord.Net.Rest/Entities/Messages/StickerItem.cs View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.StickerItem;

namespace Discord.Rest
{
/// <summary>
/// Represents a partial sticker received in a message.
/// </summary>
public class StickerItem : RestEntity<ulong>
{
/// <summary>
/// The name of this sticker.
/// </summary>
public readonly string Name;

/// <summary>
/// The format of this sticker.
/// </summary>
public readonly StickerFormatType Format;

internal StickerItem(BaseDiscordClient client, Model model)
: base(client, model.Id)
{
this.Name = model.Name;
this.Format = model.FormatType;
}

/// <summary>
/// Resolves this sticker item by fetching the <see cref="Sticker"/> from the API.
/// </summary>
/// <returns>
/// A task representing the download operation, the result of the task is a sticker object.
/// </returns>

public async Task<Sticker> ResolveStickerAsync()
{
var model = await Discord.ApiClient.GetStickerAsync(this.Id);

return Sticker.Create(model);
}
}
}

+ 150
- 1
src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs View File

@@ -19,6 +19,7 @@ using PresenceModel = Discord.API.Presence;
using RoleModel = Discord.API.Role;
using UserModel = Discord.API.User;
using VoiceStateModel = Discord.API.VoiceState;
using StickerModel = Discord.API.Sticker;

namespace Discord.WebSocket
{
@@ -36,7 +37,9 @@ namespace Discord.WebSocket
private ConcurrentDictionary<ulong, SocketGuildUser> _members;
private ConcurrentDictionary<ulong, SocketRole> _roles;
private ConcurrentDictionary<ulong, SocketVoiceState> _voiceStates;
private ConcurrentDictionary<ulong, SocketCustomSticker> _stickers;
private ImmutableArray<GuildEmote> _emotes;

private ImmutableArray<string> _features;
private AudioClient _audioClient;
#pragma warning restore IDISP002, IDISP006
@@ -322,6 +325,11 @@ namespace Discord.WebSocket
}
/// <inheritdoc />
public IReadOnlyCollection<GuildEmote> Emotes => _emotes;
/// <summary>
/// Gets a collection of all custom stickers for this guild.
/// </summary>
public IReadOnlyCollection<SocketCustomSticker> Stickers
=> _stickers.Select(x => x.Value).ToImmutableArray();
/// <inheritdoc />
public IReadOnlyCollection<string> Features => _features;
/// <summary>
@@ -440,6 +448,8 @@ namespace Discord.WebSocket
}
_voiceStates = voiceStates;


_syncPromise = new TaskCompletionSource<bool>();
_downloaderPromise = new TaskCompletionSource<bool>();
var _ = _syncPromise.TrySetResultAsync(true);
@@ -509,6 +519,23 @@ namespace Discord.WebSocket
}
}
_roles = roles;

if (model.Stickers != null)
{
var stickers = new ConcurrentDictionary<ulong, SocketCustomSticker>(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Stickers.Length * 1.05));
for (int i = 0; i < model.Stickers.Length; i++)
{
var sticker = model.Stickers[i];
if (sticker.User.IsSpecified)
AddOrUpdateUser(sticker.User.Value);

var entity = SocketCustomSticker.Create(Discord, sticker, this, sticker.User.IsSpecified ? sticker.User.Value.Id : null);

stickers.TryAdd(sticker.Id, entity);
}
}
else
_stickers = new ConcurrentDictionary<ulong, SocketCustomSticker>(ConcurrentHashSet.DefaultConcurrencyLevel, 7);
}
/*internal void Update(ClientState state, GuildSyncModel model) //TODO remove? userbot related
{
@@ -898,6 +925,33 @@ namespace Discord.WebSocket
return role;
}

internal SocketCustomSticker AddSticker(StickerModel model)
{
if (model.User.IsSpecified)
AddOrUpdateUser(model.User.Value);

var sticker = SocketCustomSticker.Create(Discord, model, this, model.User.IsSpecified ? model.User.Value.Id : null);
_stickers[model.Id] = sticker;
return sticker;
}

internal SocketCustomSticker AddOrUpdateSticker(StickerModel model)
{
if (_stickers.TryGetValue(model.Id, out SocketCustomSticker sticker))
_stickers[model.Id].Update(model);
else
sticker = AddSticker(model);

return sticker;
}

internal SocketCustomSticker RemoveSticker(ulong id)
{
if (_stickers.TryRemove(id, out SocketCustomSticker sticker))
return sticker;
return null;
}

//Users
/// <inheritdoc />
public Task<RestGuildUser> AddGuildUserAsync(ulong id, string accessToken, Action<AddGuildUserProperties> func = null, RequestOptions options = null)
@@ -1109,6 +1163,92 @@ namespace Discord.WebSocket
public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null)
=> GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options);

//Stickers
/// <summary>
/// Gets a specific sticker within this guild.
/// </summary>
/// <param name="id">The id of the sticker to get.</param>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains the sticker found with the
/// specified <paramref name="id"/>; <see langword="null" /> if none is found.
/// </returns>
public async ValueTask<SocketCustomSticker> GetStickerAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null)
{
var sticker = _stickers[id];

if (sticker != null)
return sticker;

if (mode == CacheMode.CacheOnly)
return null;

var model = await Discord.ApiClient.GetGuildStickerAsync(this.Id, id, options).ConfigureAwait(false);

if (model == null)
return null;

return AddOrUpdateSticker(model);
}
/// <summary>
/// Gets a collection of all stickers within this guild.
/// </summary>
/// <param name="mode">The <see cref="CacheMode" /> that determines whether the object should be fetched from cache.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous get operation. The task result contains a read-only collection
/// of stickers found within the guild.
/// </returns>
public async ValueTask<IReadOnlyCollection<SocketCustomSticker>> GetStickersAsync(CacheMode mode = CacheMode.AllowDownload,
RequestOptions options = null)
{
if (this.Stickers.Count > 0)
return this.Stickers;

if (mode == CacheMode.CacheOnly)
return ImmutableArray.Create<SocketCustomSticker>();

var models = await Discord.ApiClient.ListGuildStickersAsync(this.Id, options).ConfigureAwait(false);

List<SocketCustomSticker> stickers = new();

foreach (var model in models)
{
stickers.Add(AddOrUpdateSticker(model));
}

return stickers;
}
/// <summary>
/// Creates a new sticker in this guild.
/// </summary>
/// <param name="name">The name of the sticker.</param>
/// <param name="description">The description of the sticker.</param>
/// <param name="tags">The tags of the sticker.</param>
/// <param name="image">The image of the new emote.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous creation operation. The task result contains the created sticker.
/// </returns>
public async Task<SocketCustomSticker> CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image,
RequestOptions options = null)
{
var model = await GuildHelper.CreateStickerAsync(Discord, this, name, description, tags, image, options).ConfigureAwait(false);

return AddOrUpdateSticker(model);
}
/// <summary>
/// Deletes a sticker within this guild.
/// </summary>
/// <param name="sticker">The sticker to delete.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous removal operation.
/// </returns>
public Task DeleteStickerAsync(SocketCustomSticker sticker, RequestOptions options = null)
=> sticker.DeleteAsync(options);

//Voice States
internal async Task<SocketVoiceState> AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model)
{
@@ -1332,6 +1472,8 @@ namespace Discord.WebSocket
int? IGuild.ApproximateMemberCount => null;
/// <inheritdoc />
int? IGuild.ApproximatePresenceCount => null;
/// <inheritdoc />
IReadOnlyCollection<ICustomSticker> IGuild.Stickers => Stickers;

/// <inheritdoc />
async Task<IReadOnlyCollection<IBan>> IGuild.GetBansAsync(RequestOptions options)
@@ -1481,6 +1623,13 @@ namespace Discord.WebSocket
/// <inheritdoc />
async Task<IReadOnlyCollection<IApplicationCommand>> IGuild.GetApplicationCommandsAsync (RequestOptions options)
=> await GetApplicationCommandsAsync(options).ConfigureAwait(false);
async Task<ICustomSticker> IGuild.CreateStickerAsync(string name, string description, IEnumerable<string> tags, Image image, RequestOptions options)
=> await CreateStickerAsync(name, description, tags, image, options);
async Task<ICustomSticker> IGuild.GetStickerAsync(ulong id, CacheMode mode, RequestOptions options)
=> await GetStickerAsync(id, mode, options);
async Task<IReadOnlyCollection<ICustomSticker>> IGuild.GetStickersAsync(CacheMode mode, RequestOptions options)
=> await GetStickersAsync(mode, options);
Task IGuild.DeleteStickerAsync(ICustomSticker sticker, RequestOptions options) => throw new NotImplementedException();

void IDisposable.Dispose()
{
@@ -1489,6 +1638,6 @@ namespace Discord.WebSocket
_audioClient?.Dispose();
}

}
}

+ 73
- 0
src/Discord.Net.WebSocket/Entities/Messages/SocketCustomSticker.cs View File

@@ -0,0 +1,73 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Sticker;


namespace Discord.WebSocket
{
public class SocketCustomSticker : SocketSticker, ICustomSticker
{
/// <summary>
/// Gets the user that uploaded the guild sticker.
/// </summary>
/// <remarks>
/// <note>
/// This may return <see langword="null"/> in the WebSocket implementation due to incomplete user collection in
/// large guilds, or the bot doesnt have the MANAGE_EMOJIS_AND_STICKERS permission.
/// </note>
/// </remarks>
public SocketGuildUser Author
=> this.AuthorId.HasValue ? Guild.GetUser(this.AuthorId.Value) : null;

/// <summary>
/// Gets the guild the sticker lives in.
/// </summary>
public SocketGuild Guild { get; }

/// <inheritdoc/>
public ulong? AuthorId { get; set; }

internal SocketCustomSticker(DiscordSocketClient client, ulong id, SocketGuild guild, ulong? authorId = null)
: base(client, id)
{
this.Guild = guild;
this.AuthorId = authorId;
}

internal static SocketCustomSticker Create(DiscordSocketClient client, Model model, SocketGuild guild, ulong? authorId = null)
{
var entity = new SocketCustomSticker(client, model.Id, guild, authorId);
entity.Update(model);
return entity;
}

/// <inheritdoc/>
public async Task ModifyAsync(Action<StickerProperties> func, RequestOptions options = null)
{
if(!Guild.CurrentUser.GuildPermissions.Has(GuildPermission.ManageEmojisAndStickers))
throw new InvalidOperationException($"Missing permission {nameof(GuildPermission.ManageEmojisAndStickers)}");

var model = await GuildHelper.ModifyStickerAsync(this.Discord, this.Guild, this, func, options);

this.Update(model);
}

/// <inheritdoc/>
public async Task DeleteAsync(RequestOptions options = null)
{
await GuildHelper.DeleteStickerAsync(Discord, Guild, this, options);
Guild.RemoveSticker(this.Id);
}

// ICustomSticker
ulong? ICustomSticker.AuthorId
=> this.AuthorId;

IGuild ICustomSticker.Guild
=> this.Guild;
}
}

+ 68
- 0
src/Discord.Net.WebSocket/Entities/Messages/SocketSticker.cs View File

@@ -0,0 +1,68 @@
using Discord.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Model = Discord.API.Sticker;

namespace Discord.WebSocket
{
public class SocketSticker : SocketEntity<ulong>, ISticker
{
/// <inheritdoc/>
public ulong PackId { get; private set; }

/// <inheritdoc/>
public string Name { get; private set; }

/// <inheritdoc/>
public string Description { get; private set; }

/// <inheritdoc/>
public IReadOnlyCollection<string> Tags { get; private set; }

/// <inheritdoc/>
public string Asset { get; private set; }

/// <inheritdoc/>
public string PreviewAsset { get; private set; }

/// <inheritdoc/>
public StickerFormatType FormatType { get; private set; }

/// <inheritdoc/>
public string GetStickerUrl()
=> CDN.GetStickerUrl(this.Id, this.FormatType);

internal SocketSticker(DiscordSocketClient client, ulong id)
: base(client, id) { }

internal static SocketSticker Create(DiscordSocketClient client, Model model)
{
var entity = new SocketSticker(client, model.Id);
entity.Update(model);
return entity;
}

internal virtual void Update(Model model)
{
this.Name = model.Name;
this.Description = model.Desription;
this.PackId = model.PackId;
this.Asset = model.Asset;
this.PreviewAsset = model.PreviewAsset;
this.FormatType = model.FormatType;

if (model.Tags.IsSpecified)
{
this.Tags = model.Tags.Value.Split(',').Select(x => x.Trim()).ToImmutableArray();
}
else
{
this.Tags = ImmutableArray<string>.Empty;
}
}
}
}

Loading…
Cancel
Save