@@ -78,17 +78,14 @@ namespace Discord | |||||
}; | }; | ||||
} | } | ||||
public static IEmojiModel ToModel<TModel>(this IEmote emote) where TModel : IEmojiModel, new() | |||||
public static IEmojiModel ToModel(this IEmote emote, IEmojiModel model) | |||||
{ | { | ||||
if (emote == null) | if (emote == null) | ||||
return null; | return null; | ||||
var model = new TModel() | |||||
{ | |||||
Name = emote.Name | |||||
}; | |||||
model.Name = emote.Name; | |||||
if(emote is GuildEmote guildEmote) | |||||
if (emote is GuildEmote guildEmote) | |||||
{ | { | ||||
model.Id = guildEmote.Id; | model.Id = guildEmote.Id; | ||||
model.IsAnimated = guildEmote.Animated; | model.IsAnimated = guildEmote.Animated; | ||||
@@ -99,7 +96,7 @@ namespace Discord | |||||
model.Roles = guildEmote.RoleIds.ToArray(); | model.Roles = guildEmote.RoleIds.ToArray(); | ||||
} | } | ||||
if(emote is Emote e) | |||||
if (emote is Emote e) | |||||
{ | { | ||||
model.IsAnimated = e.Animated; | model.IsAnimated = e.Animated; | ||||
model.Id = e.Id; | model.Id = e.Id; | ||||
@@ -107,5 +104,13 @@ namespace Discord | |||||
return model; | return model; | ||||
} | } | ||||
public static IEmojiModel ToModel<TModel>(this IEmote emote) where TModel : IEmojiModel, new() | |||||
{ | |||||
if (emote == null) | |||||
return null; | |||||
return emote.ToModel(new TModel()); | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,16 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IPartialApplicationModel : IEntityModel<ulong> | |||||
{ | |||||
string Name { get; set; } | |||||
string Icon { get; set; } | |||||
string Description { get; set; } | |||||
string CoverImage { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IMessageComponentModel | |||||
{ | |||||
ComponentType Type { get; set; } | |||||
string CustomId { get; set; } | |||||
bool? Disabled { get; set; } | |||||
ButtonStyle? Style { get; set; } | |||||
string Label { get; set; } | |||||
// emoji | |||||
ulong? EmojiId { get; set; } | |||||
string EmojiName { get; set; } | |||||
bool? EmojiAnimated { get; set; } | |||||
string Url { get; set; } | |||||
IMessageComponentOptionModel[] Options { get; set; } | |||||
string Placeholder { get; set; } | |||||
int? MinValues { get; set; } | |||||
int? MaxValues { get; set; } | |||||
IMessageComponentModel[] Components { get; set; } | |||||
int? MinLength { get; set; } | |||||
int? MaxLength { get; set; } | |||||
bool? Required { get; set; } | |||||
string Value { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IMessageComponentOptionModel | |||||
{ | |||||
string Label { get; set; } | |||||
string Value { get; set; } | |||||
string Description { get; set; } | |||||
// emoji | |||||
ulong? EmojiId { get; set; } | |||||
string EmojiName { get; set; } | |||||
bool? EmojiAnimated { get; set; } | |||||
bool? Default { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IAttachmentModel : IEntityModel<ulong> | |||||
{ | |||||
string FileName { get; set; } | |||||
string Description { get; set; } | |||||
string ContentType { get; set; } | |||||
int Size { get; set; } | |||||
string Url { get; set; } | |||||
string ProxyUrl { get; set; } | |||||
int? Height { get; set; } | |||||
int? Width { get; set; } | |||||
bool Ephemeral { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,44 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IEmbedModel | |||||
{ | |||||
string Title { get; set; } | |||||
EmbedType Type { get; set; } | |||||
string Description { get; set; } | |||||
string Url { get; set; } | |||||
long? Timestamp { get; set; } | |||||
uint? Color { get; set; } | |||||
string FooterText { get; set; } | |||||
string FooterIconUrl { get; set; } | |||||
string FooterProxyUrl { get; set; } | |||||
string ProviderName { get; set; } | |||||
string ProviderUrl { get; set; } | |||||
string AuthorName { get; set; } | |||||
string AuthorUrl { get; set; } | |||||
string AuthorIconUrl { get; set; } | |||||
string AuthorProxyIconUrl { get; set; } | |||||
IEmbedMediaModel Image { get; set; } | |||||
IEmbedMediaModel Thumbnail { get; set; } | |||||
IEmbedMediaModel Video { get; set; } | |||||
IEmbedFieldModel[] Fields { get; set; } | |||||
} | |||||
public interface IEmbedMediaModel | |||||
{ | |||||
string Url { get; set; } | |||||
string ProxyUrl { get; set; } | |||||
int? Height { get; set; } | |||||
int? Width { get; set; } | |||||
} | |||||
public interface IEmbedFieldModel | |||||
{ | |||||
string Name { get; set; } | |||||
string Value { get; set; } | |||||
bool Inline { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IMessageActivityModel | |||||
{ | |||||
MessageActivityType? Type { get; set; } | |||||
string PartyId { get; set; } | |||||
} | |||||
} |
@@ -14,11 +14,35 @@ namespace Discord | |||||
ulong AuthorId { get; set; } | ulong AuthorId { get; set; } | ||||
bool IsWebhookMessage { get; set; } | bool IsWebhookMessage { get; set; } | ||||
string Content { get; set; } | string Content { get; set; } | ||||
DateTimeOffset Timestamp { get; set; } | |||||
DateTimeOffset? EditedTimestamp { get; set; } | |||||
long Timestamp { get; set; } | |||||
long? EditedTimestamp { get; set; } | |||||
bool IsTextToSpeech { get; set; } | bool IsTextToSpeech { get; set; } | ||||
bool MentionEveryone { get; set; } | bool MentionEveryone { get; set; } | ||||
ulong[] UserMentionIds { get; set; } | ulong[] UserMentionIds { get; set; } | ||||
ulong[] RoleMentionIds { get; set; } | ulong[] RoleMentionIds { get; set; } | ||||
IAttachmentModel[] Attachments { get; set; } | |||||
IEmbedModel[] Embeds { get; set; } | |||||
IReactionMetadataModel[] Reactions { get; set; } | |||||
bool Pinned { get; set; } | |||||
IMessageActivityModel Activity { get; set; } | |||||
IPartialApplicationModel Application { get; set; } | |||||
ulong? ApplicationId { get; set; } | |||||
// message reference | |||||
ulong? ReferenceMessageId { get; set; } | |||||
ulong? ReferenceMessageChannelId { get; set; } | |||||
ulong? ReferenceMessageGuildId { get; set; } | |||||
MessageFlags Flags { get; set; } | |||||
// interaction | |||||
ulong? InteractionId { get; set; } | |||||
string InteractionName { get; set; } | |||||
InteractionType? InteractionType { get; set; } | |||||
ulong? InteractionUserId { get; set; } | |||||
IMessageComponentModel[] Components { get; set; } | |||||
IStickerItemModel[] Stickers { get; set; } | |||||
} | } | ||||
} | } |
@@ -0,0 +1,14 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IReactionMetadataModel | |||||
{ | |||||
IEmojiModel Emoji { get; set; } | |||||
ulong[] Users { get; set; } | |||||
} | |||||
} |
@@ -0,0 +1,15 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public interface IStickerItemModel | |||||
{ | |||||
ulong Id { get; set; } | |||||
string Name { get; set; } | |||||
StickerFormatType Format { get; set; } | |||||
} | |||||
} |
@@ -46,6 +46,11 @@ namespace Discord | |||||
/// </returns> | /// </returns> | ||||
bool MentionedEveryone { get; } | bool MentionedEveryone { get; } | ||||
/// <summary> | /// <summary> | ||||
/// If the message is a <see cref="MessageType.ApplicationCommand"/> or application-owned webhook, | |||||
/// this is the id of the application. | |||||
/// </summary> | |||||
ulong? ApplicationId { get; } | |||||
/// <summary> | |||||
/// Gets the content for this message. | /// Gets the content for this message. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
@@ -10,7 +10,7 @@ namespace Discord | |||||
/// Represents a partial <see cref="IDiscordInteraction"/> within a message. | /// Represents a partial <see cref="IDiscordInteraction"/> within a message. | ||||
/// </summary> | /// </summary> | ||||
/// <typeparam name="TUser">The type of the user.</typeparam> | /// <typeparam name="TUser">The type of the user.</typeparam> | ||||
public class MessageInteraction<TUser> : IMessageInteraction where TUser : IUser | |||||
public class MessageInteraction<TUser> : IMessageInteraction where TUser : class, IUser | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Gets the snowflake id of the interaction. | /// Gets the snowflake id of the interaction. | ||||
@@ -30,14 +30,36 @@ namespace Discord | |||||
/// <summary> | /// <summary> | ||||
/// Gets the <typeparamref name="TUser"/> who invoked the interaction. | /// Gets the <typeparamref name="TUser"/> who invoked the interaction. | ||||
/// </summary> | /// </summary> | ||||
public TUser User { get; } | |||||
/// <remarks> | |||||
/// When this property is a SocketUser, the get accessor will attempt to preform a | |||||
/// synchronous cache lookup. | |||||
/// </remarks> | |||||
public TUser User | |||||
=> _user ?? (_userLookup != null ? _userLookup(UserId) : null); | |||||
/// <summary> | |||||
/// Gets the id of the user who invoked the interaction. | |||||
/// </summary> | |||||
public ulong UserId { get; } | |||||
private readonly TUser _user; | |||||
private readonly Func<ulong, TUser> _userLookup; | |||||
internal MessageInteraction(ulong id, InteractionType type, string name, TUser user) | internal MessageInteraction(ulong id, InteractionType type, string name, TUser user) | ||||
{ | { | ||||
Id = id; | Id = id; | ||||
Type = type; | Type = type; | ||||
Name = name; | Name = name; | ||||
User = user; | |||||
_user = user; | |||||
UserId = user.Id; | |||||
} | |||||
internal MessageInteraction(ulong id, InteractionType type, string name, ulong userId, Func<ulong, TUser> lookup) | |||||
{ | |||||
Id = id; | |||||
Type = type; | |||||
Name = name; | |||||
UserId = userId; | |||||
_userLookup = lookup; | |||||
} | } | ||||
IUser IMessageInteraction.User => User; | IUser IMessageInteraction.User => User; | ||||
@@ -56,5 +56,9 @@ namespace Discord | |||||
public static T? ToNullable<T>(this Optional<T> val) | public static T? ToNullable<T>(this Optional<T> val) | ||||
where T : struct | where T : struct | ||||
=> val.IsSpecified ? val.Value : null; | => val.IsSpecified ? val.Value : null; | ||||
public static Optional<T> ToOptional<T>(this T? value) | |||||
where T : struct | |||||
=> value.HasValue ? new Optional<T>(value.Value) : new(); | |||||
} | } | ||||
} | } |
@@ -3,7 +3,7 @@ using System.Linq; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class ActionRowComponent : IMessageComponent | |||||
internal class ActionRowComponent : IMessageComponent, IMessageComponentModel | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public ComponentType Type { get; set; } | public ComponentType Type { get; set; } | ||||
@@ -29,5 +29,27 @@ namespace Discord.API | |||||
[JsonIgnore] | [JsonIgnore] | ||||
string IMessageComponent.CustomId => null; | string IMessageComponent.CustomId => null; | ||||
ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentModel[] IMessageComponentModel.Components { get => Components.Select(x => x as IMessageComponentModel).ToArray(); set => throw new System.NotSupportedException(); } // cursed hack here | |||||
#region unused | |||||
string IMessageComponentModel.CustomId { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Disabled { get => null; set => throw new System.NotSupportedException(); } | |||||
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Label { get => null; set => throw new System.NotSupportedException(); } | |||||
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Placeholder { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); } | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class Attachment | |||||
internal class Attachment : IAttachmentModel | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
@@ -24,5 +24,16 @@ namespace Discord.API | |||||
public Optional<int> Width { get; set; } | public Optional<int> Width { get; set; } | ||||
[JsonProperty("ephemeral")] | [JsonProperty("ephemeral")] | ||||
public Optional<bool> Ephemeral { get; set; } | public Optional<bool> Ephemeral { get; set; } | ||||
string IAttachmentModel.FileName { get => Filename; set => throw new System.NotSupportedException(); } | |||||
string IAttachmentModel.Description { get => Description.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
string IAttachmentModel.ContentType { get => ContentType.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
int IAttachmentModel.Size { get => Size; set => throw new System.NotSupportedException(); } | |||||
string IAttachmentModel.Url { get => Url; set => throw new System.NotSupportedException(); } | |||||
string IAttachmentModel.ProxyUrl { get => ProxyUrl; set => throw new System.NotSupportedException(); } | |||||
int? IAttachmentModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
int? IAttachmentModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
bool IAttachmentModel.Ephemeral { get => Ephemeral.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
ulong IEntityModel<ulong>.Id { get => Id; set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class ButtonComponent : IMessageComponent | |||||
internal class ButtonComponent : IMessageComponent, IMessageComponentModel | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public ComponentType Type { get; set; } | public ComponentType Type { get; set; } | ||||
@@ -59,5 +59,27 @@ namespace Discord.API | |||||
[JsonIgnore] | [JsonIgnore] | ||||
string IMessageComponent.CustomId => CustomId.GetValueOrDefault(); | string IMessageComponent.CustomId => CustomId.GetValueOrDefault(); | ||||
ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.CustomId { get => CustomId.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Disabled { get => Disabled.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
ButtonStyle? IMessageComponentModel.Style { get => Style; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Label { get => Label.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
ulong? IMessageComponentModel.EmojiId { get => Emote.GetValueOrDefault()?.Id; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.EmojiName { get => Emote.GetValueOrDefault()?.Name; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.EmojiAnimated { get => Emote.GetValueOrDefault()?.Animated; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Url { get => Url.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
#region unused | |||||
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Placeholder { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); } | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -4,7 +4,7 @@ using Discord.Net.Converters; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class Embed | |||||
internal class Embed : IEmbedModel | |||||
{ | { | ||||
[JsonProperty("title")] | [JsonProperty("title")] | ||||
public string Title { get; set; } | public string Title { get; set; } | ||||
@@ -32,5 +32,15 @@ namespace Discord.API | |||||
public Optional<EmbedProvider> Provider { get; set; } | public Optional<EmbedProvider> Provider { get; set; } | ||||
[JsonProperty("fields")] | [JsonProperty("fields")] | ||||
public Optional<EmbedField[]> Fields { get; set; } | public Optional<EmbedField[]> Fields { get; set; } | ||||
EmbedType IEmbedModel.Type { get => Type; set => throw new NotSupportedException(); } | |||||
DateTimeOffset? IEmbedModel.Timestamp { get => Timestamp; set => throw new NotSupportedException(); } | |||||
IEmbedFooterModel IEmbedModel.Footer { get => Footer.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedMediaModel IEmbedModel.Image { get => Image.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedMediaModel IEmbedModel.Thumbnail { get => Thumbnail.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedMediaModel IEmbedModel.Video { get => Video.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedProviderModel IEmbedModel.Provider { get => Provider.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedAuthorModel IEmbedModel.Author { get => Author.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IEmbedFieldModel[] IEmbedModel.Fields { get => Fields.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedAuthor | |||||
internal class EmbedAuthor : IEmbedAuthorModel | |||||
{ | { | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
@@ -1,8 +1,8 @@ | |||||
using Newtonsoft.Json; | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedField | |||||
internal class EmbedField : IEmbedFieldModel | |||||
{ | { | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedFooter | |||||
internal class EmbedFooter : IEmbedFooterModel | |||||
{ | { | ||||
[JsonProperty("text")] | [JsonProperty("text")] | ||||
public string Text { get; set; } | public string Text { get; set; } | ||||
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedImage | |||||
internal class EmbedImage : IEmbedMediaModel | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
@@ -12,5 +12,8 @@ namespace Discord.API | |||||
public Optional<int> Height { get; set; } | public Optional<int> Height { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public Optional<int> Width { get; set; } | public Optional<int> Width { get; set; } | ||||
int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedProvider | |||||
internal class EmbedProvider : IEmbedProviderModel | |||||
{ | { | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedThumbnail | |||||
internal class EmbedThumbnail : IEmbedMediaModel | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
@@ -12,5 +12,8 @@ namespace Discord.API | |||||
public Optional<int> Height { get; set; } | public Optional<int> Height { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public Optional<int> Width { get; set; } | public Optional<int> Width { get; set; } | ||||
int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,13 +2,18 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class EmbedVideo | |||||
internal class EmbedVideo : IEmbedMediaModel | |||||
{ | { | ||||
[JsonProperty("url")] | [JsonProperty("url")] | ||||
public string Url { get; set; } | public string Url { get; set; } | ||||
[JsonProperty("proxy_url")] | |||||
public string ProxyUrl { get; set; } | |||||
[JsonProperty("height")] | [JsonProperty("height")] | ||||
public Optional<int> Height { get; set; } | public Optional<int> Height { get; set; } | ||||
[JsonProperty("width")] | [JsonProperty("width")] | ||||
public Optional<int> Width { get; set; } | public Optional<int> Width { get; set; } | ||||
int? IEmbedMediaModel.Height { get => Height.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
int? IEmbedMediaModel.Width { get => Width.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -1,9 +1,10 @@ | |||||
using Newtonsoft.Json; | using Newtonsoft.Json; | ||||
using System; | using System; | ||||
using System.Linq; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class Message | |||||
internal class Message : IMessageModel | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
@@ -49,6 +50,8 @@ namespace Discord.API | |||||
// sent with Rich Presence-related chat embeds | // sent with Rich Presence-related chat embeds | ||||
[JsonProperty("application")] | [JsonProperty("application")] | ||||
public Optional<MessageApplication> Application { get; set; } | public Optional<MessageApplication> Application { get; set; } | ||||
[JsonProperty("application_id")] | |||||
public Optional<ulong> ApplicationId { get; set; } | |||||
[JsonProperty("message_reference")] | [JsonProperty("message_reference")] | ||||
public Optional<MessageReference> Reference { get; set; } | public Optional<MessageReference> Reference { get; set; } | ||||
[JsonProperty("flags")] | [JsonProperty("flags")] | ||||
@@ -62,5 +65,35 @@ namespace Discord.API | |||||
public Optional<MessageInteraction> Interaction { get; set; } | public Optional<MessageInteraction> Interaction { get; set; } | ||||
[JsonProperty("sticker_items")] | [JsonProperty("sticker_items")] | ||||
public Optional<StickerItem[]> StickerItems { get; set; } | public Optional<StickerItem[]> StickerItems { get; set; } | ||||
MessageType IMessageModel.Type { get => Type; set => throw new NotSupportedException(); } | |||||
ulong IMessageModel.ChannelId { get => ChannelId; set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.GuildId { get => GuildId.ToNullable(); set => throw new NotSupportedException(); } | |||||
ulong IMessageModel.AuthorId { get => Author.IsSpecified ? Author.Value.Id : Member.IsSpecified ? Member.Value.User.Id : WebhookId.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
bool IMessageModel.IsWebhookMessage { get => WebhookId.IsSpecified; set => throw new NotSupportedException(); } | |||||
string IMessageModel.Content { get => Content.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
DateTimeOffset IMessageModel.Timestamp { get => Timestamp.Value; set => throw new NotSupportedException(); } // might break? | |||||
DateTimeOffset? IMessageModel.EditedTimestamp { get => Timestamp.ToNullable(); set => throw new NotSupportedException(); } | |||||
bool IMessageModel.IsTextToSpeech { get => IsTextToSpeech.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
bool IMessageModel.MentionEveryone { get => MentionEveryone.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
ulong[] IMessageModel.UserMentionIds { get => UserMentions.IsSpecified ? UserMentions.Value.Select(x => x.Id).ToArray() : Array.Empty<ulong>(); set => throw new NotSupportedException(); } | |||||
IAttachmentModel[] IMessageModel.Attachments { get => Attachments.GetValueOrDefault(Array.Empty<Attachment>()); set => throw new NotSupportedException(); } | |||||
IEmbedModel[] IMessageModel.Embeds { get => Embeds.GetValueOrDefault(Array.Empty<Embed>()); set => throw new NotSupportedException(); } | |||||
IReactionMetadataModel[] IMessageModel.Reactions { get => Reactions.GetValueOrDefault(Array.Empty<Reaction>()); set => throw new NotSupportedException(); } | |||||
bool IMessageModel.Pinned { get => Pinned.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IMessageActivityModel IMessageModel.Activity { get => Activity.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
IPartialApplicationModel IMessageModel.Application { get => Application.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.ApplicationId { get => ApplicationId.ToNullable(); set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.ReferenceMessageId { get => ReferencedMessage.GetValueOrDefault()?.Id; set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.ReferenceMessageChannelId { get => ReferencedMessage.GetValueOrDefault()?.ChannelId; set => throw new NotSupportedException(); } | |||||
MessageFlags IMessageModel.Flags { get => Flags.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.InteractionId { get => Interaction.GetValueOrDefault()?.Id; set => throw new NotSupportedException(); } | |||||
string IMessageModel.InteractionName { get => Interaction.GetValueOrDefault()?.Name; set => throw new NotSupportedException(); } | |||||
InteractionType? IMessageModel.InteractionType { get => Interaction.GetValueOrDefault()?.Type; set => throw new NotSupportedException(); } | |||||
ulong? IMessageModel.InteractionUserId { get => Interaction.GetValueOrDefault()?.User.Id; set => throw new NotSupportedException(); } | |||||
IMessageComponentModel[] IMessageModel.Components { get => Components.GetValueOrDefault(Array.Empty<ActionRowComponent>()); set => throw new NotSupportedException(); } | |||||
IStickerItemModel[] IMessageModel.Stickers { get => StickerItems.GetValueOrDefault(Array.Empty<StickerItem>()); set => throw new NotSupportedException(); } | |||||
ulong IEntityModel<ulong>.Id { get => Id; set => throw new NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -7,11 +7,14 @@ using System.Threading.Tasks; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
public class MessageActivity | |||||
public class MessageActivity : IMessageActivityModel | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public Optional<MessageActivityType> Type { get; set; } | public Optional<MessageActivityType> Type { get; set; } | ||||
[JsonProperty("party_id")] | [JsonProperty("party_id")] | ||||
public Optional<string> PartyId { get; set; } | public Optional<string> PartyId { get; set; } | ||||
MessageActivityType? IMessageActivityModel.Type { get => Type.ToNullable(); set => throw new NotSupportedException(); } | |||||
string IMessageActivityModel.PartyId { get => PartyId.GetValueOrDefault(); set => throw new NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -7,7 +7,7 @@ using System.Threading.Tasks; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
public class MessageApplication | |||||
internal class MessageApplication : IPartialApplicationModel | |||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Gets the snowflake ID of the application. | /// Gets the snowflake ID of the application. | ||||
@@ -34,5 +34,10 @@ namespace Discord.API | |||||
/// </summary> | /// </summary> | ||||
[JsonProperty("name")] | [JsonProperty("name")] | ||||
public string Name { get; set; } | public string Name { get; set; } | ||||
string IPartialApplicationModel.CoverImage { get => CoverImage; set => throw new NotSupportedException(); } | |||||
string IPartialApplicationModel.Icon { get => Icon; set => throw new NotSupportedException(); } | |||||
string IPartialApplicationModel.Name { get => Name; set => throw new NotSupportedException(); } | |||||
ulong IEntityModel<ulong>.Id { get => Id; set => throw new NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -1,8 +1,8 @@ | |||||
using Newtonsoft.Json; | |||||
using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class Reaction | |||||
internal class Reaction : IReactionMetadataModel | |||||
{ | { | ||||
[JsonProperty("count")] | [JsonProperty("count")] | ||||
public int Count { get; set; } | public int Count { get; set; } | ||||
@@ -10,5 +10,9 @@ namespace Discord.API | |||||
public bool Me { get; set; } | public bool Me { get; set; } | ||||
[JsonProperty("emoji")] | [JsonProperty("emoji")] | ||||
public Emoji Emoji { get; set; } | public Emoji Emoji { get; set; } | ||||
int IReactionMetadataModel.Count { get => Count; set => throw new System.NotSupportedException(); } | |||||
bool IReactionMetadataModel.Me { get => Me; set => throw new System.NotSupportedException(); } | |||||
IEmojiModel IReactionMetadataModel.Emoji { get => Emoji; set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -3,7 +3,7 @@ using System.Linq; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class SelectMenuComponent : IMessageComponent | |||||
internal class SelectMenuComponent : IMessageComponent, IMessageComponentModel | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public ComponentType Type { get; set; } | public ComponentType Type { get; set; } | ||||
@@ -28,6 +28,7 @@ namespace Discord.API | |||||
[JsonProperty("values")] | [JsonProperty("values")] | ||||
public Optional<string[]> Values { get; set; } | public Optional<string[]> Values { get; set; } | ||||
public SelectMenuComponent() { } | public SelectMenuComponent() { } | ||||
public SelectMenuComponent(Discord.SelectMenuComponent component) | public SelectMenuComponent(Discord.SelectMenuComponent component) | ||||
@@ -40,5 +41,27 @@ namespace Discord.API | |||||
MaxValues = component.MaxValues; | MaxValues = component.MaxValues; | ||||
Disabled = component.IsDisabled; | Disabled = component.IsDisabled; | ||||
} | } | ||||
ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.CustomId { get => CustomId; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Disabled { get => Disabled; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => Options; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Placeholder { get => Placeholder.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinValues { get => MinValues; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxValues { get => MaxValues; set => throw new System.NotSupportedException(); } | |||||
#region unused | |||||
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Label { get => null; set => throw new System.NotSupportedException(); } | |||||
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinLength { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxLength { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Required { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Value { get => null; set => throw new System.NotSupportedException(); } | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class SelectMenuOption | |||||
internal class SelectMenuOption : IMessageComponentOptionModel | |||||
{ | { | ||||
[JsonProperty("label")] | [JsonProperty("label")] | ||||
public string Label { get; set; } | public string Label { get; set; } | ||||
@@ -49,5 +49,13 @@ namespace Discord.API | |||||
Default = option.IsDefault ?? Optional<bool>.Unspecified; | Default = option.IsDefault ?? Optional<bool>.Unspecified; | ||||
} | } | ||||
string IMessageComponentOptionModel.Label { get => Label; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentOptionModel.Value { get => Value; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentOptionModel.Description { get => Description.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
ulong? IMessageComponentOptionModel.EmojiId { get => Emoji.GetValueOrDefault()?.Id; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentOptionModel.EmojiName { get => Emoji.GetValueOrDefault()?.Name; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentOptionModel.EmojiAnimated { get => Emoji.GetValueOrDefault()?.Animated; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentOptionModel.Default { get => Default.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class StickerItem | |||||
internal class StickerItem : IStickerItemModel | |||||
{ | { | ||||
[JsonProperty("id")] | [JsonProperty("id")] | ||||
public ulong Id { get; set; } | public ulong Id { get; set; } | ||||
@@ -12,5 +12,10 @@ namespace Discord.API | |||||
[JsonProperty("format_type")] | [JsonProperty("format_type")] | ||||
public StickerFormatType FormatType { get; set; } | public StickerFormatType FormatType { get; set; } | ||||
ulong IStickerItemModel.Id { get => Id; set => throw new System.NotSupportedException(); } | |||||
string IStickerItemModel.Name { get => Name; set => throw new System.NotSupportedException(); } | |||||
StickerFormatType IStickerItemModel.Format { get => FormatType; set => throw new System.NotSupportedException(); } | |||||
} | } | ||||
} | } |
@@ -2,7 +2,7 @@ using Newtonsoft.Json; | |||||
namespace Discord.API | namespace Discord.API | ||||
{ | { | ||||
internal class TextInputComponent : IMessageComponent | |||||
internal class TextInputComponent : IMessageComponent, IMessageComponentModel | |||||
{ | { | ||||
[JsonProperty("type")] | [JsonProperty("type")] | ||||
public ComponentType Type { get; set; } | public ComponentType Type { get; set; } | ||||
@@ -45,5 +45,29 @@ namespace Discord.API | |||||
Required = component.Required ?? Optional<bool>.Unspecified; | Required = component.Required ?? Optional<bool>.Unspecified; | ||||
Value = component.Value ?? Optional<string>.Unspecified; | Value = component.Value ?? Optional<string>.Unspecified; | ||||
} | } | ||||
ComponentType IMessageComponentModel.Type { get => Type; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.CustomId { get => CustomId; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinLength { get => MinLength.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxLength { get => MaxLength.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.Required { get => Required.ToNullable(); set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Value { get => Value.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Label { get => Label; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Placeholder { get => Placeholder.GetValueOrDefault(); set => throw new System.NotSupportedException(); } | |||||
#region unused | |||||
bool? IMessageComponentModel.Disabled { get => null; set => throw new System.NotSupportedException(); } | |||||
ButtonStyle? IMessageComponentModel.Style { get => null; set => throw new System.NotSupportedException(); } | |||||
ulong? IMessageComponentModel.EmojiId { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.EmojiName { get => null; set => throw new System.NotSupportedException(); } | |||||
bool? IMessageComponentModel.EmojiAnimated { get => null; set => throw new System.NotSupportedException(); } | |||||
string IMessageComponentModel.Url { get => null; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MinValues { get => null; set => throw new System.NotSupportedException(); } | |||||
int? IMessageComponentModel.MaxValues { get => null; set => throw new System.NotSupportedException(); } | |||||
IMessageComponentModel[] IMessageComponentModel.Components { get => null; set => throw new System.NotSupportedException(); } | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -46,6 +46,16 @@ namespace Discord.Rest | |||||
.Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); | .Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); | ||||
} | } | ||||
public static async Task<RestMessage> GetMessageAsync(BaseDiscordClient client, ulong channelId, ulong messageId, RequestOptions options) | |||||
{ | |||||
var channel = await GetChannelAsync(client, channelId, options).ConfigureAwait(false); | |||||
if (channel is not IRestMessageChannel msgChannel) | |||||
return null; | |||||
return await msgChannel.GetMessageAsync(messageId, options).ConfigureAwait(false); | |||||
} | |||||
public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) | public static async Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) | ||||
{ | { | ||||
var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); | var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); | ||||
@@ -158,6 +158,9 @@ namespace Discord.Rest | |||||
public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestGroupChannel>> GetGroupChannelsAsync(RequestOptions options = null) | ||||
=> ClientHelper.GetGroupChannelsAsync(this, options); | => ClientHelper.GetGroupChannelsAsync(this, options); | ||||
public Task<RestMessage> GetMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) | |||||
=> ClientHelper.GetMessageAsync(this, channelId, messageId, options); | |||||
public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | public Task<IReadOnlyCollection<RestConnection>> GetConnectionsAsync(RequestOptions options = null) | ||||
=> ClientHelper.GetConnectionsAsync(this, options); | => ClientHelper.GetConnectionsAsync(this, options); | ||||
@@ -1,5 +1,5 @@ | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using Model = Discord.API.Attachment; | |||||
using Model = Discord.IAttachmentModel; | |||||
namespace Discord | namespace Discord | ||||
{ | { | ||||
@@ -44,11 +44,11 @@ namespace Discord | |||||
} | } | ||||
internal static Attachment Create(Model model) | internal static Attachment Create(Model model) | ||||
{ | { | ||||
return new Attachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size, | |||||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
model.Width.IsSpecified ? model.Width.Value : (int?)null, | |||||
model.Ephemeral.ToNullable(), model.Description.GetValueOrDefault(), | |||||
model.ContentType.GetValueOrDefault()); | |||||
return new Attachment(model.Id, model.FileName, model.Url, model.ProxyUrl, model.Size, | |||||
model.Height, | |||||
model.Width, | |||||
model.Ephemeral, model.Description, | |||||
model.ContentType); | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -221,7 +221,7 @@ namespace Discord.Rest | |||||
await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); | ||||
} | } | ||||
public static ImmutableArray<ITag> ParseTags(string text, IMessageChannel channel, IGuild guild, IReadOnlyCollection<IUser> userMentions) | |||||
public static ImmutableArray<ITag> ParseTags(string text, IMessageChannel channel, IGuild guild, ulong[] userMentions) | |||||
{ | { | ||||
var tags = ImmutableArray.CreateBuilder<ITag>(); | var tags = ImmutableArray.CreateBuilder<ITag>(); | ||||
int index = 0; | int index = 0; | ||||
@@ -278,11 +278,9 @@ namespace Discord.Rest | |||||
IUser mentionedUser = null; | IUser mentionedUser = null; | ||||
foreach (var mention in userMentions) | foreach (var mention in userMentions) | ||||
{ | { | ||||
if (mention.Id == id) | |||||
if (mention == id) | |||||
{ | { | ||||
mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); | mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); | ||||
if (mentionedUser == null) | |||||
mentionedUser = mention; | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -372,11 +370,11 @@ namespace Discord.Rest | |||||
.ToImmutableArray(); | .ToImmutableArray(); | ||||
} | } | ||||
public static MessageSource GetSource(Model msg) | |||||
public static MessageSource GetSource(IMessageModel msg) | |||||
{ | { | ||||
if (msg.Type != MessageType.Default && msg.Type != MessageType.Reply) | if (msg.Type != MessageType.Default && msg.Type != MessageType.Reply) | ||||
return MessageSource.System; | return MessageSource.System; | ||||
else if (msg.WebhookId.IsSpecified) | |||||
else if (msg.IsWebhookMessage) | |||||
return MessageSource.Webhook; | return MessageSource.Webhook; | ||||
else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true) | else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true) | ||||
return MessageSource.Bot; | return MessageSource.Bot; | ||||
@@ -1,3 +1,4 @@ | |||||
using System; | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Linq; | using System.Linq; | ||||
@@ -40,17 +41,25 @@ namespace Discord.Rest | |||||
ImmutableArray.Create(model.Roles), | ImmutableArray.Create(model.Roles), | ||||
model.User.IsSpecified ? model.User.Value.Id : (ulong?)null); | model.User.IsSpecified ? model.User.Value.Id : (ulong?)null); | ||||
public static Embed ToEntity(this API.Embed model) | |||||
public static Embed ToEntity(this IEmbedModel model) | |||||
{ | { | ||||
return new Embed(model.Type, model.Title, model.Description, model.Url, model.Timestamp, | |||||
return new Embed(model.Type, model.Title, model.Description, model.Url, | |||||
model.Timestamp.HasValue ? new DateTimeOffset(model.Timestamp.Value, TimeSpan.Zero) : null, | |||||
model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, | model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, | ||||
model.Image.IsSpecified ? model.Image.Value.ToEntity() : (EmbedImage?)null, | |||||
model.Video.IsSpecified ? model.Video.Value.ToEntity() : (EmbedVideo?)null, | |||||
model.Author.IsSpecified ? model.Author.Value.ToEntity() : (EmbedAuthor?)null, | |||||
model.Footer.IsSpecified ? model.Footer.Value.ToEntity() : (EmbedFooter?)null, | |||||
model.Provider.IsSpecified ? model.Provider.Value.ToEntity() : (EmbedProvider?)null, | |||||
model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, | |||||
model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>()); | |||||
model.Image != null | |||||
? new EmbedImage(model.Image.Url, model.Image.ProxyUrl, model.Image.Height, model.Image.Width) : (EmbedImage?)null, | |||||
model.Video != null | |||||
? new EmbedVideo(model.Video.Url, model.Video.Height, model.Video.Width) : (EmbedVideo?)null, | |||||
model.AuthorIconUrl != null || model.AuthorName != null || model.AuthorProxyIconUrl != null || model.AuthorUrl != null | |||||
? new EmbedAuthor(model.AuthorName, model.AuthorUrl, model.AuthorIconUrl, model.AuthorProxyIconUrl) : (EmbedAuthor?)null, | |||||
model.FooterIconUrl != null || model.FooterProxyUrl != null || model.FooterText != null | |||||
? new EmbedFooter(model.FooterText, model.FooterIconUrl, model.FooterProxyUrl) : (EmbedFooter?)null, | |||||
model.ProviderUrl != null || model.ProviderName != null | |||||
? new EmbedProvider(model.ProviderName, model.ProviderUrl) : (EmbedProvider?)null, | |||||
model.Thumbnail != null | |||||
? new EmbedThumbnail(model.Thumbnail.Url, model.Thumbnail.ProxyUrl, model.Thumbnail.Height, model.Thumbnail.Width) : (EmbedThumbnail?)null, | |||||
model.Fields != null | |||||
? model.Fields.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create<EmbedField>()); | |||||
} | } | ||||
public static RoleTags ToEntity(this API.RoleTags model) | public static RoleTags ToEntity(this API.RoleTags model) | ||||
{ | { | ||||
@@ -116,15 +125,11 @@ namespace Discord.Rest | |||||
if (mentionTypes.HasFlag(AllowedMentionTypes.Users)) | if (mentionTypes.HasFlag(AllowedMentionTypes.Users)) | ||||
yield return "users"; | yield return "users"; | ||||
} | } | ||||
public static EmbedAuthor ToEntity(this API.EmbedAuthor model) | |||||
{ | |||||
return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); | |||||
} | |||||
public static API.EmbedAuthor ToModel(this EmbedAuthor entity) | public static API.EmbedAuthor ToModel(this EmbedAuthor entity) | ||||
{ | { | ||||
return new API.EmbedAuthor { Name = entity.Name, Url = entity.Url, IconUrl = entity.IconUrl }; | return new API.EmbedAuthor { Name = entity.Name, Url = entity.Url, IconUrl = entity.IconUrl }; | ||||
} | } | ||||
public static EmbedField ToEntity(this API.EmbedField model) | |||||
public static EmbedField ToEntity(this IEmbedFieldModel model) | |||||
{ | { | ||||
return new EmbedField(model.Name, model.Value, model.Inline); | return new EmbedField(model.Name, model.Value, model.Inline); | ||||
} | } | ||||
@@ -132,48 +137,22 @@ namespace Discord.Rest | |||||
{ | { | ||||
return new API.EmbedField { Name = entity.Name, Value = entity.Value, Inline = entity.Inline }; | return new API.EmbedField { Name = entity.Name, Value = entity.Value, Inline = entity.Inline }; | ||||
} | } | ||||
public static EmbedFooter ToEntity(this API.EmbedFooter model) | |||||
{ | |||||
return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl); | |||||
} | |||||
public static API.EmbedFooter ToModel(this EmbedFooter entity) | public static API.EmbedFooter ToModel(this EmbedFooter entity) | ||||
{ | { | ||||
return new API.EmbedFooter { Text = entity.Text, IconUrl = entity.IconUrl }; | return new API.EmbedFooter { Text = entity.Text, IconUrl = entity.IconUrl }; | ||||
} | } | ||||
public static EmbedImage ToEntity(this API.EmbedImage model) | |||||
{ | |||||
return new EmbedImage(model.Url, model.ProxyUrl, | |||||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||||
} | |||||
public static API.EmbedImage ToModel(this EmbedImage entity) | public static API.EmbedImage ToModel(this EmbedImage entity) | ||||
{ | { | ||||
return new API.EmbedImage { Url = entity.Url }; | return new API.EmbedImage { Url = entity.Url }; | ||||
} | } | ||||
public static EmbedProvider ToEntity(this API.EmbedProvider model) | |||||
{ | |||||
return new EmbedProvider(model.Name, model.Url); | |||||
} | |||||
public static API.EmbedProvider ToModel(this EmbedProvider entity) | public static API.EmbedProvider ToModel(this EmbedProvider entity) | ||||
{ | { | ||||
return new API.EmbedProvider { Name = entity.Name, Url = entity.Url }; | return new API.EmbedProvider { Name = entity.Name, Url = entity.Url }; | ||||
} | } | ||||
public static EmbedThumbnail ToEntity(this API.EmbedThumbnail model) | |||||
{ | |||||
return new EmbedThumbnail(model.Url, model.ProxyUrl, | |||||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||||
} | |||||
public static API.EmbedThumbnail ToModel(this EmbedThumbnail entity) | public static API.EmbedThumbnail ToModel(this EmbedThumbnail entity) | ||||
{ | { | ||||
return new API.EmbedThumbnail { Url = entity.Url }; | return new API.EmbedThumbnail { Url = entity.Url }; | ||||
} | } | ||||
public static EmbedVideo ToEntity(this API.EmbedVideo model) | |||||
{ | |||||
return new EmbedVideo(model.Url, | |||||
model.Height.IsSpecified ? model.Height.Value : (int?)null, | |||||
model.Width.IsSpecified ? model.Width.Value : (int?)null); | |||||
} | |||||
public static API.EmbedVideo ToModel(this EmbedVideo entity) | public static API.EmbedVideo ToModel(this EmbedVideo entity) | ||||
{ | { | ||||
return new API.EmbedVideo { Url = entity.Url }; | return new API.EmbedVideo { Url = entity.Url }; | ||||
@@ -49,7 +49,7 @@ namespace Discord.WebSocket | |||||
internal class ReferenceStore<TEntity, TModel, TId, TSharedEntity> : ILookupReferenceStore<TEntity, TId> | internal class ReferenceStore<TEntity, TModel, TId, TSharedEntity> : ILookupReferenceStore<TEntity, TId> | ||||
where TEntity : class, ICached<TModel>, TSharedEntity | where TEntity : class, ICached<TModel>, TSharedEntity | ||||
where TModel : IEntityModel<TId> | |||||
where TModel : class, IEntityModel<TId> | |||||
where TId : IEquatable<TId> | where TId : IEquatable<TId> | ||||
where TSharedEntity : class | where TSharedEntity : class | ||||
{ | { | ||||
@@ -263,20 +263,40 @@ namespace Discord.WebSocket | |||||
return _store.PurgeAllAsync(); | return _store.PurgeAllAsync(); | ||||
} | } | ||||
public IEnumerable<TEntity> GetEnumerable(IEnumerable<TId> ids) | |||||
{ | |||||
foreach (var id in ids) | |||||
{ | |||||
yield return Get(id); | |||||
} | |||||
} | |||||
public async IAsyncEnumerable<TEntity> GetEnumerableAsync(IEnumerable<TId> ids) | |||||
{ | |||||
foreach (var id in ids) | |||||
{ | |||||
yield return (TEntity)await GetAsync(id, CacheMode.CacheOnly); | |||||
} | |||||
} | |||||
TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | TEntity ILookupReferenceStore<TEntity, TId>.Get(TId id) => Get(id); | ||||
async ValueTask<TEntity> ILookupReferenceStore<TEntity, TId>.GetAsync(TId id) => (TEntity)await GetAsync(id, CacheMode.CacheOnly).ConfigureAwait(false); | async ValueTask<TEntity> ILookupReferenceStore<TEntity, TId>.GetAsync(TId id) => (TEntity)await GetAsync(id, CacheMode.CacheOnly).ConfigureAwait(false); | ||||
} | } | ||||
internal partial class ClientStateManager | internal partial class ClientStateManager | ||||
{ | { | ||||
public ReferenceStore<SocketGlobalUser, IUserModel, ulong, IUser> UserStore; | |||||
public ReferenceStore<SocketUser, IUserModel, ulong, IUser> UserStore; | |||||
public ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence> PresenceStore; | public ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence> PresenceStore; | ||||
private ConcurrentDictionary<ulong, ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser>> _memberStores; | private ConcurrentDictionary<ulong, ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser>> _memberStores; | ||||
private ConcurrentDictionary<ulong, ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> _threadMemberStores; | private ConcurrentDictionary<ulong, ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> _threadMemberStores; | ||||
private ConcurrentDictionary<ulong, ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>> _messageStores; | |||||
private SemaphoreSlim _memberStoreLock; | private SemaphoreSlim _memberStoreLock; | ||||
private SemaphoreSlim _messageStoreLock; | |||||
private SemaphoreSlim _threadMemberLock; | private SemaphoreSlim _threadMemberLock; | ||||
#region Models | |||||
private readonly Dictionary<Type, Func<object>> _defaultModelFactory = new() | private readonly Dictionary<Type, Func<object>> _defaultModelFactory = new() | ||||
{ | { | ||||
{ typeof(IUserModel), () => new SocketUser.CacheModel() }, | { typeof(IUserModel), () => new SocketUser.CacheModel() }, | ||||
@@ -284,10 +304,45 @@ namespace Discord.WebSocket | |||||
{ typeof(ICurrentUserModel), () => new SocketSelfUser.CacheModel() }, | { typeof(ICurrentUserModel), () => new SocketSelfUser.CacheModel() }, | ||||
{ typeof(IThreadMemberModel), () => new SocketThreadUser.CacheModel() }, | { typeof(IThreadMemberModel), () => new SocketThreadUser.CacheModel() }, | ||||
{ typeof(IPresenceModel), () => new SocketPresence.CacheModel() }, | { typeof(IPresenceModel), () => new SocketPresence.CacheModel() }, | ||||
{ typeof(IActivityModel), () => new SocketPresence.ActivityCacheModel() } | |||||
{ typeof(IActivityModel), () => new SocketPresence.ActivityCacheModel() }, | |||||
{ typeof(IMessageModel), () => new SocketMessage.CacheModel() }, | |||||
{ typeof(IMessageActivityModel), () => new SocketMessage.CacheModel.MessageActivityModel() }, | |||||
{ typeof(IMessageComponentModel), () => new SocketMessage.CacheModel.MessageComponentModel() }, | |||||
{ typeof(IMessageComponentOptionModel), () => new SocketMessage.CacheModel.MessageComponentModel.MessageComponentOptionModel() }, | |||||
{ typeof(IPartialApplicationModel), () => new SocketMessage.CacheModel.PartialApplicationModel() }, | |||||
{ typeof(IStickerItemModel), () => new SocketMessage.CacheModel.StickerItemModel() }, | |||||
{ typeof(IReactionMetadataModel), () => new SocketMessage.CacheModel.ReactionModel() }, | |||||
{ typeof(IEmbedModel), () => new SocketMessage.CacheModel.EmbedModel() }, | |||||
{ typeof(IEmbedFieldModel), () => new SocketMessage.CacheModel.EmbedModel.EmbedFieldModel() }, | |||||
{ typeof(IEmbedMediaModel), () => new SocketMessage.CacheModel.EmbedModel.EmbedMediaModel()} | |||||
}; | }; | ||||
public TModel GetModel<TModel, TFallback>() | |||||
where TFallback : class, TModel, new() | |||||
where TModel : class | |||||
{ | |||||
return GetModel<TModel>() ?? new TFallback(); | |||||
} | |||||
public TModel GetModel<TModel>() | |||||
where TModel : class | |||||
{ | |||||
var type = _cacheProvider.GetModel<TModel>(); | |||||
if (type != null) | |||||
{ | |||||
if (!type.GetInterfaces().Contains(typeof(TModel))) | |||||
throw new InvalidOperationException($"Cannot use {type.Name} as a model for {typeof(TModel).Name}"); | |||||
return (TModel)Activator.CreateInstance(type); | |||||
} | |||||
else | |||||
return _defaultModelFactory.TryGetValue(typeof(TModel), out var m) ? (TModel)m() : null; | |||||
} | |||||
#endregion | |||||
#region References & Initialization | |||||
public void ClearDeadReferences() | public void ClearDeadReferences() | ||||
{ | { | ||||
UserStore.ClearDeadReferences(); | UserStore.ClearDeadReferences(); | ||||
@@ -300,6 +355,29 @@ namespace Discord.WebSocket | |||||
await PresenceStore.InitializeAsync(); | await PresenceStore.InitializeAsync(); | ||||
} | } | ||||
private void CreateStores() | |||||
{ | |||||
UserStore = new ReferenceStore<SocketUser, IUserModel, ulong, IUser>( | |||||
_cacheProvider, | |||||
m => SocketGlobalUser.Create(_client, m), | |||||
async (id, options) => await _client.Rest.GetUserAsync(id, options).ConfigureAwait(false), | |||||
GetModel<IUserModel>); | |||||
PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>( | |||||
_cacheProvider, | |||||
m => SocketPresence.Create(_client, m), | |||||
(id, options) => Task.FromResult<IPresence>(null), | |||||
GetModel<IPresenceModel>); | |||||
_memberStores = new(); | |||||
_threadMemberStores = new(); | |||||
_threadMemberLock = new(1, 1); | |||||
_memberStoreLock = new(1, 1); | |||||
} | |||||
#endregion | |||||
#region Members | |||||
public ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> GetMemberStore(ulong guildId) | public ReferenceStore<SocketGuildUser, IMemberModel, ulong, IGuildUser> GetMemberStore(ulong guildId) | ||||
=> TryGetMemberStore(guildId, out var store) ? store : null; | => TryGetMemberStore(guildId, out var store) ? store : null; | ||||
@@ -331,7 +409,9 @@ namespace Discord.WebSocket | |||||
_memberStoreLock.Release(); | _memberStoreLock.Release(); | ||||
} | } | ||||
} | } | ||||
#endregion | |||||
#region Thread Members | |||||
public async Task<ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> GetThreadMemberStoreAsync(ulong threadId, ulong guildId) | public async Task<ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser>> GetThreadMemberStoreAsync(ulong threadId, ulong guildId) | ||||
{ | { | ||||
if (_threadMemberStores.TryGetValue(threadId, out var store)) | if (_threadMemberStores.TryGetValue(threadId, out var store)) | ||||
@@ -360,49 +440,40 @@ namespace Discord.WebSocket | |||||
public ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser> GetThreadMemberStore(ulong threadId) | public ReferenceStore<SocketThreadUser, IThreadMemberModel, ulong, IThreadUser> GetThreadMemberStore(ulong threadId) | ||||
=> _threadMemberStores.TryGetValue(threadId, out var store) ? store : null; | => _threadMemberStores.TryGetValue(threadId, out var store) ? store : null; | ||||
#endregion | |||||
public TModel GetModel<TModel, TFallback>() | |||||
where TFallback : class, TModel, new() | |||||
where TModel : class | |||||
{ | |||||
return GetModel<TModel>() ?? new TFallback(); | |||||
} | |||||
#region Messages | |||||
public ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage> GetMessageStore(ulong channelId) | |||||
=> TryGetMessageStore(channelId, out var store) ? store : null; | |||||
public TModel GetModel<TModel>() | |||||
where TModel : class | |||||
{ | |||||
var type = _cacheProvider.GetModel<TModel>(); | |||||
if (type != null) | |||||
{ | |||||
if (!type.GetInterfaces().Contains(typeof(TModel))) | |||||
throw new InvalidOperationException($"Cannot use {type.Name} as a model for {typeof(TModel).Name}"); | |||||
return (TModel)Activator.CreateInstance(type); | |||||
} | |||||
else | |||||
return _defaultModelFactory.TryGetValue(typeof(TModel), out var m) ? (TModel)m() : null; | |||||
} | |||||
public bool TryGetMessageStore(ulong channelId, out ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage> store) | |||||
=> _messageStores.TryGetValue(channelId, out store); | |||||
private void CreateStores() | |||||
public async ValueTask<ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>> GetMessageStoreAsync(ulong channelId) | |||||
{ | { | ||||
UserStore = new ReferenceStore<SocketGlobalUser, IUserModel, ulong, IUser>( | |||||
_cacheProvider, | |||||
m => SocketGlobalUser.Create(_client, m), | |||||
async (id, options) => await _client.Rest.GetUserAsync(id, options).ConfigureAwait(false), | |||||
GetModel<IUserModel>); | |||||
if (_messageStores.TryGetValue(channelId, out var store)) | |||||
return store; | |||||
PresenceStore = new ReferenceStore<SocketPresence, IPresenceModel, ulong, IPresence>( | |||||
_cacheProvider, | |||||
m => SocketPresence.Create(_client, m), | |||||
(id, options) => Task.FromResult<IPresence>(null), | |||||
GetModel<IPresenceModel>); | |||||
await _messageStoreLock.WaitAsync().ConfigureAwait(false); | |||||
_memberStores = new(); | |||||
_threadMemberStores = new(); | |||||
try | |||||
{ | |||||
store = new ReferenceStore<SocketMessage, IMessageModel, ulong, IMessage>( | |||||
_cacheProvider, | |||||
m => SocketMessage.Create(_client, m, channelId), | |||||
async (id, options) => await _client.Rest.GetMessageAsync(channelId, id).ConfigureAwait(false), | |||||
GetModel<IMessageModel>); | |||||
_threadMemberLock = new(1, 1); | |||||
_memberStoreLock = new(1,1); | |||||
await store.InitializeAsync(channelId).ConfigureAwait(false); | |||||
_messageStores.TryAdd(channelId, store); | |||||
return store; | |||||
} | |||||
finally | |||||
{ | |||||
_memberStoreLock.Release(); | |||||
} | |||||
} | } | ||||
#endregion | |||||
} | } | ||||
} | } |
@@ -5,19 +5,25 @@ using System.Collections.Generic; | |||||
using System.Collections.Immutable; | using System.Collections.Immutable; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.Message; | |||||
using Model = Discord.IMessageModel; | |||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
{ | { | ||||
/// <summary> | /// <summary> | ||||
/// Represents a WebSocket-based message. | /// Represents a WebSocket-based message. | ||||
/// </summary> | /// </summary> | ||||
public abstract class SocketMessage : SocketEntity<ulong>, IMessage | |||||
public abstract class SocketMessage : SocketEntity<ulong>, IMessage, ICached<Model> | |||||
{ | { | ||||
#region SocketMessage | #region SocketMessage | ||||
internal bool IsFreed { get; set; } | |||||
private long _timestampTicks; | private long _timestampTicks; | ||||
private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | private readonly List<SocketReaction> _reactions = new List<SocketReaction>(); | ||||
private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); | |||||
private ulong[] _userMentionIds; | |||||
private ulong _channelId; | |||||
private ulong _guildId; | |||||
private ulong _authorId; | |||||
private bool _isWebhook; | |||||
//private ImmutableArray<SocketUser> _userMentions = ImmutableArray.Create<SocketUser>(); | |||||
/// <summary> | /// <summary> | ||||
/// Gets the author of this message. | /// Gets the author of this message. | ||||
@@ -54,6 +60,7 @@ namespace Discord.WebSocket | |||||
public virtual DateTimeOffset? EditedTimestamp => null; | public virtual DateTimeOffset? EditedTimestamp => null; | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public virtual bool MentionedEveryone => false; | public virtual bool MentionedEveryone => false; | ||||
public virtual ulong? ApplicationId { get; private set; } | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public MessageActivity Activity { get; private set; } | public MessageActivity Activity { get; private set; } | ||||
@@ -115,10 +122,13 @@ namespace Discord.WebSocket | |||||
/// <summary> | /// <summary> | ||||
/// Returns the users mentioned in this message. | /// Returns the users mentioned in this message. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | |||||
/// The returned enumerable will preform cache lookups per enumeration. | |||||
/// </remarks> | |||||
/// <returns> | /// <returns> | ||||
/// Collection of WebSocket-based users. | /// Collection of WebSocket-based users. | ||||
/// </returns> | /// </returns> | ||||
public IReadOnlyCollection<SocketUser> MentionedUsers => _userMentions; | |||||
public IEnumerable<SocketUser> MentionedUsers => Discord.StateManager.UserStore.GetEnumerable(_userMentionIds); // TODO: async counterpart? | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); | ||||
@@ -129,6 +139,12 @@ namespace Discord.WebSocket | |||||
Author = author; | Author = author; | ||||
Source = source; | Source = source; | ||||
} | } | ||||
//internal static SocketMessage Create(DiscordSocketClient discord, Model model, ulong channelId) | |||||
//{ | |||||
//} | |||||
internal static SocketMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model) | internal static SocketMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model) | ||||
{ | { | ||||
if (model.Type == MessageType.Default || | if (model.Type == MessageType.Default || | ||||
@@ -140,55 +156,52 @@ namespace Discord.WebSocket | |||||
else | else | ||||
return SocketSystemMessage.Create(discord, state, author, channel, model); | return SocketSystemMessage.Create(discord, state, author, channel, model); | ||||
} | } | ||||
internal virtual void Update(ClientStateManager state, Model model) | |||||
internal virtual void Update(Model model) | |||||
{ | { | ||||
Type = model.Type; | Type = model.Type; | ||||
if (model.Timestamp.IsSpecified) | |||||
_timestampTicks = model.Timestamp.Value.UtcTicks; | |||||
if (model.Content.IsSpecified) | |||||
{ | |||||
Content = model.Content.Value; | |||||
} | |||||
_timestampTicks = model.Timestamp; | |||||
ApplicationId = model.ApplicationId; | |||||
Content = model.Content; | |||||
_userMentionIds = model.UserMentionIds; | |||||
if (model.Application.IsSpecified) | |||||
if (model.Application != null) | |||||
{ | { | ||||
// create a new Application from the API model | // create a new Application from the API model | ||||
Application = new MessageApplication() | Application = new MessageApplication() | ||||
{ | { | ||||
Id = model.Application.Value.Id, | |||||
CoverImage = model.Application.Value.CoverImage, | |||||
Description = model.Application.Value.Description, | |||||
Icon = model.Application.Value.Icon, | |||||
Name = model.Application.Value.Name | |||||
Id = model.Application.Id, | |||||
CoverImage = model.Application.CoverImage, | |||||
Description = model.Application.Description, | |||||
Icon = model.Application.Icon, | |||||
Name = model.Application.Name | |||||
}; | }; | ||||
} | } | ||||
if (model.Activity.IsSpecified) | |||||
if (model.Activity != null) | |||||
{ | { | ||||
// create a new Activity from the API model | // create a new Activity from the API model | ||||
Activity = new MessageActivity() | Activity = new MessageActivity() | ||||
{ | { | ||||
Type = model.Activity.Value.Type.Value, | |||||
PartyId = model.Activity.Value.PartyId.GetValueOrDefault() | |||||
Type = model.Activity.Type.Value, | |||||
PartyId = model.Activity.PartyId | |||||
}; | }; | ||||
} | } | ||||
if (model.Reference.IsSpecified) | |||||
if (model.ReferenceMessageId.HasValue) | |||||
{ | { | ||||
// Creates a new Reference from the API model | // Creates a new Reference from the API model | ||||
Reference = new MessageReference | Reference = new MessageReference | ||||
{ | { | ||||
GuildId = model.Reference.Value.GuildId, | |||||
InternalChannelId = model.Reference.Value.ChannelId, | |||||
MessageId = model.Reference.Value.MessageId | |||||
GuildId = model.ReferenceMessageGuildId.ToOptional(), | |||||
InternalChannelId = model.ReferenceMessageChannelId.ToOptional(), | |||||
MessageId = model.ReferenceMessageId.ToOptional() | |||||
}; | }; | ||||
} | } | ||||
if (model.Components.IsSpecified) | |||||
if (model.Components != null && model.Components.Length > 0) | |||||
{ | { | ||||
Components = model.Components.Value.Select(x => new ActionRowComponent(x.Components.Select<IMessageComponent, IMessageComponent>(y => | |||||
Components = model.Components.Select(x => new ActionRowComponent(x.Components.Select<IMessageComponentModel, IMessageComponent>(y => | |||||
{ | { | ||||
switch (y.Type) | switch (y.Type) | ||||
{ | { | ||||
@@ -236,38 +249,16 @@ namespace Discord.WebSocket | |||||
else | else | ||||
Components = new List<ActionRowComponent>(); | Components = new List<ActionRowComponent>(); | ||||
if (model.UserMentions.IsSpecified) | |||||
if (model.InteractionId.HasValue) | |||||
{ | { | ||||
var value = model.UserMentions.Value; | |||||
if (value.Length > 0) | |||||
{ | |||||
var newMentions = ImmutableArray.CreateBuilder<SocketUser>(value.Length); | |||||
for (int i = 0; i < value.Length; i++) | |||||
{ | |||||
var val = value[i]; | |||||
if (val != null) | |||||
{ | |||||
var user = Channel.GetUserAsync(val.Id, CacheMode.CacheOnly).GetAwaiter().GetResult() as SocketUser; | |||||
if (user != null) | |||||
newMentions.Add(user); | |||||
else | |||||
newMentions.Add(SocketUnknownUser.Create(Discord, val)); | |||||
} | |||||
} | |||||
_userMentions = newMentions.ToImmutable(); | |||||
} | |||||
Interaction = new MessageInteraction<SocketUser>(model.InteractionId.Value, | |||||
model.InteractionType.Value, | |||||
model.InteractionName, | |||||
model.InteractionUserId.Value, | |||||
Discord.StateManager.UserStore.Get); | |||||
} | } | ||||
if (model.Interaction.IsSpecified) | |||||
{ | |||||
Interaction = new MessageInteraction<SocketUser>(model.Interaction.Value.Id, | |||||
model.Interaction.Value.Type, | |||||
model.Interaction.Value.Name, | |||||
SocketGlobalUser.Create(Discord, model.Interaction.Value.User)); | |||||
} | |||||
if (model.Flags.IsSpecified) | |||||
Flags = model.Flags.Value; | |||||
Flags = model.Flags; | |||||
} | } | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -309,7 +300,6 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
IReadOnlyCollection<IStickerItem> IMessage.Stickers => Stickers; | IReadOnlyCollection<IStickerItem> IMessage.Stickers => Stickers; | ||||
internal void AddReaction(SocketReaction reaction) | internal void AddReaction(SocketReaction reaction) | ||||
{ | { | ||||
_reactions.Add(reaction); | _reactions.Add(reaction); | ||||
@@ -347,5 +337,314 @@ namespace Discord.WebSocket | |||||
public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) | public IAsyncEnumerable<IReadOnlyCollection<IUser>> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) | ||||
=> MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); | => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); | ||||
#endregion | #endregion | ||||
#region Cache | |||||
internal class CacheModel : Model | |||||
{ | |||||
public MessageType Type { get; set; } | |||||
public ulong ChannelId { get; set; } | |||||
public ulong? GuildId { get; set; } | |||||
public ulong AuthorId { get; set; } | |||||
public bool IsWebhookMessage { get; set; } | |||||
public string Content { get; set; } | |||||
public long Timestamp { get; set; } | |||||
public long? EditedTimestamp { get; set; } | |||||
public bool IsTextToSpeech { get; set; } | |||||
public bool MentionEveryone { get; set; } | |||||
public ulong[] UserMentionIds { get; set; } | |||||
public AttachmentModel[] Attachments { get; set; } | |||||
public EmbedModel[] Embeds { get; set; } | |||||
public ReactionModel[] Reactions { get; set; } // TODO: seperate store? | |||||
public bool Pinned { get; set; } | |||||
public MessageActivityModel Activity { get; set; } | |||||
public PartialApplicationModel Application { get; set; } | |||||
public ulong? ApplicationId { get; set; } | |||||
public ulong? ReferenceMessageId { get; set; } | |||||
public ulong? ReferenceMessageChannelId { get; set; } | |||||
public ulong? ReferenceMessageGuildId { get; set; } | |||||
public MessageFlags Flags { get; set; } | |||||
public ulong? InteractionId { get; set; } | |||||
public string InteractionName { get; set; } | |||||
public InteractionType? InteractionType { get; set; } | |||||
public ulong? InteractionUserId { get; set; } | |||||
public MessageComponentModel[] Components { get; set; } | |||||
public StickerItemModel[] Stickers { get; set; } | |||||
public ulong Id { get; set; } | |||||
internal class AttachmentModel : IAttachmentModel | |||||
{ | |||||
public string FileName { get; set; } | |||||
public string Description { get; set; } | |||||
public string ContentType { get; set; } | |||||
public int Size { get; set; } | |||||
public string Url { get; set; } | |||||
public string ProxyUrl { get; set; } | |||||
public int? Height { get; set; } | |||||
public int? Width { get; set; } | |||||
public bool Ephemeral { get; set; } | |||||
public ulong Id { get; set; } | |||||
} | |||||
internal class EmbedModel : IEmbedModel | |||||
{ | |||||
public string Title { get; set; } | |||||
public EmbedType Type { get; set; } | |||||
public string Description { get; set; } | |||||
public string Url { get; set; } | |||||
public long? Timestamp { get; set; } | |||||
public uint? Color { get; set; } | |||||
public string FooterText { get; set; } | |||||
public string FooterIconUrl { get; set; } | |||||
public string FooterProxyUrl { get; set; } | |||||
public string ProviderName { get; set; } | |||||
public string ProviderUrl { get; set; } | |||||
public string AuthorName { get; set; } | |||||
public string AuthorUrl { get; set; } | |||||
public string AuthorIconUrl { get; set; } | |||||
public string AuthorProxyIconUrl { get; set; } | |||||
public EmbedMediaModel Image { get; set; } | |||||
public EmbedMediaModel Thumbnail { get; set; } | |||||
public EmbedMediaModel Video { get; set; } | |||||
public EmbedFieldModel[] Fields { get; set; } | |||||
IEmbedMediaModel IEmbedModel.Image { get => Image; set => Image = value.InterfaceCopy<EmbedMediaModel>(); } | |||||
IEmbedMediaModel IEmbedModel.Thumbnail { get => Thumbnail; set => Thumbnail = value.InterfaceCopy<EmbedMediaModel>(); } | |||||
IEmbedMediaModel IEmbedModel.Video { get => Video; set => Video = value.InterfaceCopy<EmbedMediaModel>(); } | |||||
IEmbedFieldModel[] IEmbedModel.Fields { get => Fields; set => value?.Select(x => x.InterfaceCopy<EmbedFieldModel>()); } | |||||
internal class EmbedMediaModel : IEmbedMediaModel | |||||
{ | |||||
public string Url { get; set; } | |||||
public string ProxyUrl { get; set; } | |||||
public int? Height { get; set; } | |||||
public int? Width { get; set; } | |||||
} | |||||
internal class EmbedFieldModel : IEmbedFieldModel | |||||
{ | |||||
public string Name { get; set; } | |||||
public string Value { get; set; } | |||||
public bool Inline { get; set; } | |||||
} | |||||
} | |||||
internal class ReactionModel : IReactionMetadataModel | |||||
{ | |||||
public IEmojiModel Emoji { get; set; } | |||||
public ulong[] Users { get; set; } | |||||
} | |||||
internal class MessageActivityModel : IMessageActivityModel | |||||
{ | |||||
public MessageActivityType? Type { get; set; } | |||||
public string PartyId { get; set; } | |||||
} | |||||
internal class PartialApplicationModel : IPartialApplicationModel | |||||
{ | |||||
public string Name { get; set; } | |||||
public string Icon { get; set; } | |||||
public string Description { get; set; } | |||||
public string CoverImage { get; set; } | |||||
public ulong Id { get; set; } | |||||
} | |||||
internal class MessageComponentModel : IMessageComponentModel | |||||
{ | |||||
public ComponentType Type { get; set; } | |||||
public string CustomId { get; set; } | |||||
public bool? Disabled { get; set; } | |||||
public ButtonStyle? Style { get; set; } | |||||
public string Label { get; set; } | |||||
public ulong? EmojiId { get; set; } | |||||
public string EmojiName { get; set; } | |||||
public bool? EmojiAnimated { get; set; } | |||||
public string Url { get; set; } | |||||
public MessageComponentOptionModel[] Options { get; set; } | |||||
public string Placeholder { get; set; } | |||||
public int? MinValues { get; set; } | |||||
public int? MaxValues { get; set; } | |||||
public MessageComponentModel[] Components { get; set; } | |||||
public int? MinLength { get; set; } | |||||
public int? MaxLength { get; set; } | |||||
public bool? Required { get; set; } | |||||
public string Value { get; set; } | |||||
internal class MessageComponentOptionModel : IMessageComponentOptionModel | |||||
{ | |||||
public string Label { get; set; } | |||||
public string Value { get; set; } | |||||
public string Description { get; set; } | |||||
public ulong? EmojiId { get; set; } | |||||
public string EmojiName { get; set; } | |||||
public bool? EmojiAnimated { get; set; } | |||||
public bool? Default { get; set; } | |||||
} | |||||
IMessageComponentOptionModel[] IMessageComponentModel.Options { get => Options; set => Options = value.Select(x => x.InterfaceCopy(new MessageComponentOptionModel())).ToArray(); } | |||||
IMessageComponentModel[] IMessageComponentModel.Components { get => Components; set => Components = value.Select(x => x.InterfaceCopy(new MessageComponentModel())).ToArray(); } | |||||
} | |||||
internal class StickerItemModel : IStickerItemModel | |||||
{ | |||||
public ulong Id { get; set; } | |||||
public string Name { get; set; } | |||||
public StickerFormatType Format { get; set; } | |||||
} | |||||
IAttachmentModel[] Model.Attachments { get => Attachments; set => Attachments = value.Select(x => x.InterfaceCopy<AttachmentModel>()).ToArray(); } | |||||
IEmbedModel[] Model.Embeds { get => Embeds; set => Embeds = value.Select(x => x.InterfaceCopy<EmbedModel>()).ToArray(); } | |||||
IReactionMetadataModel[] Model.Reactions { get => Reactions; set => Reactions = value.Select(x => x.InterfaceCopy<ReactionModel>()).ToArray(); } | |||||
IMessageActivityModel Model.Activity { get => Activity; set => Activity = value.InterfaceCopy<MessageActivityModel>(); } | |||||
IPartialApplicationModel Model.Application { get => Application; set => Application = value.InterfaceCopy<PartialApplicationModel>(); } | |||||
IMessageComponentModel[] Model.Components { get => Components; set => Components = value.Select(x => x.InterfaceCopy<MessageComponentModel>()).ToArray(); } | |||||
IStickerItemModel[] Model.Stickers { get => Stickers; set => Stickers = value.Select(x => x.InterfaceCopy<StickerItemModel>()).ToArray(); } | |||||
} | |||||
internal virtual Model ToModel() | |||||
{ | |||||
var model = Discord.StateManager.GetModel<Model>(); | |||||
model.Content = Content; | |||||
model.Type = Type; | |||||
model.ChannelId = _channelId; | |||||
model.GuildId = _guildId; | |||||
model.AuthorId = _authorId; | |||||
model.IsWebhookMessage = _isWebhook; | |||||
model.Timestamp = _timestampTicks; | |||||
model.IsTextToSpeech = IsTTS; | |||||
model.MentionEveryone = MentionedEveryone; | |||||
model.UserMentionIds = _userMentionIds; | |||||
model.ApplicationId = ApplicationId; | |||||
model.Flags = Flags ?? MessageFlags.None; | |||||
model.Id = Id; | |||||
if(Interaction != null) | |||||
{ | |||||
model.InteractionName = Interaction.Name; | |||||
model.InteractionId = Interaction.Id; | |||||
model.InteractionType = Interaction.Type; | |||||
model.InteractionUserId = Interaction.UserId; | |||||
} | |||||
if(Reference != null) | |||||
{ | |||||
model.ReferenceMessageId = Reference.MessageId.ToNullable(); | |||||
model.ReferenceMessageGuildId = Reference.GuildId.ToNullable(); | |||||
model.ReferenceMessageChannelId = Reference.ChannelId; | |||||
} | |||||
model.Attachments = Attachments.Select(x => | |||||
{ | |||||
var attachmentModel = Discord.StateManager.GetModel<IAttachmentModel>(); | |||||
attachmentModel.Width = x.Width; | |||||
attachmentModel.Height = x.Height; | |||||
attachmentModel.Size = x.Size; | |||||
attachmentModel.Description = x.Description; | |||||
attachmentModel.Ephemeral = x.Ephemeral; | |||||
attachmentModel.FileName = x.Filename; | |||||
attachmentModel.Url = x.Url; | |||||
attachmentModel.ContentType = x.ContentType; | |||||
attachmentModel.Id = x.Id; | |||||
attachmentModel.ProxyUrl = x.ProxyUrl; | |||||
return attachmentModel; | |||||
}).ToArray(); | |||||
model.Embeds = Embeds.Select(x => | |||||
{ | |||||
var embedModel = Discord.StateManager.GetModel<IEmbedModel>(); | |||||
embedModel.AuthorName = x.Author?.Name; | |||||
embedModel.AuthorProxyIconUrl = x.Author?.ProxyIconUrl; | |||||
embedModel.AuthorIconUrl = x.Author?.IconUrl; | |||||
embedModel.AuthorUrl = x.Author?.Url; | |||||
embedModel.Color = x.Color; | |||||
embedModel.Description = x.Description; | |||||
embedModel.Title = x.Title; | |||||
embedModel.Timestamp = x.Timestamp?.UtcTicks; | |||||
embedModel.Type = x.Type; | |||||
embedModel.Url = x.Url; | |||||
embedModel.ProviderName = x.Provider?.Name; | |||||
embedModel.ProviderUrl = x.Provider?.Url; | |||||
embedModel.FooterIconUrl = x.Footer?.IconUrl; | |||||
embedModel.FooterProxyUrl = x.Footer?.ProxyUrl; | |||||
embedModel.FooterText = x.Footer?.Text; | |||||
var image = Discord.StateManager.GetModel<IEmbedMediaModel>(); | |||||
image.Width = x.Image?.Width; | |||||
image.Height = x.Image?.Height; | |||||
image.Url = x.Image?.Url; | |||||
image.ProxyUrl = x.Image?.ProxyUrl; | |||||
embedModel.Image = image; | |||||
var thumbnail = Discord.StateManager.GetModel<IEmbedMediaModel>(); | |||||
thumbnail.Width = x.Thumbnail?.Width; | |||||
thumbnail.Height = x.Thumbnail?.Height; | |||||
thumbnail.Url = x.Thumbnail?.Url; | |||||
thumbnail.ProxyUrl = x.Thumbnail?.ProxyUrl; | |||||
embedModel.Thumbnail = thumbnail; | |||||
var video = Discord.StateManager.GetModel<IEmbedMediaModel>(); | |||||
video.Width = x.Video?.Width; | |||||
video.Height = x.Video?.Height; | |||||
video.Url = x.Video?.Url; | |||||
embedModel.Video = video; | |||||
embedModel.Fields = x.Fields.Select(x => | |||||
{ | |||||
var fieldModel = Discord.StateManager.GetModel<IEmbedFieldModel>(); | |||||
fieldModel.Name = x.Name; | |||||
fieldModel.Value = x.Value; | |||||
fieldModel.Inline = x.Inline; | |||||
return fieldModel; | |||||
}).ToArray(); | |||||
return embedModel; | |||||
}).ToArray(); | |||||
model.Reactions = _reactions.GroupBy(x => x.Emote).Select(x => | |||||
{ | |||||
var reactionMetadataModel = Discord.StateManager.GetModel<IReactionMetadataModel>(); | |||||
reactionMetadataModel.Emoji = x.Key.ToModel(Discord.StateManager.GetModel<IEmojiModel>()); | |||||
reactionMetadataModel.Users = x.Select(x => x.UserId).ToArray(); | |||||
return reactionMetadataModel; | |||||
}).ToArray(); | |||||
var activityModel = Discord.StateManager.GetModel<IMessageActivityModel>(); | |||||
activityModel.PartyId = Activity?.PartyId; | |||||
activityModel.Type = Activity?.Type; | |||||
model.Activity = activityModel; | |||||
var applicationModel = Discord.StateManager.GetModel<IPartialApplicationModel>(); | |||||
applicationModel.Description = Application.Description; | |||||
applicationModel.Name = Application.Name; | |||||
applicationModel.CoverImage = Application.CoverImage; | |||||
applicationModel.Id = Application.Id; | |||||
applicationModel.Icon = Application.Icon; | |||||
model.Application = applicationModel; | |||||
return model; | |||||
} | |||||
~SocketMessage() => Dispose(); | |||||
public void Dispose() | |||||
{ | |||||
if (IsFreed) | |||||
return; | |||||
IsFreed = true; | |||||
GC.SuppressFinalize(this); | |||||
if (Discord.StateManager.TryGetMessageStore(Channel.Id, out var store)) | |||||
store.RemoveReference(Id); | |||||
} | |||||
void ICached<Model>.Update(Model model) => Update(model); | |||||
Model ICached<Model>.ToModel() => ToModel(); | |||||
bool ICached.IsFreed => IsFreed; | |||||
#endregion | |||||
} | } | ||||
} | } |
@@ -20,12 +20,19 @@ namespace Discord.WebSocket | |||||
/// </returns> | /// </returns> | ||||
public ulong UserId { get; } | public ulong UserId { get; } | ||||
/// <summary> | /// <summary> | ||||
/// Gets the ID of the message that has been reacted to. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A message snowflake identifier associated with the message. | |||||
/// </returns> | |||||
public ulong MessageId { get; } | |||||
/// <summary> | |||||
/// Gets the user who added the reaction if possible. | /// Gets the user who added the reaction if possible. | ||||
/// </summary> | /// </summary> | ||||
/// <remarks> | /// <remarks> | ||||
/// <para> | /// <para> | ||||
/// This property attempts to retrieve a WebSocket-cached user that is responsible for this reaction from | /// This property attempts to retrieve a WebSocket-cached user that is responsible for this reaction from | ||||
/// the client. In other words, when the user is not in the WebSocket cache, this property may not | |||||
/// the client. In other words, when the user is not in the cache, this property may not | |||||
/// contain a value, leaving the only identifiable information to be | /// contain a value, leaving the only identifiable information to be | ||||
/// <see cref="Discord.WebSocket.SocketReaction.UserId" />. | /// <see cref="Discord.WebSocket.SocketReaction.UserId" />. | ||||
/// </para> | /// </para> | ||||
@@ -35,25 +42,16 @@ namespace Discord.WebSocket | |||||
/// </para> | /// </para> | ||||
/// </remarks> | /// </remarks> | ||||
/// <returns> | /// <returns> | ||||
/// A user object where possible; a value is not always returned. | |||||
/// A lazily-cached user object. | |||||
/// </returns> | /// </returns> | ||||
/// <seealso cref="Optional{T}"/> | |||||
public Optional<IUser> User { get; } | |||||
/// <summary> | |||||
/// Gets the ID of the message that has been reacted to. | |||||
/// </summary> | |||||
/// <returns> | |||||
/// A message snowflake identifier associated with the message. | |||||
/// </returns> | |||||
public ulong MessageId { get; } | |||||
public LazyCached<SocketUser> User { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the message that has been reacted to if possible. | /// Gets the message that has been reacted to if possible. | ||||
/// </summary> | /// </summary> | ||||
/// <returns> | /// <returns> | ||||
/// A WebSocket-based message where possible; a value is not always returned. | |||||
/// A lazily-cached message. | |||||
/// </returns> | /// </returns> | ||||
/// <seealso cref="Optional{T}"/> | |||||
public Optional<SocketUserMessage> Message { get; } | |||||
public LazyCached<SocketMessage> Message { get; } | |||||
/// <summary> | /// <summary> | ||||
/// Gets the channel where the reaction takes place in. | /// Gets the channel where the reaction takes place in. | ||||
/// </summary> | /// </summary> | ||||
@@ -64,16 +62,26 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public IEmote Emote { get; } | public IEmote Emote { get; } | ||||
internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<IUser> user, IEmote emoji) | |||||
internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional<SocketUserMessage> message, ulong userId, Optional<SocketUser> user, IEmote emoji) | |||||
{ | { | ||||
Channel = channel; | |||||
var client = ((SocketChannel)channel).Discord; | |||||
MessageId = messageId; | MessageId = messageId; | ||||
Message = message; | |||||
UserId = userId; | UserId = userId; | ||||
User = user; | |||||
Channel = channel; | |||||
Message = message.IsSpecified | |||||
? new LazyCached<SocketMessage>(message.Value) | |||||
: new LazyCached<SocketMessage>(messageId, client.StateManager.GetMessageStore(channel.Id)); | |||||
User = user.IsSpecified | |||||
? new LazyCached<SocketUser>(user.Value) | |||||
: new LazyCached<SocketUser>(userId, client.StateManager.UserStore); | |||||
Emote = emoji; | Emote = emoji; | ||||
} | } | ||||
internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional<SocketUserMessage> message, Optional<IUser> user) | |||||
internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional<SocketUserMessage> message, Optional<SocketUser> user) | |||||
{ | { | ||||
IEmote emote; | IEmote emote; | ||||
if (model.Emoji.Id.HasValue) | if (model.Emoji.Id.HasValue) | ||||
@@ -86,11 +94,14 @@ namespace Discord.WebSocket | |||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
public override bool Equals(object other) | public override bool Equals(object other) | ||||
{ | { | ||||
if (other == null) return false; | |||||
if (other == this) return true; | |||||
if (other == null) | |||||
return false; | |||||
if (other == this) | |||||
return true; | |||||
var otherReaction = other as SocketReaction; | var otherReaction = other as SocketReaction; | ||||
if (otherReaction == null) return false; | |||||
if (otherReaction == null) | |||||
return false; | |||||
return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); | return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); | ||||
} | } | ||||
@@ -5,7 +5,7 @@ using System.Collections.Immutable; | |||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Linq; | using System.Linq; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.Message; | |||||
using Model = Discord.IMessageModel; | |||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
{ | { | ||||
@@ -17,11 +17,12 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
private bool _isMentioningEveryone, _isTTS, _isPinned; | private bool _isMentioningEveryone, _isTTS, _isPinned; | ||||
private long? _editedTimestampTicks; | private long? _editedTimestampTicks; | ||||
private IUserMessage _referencedMessage; | |||||
private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | private ImmutableArray<Attachment> _attachments = ImmutableArray.Create<Attachment>(); | ||||
private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); | private ImmutableArray<Embed> _embeds = ImmutableArray.Create<Embed>(); | ||||
private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | private ImmutableArray<ITag> _tags = ImmutableArray.Create<ITag>(); | ||||
private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>(); | |||||
private ulong[] _roleMentions; | |||||
private ulong? _referencedMessageId; | |||||
//private ImmutableArray<SocketRole> _roleMentions = ImmutableArray.Create<SocketRole>(); | |||||
private ImmutableArray<SocketSticker> _stickers = ImmutableArray.Create<SocketSticker>(); | private ImmutableArray<SocketSticker> _stickers = ImmutableArray.Create<SocketSticker>(); | ||||
/// <inheritdoc /> | /// <inheritdoc /> | ||||
@@ -56,30 +57,26 @@ namespace Discord.WebSocket | |||||
internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model) | internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientStateManager state, SocketUser author, ISocketMessageChannel channel, Model model) | ||||
{ | { | ||||
var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); | ||||
entity.Update(state, model); | |||||
entity.Update(model); | |||||
return entity; | return entity; | ||||
} | } | ||||
internal override void Update(ClientStateManager state, Model model) | |||||
internal override void Update(Model model) | |||||
{ | { | ||||
base.Update(state, model); | |||||
base.Update(model); | |||||
SocketGuild guild = (Channel as SocketGuildChannel)?.Guild; | SocketGuild guild = (Channel as SocketGuildChannel)?.Guild; | ||||
if (model.IsTextToSpeech.IsSpecified) | |||||
_isTTS = model.IsTextToSpeech.Value; | |||||
if (model.Pinned.IsSpecified) | |||||
_isPinned = model.Pinned.Value; | |||||
if (model.EditedTimestamp.IsSpecified) | |||||
_editedTimestampTicks = model.EditedTimestamp.Value?.UtcTicks; | |||||
if (model.MentionEveryone.IsSpecified) | |||||
_isMentioningEveryone = model.MentionEveryone.Value; | |||||
if (model.RoleMentions.IsSpecified) | |||||
_roleMentions = model.RoleMentions.Value.Select(x => guild.GetRole(x)).ToImmutableArray(); | |||||
if (model.Attachments.IsSpecified) | |||||
_isTTS = model.IsTextToSpeech; | |||||
_isPinned = model.Pinned; | |||||
_editedTimestampTicks = model.EditedTimestamp; | |||||
_isMentioningEveryone = model.MentionEveryone; | |||||
_roleMentions = model.RoleMentionIds; | |||||
_referencedMessageId = model.ReferenceMessageId; | |||||
if (model.Attachments != null && model.Attachments.Length > 0) | |||||
{ | { | ||||
var value = model.Attachments.Value; | |||||
var value = model.Attachments; | |||||
if (value.Length > 0) | if (value.Length > 0) | ||||
{ | { | ||||
var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length); | var attachments = ImmutableArray.CreateBuilder<Attachment>(value.Length); | ||||
@@ -91,9 +88,9 @@ namespace Discord.WebSocket | |||||
_attachments = ImmutableArray.Create<Attachment>(); | _attachments = ImmutableArray.Create<Attachment>(); | ||||
} | } | ||||
if (model.Embeds.IsSpecified) | |||||
if (model.Embeds != null && model.Embeds.Length > 0) | |||||
{ | { | ||||
var value = model.Embeds.Value; | |||||
var value = model.Embeds; | |||||
if (value.Length > 0) | if (value.Length > 0) | ||||
{ | { | ||||
var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length); | var embeds = ImmutableArray.CreateBuilder<Embed>(value.Length); | ||||
@@ -105,41 +102,16 @@ namespace Discord.WebSocket | |||||
_embeds = ImmutableArray.Create<Embed>(); | _embeds = ImmutableArray.Create<Embed>(); | ||||
} | } | ||||
if (model.Content.IsSpecified) | |||||
if (model.Content != null) | |||||
{ | { | ||||
var text = model.Content.Value; | |||||
var text = model.Content; | |||||
_tags = MessageHelper.ParseTags(text, Channel, guild, MentionedUsers); | _tags = MessageHelper.ParseTags(text, Channel, guild, MentionedUsers); | ||||
model.Content = text; | model.Content = text; | ||||
} | } | ||||
if (model.ReferencedMessage.IsSpecified && model.ReferencedMessage.Value != null) | |||||
{ | |||||
var refMsg = model.ReferencedMessage.Value; | |||||
ulong? webhookId = refMsg.WebhookId.ToNullable(); | |||||
SocketUser refMsgAuthor = null; | |||||
if (refMsg.Author.IsSpecified) | |||||
{ | |||||
if (guild != null) | |||||
{ | |||||
if (webhookId != null) | |||||
refMsgAuthor = SocketWebhookUser.Create(guild, refMsg.Author.Value, webhookId.Value); | |||||
else | |||||
refMsgAuthor = guild.GetUser(refMsg.Author.Value.Id); | |||||
} | |||||
else | |||||
refMsgAuthor = (Channel as SocketChannel).GetUser(refMsg.Author.Value.Id); | |||||
if (refMsgAuthor == null) | |||||
refMsgAuthor = SocketUnknownUser.Create(Discord, refMsg.Author.Value); | |||||
} | |||||
else | |||||
// Message author wasn't specified in the payload, so create a completely anonymous unknown user | |||||
refMsgAuthor = new SocketUnknownUser(Discord, id: 0); | |||||
_referencedMessage = SocketUserMessage.Create(Discord, state, refMsgAuthor, Channel, refMsg); | |||||
} | |||||
if (model.StickerItems.IsSpecified) | |||||
if (model.Stickers != null && model.Stickers.Length > 0) | |||||
{ | { | ||||
var value = model.StickerItems.Value; | |||||
var value = model.Stickers; | |||||
if (value.Length > 0) | if (value.Length > 0) | ||||
{ | { | ||||
var stickers = ImmutableArray.CreateBuilder<SocketSticker>(value.Length); | var stickers = ImmutableArray.CreateBuilder<SocketSticker>(value.Length); | ||||
@@ -1,7 +1,7 @@ | |||||
using System.Collections.Generic; | using System.Collections.Generic; | ||||
using System.Diagnostics; | using System.Diagnostics; | ||||
using System.Threading.Tasks; | using System.Threading.Tasks; | ||||
using Model = Discord.API.StickerItem; | |||||
using Model = Discord.IStickerItemModel; | |||||
namespace Discord.WebSocket | namespace Discord.WebSocket | ||||
{ | { | ||||
@@ -47,7 +47,7 @@ namespace Discord.WebSocket | |||||
internal void Update(Model model) | internal void Update(Model model) | ||||
{ | { | ||||
Name = model.Name; | Name = model.Name; | ||||
Format = model.FormatType; | |||||
Format = model.Format; | |||||
} | } | ||||
/// <summary> | /// <summary> | ||||
@@ -8,19 +8,26 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
internal static class CacheModelExtensions | internal static class CacheModelExtensions | ||||
{ | { | ||||
public static TDest ToSpecifiedModel<TId, TDest>(this IEntityModel<TId> source, TDest dest) | |||||
where TId : IEquatable<TId> | |||||
where TDest : IEntityModel<TId> | |||||
public static TDest InterfaceCopy<TDest>(this object source) | |||||
where TDest : class, new() | |||||
=> source.InterfaceCopy(new TDest()); | |||||
public static TDest InterfaceCopy<TSource, TDest>(this TSource source, TDest dest) | |||||
where TSource : class | |||||
where TDest : class | |||||
{ | { | ||||
if (source == null || dest == null) | if (source == null || dest == null) | ||||
throw new ArgumentNullException(source == null ? nameof(source) : nameof(dest)); | throw new ArgumentNullException(source == null ? nameof(source) : nameof(dest)); | ||||
if (source == null || dest == null) | |||||
throw new ArgumentNullException(source == null ? nameof(source) : nameof(dest)); | |||||
// get the shared model interface | // get the shared model interface | ||||
var sourceType = source.GetType(); | var sourceType = source.GetType(); | ||||
var destType = dest.GetType(); | var destType = dest.GetType(); | ||||
if (sourceType == destType) | if (sourceType == destType) | ||||
return (TDest)source; | |||||
return source as TDest; | |||||
List<Type> sharedInterfaceModels = new(); | List<Type> sharedInterfaceModels = new(); | ||||
@@ -52,5 +59,12 @@ namespace Discord.WebSocket | |||||
return dest; | return dest; | ||||
} | } | ||||
public static TDest ToSpecifiedModel<TId, TDest>(this IEntityModel<TId> source, TDest dest) | |||||
where TId : IEquatable<TId> | |||||
where TDest : class, IEntityModel<TId> | |||||
{ | |||||
return source.InterfaceCopy(dest); | |||||
} | |||||
} | } | ||||
} | } |
@@ -0,0 +1,16 @@ | |||||
using Discord.WebSocket; | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Linq; | |||||
using System.Text; | |||||
using System.Threading.Tasks; | |||||
namespace Discord | |||||
{ | |||||
public static class EntityCacheExtensions | |||||
{ | |||||
public static ValueTask<IUser> GetUserAsync(this MessageInteraction<SocketUser> interaction, DiscordSocketClient client, | |||||
CacheMode mode, RequestOptions options = null) | |||||
=> client.StateManager.UserStore.GetAsync(interaction.UserId, mode, options); | |||||
} | |||||
} |
@@ -7,6 +7,17 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
internal static class EntityExtensions | internal static class EntityExtensions | ||||
{ | { | ||||
#region Emotes | |||||
public static IEmote ToEntity(this IEmojiModel model) | |||||
{ | |||||
if (model.Id.HasValue) | |||||
return new Emote(model.Id.Value, model.Name, model.IsAnimated); | |||||
else | |||||
return new Emoji(model.Name); | |||||
} | |||||
#endregion | |||||
#region Activity | |||||
public static IActivity ToEntity(this IActivityModel model) | public static IActivity ToEntity(this IActivityModel model) | ||||
{ | { | ||||
#region Custom Status Game | #region Custom Status Game | ||||
@@ -147,5 +158,6 @@ namespace Discord.WebSocket | |||||
{ | { | ||||
return new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable()); | return new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable()); | ||||
} | } | ||||
#endregion | |||||
} | } | ||||
} | } |