using Discord.API.Rest;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Model = Discord.API.Message;
namespace Discord.Rest
{
internal static class MessageHelper
{
/// Only the author of a message may modify the message.
/// Message content is too long, length must be less or equal to .
public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func,
RequestOptions options)
{
if (msg.Author.Id != client.CurrentUser.Id)
throw new InvalidOperationException("Only the author of a message may modify the message.");
var args = new MessageProperties();
func(args);
var apiArgs = new API.Rest.ModifyMessageParams
{
Content = args.Content,
Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create()
};
return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false);
}
public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
=> DeleteAsync(msg.Channel.Id, msg.Id, client, options);
public static async Task DeleteAsync(ulong channelId, ulong msgId, BaseDiscordClient client,
RequestOptions options)
{
await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false);
}
public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options)
{
await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
}
public static async Task RemoveReactionAsync(IMessage msg, IUser user, IEmote emote, BaseDiscordClient client, RequestOptions options)
{
await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, user.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false);
}
public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options)
{
await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}
public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote,
int? limit, BaseDiscordClient client, RequestOptions options)
{
Preconditions.NotNull(emote, nameof(emote));
var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name);
return new PagedAsyncEnumerable(
DiscordConfig.MaxUserReactionsPerBatch,
async (info, ct) =>
{
var args = new GetReactionUsersParams
{
Limit = info.PageSize
};
if (info.Position != null)
args.AfterUserId = info.Position.Value;
var models = await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false);
return models.Select(x => RestUser.Create(client, x)).ToImmutableArray();
},
nextPage: (info, lastPage) =>
{
if (lastPage.Count != DiscordConfig.MaxUserReactionsPerBatch)
return false;
info.Position = lastPage.Max(x => x.Id);
return true;
},
count: limit
);
}
public static async Task PinAsync(IMessage msg, BaseDiscordClient client,
RequestOptions options)
{
await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}
public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client,
RequestOptions options)
{
await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false);
}
public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, IReadOnlyCollection userMentions)
{
var tags = ImmutableArray.CreateBuilder();
int index = 0;
int codeBlockIndex = 0;
int closeIndex;
// gets the next index of a codeblock stop/start
int IndexOfCode(int startIndex)
{
if (startIndex == -1)
return -1;
// code block has precedence over inline code
var codeIndex = text.IndexOf("```", startIndex);
if (codeIndex != -1)
return codeIndex + 3;
codeIndex = text.IndexOf('`', startIndex);
if (codeIndex != -1)
return codeIndex + 1;
return -1;
}
// checks if the tag being parsed is wrapped in code blocks
bool CheckWrappedCode()
{
closeIndex = 0;
codeBlockIndex = IndexOfCode(codeBlockIndex);
// loop through all code blocks, before the starting index
while (codeBlockIndex != -1 && closeIndex != -1 && closeIndex < index)
{
closeIndex = IndexOfCode(codeBlockIndex);
codeBlockIndex = IndexOfCode(closeIndex);
}
// break if unclosed code block, or code block beyond starting index
return closeIndex == -1 || closeIndex > index;
}
while (true)
{
index = text.IndexOf('<', index);
if (index == -1) break;
int endIndex = text.IndexOf('>', index + 1);
if (endIndex == -1) break;
if (CheckWrappedCode()) break;
string content = text.Substring(index, endIndex - index + 1);
if (MentionUtils.TryParseUser(content, out ulong id))
{
IUser mentionedUser = null;
foreach (var mention in userMentions)
{
if (mention.Id == id)
{
mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
if (mentionedUser == null)
mentionedUser = mention;
break;
}
}
tags.Add(new Tag(TagType.UserMention, index, content.Length, id, mentionedUser));
}
else if (MentionUtils.TryParseChannel(content, out id))
{
IChannel mentionedChannel = null;
if (guild != null)
mentionedChannel = guild.GetChannelAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult();
tags.Add(new Tag(TagType.ChannelMention, index, content.Length, id, mentionedChannel));
}
else if (MentionUtils.TryParseRole(content, out id))
{
IRole mentionedRole = null;
if (guild != null)
mentionedRole = guild.GetRole(id);
tags.Add(new Tag(TagType.RoleMention, index, content.Length, id, mentionedRole));
}
else if (Emote.TryParse(content, out var emoji))
tags.Add(new Tag(TagType.Emoji, index, content.Length, emoji.Id, emoji));
else //Bad Tag
{
index = index + 1;
continue;
}
index = endIndex + 1;
}
index = 0;
codeBlockIndex = 0;
closeIndex = 0;
while (true)
{
index = text.IndexOf("@everyone", index);
if (index == -1) break;
if (CheckWrappedCode()) break;
var tagIndex = FindIndex(tags, index);
if (tagIndex.HasValue)
tags.Insert(tagIndex.Value, new Tag(TagType.EveryoneMention, index, "@everyone".Length, 0, guild?.EveryoneRole));
index++;
}
index = 0;
while (true)
{
index = text.IndexOf("@here", index);
if (index == -1) break;
if (CheckWrappedCode()) break;
var tagIndex = FindIndex(tags, index);
if (tagIndex.HasValue)
tags.Insert(tagIndex.Value, new Tag(TagType.HereMention, index, "@here".Length, 0, guild?.EveryoneRole));
index++;
}
return tags.ToImmutable();
}
private static int? FindIndex(IReadOnlyList tags, int index)
{
int i = 0;
for (; i < tags.Count; i++)
{
var tag = tags[i];
if (index < tag.Index)
break; //Position before this tag
}
if (i > 0 && index < tags[i - 1].Index + tags[i - 1].Length)
return null; //Overlaps tag before this
return i;
}
public static ImmutableArray FilterTagsByKey(TagType type, ImmutableArray tags)
{
return tags
.Where(x => x.Type == type)
.Select(x => x.Key)
.ToImmutableArray();
}
public static ImmutableArray FilterTagsByValue(TagType type, ImmutableArray tags)
{
return tags
.Where(x => x.Type == type)
.Select(x => (T)x.Value)
.Where(x => x != null)
.ToImmutableArray();
}
public static MessageSource GetSource(Model msg)
{
if (msg.Type != MessageType.Default)
return MessageSource.System;
else if (msg.WebhookId.IsSpecified)
return MessageSource.Webhook;
else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true)
return MessageSource.Bot;
return MessageSource.User;
}
}
}