This commit is contained in:
milimoe 2026-05-10 22:40:42 +08:00
parent ebc7f862f4
commit 471f897d31
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
6 changed files with 320 additions and 20 deletions

View File

@ -104,6 +104,7 @@ namespace Oshima.FunGame.WebAPI.Model
public MatchStatus Status { get; set; }
public DateTime StartTime { get; set; }
public string Stage { get; set; } = "";
public string Description { get; set; } = "";
/// <summary>
/// 竞猜截止时间
@ -225,4 +226,28 @@ namespace Oshima.FunGame.WebAPI.Model
[JsonPropertyName("team2_win_odds")]
public decimal? Team2WinOdds { get; set; }
}
public class UpdateMatchRequest
{
[JsonPropertyName("uid")]
public long Uid { get; set; }
[JsonPropertyName("match_id")]
public int MatchId { get; set; }
[JsonPropertyName("team1_win_odds")]
public decimal? Team1WinOdds { get; set; }
[JsonPropertyName("team2_win_odds")]
public decimal? Team2WinOdds { get; set; }
[JsonPropertyName("start_time")]
public DateTime? StartTime { get; set; }
[JsonPropertyName("bet_deadline")]
public DateTime? BetDeadline { get; set; }
[JsonPropertyName("description")]
public string? Description { get; set; }
}
}

View File

@ -4,6 +4,7 @@ using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Constant;
using Oshima.FunGame.OshimaServers.Model;
using Oshima.FunGame.WebAPI.Model;
namespace Oshima.FunGame.WebAPI.Services
{
@ -147,8 +148,6 @@ namespace Oshima.FunGame.WebAPI.Services
return ("暂无比赛赛程。", 1);
int offset = (page - 1) * pageSize;
sql.Parameters["@offset"] = offset;
sql.Parameters["@limit"] = pageSize;
sql.ExecuteDataSet($@"
SELECT m.id, m.team1_name, m.team2_name, m.status, m.start_time, m.stage,
@ -157,8 +156,9 @@ namespace Oshima.FunGame.WebAPI.Services
LEFT JOIN csbetting_events e ON m.event_id = e.id
ORDER BY
CASE m.status WHEN 1 THEN 0 WHEN 0 THEN 1 ELSE 2 END,
m.start_time ASC
LIMIT @limit OFFSET @offset");
CASE WHEN m.status = 2 THEN m.start_time END DESC,
CASE WHEN m.status != 2 THEN m.start_time END ASC
LIMIT {pageSize} OFFSET {offset}");
if (!sql.Success || sql.DataSet.Tables.Count == 0 || sql.DataSet.Tables[0].Rows.Count == 0)
return ("暂无比赛赛程。", 1);
@ -662,11 +662,11 @@ namespace Oshima.FunGame.WebAPI.Services
.Where(s => s.Length > 0)];
string optionsJson = System.Text.Json.JsonSerializer.Serialize(options);
// 如果比赛包含比分或 MVP 选项,不允许自定义猜胜者
// 如果比赛包含比分或 MVP 选项,不允许自定义猜胜者奖励
bool hasSpecialOption = options.Contains("score") || options.Contains("mvp");
if ((team1WinOdds.HasValue || team2WinOdds.HasValue) && hasSpecialOption)
{
error = "比赛包含比分或MVP选项时不能自定义猜胜者率。";
error = "比赛包含比分或MVP选项时不能自定义猜胜者奖励率。";
return false;
}
@ -674,7 +674,7 @@ namespace Oshima.FunGame.WebAPI.Services
decimal t2Odds = team2WinOdds ?? 2.50m;
if (t1Odds <= 0 || t2Odds <= 0)
{
error = "率必须大于0。";
error = "奖励率必须大于0。";
return false;
}
@ -732,6 +732,7 @@ namespace Oshima.FunGame.WebAPI.Services
return false;
}
sql.Parameters["@mid"] = matchId;
sql.Execute("UPDATE csbetting_matches SET status = 1 WHERE id = @mid");
if (sql.Success)
{
@ -745,5 +746,95 @@ namespace Oshima.FunGame.WebAPI.Services
message = "更新比赛状态失败。";
return false;
}
public static bool UpdateMatch(UpdateMatchRequest request, out string error)
{
error = "";
using SQLHelper? sql = Factory.OpenFactory.GetSQLHelper();
if (sql == null)
{
error = "数据库连接失败。";
return false;
}
// 检查比赛是否存在
sql.Parameters["@mid"] = request.MatchId;
sql.ExecuteDataSet("SELECT status, available_options FROM csbetting_matches WHERE id = @mid");
if (!sql.Success || sql.DataSet.Tables[0].Rows.Count == 0)
{
error = "比赛不存在。";
return false;
}
int status = Convert.ToInt32(sql.DataSet.Tables[0].Rows[0]["status"]);
if (status == 2)
{
error = "比赛已结束,无法修改。";
return false;
}
string available = sql.DataSet.Tables[0].Rows[0]["available_options"]?.ToString() ?? "[]";
bool hasSpecial = available.Contains("score", StringComparison.OrdinalIgnoreCase) || available.Contains("mvp", StringComparison.OrdinalIgnoreCase);
// 校验奖励率逻辑(同创建比赛)
if ((request.Team1WinOdds.HasValue || request.Team2WinOdds.HasValue) && hasSpecial)
{
error = "比赛包含比分或MVP选项时不能修改猜胜者奖励率。";
return false;
}
if ((request.Team1WinOdds.HasValue && request.Team1WinOdds <= 0) ||
(request.Team2WinOdds.HasValue && request.Team2WinOdds <= 0))
{
error = "奖励率必须大于0。";
return false;
}
// 构建动态UPDATE
StringBuilder setClause = new();
if (request.Team1WinOdds.HasValue)
{
sql.Parameters["@t1od"] = request.Team1WinOdds.Value;
setClause.Append("team1_win_odds = @t1od, ");
}
if (request.Team2WinOdds.HasValue)
{
sql.Parameters["@t2od"] = request.Team2WinOdds.Value;
setClause.Append("team2_win_odds = @t2od, ");
}
if (request.StartTime.HasValue)
{
sql.Parameters["@st"] = request.StartTime.Value;
setClause.Append("start_time = @st, ");
}
if (request.BetDeadline.HasValue)
{
sql.Parameters["@ddl"] = request.BetDeadline.Value;
setClause.Append("bet_deadline = @ddl, ");
}
if (request.Description != null)
{
sql.Parameters["@desc"] = (object?)request.Description ?? DBNull.Value;
setClause.Append("description = @desc, ");
}
if (setClause.Length == 0)
{
error = "没有提供任何修改参数。";
return false;
}
setClause.Remove(setClause.Length - 2, 2); // 移除最后的 ", "
sql.Parameters["@mid"] = request.MatchId;
string updateSql = $"UPDATE csbetting_matches SET {setClause} WHERE id = @mid";
sql.Execute(updateSql);
if (!sql.Success)
{
error = "修改比赛属性失败。";
return false;
}
return true;
}
}
}

