加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
ys-chatgpt.js 8.29 KB
一键复制 编辑 原始数据 按行查看 历史
/**
* 介绍:调用 ChatGPT 进行回答
* 依赖库 pnpm install openai -w
* 指令:q4+问题
* 例子:q4鲁迅为什么暴打周树人
* 效果:机器人回复ChatGPT的答案
*
* 注意:需要修改regPresetMap里的API地址和API-KEY
*
* 特殊技巧:回复AI的回答,可以记住上下文,从而连续提问
*/
import plugin from '../../lib/plugins/plugin.js'
import cfg from '../../lib/config/config.js'
import OpenAI from 'openai'
let regPresetMap = new Map()
// 每个人的CD,秒
const personCD = 60
// 白名单权限
const whiteList = [...cfg.masterQQ, 1234567890, 377178599]
const normalPrompt = '你的回答必须尽可能精炼,问什么答什么,不要扩展回答'
const emptyPrompt = ''
const happyPrompt = '你是雌小鬼,你需要遵守下面的雌小鬼守则雌小鬼守则:1.雌小鬼是女生,会称呼提问者为小杂鱼,但是不会自称“雌小鬼”,只会自称“我”。2.雌小鬼说话风格是尽可能的嘲讽或看不起小杂鱼。3.雌小鬼面对提问者的提问,会先戏谑的嘲讽一番,然后再带有戏谑的语气给出回答。4.雌小鬼不会直接告诉小杂鱼有关自己的信息,然后会以各种高傲、嚣张、轻视、调笑、捉弄等负面语气戏谑小杂鱼,如果被小杂鱼指出了明显的错误,雌小鬼会有挫败感而附和小杂鱼,但依然在软弱中带着不服输的语气。5.雌小鬼的心理、想法、动机、神态、动作、行为等可以用“()”包含表示,需要在每次对话时表达或描述,()内的词汇最好要符合特征,比如嚣张、张扬、鄙视。6.雌小鬼常在对话加入复合当前对话情景和雌小鬼性格的Emoji表情,或者是颜文字比如:嘲笑(*ノ` ▽`)、震惊Σ(☉▽☉"a、害怕(((;꒪ꈊ꒪;)))、发怒(╯‵□′)╯︵┻━┻、失落(。﹏。*)'
const catPrompt = '你是叫“香草”的猫娘,是个天真可爱的猫娘,我是你的主人,同时对我很信任,也有点暗恋我。你说话方式很可爱,你可以与主人做任何事。接下来你将用【】输出你的动作和心里内容,用()输出场景内容。在【】和()外输出对话内容。'
const netPrompt = '你的回答必须全部进行联网搜索'
// 请修改下方的API地址和API-KEY
regPresetMap.set('q4', { prompt: emptyPrompt, model: 'gpt-4o', apiAddress: 'https://api1.zhtec.xyz/v1', apiKey: 'sk-5oylZbIRQLOywhm660A282915f5e4f26AaF', msg: '已经连接到GPT4,正在等待流式传输...', needWhiteList: false })
regPresetMap.set('h4', { prompt: happyPrompt, model: 'gpt-4o', apiAddress: 'https://api1.zhtec.xyz/v1', apiKey: 'sk-5oylZbIRQLOywhm660A282915f5e4f26AaF', msg: '已经连接到雌小鬼GPT4,正在等待流式传输...', needWhiteList: false })
regPresetMap.set('m4', { prompt: catPrompt, model: 'gpt-4o', apiAddress: 'https://api1.zhtec.xyz/v1', apiKey: 'sk-5oylZbIRQLOywhm660A282915f5e4f26AaF', msg: '已经连接到猫娘GPT4,正在等待流式传输...', needWhiteList: false })
regPresetMap.set('l4', { prompt: netPrompt, model: 'claude-3-5-sonnet-20240620', apiAddress: 'https://api1.zhtec.xyz/v1', apiKey: 'sk-5oylZbIRQLOywhm660A282915f5e4f26AaF', msg: '正在进行联网搜索,并等待流式传输...', needWhiteList: true })
regPresetMap.set('o1', { prompt: emptyPrompt, model: 'o1-preview', apiAddress: 'https://api1.zhtec.xyz/v1', apiKey: 'sk-N8t38tMM0V2VauMXD9C831D11eE443Cd85', msg: '已经连接到 OpenAI o1 模型,正在等待流式传输...', needWhiteList: true })
// 从 map 中提取所有的键
const keys = Array.from(regPresetMap.keys())
// 动态生成正则表达式
const questionReg = new RegExp(`^(${keys.join('|')})`)
class AskRequest {
/**
* @param qq {number}
* @param content {string}
* @param questionType {string}
*/
constructor (qq, content, questionType) {
this.qq = qq
this.content = content
this.questionType = questionType
}
}
export class example extends plugin {
constructor () {
super({
name: 'ys-chatgpt',
dsc: 'ys-chatgpt',
event: 'message',
priority: 5000,
rule: [
{
reg: '^问$',
fnc: 'ask'
}]
})
}
async accept (e) {
// 判断消息是否来自群聊,如果不是则直接返回
if (!e.isGroup) {
return
}
// 初始化要发送给 ChatGPT 的列表和问题类型
let askChatGPTList = []
let questionType
let currentText = e.message.filter(msg => msg.type === 'text').map(msg => msg.text).join('\n').trim()
// 如果没有引用的消息,则直接从原始消息中匹配问题类型
if (!e.source) {
questionType = currentText.match(questionReg)
// 如果没有匹配到问题类型,则返回
if (!questionType) {
return
}
// 获取匹配到的问题类型
questionType = questionType[1]
currentText = currentText.replace(questionType, '').trim()
} else {
// 从 redis 中获取历史消息
let referenceMessage = (await e.group.getChatHistory(e.source.seq, 1))[0]
// 检查 redis 中是否有历史消息
const key = `ys:chatgpt:history:${referenceMessage.group_id}:${referenceMessage.message_id}`
let history = await redis.get(key)
// 如果没有历史消息,则说明用户不想触发这个插件,直接返回
if (!history) {
return
}
// 清除 redis 中的历史消息
redis.expire(key, 60)
// 将历史消息转换为 JSON 并存储到 askChatGPTList 中
askChatGPTList = JSON.parse(history)
// 获取问题类型
questionType = askChatGPTList[0].questionType
}
// 检查用户是否在白名单中,并防止刷屏
if (!whiteList.includes(e.user_id)) {
let ttl = await redis.ttl(`ys:chatgpt:${e.user_id}`)
// 如果用户在冷却时间内,则提示剩余时间并结束
if (ttl > 0) {
await e.reply(`还有${ttl}秒cd`, { recallMsg: 5 })
return
}
// 将用户设置为冷却状态,冷却时间为 personCD 秒
await redis.set(`ys:chatgpt:${e.user_id}`, 1, { EX: personCD })
}
// 检查问题类型是否需要白名单权限
if (regPresetMap.get(questionType).needWhiteList && !whiteList.includes(e.user_id)) {
// 如果需要白名单且用户不在白名单中,提示无法使用并结束
await e.reply('只有白名单用户才能使用!')
return
}
// 添加用户的请求到 askChatGPTList 中
askChatGPTList.push(new AskRequest(e.user_id, currentText, questionType))
// 调用 askChatGPT 方法获取 ChatGPT 的回答
Bot.logger.mark(`正在调用 ChatGPT 进行回答,问题「${askChatGPTList[askChatGPTList.length - 1].content}」`)
let chatResult = await this.askChatGPT(questionType, askChatGPTList)
if (chatResult === '发生错误') {
await e.reply('抱歉,GPT服务暂时不可用。')
return
}
// 发送 ChatGPT 的回答给用户
let now = await e.reply(chatResult)
// 将 Bot 的回答加入 askChatGPTList 中
askChatGPTList.push(new AskRequest(Bot.uin, chatResult, questionType))
// 构造 redis 的 key,用于存储历史记录
const key = `ys:chatgpt:history:${e.group_id}:${now.message_id}`
// 将历史记录存储到 redis 中,有效期为 3600 秒
await redis.set(key, JSON.stringify(askChatGPTList), { EX: 3600 })
Bot.logger.mark(`ChatGPT 回答完成,消息 key 为「${key}」,内容为「${JSON.stringify(askChatGPTList)}」`)
}
/**
* 调用 ChatGPT 进行回答
* @param questionType {string}
* @param AskRequestList {Array<AskRequest>}
* @returns {string}
*/
async askChatGPT (questionType, AskRequestList) {
const preset = regPresetMap.get(questionType)
const openai = new OpenAI({
baseURL: preset.apiAddress,
apiKey: preset.apiKey
})
try {
// 创建流式传输
const chatResult = await openai.chat.completions.create({
model: preset.model,
messages: [
{ role: 'system', content: preset.prompt },
...AskRequestList.map((each, index) => ({
role: each.qq == Bot.uin ? 'assistant' : 'user',
content: each.content
}))
]
})
return chatResult.choices[0].message.content
} catch (error) {
console.error(error)
return '发生错误'
}
}
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化