2019-07-26 10:05:09 +00:00
using System.Collections.Generic ;
2019-12-22 11:50:47 +00:00
using System.Diagnostics ;
2019-07-16 19:59:06 +00:00
using System.Linq ;
2019-04-29 18:21:16 +00:00
using System.Threading.Tasks ;
2019-07-16 19:59:06 +00:00
using App.Metrics ;
2019-12-22 11:50:47 +00:00
2019-04-29 18:21:16 +00:00
using Discord ;
2019-12-22 11:50:47 +00:00
2019-07-26 10:05:09 +00:00
using Humanizer ;
2019-04-29 18:21:16 +00:00
2019-12-22 11:50:47 +00:00
using NodaTime ;
2019-10-05 05:41:00 +00:00
using PluralKit.Bot.CommandSystem ;
2019-12-22 11:50:47 +00:00
using PluralKit.Core ;
2019-10-05 05:41:00 +00:00
2019-05-21 21:40:26 +00:00
namespace PluralKit.Bot.Commands {
2019-10-05 05:41:00 +00:00
public class MiscCommands
{
private BotConfig _botConfig ;
private IMetrics _metrics ;
2019-12-22 11:50:47 +00:00
private CpuStatService _cpu ;
private ShardInfoService _shards ;
2019-10-05 05:41:00 +00:00
2019-12-22 11:50:47 +00:00
public MiscCommands ( BotConfig botConfig , IMetrics metrics , CpuStatService cpu , ShardInfoService shards )
2019-10-05 05:41:00 +00:00
{
_botConfig = botConfig ;
_metrics = metrics ;
2019-12-22 11:50:47 +00:00
_cpu = cpu ;
_shards = shards ;
2019-10-05 05:41:00 +00:00
}
2019-06-15 10:03:07 +00:00
2019-10-05 05:41:00 +00:00
public async Task Invite ( Context ctx )
2019-06-15 10:03:07 +00:00
{
2019-10-05 05:41:00 +00:00
var clientId = _botConfig . ClientId ? ? ( await ctx . Client . GetApplicationInfoAsync ( ) ) . Id ;
2019-04-29 18:21:16 +00:00
var permissions = new GuildPermissions (
addReactions : true ,
attachFiles : true ,
embedLinks : true ,
manageMessages : true ,
manageWebhooks : true ,
readMessageHistory : true ,
sendMessages : true
) ;
2019-06-15 10:03:07 +00:00
var invite = $"https://discordapp.com/oauth2/authorize?client_id={clientId}&scope=bot&permissions={permissions.RawValue}" ;
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} Use this link to add PluralKit to your server:\n<{invite}>" ) ;
2019-04-29 18:21:16 +00:00
}
2019-10-05 05:41:00 +00:00
public Task Mn ( Context ctx ) = > ctx . Reply ( "Gotta catch 'em all!" ) ;
public Task Fire ( Context ctx ) = > ctx . Reply ( "*A giant lightning bolt promptly erupts into a pillar of fire as it hits your opponent.*" ) ;
public Task Thunder ( Context ctx ) = > ctx . Reply ( "*A giant ball of lightning is conjured and fired directly at your opponent, vanquishing them.*" ) ;
public Task Freeze ( Context ctx ) = > ctx . Reply ( "*A giant crystal ball of ice is charged and hurled toward your opponent, bursting open and freezing them solid on contact.*" ) ;
public Task Starstorm ( Context ctx ) = > ctx . Reply ( "*Vibrant colours burst forth from the sky as meteors rain down upon your opponent.*" ) ;
2019-10-27 22:03:20 +00:00
public Task Flash ( Context ctx ) = > ctx . Reply ( "*A ball of green light appears above your head and flies towards your enemy, exploding on contact.*" ) ;
2019-07-10 11:57:59 +00:00
2019-10-05 05:41:00 +00:00
public async Task Stats ( Context ctx )
2019-07-16 19:59:06 +00:00
{
2019-12-22 11:50:47 +00:00
var msg = await ctx . Reply ( $"..." ) ;
2019-10-05 05:41:00 +00:00
var messagesReceived = _metrics . Snapshot . GetForContext ( "Bot" ) . Meters . First ( m = > m . MultidimensionalName = = BotMetrics . MessagesReceived . Name ) . Value ;
var messagesProxied = _metrics . Snapshot . GetForContext ( "Bot" ) . Meters . First ( m = > m . MultidimensionalName = = BotMetrics . MessagesProxied . Name ) . Value ;
var commandsRun = _metrics . Snapshot . GetForContext ( "Bot" ) . Meters . First ( m = > m . MultidimensionalName = = BotMetrics . CommandsRun . Name ) . Value ;
2019-12-22 11:50:47 +00:00
var totalSystems = _metrics . Snapshot . GetForContext ( "Application" ) . Gauges . First ( m = > m . MultidimensionalName = = CoreMetrics . SystemCount . Name ) . Value ;
var totalMembers = _metrics . Snapshot . GetForContext ( "Application" ) . Gauges . First ( m = > m . MultidimensionalName = = CoreMetrics . MemberCount . Name ) . Value ;
var totalSwitches = _metrics . Snapshot . GetForContext ( "Application" ) . Gauges . First ( m = > m . MultidimensionalName = = CoreMetrics . SwitchCount . Name ) . Value ;
var totalMessages = _metrics . Snapshot . GetForContext ( "Application" ) . Gauges . First ( m = > m . MultidimensionalName = = CoreMetrics . MessageCount . Name ) . Value ;
var shardId = ctx . Shard . ShardId ;
var shardTotal = ctx . Client . Shards . Count ;
var shardUpTotal = ctx . Client . Shards . Select ( s = > s . ConnectionState = = ConnectionState . Connected ) . Count ( ) ;
var shardInfo = _shards . GetShardInfo ( ctx . Shard ) ;
2019-07-16 19:59:06 +00:00
2019-12-22 11:50:47 +00:00
var process = Process . GetCurrentProcess ( ) ;
var memoryUsage = process . WorkingSet64 ;
var shardUptime = SystemClock . Instance . GetCurrentInstant ( ) - shardInfo . LastConnectionTime ;
var embed = new EmbedBuilder ( )
. AddField ( "Messages processed" , $"{messagesReceived.OneMinuteRate * 60:F1}/m ({messagesReceived.FifteenMinuteRate * 60:F1}/m over 15m)" , true )
. AddField ( "Messages proxied" , $"{messagesProxied.OneMinuteRate * 60:F1}/m ({messagesProxied.FifteenMinuteRate * 60:F1}/m over 15m)" , true )
. AddField ( "Commands executed" , $"{commandsRun.OneMinuteRate * 60:F1}/m ({commandsRun.FifteenMinuteRate * 60:F1}/m over 15m)" , true )
. AddField ( "Current shard" , $"Shard #{shardId} (of {shardTotal} total, {shardUpTotal} are up)" , true )
. AddField ( "Shard uptime" , $"{Formats.DurationFormat.Format(shardUptime)} ({shardInfo.DisconnectionCount} disconnections)" , true )
2019-12-22 13:37:55 +00:00
. AddField ( "CPU usage" , $"{_cpu.LastCpuMeasure:P1}" , true )
2019-12-22 11:50:47 +00:00
. AddField ( "Memory usage" , $"{memoryUsage / 1024 / 1024} MiB" , true )
. AddField ( "Latency" , $"API: {(msg.Timestamp - ctx.Message.Timestamp).TotalMilliseconds:F0} ms, shard: {shardInfo.ShardLatency} ms" , true )
2019-12-22 13:37:55 +00:00
. AddField ( "Total numbers" , $"{totalSystems:N0} systems, {totalMembers:N0} members, {totalSwitches:N0} switches, {totalMessages:N0} messages" ) ;
2019-12-22 11:50:47 +00:00
await msg . ModifyAsync ( f = >
{
2019-12-22 13:35:18 +00:00
f . Content = "" ;
2019-12-22 11:50:47 +00:00
f . Embed = embed . Build ( ) ;
} ) ;
2019-07-16 19:59:06 +00:00
}
2019-10-05 05:41:00 +00:00
public async Task PermCheckGuild ( Context ctx )
2019-07-26 10:05:09 +00:00
{
2019-10-27 20:58:04 +00:00
IGuild guild ;
if ( ctx . Guild ! = null & & ! ctx . HasNext ( ) )
{
guild = ctx . Guild ;
}
else
{
var guildIdStr = ctx . RemainderOrNull ( ) ? ? throw new PKSyntaxError ( "You must pass a server ID or run this command as ." ) ;
if ( ! ulong . TryParse ( guildIdStr , out var guildId ) )
throw new PKSyntaxError ( $"Could not parse `{guildIdStr.SanitizeMentions()}` as an ID." ) ;
// TODO: will this call break for sharding if you try to request a guild on a different bot instance?
guild = ctx . Client . GetGuild ( guildId ) ;
if ( guild = = null )
throw Errors . GuildNotFound ( guildId ) ;
}
2019-07-26 10:05:09 +00:00
var requiredPermissions = new [ ]
{
2019-10-27 20:58:04 +00:00
ChannelPermission . ViewChannel ,
ChannelPermission . SendMessages ,
ChannelPermission . AddReactions ,
ChannelPermission . AttachFiles ,
ChannelPermission . EmbedLinks ,
2019-07-26 10:05:09 +00:00
ChannelPermission . ManageMessages ,
ChannelPermission . ManageWebhooks
} ;
// Loop through every channel and group them by sets of permissions missing
var permissionsMissing = new Dictionary < ulong , List < ITextChannel > > ( ) ;
foreach ( var channel in await guild . GetTextChannelsAsync ( ) )
{
// TODO: do we need to hide channels here to prevent info-leaking?
var perms = await channel . PermissionsIn ( ) ;
// We use a bitfield so we can set individual permission bits in the loop
ulong missingPermissionField = 0 ;
foreach ( var requiredPermission in requiredPermissions )
if ( ! perms . Has ( requiredPermission ) )
missingPermissionField | = ( ulong ) requiredPermission ;
// If we're not missing any permissions, don't bother adding it to the dict
// This means we can check if the dict is empty to see if all channels are proxyable
if ( missingPermissionField ! = 0 )
{
permissionsMissing . TryAdd ( missingPermissionField , new List < ITextChannel > ( ) ) ;
permissionsMissing [ missingPermissionField ] . Add ( channel ) ;
}
}
// Generate the output embed
var eb = new EmbedBuilder ( )
2019-10-18 11:14:36 +00:00
. WithTitle ( $"Permission check for **{guild.Name.SanitizeMentions()}**" ) ;
2019-07-26 10:05:09 +00:00
if ( permissionsMissing . Count = = 0 )
{
eb . WithDescription ( $"No errors found, all channels proxyable :)" ) . WithColor ( Color . Green ) ;
}
else
{
foreach ( var ( missingPermissionField , channels ) in permissionsMissing )
{
// Each missing permission field can have multiple missing channels
// so we extract them all and generate a comma-separated list
var missingPermissionNames = string . Join ( ", " , new ChannelPermissions ( missingPermissionField )
. ToList ( )
. Select ( perm = > perm . Humanize ( ) . Transform ( To . TitleCase ) ) ) ;
var channelsList = string . Join ( "\n" , channels
. OrderBy ( c = > c . Position )
. Select ( c = > $"#{c.Name}" ) ) ;
2019-10-27 20:58:04 +00:00
eb . AddField ( $"Missing *{missingPermissionNames}*" , channelsList . Truncate ( 1000 ) ) ;
2019-07-26 10:05:09 +00:00
eb . WithColor ( Color . Red ) ;
}
}
// Send! :)
2019-10-05 05:41:00 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
2019-07-26 10:05:09 +00:00
}
2019-04-29 18:21:16 +00:00
}
}