View File

@ -2585,7 +2585,7 @@ namespace Oshima.FunGame.OshimaServers.Service
if (filteredActivities.Any())
{
builder.AppendLine($"【{CommonSet.GetActivityStatus(state)}】");
builder.AppendLine($"{string.Join("\r\n", filteredActivities.Select(a => a.GetIdName() + $"{a.GetTimeString(false)}"))}");
builder.AppendLine($"{string.Join("\r\n", filteredActivities.Select(a => a.GetIdName().CreateCmdInput($" {a.Id}") + $"{a.GetTimeString(false)}"))}");
}
}
@ -5068,7 +5068,7 @@ namespace Oshima.FunGame.OshimaServers.Service
progressString = $"\r\n当前进度{quest.Progress}/{quest.MaxProgress}";
}
string str = $"{quest.Id}. {quest.Name}\r\n" +
string str = $"{quest.Id}. {quest.Name.CreateCmdInput(BuildQuestCmdInput(quest, activity))}\r\n" +
$"{quest.Description}\r\n" +
(quest.QuestType == QuestType.Continuous ? $"需要时间:{quest.EstimatedMinutes} 分钟\r\n" : "") +
(quest.StartTime.HasValue ? $"开始时间:{quest.StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}" +
@ -5091,6 +5091,18 @@ namespace Oshima.FunGame.OshimaServers.Service
return str + progressString + (quest.SettleTime.HasValue ? $"\r\n结算时间{quest.SettleTime.Value.ToString(General.GeneralDateTimeFormatChinese)}" : "");
}
public static string BuildQuestCmdInput(Quest quest, Activity? activity = null)
{
if (quest.Status == QuestState.InProgress)
{
return $"做{(activity is null ? "" : "")}任务 {quest.Id}";
}
else
{
return activity is null ? "任务结算" : $"领取奖励 {activity.Id} {quest.Id}";
}
}
public static void RefreshNotice()
{
Notices.LoadConfig();

View File

@ -309,5 +309,35 @@ namespace Oshima.FunGame.WebAPI.Controllers
return reply;
}
}
[HttpPost("update-match")]
public BotReply UpdateMatch([FromBody] UpdateMatchRequest request)
{
MarkdownMessage md = new() { Content = busy };
BotReply reply = new() { Markdown = md };
try
{
if (!FunGameConstant.UserIdAndUsername.TryGetValue(request.Uid, out User? user) || (!user.IsAdmin && !user.IsOperator))
{
md.Content = "你没有权限执行此操作。";
return reply;
}
if (CSBettingService.UpdateMatch(request, out string error))
{
md.Content = $"比赛 {request.MatchId} 属性修改成功。";
}
else
{
md.Content = error;
}
return reply;
}
catch (Exception e)
{
Logger.LogError(e, "UpdateMatch 异常");
return reply;
}
}
}
}

