using Microsoft.AspNetCore.Builder; using SqlSugar; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.FileProviders; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication; using System.Security.Claims; using System.Reflection; using RainOpsMini.Models; using System.Text.Json; using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; using System; using System.IO; using RainOpsMini.Helpers.SquadGameLog; using RainOpsMini.Helpers; using RainOpsMini.Helpers.RCON; using RainOpsMini.Services; using RainOpsMini.Plugins; using RainOpsMini.Hubs; using Microsoft.AspNetCore.SignalR; using RainOpsMini.Apis; namespace RainOpsMini { using RainOpsMini.Models; using RainOpsMini; public class Program { /// /// RCON配置类,用于存储连接信息和Web服务配置 /// public class RconConfig { /// /// RCON服务器IP地址 /// public string Ip { get; set; } = ""; /// /// RCON端口 /// public int Port { get; set; } /// /// RCON密码 /// public string Password { get; set; } = ""; /// /// Web管理界面端口,默认5000 /// public int WebPort { get; set; } = 5000; /// /// Web管理界面用户名 /// public string WebUser { get; set; } = ""; /// /// Web管理界面密码 /// public string WebPassword { get; set; } = ""; } // 全局配置实例 public static RconConfig Config = new RconConfig(); // 配置文件路径 public static string ConfigPath = "rcon_Config.json"; // RCON客户端实例 static RconClient? client; // 公开访问的RCON客户端属性 public static RconClient? Client => client; // SignalR Hub上下文,用于发送实时消息 public static IHubContext? ChatHubContext { get; set; } // 静态缓存实例,在静态上下文和DI容器之间共享 public static RconDataCache RconCache = new RconDataCache(); // 插件监听原始RCON消息的事件 public static event Action? OnRconMessageReceived; // 系统是否已配置标记 public static bool isConfigured = false; static async Task Main(string[] args) { // 1. 初始化数据库 // 确保数据库表结构存在,这是程序运行的基础 try { Console.WriteLine("====== STARTING DATABASE INITIALIZATION ======"); DbHelper.InitDatabase(); Console.WriteLine("====== DATABASE INITIALIZATION COMPLETED ======"); } catch (Exception ex) { // 数据库初始化失败,打印严重错误并退出程序 Console.WriteLine($"\n\n[FATAL ERROR] Database initialization failed. The program cannot start."); Console.WriteLine($"Error: {ex.Message}"); Console.WriteLine("Please check 'db_init_error.log' for details."); Console.WriteLine("Press any key to exit..."); Console.ReadKey(); return; } // 2. 启动 Squad 游戏日志监听(后台任务) // 负责实时解析游戏日志文件,捕捉击杀、聊天等关键事件 Task.Run(() => { try { SquadLogHelper squadLogHelper = new SquadLogHelper(); // 确定日志文件路径,当前默认为程序运行目录下的 SquadGame/Saved/Logs/SquadGame.log // 建议后续改为可配置路径 string logpath = Environment.CurrentDirectory + "/SquadGame/Saved/Logs/SquadGame.log"; squadLogHelper.Run(logpath); } catch (Exception ex) { Console.WriteLine($"日志监听启动失败: {ex.Message}"); } }); // 3. 确定 Web 服务端口 // 默认端口 23368 int webPort = 23368; // 尝试从命令行参数获取端口,优先级最高 if (args.Length > 0 && int.TryParse(args[0], out int p) && p > 0 && p < 65536) { webPort = p; } // 加载配置文件 rcon_Config.json LoadConfig(); // 端口优先级逻辑:命令行参数 > 配置文件 > 默认值 // 如果指定了命令行参数,则覆盖配置中的端口 if (Config.WebPort == 0) Config.WebPort = webPort; else if (args.Length > 0) Config.WebPort = webPort; // 自动检测端口占用,如果被占用则自动+1查找 Config.WebPort = GetAvailablePort(Config.WebPort); // 4. 初始化 RCON 连接 // 如果系统已配置(非首次运行),则尝试连接 RCON if (isConfigured) { await InitializeRcon(); } // 启动后台任务:保持 RCON 连接活跃并自动重连 _ = KeepAliveAndReconnectTask(); // 5. 配置 Web 应用程序构建器 var builder = WebApplication.CreateBuilder(args); // 优化控制台体验:设置日志级别为 Warning,减少常规日志干扰控制台交互 builder.Logging.SetMinimumLevel(LogLevel.Warning); // 注册 RCON 客户端(注意:这里可能为空或占位符,后续会更新) builder.Services.AddSingleton(client ?? new RconClient("", 0, "")); // 占位,避免 DI 报错 // 注册缓存和 RCON 轮询服务 builder.Services.AddMemoryCache(); builder.Services.AddSingleton(RconCache); builder.Services.AddHostedService(); // RCON 状态轮询 builder.Services.AddHostedService(); // VIP 过期检查 builder.Services.AddHostedService(); // 服务器历史信息存储 builder.Services.AddHostedService(); // 数据库清理任务 builder.Services.AddSingleton(); // 在线玩家服务 builder.Services.AddSingleton(); // 封禁管理服务 builder.Services.AddSingleton(); // 封禁同步状态 builder.Services.AddHostedService(); // 封禁同步后台服务 // 注册玩家数据维护服务 builder.Services.AddHostedService(); // 注册 FRP 内网穿透管理服务 builder.Services.AddSingleton(); // 注册插件系统 (使用扩展方法自动扫描注册) builder.Services.AddAllPlugins(); // 注册 SignalR (实时通信) builder.Services.AddSignalR(); // 配置 Cookie 认证服务 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/login.html"; options.ExpireTimeSpan = TimeSpan.FromDays(1); }); builder.Services.AddAuthorization(); // 配置 JSON 序列化选项 (忽略大小写) builder.Services.Configure(options => { options.SerializerOptions.PropertyNameCaseInsensitive = true; }); // 6. 构建并配置 Web 应用管道 var app = builder.Build(); // 获取 ChatHub 上下文,供非 Hub 类使用 ChatHubContext = app.Services.GetRequiredService>(); // 开启认证中间件 app.UseAuthentication(); app.UseAuthorization(); // 7. 配置静态文件服务 // 优先使用物理目录下的 wwwroot(便于开发调试),如果不存在则使用嵌入式资源 IFileProvider fileProvider = new ManifestEmbeddedFileProvider(Assembly.GetExecutingAssembly(), "wwwroot"); string physicalWwwroot = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot"); if (Directory.Exists(physicalWwwroot)) { fileProvider = new CompositeFileProvider( new PhysicalFileProvider(physicalWwwroot), fileProvider ); } // 启用默认文件映射(如 index.html) app.UseDefaultFiles(new DefaultFilesOptions { FileProvider = fileProvider }); // 8. 自定义请求拦截中间件 // 处理系统初始化检查、页面重定向和全局权限验证 app.Use(async (context, next) => { var path = context.Request.Path.Value?.ToLower(); // 场景A: 系统未初始化 // 强制重定向到安装向导 (setup.html),除非正在访问安装相关接口 if (!isConfigured) { if (path == "/setup.html" || path == "/api/setup" || path == "/api/setup/detect-Config") { await next(); return; } context.Response.Redirect("/setup.html"); return; } // 场景B: 系统已初始化 // 禁止再次访问安装向导,重定向回首页 if (path == "/setup.html" || path == "/api/setup" || path == "/api/setup/detect-Config") { context.Response.Redirect("/"); return; } // 场景C: 公开资源访问 // 放行登录页面、登录接口和验证码接口 if (path == "/login.html" || path == "/api/login" || path == "/api/captcha") { await next(); return; } // 场景D: 需要认证的资源 // 检查用户是否已登录 if (!context.User.Identity?.IsAuthenticated ?? true) { // 特殊处理 SignalR 连接 (chatHub) // 允许协商请求通过,SignalR 内部会处理或依赖 Cookie if (path?.StartsWith("/chatHub") == true || path?.StartsWith("/presenceHub") == true) { await next(); return; } // 如果是 API 请求,返回 401 未授权状态码 if (path?.StartsWith("/api/") == true) { context.Response.StatusCode = 401; return; } // 如果是普通页面访问,重定向到登录页 context.Response.Redirect("/login.html"); return; } // 已认证,放行请求 await next(); }); // 启用静态文件中间件 app.UseStaticFiles(new StaticFileOptions { FileProvider = fileProvider }); // API 路由 // Register Modular APIs app.MapSystemApis(); app.MapAuthApis(); app.MapUserApis(); app.MapVipApis(); app.MapPlayerApis(); app.MapServerApis(); app.MapLogApis(); app.MapFrpApis(); app.MapBanApis(); // Ensure PluginManager is initialized and Configs are loaded try { app.Services.GetRequiredService(); } catch (Exception ex) { Console.WriteLine($"[Warning] PluginManager initialization failed: {ex.Message}"); } Console.WriteLine($"Web 服务启动中: http://localhost:{Config.WebPort}"); // FRP no longer auto-starts at application startup. // Use console command "frp start" or /api/frp/start when needed. // 9. 启动 Web 服务 // 使用异步非阻塞方式启动,以便主线程可以继续运行控制台交互循环 var webTask = app.RunAsync($"http://*:{Config.WebPort}"); // 10. 启动控制台交互循环 // 允许用户在控制台直接输入命令进行管理 await RunConsoleLoop(app); // 当控制台退出时,优雅停止 Web 服务 await app.StopAsync(); await webTask; } // 控制台交互主循环 // 允许用户在控制台直接输入命令进行管理 static async Task RunConsoleLoop(WebApplication app) { var version = Assembly.GetExecutingAssembly().GetName().Version; Console.WriteLine("--------------------------------------------------"); Console.WriteLine($" RainOPS Mini 控制台已就绪 v{version}"); Console.WriteLine(" 输入 'help' 查看可用命令"); Console.WriteLine("--------------------------------------------------"); while (true) { string statusIndicator = (client != null && client.IsConnected) ? "Online" : "Offline"; Console.Write($"RainOPS [{statusIndicator}]> "); string? input = Console.ReadLine(); if (string.IsNullOrWhiteSpace(input)) continue; string cmd = input.Trim(); switch (cmd.ToLower()) { case "help": PrintHelp(); break; case "1": case "Config": ShowConfig(); break; case "2": case "setrcon": await UpdateRconConfig(); break; case "3": case "setwebport": UpdateWebPort(); break; case "4": case "showlogin": ShowLoginInfo(); break; case "5": case "resetlogin": ResetLoginInfo(); break; case "setinterval": Console.WriteLine("请输入刷新间隔 (10-60 秒):"); string? intStr = Console.ReadLine(); if (int.TryParse(intStr, out int interval)) { var cache = app.Services.GetService(); if (cache != null) { cache.RefreshIntervalSeconds = interval; Console.WriteLine($"刷新频率已设置为 {cache.RefreshIntervalSeconds} 秒"); } } else { Console.WriteLine("输入无效"); } break; case "status": ShowStatus(app); break; case "frp": Console.WriteLine("用法: frp start | frp stop | frp restart"); break; case "frp start": var frpStart = app.Services.GetService(); if (frpStart != null) { try { frpStart.Start(); Console.WriteLine("FRP 任务已启动,日志将显示在下方。"); } catch (Exception ex) { Console.WriteLine($"启动失败: {ex.Message}"); } } break; case "frp stop": var frpStop = app.Services.GetService(); if (frpStop != null) { frpStop.Stop(); Console.WriteLine("FRP 任务已停止。"); } break; case "frp restart": var frpRestart = app.Services.GetService(); if (frpRestart != null) { try { frpRestart.Restart(); Console.WriteLine("FRP 任务已重启。"); } catch (Exception ex) { Console.WriteLine($"重启失败: {ex.Message}"); } } break; case "connect": await ManualConnect(); break; case "exit": case "quit": Console.WriteLine("正在退出程序..."); return; default: // 尝试作为 RCON 命令发送到游戏服务器 if (client != null && client.IsConnected) { try { // Console.WriteLine($"[发送] {cmd}"); string response = await client.SendCommandAsync(cmd); if (!string.IsNullOrWhiteSpace(response)) { Console.WriteLine(response); } else { Console.WriteLine("(无返回数据)"); } } catch (Exception ex) { Console.WriteLine($"[错误] 命令执行失败: {ex.Message}"); } } else { Console.WriteLine("未知命令,且 RCON 未连接。输入 'help' 查看帮助。"); } break; } } } // 显示系统状态 // 打印 RCON 连接、Web 端口、FRP 状态等信息 static void ShowStatus(WebApplication app) { Console.WriteLine("------------- 系统状态 -------------"); Console.WriteLine($" RCON 连接状态 : {(client?.IsConnected == true ? "已连接" : "未连接")}"); if (client?.IsConnected == true) { Console.WriteLine($" RCON 目标 : {Config.Ip}:{Config.Port}"); } Console.WriteLine($" Web 服务端口 : {Config.WebPort}"); var frp = app.Services.GetService(); if (frp != null) { Console.WriteLine($" FRP 运行状态 : {(frp.IsRunning ? "运行中" : "未运行")}"); } Console.WriteLine("------------------------------------"); } // 手动连接 RCON // 尝试根据当前配置重新连接游戏服务器 static async Task ManualConnect() { Console.WriteLine("正在尝试连接 RCON..."); if (!isConfigured) LoadConfig(); if (string.IsNullOrEmpty(Config.Ip)) { Console.WriteLine("错误: 未配置 RCON IP。请先使用 'Config' 或 'setrcon' 进行配置。"); return; } await InitializeRcon(); } // 打印帮助信息 // 显示控制台支持的所有命令 static void PrintHelp() { Console.WriteLine("================ 功能菜单 ================"); Console.WriteLine(" [直接输入 RCON 命令即可发送到服务器]"); Console.WriteLine(" status - 查看连接状态"); Console.WriteLine(" connect - 手动尝试连接 RCON"); Console.WriteLine(" 1. 查看配置文件全部信息 (或输入 Config)"); Console.WriteLine(" - 显示服务器IP、端口、RCON密码、登录名、登录密码等"); Console.WriteLine(" 2. 修改 RCON 信息 (或输入 setrcon)"); Console.WriteLine(" 3. 修改 WEB 端口 (或输入 setwebport)"); Console.WriteLine(" 4. 查看登录名和密码 (或输入 showlogin)"); Console.WriteLine(" 5. 重置登录名和密码 (或输入 resetlogin)"); Console.WriteLine(" setinterval - 设置 RCON 数据刷新频率 (10-60秒)"); Console.WriteLine(" frp start - 启动 FRP 任务"); Console.WriteLine(" frp stop - 停止 FRP 任务"); Console.WriteLine(" frp restart - 重启 FRP 任务"); Console.WriteLine(" help - 显示此帮助信息"); Console.WriteLine(" exit - 退出程序"); Console.WriteLine("=========================================="); } // 显示配置信息 // 打印当前加载的所有配置项 static void ShowConfig() { Console.WriteLine("------------- 当前配置信息 -------------"); Console.WriteLine($" RCON 服务器IP : {Config.Ip}"); Console.WriteLine($" RCON 端口 : {Config.Port}"); Console.WriteLine($" RCON 密码 : {Config.Password}"); Console.WriteLine($" WEB 端口 : {Config.WebPort}"); Console.WriteLine($" WEB 登录名 : {Config.WebUser}"); Console.WriteLine($" WEB 登录密码 : {Config.WebPassword}"); Console.WriteLine("----------------------------------------"); } // 显示登录凭据 // 打印当前的 Web 登录用户名和密码 static void ShowLoginInfo() { Console.WriteLine("------------- 登录凭据 -------------"); Console.WriteLine($" 登录名 : {Config.WebUser}"); Console.WriteLine($" 登录密码 : {Config.WebPassword}"); Console.WriteLine("------------------------------------"); } // 修改 RCON 配置 // 交互式修改 IP、端口和密码 static async Task UpdateRconConfig() { Console.WriteLine(">>> 修改 RCON 配置 (直接回车保持原值) <<<"); Console.Write($"输入新IP [{Config.Ip}]: "); string? ip = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(ip)) Config.Ip = ip.Trim(); Console.Write($"输入新端口 [{Config.Port}]: "); string? portStr = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(portStr) && int.TryParse(portStr, out int p)) Config.Port = p; Console.Write($"输入新密码 [{Config.Password}]: "); string? pass = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(pass)) Config.Password = pass.Trim(); SaveConfigToFile(); Console.WriteLine("配置已保存。正在重新初始化 RCON 连接..."); isConfigured = true; await InitializeRcon(); } // 修改 Web 端口 // 修改 Web 服务监听端口(需重启生效) static void UpdateWebPort() { Console.WriteLine(">>> 修改 WEB 端口 <<<"); Console.Write($"输入新端口 [{Config.WebPort}]: "); string? portStr = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(portStr) && int.TryParse(portStr, out int p) && p > 0 && p < 65536) { Config.WebPort = p; SaveConfigToFile(); Console.WriteLine($"端口已修改为 {p}。"); Console.WriteLine("注意:Web 端口修改需要重启程序才能生效!"); } else { Console.WriteLine("端口无效或未修改。"); } } // 重置 Web 登录信息 // 修改或生成新的 Web 登录凭据 static void ResetLoginInfo() { Console.WriteLine(">>> 重置 Web 登录信息 <<<"); Console.Write($"输入新用户名 (回车保持 '{Config.WebUser}'): "); string? user = Console.ReadLine(); if (!string.IsNullOrWhiteSpace(user)) Config.WebUser = user.Trim(); Console.Write("输入新密码 (回车自动生成随机密码): "); string? pass = Console.ReadLine(); if (string.IsNullOrWhiteSpace(pass)) { pass = GenerateRandomPassword(); Console.WriteLine($"已生成随机密码: {pass}"); } else { if (!ValidatePassword(pass)) { Console.WriteLine("密码强度不足(需至少包含字母、数字、符号中的两种,且长度>=6)。是否强制使用?(y/n)"); string? confirm = Console.ReadLine(); if (confirm?.Trim().ToLower() != "y") { Console.WriteLine("取消修改。"); return; } } } Config.WebPassword = pass; SaveConfigToFile(); Console.WriteLine("登录信息已更新并保存。"); } // 保存配置到文件 // 将内存中的 Config 对象序列化并写入 JSON 文件 public static void SaveConfigToFile() { try { string json = JsonSerializer.Serialize(Config, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(ConfigPath, json); Console.WriteLine("[系统] 配置文件已更新。"); } catch (Exception ex) { Console.WriteLine($"[错误] 保存配置文件失败: {ex.Message}"); } } // 加载配置文件 // 从 JSON 文件读取配置,如果不存在则提示 static void LoadConfig() { if (!File.Exists(ConfigPath)) { Console.WriteLine("未检测到配置文件,请通过 Web 页面进行初始化配置,或在控制台使用命令配置。"); isConfigured = false; } else { try { string json = File.ReadAllText(ConfigPath); var loadedConfig = JsonSerializer.Deserialize(json); if (loadedConfig != null) { Config = loadedConfig; isConfigured = true; Console.WriteLine($"已加载配置: RCON IP={Config.Ip}, Port={Config.Port}"); } else { Console.WriteLine("配置文件格式错误,需重新配置。"); isConfigured = false; } } catch (Exception ex) { Console.WriteLine($"读取配置文件出错: {ex.Message}"); isConfigured = false; } } } // 初始化 RCON 连接 // 建立连接并设置事件监听器 public static async Task InitializeRcon() { if (!string.IsNullOrEmpty(Config.Ip) && Config.Port > 0) { // 如果已有连接,尝试断开并清理资源 client?.Dispose(); client = new RconClient(Config.Ip, Config.Port, Config.Password); // 监听 RCON 服务端主动推送的消息 (如聊天、击杀日志) client.OnMessageReceived += (msg) => { // 1. 通知插件系统 try { OnRconMessageReceived?.Invoke(msg); } catch (Exception ex) { Console.WriteLine($"[Error] Plugin RCON message handler failed: {ex.Message}"); } // 2. 解析聊天消息 var chatMsg = RconParser.ParseChatMessage(msg); if (chatMsg != null) { // 尝试从缓存中补充玩家的队伍和分队信息 var player = RconCache.GetPlayer(chatMsg.SteamId, chatMsg.EosId); if (player != null) { chatMsg.TeamId = player.TeamId; chatMsg.SquadId = player.SquadId; } // 添加到内存缓存 RconCache.AddChatMessage(chatMsg); // 3. 保存到数据库 (SQLite) try { var chatLog = new ChatLog { Type = chatMsg.Type, EosId = chatMsg.EosId, SteamId = chatMsg.SteamId, Name = chatMsg.Name, Message = chatMsg.Message, Timestamp = chatMsg.Timestamp, TeamId = chatMsg.TeamId, SquadId = chatMsg.SquadId }; using (var db = DbHelper.GetInstance()) { db.Insertable(chatLog).ExecuteCommand(); } } catch (Exception ex) { Console.WriteLine($"[Error] Failed to save chat to DB: {ex.Message}"); } Console.WriteLine($"[{chatMsg.Type}] {chatMsg.Name}: {chatMsg.Message}"); // 4. 通过 SignalR 广播给前端 if (ChatHubContext != null) { _ = ChatHubContext.Clients.All.SendAsync("ReceiveChat", chatMsg); } } else { // 非聊天消息,直接打印 Console.WriteLine($"[RCON Push] {msg}"); } }; //client.OnLog += (log) => Console.WriteLine($"[RCON Log] {log}"); try { await client.ConnectAsync(); Console.WriteLine($"[系统] RCON 客户端已连接 ({Config.Ip}:{Config.Port})"); } catch (Exception ex) { Console.WriteLine($"[警告] RCON 连接失败: {ex.Message}"); } } } static int GetAvailablePort(int startPort) { int port = startPort; while (IsPortInUse(port)) { Console.WriteLine($"[系统] 端口 {port} 已被占用,尝试使用 {port + 10}..."); port += 10; if (port > 65535) { throw new Exception("无法找到可用端口"); } } if (port != startPort) { Console.WriteLine($"[系统] 最终使用端口: {port}"); } return port; } static bool IsPortInUse(int port) { bool inUse = false; System.Net.NetworkInformation.IPGlobalProperties ipProperties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); System.Net.IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners(); foreach (System.Net.IPEndPoint endPoint in ipEndPoints) { if (endPoint.Port == port) { inUse = true; break; } } return inUse; } static async Task KeepAliveAndReconnectTask() { while (true) { if (client != null && !client.IsConnected) { // Console.WriteLine("[系统] 检测到 RCON 未连接,正在尝试连接..."); try { await client.ConnectAsync(); // Console.WriteLine("[系统] RCON 重连成功"); } catch { // 静默失败,等待下次重试 } } await Task.Delay(5000); } } public static bool ValidatePassword(string pwd) { if (string.IsNullOrEmpty(pwd) || pwd.Length < 6) return false; int types = 0; if (pwd.Any(char.IsDigit)) types++; if (pwd.Any(char.IsLetter)) types++; if (pwd.Any(c => !char.IsLetterOrDigit(c))) types++; return types >= 2; } public static string GenerateRandomPassword() { const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const string digits = "0123456789"; const string symbols = "!@#$%^&*()_+-=[]{}|;:,.<>?"; var random = new Random(); var password = new char[12]; // 确保至少包含各类字符以满足要求 password[0] = chars[random.Next(chars.Length)]; password[1] = digits[random.Next(digits.Length)]; password[2] = symbols[random.Next(symbols.Length)]; string allChars = chars + digits + symbols; for (int i = 3; i < 12; i++) { password[i] = allChars[random.Next(allChars.Length)]; } // 打乱顺序 return new string(password.OrderBy(x => random.Next()).ToArray()); } } public class CommandRequest { public string Command { get; set; } = string.Empty; } public class LoginRequest { public string Username { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; public string CaptchaId { get; set; } = string.Empty; public string CaptchaCode { get; set; } = string.Empty; } public class SetupRequest { public string Ip { get; set; } = ""; public int Port { get; set; } public string Password { get; set; } = ""; public string WebUser { get; set; } = ""; public string WebPassword { get; set; } = ""; } public class ConfigSaveRequest { public string Filename { get; set; } = ""; public string Content { get; set; } = ""; } public class PasswordResetRequest { public string NewPassword { get; set; } = string.Empty; } public class BanRequest { public string SteamId { get; set; } = ""; public string Reason { get; set; } = ""; public string Duration { get; set; } = "perm"; } public class KickRequest { public string SteamId { get; set; } = ""; public string Reason { get; set; } = ""; } public class IntervalRequest { public int Seconds { get; set; } } public class TicketRequest { public int TeamId { get; set; } public int Tickets { get; set; } } public class GameTimeUpdateModel { public int Hours { get; set; } } }