| @@ -13,8 +13,8 @@ namespace Discord.API | |||
| [JsonProperty("url")] | |||
| public string Url { get; set; } | |||
| [JsonProperty("thumbnail")] | |||
| public EmbedThumbnail Thumbnail { get; set; } | |||
| public Optional<EmbedThumbnail> Thumbnail { get; set; } | |||
| [JsonProperty("provider")] | |||
| public EmbedProvider Provider { get; set; } | |||
| public Optional<EmbedProvider> Provider { get; set; } | |||
| } | |||
| } | |||
| @@ -10,24 +10,22 @@ namespace Discord.API | |||
| [JsonProperty("channel_id")] | |||
| public ulong ChannelId { get; set; } | |||
| [JsonProperty("author")] | |||
| public User Author { get; set; } | |||
| public Optional<User> Author { get; set; } | |||
| [JsonProperty("content")] | |||
| public string Content { get; set; } | |||
| public Optional<string> Content { get; set; } | |||
| [JsonProperty("timestamp")] | |||
| public DateTime Timestamp { get; set; } | |||
| public Optional<DateTime> Timestamp { get; set; } | |||
| [JsonProperty("edited_timestamp")] | |||
| public DateTime? EditedTimestamp { get; set; } | |||
| public Optional<DateTime?> EditedTimestamp { get; set; } | |||
| [JsonProperty("tts")] | |||
| public bool IsTextToSpeech { get; set; } | |||
| public Optional<bool> IsTextToSpeech { get; set; } | |||
| [JsonProperty("mention_everyone")] | |||
| public bool IsMentioningEveryone { get; set; } | |||
| public Optional<bool> IsMentioningEveryone { get; set; } | |||
| [JsonProperty("mentions")] | |||
| public User[] Mentions { get; set; } | |||
| public Optional<User[]> Mentions { get; set; } | |||
| [JsonProperty("attachments")] | |||
| public Attachment[] Attachments { get; set; } | |||
| public Optional<Attachment[]> Attachments { get; set; } | |||
| [JsonProperty("embeds")] | |||
| public Embed[] Embeds { get; set; } | |||
| /*[JsonProperty("nonce")] | |||
| public object Nonce { get; set; }*/ | |||
| public Optional<Embed[]> Embeds { get; set; } | |||
| } | |||
| } | |||
| @@ -729,7 +729,7 @@ namespace Discord | |||
| var channel = DataStore.GetChannel(data.ChannelId) as ICachedMessageChannel; | |||
| if (channel != null) | |||
| { | |||
| var author = channel.GetUser(data.Author.Id); | |||
| var author = channel.GetUser(data.Author.Value.Id); | |||
| if (author != null) | |||
| { | |||
| @@ -70,7 +70,7 @@ namespace Discord | |||
| { | |||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.CreateDMMessageAsync(Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| { | |||
| @@ -79,33 +79,33 @@ namespace Discord | |||
| { | |||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, file, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| } | |||
| public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| { | |||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadDMFileAsync(Id, stream, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| public virtual async Task<IMessage> GetMessageAsync(ulong id) | |||
| { | |||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| return null; | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray(); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author.Value), x)).ToImmutableArray(); | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray(); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author.Value), x)).ToImmutableArray(); | |||
| } | |||
| public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||
| { | |||
| @@ -62,7 +62,7 @@ namespace Discord | |||
| { | |||
| var args = new CreateMessageParams { Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.CreateMessageAsync(Guild.Id, Id, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| public async Task<IMessage> SendFileAsync(string filePath, string text, bool isTTS) | |||
| { | |||
| @@ -71,33 +71,33 @@ namespace Discord | |||
| { | |||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, file, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| } | |||
| public async Task<IMessage> SendFileAsync(Stream stream, string filename, string text, bool isTTS) | |||
| { | |||
| var args = new UploadFileParams { Filename = filename, Content = text, IsTTS = isTTS }; | |||
| var model = await Discord.ApiClient.UploadFileAsync(Guild.Id, Id, stream, args).ConfigureAwait(false); | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| } | |||
| public virtual async Task<IMessage> GetMessageAsync(ulong id) | |||
| { | |||
| var model = await Discord.ApiClient.GetChannelMessageAsync(Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new Message(this, new User(Discord, model.Author), model); | |||
| return new Message(this, new User(Discord, model.Author.Value), model); | |||
| return null; | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray(); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author.Value), x)).ToImmutableArray(); | |||
| } | |||
| public virtual async Task<IReadOnlyCollection<IMessage>> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit) | |||
| { | |||
| var args = new GetChannelMessagesParams { Limit = limit }; | |||
| var models = await Discord.ApiClient.GetChannelMessagesAsync(Id, args).ConfigureAwait(false); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author), x)).ToImmutableArray(); | |||
| return models.Select(x => new Message(this, new User(Discord, x.Author.Value), x)).ToImmutableArray(); | |||
| } | |||
| public async Task DeleteMessagesAsync(IEnumerable<IMessage> messages) | |||
| { | |||
| @@ -18,8 +18,10 @@ namespace Discord | |||
| Title = model.Title; | |||
| Description = model.Description; | |||
| Provider = new EmbedProvider(model.Provider); | |||
| Thumbnail = new EmbedThumbnail(model.Thumbnail); | |||
| if (model.Provider.IsSpecified) | |||
| Provider = new EmbedProvider(model.Provider.Value); | |||
| if (model.Thumbnail.IsSpecified) | |||
| Thumbnail = new EmbedThumbnail(model.Thumbnail.Value); | |||
| } | |||
| } | |||
| } | |||
| @@ -10,7 +10,9 @@ namespace Discord | |||
| { | |||
| [DebuggerDisplay(@"{DebuggerDisplay,nq}")] | |||
| internal class Message : SnowflakeEntity, IMessage | |||
| { | |||
| { | |||
| private bool _isMentioningEveryone; | |||
| public DateTime? EditedTimestamp { get; private set; } | |||
| public bool IsTTS { get; private set; } | |||
| public string RawText { get; private set; } | |||
| @@ -34,6 +36,13 @@ namespace Discord | |||
| Channel = channel; | |||
| Author = author; | |||
| if (channel is IGuildChannel) | |||
| { | |||
| MentionedUsers = ImmutableArray.Create<User>(); | |||
| MentionedChannelIds = ImmutableArray.Create<ulong>(); | |||
| MentionedRoleIds = ImmutableArray.Create<ulong>(); | |||
| } | |||
| Update(model, UpdateSource.Creation); | |||
| } | |||
| public void Update(Model model, UpdateSource source) | |||
| @@ -44,57 +53,73 @@ namespace Discord | |||
| var guild = guildChannel?.Guild; | |||
| var discord = Discord; | |||
| IsTTS = model.IsTextToSpeech; | |||
| Timestamp = model.Timestamp; | |||
| EditedTimestamp = model.EditedTimestamp; | |||
| RawText = model.Content; | |||
| if (model.Attachments.Length > 0) | |||
| if (model.IsTextToSpeech.IsSpecified) | |||
| IsTTS = model.IsTextToSpeech.Value; | |||
| if (model.Timestamp.IsSpecified) | |||
| Timestamp = model.Timestamp.Value; | |||
| if (model.EditedTimestamp.IsSpecified) | |||
| EditedTimestamp = model.EditedTimestamp.Value; | |||
| if (model.IsMentioningEveryone.IsSpecified) | |||
| _isMentioningEveryone = model.IsMentioningEveryone.Value; | |||
| if (model.Attachments.IsSpecified) | |||
| { | |||
| var attachments = new Attachment[model.Attachments.Length]; | |||
| for (int i = 0; i < attachments.Length; i++) | |||
| attachments[i] = new Attachment(model.Attachments[i]); | |||
| Attachments = ImmutableArray.Create(attachments); | |||
| var value = model.Attachments.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var attachments = new Attachment[value.Length]; | |||
| for (int i = 0; i < attachments.Length; i++) | |||
| attachments[i] = new Attachment(value[i]); | |||
| Attachments = ImmutableArray.Create(attachments); | |||
| } | |||
| else | |||
| Attachments = ImmutableArray.Create<Attachment>(); | |||
| } | |||
| else | |||
| Attachments = ImmutableArray.Create<Attachment>(); | |||
| if (model.Embeds.Length > 0) | |||
| if (model.Embeds.IsSpecified) | |||
| { | |||
| var embeds = new Embed[model.Attachments.Length]; | |||
| for (int i = 0; i < embeds.Length; i++) | |||
| embeds[i] = new Embed(model.Embeds[i]); | |||
| Embeds = ImmutableArray.Create(embeds); | |||
| var value = model.Embeds.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var embeds = new Embed[value.Length]; | |||
| for (int i = 0; i < embeds.Length; i++) | |||
| embeds[i] = new Embed(value[i]); | |||
| Embeds = ImmutableArray.Create(embeds); | |||
| } | |||
| else | |||
| Embeds = ImmutableArray.Create<Embed>(); | |||
| } | |||
| else | |||
| Embeds = ImmutableArray.Create<Embed>(); | |||
| if (guildChannel != null && model.Mentions.Length > 0) | |||
| if (model.Mentions.IsSpecified) | |||
| { | |||
| var mentions = new User[model.Mentions.Length]; | |||
| for (int i = 0; i < model.Mentions.Length; i++) | |||
| mentions[i] = new User(discord, model.Mentions[i]); | |||
| MentionedUsers = ImmutableArray.Create(mentions); | |||
| var value = model.Mentions.Value; | |||
| if (value.Length > 0) | |||
| { | |||
| var mentions = new User[value.Length]; | |||
| for (int i = 0; i < value.Length; i++) | |||
| mentions[i] = new User(discord, value[i]); | |||
| MentionedUsers = ImmutableArray.Create(mentions); | |||
| } | |||
| else | |||
| MentionedUsers = ImmutableArray.Create<User>(); | |||
| } | |||
| else | |||
| MentionedUsers = ImmutableArray.Create<User>(); | |||
| if (guildChannel != null) | |||
| { | |||
| MentionedChannelIds = MentionUtils.GetChannelMentions(model.Content); | |||
| var mentionedRoleIds = MentionUtils.GetRoleMentions(model.Content); | |||
| if (model.IsMentioningEveryone) | |||
| mentionedRoleIds = mentionedRoleIds.Add(guildChannel.Guild.EveryoneRole.Id); | |||
| MentionedRoleIds = mentionedRoleIds; | |||
| } | |||
| else | |||
| if (model.Content.IsSpecified) | |||
| { | |||
| MentionedChannelIds = ImmutableArray.Create<ulong>(); | |||
| MentionedRoleIds = ImmutableArray.Create<ulong>(); | |||
| RawText = model.Content.Value; | |||
| if (Channel is IGuildChannel) | |||
| { | |||
| Text = MentionUtils.CleanUserMentions(RawText, MentionedUsers); | |||
| MentionedChannelIds = MentionUtils.GetChannelMentions(RawText); | |||
| var mentionedRoleIds = MentionUtils.GetRoleMentions(RawText); | |||
| if (_isMentioningEveryone) | |||
| mentionedRoleIds = mentionedRoleIds.Add(guildChannel.Guild.EveryoneRole.Id); | |||
| MentionedRoleIds = mentionedRoleIds; | |||
| } | |||
| else | |||
| Text = RawText; | |||
| } | |||
| Text = MentionUtils.CleanUserMentions(model.Content, model.Mentions); | |||
| } | |||
| public async Task UpdateAsync() | |||
| @@ -55,6 +55,7 @@ namespace Discord.Net.Converters | |||
| converter = ImageConverter.Instance; | |||
| else if (type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Optional<>)) | |||
| { | |||
| var innerType = type.GenericTypeArguments[0]; | |||
| var typeInput = propInfo.DeclaringType; | |||
| var typeOutput = propInfo.PropertyType; | |||
| @@ -62,9 +63,10 @@ namespace Discord.Net.Converters | |||
| var getterDelegate = propInfo.GetMethod.CreateDelegate(getter); | |||
| var shouldSerialize = _shouldSerialize.MakeGenericMethod(typeInput, typeOutput); | |||
| var shouldSerializeDelegate = (Func<object, Delegate, bool>)shouldSerialize.CreateDelegate(typeof(Func<object, Delegate, bool>)); | |||
| property.ShouldSerialize = x => shouldSerializeDelegate(x, getterDelegate); | |||
| converter = OptionalConverter.Instance; | |||
| var converterType = typeof(OptionalConverter<>).MakeGenericType(innerType); | |||
| converter = converterType.GetTypeInfo().GetDeclaredField("Instance").GetValue(null) as JsonConverter; | |||
| } | |||
| } | |||
| @@ -3,22 +3,22 @@ using System; | |||
| namespace Discord.Net.Converters | |||
| { | |||
| public class OptionalConverter : JsonConverter | |||
| public class OptionalConverter<T> : JsonConverter | |||
| { | |||
| public static readonly OptionalConverter Instance = new OptionalConverter(); | |||
| public static readonly OptionalConverter<T> Instance = new OptionalConverter<T>(); | |||
| public override bool CanConvert(Type objectType) => true; | |||
| public override bool CanRead => false; | |||
| public override bool CanRead => true; | |||
| public override bool CanWrite => true; | |||
| public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) | |||
| { | |||
| throw new InvalidOperationException(); | |||
| return new Optional<T>(serializer.Deserialize<T>(reader)); | |||
| } | |||
| public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) | |||
| { | |||
| serializer.Serialize(writer, (value as IOptional).Value); | |||
| serializer.Serialize(writer, ((Optional<T>)value).Value); | |||
| } | |||
| } | |||
| } | |||
| @@ -82,7 +82,7 @@ namespace Discord | |||
| return builder; | |||
| } | |||
| internal static string CleanUserMentions(string text, API.User[] mentions) | |||
| internal static string CleanUserMentions(string text, ImmutableArray<User> mentions) | |||
| { | |||
| return _userRegex.Replace(text, new MatchEvaluator(e => | |||
| { | |||
| @@ -88,7 +88,7 @@ namespace Discord | |||
| return msg; | |||
| var model = await _discord.ApiClient.GetChannelMessageAsync(_channel.Id, id).ConfigureAwait(false); | |||
| if (model != null) | |||
| return new CachedMessage(_channel, new User(_discord, model.Author), model); | |||
| return new CachedMessage(_channel, new User(_discord, model.Author.Value), model); | |||
| return null; | |||
| } | |||
| public async Task<IReadOnlyCollection<CachedMessage>> DownloadAsync(ulong? fromId, Direction dir, int limit) | |||