/// <summary> /// Wrapper for asynchronous entry point. /// </summary> /// <param name="args">Command-line arguments for the binary.</param> internal static void Main(string[] args) { // pass the execution to the asynchronous entry point var async = new AsyncExecutor(); async.Execute(MainAsync(args)); }
/// <summary> /// Asynchronous entry point of the bot's binary. /// </summary> /// <param name="args">Command-line arguments for the binary.</param> /// <returns></returns> private static async Task MainAsync(string[] args) { Console.WriteLine("Loading Companion Cube..."); Console.Write("[1/4] Loading configuration "); // locate the config file var cfgFile = new FileInfo("config.json"); // load the config file and validate it var cfgLoader = new CompanionCubeConfigLoader(); if (!cfgFile.Exists) { await cfgLoader.SaveConfigurationAsync(new CompanionCubeConfig(), cfgFile); return; } var cfg = await cfgLoader.LoadConfigurationAsync(cfgFile).ConfigureAwait(false); cfgLoader.ValidateConfiguration(cfg); Console.Write("\r[2/4] Loading unicode data "); // load unicode data using (var utfloader = new UnicodeDataLoader("unicode_data.json.gz")) await utfloader.LoadCodepointsAsync().ConfigureAwait(false); Console.Write("\r[3/4] Validating PostgreSQL database"); Console.Write("\r[4/4] Creating and booting shards "); // create shards Shards = new Dictionary <int, CompanionCubeBot>(); var async = new AsyncExecutor(); for (int i = 0; i < cfg.Discord.ShardCount; i++) { Shards[i] = new CompanionCubeBot(cfg, i, async); } // --- LOADING COMPLETED --- Console.WriteLine("\rLoading completed, booting the shards"); Console.WriteLine("-------------------------------------"); // boot shards foreach (var(k, shard) in Shards) { await shard.StartAsync(); } // do a minimal cleanup GC.Collect(); // wait forever await Task.Delay(-1); }
/// <summary> /// Asynchronous entry point of the bot's binary. /// </summary> /// <param name="args">Command-line arguments for the binary.</param> /// <returns></returns> private static async Task MainAsync(string[] args) { Console.WriteLine("Loading Companion Cube..."); Console.Write("[1/4] Loading configuration "); // locate the config file var dockerSecret = Environment.GetEnvironmentVariable("DOCKER_SECRET"); var cfgFile = new FileInfo(dockerSecret != null ? Path.Combine("/run/secrets", dockerSecret) : "config.json"); // load the config file and validate it var cfgLoader = new CompanionCubeConfigLoader(); var cfg = await cfgLoader.LoadConfigurationAsync(cfgFile); cfgLoader.ValidateConfiguration(cfg); Console.Write("\r[2/4] Loading unicode data "); // load unicode data using (var utfloader = new UnicodeDataLoader("unicode_data.json.gz")) await utfloader.LoadCodepointsAsync(); Console.Write("\r[3/4] Validating PostgreSQL database"); // create database type mapping NpgsqlConnection.GlobalTypeMapper.MapEnum <DatabaseEntityKind>("entity_kind"); NpgsqlConnection.GlobalTypeMapper.MapEnum <DatabaseTagKind>("tag_kind"); // create database connection and validate schema var dbcsp = new ConnectionStringProvider(cfg.PostgreSQL); using (var db = new DatabaseContext(dbcsp)) { var dbv = db.Metadata.SingleOrDefault(x => x.MetaKey == "schema_version"); if (dbv == null || dbv.MetaValue != "8") { throw new InvalidDataException("Database schema version mismatch."); } dbv = db.Metadata.SingleOrDefault(x => x.MetaKey == "project"); if (dbv == null || dbv.MetaValue != "Companion Cube") { throw new InvalidDataException("Database schema type mismatch."); } } Console.Write("\r[4/4] Creating and booting shards "); // create shards Shards = new Dictionary <int, CompanionCubeBot>(); var async = new AsyncExecutor(); for (int i = 0; i < cfg.Discord.ShardCount; i++) { Shards[i] = new CompanionCubeBot(cfg, i, async); } // --- LOADING COMPLETED --- Console.WriteLine("\rLoading completed, booting the shards"); Console.WriteLine("-------------------------------------"); // boot shards foreach (var(k, shard) in Shards) { await shard.StartAsync(); } // do a minimal cleanup GC.Collect(); // wait forever await Task.Delay(-1); }
/// <summary> /// Creates a new instance of Companion Cube bot shard handler. /// </summary> /// <param name="cfg">Configuration options for the shard.</param> /// <param name="shardId">ID of this shard.</param> /// <param name="async">Synchronous executor of asynchronous tasks.</param> public CompanionCubeBot(CompanionCubeConfig cfg, int shardId, AsyncExecutor async) { // assign the properties this.ShardId = shardId; this.BotVersion = CompanionCubeUtilities.GetBotVersion(); this.Configuration = cfg; this.ConnectionStringProvider = new ConnectionStringProvider(cfg.PostgreSQL); this.AsyncExecutor = async; // create discord client instance this.Discord = new DiscordClient(new DiscordConfiguration { Token = cfg.Discord.Token, TokenType = TokenType.Bot, ShardCount = cfg.Discord.ShardCount, ShardId = this.ShardId, AutoReconnect = true, ReconnectIndefinitely = true, GatewayCompressionLevel = GatewayCompressionLevel.Stream, LargeThreshold = 250, UseInternalLogHandler = false, LogLevel = LogLevel.Info }); // attach log handler this.Discord.DebugLogger.LogMessageReceived += this.DebugLogger_LogMessageReceived; // attach event handlers this.Discord.Ready += this.Discord_Ready; this.Discord.GuildDownloadCompleted += this.Discord_GuildDownloadCompleted; this.Discord.ClientErrored += this.Discord_ClientErrored; this.Discord.SocketErrored += this.Discord_SocketErrored; this.Discord.GuildAvailable += this.Discord_GuildAvailable; this.Discord.VoiceStateUpdated += this.Discord_VoiceStateUpdated; // create service provider this.Services = new ServiceCollection() .AddTransient <CSPRNG>() .AddSingleton(this.ConnectionStringProvider) .AddSingleton <MusicService>() .AddScoped <DatabaseContext>() .AddSingleton(new LavalinkService(cfg.Lavalink, this.Discord)) .AddSingleton(new YouTubeSearchProvider(cfg.YouTube)) .AddSingleton <HttpClient>() .AddSingleton(this) .BuildServiceProvider(true); // create CommandsNext this.CommandsNext = this.Discord.UseCommandsNext(new CommandsNextConfiguration { CaseSensitive = false, EnableDms = false, IgnoreExtraArguments = false, EnableDefaultHelp = true, DefaultHelpChecks = new[] { new NotBlacklistedAttribute() }, EnableMentionPrefix = cfg.Discord.EnableMentionPrefix, PrefixResolver = this.ResolvePrefixAsync, Services = this.Services }); // set help formatter this.CommandsNext.SetHelpFormatter <CompanionCubeHelpFormatter>(); // register type converters this.CommandsNext.RegisterConverter(new TagTypeConverter()); this.CommandsNext.RegisterUserFriendlyTypeName <TagType>("tag type"); // attach event handlers this.CommandsNext.CommandExecuted += this.CommandsNext_CommandExecuted; this.CommandsNext.CommandErrored += this.CommandsNext_CommandErrored; // create commands this.CommandsNext.RegisterCommands(Assembly.GetExecutingAssembly()); // create interactivity this.Interactivity = this.Discord.UseInteractivity(new InteractivityConfiguration { Timeout = TimeSpan.FromSeconds(30) }); // create lavalink this.Lavalink = this.Discord.UseLavalink(); }