import axios from "axios"; import _ from "lodash"; import fetch from "node-fetch"; // 常量 import { CAT_LIMIT, COMMON_USER_AGENT } from "../constants/constant.js"; import { BING_SEARCH, LINUX_AI_PROMPT, LINUX_QUERY, RDOC_AI_PROMPT, RDOC_LINK, REDIS_YUNZAI_LINUX, REDIS_YUNZAI_RDOC } from "../constants/query.js"; // 配置文件 import config from "../model/config.js"; import { deepSeekChat, llmRead } from "../utils/llm-util.js"; import { OpenaiBuilder } from "../utils/openai-builder.js"; import { redisExistAndGetKey, redisExistAndInsertObject, redisExistAndUpdateObject, redisSetKey } from "../utils/redis-util.js"; import { textArrayToMakeForward } from "../utils/yunzai-util.js"; export class query extends plugin { constructor() { super({ name: "R插件查询类", dsc: "R插件查询相关指令", event: "message.group", priority: 500, rule: [ { reg: "^#医药查询(.*)$", fnc: "doctor", }, { reg: "^#cat$", fnc: "cat", }, { reg: "^#推荐软件$", fnc: "softwareRecommended", }, { reg: "^#买家秀$", fnc: "buyerShow", }, { reg: "^#累了$", fnc: "cospro", }, { reg: "^#竹白(.*)", fnc: "zhubaiSearch", }, { reg: "^#(linux|Linux)(.*)", fnc: "linuxQuery" }, { reg: "^#R文档(.*)", fnc: "intelligentDoc", }, { ret: "^#(R|r)so(.*)", fnc: "rso", } ], }); // 配置文件 this.toolsConfig = config.getConfig("tools"); // 视频保存路径 this.defaultPath = this.toolsConfig.defaultPath; // ai接口 this.aiBaseURL = this.toolsConfig.aiBaseURL; // ai api key this.aiApiKey = this.toolsConfig.aiApiKey; // ai模型 this.aiModel = this.toolsConfig.aiModel; } async doctor(e) { const keyword = e.msg.replace("#医药查询", "").trim(); const url = `https://server.dayi.org.cn/api/search2?keyword=${ keyword }&pageNo=1&pageSize=10`; try { const res = await fetch(url) .then(resp => resp.json()) .then(resp => resp.list); let msg = []; for (let element of res) { const title = this.removeTag(element.title); const thumbnail = element?.thumbnail || element?.auditDoctor?.thumbnail; const doctor = `\n\n👨‍⚕️ 医生信息:${ element?.auditDoctor?.name } - ${ element?.auditDoctor?.clinicProfessional } - ${ element?.auditDoctor?.eduProfessional } - ${ element?.auditDoctor?.institutionName } - ${ element?.auditDoctor?.institutionLevel } - ${ element?.auditDoctor?.departmentName }` const template = `📌 ${ title } - ${ element.secondTitle }${ element?.auditDoctor ? doctor : '' }\n\n📝 简介:${ element.introduction }`; if (thumbnail) { msg.push({ message: [segment.image(thumbnail), { type: "text", text: template, }], nickname: e.sender.card || e.user_id, user_id: e.user_id, }); } else { msg.push({ message: { type: "text", text: template, }, nickname: e.sender.card || e.user_id, user_id: e.user_id, }) } } e.reply(await Bot.makeForwardMsg(msg)); } catch (err) { logger.error(err); } return true; } async cat(e) { const [shibes, cats] = await Promise.allSettled([ fetch(`https://shibe.online/api/cats?count=${ CAT_LIMIT }`).then(data => data.json()), fetch(`https://api.thecatapi.com/v1/images/search?limit=${ CAT_LIMIT }`).then(data => data.json(), ), ]); const shibeUrls = shibes.status === "fulfilled" ? shibes.value : []; const catUrls = cats.status === "fulfilled" ? cats.value.map(item => item.url) : []; const reqRes = [...shibeUrls, ...catUrls]; e.reply("涩图也不看了,就看猫是吧"); const images = reqRes.map(item => ({ message: segment.image(item), nickname: this.e.sender.card || this.e.user_id, user_id: this.e.user_id, })); e.reply(await Bot.makeForwardMsg(images)); return true; } async softwareRecommended(e) { // 接口 const urls = [ "https://www.ghxi.com/ghapi?type=query&n=pc", "https://www.ghxi.com/ghapi?type=query&n=and", ]; // 一起请求 const promises = urls.map(url => fetch(url) .then(resp => resp.json()) .catch(err => logger.error(err)), ); const results = await Promise.allSettled(promises); const msg = results .filter(result => result.status === "fulfilled") // 只保留已解决的 Promise .flatMap(result => result.value.data.list.map(element => { const template = `推荐软件:${ element.title }\n地址:${ element.url }\n`; return { message: { type: "text", text: template }, nickname: e.sender.card || e.user_id, user_id: e.user_id, }; }), ); // 异步操作 e.reply(await Bot.makeForwardMsg(msg)); return true; } async buyerShow(e) { const p1 = fetch("https://api.vvhan.com/api/tao").then(resp => resp.url); const p2 = fetch("https://api.uomg.com/api/rand.img3?format=json") .then(resp => resp.json()) .then(resp => resp.imgurl); const results = await Promise.allSettled([p1, p2]); const images = results .filter(result => result.status === "fulfilled") .map(result => result.value); for (const img of images) { e.reply(segment.image(img)); } return true; } async cospro(e) { let [res1, res2] = ( await Promise.allSettled([ fetch("https://imgapi.cn/cos2.php?return=jsonpro").then(resp => resp.json()), fetch("https://imgapi.cn/cos.php?return=jsonpro").then(resp => resp.json()), ]) ) .filter(result => result.status === "fulfilled") .map(result => result.value); let req = [...res1.imgurls, ...res2.imgurls]; e.reply("哪天克火掉一定是在这个群里面..."); let images = req.map(item => ({ message: segment.image(encodeURI(item)), nickname: this.e.sender.card || this.e.user_id, user_id: this.e.user_id, })); e.reply(await Bot.makeForwardMsg(images)); return true; } // 竹白百科 async zhubaiSearch(e) { const keyword = e.msg.replace("#竹白", "").trim(); if (keyword === "") { e.reply("请输入想了解的内容,例如:#竹白 javascript"); return true; } await axios .post( "https://open.zhubai.wiki/a/zb/s/ep/", { content: 1, keyword: keyword, }, { headers: { "User-Agent": COMMON_USER_AGENT, }, }, ) .then(async resp => { const res = resp.data.data; const content = res .sort((a, b) => b.luSort - a.luSort) .map(item => { const { pn, pa, zn, lu, pu, pq, aa, hl } = item; const template = `标题:${ pn }\n${ pa }\n期刊:${ zn }\n发布日期距今:${ lu }\n链接1:${ pu }\n链接2:${ pq }\n\n 大致描述:${ hl .join("\n") .replace(/<\/?font[^>]*>/g, "") }`; return { message: [segment.image(aa), template], nickname: this.e.sender.card || this.e.user_id, user_id: this.e.user_id, }; }); e.reply(await Bot.makeForwardMsg(content)); }); return true; } async linuxQuery(e) { const order = e.msg.replace(/^#([lL])inux/, "").trim(); // 查询 Redis 中是否存在这个命令如果存在直接返回没有的话就发起网络请求 const linuxInRedis = await redisExistAndGetKey(REDIS_YUNZAI_LINUX) let linuxOrderData; // 判断这个命令是否在缓存里 const isOrderInRedis = linuxInRedis && Object.keys(linuxInRedis).includes(order); if (!isOrderInRedis) { // 没有在缓存里,直接发起网络请求 const resp = await fetch(LINUX_QUERY.replace("{}", order), { headers: { "User-Agent": COMMON_USER_AGENT } }); linuxOrderData = (await resp.json()).data; // 如果缓存里没有就保存一份到缓存里 linuxOrderData && await redisExistAndInsertObject(REDIS_YUNZAI_LINUX, { [order]: linuxOrderData }); } else { // 在缓存里就取出 linuxOrderData = linuxInRedis[order]; } try { const builder = await new OpenaiBuilder() .setBaseURL(this.aiBaseURL) .setApiKey(this.aiApiKey) .setModel(this.aiModel) .setPrompt(LINUX_AI_PROMPT) .build(); let aiBuilder; if (linuxOrderData) { const { linux, content, link } = linuxOrderData; // 发送消息 e.reply(`识别:Linux命令 <${ linux }>\n\n功能:${ content }`); aiBuilder = await builder.kimi(`能否帮助根据${ link }网站的Linux命令内容返回一些常见的用法,内容简洁明了即可`) } else { aiBuilder = await builder.kimi(`我现在需要一个Linux命令去完成:“${ order }”,你能否帮助我查询到相关的一些命令用法和示例,内容简洁明了即可`); } // 如果填了写 AI 才总结 if (this.aiApiKey && this.aiBaseURL) { const { ans: kimiAns, model } = aiBuilder; const Msg = await Bot.makeForwardMsg(textArrayToMakeForward(e, [`「R插件 x ${ model }」联合为您总结内容:`, kimiAns])); await e.reply(Msg); // 提取AI返回的内容并进行解析 if (_.isEmpty(linuxInRedis[order]?.content.trim())) { const parsedData = this.parseAiResponse(order, kimiAns); await redisExistAndUpdateObject(REDIS_YUNZAI_LINUX, order, parsedData); e.reply(`已重新学习命令 ${ order } 的用法,当前已经更新功能为:${ parsedData.content }`); } } } catch (err) { e.reply(`暂时无法查询到当前命令!`); logger.error(logger.red(`[R插件][linux]: ${ err }`)); } return true; } /** * AI响应解析函数 * @param order * @param aiResponse * @returns {{linux, link: string, content: (*|string)}} */ parseAiResponse(order, aiResponse) { // 检查 aiResponse 是否为有效字符串 if (!aiResponse || typeof aiResponse !== 'string') { return { linux: order, content: '', link: `https://www.linuxcool.com/${ order }` // 默认参考链接 }; } // 初始化保存数据的对象 let parsedData = { linux: order, content: '', link: `https://www.linuxcool.com/${ order }` // 默认参考链接 }; // 清理多余的换行符,避免意外的分隔问题 const lines = aiResponse.split('\n').map(line => line.trim()).filter(line => line); // 遍历每一行查找命令相关的描述 lines.forEach(line => { // 允许命令带有可选的路径,修改正则表达式以适应路径变化 const match = line.match(/[`'“](.+?)[`'”]\s*[::—-]?\s*(.*)/); if (match) { logger.info(match) const command = match[1].trim(); // 提取命令部分 const description = match[2].trim(); // 提取描述部分// 同样忽略路径 // 如果命令和参数部分匹配,保存描述 if (command.includes(order)) { parsedData.content = description; } } }); // 如果没有找到具体的描述内容,则给出默认提示 if (!parsedData.content) { parsedData.content = ''; } return parsedData; } async intelligentDoc(e) { const question = e.msg.replace("#R文档", "").trim(); const rPluginDocument = await redisExistAndGetKey(REDIS_YUNZAI_RDOC); if (question === "") { e.reply("请输入要查询的文档内容!\n例如:#R文档 如何玩转BBDown"); return; } else if (question === "更新" || rPluginDocument?.content === undefined) { // 权限判定 if (!e.isMaster) { e.reply("请让管理员发送以进行初始化,或者让管理员进行更新!"); return; } e.reply("更新文档中..."); const content = await llmRead(RDOC_LINK); await redisSetKey(REDIS_YUNZAI_RDOC, { content }) e.reply("文档更新完成!"); } let kimiAns, model = "DeepSeek"; if (this.aiBaseURL && this.aiApiKey) { const builder = await new OpenaiBuilder() .setBaseURL(this.aiBaseURL) .setApiKey(this.aiApiKey) .setModel(this.aiModel) .setPrompt(rPluginDocument) .build(); const kimiResp = await builder.kimi(RDOC_AI_PROMPT.replace("{}", question)); kimiAns = kimiResp.ans; model = kimiResp.model; } else { logger.info(RDOC_AI_PROMPT.replace("{}", question)); kimiAns = await deepSeekChat(RDOC_AI_PROMPT.replace("{}", question), rPluginDocument.content); } const Msg = await Bot.makeForwardMsg(textArrayToMakeForward(e, [`「R插件 x ${ model }」联合为您总结内容:`, kimiAns])); await e.reply(Msg); return; } async rso(e) { const question = e.msg.replace("#Rso", "").replace("#rso", "").trim(); const resp = await fetch(BING_SEARCH.replace("{}", question), { headers: { "User-Agent": COMMON_USER_AGENT, } }); const respJson = (await resp.json()).data; const constructSearchRes = textArrayToMakeForward(e, respJson.map(item => `标题:《${ item.title }》\n📝 简要:${ item.abstract }\n➡️链接:${ item.href }` )); await e.reply(Bot.makeForwardMsg(constructSearchRes)); return; } // 删除标签 removeTag(title) { const titleRex = /<[^>]+>/g; return title.replace(titleRex, ""); } }