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)
{