using Microsoft.Extensions.Logging; using SharpChat; using SharpChat.Configuration; using SharpChat.Flashii; using SharpChat.MariaDB; using SharpChat.SQLite; using System.Text; using ZLogger; using ZLogger.Providers; const string CONFIG = "sharpchat.cfg"; Console.WriteLine(@" _____ __ ________ __ "); Console.WriteLine(@" / ___// /_ ____ __________ / ____/ /_ ____ _/ /_"); Console.WriteLine(@" \__ \/ __ \/ __ `/ ___/ __ \/ / / __ \/ __ `/ __/"); Console.WriteLine(@" ___/ / / / / /_/ / / / /_/ / /___/ / / / /_/ / /_ "); Console.WriteLine(@"/____/_/ /_/\__,_/_/ / .___/\____/_/ /_/\__,_/\__/ "); /**/Console.Write(@" /__/"); if(SharpInfo.IsDebugBuild) { Console.WriteLine(); Console.Write(@"== "); Console.Write(SharpInfo.VersionString); Console.WriteLine(@" == DBG =="); } else Console.WriteLine(SharpInfo.VersionStringShort.PadLeft(28, ' ')); using ILoggerFactory logFactory = LoggerFactory.Create(logging => { logging.ClearProviders(); #if DEBUG logging.SetMinimumLevel(LogLevel.Trace); #else logging.SetMinimumLevel(LogLevel.Information); #endif logging.AddZLoggerConsole(opts => { opts.OutputEncodingToUtf8 = true; opts.UsePlainTextFormatter(formatter => { formatter.SetPrefixFormatter($"{0} [{1} {2}] ", (in MessageTemplate template, in LogInfo info) => template.Format(info.Timestamp, info.Category, info.LogLevel)); }); }); logging.AddZLoggerRollingFile(opts => { opts.FilePathSelector = (ts, seqNo) => $"logs/{ts.ToLocalTime():yyyy-MM-dd_HH-mm-ss}_{seqNo:000}.json"; opts.RollingInterval = RollingInterval.Day; opts.RollingSizeKB = 1024; opts.FileShared = true; opts.UseJsonFormatter(formatter => { formatter.UseUtcTimestamp = true; }); }); }); ILogger logger = logFactory.CreateLogger("main"); using CancellationTokenSource cts = new(); void exitHandler() { if(cts.IsCancellationRequested) return; try { cts.Cancel(); logger.ZLogInformation($"Shutdown requested through console..."); } catch(ObjectDisposedException) { } } AppDomain.CurrentDomain.ProcessExit += (sender, ev) => { exitHandler(); }; Console.CancelKeyPress += (sender, ev) => { ev.Cancel = true; exitHandler(); }; if(cts.IsCancellationRequested) return; string configFile = CONFIG; // If the config file doesn't exist and we're using the default path, run the converter if(!File.Exists(configFile) && configFile == CONFIG) { logger.ZLogInformation($"Creating example configuration file..."); using Stream s = new FileStream(CONFIG, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); s.SetLength(0); s.Flush(); using StreamWriter sw = new(s, new UTF8Encoding(false)); sw.WriteLine("# and ; can be used at the start of a line for comments."); sw.WriteLine(); sw.WriteLine("# General Configuration"); sw.WriteLine($"#chat:msgMaxLength {Context.DEFAULT_MSG_LENGTH_MAX}"); sw.WriteLine($"#chat:connMaxCount {Context.DEFAULT_MAX_CONNECTIONS}"); sw.WriteLine($"#chat:floodKickLength {Context.DEFAULT_FLOOD_KICK_LENGTH}"); sw.WriteLine("# Sock Chat Configuration"); sw.WriteLine($"#sockchat:port {SockChatServer.DEFAULT_PORT}"); sw.WriteLine(); sw.WriteLine("# Channels"); sw.WriteLine("chat:channels lounge staff"); sw.WriteLine(); sw.WriteLine("# Lounge channel settings"); sw.WriteLine("chat:channels:lounge:name Lounge"); sw.WriteLine("chat:channels:lounge:autoJoin true"); sw.WriteLine(); sw.WriteLine("# Staff channel settings"); sw.WriteLine("chat:channels:staff:name Staff"); sw.WriteLine("chat:channels:staff:minRank 5"); const string msz_secret = "login_key.txt"; const string msz_url = "msz_url.txt"; sw.WriteLine(); sw.WriteLine("# Misuzu integration settings"); if(File.Exists(msz_secret)) sw.WriteLine(string.Format("msz:secret {0}", File.ReadAllText(msz_secret).Trim())); else sw.WriteLine("#msz:secret woomy"); if(File.Exists(msz_url)) sw.WriteLine(string.Format("msz:url {0}/_sockchat", File.ReadAllText(msz_url).Trim())); else sw.WriteLine("#msz:url https://flashii.net/_sockchat"); const string mdb_config = @"mariadb.txt"; string[] mdbCfg = File.Exists(mdb_config) ? File.ReadAllLines(mdb_config) : []; sw.WriteLine(); sw.WriteLine("# MariaDB configuration"); if(mdbCfg.Length > 0) sw.WriteLine($"mariadb:host {mdbCfg[0]}"); else sw.WriteLine($"#mariadb:host <username>"); if(mdbCfg.Length > 1) sw.WriteLine($"mariadb:user {mdbCfg[1]}"); else sw.WriteLine($"#mariadb:user <username>"); if(mdbCfg.Length > 2) sw.WriteLine($"mariadb:pass {mdbCfg[2]}"); else sw.WriteLine($"#mariadb:pass <password>"); if(mdbCfg.Length > 3) sw.WriteLine($"mariadb:db {mdbCfg[3]}"); else sw.WriteLine($"#mariadb:db <database>"); sw.Flush(); } logger.ZLogInformation($"Initialising configuration..."); using StreamConfig config = StreamConfig.FromPath(configFile); if(cts.IsCancellationRequested) return; if(args.Contains("--migrate-storage") || args.Contains("--convert-db")) { MariaDBStorage mariadb = new(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb"))); using SQLiteStorage sqlite = new(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite"), false)); await new StorageMigrator(logFactory.CreateLogger("migrate"), mariadb, sqlite).Migrate(cts.Token); return; } logger.ZLogInformation($"Initialising HTTP client..."); using HttpClient httpClient = new(new HttpClientHandler() { UseProxy = false, }); httpClient.DefaultRequestHeaders.Add("User-Agent", SharpInfo.ProgramName); if(cts.IsCancellationRequested) return; logger.ZLogInformation($"Initialising Flashii client..."); FlashiiClient flashii = new(logFactory.CreateLogger("flashii"), httpClient, config.ScopeTo("msz")); if(cts.IsCancellationRequested) return; logger.ZLogInformation($"Initialising storage..."); Storage storage = string.IsNullOrWhiteSpace(config.SafeReadValue("mariadb:host", string.Empty)) ? new SQLiteStorage(logFactory.CreateLogger("sqlite"), SQLiteStorage.BuildConnectionString(config.ScopeTo("sqlite"))) : new MariaDBStorage(logFactory.CreateLogger("mariadb"), MariaDBStorage.BuildConnectionString(config.ScopeTo("mariadb"))); try { if(cts.IsCancellationRequested) return; await storage.UpgradeStorage(); if(cts.IsCancellationRequested) return; logger.ZLogDebug($"Creating context..."); Config chatConfig = config.ScopeTo("chat"); Context ctx = new(logFactory, chatConfig, storage, flashii, flashii); if(cts.IsCancellationRequested) return; logger.ZLogInformation($"Preparing server..."); await new SockChatServer( cts, ctx, config.ScopeTo("sockchat") ).Listen(cts.Token); } finally { if(storage is IDisposable disp) { logger.ZLogInformation($"Cleaning storage..."); disp.Dispose(); } } logger.ZLogInformation($"Exiting...");