public LaveyBot(LaveyConfig config, int shard_id, int shard_count)
        {
            this._log = LogManager.GetLogger("LaveyBot#" + (shard_id + 1));

            this.Config = config;

            this.Discord = new DiscordClient(new DiscordConfiguration(this.Config.Discord.Build())
            {
                ShardId    = shard_id,
                ShardCount = shard_count
            });

            this.Discord.DebugLogger.LogMessageReceived += (sender, e) =>
            {
                this._log.Log(e.Level.ToNLog(), e.Exception, e.Message);
            };

            this.Services = new ServiceCollection()
                            .AddSingleton(this)
                            .AddSingleton(new MusicService(this))
                            .BuildServiceProvider(true);

            this.Lavalink      = this.Discord.UseLavalink();
            this.Interactivity = this.Discord.UseInteractivity(this.Config.Interactivity.Build());
            this.CommandsNext  = this.Discord.UseCommandsNext(this.Config.CommandsNext.Build(this.Services));
            this.CommandsNext.RegisterCommands(typeof(LaveyBot).Assembly);
            this.CommandsNext.CommandErrored += this.OnCommandErrored;
        }
        static async Task MainAsync()
        {
            Console.CancelKeyPress += (_, e) =>
            {
                if (!_cts.IsCancellationRequested)
                {
                    _cts.Cancel();
                }

                e.Cancel = true;
            };

            var cfile = new FileInfo(Path.Combine(Directory.GetCurrentDirectory(), "Config.json"));

            if (!cfile.Exists)
            {
                using (var fs = cfile.CreateText())
                {
                    fs.WriteLine(JsonConvert.SerializeObject(LaveyConfig.Default, Formatting.Indented));
                    fs.Flush();
                }

                _log.Info("Default configuration file created.");
                Environment.Exit(1);
            }

            LaveyConfig config = default;

            using (var fs = cfile.OpenText())
                config = JsonConvert.DeserializeObject <LaveyConfig>(fs.ReadToEnd());

            NLogExtensions.InstallSeq(config);

            await SetupShardsAsync(config);

            foreach (var bot in GetShards())
            {
                await bot.InitializeAsync().ConfigureAwait(false);
            }

            while (!_cts.IsCancellationRequested)
            {
                await Task.Delay(100);
            }

            foreach (var bot in GetShards())
            {
                await bot.ShutdownAsync();
            }
        }
        public static void InstallSeq(LaveyConfig config)
        {
            var cfg = LogManager.Configuration;

            var seq = new SeqTarget();

            seq.Name      = nameof(seq);
            seq.ApiKey    = config.Seq.ApiKey;
            seq.ServerUrl = config.Seq.ToString();

            cfg.AddTarget(seq);
            cfg.LoggingRules.Add(new LoggingRule("CommandsNext", LogLevel.Debug, seq)
            {
                Final = true
            });
        }
        static async Task SetupShardsAsync(LaveyConfig config)
        {
            int shard_count = -1;

            using (var client = new HttpClient())
            {
                var req = new HttpRequestMessage(HttpMethod.Get, "https://discordapp.com/api/gateway/bot");
                req.Headers.TryAddWithoutValidation("Authorization", $"Bot {config.Discord.Token}");
                req.Headers.TryAddWithoutValidation("Content-Type", "application/json");

                var res = await client.SendAsync(req);

                using (req)
                    using (res)
                    {
                        var json = "{}";

                        if (res.IsSuccessStatusCode)
                        {
                            json = await res.Content.ReadAsStringAsync();
                        }

                        var jo = JObject.Parse(json);

                        if (jo != null && jo["shards"] != null)
                        {
                            shard_count = jo["shards"].Value <int>();
                        }
                    }
            }

            if (shard_count <= 0)
            {
                throw new InvalidOperationException("Cannot setup shards.");
            }

            for (var shard_id = 0; shard_id < shard_count; shard_id++)
            {
                var bot = new LaveyBot(config, shard_id, shard_count);
                _shards.AddOrUpdate(shard_id, bot, (key, value) => bot);
            }
        }