|
| 1 | +// 此版本对所有私聊生效,群聊只有艾特你时才会生效 |
| 2 | +import org.json.JSONArray; |
| 3 | +import org.json.JSONObject; |
| 4 | + |
| 5 | +import java.util.ArrayList; |
| 6 | +import java.util.HashMap; |
| 7 | +import java.util.List; |
| 8 | +import java.util.Map; |
| 9 | + |
| 10 | +import me.hd.wauxv.plugin.api.callback.PluginCallBack; |
| 11 | + |
| 12 | +// 按用户ID存储对话历史 |
| 13 | +Map msgListMap = new HashMap(); |
| 14 | +// DeepSeek API密钥 |
| 15 | +String deepseekApiKey = "sk-你的密钥"; |
| 16 | +// 私聊触发关键词(可留空,空数组时允许所有私聊) |
| 17 | +String[] privateTriggerWords = {}; // 关键词触发,如不填则无限制{"你好","在吗"} |
| 18 | +// **新增:来源ID排除列表(精确匹配,支持私聊/群聊用户ID)** |
| 19 | +String[] excludedIds = {}; // 填写需要排除的用户ID |
| 20 | +// 违禁词列表(不区分大小写,包含即拦截) |
| 21 | +String[] forbiddenWords = {}; // 可自定义违禁词 |
| 22 | + |
| 23 | +final String MODEL_REASONER = "deepseek-reasoner"; // 推理模型 |
| 24 | +final String MODEL_CHAT = "deepseek-chat"; // 聊天模型 |
| 25 | +String currentModel = MODEL_CHAT; // 默认聊天模型 |
| 26 | +// 可配置最大对话记录数 |
| 27 | +int MAX_HISTORY_LENGTH = 100; |
| 28 | + |
| 29 | +// 添加系统角色消息到历史记录(替换旧系统消息) |
| 30 | +void addSystemMsg(String talkerId, String content) { |
| 31 | + List userMsgList = msgListMap.get(talkerId); |
| 32 | + if (userMsgList == null) { |
| 33 | + userMsgList = new ArrayList(); |
| 34 | + msgListMap.put(talkerId, userMsgList); |
| 35 | + } |
| 36 | + |
| 37 | + boolean systemMsgFound = false; |
| 38 | + for (int i = 0; i < userMsgList.size(); i++) { |
| 39 | + Map msg = userMsgList.get(i); |
| 40 | + if ("system".equals(msg.get("role"))) { |
| 41 | + // 替换已有的系统消息 |
| 42 | + msg.put("content", content); |
| 43 | + log("更新了角色设定" + talkerId); |
| 44 | + systemMsgFound = true; |
| 45 | + break; |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | + if (!systemMsgFound) { |
| 50 | + // 不存在系统消息,添加新的 |
| 51 | + Map systemMsg = new HashMap(); |
| 52 | + systemMsg.put("role", "system"); |
| 53 | + systemMsg.put("content", content); |
| 54 | + userMsgList.add(systemMsg); |
| 55 | + log("初始化角色设定: " + talkerId); |
| 56 | + } |
| 57 | +} |
| 58 | + |
| 59 | +void checkMsgListLimit(String talkerId) { |
| 60 | + List userMsgList = msgListMap.get(talkerId); |
| 61 | + if (userMsgList == null) return; |
| 62 | + |
| 63 | + if (userMsgList.size() > MAX_HISTORY_LENGTH) { |
| 64 | + // 移除最早的消息(保留系统消息) |
| 65 | + userMsgList.remove(1); |
| 66 | + } |
| 67 | +} |
| 68 | + |
| 69 | +// 添加用户消息到历史记录 |
| 70 | +void addUserMsg(String talkerId, String content) { |
| 71 | + List userMsgList = msgListMap.get(talkerId); |
| 72 | + if (userMsgList == null) return; |
| 73 | + |
| 74 | + Map msgMap = new HashMap(); |
| 75 | + msgMap.put("role", "user"); |
| 76 | + msgMap.put("content", content); |
| 77 | + userMsgList.add(msgMap); |
| 78 | + |
| 79 | + checkMsgListLimit(talkerId); |
| 80 | +} |
| 81 | + |
| 82 | +// 添加助手消息 |
| 83 | +void addAssistantMsg(String talkerId, String content) { |
| 84 | + List userMsgList = msgListMap.get(talkerId); |
| 85 | + if (userMsgList == null) return; |
| 86 | + |
| 87 | + Map msgMap = new HashMap(); |
| 88 | + msgMap.put("role", "assistant"); |
| 89 | + msgMap.put("content", content); |
| 90 | + userMsgList.add(msgMap); |
| 91 | + |
| 92 | + checkMsgListLimit(talkerId); |
| 93 | +} |
| 94 | + |
| 95 | +// 构建DeepSeek API请求参数 |
| 96 | +Map getDeepSeekParam(String talkerId, String content) { |
| 97 | + List userMsgList = msgListMap.get(talkerId); |
| 98 | + Map paramMap = new HashMap(); |
| 99 | + paramMap.put("model", currentModel); // 指定模型 |
| 100 | + addUserMsg(talkerId, content); // 将用户输入加入历史 |
| 101 | + paramMap.put("messages", userMsgList); // 完整对话历史 |
| 102 | + paramMap.put("temperature", 0.7); // 响应随机性控制 |
| 103 | + paramMap.put("max_tokens", 2000); // 最大返回token数 |
| 104 | + return paramMap; |
| 105 | +} |
| 106 | + |
| 107 | +// 构建API请求头 |
| 108 | +Map getDeepSeekHeader() { |
| 109 | + Map headerMap = new HashMap(); |
| 110 | + headerMap.put("Content-Type", "application/json"); // JSON格式 |
| 111 | + headerMap.put("Authorization", "Bearer " + deepseekApiKey); // API认证 |
| 112 | + return headerMap; |
| 113 | +} |
| 114 | + |
| 115 | +// 发送请求到DeepSeek API并处理响应 |
| 116 | +void sendDeepSeekResp(String talker, String content) { |
| 117 | + // 发起POST请求 |
| 118 | + post("https://api.deepseek.com/v1/chat/completions", // API地址 |
| 119 | + getDeepSeekParam(talker, content), // 请求参数 |
| 120 | + getDeepSeekHeader(), // 请求头 |
| 121 | + new PluginCallBack.HttpCallback() { |
| 122 | + // 成功回调 |
| 123 | + public void onSuccess(int code, String respContent) { |
| 124 | + try { |
| 125 | + JSONObject jsonObj = new JSONObject(respContent); |
| 126 | + JSONArray choices = jsonObj.getJSONArray("choices"); |
| 127 | + if (choices.length() > 0) { |
| 128 | + JSONObject firstChoice = choices.getJSONObject(0); |
| 129 | + JSONObject message = firstChoice.getJSONObject("message"); |
| 130 | + String msgContent = message.getString("content"); |
| 131 | + |
| 132 | + // 将AI回复加入对话历史 |
| 133 | + addAssistantMsg(talker, msgContent); |
| 134 | + |
| 135 | + // 发送回复给用户 |
| 136 | + sendText(talker, msgContent); |
| 137 | + } |
| 138 | + } catch (Exception e) { |
| 139 | + // 错误处理:解析失败 |
| 140 | + sendText(talker, "[DeepSeek] 解析响应失败: " + e.getMessage()); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + // 失败回调 |
| 145 | + public void onError(Exception e) { |
| 146 | + // 错误处理:请求异常 |
| 147 | + sendText(talker, "[DeepSeek] 请求异常: " + e.getMessage()); |
| 148 | + } |
| 149 | + } |
| 150 | + ); |
| 151 | +} |
| 152 | + |
| 153 | +// 清空当前对话 |
| 154 | +void cleanupInactiveSessions(String talkerId) { |
| 155 | + msgListMap.remove(talkerId); |
| 156 | +} |
| 157 | + |
| 158 | +boolean onLongClickSendBtn(String text) { |
| 159 | + String talkerId = getTargetTalker(); |
| 160 | + if (text.equals("切换模型")) { |
| 161 | + currentModel = (currentModel.equals(MODEL_REASONER)) ? MODEL_CHAT : MODEL_REASONER; |
| 162 | + return true; // 消耗事件,确保模型切换生效 |
| 163 | + } |
| 164 | + |
| 165 | + if (text.equals("当前模型")) { |
| 166 | + sendText(talkerId, currentModel); |
| 167 | + return true; |
| 168 | + } |
| 169 | + |
| 170 | + if (text.startsWith("角色设定: ")) { |
| 171 | + addSystemMsg(talkerId, text.replace("角色设定: ", "")); |
| 172 | + return true; |
| 173 | + } |
| 174 | + |
| 175 | + if (text.equals("重置")) { |
| 176 | + cleanupInactiveSessions(talkerId); |
| 177 | + return true; |
| 178 | + } |
| 179 | + |
| 180 | + return false; |
| 181 | +} |
| 182 | + |
| 183 | +// 消息处理主入口 |
| 184 | +void onHandleMsg(Object msgInfoBean) { |
| 185 | + // **新增:来源ID排除判断(优先级最高,所有消息先过滤)** |
| 186 | + String talkerId = msgInfoBean.getTalker(); // 获取消息来源ID(需根据框架实际API调整,此处假设talker为用户ID) |
| 187 | + |
| 188 | + // 未设定角色直接跳过 |
| 189 | + if (msgListMap.get(talkerId) == null) { |
| 190 | + return; |
| 191 | + } |
| 192 | + |
| 193 | + // 忽略自己发送的消息 |
| 194 | + if (msgInfoBean.isSend()) { |
| 195 | + return; |
| 196 | + } |
| 197 | + |
| 198 | + String content = msgInfoBean.getContent(); |
| 199 | + |
| 200 | + // 群聊消息处理 |
| 201 | + if (msgInfoBean.isGroupChat()) { |
| 202 | + if (!msgInfoBean.isAtMe()) { |
| 203 | + return; |
| 204 | + } |
| 205 | + |
| 206 | + String talker = msgInfoBean.getTalker(); // 群聊中talker为群成员ID |
| 207 | + |
| 208 | + // 使用通用正则表达式移除所有 @提及 |
| 209 | + String processedContent = content |
| 210 | + .replaceAll("(^|\\s+)@[^\\s\\u2005]+", "") // 匹配并移除 @+任意非空字符 |
| 211 | + .replaceAll("^\\s+|\\s+$", "") // 去除首尾空格 |
| 212 | + .replaceAll("\\s+", " "); // 合并中间多个空格为一个 |
| 213 | + |
| 214 | + sendDeepSeekResp(talker, processedContent); |
| 215 | + } |
| 216 | + // 私聊消息处理(含违禁词+关键词判断) |
| 217 | + else { |
| 218 | + if (msgInfoBean.isText()) { |
| 219 | + String userContent = content.trim().toLowerCase(); // 转小写统一校验 |
| 220 | + String talker = msgInfoBean.getTalker(); // 私聊中talker为对方用户ID |
| 221 | + |
| 222 | + // 第一步:违禁词判断 |
| 223 | + if (hasForbiddenWord(userContent)) { |
| 224 | + return; |
| 225 | + } |
| 226 | + |
| 227 | + // 第二步:关键词触发逻辑 |
| 228 | + boolean containsTrigger = false; |
| 229 | + if (privateTriggerWords.length == 0) { |
| 230 | + containsTrigger = true; |
| 231 | + } else { |
| 232 | + for (String word : privateTriggerWords) { |
| 233 | + if (userContent.contains(word.toLowerCase())) { |
| 234 | + containsTrigger = true; |
| 235 | + break; |
| 236 | + } |
| 237 | + } |
| 238 | + } |
| 239 | + |
| 240 | + if (!containsTrigger) { |
| 241 | + return; |
| 242 | + } |
| 243 | + |
| 244 | + sendDeepSeekResp(talker, userContent); |
| 245 | + } |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +// **新增:判断消息是否包含违禁词(复用原有逻辑,封装为方法)** |
| 250 | +boolean hasForbiddenWord(String content) { |
| 251 | + for (String word : forbiddenWords) { |
| 252 | + if (content.contains(word)) { |
| 253 | + return true; |
| 254 | + } |
| 255 | + } |
| 256 | + return false; |
| 257 | +} |
0 commit comments