View File

@ -94,7 +94,7 @@ namespace Oshima.FunGame.WebAPI.Services
BotReply reply2 = BettingController.GetMyBets(uid, mid: matchId);
if (reply.Markdown != null && reply.Markdown.Content != null && !(reply2.Markdown?.Content?.Equals("你还没有任何竞猜记录。") ?? true))
{
reply.Markdown.Content += reply2.Markdown.Content;
reply.Markdown.Content += (reply.Markdown.Content.Contains("点击下方按钮快速竞猜") ? "\r\n" : "") + reply2.Markdown.Content;
}
await SendAsync(e, "CS赛事竞猜", reply);
}
@ -357,7 +357,7 @@ namespace Oshima.FunGame.WebAPI.Services
return true;
}
// 解析剩余参数:选项和
// 解析剩余参数:选项和奖励
List<string> optionParts = [];
decimal? team1Odds = null, team2Odds = null;
for (int i = 8; i < parts.Length; i++)
@ -427,10 +427,87 @@ namespace Oshima.FunGame.WebAPI.Services
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 "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;
default:
await SendAsync(e, "修改比赛", $"未知参数:{key}");
return true;
}
}
// 调用控制器API
BotReply reply = BettingController.UpdateMatch(request);
reply.Keyboard = new KeyboardMessage()
.AppendButtons(2,
Button.CreateCmdButton("📋 赛事列表", "赛事列表"),
Button.CreateCmdButton("🔍 比赛详情", $"比赛详情 {matchId}"));
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();
}
}

View File

