NadekoBot/src/NadekoBot/Modules/Utility/Utility.cs

480 lines
20 KiB
C#
Raw Normal View History

2016-08-13 18:45:08 +00:00
using Discord;
using Discord.Commands;
using NadekoBot.Attributes;
using System;
using System.Linq;
using System.Threading.Tasks;
2016-08-15 14:57:40 +00:00
using System.Text;
using NadekoBot.Extensions;
using System.Reflection;
using NadekoBot.Services.Impl;
2016-12-24 07:05:43 +00:00
using System.Net.Http;
2017-01-05 12:49:50 +00:00
using System.Collections.Concurrent;
using System.Threading;
using ImageSharp;
2017-01-11 13:05:57 +00:00
using System.Collections.Generic;
using Newtonsoft.Json;
2017-01-29 21:54:27 +00:00
using Discord.WebSocket;
using NadekoBot.Services;
2016-08-13 18:45:08 +00:00
namespace NadekoBot.Modules.Utility
{
[NadekoModule("Utility", ".")]
public partial class Utility : NadekoTopLevelModule
2016-08-13 18:45:08 +00:00
{
2017-01-05 12:49:50 +00:00
private static ConcurrentDictionary<ulong, Timer> rotatingRoleColors = new ConcurrentDictionary<ulong, Timer>();
2017-02-06 06:53:27 +00:00
//[NadekoCommand, Usage, Description, Aliases]
//[RequireContext(ContextType.Guild)]
//public async Task Midorina([Remainder] string arg)
//{
// var channel = (ITextChannel)Context.Channel;
// var roleNames = arg?.Split(';');
// if (roleNames == null || roleNames.Length == 0)
// return;
// var j = 0;
// var roles = roleNames.Select(x => Context.Guild.Roles.FirstOrDefault(r => String.Compare(r.Name, x, StringComparison.OrdinalIgnoreCase) == 0))
2017-02-06 06:53:27 +00:00
// .Where(x => x != null)
// .Take(10)
// .ToArray();
// var rnd = new NadekoRandom();
// var reactions = new[] { "🎬", "🐧", "🌍", "🌺", "🚀", "☀", "🌲", "🍒", "🐾", "🏀" }
// .OrderBy(x => rnd.Next())
// .ToArray();
2017-02-06 06:53:27 +00:00
// var roleStrings = roles
// .Select(x => $"{reactions[j++]} -> {x.Name}");
2017-02-06 06:53:27 +00:00
// var msg = await Context.Channel.SendConfirmAsync("Pick a Role",
// string.Join("\n", roleStrings)).ConfigureAwait(false);
2017-02-06 06:53:27 +00:00
// for (int i = 0; i < roles.Length; i++)
// {
// try { await msg.AddReactionAsync(reactions[i]).ConfigureAwait(false); }
// catch (Exception ex) { _log.Warn(ex); }
2017-02-06 06:53:27 +00:00
// await Task.Delay(1000).ConfigureAwait(false);
// }
// msg.OnReaction((r) => Task.Run(async () =>
// {
// try
// {
// var usr = r.User.GetValueOrDefault() as IGuildUser;
// if (usr == null)
// return;
// var index = Array.IndexOf<string>(reactions, r.Emoji.Name);
// if (index == -1)
// return;
// await usr.RemoveRolesAsync(roles[index]);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
// }), (r) => Task.Run(async () =>
// {
// try
// {
// var usr = r.User.GetValueOrDefault() as IGuildUser;
// if (usr == null)
// return;
// var index = Array.IndexOf<string>(reactions, r.Emoji.Name);
// if (index == -1)
// return;
// await usr.RemoveRolesAsync(roles[index]);
// }
// catch (Exception ex)
// {
// _log.Warn(ex);
// }
// }));
2017-02-06 06:53:27 +00:00
//}
2017-01-05 12:49:50 +00:00
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireUserPermission(GuildPermission.ManageRoles)]
2017-01-05 12:49:50 +00:00
[OwnerOnly]
public async Task RotateRoleColor(int timeout, IRole role, params string[] hexes)
{
var channel = (ITextChannel)Context.Channel;
if ((timeout < 60 && timeout != 0) || timeout > 3600)
2017-01-05 12:49:50 +00:00
return;
Timer t;
if (timeout == 0 || hexes.Length == 0)
{
if (rotatingRoleColors.TryRemove(role.Id, out t))
{
t.Change(Timeout.Infinite, Timeout.Infinite);
await channel.SendConfirmAsync($"Stopped rotating colors for the **{role.Name}** role").ConfigureAwait(false);
}
return;
}
2017-01-05 12:49:50 +00:00
var hexColors = hexes.Select(hex =>
{
try { return (ImageSharp.Color?)ImageSharp.Color.FromHex(hex.Replace("#", "")); } catch { return null; }
2017-01-05 12:49:50 +00:00
})
.Where(c => c != null)
.Select(c => c.Value)
.ToArray();
if (!hexColors.Any())
{
await channel.SendMessageAsync("No colors are in the correct format. Use `#00ff00` for example.").ConfigureAwait(false);
return;
}
var images = hexColors.Select(color =>
{
var img = new ImageSharp.Image(50, 50);
img.BackgroundColor(color);
return img;
}).Merge().ToStream();
var i = 0;
t = new Timer(async (_) =>
{
try
{
var color = hexColors[i];
await role.ModifyAsync(r => r.Color = new Discord.Color(color.R, color.G, color.B)).ConfigureAwait(false);
++i;
if (i >= hexColors.Length)
i = 0;
}
catch { }
}, null, 0, timeout * 1000);
rotatingRoleColors.AddOrUpdate(role.Id, t, (key, old) =>
{
old.Change(Timeout.Infinite, Timeout.Infinite);
return t;
});
2017-01-11 13:05:57 +00:00
2017-01-05 12:49:50 +00:00
await channel.SendFileAsync(images, "magicalgirl.jpg", $"Rotating **{role.Name}** role's color.").ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
2017-01-01 11:52:52 +00:00
public async Task TogetherTube()
2016-12-24 07:05:43 +00:00
{
Uri target;
using (var http = new HttpClient())
{
var res = await http.GetAsync("https://togethertube.com/room/create").ConfigureAwait(false);
target = res.RequestMessage.RequestUri;
}
2016-12-31 10:55:12 +00:00
await Context.Channel.EmbedAsync(new EmbedBuilder().WithOkColor()
2016-12-24 07:05:43 +00:00
.WithAuthor(eab => eab.WithIconUrl("https://togethertube.com/assets/img/favicons/favicon-32x32.png")
.WithName("Together Tube")
.WithUrl("https://togethertube.com/"))
2017-01-01 12:28:35 +00:00
.WithDescription($"{Context.User.Mention} Here is your room link:\n{target}"));
2016-12-24 07:05:43 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-08-13 18:45:08 +00:00
[RequireContext(ContextType.Guild)]
2017-01-01 11:52:52 +00:00
public async Task WhosPlaying([Remainder] string game = null)
2016-08-13 18:45:08 +00:00
{
game = game.Trim().ToUpperInvariant();
if (string.IsNullOrWhiteSpace(game))
return;
2017-01-29 21:54:27 +00:00
var socketGuild = Context.Guild as SocketGuild;
if (socketGuild == null)
{
2017-01-29 21:54:27 +00:00
_log.Warn("Can't cast guild to socket guild.");
return;
}
var rng = new NadekoRandom();
var arr = await Task.Run(() => socketGuild.Users
2016-08-15 14:57:40 +00:00
.Where(u => u.Game?.Name?.ToUpperInvariant() == game)
2016-08-13 18:45:08 +00:00
.Select(u => u.Username)
2017-01-29 21:54:27 +00:00
.OrderBy(x => rng.Next())
2017-01-26 07:02:57 +00:00
.Take(60)
2017-01-29 21:54:27 +00:00
.ToArray()).ConfigureAwait(false);
2016-08-13 18:45:08 +00:00
int i = 0;
2017-01-29 21:54:27 +00:00
if (arr.Length == 0)
2016-12-16 18:43:57 +00:00
await Context.Channel.SendErrorAsync("Nobody is playing that game.").ConfigureAwait(false);
2017-01-29 21:54:27 +00:00
else
{
2016-12-16 18:43:57 +00:00
await Context.Channel.SendConfirmAsync("```css\n" + string.Join("\n", arr.GroupBy(item => (i++) / 2)
2016-12-08 15:40:59 +00:00
.Select(ig => string.Concat(ig.Select(el => $"• {el,-27}")))) + "\n```")
.ConfigureAwait(false);
2017-01-29 21:54:27 +00:00
}
2016-08-13 18:45:08 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-08-13 18:45:08 +00:00
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task InRole([Remainder] string roles)
2016-08-15 14:57:40 +00:00
{
2016-08-13 18:45:08 +00:00
if (string.IsNullOrWhiteSpace(roles))
return;
var arg = roles.Split(',').Select(r => r.Trim().ToUpperInvariant());
2016-12-08 15:40:59 +00:00
string send = " **Here is a list of users in those roles:**";
2016-08-13 18:45:08 +00:00
foreach (var roleStr in arg.Where(str => !string.IsNullOrWhiteSpace(str) && str != "@EVERYONE" && str != "EVERYONE"))
{
2016-12-16 18:43:57 +00:00
var role = Context.Guild.Roles.Where(r => r.Name.ToUpperInvariant() == roleStr).FirstOrDefault();
2016-08-13 18:45:08 +00:00
if (role == null) continue;
2016-11-21 00:56:12 +00:00
send += $"```css\n[{role.Name}]\n";
2016-12-17 04:09:04 +00:00
send += string.Join(", ", (await Context.Guild.GetUsersAsync()).Where(u => u.RoleIds.Contains(role.Id)).Select(u => u.ToString()));
2016-11-21 00:56:12 +00:00
send += $"\n```";
2016-08-15 14:57:40 +00:00
}
2016-12-16 18:43:57 +00:00
var usr = Context.User as IGuildUser;
2016-08-15 14:57:40 +00:00
while (send.Length > 2000)
{
2016-12-16 18:43:57 +00:00
if (!usr.GetPermissions((ITextChannel)Context.Channel).ManageMessages)
2016-08-15 14:57:40 +00:00
{
2016-12-16 18:43:57 +00:00
await Context.Channel.SendErrorAsync($"⚠️ {usr.Mention} **you are not allowed to use this command on roles with a lot of users in them to prevent abuse.**").ConfigureAwait(false);
2016-08-15 14:57:40 +00:00
return;
}
var curstr = send.Substring(0, 2000);
2016-12-16 18:43:57 +00:00
await Context.Channel.SendConfirmAsync(curstr.Substring(0,
2016-08-15 14:57:40 +00:00
curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1)).ConfigureAwait(false);
send = curstr.Substring(curstr.LastIndexOf(", ", StringComparison.Ordinal) + 1) +
send.Substring(2000);
2016-08-13 18:45:08 +00:00
}
2016-12-16 18:43:57 +00:00
await Context.Channel.SendConfirmAsync(send).ConfigureAwait(false);
2016-08-15 14:57:40 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-08-15 14:57:40 +00:00
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task CheckMyPerms()
2016-08-15 14:57:40 +00:00
{
StringBuilder builder = new StringBuilder("```http\n");
2016-12-16 18:43:57 +00:00
var user = Context.User as IGuildUser;
var perms = user.GetPermissions((ITextChannel)Context.Channel);
2016-08-15 14:57:40 +00:00
foreach (var p in perms.GetType().GetProperties().Where(p => !p.GetGetMethod().GetParameters().Any()))
{
builder.AppendLine($"{p.Name} : {p.GetValue(perms, null).ToString()}");
}
builder.Append("```");
2016-12-16 18:43:57 +00:00
await Context.Channel.SendConfirmAsync(builder.ToString());
2016-08-15 14:57:40 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-08-15 14:57:40 +00:00
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task UserId(IGuildUser target = null)
2016-08-15 14:57:40 +00:00
{
2016-12-16 18:43:57 +00:00
var usr = target ?? Context.User;
await Context.Channel.SendConfirmAsync($"🆔 of the user **{ usr.Username }** is `{ usr.Id }`").ConfigureAwait(false);
2016-08-15 14:57:40 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-12-16 18:43:57 +00:00
public async Task ChannelId()
2016-08-15 14:57:40 +00:00
{
2016-12-16 18:43:57 +00:00
await Context.Channel.SendConfirmAsync($"🆔 of this channel is `{Context.Channel.Id}`").ConfigureAwait(false);
2016-08-15 14:57:40 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-08-15 14:57:40 +00:00
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task ServerId()
2016-08-15 14:57:40 +00:00
{
await Context.Channel.SendConfirmAsync($"🆔 of this server is `{Context.Guild.Id}`").ConfigureAwait(false);
2016-08-15 14:57:40 +00:00
}
2016-08-13 18:45:08 +00:00
[NadekoCommand, Usage, Description, Aliases]
2016-08-15 14:57:40 +00:00
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public async Task Roles(IGuildUser target, int page = 1)
2016-08-15 14:57:40 +00:00
{
2016-12-16 18:43:57 +00:00
var channel = (ITextChannel)Context.Channel;
var guild = channel.Guild;
const int RolesPerPage = 20;
if (page < 1 || page > 100)
return;
2016-08-15 14:57:40 +00:00
if (target != null)
{
2016-12-21 11:52:01 +00:00
var roles = target.GetRoles().Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
if (!roles.Any())
{
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
2016-12-21 11:52:01 +00:00
}
else
{
await channel.SendConfirmAsync($"⚔ **Page #{page} of roles for {target.Username}**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
2016-12-21 11:52:01 +00:00
}
2016-08-15 14:57:40 +00:00
}
else
{
2016-12-21 11:52:01 +00:00
var roles = guild.Roles.Except(new[] { guild.EveryoneRole }).OrderBy(r => -r.Position).Skip((page - 1) * RolesPerPage).Take(RolesPerPage);
if (!roles.Any())
{
await channel.SendErrorAsync("No roles on this page.").ConfigureAwait(false);
2016-12-21 11:52:01 +00:00
}
else
{
await channel.SendConfirmAsync($"⚔ **Page #{page} of all roles on this server:**", $"```css\n• " + string.Join("\n• ", roles).SanitizeMentions() + "\n```").ConfigureAwait(false);
2016-12-21 11:52:01 +00:00
}
2016-08-15 14:57:40 +00:00
}
2016-08-13 18:45:08 +00:00
}
2016-08-18 21:00:54 +00:00
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
2016-12-16 18:43:57 +00:00
public Task Roles(int page = 1) =>
Roles(null, page);
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
2017-01-08 17:14:10 +00:00
public async Task ChannelTopic([Remainder]ITextChannel channel = null)
{
2017-01-08 17:14:10 +00:00
if (channel == null)
channel = (ITextChannel)Context.Channel;
var topic = channel.Topic;
if (string.IsNullOrWhiteSpace(topic))
await Context.Channel.SendErrorAsync("No topic set.").ConfigureAwait(false);
else
await Context.Channel.SendConfirmAsync("Channel topic", topic).ConfigureAwait(false);
}
2017-01-07 15:33:37 +00:00
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[RequireBotPermission(ChannelPermission.CreateInstantInvite)]
[RequireUserPermission(ChannelPermission.CreateInstantInvite)]
public async Task CreateInvite()
{
var invite = await ((ITextChannel)Context.Channel).CreateInviteAsync(0, null, isUnique: true);
await Context.Channel.SendConfirmAsync($"{Context.User.Mention} https://discord.gg/{invite.Code}");
}
2017-02-04 17:19:03 +00:00
[NadekoCommand, Usage, Description, Aliases]
public async Task ShardStats(int page = 1)
{
2017-02-06 07:35:45 +00:00
if (page < 1)
2017-02-04 17:19:03 +00:00
return;
2017-02-06 07:35:45 +00:00
var status = string.Join(", ", NadekoBot.Client.Shards.GroupBy(x => x.ConnectionState)
.Select(x => $"{x.Count()} shards {x.Key}")
.ToArray());
2017-02-04 17:19:03 +00:00
2017-02-06 07:35:45 +00:00
var allShardStrings = NadekoBot.Client.Shards
.Select(x => $"Shard **#{x.ShardId.ToString()}** is in {Format.Bold(x.ConnectionState.ToString())} state with {Format.Bold(x.Guilds.Count.ToString())} servers")
.ToArray();
2017-02-04 17:19:03 +00:00
2017-02-06 07:35:45 +00:00
await Context.Channel.SendPaginatedConfirmAsync(page, (curPage) =>
{
2017-02-06 07:35:45 +00:00
var str = string.Join("\n", allShardStrings.Skip(25 * (curPage - 1)).Take(25));
if (string.IsNullOrWhiteSpace(str))
str = "No shards on this page.";
return new EmbedBuilder()
.WithAuthor(a => a.WithName("Shard Stats"))
.WithTitle(status)
.WithOkColor()
.WithDescription(str);
}, allShardStrings.Length / 25);
2017-02-04 17:19:03 +00:00
}
2017-02-05 05:36:07 +00:00
[NadekoCommand, Usage, Description, Aliases]
public async Task ShardId(ulong guildid)
{
var shardId = NadekoBot.Client.GetShardIdFor(guildid);
await Context.Channel.SendConfirmAsync($"ShardId for **{guildid}** with {NadekoBot.Client.Shards.Count} total shards", shardId.ToString()).ConfigureAwait(false);
}
[NadekoCommand, Usage, Description, Aliases]
2016-12-16 18:43:57 +00:00
public async Task Stats()
2016-08-18 21:00:54 +00:00
{
var stats = NadekoBot.Stats;
2017-02-04 17:19:03 +00:00
var shardId = Context.Guild != null
? NadekoBot.Client.GetShardIdFor(Context.Guild.Id)
: 0;
await Context.Channel.EmbedAsync(
new EmbedBuilder().WithOkColor()
2016-12-16 18:43:57 +00:00
.WithAuthor(eab => eab.WithName($"NadekoBot v{StatsService.BotVersion}")
.WithUrl("http://nadekobot.readthedocs.io/en/latest/")
.WithIconUrl("https://cdn.discordapp.com/avatars/116275390695079945/b21045e778ef21c96d175400e779f0fb.jpg"))
.AddField(efb => efb.WithName(Format.Bold("Author")).WithValue(stats.Author).WithIsInline(true))
2017-01-15 01:08:14 +00:00
.AddField(efb => efb.WithName(Format.Bold("Bot ID")).WithValue(NadekoBot.Client.CurrentUser.Id.ToString()).WithIsInline(true))
2017-02-06 06:53:27 +00:00
.AddField(efb => efb.WithName(Format.Bold("Shard")).WithValue($"#{shardId}, {NadekoBot.Client.Shards.Count} total").WithIsInline(true))
2016-12-16 18:43:57 +00:00
.AddField(efb => efb.WithName(Format.Bold("Commands Ran")).WithValue(stats.CommandsRan.ToString()).WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Messages")).WithValue($"{stats.MessageCounter} ({stats.MessagesPerSecond:F2}/sec)").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Memory")).WithValue($"{stats.Heap} MB").WithIsInline(true))
.AddField(efb => efb.WithName(Format.Bold("Owner ID(s)")).WithValue(string.Join("\n", NadekoBot.Credentials.OwnerIds)).WithIsInline(true))
2016-12-16 18:43:57 +00:00
.AddField(efb => efb.WithName(Format.Bold("Uptime")).WithValue(stats.GetUptimeString("\n")).WithIsInline(true))
2017-01-15 01:28:33 +00:00
.AddField(efb => efb.WithName(Format.Bold("Presence")).WithValue($"{NadekoBot.Client.GetGuildCount()} Servers\n{stats.TextChannels} Text Channels\n{stats.VoiceChannels} Voice Channels").WithIsInline(true))
2017-02-06 06:53:27 +00:00
#if !GLOBAL_NADEKO
.WithFooter(efb => efb.WithText($"Playing {Music.Music.MusicPlayers.Where(mp => mp.Value.CurrentSong != null).Count()} songs, {Music.Music.MusicPlayers.Sum(mp => mp.Value.Playlist.Count)} queued."))
#endif
);
2016-08-18 21:00:54 +00:00
}
[NadekoCommand, Usage, Description, Aliases]
2016-12-16 18:43:57 +00:00
public async Task Showemojis([Remainder] string emojis)
{
2017-01-14 22:23:11 +00:00
var tags = Context.Message.Tags.Where(t => t.Type == TagType.Emoji).Select(t => (Emoji)t.Value);
2017-01-14 22:23:11 +00:00
var result = string.Join("\n", tags.Select(m => $"**Name:** {m} **Link:** {m.Url}"));
2016-12-08 17:35:34 +00:00
if (string.IsNullOrWhiteSpace(result))
2016-12-16 18:43:57 +00:00
await Context.Channel.SendErrorAsync("No special emojis found.");
2016-12-08 17:35:34 +00:00
else
2016-12-16 18:43:57 +00:00
await Context.Channel.SendMessageAsync(result).ConfigureAwait(false);
}
2016-10-28 11:31:21 +00:00
[NadekoCommand, Usage, Description, Aliases]
[OwnerOnly]
public async Task ListServers(int page = 1)
2016-10-28 11:31:21 +00:00
{
page -= 1;
if (page < 0)
return;
var guilds = await Task.Run(() => NadekoBot.Client.GetGuilds().OrderBy(g => g.Name).Skip((page - 1) * 15).Take(15)).ConfigureAwait(false);
2016-10-28 11:31:21 +00:00
if (!guilds.Any())
{
await Context.Channel.SendErrorAsync("No servers found on that page.").ConfigureAwait(false);
2016-10-28 11:31:21 +00:00
return;
}
await Context.Channel.EmbedAsync(guilds.Aggregate(new EmbedBuilder().WithOkColor(),
2016-12-08 15:40:59 +00:00
(embed, g) => embed.AddField(efb => efb.WithName(g.Name)
2016-12-17 00:21:05 +00:00
.WithValue($"```css\nID: {g.Id}\nMembers: {g.Users.Count}\nOwnerID: {g.OwnerId} ```")
2016-12-16 18:43:57 +00:00
.WithIsInline(false))))
2016-12-08 15:40:59 +00:00
.ConfigureAwait(false);
2016-10-28 11:31:21 +00:00
}
2017-01-11 13:05:57 +00:00
[NadekoCommand, Usage, Description, Aliases]
[RequireContext(ContextType.Guild)]
[OwnerOnly]
public async Task SaveChat(int cnt)
{
var msgs = new List<IMessage>(cnt);
await Context.Channel.GetMessagesAsync(cnt).ForEachAsync(dled => msgs.AddRange(dled)).ConfigureAwait(false);
var title = $"Chatlog-{Context.Guild.Name}/#{Context.Channel.Name}-{DateTime.Now}.txt";
var grouping = msgs.GroupBy(x => $"{x.CreatedAt.Date:dd.MM.yyyy}")
.Select(g => new { date = g.Key, messages = g.OrderBy(x => x.CreatedAt).Select(s => $"【{s.Timestamp:HH:mm:ss}】{s.Author}:" + s.ToString()) });
await Context.User.SendFileAsync(
await JsonConvert.SerializeObject(grouping, Formatting.Indented).ToStream().ConfigureAwait(false), title, title).ConfigureAwait(false);
}
2016-08-13 18:45:08 +00:00
}
2017-02-06 06:53:27 +00:00
}