From a5d91d6fc518d4373bb06237d646771ea724a310 Mon Sep 17 00:00:00 2001 From: milimoe Date: Sat, 9 May 2026 16:44:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ttingSQLService.cs => CSBettingService.cs} | 205 ++++++++--- OshimaServers/Service/FunGameService.cs | 1 - .../Controllers/CSBettingController.cs | 25 +- .../Services/CSBettingInputHandler.cs | 336 ++++++++++++++++++ OshimaWebAPI/Services/RainBOTService.cs | 304 +--------------- 5 files changed, 520 insertions(+), 351 deletions(-) rename OshimaServers/Service/{CSBettingSQLService.cs => CSBettingService.cs} (63%) create mode 100644 OshimaWebAPI/Services/CSBettingInputHandler.cs diff --git a/OshimaServers/Service/CSBettingSQLService.cs b/OshimaServers/Service/CSBettingService.cs similarity index 63% rename from OshimaServers/Service/CSBettingSQLService.cs rename to OshimaServers/Service/CSBettingService.cs index 579995a..47432c9 100644 --- a/OshimaServers/Service/CSBettingSQLService.cs +++ b/OshimaServers/Service/CSBettingService.cs @@ -1,19 +1,21 @@ using System.Data; -using System.Security.Cryptography; using System.Text; using Milimoe.FunGame.Core.Api.Transmittal; using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Library.Constant; using Oshima.FunGame.OshimaServers.Model; namespace Oshima.FunGame.WebAPI.Services { - public class CSBettingSQLService + public class CSBettingService { public static string GetEventsOverview() { using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + UpdateStatuses(sql); + sql.ExecuteDataSet("SELECT id, name, status, start_time FROM csbetting_events ORDER BY start_time DESC"); if (!sql.Success || sql.DataSet.Tables.Count == 0) return "暂无赛事。"; StringBuilder sb = new(); @@ -35,6 +37,8 @@ namespace Oshima.FunGame.WebAPI.Services using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + UpdateStatuses(sql); + sql.Parameters["@id"] = eventId; sql.ExecuteDataSet("SELECT * FROM csbetting_events WHERE id = @id"); if (!sql.Success || sql == null || sql.DataSet.Tables[0].Rows.Count == 0) return "赛事不存在。"; @@ -66,7 +70,7 @@ namespace Oshima.FunGame.WebAPI.Services string mStatusStr = mstatus switch { 0 => "未开始", 1 => "进行中", 2 => "已结束", _ => "未知" }; string matchLabel = $"{t1} vs {t2}"; string clickableMatch = matchLabel.CreateCmdInput($"比赛详情 {mid}"); - sb.AppendLine($" [{mid}] {(stage != "" ? $"{stage} - " : "")} {clickableMatch} (状态:{mStatusStr}, 截止:{deadline:MM-dd HH:mm})"); + sb.AppendLine($" [{mid}] {(stage != "" ? $"{stage} " : "")} {clickableMatch} (状态:{mStatusStr}, 截止:{deadline:MM-dd HH:mm})"); } } return sb.ToString(); @@ -74,11 +78,14 @@ namespace Oshima.FunGame.WebAPI.Services return "数据库连接失败。"; } - public static string GetMatchDetail(int matchId) + public static string GetMatchDetail(int matchId, out int status) { + status = 0; using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + UpdateStatuses(sql); + sql.Parameters["@mid"] = matchId; sql.ExecuteDataSet("SELECT * FROM csbetting_matches WHERE id = @mid"); if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0) return "比赛不存在。"; @@ -86,11 +93,13 @@ namespace Oshima.FunGame.WebAPI.Services long eventId = Convert.ToInt64(row["event_id"]); string t1 = row["team1_name"].ToString() ?? ""; string t2 = row["team2_name"].ToString() ?? ""; - int status = Convert.ToInt32(row["status"]); + status = Convert.ToInt32(row["status"]); DateTime start = Convert.ToDateTime(row["start_time"]); DateTime deadline = Convert.ToDateTime(row["bet_deadline"]); string stage = row["stage"].ToString() ?? ""; string available = row["available_options"]?.ToString() ?? "[]"; + string result = row["result"] != DBNull.Value ? row["result"].ToString() ?? "" : ""; + long winner = row["winner"] != DBNull.Value ? Convert.ToInt64(row["winner"]) : 0; string eventName = ""; sql.Parameters["@eid"] = eventId; @@ -113,12 +122,22 @@ namespace Oshima.FunGame.WebAPI.Services sb.AppendLine($"开赛:{start:yyyy/MM/dd HH:mm}"); sb.AppendLine($"竞猜截止:{deadline:yyyy/MM/dd HH:mm}"); sb.AppendLine($"状态:{statusStr}"); - sb.AppendLine($"可用选项:"); - if (available.Contains("team1_win")) sb.AppendLine($" - {t1}胜 (x 2.5)"); - if (available.Contains("team2_win")) sb.AppendLine($" - {t2}胜 (x 2.5)"); - if (available.Contains("score")) sb.AppendLine($" - 精确比分 (x 3.5)"); - if (available.Contains("mvp")) sb.AppendLine($" - 赛事MVP (x 3.5)"); - return sb.ToString(); + if (status == 0) + { + sb.AppendLine($"可用选项:"); + if (available.Contains("team1_win")) sb.AppendLine($" - {t1}胜 (x 2.5)"); + if (available.Contains("team2_win")) sb.AppendLine($" - {t2}胜 (x 2.5)"); + if (available.Contains("score")) sb.AppendLine($" - 精确比分 (x 3.5)"); + if (available.Contains("mvp")) sb.AppendLine($" - 赛事MVP (x 3.5)"); + } + else if (status == 2) + { + string winnerName = winner switch { 1 => t1, 2 => t2, 3 => result, _ => "待定" }; + sb.AppendLine($"胜者:{winnerName}"); + if (winner != 3) sb.AppendLine($"结果:{result}"); + } + + return sb.ToString().Trim(); } return "数据库连接失败。"; } @@ -142,7 +161,24 @@ namespace Oshima.FunGame.WebAPI.Services DateTime deadline = Convert.ToDateTime(row["bet_deadline"]); if (status != 0 || DateTime.Now > deadline) { - error = "当前比赛已截止或非投注期。"; + error = "当前比赛已结束或非投注期。"; + return false; + } + + // --- 单场比赛投注上限检查 --- + long alreadyBet = 0; + long totalBet = amount; + sql.Parameters["@uid"] = uid; + sql.Parameters["@mid"] = matchId; + sql.ExecuteDataSet("SELECT COALESCE(SUM(amount), 0) AS total FROM csbetting_bet_records WHERE user_id = @uid AND match_id = @mid"); + if (sql.Success && sql.DataSet.Tables[0].Rows.Count > 0) + { + alreadyBet = Convert.ToInt64(sql.DataSet.Tables[0].Rows[0]["total"] ?? 0L); + totalBet += alreadyBet; + } + if (totalBet > 5000) + { + error = $"本场比赛你的投注总额不能超过 5000 {General.GameplayEquilibriumConstant.InGameCurrency}(已投 {alreadyBet})。"; return false; } @@ -193,19 +229,26 @@ namespace Oshima.FunGame.WebAPI.Services using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + UpdateStatuses(sql); + sql.Parameters["@mid"] = matchId; sql.ExecuteDataSet("SELECT * FROM csbetting_matches WHERE id = @mid"); if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0) return "比赛不存在。"; DataRow row = sql.DataSet.Tables[0].Rows[0]; + string available = row["available_options"]?.ToString() ?? "[]"; + bool isMvp = available.Contains("mvp", StringComparison.CurrentCultureIgnoreCase); int status = Convert.ToInt32(row["status"]); if (status == 2) return "比赛已结算。"; - int winTeam; - if (winner == "team1") winTeam = 1; - else if (winner == "team2") winTeam = 2; - else return "请指定获胜方为 team1 或 team2。"; + int winTeam = 3; + if (!isMvp) + { + if (winner == "team1") winTeam = 1; + else if (winner == "team2") winTeam = 2; + else return "请指定获胜方为 team1 或 team2。MVP 赛事获胜方请直接指定选手 ID。"; + } // 更新比赛结果 sql.Parameters["@res"] = result; @@ -228,7 +271,8 @@ namespace Oshima.FunGame.WebAPI.Services bool win = false; if (otype == 1 && winTeam == 1) win = true; else if (otype == 2 && winTeam == 2) win = true; - else if (otype == 3 && ovalue == result) win = true; + else if (otype == 3 && ovalue.Replace(":", ":").Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true; + else if (otype == 4 && ovalue.Equals(result, StringComparison.CurrentCultureIgnoreCase)) win = true; long payout = 0; string note = "未中奖"; @@ -249,46 +293,127 @@ namespace Oshima.FunGame.WebAPI.Services return "数据库连接失败。"; } - public static string GetMyBets(long uid) + public static string GetMyBets(long uid, long mid = -1) { using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper(); if (sql != null) { + UpdateStatuses(sql); + sql.Parameters["@uid"] = uid; - sql.ExecuteDataSet(@" - SELECT br.id, br.match_id, br.option_type, br.option_value, br.amount, br.bet_time, - br.is_settled, br.payout, br.is_claimed, br.result_note, - m.team1_name, m.team2_name - FROM csbetting_bet_records br - JOIN csbetting_matches m ON br.match_id = m.id - WHERE br.user_id = @uid - ORDER BY br.bet_time DESC"); + string matchFilter = ""; + if (mid > 0) + { + sql.Parameters["@mid"] = mid; + matchFilter = " AND br.match_id = @mid"; + } + sql.ExecuteDataSet($@"SELECT br.match_id, m.team1_name, m.team2_name, m.status AS match_status, + GROUP_CONCAT(CONCAT(br.option_type, ':', br.option_value, ':', br.amount) ORDER BY br.id SEPARATOR '|') AS details, + SUM(br.amount) AS total_amount, + MIN(br.is_settled) AS all_settled, + SUM(CASE WHEN br.is_settled = 1 AND br.payout > 0 AND br.is_claimed = 1 THEN 1 ELSE 0 END) AS claimed_count, + SUM(CASE WHEN br.is_settled = 1 AND br.payout > 0 AND br.is_claimed = 0 THEN 1 ELSE 0 END) AS unclaimed_count, + SUM(CASE WHEN br.is_settled = 1 AND br.payout = 0 THEN 1 ELSE 0 END) AS lost_count, + SUM(CASE WHEN br.is_settled = 1 THEN br.payout ELSE 0 END) AS total_payout + FROM csbetting_bet_records br + JOIN csbetting_matches m ON br.match_id = m.id + WHERE br.user_id = @uid {matchFilter} + GROUP BY br.match_id, m.team1_name, m.team2_name, m.status + ORDER BY MAX(br.bet_time) DESC"); if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0) return "你还没有任何竞猜记录。"; + StringBuilder sb = new(); foreach (DataRow row in sql.DataSet.Tables[0].Rows) { - long bid = Convert.ToInt64(row["id"]); - long mid = Convert.ToInt64(row["match_id"]); + int matchId = Convert.ToInt32(row["match_id"]); string t1 = row["team1_name"].ToString() ?? ""; string t2 = row["team2_name"].ToString() ?? ""; - int otype = Convert.ToInt32(row["option_type"]); - string ovalue = row["option_value"].ToString() ?? ""; - long amt = Convert.ToInt64(row["amount"]); - bool settled = Convert.ToBoolean(row["is_settled"]); - long? payout = row["payout"] as long?; - bool claimed = Convert.ToBoolean(row["is_claimed"]); + int matchStatus = Convert.ToInt32(row["match_status"]); + long totalAmount = Convert.ToInt64(row["total_amount"]); + long totalPayout = Convert.ToInt64(row["total_payout"]); + int allSettled = Convert.ToInt32(row["all_settled"]); + int claimedCount = Convert.ToInt32(row["claimed_count"]); + int unclaimedCount = Convert.ToInt32(row["unclaimed_count"]); + int lostCount = Convert.ToInt32(row["lost_count"]); - string optStr = otype switch { 1 => $"{t1}胜", 2 => $"{t2}胜", 3 => $"比分 {ovalue}", 4 => $"MVP {ovalue}", _ => ovalue }; - string statusStr = settled ? (payout > 0 ? (claimed ? $"+{payout} (已领)" : $"+{payout} (可领)") : "未中奖") : "进行中"; - string matchLabel = $"{t1} vs {t2}"; - string clickableMatch = matchLabel.CreateCmdInput($"比赛详情 {mid}"); - sb.AppendLine($"[{bid}] {clickableMatch} | 选项:{optStr} | 投注:{amt} | 状态:{statusStr}"); + // 解析投注详情 + string detailsStr = row["details"].ToString() ?? ""; + string[]?parts = detailsStr.Split('|'); + List summary = []; + foreach (string part in parts) + { + string[] items = part.Split(':'); + if (items.Length >= 3) + { + int otype = int.Parse(items[0]); + string ovalue = items[1]; + long oamount = long.Parse(items[2]); + string optStr = otype switch + { + 1 => $"{t1}胜", + 2 => $"{t2}胜", + 3 => $"比分 {ovalue}", + 4 => $"MVP {ovalue}", + _ => ovalue + }; + summary.Add($"{optStr} {oamount}G"); + } + } + + string detailLine = string.Join(", ", summary); + string statusLine; + if (allSettled == 0) + { + statusLine = "待开奖"; + } + else + { + if (claimedCount > 0 && unclaimedCount == 0 && lostCount == 0) + statusLine = "已领取"; + else if (unclaimedCount > 0) + statusLine = "待领奖"; + else if (lostCount > 0 && claimedCount == 0 && unclaimedCount == 0) + statusLine = "未中奖"; + else + statusLine = "部分已领"; + } + + string matchLabel = $"{t1} vs {t2}".CreateCmdInput($"比赛详情 {matchId}"); + sb.Append($"[比赛{matchId}] {matchLabel} | "); + sb.Append($"投注:{totalAmount}G ({detailLine}) | "); + sb.Append($"状态:{statusLine}"); + if (totalPayout > 0) + sb.Append($" (+{totalPayout}G)"); + sb.AppendLine(); } - return sb.ToString(); + return sb.ToString().TrimEnd(); } return "数据库连接失败。"; } + /// + /// 根据当前时间更新赛事和比赛的状态(仅更新未结束的记录) + /// + private static void UpdateStatuses(SQLHelper sql) + { + DateTime now = DateTime.Now; + + // 更新赛事状态:0→1 (进行中),1→2 (已结束) + sql.Parameters["@now"] = now; + sql.Execute("UPDATE csbetting_events SET status = 1 WHERE status = 0 AND start_time <= @now AND end_time > @now"); + sql.Parameters["@now"] = now; + sql.Execute("UPDATE csbetting_events SET status = 2 WHERE status <= 1 AND end_time <= @now"); + + // 更新比赛状态:0→1 (进行中),1→2 (已结束) - 注意不要覆盖已结算的比赛(winner 为 null 时视为未结束) + sql.Parameters["@now"] = now; + sql.Execute("UPDATE csbetting_matches SET status = 1 WHERE status = 0 AND start_time <= @now AND bet_deadline < @now AND winner IS NULL"); + // 对于已经过了开始时间但还没有 winner 且状态为 1 的,可保留为进行中;实际上只要 winner 为 null,状态应为 1(进行中) + // 如果有结果但 winner 不为 null,管理员应该已经手动结算,状态会设为 2,这里不做额外修改。 + // 安全起见,只更新未开始的,以及当比赛时间已过且无 winner 时自动变成进行中。 + // 如果需要自动结束(比如时间过长),可再添加规则,但竞猜系统通常由管理员手动结算结束。 + // 这里只做基础更新。 + } + public static long ClaimRewards(long uid) { // 返回领取的总金币,由上层加到用户身上 diff --git a/OshimaServers/Service/FunGameService.cs b/OshimaServers/Service/FunGameService.cs index b08fc44..be86b55 100644 --- a/OshimaServers/Service/FunGameService.cs +++ b/OshimaServers/Service/FunGameService.cs @@ -13,7 +13,6 @@ using Oshima.FunGame.OshimaModules.Regions; using Oshima.FunGame.OshimaModules.Skills; using Oshima.FunGame.OshimaModules.Units; using Oshima.FunGame.OshimaServers.Model; -using Oshima.FunGame.OshimaServers.Model; using ProjectRedbud.FunGame.SQLQueryExtension; namespace Oshima.FunGame.OshimaServers.Service diff --git a/OshimaWebAPI/Controllers/CSBettingController.cs b/OshimaWebAPI/Controllers/CSBettingController.cs index 65a5cd3..80a98b8 100644 --- a/OshimaWebAPI/Controllers/CSBettingController.cs +++ b/OshimaWebAPI/Controllers/CSBettingController.cs @@ -28,28 +28,35 @@ namespace Oshima.FunGame.WebAPI.Controllers [HttpGet("events")] public BotReply GetEventsOverview() { - return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingSQLService.GetEventsOverview() } }; + return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetEventsOverview() } }; } [AllowAnonymous] [HttpGet("event/{eventId:int}")] public BotReply GetEventDetail(int eventId) { - return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingSQLService.GetEventDetail(eventId) } }; + return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetEventDetail(eventId) } }; } [AllowAnonymous] [HttpGet("match/{matchId:int}")] public BotReply GetMatchDetail(int matchId) { - return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingSQLService.GetMatchDetail(matchId) } }; + return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetMatchDetail(matchId, out int status) + (status == 0 ? $"\r\n竞猜指令:{"竞猜".CreateCmdInput()} <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n👇🏻 点击下方按钮快速竞猜" : "")} }; } [AllowAnonymous] [HttpGet("mybets/{uid:long}")] public BotReply GetMyBets(long uid) { - return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingSQLService.GetMyBets(uid) } }; + return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetMyBets(uid) } }; + } + + [AllowAnonymous] + [HttpGet("mybets/{uid:long}/{mid:long}")] + public BotReply GetMyBets(long uid, long mid) + { + return new BotReply { Markdown = new MarkdownMessage { Content = CSBettingService.GetMyBets(uid, mid) } }; } // ---------- 需要用户锁的操作 ---------- @@ -88,7 +95,7 @@ namespace Oshima.FunGame.WebAPI.Controllers return reply; } - if (CSBettingSQLService.PlaceBet(uid, matchId, option, amount, out string error)) + if (CSBettingService.PlaceBet(uid, matchId, option, amount, out string error)) { user.Inventory.Credits -= (int)amount; FunGameService.SetUserConfigButNotRelease(uid, pc, user); @@ -131,7 +138,7 @@ namespace Oshima.FunGame.WebAPI.Controllers } User user = FunGameService.GetUser(pc); - long total = CSBettingSQLService.ClaimRewards(uid); + long total = CSBettingService.ClaimRewards(uid); if (total > 0) { user.Inventory.Credits += (int)total; @@ -176,7 +183,7 @@ namespace Oshima.FunGame.WebAPI.Controllers return reply; } - md.Content = CSBettingSQLService.SettleMatch(matchId, winner, result); + md.Content = CSBettingService.SettleMatch(matchId, winner, result); return reply; } catch (Exception e) @@ -202,7 +209,7 @@ namespace Oshima.FunGame.WebAPI.Controllers return reply; } - if (CSBettingSQLService.CreateEvent(request.Name, request.StartTime, request.EndTime, out string error, out long? newId)) + if (CSBettingService.CreateEvent(request.Name, request.StartTime, request.EndTime, out string error, out long? newId)) { md.Content = $"赛事创建成功!新赛事ID:{newId}"; } @@ -235,7 +242,7 @@ namespace Oshima.FunGame.WebAPI.Controllers return reply; } - if (CSBettingSQLService.CreateMatch(request.EventId, request.Team1Name, request.Team2Name, request.Stage, + if (CSBettingService.CreateMatch(request.EventId, request.Team1Name, request.Team2Name, request.Stage, request.StartTime, request.BetDeadline, request.AvailableOptions, out string error, out long? newId)) { md.Content = $"比赛创建成功!新比赛ID:{newId}"; diff --git a/OshimaWebAPI/Services/CSBettingInputHandler.cs b/OshimaWebAPI/Services/CSBettingInputHandler.cs new file mode 100644 index 0000000..8290b37 --- /dev/null +++ b/OshimaWebAPI/Services/CSBettingInputHandler.cs @@ -0,0 +1,336 @@ +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Constant; +using Oshima.FunGame.OshimaModules.Models; +using Oshima.FunGame.OshimaServers.Model; +using Oshima.FunGame.WebAPI.Model; + +namespace Oshima.FunGame.WebAPI.Services +{ + public partial class RainBOTService + { + public async Task HandleCSBettingInput(IBotMessage e, long uid) + { + if (e.Detail == "竞猜帮助") + { + e.UseNotice = false; + BotReply reply = new() + { + Markdown = new MarkdownMessage + { + Content = "🎮 CS赛事竞猜帮助:\r\n" + + $"✨ {"赛事列表".CreateCmdInput()} - 查看所有赛事\r\n" + + $"✨ {"我的竞猜".CreateCmdInput()} - 查看我的投注记录\r\n" + + $"✨ {"竞猜领奖".CreateCmdInput()} - 领取竞猜奖励\r\n" + + $"✨ {"比赛详情".CreateCmdInput()} - 查看单场比赛并投注" + }, + Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), + Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖"), + Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")) + }; + await SendAsync(e, "CS赛事竞猜", reply); + return true; + } + + // 赛事列表 + if (e.Detail == "赛事列表") + { + BotReply reply = BettingController.GetEventsOverview(); + await SendAsync(e, "CS赛事竞猜", reply); + return true; + } + + if (e.Detail.StartsWith("比赛详情")) + { + string detail = e.Detail.Replace("比赛详情", "").Trim(); + if (int.TryParse(detail, out int matchId)) + { + BotReply reply = BettingController.GetMatchDetail(matchId); + KeyboardMessage kb = new(); + // 构建投注键盘(填充“竞猜 <选项> ”) + if (reply.Markdown?.Content?.Contains("可用选项") ?? false) + { + kb.AppendButtons(2, + Button.CreateCmdButton("⚔️ 队伍1胜", $"竞猜 {matchId} team1 1000", enter: false), + Button.CreateCmdButton("🛡️ 队伍2胜", $"竞猜 {matchId} team2 1000", enter: false), + Button.CreateCmdButton("🎯 精确比分", $"竞猜 {matchId} score:", enter: false), + Button.CreateCmdButton("🏆 MVP", $"竞猜 {matchId} mvp:", enter: false)); + } + kb.AppendButtonsWithNewRow(2, + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖")); + reply.Keyboard = kb; + BotReply reply2 = BettingController.GetMyBets(uid, matchId); + if (reply.Markdown != null && reply.Markdown.Content != null && !(reply2.Markdown?.Content?.Equals("你还没有任何竞猜记录。") ?? true)) + { + reply.Markdown.Content = $"{reply.Markdown.Content.Trim()}\r\n你的本场竞猜记录:\r\n{reply2.Markdown.Content}"; + } + await SendAsync(e, "CS赛事竞猜", reply); + } + else + { + await SendAsync(e, "CS赛事竞猜", "格式:比赛详情 <比赛ID>"); + } + return true; + } + + // 赛事详情:用于查看某一赛事下的所有比赛 + if (e.Detail.StartsWith("赛事详情")) + { + string detail = e.Detail.Replace("赛事详情", "").Trim(); + if (int.TryParse(detail, out int eventId)) + { + // 调用控制器获取详情(返回BotReply) + BotReply reply = BettingController.GetEventDetail(eventId); + reply.Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("🔍 比赛详情 ", "比赛详情 ", enter: false), + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), + Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖")); + await SendAsync(e, "CS赛事竞猜", reply); + } + else + { + BotReply reply = new() + { + Markdown = new() + { + Content = "格式:赛事详情 <赛事ID>" + }, + Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")) + }; + await SendAsync(e, "CS赛事竞猜", reply); + } + return true; + } + + if (e.Detail == "我的竞猜") + { + BotReply reply = BettingController.GetMyBets(uid); + reply.Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖", enter: true), + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")); + if (reply.Markdown?.Content?.Contains("创建存档") ?? false) + { + reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档")); + } + await SendAsync(e, "CS赛事竞猜", reply); + return true; + } + + if (e.Detail == "竞猜领奖") + { + BotReply reply = BettingController.ClaimRewards(uid); + reply.Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), + Button.CreateCmdButton("📋 赛事列表", "赛事列表")); + await SendAsync(e, "CS赛事竞猜", reply); + return true; + } + + if (e.Detail.StartsWith("竞猜")) + { + string detail = e.Detail.Replace("竞猜", "").Trim(); + string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 3 || !int.TryParse(parts[0], out int mid) || !long.TryParse(parts[^1], out long amt)) + { + await SendAsync(e, "CS赛事竞猜", $"格式:竞猜 <比赛ID> <选项> <{General.GameplayEquilibriumConstant.InGameCurrency}数>\r\n选项:team1 / team2 / score:2:0 / mvp:选手UID"); + return true; + } + string option = string.Join(" ", parts[1..^1]).ToLower(); + + BotReply reply = BettingController.PlaceBet(uid, mid, option, amt); + + // 根据控制器返回的消息判断投注结果(简单判断是否包含"成功") + bool success = reply.Markdown?.Content?.Contains("成功") ?? false; + + KeyboardMessage kb = new(); + // 成功与失败通用的按钮 + kb.AppendButtons(2, + Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")); + + if (reply.Markdown?.Content?.Contains("创建存档") ?? false) + { + kb.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档")); + } + + // 成功时追加“继续查看该场比赛”按钮(填充指令) + if (success) + { + kb.AppendButtonsWithNewRow(2, + Button.CreateCmdButton("🔄 再次投注", e.Detail, enter: false), + Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {mid}")); + } + + reply.Keyboard = kb; + await SendAsync(e, "CS赛事竞猜", reply); + return true; + } + + // 管理员结算 + if (e.Detail.StartsWith("结算比赛")) + { + string detail = e.Detail.Replace("结算比赛", "").Trim(); + string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2 && int.TryParse(parts[0], out int mid)) + { + string winner = "", mResult = ""; + foreach (var p in parts[1..]) + { + if (p.StartsWith("winner=")) winner = p[7..]; + if (p.StartsWith("result=")) mResult = p[7..]; + } + BotReply reply = BettingController.SettleMatch(uid, mid, winner, mResult); + reply.Keyboard = new KeyboardMessage() + .AppendButtons(2, + Button.CreateCmdButton("📋 赛事列表", "赛事列表"), + Button.CreateCmdButton("⚙️ 继续结算", "结算比赛 ", enter: false)); + await SendAsync(e, "CS赛事竞猜", reply); + } + else + await SendAsync(e, "CS赛事竞猜", "格式:结算比赛 <比赛ID> winner=team1 result=2:0"); + return true; + } + + // 指令:创建赛事 <名称> <开始时间> <结束时间> + // 示例:创建赛事 春季赛 2026-03-01 2026-03-10 + // 示例:创建赛事 总决赛 2026-04-01 2026-04-05 1001,1002 + if (e.Detail.StartsWith("创建赛事")) + { + e.UseNotice = false; + if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator)) + { + await SendAsync(e, "创建赛事", "你没有权限执行此操作。"); + return true; + } + + string detail = e.Detail.Replace("创建赛事", "").Trim(); + string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 3) + { + await SendAsync(e, "创建赛事", "格式:创建赛事 <名称> <开始时间> <结束时间>\r\n" + + "时间格式:yyyy-MM-dd HH:mm 或 yyyy-MM-dd(默认00:00)\r\n" + + "示例:创建赛事 春季赛 2026-03-01 2026-03-10\r\n" + + "示例:创建赛事 总决赛 2026-04-01 12:00 2026-04-05 18:00"); + return true; + } + + string name = parts[0]; + if (name.Length > 100) + { + await SendAsync(e, "创建赛事", "赛事名称不能超过100个字符。"); + return true; + } + + // 尝试解析时间(支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm) + string startStr = parts[1] + (parts[2].Contains(':') ? " " + parts[2] : " 00:00"); + int nextIndex = parts[2].Contains(':') ? 3 : 2; + string endStr = parts[nextIndex] + (parts.Length > nextIndex + 1 && parts[nextIndex + 1].Contains(':') ? " " + parts[nextIndex + 1] : " 00:00"); + + if (!DateTime.TryParseExact(startStr, CheckDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime startTime) || + !DateTime.TryParseExact(endStr, CheckDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime endTime)) + { + await SendAsync(e, "创建赛事", "时间格式错误,请使用 yyyy-MM-dd 或 yyyy-MM-dd HH:mm。"); + return true; + } + + BotReply reply = BettingController.CreateEvent(new CreateEventRequest + { + Uid = uid, + Name = name, + StartTime = startTime, + EndTime = endTime + }); + await SendAsync(e, "创建赛事", reply); + return true; + } + + // 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)] + // 示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55 + // 示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win,team2_win,score + if (e.Detail.StartsWith("创建比赛")) + { + e.UseNotice = false; + if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator)) + { + await SendAsync(e, "创建比赛", "你没有权限执行此操作。"); + return true; + } + + string detail = e.Detail.Replace("创建比赛", "").Trim(); + string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 7) // 最少需要 eventId, team1, team2, stage, start date, start time, deadline date, deadline time + { + await SendAsync(e, "创建比赛", + "格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)]\r\n" + + "时间格式:yyyy-MM-dd HH:mm(开始/截止各占两段)\r\n" + + "示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55\r\n" + + "选项默认 team1_win,team2_win ,可额外添加 score,mvp"); + return true; + } + + if (!int.TryParse(parts[0], out int eventId)) + { + await SendAsync(e, "创建比赛", "赛事ID必须为数字。"); + return true; + } + + string team1 = parts[1]; + string team2 = parts[2]; + string stage = parts[3]; + + // 开始时间(parts[4] + parts[5]) + string startDate = parts[4]; + string startTime = parts[5]; + // 截止时间(parts[6] + parts[7] 如果存在) + if (parts.Length < 8) + { + await SendAsync(e, "创建比赛", "投注截止时间需要完整日期和时间,示例:2026-03-05 13:55"); + return true; + } + string deadlineDate = parts[6]; + string deadlineTime = parts[7]; + + if (!DateTime.TryParseExact(startDate + " " + startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime startDt) || + !DateTime.TryParseExact(deadlineDate + " " + deadlineTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime deadlineDt)) + { + await SendAsync(e, "创建比赛", "时间格式错误,请使用 yyyy-MM-dd HH:mm(开始时间和截止时间各两段)。"); + return true; + } + + string options = "team1_win,team2_win"; + if (parts.Length > 8) + { + options = string.Join(",", parts[8..]); // 剩余部分视为选项列表 + } + + BotReply reply = BettingController.CreateMatch(new CreateMatchRequest + { + Uid = uid, + EventId = eventId, + Team1Name = team1, + Team2Name = team2, + Stage = stage, + StartTime = startDt, + BetDeadline = deadlineDt, + AvailableOptions = options + }); + await SendAsync(e, "创建比赛", reply); + return true; + } + + return false; + } + } +} diff --git a/OshimaWebAPI/Services/RainBOTService.cs b/OshimaWebAPI/Services/RainBOTService.cs index 75fbaa4..27bbefe 100644 --- a/OshimaWebAPI/Services/RainBOTService.cs +++ b/OshimaWebAPI/Services/RainBOTService.cs @@ -13,14 +13,14 @@ using Oshima.FunGame.OshimaServers.Model; using Oshima.FunGame.OshimaServers.Service; using Oshima.FunGame.WebAPI.Constant; using Oshima.FunGame.WebAPI.Controllers; -using Oshima.FunGame.WebAPI.Model; using Oshima.FunGame.WebAPI.Models; namespace Oshima.FunGame.WebAPI.Services { - public class RainBOTService(FunGameController controller, QQController qqcontroller, QQBotService service, ILogger logger, IMemoryCache memoryCache, TestController testController, CSBettingController bettingController) + public partial class RainBOTService(FunGameController controller, QQController qqcontroller, QQBotService service, ILogger logger, IMemoryCache memoryCache, TestController testController, CSBettingController bettingController) { private static List FunGameItemType { get; } = ["卡包", "武器", "防具", "鞋子", "饰品", "消耗品", "魔法卡", "收藏品", "特殊物品", "任务物品", "礼包", "其他"]; + private static string[] CheckDateTimeFormat { get; } = ["yyyy-MM-dd HH:mm", "yyyy-MM-dd"]; private bool FunGameSimulation { get; set; } = false; private FunGameController Controller { get; } = controller; private QQController QQController { get; } = qqcontroller; @@ -3878,306 +3878,8 @@ namespace Oshima.FunGame.WebAPI.Services return result; } - if (e.Detail == "竞猜帮助") + if (await HandleCSBettingInput(e, uid)) { - e.UseNotice = false; - BotReply reply = new() - { - Markdown = new MarkdownMessage - { - Content = "🎮 CS赛事竞猜帮助:\r\n" - + $"✨ {"赛事列表".CreateCmdInput()} - 查看所有赛事\r\n" - + $"✨ {"我的竞猜".CreateCmdInput()} - 查看我的投注记录\r\n" - + $"✨ {"竞猜领奖".CreateCmdInput()} - 领取竞猜奖励\r\n" - + $"✨ {"比赛详情".CreateCmdInput()} - 查看单场比赛并投注" - }, - Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), - Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖"), - Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")) - }; - await SendAsync(e, "CS赛事竞猜", reply); - return true; - } - - // 赛事列表 - if (e.Detail == "赛事列表") - { - BotReply reply = BettingController.GetEventsOverview(); - await SendAsync(e, "CS赛事竞猜", reply); - return true; - } - - if (e.Detail.StartsWith("比赛详情")) - { - string detail = e.Detail.Replace("比赛详情", "").Trim(); - if (int.TryParse(detail, out int matchId)) - { - BotReply reply = BettingController.GetMatchDetail(matchId); - // 构建投注键盘(填充“竞猜 <选项> ”) - reply.Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("⚔️ 队伍1胜", $"竞猜 {matchId} team1 ", enter: false), - Button.CreateCmdButton("🛡️ 队伍2胜", $"竞猜 {matchId} team2 ", enter: false), - Button.CreateCmdButton("🎯 精确比分", $"竞猜 {matchId} score:", enter: false), - Button.CreateCmdButton("🏆 MVP", $"竞猜 {matchId} mvp:", enter: false)) - .AppendButtonsWithNewRow(2, - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖")); - await SendAsync(e, "CS赛事竞猜", reply); - } - else - { - await SendAsync(e, "CS赛事竞猜", "格式:比赛详情 <比赛ID>"); - } - return true; - } - - // 赛事详情:用于查看某一赛事下的所有比赛 - if (e.Detail.StartsWith("赛事详情")) - { - string detail = e.Detail.Replace("赛事详情", "").Trim(); - if (int.TryParse(detail, out int eventId)) - { - // 调用控制器获取详情(返回BotReply) - BotReply reply = BettingController.GetEventDetail(eventId); - reply.Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("🔍 比赛详情 ", "比赛详情 ", enter: false), - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), - Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖")); - await SendAsync(e, "CS赛事竞猜", reply); - } - else - { - BotReply reply = new() - { - Markdown = new() - { - Content = "格式:赛事详情 <赛事ID>" - }, - Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")) - }; - await SendAsync(e, "CS赛事竞猜", reply); - } - return true; - } - - if (e.Detail == "我的竞猜") - { - BotReply reply = BettingController.GetMyBets(uid); - reply.Keyboard = new KeyboardMessage() - .AppendButtons(3, - Button.CreateCmdButton("💰 竞猜领奖", "竞猜领奖", enter: true), - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")); - await SendAsync(e, "CS赛事竞猜", reply); - return true; - } - - if (e.Detail == "竞猜领奖") - { - BotReply reply = BettingController.ClaimRewards(uid); - reply.Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), - Button.CreateCmdButton("📋 赛事列表", "赛事列表")); - await SendAsync(e, "CS赛事竞猜", reply); - return true; - } - - if (e.Detail.StartsWith("竞猜")) - { - string detail = e.Detail.Replace("竞猜", "").Trim(); - string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length < 3 || !int.TryParse(parts[0], out int mid) || !long.TryParse(parts[^1], out long amt)) - { - await SendAsync(e, "CS赛事竞猜", "格式:竞猜 <比赛ID> <选项> <金额>\r\n选项:team1 / team2 / score:16:14 / mvp:选手UID"); - return true; - } - string option = string.Join(" ", parts[1..^1]).ToLower(); - - BotReply reply = BettingController.PlaceBet(uid, mid, option, amt); - - // 根据控制器返回的消息判断投注结果(简单判断是否包含"成功") - bool success = reply.Markdown?.Content?.Contains("成功") ?? false; - - KeyboardMessage kb = new(); - // 成功与失败通用的按钮 - kb.AppendButtons(3, - Button.CreateCmdButton("📜 我的竞猜", "我的竞猜"), - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("❓ 竞猜帮助", "竞猜帮助")); - - // 成功时追加“继续查看该场比赛”按钮(填充指令) - if (success) - { - kb.AppendButtonsWithNewRow(2, - Button.CreateCmdButton("🔄 再次投注", e.Detail, enter: false), - Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {mid}")); - } - - reply.Keyboard = kb; - await SendAsync(e, "CS赛事竞猜", reply); - return true; - } - - // 管理员结算 - if (e.Detail.StartsWith("结算比赛")) - { - string detail = e.Detail.Replace("结算比赛", "").Trim(); - string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length >= 2 && int.TryParse(parts[0], out int mid)) - { - string winner = "", mResult = ""; - foreach (var p in parts[1..]) - { - if (p.StartsWith("winner=")) winner = p[7..]; - if (p.StartsWith("result=")) mResult = p[7..]; - } - BotReply reply = BettingController.SettleMatch(uid, mid, winner, mResult); - reply.Keyboard = new KeyboardMessage() - .AppendButtons(2, - Button.CreateCmdButton("📋 赛事列表", "赛事列表"), - Button.CreateCmdButton("⚙️ 继续结算", "结算比赛 ", enter: false)); - await SendAsync(e, "CS赛事竞猜", reply); - } - else - await SendAsync(e, "CS赛事竞猜", "格式:结算比赛 <比赛ID> winner=team1 result=16:14"); - return true; - } - - // 指令:创建赛事 <名称> <开始时间> <结束时间> - // 示例:创建赛事 春季赛 2026-03-01 2026-03-10 - // 示例:创建赛事 总决赛 2026-04-01 2026-04-05 1001,1002 - if (e.Detail.StartsWith("创建赛事")) - { - e.UseNotice = false; - if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator)) - { - await SendAsync(e, "创建赛事", "你没有权限执行此操作。"); - return true; - } - - string detail = e.Detail.Replace("创建赛事", "").Trim(); - string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length < 3) - { - await SendAsync(e, "创建赛事", "格式:创建赛事 <名称> <开始时间> <结束时间>\r\n" + - "时间格式:yyyy-MM-dd HH:mm 或 yyyy-MM-dd(默认00:00)\r\n" + - "示例:创建赛事 春季赛 2026-03-01 2026-03-10\r\n" + - "示例:创建赛事 总决赛 2026-04-01 12:00 2026-04-05 18:00"); - return true; - } - - string name = parts[0]; - if (name.Length > 100) - { - await SendAsync(e, "创建赛事", "赛事名称不能超过100个字符。"); - return true; - } - - // 尝试解析时间(支持 yyyy-MM-dd 或 yyyy-MM-dd HH:mm) - string startStr = parts[1] + (parts[2].Contains(':') ? " " + parts[2] : " 00:00"); - int nextIndex = parts[2].Contains(':') ? 3 : 2; - string endStr = parts[nextIndex] + (parts.Length > nextIndex + 1 && parts[nextIndex + 1].Contains(':') ? " " + parts[nextIndex + 1] : " 00:00"); - int mvpStart = parts[nextIndex].Contains(':') ? nextIndex + 2 : nextIndex + 1; - - if (!DateTime.TryParseExact(startStr, new[] { "yyyy-MM-dd HH:mm", "yyyy-MM-dd" }, null, System.Globalization.DateTimeStyles.None, out DateTime startTime) || - !DateTime.TryParseExact(endStr, new[] { "yyyy-MM-dd HH:mm", "yyyy-MM-dd" }, null, System.Globalization.DateTimeStyles.None, out DateTime endTime)) - { - await SendAsync(e, "创建赛事", "时间格式错误,请使用 yyyy-MM-dd 或 yyyy-MM-dd HH:mm。"); - return true; - } - - BotReply reply = BettingController.CreateEvent(new CreateEventRequest - { - Uid = uid, - Name = name, - StartTime = startTime, - EndTime = endTime - }); - await SendAsync(e, "创建赛事", reply); - return true; - } - - // 指令:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)] - // 示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55 - // 示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 2026-04-02 17:55 team1_win,team2_win,score - if (e.Detail.StartsWith("创建比赛")) - { - e.UseNotice = false; - if (!FunGameConstant.UserIdAndUsername.TryGetValue(uid, out User? user) || (!user.IsAdmin && !user.IsOperator)) - { - await SendAsync(e, "创建比赛", "你没有权限执行此操作。"); - return true; - } - - string detail = e.Detail.Replace("创建比赛", "").Trim(); - string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries); - if (parts.Length < 7) // 最少需要 eventId, team1, team2, stage, start date, start time, deadline date, deadline time - { - await SendAsync(e, "创建比赛", - "格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> <投注截止时间> [选项列表(逗号分隔)]\r\n" + - "时间格式:yyyy-MM-dd HH:mm(开始/截止各占两段)\r\n" + - "示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 2026-03-05 13:55\r\n" + - "选项默认 team1_win,team2_win ,可额外添加 score,mvp"); - return true; - } - - if (!int.TryParse(parts[0], out int eventId)) - { - await SendAsync(e, "创建比赛", "赛事ID必须为数字。"); - return true; - } - - string team1 = parts[1]; - string team2 = parts[2]; - string stage = parts[3]; - - // 开始时间(parts[4] + parts[5]) - string startDate = parts[4]; - string startTime = parts[5]; - // 截止时间(parts[6] + parts[7] 如果存在) - if (parts.Length < 8) - { - await SendAsync(e, "创建比赛", "投注截止时间需要完整日期和时间,示例:2026-03-05 13:55"); - return true; - } - string deadlineDate = parts[6]; - string deadlineTime = parts[7]; - - if (!DateTime.TryParseExact(startDate + " " + startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime startDt) || - !DateTime.TryParseExact(deadlineDate + " " + deadlineTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime deadlineDt)) - { - await SendAsync(e, "创建比赛", "时间格式错误,请使用 yyyy-MM-dd HH:mm(开始时间和截止时间各两段)。"); - return true; - } - - string options = "team1_win,team2_win"; - if (parts.Length > 8) - { - options = string.Join(",", parts[8..]); // 剩余部分视为选项列表 - } - - BotReply reply = BettingController.CreateMatch(new CreateMatchRequest - { - Uid = uid, - EventId = eventId, - Team1Name = team1, - Team2Name = team2, - Stage = stage, - StartTime = startDt, - BetDeadline = deadlineDt, - AvailableOptions = options - }); - await SendAsync(e, "创建比赛", reply); return true; }