@ -1322,6 +1322,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "签到")
{
BotReply reply = Controller.SignIn(uid);
reply.Keyboard = new KeyboardMessage().AppendButtons(1, Button.CreateCmdButton("帮助", "帮助"));
await SendAsync(e, "签到", reply);
return result;
}
@ -2074,9 +2075,15 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "每日商店")
{
BotReply rpy = Controller.ShowDailyStore(uid);
rpy.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
rpy.Keyboard = new KeyboardMessage().AppendButtons(4, [
Button.CreateCmdButton("查看1", "商店查看 1", false),
Button.CreateCmdButton("查看2", "商店查看 2", false),
Button.CreateCmdButton("查看3", "商店查看 3", false),
Button.CreateCmdButton("查看4", "商店查看 4", false),
Button.CreateCmdButton("购买1", "商店购买 1", false),
Button.CreateCmdButton("购买2", "商店购买 2", false),
Button.CreateCmdButton("购买3", "商店购买 3", false),
Button.CreateCmdButton("购买4", "商店购买 4", false)
]);
await SendAsync(e, "商店", rpy);
return result;
@ -2131,6 +2138,7 @@ namespace Oshima.FunGame.WebAPI.Services
{
reply = Controller.SystemStoreShowInfo(uid, model.StoreRegion, model.StoreName, id);
}
reply.Keyboard = new KeyboardMessage().AppendButtons(1, Button.CreateCmdButton("购买此商品", $"商店购买 {id}", false));
await SendAsync(e, "商店", reply);
}
return result;
@ -2191,7 +2199,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "探索结算")
{
BotReply reply = Controller.SettleExploreAll(uid);
await SendAsync(e, "探索结算", string.Join("\r\n", reply));
await SendAsync(e, "探索结算", reply);
return result;
}
@ -2268,14 +2276,14 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "生命之泉")
{
BotReply reply = Controller.SpringOfLife(uid);
await SendAsync(e, "生命之泉", string.Join("\r\n", reply));
await SendAsync(e, "生命之泉", reply);
return result;
}
if (e.Detail == "酒馆" || e.Detail == "上酒")
{
BotReply reply = Controller.Pub(uid);
await SendAsync(e, "酒馆", string.Join("\r\n", reply));
await SendAsync(e, "酒馆", reply);
return result;
}
@ -2284,7 +2292,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (FunGameService.Activities.FirstOrDefault(a => a.Name == "毕业季") is Activity activity && activity.Status == ActivityState.InProgress)
{
BotReply reply = Controller.CreateGiftBox(uid, "毕业礼包", true, 2);
await SendAsync(e, "毕业礼包", string.Join("\r\n", reply));
await SendAsync(e, "毕业礼包", reply);
}
else
{
@ -2296,7 +2304,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "活动" || e.Detail == "活动中心")
{
BotReply reply = Controller.GetEvents(uid);
await SendAsync(e, "活动中心", string.Join("\r\n", reply));
await SendAsync(e, "活动中心", reply);
return result;
}
@ -2728,6 +2736,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "后勤部")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_logistics");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2735,6 +2747,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "武器商会")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_weapons");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2742,6 +2758,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "杂货铺")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_yuki");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2749,6 +2769,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "基金会")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_welfare");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2756,6 +2780,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "锻造商店")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_forge");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2763,6 +2791,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "赛马商店")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_horseracing");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2770,6 +2802,10 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail == "共斗商店")
{
BotReply reply = Controller.ShowSystemStore(uid, "铎京城", "dokyo_cooperative");
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
return result;
}
@ -2806,6 +2842,10 @@ namespace Oshima.FunGame.WebAPI.Services
default:
break;
}
reply.Keyboard = new KeyboardMessage().AppendButtons(2, [
Button.CreateCmdButton("商店查看", "商店查看", false),
Button.CreateCmdButton("商店购买", "商店购买", false)
]);
await SendAsync(e, "商店", reply);
}
return result;
@ -2841,7 +2881,12 @@ namespace Oshima.FunGame.WebAPI.Services
FunGameService.GenerateForgeResult(user, model, true);
if (model.ResultString != "")
{
await SendAsync(e, "模拟锻造配方", model.ResultString);
BotReply reply = new()
{
Markdown = new() { Content = model.ResultString }
};
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "模拟锻造配方", reply);
}
return result;
}
@ -2868,6 +2913,7 @@ namespace Oshima.FunGame.WebAPI.Services
}
BotReply reply = Controller.ForgeItem_Create(uid, recipeItems);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "锻造配方", reply);
return result;
}
@ -2875,6 +2921,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail.StartsWith("模拟锻造"))
{
BotReply reply = Controller.ForgeItem_Simulate(uid);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "模拟锻造", reply);
return result;
}
@ -2882,6 +2929,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail.StartsWith("取消锻造"))
{
BotReply reply = Controller.ForgeItem_Cancel(uid);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "取消锻造", reply);
return result;
}
@ -2889,6 +2937,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail.StartsWith("确认开始锻造"))
{
BotReply reply = Controller.ForgeItem_Complete(uid);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "确认开始锻造", reply);
return result;
}
@ -2896,6 +2945,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (e.Detail.StartsWith("锻造信息"))
{
BotReply reply = Controller.ForgeItem_Info(uid);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "锻造信息", reply);
return result;
}
@ -2910,6 +2960,7 @@ namespace Oshima.FunGame.WebAPI.Services
if (r != -1 && q != -1)
{
BotReply reply = Controller.ForgeItem_Master(uid, r, q);
reply = CreateForgeSystemButtons(reply);
await SendAsync(e, "大师锻造", reply);
}
}
@ -3523,6 +3574,20 @@ namespace Oshima.FunGame.WebAPI.Services
return real;
}
public BotReply CreateForgeSystemButtons(BotReply reply)
{
reply.Keyboard ??= new KeyboardMessage();
reply.Keyboard.AppendButtonsWithNewRow(2, [
Button.CreateCmdButton("创建配方", "锻造配方", false),
Button.CreateCmdButton("模拟配方", "模拟锻造配方", false),
Button.CreateCmdButton("锻造信息", "锻造信息", false),
Button.CreateCmdButton("取消锻造", "取消锻造", false),
Button.CreateCmdButton("模拟锻造", "模拟锻造", false),
Button.CreateCmdButton("确认锻造", "确认开始锻造", false)
]);
return reply;
}
public BotReply CreateMarkdownFromText(string text)
{
return new()