修复活动相关逻辑,添加markdown消息发送

This commit is contained in:
milimoe 2026-05-03 17:43:45 +08:00
parent 8f02fb5232
commit 2f969d2ebd
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
7 changed files with 331 additions and 42 deletions

View File

@ -2549,14 +2549,26 @@ namespace Oshima.FunGame.OshimaServers.Service
}
ActivitiesEventCache.RemoveAll(willRemove.Contains);
}
foreach (Activity activity in Activities)
{
if (user != null && (activity.Status == ActivityState.InProgress || activity.Status == ActivityState.Ended))
{
if (SettleQuest(user, activity.Quests, activity))
{
update = true;
}
}
if (activity.Predecessor != -1 && Activities.FirstOrDefault(a => a.Id == activity.Predecessor) is Activity preActivity)
{
activity.PredecessorStatus = preActivity.Status;
activity.UpdateState();
update = true;
}
}
if (update)
{
foreach (Activity activity in Activities)
{
if (user != null && (activity.Status == ActivityState.InProgress || activity.Status == ActivityState.Ended))
{
SettleQuest(user, activity.Quests, activity);
}
activities.Add(activity.Id.ToString(), activity);
}
activities.SaveConfig();
@ -2565,7 +2577,7 @@ namespace Oshima.FunGame.OshimaServers.Service
StringBuilder builder = new();
builder.AppendLine("★☆★ 活动中心 ★☆★");
ActivityState[] status = [ActivityState.InProgress, ActivityState.Upcoming, ActivityState.Future, ActivityState.Ended];
ActivityState[] status = [ActivityState.InProgress, ActivityState.ClaimPeriod, ActivityState.Upcoming, ActivityState.Future, ActivityState.Ended];
foreach (ActivityState state in status)
{
IEnumerable<Activity> filteredActivities = activities.Values.Where(a => a.Status == state);
@ -2599,7 +2611,7 @@ namespace Oshima.FunGame.OshimaServers.Service
GetEventCenter(user);
if (Activities.FirstOrDefault(a => a.Id == id) is Activity activity)
{
string result = activity.ToString();
string result = GetActivityString(activity, true, false, user);
if (user != null)
{
EntityModuleConfig<Activity> userActivities = new("activities", user.Id.ToString());
@ -2634,6 +2646,20 @@ namespace Oshima.FunGame.OshimaServers.Service
return "该活动已删除!";
}
public static void SaveActivities()
{
lock (Activities)
{
EntityModuleConfig<Activity> activities = new("activities", "activities");
activities.LoadConfig();
foreach (Activity activity in Activities)
{
activities.Add(activity.Id.ToString(), activity);
}
activities.SaveConfig();
}
}
public static bool AddEventActivity(Activity activity, EntityModuleConfig<Activity> userActivities)
{
if (activity.Id == 7 && activity.Status == ActivityState.InProgress)
@ -4997,10 +5023,7 @@ namespace Oshima.FunGame.OshimaServers.Service
{
for (int i = 0; i < qty; i++)
{
if (FunGameConstant.AllItems.FirstOrDefault(i => i.Name == item.Name) != null)
{
AddItemToUserInventory(user, item, copyLevel: item.ItemType == ItemType.MagicCard);
}
AddItemToUserInventory(user, item, copyLevel: item.ItemType == ItemType.MagicCard);
}
}
}

View File

@ -6664,6 +6664,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
{
msg = awardString;
activity.RegisterAwardedUser(user.Id, quest);
FunGameService.SaveActivities();
}
else
{

View File

@ -22,7 +22,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
private RainBOTService FungameService { get; set; } = fungameService;
[HttpPost]
public IActionResult Post([FromBody] Payload? payload)
public async Task<IActionResult> Post([FromBody] Payload? payload)
{
if (payload is null)
{
@ -40,7 +40,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
else if (payload.Op == 0)
{
// 处理其他事件
return HandleEventAsync(payload);
return await HandleEventAsync(payload);
}
else
{
@ -95,7 +95,7 @@ namespace Oshima.FunGame.WebAPI.Controllers
return Ok(response);
}
private IActionResult HandleEventAsync(Payload payload)
private async Task<IActionResult> HandleEventAsync(Payload payload)
{
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug("处理事件:{EventType}, 数据:{Data}", payload.EventType, payload.Data);
@ -119,21 +119,55 @@ namespace Oshima.FunGame.WebAPI.Controllers
}
// TODO
if (Logger.IsEnabled(LogLevel.Information)) Logger.LogInformation("收到来自用户 {c2cMessage.Author.UserOpenId} 的消息:{c2cMessage.Content}", c2cMessage.Author.UserOpenId, c2cMessage.Content);
//// 上传图片
// 上传图片
//string url = $"{Request.Scheme}://{Request.Host}{Request.PathBase}/images/zi/dj1.png";
//var (fileUuid, fileInfo, ttl, error) = await _service.UploadC2CMediaAsync(c2cMessage.Author.UserOpenId, 1, url);
//_if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug("发送的图片地址:{url}", url);
//if (string.IsNullOrEmpty(error))
//UploadMediaResult uploadMediaResult = await Service.UploadC2CMediaAsync(c2cMessage.Author.UserOpenId, 1, url);
//if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug("发送的图片地址:{url}", url);
//if (string.IsNullOrEmpty(uploadMediaResult.Error))
//{
// // 回复消息
// await _service.SendC2CMessageAsync(c2cMessage.Author.UserOpenId, $"你发送的消息是:{c2cMessage.Content}", msgId: c2cMessage.Id);
// await Service.SendC2CMessageAsync(c2cMessage.Author.UserOpenId, $"你发送的消息是:{c2cMessage.Content}", msgId: c2cMessage.Id);
// // 回复富媒体消息
// await _service.SendC2CMessageAsync(c2cMessage.Author.UserOpenId, "", msgType: 7, media: new { file_info = fileInfo }, msgId: c2cMessage.Id);
// await Service.SendC2CMessageAsync(c2cMessage.Author.UserOpenId, "", msgType: 7, media: new { file_info = uploadMediaResult.FileInfo }, msgId: c2cMessage.Id);
//}
//else
//{
// _if (Logger.IsEnabled(LogLevel.Error)) Logger.LogError("上传图片失败:{error}", error);
// if (Logger.IsEnabled(LogLevel.Error)) Logger.LogError("上传图片失败:{error}", uploadMediaResult.Error);
//}
// 发 md 示例
//// 1. 带蓝字链接
//MarkdownMessage mdMsg = new()
//{
// Content = "请选择:\n<qqbot-cmd-enter text=\"/签到\"/>"
//};
//// 2. 带按钮
//KeyboardMessage kbMsg = new()
//{
// Content = new()
// {
// Rows = [
// new()
// {
// Buttons = [
// new()
// {
// Id = "btn1",
// RenderData = new RenderData { Label = "同意", VisitedLabel = "已同意", Style = 1 },
// Action = new Models.Action
// {
// Type = 2,
// Data = "我同意服务条款",
// Enter = true,
// Reply = false,
// Permission = new Permission { Type = 2 }
// }
// }
// ]
// }
// ]
// }
//};
//await Service.SendC2CMarkdownAsync(c2cMessage.AuthorOpenId, mdMsg, kbMsg, c2cMessage.Id);
TaskUtility.NewTask(async () => await FungameService.Handler(c2cMessage, data));
}
else

View File

@ -213,6 +213,35 @@ namespace Oshima.FunGame.WebAPI.Models
public int Ttl { get; set; }
}
public class UploadMediaResult
{
/// <summary>
/// 文件 UUID成功时有值
/// </summary>
public string? FileUuid { get; set; }
/// <summary>
/// 文件信息,成功时有值
/// </summary>
public string? FileInfo { get; set; }
/// <summary>
/// 有效期(秒)
/// </summary>
public int Ttl { get; set; }
/// <summary>
/// 错误信息,成功时为 null
/// </summary>
public string? Error { get; set; }
/// <summary>
/// 是否上传成功
/// </summary>
[JsonIgnore]
public bool IsSuccess => string.IsNullOrEmpty(Error);
}
public class AccessTokenResponse
{
[JsonPropertyName("access_token")]
@ -226,4 +255,137 @@ namespace Oshima.FunGame.WebAPI.Models
{
public string RequestUrl { get; set; } = "";
}
public class MarkdownMessage
{
/// <summary>
/// 自定义 Markdown和模板 ID 互斥,只能用一种方式)
/// </summary>
[JsonPropertyName("content")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Content { get; set; }
/// <summary>
/// 模板 ID
/// </summary>
[JsonPropertyName("custom_template_id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? CustomTemplateId { get; set; }
/// <summary>
/// 模板参数列表(仅在模板模式下使用)
/// </summary>
[JsonPropertyName("params")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<MarkdownParam>? Params { get; set; }
}
public class MarkdownParam
{
[JsonPropertyName("key")]
public string Key { get; set; } = "";
[JsonPropertyName("values")]
public List<string> Values { get; set; } = [];
}
public class KeyboardMessage
{
/// <summary>
/// 模板按钮 ID与 content 互斥)
/// </summary>
[JsonPropertyName("id")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Id { get; set; }
/// <summary>
/// 自定义按钮内容
/// </summary>
[JsonPropertyName("content")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public KeyboardContent? Content { get; set; }
}
public class KeyboardContent
{
[JsonPropertyName("rows")]
public List<Row> Rows { get; set; } = [];
}
public class Row
{
[JsonPropertyName("buttons")]
public List<Button> Buttons { get; set; } = [];
}
public class Button
{
[JsonPropertyName("id")]
public string Id { get; set; } = "";
[JsonPropertyName("render_data")]
public RenderData RenderData { get; set; } = new();
[JsonPropertyName("action")]
public Action Action { get; set; } = new();
}
public class RenderData
{
[JsonPropertyName("label")]
public string Label { get; set; } = "";
[JsonPropertyName("visited_label")]
public string VisitedLabel { get; set; } = "";
[JsonPropertyName("style")]
public int Style { get; set; } = 0;
}
public class Action
{
/// <summary>
/// 种类0 - 跳转HTTP/小程序1 - 回调(机器人后台会收到 INTERACTION_CREATE 事件data 会回传2 - 指令(发送消息或填充输入框,常用)
/// </summary>
[JsonPropertyName("type")]
public int Type { get; set; }
/// <summary>
/// 按钮的点击权限,内部的 Type = 0 为指定用户1 为管理者2 为所有人。
/// </summary>
[JsonPropertyName("permission")]
public Permission Permission { get; set; } = new();
/// <summary>
/// 内容
/// </summary>
[JsonPropertyName("data")]
public string Data { get; set; } = "";
/// <summary>
/// 回调数据,机器人后台会收到 INTERACTION_CREATE 事件data 会回传
/// </summary>
[JsonPropertyName("enter")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Enter { get; set; }
/// <summary>
/// 当 Type = 2 时,是否引用当前消息
/// </summary>
[JsonPropertyName("reply")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Reply { get; set; }
/// <summary>
/// 打开图片选取器,与 Enter 互斥
/// </summary>
[JsonPropertyName("anchor")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? Anchor { get; set; }
}
public class Permission
{
[JsonPropertyName("type")]
public int Type { get; set; }
}
}

View File

@ -57,7 +57,7 @@ namespace Oshima.FunGame.WebAPI
GroupOpenId = "1",
AuthorOpenId = "1",
OpenId = "1",
Detail = order,
Detail = order + (args.Length > 0 ? (" " + string.Join(" ", args).Trim()) : ""),
Id = "1",
Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
};

View File

@ -1,4 +1,5 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
@ -26,6 +27,16 @@ namespace Oshima.FunGame.WebAPI.Services
await SendMessageAsync($"/v2/groups/{groupOpenid}/messages", content, msgType, media, msgId, msgSeq);
}
public async Task SendC2CMarkdownAsync(string openid, MarkdownMessage markdown, KeyboardMessage? keyboard = null, string? msgId = null, int? msgSeq = null)
{
await SendMarkdownMessageAsync($"/v2/users/{openid}/messages", markdown, keyboard, msgId, msgSeq);
}
public async Task SendGroupMarkdownAsync(string groupOpenid, MarkdownMessage markdown, KeyboardMessage? keyboard = null, string? msgId = null, int? msgSeq = null)
{
await SendMarkdownMessageAsync($"/v2/groups/{groupOpenid}/messages", markdown, keyboard, 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();
@ -59,44 +70,78 @@ namespace Oshima.FunGame.WebAPI.Services
}
}
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)
private async Task SendMarkdownMessageAsync(string url, MarkdownMessage markdown, KeyboardMessage? keyboard = 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);
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug("使用的 Access Token{accessToken}", accessToken);
Dictionary<string, object> body = new()
{
{ "msg_type", 2 },
{ "markdown", markdown }
};
if (keyboard != null) body.Add("keyboard", keyboard);
if (!string.IsNullOrEmpty(msgId)) body.Add("msg_id", msgId);
if (msgSeq.HasValue) body.Add("msg_seq", msgSeq.Value);
request.Content = new StringContent(JsonSerializer.Serialize(body), Encoding.UTF8, "application/json");
HttpResponseMessage response = await HttpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
string errorBody = await response.Content.ReadAsStringAsync();
if (Logger.IsEnabled(LogLevel.Error)) Logger.LogError("状态码:{response.StatusCode},错误信息:{errorBody}", response.StatusCode, errorBody);
}
}
public async Task<UploadMediaResult> UploadC2CMediaAsync(string openid, int fileType, string url)
{
return await UploadMediaAsync($"/v2/users/{openid}/files", fileType, url);
}
public async Task<UploadMediaResult> UploadGroupMediaAsync(string groupOpenid, int fileType, string url)
{
return await UploadMediaAsync($"/v2/groups/{groupOpenid}/files", fileType, url);
}
private async Task<UploadMediaResult> 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);
Dictionary<string, object> requestBody = new()
{
{ "file_type", fileType },
{ "url", fileUrl },
{ "srv_send_msg", false }
};
request.Content = new StringContent(JsonSerializer.Serialize(requestBody), System.Text.Encoding.UTF8, "application/json");
request.Content = new StringContent(JsonSerializer.Serialize(requestBody), 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}多媒体URL地址{fileUrl}");
string error = $"状态码:{response.StatusCode},错误信息:{errorBody}多媒体URL地址{fileUrl}";
if (Logger.IsEnabled(LogLevel.Error)) Logger.LogError("{Error}", error);
return new UploadMediaResult { Error = error };
}
string responseBody = await response.Content.ReadAsStringAsync();
MediaResponse? mediaResponse = JsonSerializer.Deserialize<MediaResponse>(responseBody);
if (mediaResponse == null)
{
return (null, null, 0, "反序列化富媒体消息失败。");
return new UploadMediaResult { Error = "反序列化富媒体消息失败。" };
}
if (Logger.IsEnabled(LogLevel.Debug)) Logger.LogDebug("接收到的富媒体消息:{mediaResponse.FileInfo},有效时间:{mediaResponse.Ttl}", mediaResponse.FileInfo, mediaResponse.Ttl);
return (mediaResponse.FileUuid, mediaResponse.FileInfo, mediaResponse.Ttl, null);
if (Logger.IsEnabled(LogLevel.Debug))
Logger.LogDebug("接收到的富媒体消息:{FileInfo},有效时间:{Ttl}", mediaResponse.FileInfo, mediaResponse.Ttl);
return new UploadMediaResult
{
FileUuid = mediaResponse.FileUuid,
FileInfo = mediaResponse.FileInfo,
Ttl = mediaResponse.Ttl
};
}
public async Task<string> GetAccessTokenAsync()

View File

@ -45,10 +45,34 @@ namespace Oshima.FunGame.WebAPI.Services
content = content.Trim();
await Service.SendC2CMessageAsync(msg.OpenId, content, msgType, media, msg.Id, msgSeq);
}
await CheckOfflineNotice(msg);
}
private async Task SendMarkdownAsync(IBotMessage msg, string title, MarkdownMessage mdMsg, KeyboardMessage? kbMsg = null, int? msgSeq = null)
{
Statics.RunningPlugin?.Controller.WriteLine(title, Milimoe.FunGame.Core.Library.Constant.LogLevel.Debug);
if (msg is ThirdPartyMessage third)
{
third.Result += "\r\n" + mdMsg.Content?.Trim();
third.IsCompleted = true;
}
else if (msg.IsGroup)
{
await Service.SendGroupMarkdownAsync(msg.OpenId, mdMsg, kbMsg, msg.Id, msgSeq);
}
else
{
await Service.SendC2CMarkdownAsync(msg.OpenId, mdMsg, kbMsg, msg.Id, msgSeq);
}
await CheckOfflineNotice(msg);
}
private async Task CheckOfflineNotice(IBotMessage msg)
{
if (msg.UseNotice && msg.FunGameUID > 0 && FunGameService.UserNotice.TryGetValue(msg.FunGameUID, out HashSet<string>? msgs) && msgs != null)
{
FunGameService.UserNotice.Remove(msg.FunGameUID, out _);
await SendAsync(msg, "离线未读信箱", $"☆--- 离线未读信箱 ---☆\r\n{string.Join("\r\n", msgs)}", msgType, null, 5);
await SendAsync(msg, "离线未读信箱", $"☆--- 离线未读信箱 ---☆\r\n{string.Join("\r\n", msgs)}", 0, null, 5);
}
}
@ -186,9 +210,9 @@ namespace Oshima.FunGame.WebAPI.Services
string? err = "";
try
{
var (fileUuid, fileInfo, ttl, error) = e.IsGroup ? await Service.UploadGroupMediaAsync(e.OpenId, 1, img) : await Service.UploadC2CMediaAsync(e.OpenId, 1, img);
fi = fileInfo;
err = error;
UploadMediaResult uploadMediaResult = e.IsGroup ? await Service.UploadGroupMediaAsync(e.OpenId, 1, img) : await Service.UploadC2CMediaAsync(e.OpenId, 1, img);
fi = uploadMediaResult.FileInfo;
err = uploadMediaResult.Error;
}
catch (Exception ex)
{