@@ -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) | |||