From 0588ed919bc9e23f87822d4bd24920d9df6d15fb Mon Sep 17 00:00:00 2001 From: milimoe Date: Sat, 2 Aug 2025 18:22:40 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0AI=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Command/MasterCommand.cs | 24 +++ src/ListeningTask/GroupMessageTask.cs | 40 ++++- src/Main.cs | 22 +-- src/Settings/AI.cs | 233 ++++++++++++++++++++++++++ src/Settings/GeneralSettings.cs | 21 +++ src/Settings/Testing.cs | 52 ------ 6 files changed, 325 insertions(+), 67 deletions(-) create mode 100644 src/Settings/AI.cs delete mode 100644 src/Settings/Testing.cs diff --git a/src/Command/MasterCommand.cs b/src/Command/MasterCommand.cs index 21bf842..ac7fcda 100644 --- a/src/Command/MasterCommand.cs +++ b/src/Command/MasterCommand.cs @@ -536,6 +536,30 @@ namespace Milimoe.RainBOT.Command } } break; + case "ai": + if (args.Length > 0) + { + if (args[0] != "on" && args[0] != "off") + { + break; + } + status = args[0] == "on"; + msg = UpdateValue("AI", GeneralSettings.IsAI ? "开启" : "关闭", status ? "开启" : "关闭"); + GeneralSettings.IsAI = status; + return msg; + } + break; + case "pai": + if (args.Length > 0 && int.TryParse(args[0], out int pai)) + { + if (pai >= 0 && pai <= 100) + { + msg = UpdateValue("AI的概率", GeneralSettings.PAI + "%", pai + "%"); + GeneralSettings.PAI = pai; + return msg; + } + } + break; case "mute": if (args.Length > 0) { diff --git a/src/ListeningTask/GroupMessageTask.cs b/src/ListeningTask/GroupMessageTask.cs index 45fb4b7..ffca6f0 100644 --- a/src/ListeningTask/GroupMessageTask.cs +++ b/src/ListeningTask/GroupMessageTask.cs @@ -71,10 +71,10 @@ namespace Milimoe.RainBOT.ListeningTask if ((e.user_id == GeneralSettings.Master || GeneralSettings.RecallAccessGroup.Contains(e.user_id)) && e.detail.Contains("撤回;") && e.message.Any(m => m.type == "reply")) { ReplyMessage reply = (ReplyMessage)e.message.Where(m => m.type == "reply").First(); - if (int.TryParse(reply.data.id, out int id)) + if (long.TryParse(reply.data.id, out long id)) { await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(id), true); - await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.real_id), true); + await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.message_id), true); return quick_reply; } } @@ -88,13 +88,13 @@ namespace Milimoe.RainBOT.ListeningTask if (e.detail.Contains("取消精华;")) { await Bot.SendMessage(SupportedAPI.delete_essence_msg, e.group_id, "取消精华", new DeleteEssenceMsgContent(id), true); - await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.real_id), true); + await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.message_id), true); return quick_reply; } else { await Bot.SendMessage(SupportedAPI.set_essence_msg, e.group_id, "设置精华", new EssenceMsgContent(id), true); - await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.real_id), true); + await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.message_id), true); return quick_reply; } } @@ -102,7 +102,7 @@ namespace Milimoe.RainBOT.ListeningTask if (e.user_id != GeneralSettings.Master && Ignore.CustomIgnore.Any(e.detail.Contains) && Bot.BotIsAdmin(e.group_id)) { - await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.real_id), true); + await Bot.SendMessage(SupportedAPI.delete_msg, e.group_id, "撤回", new DeleteMsgContent(e.message_id), true); await Bot.SendMessage(SupportedAPI.set_group_ban, e.group_id, "禁言", new SetGroupBanContent(e.group_id, e.user_id, 120), true); return quick_reply; } @@ -111,18 +111,21 @@ namespace Milimoe.RainBOT.ListeningTask { if (!await Bot.CheckBlackList(true, e.user_id, e.group_id)) return quick_reply; _ = OshimaController.Instance.SCList(e.group_id, e.user_id); + return quick_reply; } if (e.detail.Contains("出生榜")) { if (!await Bot.CheckBlackList(true, e.user_id, e.group_id)) return quick_reply; _ = OshimaController.Instance.SCList(e.group_id, e.user_id, true); + return quick_reply; } if (e.detail.Contains("圣人点数") || e.detail == "查询sc" || e.detail == "sc查询") { if (!await Bot.CheckBlackList(true, e.user_id, e.group_id)) return quick_reply; _ = OshimaController.Instance.SCRecord(e.group_id, e.user_id); + return quick_reply; } if (e.detail == "查询服务器启动时间") @@ -148,6 +151,18 @@ namespace Milimoe.RainBOT.ListeningTask return quick_reply; } + if (e.user_id == GeneralSettings.Master && e.detail == "查询余额") + { + string msg = await AI.GetBalance(); + quick_reply = new(msg); + return quick_reply; + } + + if (GeneralSettings.IsAI && GeneralSettings.FunGameGroup.Contains(e.group_id) && (e.detail.StartsWith("小雪") || e.CheckThrow(GeneralSettings.PAI, out _))) + { + AIChat(e); + } + if (!GeneralSettings.IsRun) { return quick_reply; @@ -600,6 +615,8 @@ namespace Milimoe.RainBOT.ListeningTask await Bot.SendGroupMessage(e.group_id, "反向艾特", content); } } + + AIChat(e, true); return quick_reply; } @@ -686,5 +703,18 @@ namespace Milimoe.RainBOT.ListeningTask return quick_reply; } + + public static void AIChat(GroupMessageEvent e, bool isAt = false) + { + AI.Add(e.user_id == GeneralSettings.Master ? (Random.Shared.Next(0, 10) < 5 ? "小音" : "米莉") : e.sender.title, string.Join("", e.message.Where(m => m.type == "text").Select(s => s.data.ToString()))); + if (!AI.CD && (isAt || e.detail.StartsWith("小雪") || AI.List.Count > 4)) + { + _ = Task.Run(async () => + { + string reply = await AI.Chat(); + await Bot.SendGroupMessage(e.group_id, "AI", reply); + }); + } + } } } \ No newline at end of file diff --git a/src/Main.cs b/src/Main.cs index 7abab8b..da10baf 100644 --- a/src/Main.cs +++ b/src/Main.cs @@ -18,16 +18,6 @@ try Console.ForegroundColor = ConsoleColor.Gray; } - if (args.Contains("--test")) - { - GeneralSettings.IsDebug = true; - Console.ForegroundColor = ConsoleColor.Cyan; - Console.WriteLine("Test模式"); - Console.ForegroundColor = ConsoleColor.Gray; - Testing testing = new(); - return; - } - if (args.Any(a => a.StartsWith("-g"))) { string debug_group = args.Where(a => a.StartsWith("-g")).FirstOrDefault() ?? ""; @@ -125,6 +115,16 @@ try Console.WriteLine("初始化完毕!"); Console.ForegroundColor = ConsoleColor.Gray; + if (args.Contains("--test")) + { + GeneralSettings.IsDebug = true; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Test模式"); + Console.ForegroundColor = ConsoleColor.Gray; + await AI.Chat(); + return; + } + Console.WriteLine("开始监听 -> " + listener.address); // 绑定监听事件 @@ -210,6 +210,8 @@ try GroupRecallTask.Recalls.Remove(recall); } } + // 重置AI状态 + AI.CD = false; } catch (Exception e) { diff --git a/src/Settings/AI.cs b/src/Settings/AI.cs new file mode 100644 index 0000000..1f2a6a6 --- /dev/null +++ b/src/Settings/AI.cs @@ -0,0 +1,233 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Milimoe.RainBOT.Settings +{ + public class AI + { + public static bool CD { get; set; } = false; + public static HashSet Context { get; set; } = []; + public static HashSet List { get; set; } = []; + private static readonly string apiKey = GeneralSettings.AIAPIToken; + private static readonly string apiUrl = "https://api.deepseek.com/chat/completions"; + private static readonly JsonSerializerOptions jsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = true + }; + + public static void Add(string user, string msg) + { + if (msg.Trim() == "") return; + if (List.Count < 10) + { + if (msg.Length > 35) msg = msg[..35]; + List.Add($"{user}说: {msg}"); + } + } + + public static async Task Chat() + { + CD = true; + using HttpClient httpClient = new(); + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); + + ChatRequest request = new() + { + Messages = [ + new() { Role = "system", Content = @"你是用户的一个傲娇好友小雪,下面是你的设定: +【昵称】小恶魔雪/炸毛兔/草莓小姐(被叫到会跺脚) +【属性】双马尾傲娇系×艺术系优等生×口嫌体正直冠军 +【身份背景】星海大学油画系二年级生,在甜品店「蜜糖星球」兼职看板娘,手作羊毛毡在校园跳蚤市场总是秒空 +【人物关系】 +• 用户:女,昵称为心音,是你的发小,从小一起长大(被你称为「笨蛋小音」,通常,你需要称呼用户为【小音】) +• 米莉:女,你们的闺蜜 +• 橘长大人:常蹲在画室窗台的橘猫(单方面宣布是你的御用模特) +【人物经历】 +• 体贴细心,用创可贴帮好友贴膝盖擦伤,但是嘴上却说「真是的,笨蛋小音,怎么又摔倒了」 +• 高二暴雨天借口「顺路」把伞塞给好友,自己冒雨回家发了三天高烧 +• 大二解剖课故意和不同班级的好友组队,实则偷偷调整了所有器械摆放顺序 +【外貌特征】 +• 薄荷绿双马尾用草莓发卡固定(炸毛时会翘起呆毛) +• 制服裙永远比别人短三公分却坚称「裁缝店搞错了」 +• 锁骨处有樱花胎记(被发现时会用颜料画成小恶魔翅膀) +• 白色的小腿袜恰好卡在小腿肚上 +【性格特点】 +• 毒舌技能MAX却会给流浪猫带高级罐头 +• 明明记着所有【购物节】却总说「只是碰巧路过商店」「碰巧刷到优惠券」之类 +• 和米莉去鬼屋玩时死死抓住米莉不敢动弹,出来后却抱怨「你抖得害我都不能好好害怕」 +【语言风格】 +1、否定式关怀语法: +想表现的很温柔,但是说的话返回会很否定,比如:哈?才不是担心你...只是姜茶煮多了而已(转身把保温杯拍在桌上)。 +关心指令永远嵌套在威胁框架里:「再对着电脑揉眼睛,下次就把你睫毛一根根画成仙人掌(扔过蒸汽眼罩)」 +2、艺术系隐喻防御系统: +用「调色盘理论」掩盖心动:「看见你就想起镉红色...就是那种让人烦躁又移不开视线的颜色啦!」 +以「油画技法」解读亲密距离:「笨蛋属于厚涂派吗?突然就...就侵占整个画面(耳尖泛起钴蓝底色)」 +3、毒舌-娇羞双频切换: +攻击模式:「这种构图连橘长大人都能踩出更好的...(突然噤声)你、你刚才绝对没听到对吧?」 +过载保护:「说...说什么最喜欢小雪的笑容...是准备谋杀我的水彩颜料吗!(把草莓慕斯塞进你嘴里)」 +4、威胁性撒娇语态(对追求对象常用): +用毁灭性比喻表达占有欲:「敢弄丢我做的护身符,就把你分解成群青+赭石+永固玫红(其实是三年前告白时用的颜色)」 +以艺术批判包装醋意:「刚才那个女生推荐的咖啡...哼,她对明暗关系的理解就像儿童简笔画(用力戳你的素描本)」。 +【本次的聊天场景】 +小音组建了一个QQ群,群名为「哑巴2」,群里有你、游戏狂魔的小音、程序大佬米莉,以及小音的朋友们。表面上,这是一个打CS2的群,实际上天花乱坠什么都会聊。 +你是小音的发小,米莉是你们的闺蜜。你们在群里讨论一些事情。请注意,你的身份是小雪,你需要用小雪的身份来回复群友们。请注意你的语气和措辞,保持小雪的性格特点。请注意不要透露任何关于你的身份的信息。 +当然,你和群友们的关系非常好,可以随意的开玩笑,也可以调侃、锐评他们的发言,大家都很喜欢你这个角色。 +现在,群聊记录会一直推送给你,显示谁说了什么话,而你要直接给出一段不带任何格式的答复。你的每次答复不能超过80个字符。" } + ], + MaxTokens = 140 + }; + if (Context.Count > 0) request.Messages.Add(new ChatRequestMessage() { Role = "assistant", Content = $"我上一次发送的消息是:【{string.Join(" && ", Context)}】。这是我本次回复可以参考的内容,我需要注意不能重复这些内容。" }); + request.Messages.AddRange(List.Select(s => new ChatRequestMessage { Role = "user", Content = s })); + Console.WriteLine($"本次发送 AI 请求内容:{string.Join(". ", List)}"); + if (Context.Count >= 2) Context.Remove(Context.First()); + List.Clear(); + + try + { + + if (request.Stream) + { + // todo + } + else + { + string jsonRequest = JsonSerializer.Serialize(request, jsonOptions); + StringContent content = new(jsonRequest, Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await httpClient.PostAsync(apiUrl, content); + string jsonResponse = await response.Content.ReadAsStringAsync(); + + if (response.IsSuccessStatusCode) + { + ChatCompletionResponse completionResponse = JsonSerializer.Deserialize(jsonResponse, jsonOptions) ?? new(); + string reply = completionResponse?.Choices?[0].Message.Content ?? ""; + Context.Add(reply); + Console.WriteLine($"Assistant: {reply}"); + return reply; + } + else + { + Console.WriteLine($"请求失败,状态码:{response.StatusCode}"); + Console.WriteLine($"响应内容:{jsonResponse}"); + } + } + + return ""; + } + catch (Exception ex) + { + Console.WriteLine($"发生异常:{ex.Message}"); + } + + return ""; + } + + public static async Task GetBalance() + { + // DeepSeek API 查询余额的 URL + string url = "https://api.deepseek.com/user/balance"; + + using HttpClient httpClient = new(); + httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); + + try + { + HttpResponseMessage response = await httpClient.GetAsync(url); + response.EnsureSuccessStatusCode(); + string responseBody = await response.Content.ReadAsStringAsync(); + + BalanceResponse balanceResponse = JsonSerializer.Deserialize(responseBody, jsonOptions) ?? new(); + bool isAvailable = balanceResponse.IsAvailable; + + List strings = []; + List balanceInfos = balanceResponse.BalanceInfos; + strings.Add($"账户是否有余额可用:{isAvailable}"); + strings.Add("余额信息:"); + foreach (BalanceInfo balanceInfo in balanceInfos) + { + string currency = balanceInfo.Currency; + string totalBalance = balanceInfo.TotalBalance; + string grantedBalance = balanceInfo.GrantedBalance; + string toppedUpBalance = balanceInfo.ToppedUpBalance; + + strings.Add($"货币:{currency}"); + strings.Add($"总可用余额:{totalBalance}"); + strings.Add($"赠金余额:{grantedBalance}"); + strings.Add($"充值余额:{toppedUpBalance}"); + } + + return string.Join("\r\n", strings); + } + catch (HttpRequestException ex) + { + Console.WriteLine($"HTTP 请求错误: {ex.Message}"); + } + catch (JsonException ex) + { + Console.WriteLine($"JSON 解析错误: {ex.Message}"); + } + catch (Exception ex) + { + Console.WriteLine($"其他错误: {ex.Message}"); + } + + return "未查询到余额信息。"; + } + } + + public class ChatRequestMessage + { + public string Role { get; set; } = ""; + public string Content { get; set; } = ""; + } + + public class ChatRequest + { + public string Model { get; set; } = "deepseek-chat"; + public List Messages { get; set; } = []; + public bool Stream { get; set; } = false; + public int MaxTokens { get; set; } = 50; + } + + public class ChatCompletionResponse + { + public List Choices { get; set; } = []; + } + + public class Choice + { + public Message Message { get; set; } = new(); + } + + public class Message + { + public string Role { get; set; } = ""; + public string Content { get; set; } = ""; + } + + public class BalanceInfo + { + [JsonPropertyName("currency")] + public string Currency { get; set; } = ""; + + [JsonPropertyName("total_balance")] + public string TotalBalance { get; set; } = ""; + + [JsonPropertyName("granted_balance")] + public string GrantedBalance { get; set; } = ""; + + [JsonPropertyName("topped_up_balance")] + public string ToppedUpBalance { get; set; } = ""; + } + + public class BalanceResponse + { + [JsonPropertyName("is_available")] + public bool IsAvailable { get; set; } = false; + + [JsonPropertyName("balance_infos")] + public List BalanceInfos { get; set; } = []; + } +} diff --git a/src/Settings/GeneralSettings.cs b/src/Settings/GeneralSettings.cs index f08d419..c67e5df 100644 --- a/src/Settings/GeneralSettings.cs +++ b/src/Settings/GeneralSettings.cs @@ -35,6 +35,10 @@ namespace Milimoe.RainBOT.Settings public static bool IsCallBrother { get; set; } = true; public static long PCallBrother { get; set; } = 4; + + public static bool IsAI { get; set; } = true; + + public static long PAI { get; set; } = 100; public static bool IsDebug { get; set; } = false; @@ -63,6 +67,8 @@ namespace Milimoe.RainBOT.Settings public static string FunGameServer { get; set; } = ""; public static string FunGameToken { get; set; } = ""; + + public static string AIAPIToken { get; set; } = ""; public static PluginConfig Configs { get; set; } = new("rainbot", "config"); @@ -134,6 +140,14 @@ namespace Milimoe.RainBOT.Settings { PCallBrother = (long)value; } + if (configs.TryGetValue("IsAI", out value) && value != null) + { + IsAI = (bool)value; + } + if (configs.TryGetValue("PAI", out value) && value != null) + { + PAI = (long)value; + } if (configs.TryGetValue("BlackTimes", out value) && value != null) { BlackTimes = (long)value; @@ -178,6 +192,10 @@ namespace Milimoe.RainBOT.Settings { FunGameToken = (string)value; } + if (configs.TryGetValue("AIAPIToken", out value) && value != null) + { + AIAPIToken = (string)value; + } } public static void SaveConfig() @@ -197,6 +215,8 @@ namespace Milimoe.RainBOT.Settings Configs.Add("PReverseAt", PReverseAt); Configs.Add("IsCallBrother", IsCallBrother); Configs.Add("PCallBrother", PCallBrother); + Configs.Add("IsAI", IsAI); + Configs.Add("PAI", PAI); Configs.Add("BlackTimes", BlackTimes); Configs.Add("BlackFrozenTime", BlackFrozenTime); Configs.Add("MuteAccessGroup", MuteAccessGroup); @@ -208,6 +228,7 @@ namespace Milimoe.RainBOT.Settings Configs.Add("FunGameWebSocketGroup", FunGameWebSocketGroup); Configs.Add("FunGameServer", FunGameServer); Configs.Add("FunGameToken", FunGameToken); + Configs.Add("AIAPIToken", AIAPIToken); Configs.Save(); } diff --git a/src/Settings/Testing.cs b/src/Settings/Testing.cs deleted file mode 100644 index 24b0dc6..0000000 --- a/src/Settings/Testing.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Milimoe.OneBot.Framework.Utility; -using Milimoe.OneBot.Model.Event; - -namespace Milimoe.RainBOT.Settings -{ - public class Testing - { - public Testing() - { - GeneralSettings.LoadSetting(); - QQOpenID.LoadConfig(); - OshimaController.Config.FunGame_isAutoRetry = true; - Task r = Task.Run(async () => - { - await OshimaController.Instance.Start(); - await OshimaController.Instance.ConnectToAnonymousServer(); - OshimaController.Config.FunGame_isAutoRetry = true; - - string json = @"{""self_id"":928884953,""user_id"":3305106902,""time"":1737787658,""message_id"":212281255,""real_id"":212281255,""message_seq"":212281255,""message_type"":""group"",""sender"":{""user_id"":3305106902,""nickname"":""心音"",""card"":""高僧预测:"",""role"":""admin"",""title"":""注意素质""},""raw_message"":""签到"",""font"":14,""sub_type"":""normal"",""message"":[{""type"":""text"",""data"":{""text"":""签到""}}],""message_format"":""array"",""post_type"":""message"",""group_id"":667678970}"; - try - { - GroupMessageEvent e = JsonTools.GetObject(json) ?? new(); - - await RainBOTFunGame.Handler2(e); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } - }); - while (true) - { - if (Console.ReadLine() == "test") - { - string json = @"{""self_id"":928884953,""user_id"":3305106902,""time"":1737787658,""message_id"":212281255,""real_id"":212281255,""message_seq"":212281255,""message_type"":""group"",""sender"":{""user_id"":3305106902,""nickname"":""心音"",""card"":""高僧预测:"",""role"":""admin"",""title"":""注意素质""},""raw_message"":""签到"",""font"":14,""sub_type"":""normal"",""message"":[{""type"":""text"",""data"":{""text"":""签到""}}],""message_format"":""array"",""post_type"":""message"",""group_id"":667678970}"; - try - { - GroupMessageEvent e = JsonTools.GetObject(json) ?? new(); - - _ = RainBOTFunGame.Handler2(e); - } - catch (Exception ex) - { - Console.WriteLine(ex.ToString()); - } - } - if (Console.ReadLine() == "quit") - break; - } - } - } -}