OshimaGameModule/OshimaWebAPI/Services/CSBettingInputHandler.cs
2026-05-14 20:19:29 +08:00

599 lines
30 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<bool> HandleCSBettingInput(IBotMessage e, long uid)
{
if (e.Detail == "预测帮助" || e.Detail == "赛事帮助")
{
e.UseNotice = false;
BotReply reply = new()
{
Markdown = new MarkdownMessage
{
Content = "🎮 赛事预测系统帮助:\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看所有赛事\r\n"
+ $"✨ {"".CreateCmdInput()} - 查看所有比赛\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("📜 我的预测", "我的预测"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"))
};
await SendAsync(e, "赛事预测", reply);
return true;
}
// 赛程:显示所有比赛
if (e.Detail.StartsWith("赛程") || e.Detail.StartsWith("比赛列表"))
{
int page = 1;
string detail = e.Detail.Replace("赛程", "").Replace("比赛列表", "").Trim();
System.Text.RegularExpressions.Match match = GetFirstNumber().Match(detail);
if (match.Success && int.TryParse(match.Value, out int p)) page = p;
BotReply reply = BettingController.GetAllMatches(page);
reply.Keyboard ??= new KeyboardMessage();
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
await SendAsync(e, "比赛赛程", 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();
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
BotReply reply2 = BettingController.GetMyBets(uid, mid: matchId);
if (reply.Markdown != null && reply.Markdown.Content != null && !(reply2.Markdown?.Content?.Equals("你还没有任何预测记录。") ?? true))
{
reply.Markdown.Content += "\r\n" + reply2.Markdown.Content;
}
await SendAsync(e, "赛事预测", reply);
}
else
{
await SendAsync(e, "赛事预测", "格式:比赛详情 <比赛ID>");
}
return true;
}
// 赛事详情:用于查看某一赛事下的所有比赛
if (e.Detail.StartsWith("赛事详情"))
{
string detail = e.Detail["赛事详情".Length..].Trim();
// 解析事件ID和页码
string[] parts = detail.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 1 && int.TryParse(parts[0], out int eventId))
{
int page = 1;
if (parts.Length > 1 && int.TryParse(parts[1], out int p)) page = p;
// 调用控制器获取详情返回BotReply
BotReply reply = BettingController.GetEventDetail(eventId, page);
reply.Keyboard ??= new KeyboardMessage();
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"));
await SendAsync(e, "赛事预测", reply);
}
else
{
BotReply reply = new()
{
Markdown = new()
{
Content = "格式:赛事详情 <赛事ID>"
},
Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"),
Button.CreateCmdButton("📜 我的预测", "我的预测"))
};
await SendAsync(e, "赛事预测", reply);
}
return true;
}
// 赛事列表
if (e.Detail.StartsWith("赛事") || e.Detail.StartsWith("赛事列表"))
{
int page = 1;
string detail = e.Detail.Replace("赛事", "").Replace("赛事列表", "").Trim();
System.Text.RegularExpressions.Match match = GetFirstNumber().Match(detail);
if (match.Success && int.TryParse(match.Value, out int p)) page = p;
BotReply reply = BettingController.GetEventsOverview(page);
await SendAsync(e, "赛事预测", reply);
return true;
}
if (e.Detail.StartsWith("我的预测"))
{
int page = 1;
string detail = e.Detail.Replace("我的预测", "").Trim();
System.Text.RegularExpressions.Match match = GetFirstNumber().Match(detail);
if (match.Success && int.TryParse(match.Value, out int p)) page = p;
BotReply reply = BettingController.GetMyBets(uid, page);
reply.Keyboard ??= new KeyboardMessage();
reply.Keyboard.AppendButtonsWithNewRow(2,
Button.CreateCmdButton("💰 预测领奖", "预测领奖", enter: true),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("❓ 预测帮助", "预测帮助"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档"));
}
await SendAsync(e, "赛事预测", reply);
return true;
}
if (e.Detail == "预测领奖")
{
BotReply reply = BettingController.ClaimRewards(uid);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📜 我的预测", "我的预测"),
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"));
if (reply.Markdown?.Content?.Contains("创建存档") ?? false)
{
reply.Keyboard.AppendButtons(2, Button.CreateCmdButton("⚙️ 创建存档", "创建存档"));
}
await SendAsync(e, "赛事预测", 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, "赛事预测", $"格式:预测 <比赛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("📅 比赛列表", "比赛列表"),
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, "赛事预测", 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("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("⚙️ 继续结算", "结算比赛 ", enter: false),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {mid}"));
await SendAsync(e, "赛事预测", reply);
}
else
await SendAsync(e, "赛事预测", "格式:结算比赛 <比赛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> <阶段> <开始时间> [选项列表(key=value格式空格分隔)]
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 < 6) // 最少需要 eventId, team1, team2, stage, start date, start time
{
await SendAsync(e, "创建比赛",
"格式:创建比赛 <赛事ID> <队伍1> <队伍2> <阶段> <开始时间> [选项列表(key=value格式空格分隔)]\r\n" +
"开始时间格式yyyy-MM-dd HH:mm两段\r\n" +
"可选参数ddl=截止时间需要用双引号包围opts=选项列表逗号分隔默认team1_win,team2_winteam1_win=奖励率team2_win=奖励率prob=胜率\r\n" +
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00\r\n" +
"示例:创建比赛 1 NAVI FaZe Quarter-final 2026-03-05 14:00 ddl=\"2026-03-05 13:55\" opts=team1_win,team2_win\r\n" +
"示例:创建比赛 1 G2 Vitality Semi-final 2026-04-02 18:00 ddl=\"2026-04-02 17:55\" opts=team1_win,team2_win team1_win=2.5 prob=0.6");
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];
if (!DateTime.TryParseExact(startDate + " " + startTime, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out DateTime startDt))
{
await SendAsync(e, "创建比赛", "开始时间格式错误,请使用 yyyy-MM-dd HH:mm日期和时间分为两段。");
return true;
}
// 解析剩余参数从第6段开始key=value格式value支持双引号包围
int spaceCount = 0, idx = 0;
for (; idx < detail.Length && spaceCount < 6; idx++)
{
if (detail[idx] == ' ') spaceCount++;
}
string paramString = detail[idx..].Trim();
System.Text.RegularExpressions.MatchCollection matches = GetParamValue().Matches(paramString);
// 解析剩余可选参数key=value 格式支持双引号包围含空格的value
Dictionary<string, string> paramDict = new(StringComparer.OrdinalIgnoreCase);
foreach (System.Text.RegularExpressions.Match m in matches)
{
string key = m.Groups[1].Value;
string value = m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value;
if (!paramDict.TryAdd(key, value))
{
await SendAsync(e, "创建比赛", $"参数 '{key}' 重复。");
return true;
}
}
// 解析截止时间 (ddl=),默认与开始时间相同
DateTime deadlineDt = startDt;
if (paramDict.TryGetValue("ddl", out string? ddlStr))
{
if (!DateTime.TryParseExact(ddlStr, "yyyy-MM-dd HH:mm", null, System.Globalization.DateTimeStyles.None, out deadlineDt))
{
await SendAsync(e, "创建比赛", "截止时间格式错误,请使用 yyyy-MM-dd HH:mm。");
return true;
}
}
// 解析选项列表 (opts=)
string options = "team1_win,team2_win";
if (paramDict.TryGetValue("opts", out string? optsStr))
{
options = optsStr; // 预期逗号分隔的列表
}
// 解析奖励率与胜率
decimal? team1Odds = null, team2Odds = null, team1WinProbability = null;
if (paramDict.TryGetValue("team1_win", out string? t1OddsStr))
{
if (decimal.TryParse(t1OddsStr, out decimal o1))
{
team1Odds = o1;
}
else
{
await SendAsync(e, "创建比赛", "team1_win 奖励率无效。");
return true;
}
}
if (paramDict.TryGetValue("team2_win", out string? t2OddsStr))
{
if (decimal.TryParse(t2OddsStr, out decimal o2))
{
team2Odds = o2;
}
else
{
await SendAsync(e, "创建比赛", "team2_win 奖励率无效。");
return true;
}
}
if (paramDict.TryGetValue("prob", out string? probStr))
{
if (decimal.TryParse(probStr, out decimal prob) && prob > 0 && prob < 1)
{
team1WinProbability = prob;
}
else
{
await SendAsync(e, "创建比赛", "胜率值无效应为0~1之间的小数。");
return true;
}
}
BotReply reply = BettingController.CreateMatch(new CreateMatchRequest
{
Uid = uid,
EventId = eventId,
Team1Name = team1,
Team2Name = team2,
Stage = stage,
StartTime = startDt,
BetDeadline = deadlineDt,
AvailableOptions = options,
Team1WinOdds = team1Odds,
Team2WinOdds = team2Odds,
Team1WinProbability = team1WinProbability
});
await SendAsync(e, "创建比赛", reply);
return true;
}
// 指令:关闭预测 <比赛ID>
if (e.Detail.StartsWith("关闭预测") || 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("关闭预测", "")
.Replace("结束预测", "")
.Trim();
if (int.TryParse(detail, out int matchId))
{
BotReply reply = BettingController.CloseBetting(uid, matchId);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
await SendAsync(e, "关闭预测", reply);
}
else
{
await SendAsync(e, "关闭预测", "格式:关闭预测 <比赛ID>");
}
return true;
}
// 指令:修改比赛 <比赛ID> [参数=值 ...]
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 < 2)
{
await SendAsync(e, "修改比赛",
"格式:修改比赛 <比赛ID> [参数=值]\r\n" +
"可用参数t1od=<奖励率> t2od=<奖励率> st=<开始时间> ddl=<截止时间> des=<描述>\r\n" +
"时间格式yyyy-MM-dd HH:mm\r\n" +
"示例:修改比赛 10 t1od=2.8 ddl=2026-05-12 18:00 des=\"决赛Bo3\"");
return true;
}
if (!int.TryParse(parts[0], out int matchId))
{
await SendAsync(e, "修改比赛", "比赛ID必须为数字。");
return true;
}
// 解析剩余参数 key=value支持引号包裹的含空格值
UpdateMatchRequest request = new() { Uid = uid, MatchId = matchId };
string remaining = string.Join(" ", parts[1..]);
// 使用正则匹配 key=valuevalue 可能带双引号
System.Text.RegularExpressions.MatchCollection matches = GetParamValue().Matches(remaining);
foreach (System.Text.RegularExpressions.Match m in matches)
{
string key = m.Groups[1].Value;
string val = m.Groups[2].Success ? m.Groups[2].Value : m.Groups[3].Value;
switch (key)
{
case "t1od":
if (decimal.TryParse(val, out decimal t1od)) request.Team1WinOdds = t1od;
else { await SendAsync(e, "修改比赛", "t1od 值无效。"); return true; }
break;
case "t2od":
if (decimal.TryParse(val, out decimal t2od)) request.Team2WinOdds = t2od;
else { await SendAsync(e, "修改比赛", "t2od 值无效。"); return true; }
break;
case "prob":
if (decimal.TryParse(val, out decimal prob)) request.Team1WinProbability = prob;
else { await SendAsync(e, "修改比赛", "prob 值无效。"); return true; }
break;
case "st":
if (DateTime.TryParse(val, out DateTime st)) request.StartTime = st;
else { await SendAsync(e, "修改比赛", "开始时间格式错误,请用 yyyy-MM-dd HH:mm。"); return true; }
break;
case "ddl":
if (DateTime.TryParse(val, out DateTime ddl)) request.BetDeadline = ddl;
else { await SendAsync(e, "修改比赛", "截止时间格式错误,请用 yyyy-MM-dd HH:mm。"); return true; }
break;
case "des":
request.Description = val; // 允许空字符串清空描述
break;
case "result":
request.Result = val;
break;
case "stage":
request.Stage = val;
break;
case "t1":
request.Team1 = val;
break;
case "t2":
request.Team2 = val;
break;
case "be":
if (val == "0" || val == "1")
request.BettingEnabled = val == "1";
else
{
await SendAsync(e, "修改比赛", "enabled 值必须为 0 或 1。");
return true;
}
break;
default:
await SendAsync(e, "修改比赛", $"未知参数:{key}");
return true;
}
}
// 调用控制器API
BotReply reply = BettingController.UpdateMatch(request);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
await SendAsync(e, "修改比赛", reply);
return true;
}
// 取消比赛指令
if (e.Detail.StartsWith("取消比赛"))
{
e.UseNotice = false;
string detail = e.Detail.Replace("取消比赛", "").Trim();
if (!int.TryParse(detail, out int matchId))
{
await SendAsync(e, "取消比赛", "格式:取消比赛 <比赛ID>");
return true;
}
BotReply reply = BettingController.CancelMatch(uid, matchId);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("📅 比赛列表", "比赛列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"),
Button.CreateCmdButton("💰 预测领奖", "预测领奖"));
await SendAsync(e, "取消比赛", reply);
return true;
}
return false;
}
[System.Text.RegularExpressions.GeneratedRegex(@"\d+")]
private static partial System.Text.RegularExpressions.Regex GetFirstNumber();
[System.Text.RegularExpressions.GeneratedRegex(@"(\w+)=(?:""([^""]*)""|(\S+))")]
private static partial System.Text.RegularExpressions.Regex GetParamValue();
}
}