From d5327af742f989d99164b0a17ff34a7c979ccabf Mon Sep 17 00:00:00 2001 From: milimoe Date: Sat, 18 Jan 2025 16:01:01 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E7=BB=84=E6=9E=B6=E6=9E=84=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Constant}/Constant.cs | 3 +- OshimaMaps/AnonymousMap.cs | 2 +- OshimaMaps/FastAutoMap.cs | 2 +- OshimaMaps/OshimaMaps.csproj | 2 +- OshimaModes/FastAuto.cs | 2 +- OshimaModules/Modules/CharacterModule.cs | 1 + OshimaModules/Modules/ItemModule.cs | 1 + OshimaModules/Modules/SkillModule.cs | 1 + OshimaModules/OshimaModules.csproj | 4 + OshimaServers/AnonymousServer.cs | 7 +- OshimaServers/FastAutoServer.cs | 2 +- OshimaServers/OshimaServer.cs | 129 ++++++++++++++ OshimaServers/Service/FunGameService.cs | 2 +- OshimaWebAPI/Controllers/QQBotController.cs | 160 ++++++++++++++++++ .../Controllers/UserDailyController.cs | 10 +- OshimaWebAPI/Models/QQBot.cs | 136 +++++++++++++++ OshimaWebAPI/OshimaWebAPI.cs | 101 ++--------- OshimaWebAPI/OshimaWebAPI.csproj | 4 + OshimaWebAPI/Services/QQBotService.cs | 125 ++++++++++++++ .../UserDailyService.cs} | 4 +- 20 files changed, 590 insertions(+), 108 deletions(-) rename {OshimaModules/Modules => OshimaCore/Constant}/Constant.cs (92%) create mode 100644 OshimaServers/OshimaServer.cs create mode 100644 OshimaWebAPI/Controllers/QQBotController.cs create mode 100644 OshimaWebAPI/Models/QQBot.cs create mode 100644 OshimaWebAPI/Services/QQBotService.cs rename OshimaWebAPI/{Utils/UserDailyUtil.cs => Services/UserDailyService.cs} (97%) diff --git a/OshimaModules/Modules/Constant.cs b/OshimaCore/Constant/Constant.cs similarity index 92% rename from OshimaModules/Modules/Constant.cs rename to OshimaCore/Constant/Constant.cs index 070adaa..bba0dc3 100644 --- a/OshimaModules/Modules/Constant.cs +++ b/OshimaCore/Constant/Constant.cs @@ -1,6 +1,6 @@ using Milimoe.FunGame.Core.Library.Common.Addon; -namespace Oshima.FunGame.OshimaModules +namespace Oshima.Core.Constant { public class OshimaGameModuleConstant { @@ -16,6 +16,7 @@ namespace Oshima.FunGame.OshimaModules public const string FastAutoMap = "oshima.fungame.fastauto.map"; public const string Anonymous = "oshima.fungame.anonymous"; public const string AnonymousMap = "oshima.fungame.anonymous.map"; + public const string Server = "oshima.fungame.server"; private static readonly string[] Maps = [FastAutoMap]; private static readonly string[] Characters = [Character]; diff --git a/OshimaMaps/AnonymousMap.cs b/OshimaMaps/AnonymousMap.cs index d8a7029..6fa4281 100644 --- a/OshimaMaps/AnonymousMap.cs +++ b/OshimaMaps/AnonymousMap.cs @@ -1,5 +1,5 @@ using Milimoe.FunGame.Core.Library.Common.Addon; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; namespace Oshima.FunGame.OshimaMaps { diff --git a/OshimaMaps/FastAutoMap.cs b/OshimaMaps/FastAutoMap.cs index 40a9ef7..40bff8a 100644 --- a/OshimaMaps/FastAutoMap.cs +++ b/OshimaMaps/FastAutoMap.cs @@ -1,5 +1,5 @@ using Milimoe.FunGame.Core.Library.Common.Addon; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; namespace Oshima.FunGame.OshimaMaps { diff --git a/OshimaMaps/OshimaMaps.csproj b/OshimaMaps/OshimaMaps.csproj index 099fcfb..508a472 100644 --- a/OshimaMaps/OshimaMaps.csproj +++ b/OshimaMaps/OshimaMaps.csproj @@ -19,7 +19,7 @@ - + diff --git a/OshimaModes/FastAuto.cs b/OshimaModes/FastAuto.cs index bab0a32..cbcbc2f 100644 --- a/OshimaModes/FastAuto.cs +++ b/OshimaModes/FastAuto.cs @@ -5,7 +5,7 @@ using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Common.Event; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Model; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; namespace Oshima.FunGame.OshimaModes { diff --git a/OshimaModules/Modules/CharacterModule.cs b/OshimaModules/Modules/CharacterModule.cs index 4b61826..55401b0 100644 --- a/OshimaModules/Modules/CharacterModule.cs +++ b/OshimaModules/Modules/CharacterModule.cs @@ -1,5 +1,6 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaModules.Characters; namespace Oshima.FunGame.OshimaModules diff --git a/OshimaModules/Modules/ItemModule.cs b/OshimaModules/Modules/ItemModule.cs index 2e16ccb..d3d8129 100644 --- a/OshimaModules/Modules/ItemModule.cs +++ b/OshimaModules/Modules/ItemModule.cs @@ -1,5 +1,6 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaModules.Items; namespace Oshima.FunGame.OshimaModules diff --git a/OshimaModules/Modules/SkillModule.cs b/OshimaModules/Modules/SkillModule.cs index e7240f0..b210e39 100644 --- a/OshimaModules/Modules/SkillModule.cs +++ b/OshimaModules/Modules/SkillModule.cs @@ -1,5 +1,6 @@ using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaModules.Effects.ItemEffects; using Oshima.FunGame.OshimaModules.Effects.OpenEffects; using Oshima.FunGame.OshimaModules.Items; diff --git a/OshimaModules/OshimaModules.csproj b/OshimaModules/OshimaModules.csproj index 6119995..3c2bad8 100644 --- a/OshimaModules/OshimaModules.csproj +++ b/OshimaModules/OshimaModules.csproj @@ -22,6 +22,10 @@ 1701;1702;CS8981;IDE1006;IDE0130 + + + + ..\..\FunGame.Core\bin\Debug\net9.0\FunGame.Core.dll diff --git a/OshimaServers/AnonymousServer.cs b/OshimaServers/AnonymousServer.cs index e52653c..3ccabe1 100644 --- a/OshimaServers/AnonymousServer.cs +++ b/OshimaServers/AnonymousServer.cs @@ -5,10 +5,8 @@ using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Constant; -using Oshima.Core; using Oshima.Core.Configs; -using Oshima.FunGame.OshimaModules; -using Oshima.FunGame.OshimaServers.Service; +using Oshima.Core.Constant; namespace Oshima.FunGame.OshimaServers { @@ -94,9 +92,6 @@ namespace Oshima.FunGame.OshimaServers { Controller.NewSQLHelper(); Controller.NewMailSender(); - OSMCore.InitOSMCore(); - FunGameService.InitFunGame(); - FunGameSimulation.InitFunGameSimulation(); } /// diff --git a/OshimaServers/FastAutoServer.cs b/OshimaServers/FastAutoServer.cs index fe7ebae..2aabb9d 100644 --- a/OshimaServers/FastAutoServer.cs +++ b/OshimaServers/FastAutoServer.cs @@ -7,7 +7,7 @@ using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Library.Common.Addon; using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Model; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaModules.Items; using Oshima.FunGame.OshimaModules.Skills; using Oshima.FunGame.OshimaServers.Service; diff --git a/OshimaServers/OshimaServer.cs b/OshimaServers/OshimaServer.cs new file mode 100644 index 0000000..16ed62c --- /dev/null +++ b/OshimaServers/OshimaServer.cs @@ -0,0 +1,129 @@ +using Milimoe.FunGame.Core.Api.Utility; +using Milimoe.FunGame.Core.Entity; +using Milimoe.FunGame.Core.Library.Common.Addon; +using Milimoe.FunGame.Core.Library.Constant; +using Oshima.Core; +using Oshima.Core.Configs; +using Oshima.Core.Constant; +using Oshima.FunGame.OshimaServers.Service; +using TaskScheduler = Milimoe.FunGame.Core.Api.Utility.TaskScheduler; + +namespace Oshima.FunGame.OshimaServers +{ + public class OshimaServer : ServerPlugin + { + public override string Name => OshimaGameModuleConstant.Server; + + public override string Description => OshimaGameModuleConstant.Description; + + public override string Version => OshimaGameModuleConstant.Version; + + public override string Author => OshimaGameModuleConstant.Author; + + public override void ProcessInput(string input) + { + if (input.StartsWith("fungametest")) + { + FunGameSimulation.StartSimulationGame(true, true); + } + // OSM指令 + if (input.Length >= 4 && input[..4].Equals(".osm", StringComparison.CurrentCultureIgnoreCase)) + { + //MasterCommand.Execute(read, GeneralSettings.Master, false, GeneralSettings.Master, false); + Controller.WriteLine("试图使用 .osm 指令:" + input); + } + } + + public override void AfterLoad(ServerPluginLoader loader, params object[] objs) + { + Controller.NewSQLHelper(); + Controller.NewMailSender(); + OSMCore.InitOSMCore(); + FunGameService.InitFunGame(); + FunGameSimulation.InitFunGameSimulation(); + TaskScheduler.Shared.AddTask("重置每日运势", new TimeSpan(0, 0, 0), () => + { + Controller.WriteLine("已重置所有人的今日运势"); + Daily.ClearDaily(); + }); + TaskScheduler.Shared.AddTask("重置交易冷却1", new TimeSpan(9, 0, 0), () => + { + Controller.WriteLine("重置物品交易冷却时间"); + _ = FunGameService.AllowSellAndTrade(); + }); + TaskScheduler.Shared.AddTask("重置交易冷却2", new TimeSpan(15, 0, 0), () => + { + Controller.WriteLine("重置物品交易冷却时间"); + _ = FunGameService.AllowSellAndTrade(); + }); + TaskScheduler.Shared.AddRecurringTask("刷新存档缓存", TimeSpan.FromMinutes(1), () => + { + string directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/saved"; + if (Directory.Exists(directoryPath)) + { + string[] filePaths = Directory.GetFiles(directoryPath); + foreach (string filePath in filePaths) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + PluginConfig pc = new("saved", fileName); + pc.LoadConfig(); + if (pc.Count > 0) + { + User user = FunGameService.GetUser(pc); + // 将用户存入缓存 + FunGameService.UserIdAndUsername[user.Id] = user; + // 任务结算 + EntityModuleConfig quests = new("quests", user.Id.ToString()); + quests.LoadConfig(); + if (quests.Count > 0 && FunGameService.SettleQuest(user, quests)) + { + quests.SaveConfig(); + user.LastTime = DateTime.Now; + pc.Add("user", user); + pc.SaveConfig(); + } + } + } + Controller.WriteLine("读取 FunGame 存档缓存", LogLevel.Debug); + } + }, true); + TaskScheduler.Shared.AddTask("刷新每日任务", new TimeSpan(4, 0, 0), () => + { + string directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/quests"; + if (Directory.Exists(directoryPath)) + { + string[] filePaths = Directory.GetFiles(directoryPath); + foreach (string filePath in filePaths) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + EntityModuleConfig quests = new("quests", fileName); + quests.Clear(); + FunGameService.CheckQuestList(quests); + quests.SaveConfig(); + } + Controller.WriteLine("刷新每日任务"); + } + // 刷新签到 + directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/saved"; + if (Directory.Exists(directoryPath)) + { + string[] filePaths = Directory.GetFiles(directoryPath); + foreach (string filePath in filePaths) + { + string fileName = Path.GetFileNameWithoutExtension(filePath); + PluginConfig pc = new("saved", fileName); + pc.LoadConfig(); + pc.Add("signed", false); + pc.SaveConfig(); + } + Controller.WriteLine("刷新签到"); + } + }); + TaskScheduler.Shared.AddRecurringTask("刷新boss", TimeSpan.FromHours(1), () => + { + FunGameService.GenerateBoss(); + Controller.WriteLine("刷新boss"); + }, true); + } + } +} diff --git a/OshimaServers/Service/FunGameService.cs b/OshimaServers/Service/FunGameService.cs index de4d2d1..d160acb 100644 --- a/OshimaServers/Service/FunGameService.cs +++ b/OshimaServers/Service/FunGameService.cs @@ -2,7 +2,7 @@ using System.Text; using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Library.Constant; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaModules.Characters; using Oshima.FunGame.OshimaModules.Effects.OpenEffects; using Oshima.FunGame.OshimaModules.Items; diff --git a/OshimaWebAPI/Controllers/QQBotController.cs b/OshimaWebAPI/Controllers/QQBotController.cs new file mode 100644 index 0000000..b24b01a --- /dev/null +++ b/OshimaWebAPI/Controllers/QQBotController.cs @@ -0,0 +1,160 @@ +using System.Globalization; +using System.Text; +using System.Text.Json; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Oshima.FunGame.WebAPI.Models; +using Oshima.FunGame.WebAPI.Services; +using Rebex.Security.Cryptography; + +namespace Oshima.FunGame.WebAPI.Controllers +{ + [ApiController] + [Route("[controller]")] + public class QQBotController(IOptions botConfig, ILogger logger, QQBotService service) : ControllerBase + { + private readonly BotConfig _botConfig = botConfig.Value; + private readonly ILogger _logger = logger; + private readonly QQBotService _service = service; + + [HttpPost] + public async Task Post([FromBody] Payload? payload) + { + if (payload is null) + { + return BadRequest("Payload 格式无效"); + } + + _logger.LogDebug("收到 Webhook 请求:{payload}", payload); + + try + { + if (payload.Op == 13) + { + return HandleValidation(payload); + } + else if (payload.Op == 0) + { + // 处理其他事件 + return await HandleEventAsync(payload); + } + else + { + _logger.LogWarning("未处理操作码:{payload.Op}", payload.Op); + return Ok(); + } + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + return StatusCode(500, "服务器内部错误"); + } + } + + private IActionResult HandleValidation(Payload payload) + { + ValidationRequest? validationPayload = JsonSerializer.Deserialize(payload.Data.ToString() ?? ""); + if (validationPayload is null) + { + _logger.LogError("反序列化验证 Payload 失败"); + return BadRequest("无效的验证 Payload 格式"); + } + string seed = _botConfig.Secret; + while (seed.Length < 32) + { + seed += seed; + } + seed = seed[..32]; + + byte[] privateKeyBytes = Encoding.UTF8.GetBytes(seed); + + Ed25519 ed25519 = new(); + + ed25519.FromSeed(privateKeyBytes); + + // 将你的消息转换为 byte[] + byte[] message = Encoding.UTF8.GetBytes(validationPayload.EventTs + validationPayload.PlainToken); + + // 使用 Sign 方法签名消息 + byte[] result = ed25519.SignMessage(message); + + string signature = Convert.ToHexString(result).ToLower(CultureInfo.InvariantCulture); + + + ValidationResponse response = new() + { + PlainToken = validationPayload.PlainToken, + Signature = signature + }; + string responseJson = JsonSerializer.Serialize(response); + _logger.LogDebug("验证相应:{responseJson}", responseJson); + return Ok(response); + } + + private async Task HandleEventAsync(Payload payload) + { + _logger.LogDebug("处理事件:{EventType}, 数据:{Data}", payload.EventType, payload.Data); + + try + { + switch (payload.EventType) + { + case "C2C_MESSAGE_CREATE": + C2CMessage? c2cMessage = JsonSerializer.Deserialize(payload.Data.ToString() ?? ""); + if (c2cMessage != null) + { + // TODO + _logger.LogInformation("收到来自用户 {c2cMessage.Author.UserOpenId} 的消息:{c2cMessage.Content}", c2cMessage.Author.UserOpenId, c2cMessage.Content); + // 上传图片 + var (fileUuid, fileInfo, ttl, error) = await _service.UploadC2CMediaAsync(c2cMessage.Author.UserOpenId, 1, $"{Request.Scheme}://{Request.Host}{Request.PathBase}/images/zi/dj1.png"); + if (string.IsNullOrEmpty(error)) + { + // 回复富媒体消息 + await _service.SendC2CMessageAsync(c2cMessage.Author.UserOpenId, "", msgType: 7, media: new { file_info = fileInfo }); + } + else + { + _logger.LogError("上传图片失败:{error}", error); + } + } + else + { + _logger.LogError("反序列化 C2C 消息数据失败"); + return BadRequest("无效的 C2C 消息数据格式"); + } + break; + case "GROUP_AT_MESSAGE_CREATE": + GroupAtMessage? groupAtMessage = JsonSerializer.Deserialize(payload.Data.ToString() ?? ""); + if (groupAtMessage != null) + { + // TODO + _logger.LogInformation("收到来自群组 {groupAtMessage.GroupOpenId} 的消息:{groupAtMessage.Content}", groupAtMessage.GroupOpenId, groupAtMessage.Content); + // 回复消息 + await _service.SendGroupMessageAsync(groupAtMessage.GroupOpenId, $"你发送的消息是:{groupAtMessage.Content}", msgType: 0); + } + else + { + _logger.LogError("反序列化群聊消息数据失败"); + return BadRequest("无效的群聊消息数据格式"); + } + break; + default: + _logger.LogWarning("未定义事件:{EventType}", payload.EventType); + break; + } + return Ok(); + } + catch (JsonException e) + { + _logger.LogError("反序列化过程遇到错误:{e}", e); + return BadRequest("Invalid JSON format"); + } + catch (Exception e) + { + _logger.LogError("Error: {e}", e); + return StatusCode(500, "服务器内部错误"); + } + } + } +} diff --git a/OshimaWebAPI/Controllers/UserDailyController.cs b/OshimaWebAPI/Controllers/UserDailyController.cs index fca13cb..0e98d52 100644 --- a/OshimaWebAPI/Controllers/UserDailyController.cs +++ b/OshimaWebAPI/Controllers/UserDailyController.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using Milimoe.FunGame.Core.Api.Utility; using Oshima.Core.Configs; using Oshima.FunGame.WebAPI.Models; -using Oshima.FunGame.WebAPI.Utils; +using Oshima.FunGame.WebAPI.Services; namespace Oshima.FunGame.WebAPI.Controllers { @@ -16,13 +16,13 @@ namespace Oshima.FunGame.WebAPI.Controllers [HttpPost("get/{user_id}", Name = "GetUserDaily")] public UserDaily Get(long user_id) { - return UserDailyUtil.GetUserDaily(user_id); + return UserDailyService.GetUserDaily(user_id); } [HttpGet("view/{user_id}", Name = "ViewUserDaily")] public UserDaily View(long user_id) { - return UserDailyUtil.ViewUserDaily(user_id); + return UserDailyService.ViewUserDaily(user_id); } [HttpPost("open/{open_id}", Name = "GetOpenUserDaily")] @@ -30,7 +30,7 @@ namespace Oshima.FunGame.WebAPI.Controllers { if (QQOpenID.QQAndOpenID.TryGetValue(open_id, out long qq) && qq != 0) { - return UserDailyUtil.GetUserDaily(qq); + return UserDailyService.GetUserDaily(qq); } return new(0, 0, "ƺûаQQأȷ͡+QQš磺123456789ʹŶ"); } @@ -38,7 +38,7 @@ namespace Oshima.FunGame.WebAPI.Controllers [HttpPost("remove/{user_id}", Name = "RemoveUserDaily")] public string Remove(long user_id) { - return UserDailyUtil.RemoveDaily(user_id); + return UserDailyService.RemoveDaily(user_id); } [HttpGet("img/{type}", Name = "GetTypeImage")] diff --git a/OshimaWebAPI/Models/QQBot.cs b/OshimaWebAPI/Models/QQBot.cs new file mode 100644 index 0000000..731e6bf --- /dev/null +++ b/OshimaWebAPI/Models/QQBot.cs @@ -0,0 +1,136 @@ +using System.Text.Json.Serialization; + +namespace Oshima.FunGame.WebAPI.Models +{ + public class Payload + { + [JsonPropertyName("id")] + public string Id { get; set; } = ""; + + [JsonPropertyName("op")] + public int Op { get; set; } = 0; + + [JsonPropertyName("d")] + public object Data { get; set; } = new(); + + [JsonPropertyName("s")] + public int SequenceNumber { get; set; } = 0; + + [JsonPropertyName("t")] + public string EventType { get; set; } = ""; + } + + public class ValidationRequest + { + [JsonPropertyName("plain_token")] + public string PlainToken { get; set; } = ""; + + [JsonPropertyName("event_ts")] + public string EventTs { get; set; } = ""; + } + + public class ValidationResponse + { + [JsonPropertyName("plain_token")] + public string PlainToken { get; set; } = ""; + + [JsonPropertyName("signature")] + public string Signature { get; set; } = ""; + } + + public class BotConfig + { + public string AppId { get; set; } = ""; + public string Secret { get; set; } = ""; + } + + public class Author + { + [JsonPropertyName("user_openid")] + public string UserOpenId { get; set; } = ""; + + [JsonPropertyName("member_openid")] + public string MemberOpenId { get; set; } = ""; + } + + public class Attachment + { + [JsonPropertyName("content_type")] + public string ContentType { get; set; } = ""; + + [JsonPropertyName("filename")] + public string Filename { get; set; } = ""; + + [JsonPropertyName("height")] + public int Height { get; set; } = 0; + + [JsonPropertyName("width")] + public int Width { get; set; } = 0; + + [JsonPropertyName("size")] + public int Size { get; set; } = 0; + + [JsonPropertyName("url")] + public string Url { get; set; } = ""; + } + + public class C2CMessage + { + [JsonPropertyName("id")] + public string Id { get; set; } = ""; + + [JsonPropertyName("author")] + public Author Author { get; set; } = new(); + + [JsonPropertyName("content")] + public string Content { get; set; } = ""; + + [JsonPropertyName("timestamp")] + public string Timestamp { get; set; } = ""; + + [JsonPropertyName("attachments")] + public Attachment[] Attachments { get; set; } = []; + } + + public class GroupAtMessage + { + [JsonPropertyName("id")] + public string Id { get; set; } = ""; + + [JsonPropertyName("author")] + public Author Author { get; set; } = new(); + + [JsonPropertyName("content")] + public string Content { get; set; } = ""; + + [JsonPropertyName("timestamp")] + public string Timestamp { get; set; } = ""; + + [JsonPropertyName("group_openid")] + public string GroupOpenId { get; set; } = ""; + + [JsonPropertyName("attachments")] + public Attachment[] Attachments { get; set; } = []; + } + + public class MediaResponse + { + [JsonPropertyName("file_uuid")] + public string FileUuid { get; set; } = ""; + + [JsonPropertyName("file_info")] + public string FileInfo { get; set; } = ""; + + [JsonPropertyName("ttl")] + public int Ttl { get; set; } + } + + public class AccessTokenResponse + { + [JsonPropertyName("access_token")] + public string AccessToken { get; set; } = ""; + + [JsonPropertyName("expires_in")] + public string ExpiresIn { get; set; } = ""; + } +} diff --git a/OshimaWebAPI/OshimaWebAPI.cs b/OshimaWebAPI/OshimaWebAPI.cs index 6f0d6f7..23864b3 100644 --- a/OshimaWebAPI/OshimaWebAPI.cs +++ b/OshimaWebAPI/OshimaWebAPI.cs @@ -1,12 +1,13 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; using Milimoe.FunGame.Core.Api.Utility; -using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Library.Common.Addon; -using Milimoe.FunGame.Core.Library.Constant; using Oshima.Core.Configs; -using Oshima.FunGame.OshimaModules; +using Oshima.Core.Constant; using Oshima.FunGame.OshimaServers.Service; using Oshima.FunGame.WebAPI.Constant; -using TaskScheduler = Milimoe.FunGame.Core.Api.Utility.TaskScheduler; +using Oshima.FunGame.WebAPI.Models; +using Oshima.FunGame.WebAPI.Services; namespace Oshima.FunGame.WebAPI { @@ -34,95 +35,19 @@ namespace Oshima.FunGame.WebAPI } } - public override void AfterLoad(params object[] objs) + public override void AfterLoad(WebAPIPluginLoader loader, params object[] objs) { Statics.RunningPlugin = this; Controller.NewSQLHelper(); Controller.NewMailSender(); + if (objs.Length > 0 && objs[0] is WebApplicationBuilder builder) + { + builder.Services.AddMemoryCache(); + builder.Services.AddScoped(); + // 使用 Configure 从配置源绑定 + builder.Services.Configure(builder.Configuration.GetSection("Bot")); + } WebAPIAuthenticator.WebAPICustomBearerTokenAuthenticator += CustomBearerTokenAuthenticator; - TaskScheduler.Shared.AddTask("重置每日运势", new TimeSpan(0, 0, 0), () => - { - Controller.WriteLine("已重置所有人的今日运势"); - Daily.ClearDaily(); - }); - TaskScheduler.Shared.AddTask("重置交易冷却1", new TimeSpan(9, 0, 0), () => - { - Controller.WriteLine("重置物品交易冷却时间"); - _ = FunGameService.AllowSellAndTrade(); - }); - TaskScheduler.Shared.AddTask("重置交易冷却2", new TimeSpan(15, 0, 0), () => - { - Controller.WriteLine("重置物品交易冷却时间"); - _ = FunGameService.AllowSellAndTrade(); - }); - TaskScheduler.Shared.AddRecurringTask("刷新存档缓存", TimeSpan.FromMinutes(1), () => - { - string directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/saved"; - if (Directory.Exists(directoryPath)) - { - string[] filePaths = Directory.GetFiles(directoryPath); - foreach (string filePath in filePaths) - { - string fileName = Path.GetFileNameWithoutExtension(filePath); - PluginConfig pc = new("saved", fileName); - pc.LoadConfig(); - if (pc.Count > 0) - { - User user = FunGameService.GetUser(pc); - // 将用户存入缓存 - FunGameService.UserIdAndUsername[user.Id] = user; - // 任务结算 - EntityModuleConfig quests = new("quests", user.Id.ToString()); - quests.LoadConfig(); - if (quests.Count > 0 && FunGameService.SettleQuest(user, quests)) - { - quests.SaveConfig(); - user.LastTime = DateTime.Now; - pc.Add("user", user); - pc.SaveConfig(); - } - } - } - Controller.WriteLine("读取 FunGame 存档缓存", LogLevel.Debug); - } - }, true); - TaskScheduler.Shared.AddTask("刷新每日任务", new TimeSpan(4, 0, 0), () => - { - string directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/quests"; - if (Directory.Exists(directoryPath)) - { - string[] filePaths = Directory.GetFiles(directoryPath); - foreach (string filePath in filePaths) - { - string fileName = Path.GetFileNameWithoutExtension(filePath); - EntityModuleConfig quests = new("quests", fileName); - quests.Clear(); - FunGameService.CheckQuestList(quests); - quests.SaveConfig(); - } - Controller.WriteLine("刷新每日任务"); - } - // 刷新签到 - directoryPath = $@"{AppDomain.CurrentDomain.BaseDirectory}configs/saved"; - if (Directory.Exists(directoryPath)) - { - string[] filePaths = Directory.GetFiles(directoryPath); - foreach (string filePath in filePaths) - { - string fileName = Path.GetFileNameWithoutExtension(filePath); - PluginConfig pc = new("saved", fileName); - pc.LoadConfig(); - pc.Add("signed", false); - pc.SaveConfig(); - } - Controller.WriteLine("刷新签到"); - } - }); - TaskScheduler.Shared.AddRecurringTask("刷新boss", TimeSpan.FromHours(1), () => - { - FunGameService.GenerateBoss(); - Controller.WriteLine("刷新boss"); - }, true); } private string CustomBearerTokenAuthenticator(string token) diff --git a/OshimaWebAPI/OshimaWebAPI.csproj b/OshimaWebAPI/OshimaWebAPI.csproj index daad622..445fa0c 100644 --- a/OshimaWebAPI/OshimaWebAPI.csproj +++ b/OshimaWebAPI/OshimaWebAPI.csproj @@ -24,6 +24,10 @@ + + + + diff --git a/OshimaWebAPI/Services/QQBotService.cs b/OshimaWebAPI/Services/QQBotService.cs new file mode 100644 index 0000000..1623a98 --- /dev/null +++ b/OshimaWebAPI/Services/QQBotService.cs @@ -0,0 +1,125 @@ +using System.Net.Http.Headers; +using System.Text.Json; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; +using Oshima.FunGame.WebAPI.Constant; +using Oshima.FunGame.WebAPI.Models; + +namespace Oshima.FunGame.WebAPI.Services +{ + public class QQBotService(IOptions botConfig, IHttpClientFactory httpClientFactory, IMemoryCache memoryCache) + { + private readonly BotConfig _botConfig = botConfig.Value; + private readonly HttpClient _httpClient = httpClientFactory.CreateClient(); + private readonly IMemoryCache _memoryCache = memoryCache; + private const string AccessTokenCacheKey = "QQBotAccessToken"; + + public async Task SendC2CMessageAsync(string openid, string content, int msgType = 0, object? media = null, string? msgId = null, int? msgSeq = null) + { + await SendMessageAsync($"/v2/users/{openid}/messages", content, msgType, media, msgId, msgSeq); + } + + public async Task SendGroupMessageAsync(string groupOpenid, string content, int msgType = 0, object? media = null, string? msgId = null, int? msgSeq = null) + { + await SendMessageAsync($"/v2/groups/{groupOpenid}/messages", content, msgType, media, msgId, msgSeq); + } + + private async Task SendMessageAsync(string url, string content, int msgType = 0, object? media = null, string? msgId = null, int? msgSeq = null) + { + string accessToken = await GetAccessTokenAsync(); + HttpRequestMessage request = new(HttpMethod.Post, $"https://api.sgroup.qq.com{url}"); + request.Headers.Authorization = new AuthenticationHeaderValue("QQBot", accessToken); + Statics.RunningPlugin?.Controller.WriteLine($"使用的 Access Token:{accessToken}", Milimoe.FunGame.Core.Library.Constant.LogLevel.Debug); + Dictionary requestBody = new() + { + { "content", content }, + { "msg_type", msgType } + }; + if (media != null) + { + requestBody.Add("media", media); + } + if (!string.IsNullOrEmpty(msgId)) + { + requestBody.Add("msg_id", msgId); + } + if (msgSeq.HasValue) + { + requestBody.Add("msg_seq", msgSeq.Value); + } + request.Content = new StringContent(JsonSerializer.Serialize(requestBody), System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + } + + public async Task<(string? fileUuid, string? fileInfo, int ttl, string? error)> UploadC2CMediaAsync(string openid, int fileType, string url) + { + return await UploadMediaAsync($"/v2/users/{openid}/files", fileType, url); + } + + public async Task<(string? fileUuid, string? fileInfo, int ttl, string? error)> UploadGroupMediaAsync(string groupOpenid, int fileType, string url) + { + return await UploadMediaAsync($"/v2/groups/{groupOpenid}/files", fileType, url); + } + + private async Task<(string? fileUuid, string? fileInfo, int ttl, string? error)> UploadMediaAsync(string url, int fileType, string fileUrl) + { + string accessToken = await GetAccessTokenAsync(); + HttpRequestMessage request = new(HttpMethod.Post, $"https://api.sgroup.qq.com{url}"); + request.Headers.Authorization = new AuthenticationHeaderValue("QQBot", accessToken); + Statics.RunningPlugin?.Controller.WriteLine($"使用的 Access Token:{accessToken}", Milimoe.FunGame.Core.Library.Constant.LogLevel.Debug); + Dictionary requestBody = new() + { + { "file_type", fileType }, + { "url", fileUrl }, + { "srv_send_msg", false } + }; + request.Content = new StringContent(JsonSerializer.Serialize(requestBody), System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await _httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + string errorBody = await response.Content.ReadAsStringAsync(); + return (null, null, 0, $"状态码:{response.StatusCode},错误信息:{errorBody}"); + } + string responseBody = await response.Content.ReadAsStringAsync(); + MediaResponse? mediaResponse = JsonSerializer.Deserialize(responseBody); + if (mediaResponse == null) + { + return (null, null, 0, "反序列化富媒体消息失败。"); + } + Statics.RunningPlugin?.Controller.WriteLine($"接收到的富媒体消息:{mediaResponse.FileInfo}", Milimoe.FunGame.Core.Library.Constant.LogLevel.Debug); + return (mediaResponse.FileUuid, mediaResponse.FileInfo, mediaResponse.Ttl, null); + } + + public async Task GetAccessTokenAsync() + { + if (_memoryCache.TryGetValue(AccessTokenCacheKey, out string? accessToken) && !string.IsNullOrEmpty(accessToken)) + { + return accessToken; + } + + return await RefreshTokenAsync(); + } + + public async Task RefreshTokenAsync() + { + HttpRequestMessage request = new(HttpMethod.Post, "https://bots.qq.com/app/getAppAccessToken") + { + Content = new StringContent(JsonSerializer.Serialize(new { appId = _botConfig.AppId, clientSecret = _botConfig.Secret }), System.Text.Encoding.UTF8, "application/json") + }; + HttpResponseMessage response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + AccessTokenResponse? tokenResponse = JsonSerializer.Deserialize(responseBody); + if (tokenResponse == null || string.IsNullOrEmpty(tokenResponse.AccessToken) || !int.TryParse(tokenResponse.ExpiresIn, out int expiresIn)) + { + throw new Exception("获取 Access Token 失败!"); + } + _memoryCache.Set(AccessTokenCacheKey, tokenResponse.AccessToken, TimeSpan.FromSeconds(expiresIn - 60)); + Statics.RunningPlugin?.Controller.WriteLine($"获取到 Access Token:{tokenResponse.AccessToken}", Milimoe.FunGame.Core.Library.Constant.LogLevel.Debug); + return tokenResponse.AccessToken; + } + } +} diff --git a/OshimaWebAPI/Utils/UserDailyUtil.cs b/OshimaWebAPI/Services/UserDailyService.cs similarity index 97% rename from OshimaWebAPI/Utils/UserDailyUtil.cs rename to OshimaWebAPI/Services/UserDailyService.cs index ad83001..d748163 100644 --- a/OshimaWebAPI/Utils/UserDailyUtil.cs +++ b/OshimaWebAPI/Services/UserDailyService.cs @@ -3,9 +3,9 @@ using Oshima.Core.Configs; using Oshima.Core.Constant; using Oshima.FunGame.WebAPI.Models; -namespace Oshima.FunGame.WebAPI.Utils +namespace Oshima.FunGame.WebAPI.Services { - public class UserDailyUtil + public class UserDailyService { public static UserDaily GetUserDaily(long user_id) {