2018-07-19 12:42:48 +00:00
using System ;
2020-02-20 13:11:48 +00:00
using System.Diagnostics ;
2018-11-05 11:01:31 +00:00
using System.IO ;
2019-10-31 12:18:04 +00:00
using System.Linq ;
2019-08-24 15:46:10 +00:00
using System.Reflection ;
2018-08-05 14:36:16 +00:00
using System.Threading ;
2018-07-19 12:42:48 +00:00
using System.Threading.Tasks ;
using CompatBot.Commands ;
2018-07-19 20:55:54 +00:00
using CompatBot.Commands.Converters ;
2018-07-19 12:42:48 +00:00
using CompatBot.Database ;
2018-08-29 16:52:47 +00:00
using CompatBot.Database.Providers ;
2018-07-19 12:42:48 +00:00
using CompatBot.EventHandlers ;
2018-08-03 16:39:57 +00:00
using CompatBot.ThumbScrapper ;
2019-08-24 15:46:10 +00:00
using CompatBot.Utils ;
2018-07-19 12:42:48 +00:00
using DSharpPlus ;
using DSharpPlus.CommandsNext ;
2018-11-12 08:28:52 +00:00
using DSharpPlus.Entities ;
2019-02-08 16:55:57 +00:00
using DSharpPlus.Interactivity ;
2019-08-24 15:46:10 +00:00
using Microsoft.Extensions.Configuration.UserSecrets ;
2018-08-03 16:39:57 +00:00
using Microsoft.Extensions.DependencyInjection ;
2018-07-19 12:42:48 +00:00
namespace CompatBot
{
internal static class Program
{
2018-08-05 14:36:16 +00:00
private static readonly SemaphoreSlim InstanceCheck = new SemaphoreSlim ( 0 , 1 ) ;
private static readonly SemaphoreSlim ShutdownCheck = new SemaphoreSlim ( 0 , 1 ) ;
2020-02-20 13:11:48 +00:00
// pre-load the assembly so it won't fail after framework update while the process is still running
private static readonly Assembly diagnosticsAssembly = Assembly . Load ( typeof ( Process ) . Assembly . GetName ( ) ) ;
2019-04-21 17:38:20 +00:00
internal const ulong InvalidChannelId = 13 ;
2018-08-05 14:36:16 +00:00
2018-07-19 12:42:48 +00:00
internal static async Task Main ( string [ ] args )
{
2020-04-13 11:50:50 +00:00
Config . TelemetryClient ? . TrackEvent ( "startup" ) ;
2019-10-31 12:18:04 +00:00
Console . WriteLine ( "Confinement: " + SandboxDetector . Detect ( ) ) ;
2019-08-24 15:46:10 +00:00
if ( args . Length > 0 & & args [ 0 ] = = "--dry-run" )
{
Console . WriteLine ( "Database path: " + Path . GetDirectoryName ( Path . GetFullPath ( DbImporter . GetDbPath ( "fake.db" , Environment . SpecialFolder . ApplicationData ) ) ) ) ;
2019-10-31 12:18:04 +00:00
if ( Assembly . GetEntryAssembly ( ) . GetCustomAttribute < UserSecretsIdAttribute > ( ) ! = null )
2019-08-24 15:46:10 +00:00
Console . WriteLine ( "Bot config path: " + Path . GetDirectoryName ( Path . GetFullPath ( Config . GoogleApiConfigPath ) ) ) ;
return ;
}
2020-02-20 13:11:48 +00:00
if ( Process . GetCurrentProcess ( ) . Id = = 0 )
Config . Log . Info ( "Well, this was unexpected" ) ;
2018-09-08 14:59:59 +00:00
var singleInstanceCheckThread = new Thread ( ( ) = >
2019-11-25 13:07:01 +00:00
{
using var instanceLock = new Mutex ( false , @"Global\RPCS3 Compatibility Bot" ) ;
if ( instanceLock . WaitOne ( 1000 ) )
try
{
InstanceCheck . Release ( ) ;
ShutdownCheck . Wait ( ) ;
}
finally
{
instanceLock . ReleaseMutex ( ) ;
}
} ) ;
2018-08-05 14:36:16 +00:00
try
2018-07-19 12:42:48 +00:00
{
2018-09-08 14:59:59 +00:00
singleInstanceCheckThread . Start ( ) ;
2019-03-01 10:56:54 +00:00
if ( ! await InstanceCheck . WaitAsync ( 1000 ) . ConfigureAwait ( false ) )
2018-08-05 14:36:16 +00:00
{
2018-10-05 17:56:44 +00:00
Config . Log . Fatal ( "Another instance is already running." ) ;
2018-08-03 16:39:57 +00:00
return ;
2018-08-05 14:36:16 +00:00
}
2018-08-03 16:39:57 +00:00
2019-10-26 14:26:21 +00:00
if ( string . IsNullOrEmpty ( Config . Token ) | | Config . Token . Length < 16 )
2018-08-05 14:36:16 +00:00
{
2018-10-05 17:56:44 +00:00
Config . Log . Fatal ( "No token was specified." ) ;
2018-08-03 16:39:57 +00:00
return ;
2018-08-05 14:36:16 +00:00
}
2018-07-19 12:42:48 +00:00
2019-11-18 20:02:12 +00:00
if ( SandboxDetector . Detect ( ) = = SandboxType . Docker )
{
Config . Log . Info ( "Checking for updates..." ) ;
try
{
var ( updated , stdout ) = await Sudo . Bot . UpdateAsync ( ) . ConfigureAwait ( false ) ;
if ( ! string . IsNullOrEmpty ( stdout ) & & updated )
Config . Log . Debug ( stdout ) ;
if ( updated )
{
2020-03-21 10:16:03 +00:00
Sudo . Bot . Restart ( InvalidChannelId , "Restarted due to new bot updates not present in this Docker image" ) ;
2019-11-18 20:02:12 +00:00
return ;
}
}
catch ( Exception e )
{
Config . Log . Error ( e , "Failed to check for updates" ) ;
}
}
2018-08-05 14:36:16 +00:00
using ( var db = new BotDb ( ) )
if ( ! await DbImporter . UpgradeAsync ( db , Config . Cts . Token ) )
return ;
2018-07-19 12:42:48 +00:00
2018-08-05 14:36:16 +00:00
using ( var db = new ThumbnailDb ( ) )
if ( ! await DbImporter . UpgradeAsync ( db , Config . Cts . Token ) )
return ;
2018-07-19 12:42:48 +00:00
2020-03-09 19:07:26 +00:00
await SqlConfiguration . RestoreAsync ( ) . ConfigureAwait ( false ) ;
Config . Log . Debug ( "Restored configuration variables from persistent storage" ) ;
2019-03-01 15:52:37 +00:00
await StatsStorage . RestoreAsync ( ) . ConfigureAwait ( false ) ;
Config . Log . Debug ( "Restored stats from persistent storage" ) ;
2019-01-11 16:43:45 +00:00
var backgroundTasks = Task . WhenAll (
AmdDriverVersionProvider . RefreshAsync ( ) ,
2020-03-09 17:51:44 +00:00
#if ! DEBUG
2019-01-11 16:43:45 +00:00
GameTdbScraper . RunAsync ( Config . Cts . Token ) ,
2020-03-09 17:51:44 +00:00
#endif
2020-03-08 17:44:05 +00:00
StatsStorage . BackgroundSaveAsync ( ) ,
2020-04-02 18:00:33 +00:00
CompatList . ImportCompatListAsync ( )
2019-01-11 16:43:45 +00:00
) ;
2018-08-05 14:36:16 +00:00
2018-11-05 11:01:31 +00:00
try
{
if ( ! Directory . Exists ( Config . IrdCachePath ) )
Directory . CreateDirectory ( Config . IrdCachePath ) ;
}
catch ( Exception e )
{
Config . Log . Warn ( e , $"Failed to create new folder {Config.IrdCachePath}: {e.Message}" ) ;
}
2018-08-05 14:36:16 +00:00
var config = new DiscordConfiguration
2018-07-19 12:42:48 +00:00
{
2018-08-05 14:36:16 +00:00
Token = Config . Token ,
TokenType = TokenType . Bot ,
2020-05-12 20:20:06 +00:00
MessageCacheSize = Config . MessageCacheSize ,
2020-09-01 10:05:27 +00:00
LoggerFactory = Config . LoggerFactory ,
2018-08-05 14:36:16 +00:00
} ;
2019-11-25 13:07:01 +00:00
using var client = new DiscordClient ( config ) ;
var commands = client . UseCommandsNext ( new CommandsNextConfiguration
2018-07-19 12:42:48 +00:00
{
2019-11-25 13:07:01 +00:00
StringPrefixes = new [ ] { Config . CommandPrefix , Config . AutoRemoveCommandPrefix } ,
Services = new ServiceCollection ( ) . BuildServiceProvider ( ) ,
} ) ;
commands . RegisterConverter ( new TextOnlyDiscordChannelConverter ( ) ) ;
commands . RegisterCommands < Misc > ( ) ;
commands . RegisterCommands < CompatList > ( ) ;
commands . RegisterCommands < Sudo > ( ) ;
commands . RegisterCommands < CommandsManagement > ( ) ;
commands . RegisterCommands < ContentFilters > ( ) ;
commands . RegisterCommands < Warnings > ( ) ;
commands . RegisterCommands < Explain > ( ) ;
commands . RegisterCommands < Psn > ( ) ;
commands . RegisterCommands < Invites > ( ) ;
commands . RegisterCommands < Moderation > ( ) ;
commands . RegisterCommands < Ird > ( ) ;
commands . RegisterCommands < BotMath > ( ) ;
commands . RegisterCommands < Pr > ( ) ;
commands . RegisterCommands < Events > ( ) ;
commands . RegisterCommands < E3 > ( ) ;
commands . RegisterCommands < Cyberpunk2077 > ( ) ;
commands . RegisterCommands < Rpcs3Ama > ( ) ;
commands . RegisterCommands < BotStats > ( ) ;
commands . RegisterCommands < Syscall > ( ) ;
2019-11-29 23:39:26 +00:00
commands . RegisterCommands < ForcedNicknames > ( ) ;
2020-07-08 19:05:40 +00:00
commands . RegisterCommands < Minesweeper > ( ) ;
2018-07-19 12:42:48 +00:00
2020-03-08 12:29:17 +00:00
if ( ! string . IsNullOrEmpty ( Config . AzureComputerVisionKey ) )
commands . RegisterCommands < Vision > ( ) ;
2019-11-25 13:07:01 +00:00
commands . CommandErrored + = UnknownCommandHandler . OnError ;
2019-02-08 00:23:44 +00:00
2020-03-21 15:03:13 +00:00
client . UseInteractivity ( new InteractivityConfiguration ( ) ) ;
2019-02-08 16:55:57 +00:00
2020-10-07 09:20:57 +00:00
client . Ready + = async ( c , _ ) = >
2020-03-21 15:03:13 +00:00
{
2020-10-07 09:20:57 +00:00
var admin = await c . GetUserAsync ( Config . BotAdminId ) . ConfigureAwait ( false ) ;
2020-03-21 15:03:13 +00:00
Config . Log . Info ( "Bot is ready to serve!" ) ;
Config . Log . Info ( "" ) ;
2020-10-07 09:20:57 +00:00
Config . Log . Info ( $"Bot user id : {c.CurrentUser.Id} ({c.CurrentUser.Username})" ) ;
2020-03-21 15:03:13 +00:00
Config . Log . Info ( $"Bot admin id : {Config.BotAdminId} ({admin.Username ?? " ? ? ? "}#{admin.Discriminator ?? " ? ? ? ? "})" ) ;
Config . Log . Info ( "" ) ;
} ;
2020-10-07 09:20:57 +00:00
client . GuildAvailable + = async ( c , gaArgs ) = >
2019-11-25 13:07:01 +00:00
{
2020-10-07 09:20:57 +00:00
await BotStatusMonitor . RefreshAsync ( c ) . ConfigureAwait ( false ) ;
2019-11-25 13:07:01 +00:00
Watchdog . DisconnectTimestamps . Clear ( ) ;
2019-12-24 15:26:25 +00:00
Watchdog . TimeSinceLastIncomingMessage . Restart ( ) ;
2019-11-25 13:07:01 +00:00
if ( gaArgs . Guild . Id ! = Config . BotGuildId )
2019-06-24 14:25:04 +00:00
{
2018-09-08 15:41:56 +00:00
#if DEBUG
2019-11-25 13:07:01 +00:00
Config . Log . Warn ( $"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name})" ) ;
2018-09-08 15:41:56 +00:00
#else
2019-11-25 13:07:01 +00:00
Config . Log . Warn ( $"Unknown discord server {gaArgs.Guild.Id} ({gaArgs.Guild.Name}), leaving..." ) ;
await gaArgs . Guild . LeaveAsync ( ) . ConfigureAwait ( false ) ;
2018-09-08 15:41:56 +00:00
#endif
2019-11-25 13:07:01 +00:00
return ;
}
2018-09-08 15:41:56 +00:00
2019-11-25 13:07:01 +00:00
Config . Log . Info ( $"Server {gaArgs.Guild.Name} is available now" ) ;
Config . Log . Info ( $"Checking moderation backlogs in {gaArgs.Guild.Name}..." ) ;
try
{
await Task . WhenAll (
2020-10-07 09:20:57 +00:00
Starbucks . CheckBacklogAsync ( c , gaArgs . Guild ) . ContinueWith ( _ = > Config . Log . Info ( $"Starbucks backlog checked in {gaArgs.Guild.Name}." ) , TaskScheduler . Default ) ,
DiscordInviteFilter . CheckBacklogAsync ( c , gaArgs . Guild ) . ContinueWith ( _ = > Config . Log . Info ( $"Discord invites backlog checked in {gaArgs.Guild.Name}." ) , TaskScheduler . Default )
2019-11-25 13:07:01 +00:00
) . ConfigureAwait ( false ) ;
}
catch ( Exception e )
{
Config . Log . Warn ( e , "Error running backlog tasks" ) ;
}
Config . Log . Info ( $"All moderation backlogs checked in {gaArgs.Guild.Name}." ) ;
} ;
2020-10-07 09:20:57 +00:00
client . GuildAvailable + = ( c , _ ) = > UsernameValidationMonitor . MonitorAsync ( c , true ) ;
client . GuildUnavailable + = ( _ , guArgs ) = >
2019-11-25 13:07:01 +00:00
{
Config . Log . Warn ( $"{guArgs.Guild.Name} is unavailable" ) ;
return Task . CompletedTask ;
} ;
2020-04-02 14:58:43 +00:00
#if ! DEBUG
2020-04-03 11:14:42 +00:00
/ *
2020-04-02 14:58:43 +00:00
client . GuildDownloadCompleted + = async gdcArgs = >
{
foreach ( var guild in gdcArgs . Guilds )
await ModProvider . SyncRolesAsync ( guild . Value ) . ConfigureAwait ( false ) ;
} ;
2020-04-03 11:14:42 +00:00
* /
2020-04-02 14:58:43 +00:00
#endif
2019-11-25 13:07:01 +00:00
client . MessageReactionAdded + = Starbucks . Handler ;
2019-11-26 16:42:42 +00:00
client . MessageReactionAdded + = ContentFilterMonitor . OnReaction ;
2018-07-19 12:42:48 +00:00
2020-10-07 09:20:57 +00:00
client . MessageCreated + = ( _ , __ ) = > { Watchdog . TimeSinceLastIncomingMessage . Restart ( ) ; return Task . CompletedTask ; } ;
2019-11-26 16:42:42 +00:00
client . MessageCreated + = ContentFilterMonitor . OnMessageCreated ; // should be first
2020-09-29 10:34:26 +00:00
client . MessageCreated + = GlobalMessageCache . OnMessageCreated ;
2020-10-07 09:20:57 +00:00
var mediaScreenshotMonitor = new MediaScreenshotMonitor ( client ) ;
2020-03-08 15:36:31 +00:00
if ( ! string . IsNullOrEmpty ( Config . AzureComputerVisionKey ) )
2020-10-07 09:20:57 +00:00
client . MessageCreated + = mediaScreenshotMonitor . OnMessageCreated ;
2019-11-25 13:07:01 +00:00
client . MessageCreated + = ProductCodeLookup . OnMessageCreated ;
client . MessageCreated + = LogParsingHandler . OnMessageCreated ;
client . MessageCreated + = LogAsTextMonitor . OnMessageCreated ;
client . MessageCreated + = DiscordInviteFilter . OnMessageCreated ;
client . MessageCreated + = PostLogHelpHandler . OnMessageCreated ;
client . MessageCreated + = BotReactionsHandler . OnMessageCreated ;
client . MessageCreated + = GithubLinksHandler . OnMessageCreated ;
client . MessageCreated + = NewBuildsMonitor . OnMessageCreated ;
client . MessageCreated + = TableFlipMonitor . OnMessageCreated ;
client . MessageCreated + = IsTheGamePlayableHandler . OnMessageCreated ;
client . MessageCreated + = EmpathySimulationHandler . OnMessageCreated ;
2018-08-05 14:36:16 +00:00
2020-09-29 10:34:26 +00:00
client . MessageUpdated + = GlobalMessageCache . OnMessageUpdated ;
2019-11-26 16:42:42 +00:00
client . MessageUpdated + = ContentFilterMonitor . OnMessageUpdated ;
2019-11-25 13:07:01 +00:00
client . MessageUpdated + = DiscordInviteFilter . OnMessageUpdated ;
client . MessageUpdated + = EmpathySimulationHandler . OnMessageUpdated ;
2018-08-05 14:36:16 +00:00
2020-09-29 10:34:26 +00:00
client . MessageDeleted + = GlobalMessageCache . OnMessageDeleted ;
2020-05-12 20:20:06 +00:00
if ( Config . DeletedMessagesLogChannelId > 0 )
client . MessageDeleted + = DeletedMessagesMonitor . OnMessageDeleted ;
2019-11-25 13:07:01 +00:00
client . MessageDeleted + = ThumbnailCacheMonitor . OnMessageDeleted ;
client . MessageDeleted + = EmpathySimulationHandler . OnMessageDeleted ;
2018-08-05 14:36:16 +00:00
2020-09-29 10:34:26 +00:00
client . MessagesBulkDeleted + = GlobalMessageCache . OnMessagesBulkDeleted ;
2019-11-25 13:07:01 +00:00
client . UserUpdated + = UsernameSpoofMonitor . OnUserUpdated ;
client . UserUpdated + = UsernameZalgoMonitor . OnUserUpdated ;
2019-03-13 18:14:00 +00:00
2019-11-29 23:39:26 +00:00
client . GuildMemberAdded + = Greeter . OnMemberAdded ;
client . GuildMemberAdded + = UsernameSpoofMonitor . OnMemberAdded ;
client . GuildMemberAdded + = UsernameZalgoMonitor . OnMemberAdded ;
client . GuildMemberAdded + = UsernameValidationMonitor . OnMemberAdded ;
2019-10-30 00:19:45 +00:00
2019-11-29 23:39:26 +00:00
client . GuildMemberUpdated + = UsernameSpoofMonitor . OnMemberUpdated ;
client . GuildMemberUpdated + = UsernameZalgoMonitor . OnMemberUpdated ;
client . GuildMemberUpdated + = UsernameValidationMonitor . OnMemberUpdated ;
2018-09-12 16:25:30 +00:00
2019-11-25 13:07:01 +00:00
Watchdog . DisconnectTimestamps . Enqueue ( DateTime . UtcNow ) ;
try
{
await client . ConnectAsync ( ) . ConfigureAwait ( false ) ;
}
catch ( Exception e )
{
Config . Log . Error ( e , "Failed to connect to Discord: " + e . Message ) ;
throw ;
}
2019-06-05 19:06:10 +00:00
2019-11-25 13:07:01 +00:00
ulong? channelId = null ;
2020-03-21 10:16:03 +00:00
string restartMsg = null ;
using ( var db = new BotDb ( ) )
2019-11-25 13:07:01 +00:00
{
var chState = db . BotState . FirstOrDefault ( k = > k . Key = = "bot-restart-channel" ) ;
if ( chState ! = null )
2018-08-05 14:36:16 +00:00
{
2019-11-25 13:07:01 +00:00
if ( ulong . TryParse ( chState . Value , out var ch ) )
channelId = ch ;
db . BotState . Remove ( chState ) ;
2019-11-07 16:48:30 +00:00
}
2020-03-21 10:16:03 +00:00
var msgState = db . BotState . FirstOrDefault ( i = > i . Key = = "bot-restart-msg" ) ;
if ( msgState ! = null )
{
restartMsg = msgState . Value ;
db . BotState . Remove ( msgState ) ;
}
db . SaveChanges ( ) ;
2019-11-25 13:07:01 +00:00
}
2020-03-21 10:16:03 +00:00
if ( string . IsNullOrEmpty ( restartMsg ) )
restartMsg = null ;
2019-11-07 16:48:30 +00:00
2019-11-25 13:07:01 +00:00
if ( channelId . HasValue )
{
Config . Log . Info ( $"Found channelId {channelId}" ) ;
DiscordChannel channel ;
if ( channelId = = InvalidChannelId )
2019-11-07 16:48:30 +00:00
{
2019-11-25 13:07:01 +00:00
channel = await client . GetChannelAsync ( Config . ThumbnailSpamId ) . ConfigureAwait ( false ) ;
2020-03-21 10:16:03 +00:00
await channel . SendMessageAsync ( restartMsg ? ? "Bot has suffered some catastrophic failure and was restarted" ) . ConfigureAwait ( false ) ;
2018-08-05 14:36:16 +00:00
}
2019-11-06 00:06:14 +00:00
else
{
2019-11-25 13:07:01 +00:00
channel = await client . GetChannelAsync ( channelId . Value ) . ConfigureAwait ( false ) ;
await channel . SendMessageAsync ( "Bot is up and running" ) . ConfigureAwait ( false ) ;
2019-11-06 00:06:14 +00:00
}
2019-11-25 13:07:01 +00:00
}
else
{
Config . Log . Debug ( $"Args count: {args.Length}" ) ;
var pArgs = args . Select ( a = > a = = Config . Token ? "<Token>" : $"[{a}]" ) ;
Config . Log . Debug ( "Args: " + string . Join ( " " , pArgs ) ) ;
}
2018-08-05 14:36:16 +00:00
2019-11-29 23:39:26 +00:00
Config . Log . Debug ( "Running RPCS3 update check thread" ) ;
backgroundTasks = Task . WhenAll (
backgroundTasks ,
NewBuildsMonitor . MonitorAsync ( client ) ,
Watchdog . Watch ( client ) ,
InviteWhitelistProvider . CleanupAsync ( client ) ,
2020-03-31 05:20:15 +00:00
UsernameValidationMonitor . MonitorAsync ( client ) ,
2020-07-03 09:03:37 +00:00
Psn . Check . MonitorFwUpdates ( client , Config . Cts . Token ) ,
2020-09-09 17:17:54 +00:00
Watchdog . SendMetrics ( client ) ,
2020-10-07 09:20:57 +00:00
Watchdog . CheckGCStats ( ) ,
mediaScreenshotMonitor . ProcessWorkQueue ( )
2019-11-29 23:39:26 +00:00
) ;
2018-08-05 14:36:16 +00:00
2019-11-25 13:07:01 +00:00
while ( ! Config . Cts . IsCancellationRequested )
{
if ( client . Ping > 1000 )
Config . Log . Warn ( $"High ping detected: {client.Ping}" ) ;
await Task . Delay ( TimeSpan . FromMinutes ( 1 ) , Config . Cts . Token ) . ContinueWith ( dt = > { /* in case it was cancelled */ } , TaskScheduler . Default ) . ConfigureAwait ( false ) ;
2018-07-19 12:42:48 +00:00
}
2019-01-11 16:43:45 +00:00
await backgroundTasks . ConfigureAwait ( false ) ;
2018-08-05 14:36:16 +00:00
}
2019-05-04 16:53:10 +00:00
catch ( Exception e )
2018-10-05 17:56:44 +00:00
{
2019-05-28 14:13:32 +00:00
if ( ! Config . inMemorySettings . ContainsKey ( "shutdown" ) )
Config . Log . Fatal ( e , "Experienced catastrophic failure, attempting to restart..." ) ;
2018-10-05 17:56:44 +00:00
}
2018-08-05 14:36:16 +00:00
finally
{
2020-04-13 11:50:50 +00:00
Config . TelemetryClient ? . Flush ( ) ;
2018-08-05 14:36:16 +00:00
ShutdownCheck . Release ( ) ;
2018-10-05 17:56:44 +00:00
if ( singleInstanceCheckThread . IsAlive )
singleInstanceCheckThread . Join ( 100 ) ;
2018-07-19 12:42:48 +00:00
}
2019-05-17 19:29:44 +00:00
if ( ! Config . inMemorySettings . ContainsKey ( "shutdown" ) )
2020-03-21 10:16:03 +00:00
Sudo . Bot . Restart ( InvalidChannelId , null ) ;
2018-07-19 12:42:48 +00:00
}
}
}