import plugin from '../../../lib/plugins/plugin.js' import { Config } from '../utils/config.js' import { formatDuration, getAzureRoleList, getPublicIP, getUserReplySetting, getVitsRoleList, getVoicevoxRoleList, makeForwardMsg, parseDuration, renderUrl } from '../utils/common.js' import SydneyAIClient from '../utils/SydneyAIClient.js' import { convertSpeaker, speakers as vitsRoleList } from '../utils/tts.js' import md5 from 'md5' import path from 'path' import fs from 'fs' import loader from '../../../lib/plugins/loader.js' import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/voicevox.js' import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js' export class ChatgptManagement extends plugin { constructor (e) { super({ name: 'ChatGPT-Plugin 管理', dsc: '插件的管理项配置,让你轻松掌控各个功能的开闭和管理。包含各种实用的配置选项,让你的聊天更加便捷和高效!', event: 'message', priority: 500, rule: [ { reg: '#chatgpt开启(问题)?(回复)?确认', fnc: 'turnOnConfirm', permission: 'master' }, { reg: '#chatgpt关闭(问题)?(回复)?确认', fnc: 'turnOffConfirm', permission: 'master' }, { reg: '#chatgpt(设置|绑定)(token|Token)', fnc: 'setAccessToken', permission: 'master' }, { reg: '#chatgpt(设置|绑定)(Poe|POE)(token|Token)', fnc: 'setPoeCookie', permission: 'master' }, { reg: '#chatgpt(设置|绑定|添加)(必应|Bing |bing )(token|Token)', fnc: 'setBingAccessToken', permission: 'master' }, { reg: '#chatgpt(删除|移除)(必应|Bing |bing )(token|Token)', fnc: 'delBingAccessToken', permission: 'master' }, { reg: '#chatgpt(查看|浏览)(必应|Bing |bing )(token|Token)', fnc: 'getBingAccessToken', permission: 'master' }, { reg: '#chatgpt(迁移|恢复)(必应|Bing |bing )(token|Token)', fnc: 'migrateBingAccessToken', permission: 'master' }, { reg: '^#chatgpt切换浏览器$', fnc: 'useBrowserBasedSolution', permission: 'master' }, { reg: '^#chatgpt切换API$', fnc: 'useOpenAIAPIBasedSolution', permission: 'master' }, { reg: '^#chatgpt切换(ChatGLM|chatglm)$', fnc: 'useChatGLMSolution', permission: 'master' }, { reg: '^#chatgpt切换API3$', fnc: 'useReversedAPIBasedSolution2', permission: 'master' }, { reg: '^#chatgpt切换(必应|Bing)$', fnc: 'useBingSolution', permission: 'master' }, { reg: '^#chatgpt切换(Poe|poe)$', fnc: 'useClaudeBasedSolution', permission: 'master' }, { reg: '^#chatgpt切换(Claude|claude|slack)$', fnc: 'useSlackClaudeBasedSolution', permission: 'master' }, { reg: '^#chatgpt切换星火$', fnc: 'useXinghuoBasedSolution', permission: 'master' }, { reg: '^#chatgpt(必应|Bing)切换', fnc: 'changeBingTone', permission: 'master' }, { reg: '^#chatgpt(必应|Bing)(开启|关闭)建议(回复)?', fnc: 'bingOpenSuggestedResponses', permission: 'master' }, { reg: '^#chatgpt模式(帮助)?$', fnc: 'modeHelp' }, { reg: '^#chatgpt版本(信息)', fnc: 'versionChatGPTPlugin' }, { reg: '^#chatgpt(本群)?(群\\d+)?(关闭|闭嘴|关机|休眠|下班)', fnc: 'shutUp', permission: 'master' }, { reg: '^#chatgpt(本群)?(群\\d+)?(开启|启动|激活|张嘴|开口|说话|上班)$', fnc: 'openMouth', permission: 'master' }, { reg: '^#chatgpt查看?(关闭|闭嘴|关机|休眠|下班|休眠)列表$', fnc: 'listShutUp', permission: 'master' }, { reg: '^#chatgpt设置(API|key)(Key|key)$', fnc: 'setAPIKey', permission: 'master' }, { reg: '^#chatgpt设置(API|api)设定$', fnc: 'setAPIPromptPrefix', permission: 'master' }, { reg: '^#chatgpt设置星火token$', fnc: 'setXinghuoToken', permission: 'master' }, { reg: '^#chatgpt设置(Bing|必应|Sydney|悉尼|sydney|bing)设定$', fnc: 'setBingPromptPrefix', permission: 'master' }, { reg: '^#chatgpt(开启|关闭)画图$', fnc: 'switchDraw', permission: 'master' }, { reg: '^#chatgpt查看(API|api)设定$', fnc: 'queryAPIPromptPrefix', permission: 'master' }, { reg: '^#chatgpt查看(Bing|必应|Sydney|悉尼|sydney|bing)设定$', fnc: 'queryBingPromptPrefix', permission: 'master' }, { reg: '^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色).*)|回复帮助)$', fnc: 'setDefaultReplySetting', permission: 'master' }, { /** 命令正则匹配 */ reg: '^#(关闭|打开)群聊上下文$', /** 执行方法 */ fnc: 'enableGroupContext', permission: 'master' }, { reg: '^#chatgpt(允许|禁止|打开|关闭|同意)私聊$', fnc: 'enablePrivateChat', permission: 'master' }, { reg: '^#(设置|修改)管理密码', fnc: 'setAdminPassword', permission: 'master' }, { reg: '^#(设置|修改)用户密码', fnc: 'setUserPassword' }, { reg: '^#chatgpt系统(设置|配置|管理)', fnc: 'adminPage', permission: 'master' }, { reg: '^#chatgpt用户(设置|配置|管理)', fnc: 'userPage' }, { reg: '^#?(chatgpt)(对话|管理|娱乐|绘图|人物设定|聊天记录)?指令表(帮助|搜索(.+))?', fnc: 'commandHelp' }, { reg: '^#语音切换.*', fnc: 'ttsSwitch', permission: 'master' }, { reg: '^#(chatgpt)?(vits|azure|vox)?语音(角色列表|服务)$', fnc: 'getTTSRoleList' }, { reg: '^#chatgpt设置后台(刷新|refresh)(t|T)oken$', fnc: 'setOpenAIPlatformToken' }, { reg: '^#(chatgpt)?查看回复设置$', fnc: 'viewUserSetting' }, { reg: '^#chatgpt导出配置', fnc: 'exportConfig', permission: 'master' }, { reg: '^#chatgpt导入配置', fnc: 'importConfig', permission: 'master' }, { reg: '^#chatgpt(开启|关闭)智能模式$', fnc: 'switchSmartMode', permission: 'master' } ] }) } async viewUserSetting (e) { const userSetting = await getUserReplySetting(this.e) const replyMsg = `${this.e.sender.user_id}的回复设置: 图片模式: ${userSetting.usePicture === true ? '开启' : '关闭'} 语音模式: ${userSetting.useTTS === true ? '开启' : '关闭'} Vits语音角色: ${userSetting.ttsRole} Azure语音角色: ${userSetting.ttsRoleAzure} VoiceVox语音角色: ${userSetting.ttsRoleVoiceVox} ${userSetting.useTTS === true ? '当前语音模式为' + Config.ttsMode : ''}` await this.reply(replyMsg.replace(/\n\s*$/, ''), e.isGroup) return true } async getTTSRoleList (e) { const matchCommand = e.msg.match(/^#(chatgpt)?(vits|azure|vox)?语音(服务|角色列表)/) if (matchCommand[3] === '服务') { await this.reply(`当前支持vox、vits、azure语音服务,可使用'#(vox|azure|vits)语音角色列表'查看支持的语音角色。 vits语音:主要有赛马娘,原神中文,原神日语,崩坏 3 的音色、结果有随机性,语调可能很奇怪。 vox语音:Voicevox 是一款由日本 DeNA 开发的语音合成软件,它可以将文本转换为自然流畅的语音。Voicevox 支持多种语言和声音,可以用于制作各种语音内容,如动画、游戏、广告等。Voicevox 还提供了丰富的调整选项,可以调整声音的音调、速度、音量等参数,以满足不同需求。除了桌面版软件外,Voicevox 还提供了 Web 版本和 API 接口,方便开发者在各种平台上使用。 azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,它可以帮助开发者将语音转换为文本、将文本转换为语音、实现自然语言理解和对话等功能。Azure 语音支持多种语言和声音,可以用于构建各种语音应用程序,如智能客服、语音助手、自动化电话系统等。Azure 语音还提供了丰富的 API 和 SDK,方便开发者在各种平台上集成使用。 `) return true } let userReplySetting = await getUserReplySetting(this.e) if (!userReplySetting.useTTS && matchCommand[2] === undefined) { await this.reply('当前不是语音模式,如果想查看不同语音模式下支持的角色列表,可使用"#(vox|azure|vits)语音角色列表"查看') return false } let ttsMode = Config.ttsMode let roleList = [] if (matchCommand[2] === 'vits') { roleList = getVitsRoleList(this.e) } else if (matchCommand[2] === 'vox') { roleList = getVoicevoxRoleList() } else if (matchCommand[2] === 'azure') { roleList = getAzureRoleList() } else if (matchCommand[2] === undefined) { switch (ttsMode) { case 'vits-uma-genshin-honkai': roleList = getVitsRoleList(this.e) break case 'voicevox': roleList = getVoicevoxRoleList() break case 'azure': roleList = getAzureRoleList() break default: break } } else { await this.reply('设置错误,请使用"#chatgpt语音服务"查看支持的语音配置') return false } if (roleList.length > 300) { let chunks = roleList.match(/[^、]+(?:、[^、]+){0,30}/g) roleList = await makeForwardMsg(e, chunks, `${Config.ttsMode}语音角色列表`) } await this.reply(roleList) } async ttsSwitch (e) { let userReplySetting = await getUserReplySetting(this.e) if (!userReplySetting.useTTS) { let replyMsg if (userReplySetting.usePicture) { replyMsg = `当前为${!userReplySetting.useTTS ? '图片模式' : ''},请先切换到语音模式吧~` } else { replyMsg = `当前为${!userReplySetting.useTTS ? '文本模式' : ''},请先切换到语音模式吧~` } await this.reply(replyMsg, e.isGroup) return false } let regExp = /#语音切换(.*)/ let ttsMode = e.msg.match(regExp)[1] if (['vits', 'azure', 'voicevox'].includes(ttsMode)) { if (ttsMode === 'vits') { Config.ttsMode = 'vits-uma-genshin-honkai' } else { Config.ttsMode = ttsMode } await this.reply(`语音回复已切换至${Config.ttsMode}模式${Config.ttsMode === 'azure' ? ',建议重新开始对话以获得更好的对话效果!' : ''}`) } else { await this.reply('暂不支持此模式,当前支持vits,azure,voicevox。') } return false } async commandHelp (e) { if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) { await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' + '#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' + '#chatgpt指令表搜索xxx: 查看包含对应关键词的指令') return false } const categories = { 对话: '对话', 管理: '管理', 娱乐: '娱乐', 绘图: '绘图', 人物设定: '人物设定', 聊天记录: '聊天记录' } function getCategory (e, plugin) { for (const key in categories) { if (e.msg.includes(key) && plugin.name.includes(categories[key])) { return '功能名称: ' } } return '' } const commandSet = [] const plugins = await Promise.all(loader.priority.map(p => new p.class())) for (const plugin of plugins) { const name = plugin.name const rule = plugin.rule if (/^chatgpt/i.test(name) && rule) { commandSet.push({ name, dsc: plugin.dsc, rule }) } } if (/^#(chatgpt)?指令表搜索(.+)/.test(e.msg.trim())) { let cmd = e.msg.trim().match(/#(chatgpt)?指令表搜索(.+)/)[2] if (!cmd) { await this.reply('(⊙ˍ⊙)') return 0 } else { let searchResults = [] commandSet.forEach(plugin => { plugin.rule.forEach(item => { if (item.reg.toLowerCase().includes(cmd.toLowerCase())) { searchResults.push(item.reg) } }) }) if (!searchResults.length) { await this.reply('没有找到符合的结果,换个关键词吧!', e.isGroup) return 0 } else if (searchResults.length <= 5) { await this.reply(searchResults.join('\n'), e.isGroup) return 1 } else { let msg = await makeForwardMsg(e, searchResults, e.msg.slice(1).startsWith('chatgpt') ? e.msg.slice(8) : 'chatgpt' + e.msg.slice(1)) await this.reply(msg) return 1 } } } const generatePrompt = (plugin, command) => { const category = getCategory(e, plugin) const commandsStr = command.length ? `正则指令:\n${command.join('\n')}\n` : '正则指令: 无\n' const description = `功能介绍:${plugin.dsc}\n` return `${category}${plugin.name}\n${description}${commandsStr}` } const prompts = [] for (const plugin of commandSet) { const commands = plugin.rule.map(v => v.reg.includes('[#*0-9]') ? '表情合成功能只需要发送两个emoji表情即可' : v.reg) const category = getCategory(e, plugin) if (category || (!e.msg.includes('对话') && !e.msg.includes('管理') && !e.msg.includes('娱乐') && !e.msg.includes('绘图') && !e.msg.includes('人物设定') && !e.msg.includes('聊天记录'))) { prompts.push(generatePrompt(plugin, commands)) } } let msg = await makeForwardMsg(e, prompts, e.msg.slice(1).startsWith('chatgpt') ? e.msg.slice(1) : ('chatgpt' + e.msg.slice(1))) await this.reply(msg) return true } async enablePrivateChat (e) { Config.enablePrivateChat = !!e.msg.match(/(允许|打开|同意)/) await this.reply('设置成功', e.isGroup) return false } async enableGroupContext (e) { const reg = /(关闭|打开)/ const match = e.msg.match(reg) if (match) { const action = match[1] if (action === '关闭') { Config.enableGroupContext = false // 关闭 await this.reply('已关闭群聊上下文功能', true) } else { Config.enableGroupContext = true // 打开 await this.reply('已打开群聊上下文功能', true) } } return false } async setDefaultReplySetting (e) { const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色)(.*))|回复帮助)/ const matchCommand = e.msg.match(reg) const settingType = matchCommand[2] let replyMsg = '' let ttsSupportKinds = [] if (Config.azureTTSKey) ttsSupportKinds.push(1) if (Config.ttsSpace) ttsSupportKinds.push(2) if (Config.voicevoxSpace) ttsSupportKinds.push(3) switch (settingType) { case '图片模式': if (matchCommand[1] === '打开') { Config.defaultUsePicture = true Config.defaultUseTTS = false replyMsg = 'ChatGPT将默认以图片回复' } else if (matchCommand[1] === '关闭') { Config.defaultUsePicture = false if (Config.defaultUseTTS) { replyMsg = 'ChatGPT将默认以语音回复' } else { replyMsg = 'ChatGPT将默认以文本回复' } } else if (matchCommand[1] === '设置') { replyMsg = '请使用“#chatgpt打开全局图片模式”或“#chatgpt关闭全局图片模式”命令来设置回复模式' } break case '文本模式': if (matchCommand[1] === '打开') { Config.defaultUsePicture = false Config.defaultUseTTS = false replyMsg = 'ChatGPT将默认以文本回复' } else if (matchCommand[1] === '关闭') { if (Config.defaultUseTTS) { replyMsg = 'ChatGPT将默认以语音回复' } else if (Config.defaultUsePicture) { replyMsg = 'ChatGPT将默认以图片回复' } else { Config.defaultUseTTS = true replyMsg = 'ChatGPT将默认以语音回复' } } else if (matchCommand[1] === '设置') { replyMsg = '请使用“#chatgpt打开全局文本模式”或“#chatgpt关闭全局文本模式”命令来设置回复模式' } break case '语音模式': if (!ttsSupportKinds.length) { replyMsg = '您没有配置任何语音服务,请前往锅巴面板进行配置' break } if (matchCommand[1] === '打开') { Config.defaultUseTTS = true Config.defaultUsePicture = false replyMsg = 'ChatGPT将默认以语音回复' } else if (matchCommand[1] === '关闭') { Config.defaultUseTTS = false if (Config.defaultUsePicture) { replyMsg = 'ChatGPT将默认以图片回复' } else { replyMsg = 'ChatGPT将默认以文本回复' } } else if (matchCommand[1] === '设置') { replyMsg = '请使用“#chatgpt打开全局语音模式”或“#chatgpt关闭全局语音模式”命令来设置回复模式' } break case '回复帮助': replyMsg = '可使用以下命令配置全局回复:\n#chatgpt(打开/关闭)全局(语音/图片/文本)模式\n#chatgpt设置全局(vox|azure|vits)语音角色+角色名称(留空则为随机)\n' break default: if (!ttsSupportKinds) { replyMsg = '您没有配置任何语音服务,请前往锅巴面板进行配置' break } if (settingType.match(/(语音角色|角色语音|角色)/)) { const voiceKind = matchCommand[5] let speaker = matchCommand[6] || '' if (voiceKind === undefined) { await this.reply('请选择需要设置的语音类型。使用"#chatgpt语音服务"查看支持的语音类型') return false } if (!speaker.length || speaker === '随机') { replyMsg = `设置成功,ChatGpt将在${voiceKind}语音模式下随机挑选角色进行回复` if (voiceKind === 'vits') Config.defaultTTSRole = '随机' if (voiceKind === 'azure') Config.azureTTSSpeaker = '随机' if (voiceKind === 'vox') Config.voicevoxTTSSpeaker = '随机' } else { if (ttsSupportKinds.includes(1) && voiceKind === 'azure') { if (getAzureRoleList().includes(speaker)) { Config.defaultUseTTS = azureRoleList.filter(s => s.name === speaker)[0].code replyMsg = `ChatGPT默认语音角色已被设置为“${speaker}”` } else { await this.reply(`抱歉,没有"${speaker}"这个角色,目前azure模式下支持的角色有${azureRoleList.map(item => item.name).join('、')}`) return false } } else if (ttsSupportKinds.includes(2) && voiceKind === 'vits') { const ttsRole = convertSpeaker(speaker) if (vitsRoleList.includes(ttsRole)) { Config.defaultTTSRole = ttsRole replyMsg = `ChatGPT默认语音角色已被设置为“${ttsRole}”` } else { replyMsg = `抱歉,我还不认识“${ttsRole}”这个语音角色,可使用'#vits角色列表'查看可配置的角色` } } else if (ttsSupportKinds.includes(3) && voiceKind === 'vox') { if (getVoicevoxRoleList().includes(speaker)) { let regex = /^(.*?)-(.*)$/ let match = regex.exec(speaker) let style = null if (match) { speaker = match[1] style = match[2] } let chosen = VoiceVoxTTS.supportConfigurations.filter(s => s.name === speaker) if (chosen.length === 0) { await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${VoiceVoxTTS.supportConfigurations.map(item => item.name).join('、')}`) break } if (style && !chosen[0].styles.find(item => item.name === style)) { await this.reply(`抱歉,"${speaker}"这个角色没有"${style}"这个风格,目前支持的风格有${chosen[0].styles.map(item => item.name).join('、')}`) break } Config.ttsRoleVoiceVox = chosen[0].name + (style ? `-${style}` : '') replyMsg = `ChatGPT默认语音角色已被设置为“${speaker}”` } else { await this.reply(`抱歉,没有"${speaker}"这个角色,目前voicevox模式下支持的角色有${voxRoleList.map(item => item.name).join('、')}`) return false } } else { replyMsg = `${voiceKind}语音角色设置错误,请检查语音配置~` } } } else { replyMsg = "无法识别的设置类型\n请使用'#chatgpt全局回复帮助'查看正确命令" } } await this.reply(replyMsg, true) } async turnOnConfirm (e) { await redis.set('CHATGPT:CONFIRM', 'on') await this.reply('已开启消息确认', true) return false } async turnOffConfirm (e) { await redis.set('CHATGPT:CONFIRM', 'off') await this.reply('已关闭消息确认', true) return false } async setAccessToken (e) { this.setContext('saveToken') await this.reply('请发送ChatGPT AccessToken', true) return false } async setPoeCookie () { this.setContext('savePoeToken') await this.reply('请发送Poe Cookie', true) return false } async savePoeToken (e) { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('p-b=')) { await this.reply('Poe cookie格式错误', true) this.finish('savePoeToken') return } await redis.set('CHATGPT:POE_TOKEN', token) await this.reply('Poe cookie设置成功', true) this.finish('savePoeToken') } async setBingAccessToken (e) { this.setContext('saveBingToken') await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true) return false } async migrateBingAccessToken () { let token = await redis.get('CHATGPT:BING_TOKEN') if (token) { token = token.split('|') token = token.map((item, index) => ( { Token: item, State: '正常', Usage: 0 } )) } else { token = [] } let tokens = await redis.get('CHATGPT:BING_TOKENS') if (tokens) { tokens = JSON.parse(tokens) } else { tokens = [] } await redis.set('CHATGPT:BING_TOKENS', JSON.stringify([...token, ...tokens])) await this.reply('迁移完成', true) } async getBingAccessToken (e) { let tokens = await redis.get('CHATGPT:BING_TOKENS') if (tokens) tokens = JSON.parse(tokens) else tokens = [] tokens = tokens.length > 0 ? tokens.map((item, index) => ( `【${index}】 Token:${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}` )).join('\n') : '无必应Token记录' await this.reply(`${tokens}`, true) return false } async delBingAccessToken (e) { this.setContext('deleteBingToken') let tokens = await redis.get('CHATGPT:BING_TOKENS') if (tokens) tokens = JSON.parse(tokens) else tokens = [] tokens = tokens.length > 0 ? tokens.map((item, index) => ( `【${index}】 Token:${item.Token.substring(0, 5 / 2) + '...' + item.Token.substring(item.Token.length - 5 / 2, item.Token.length)}` )).join('\n') : '无必应Token记录' await this.reply(`请发送要删除的token编号\n${tokens}`, true) if (tokens.length == 0) this.finish('saveBingToken') return false } async saveBingToken () { if (!this.e.msg) return let token = this.e.msg if (token.length < 100) { await this.reply('Bing Token格式错误,请确定获取了有效的_U Cookie或完整的Cookie', true) this.finish('saveBingToken') return } let cookie if (token?.indexOf('=') > -1) { cookie = token } const bingAIClient = new SydneyAIClient({ userToken: token, // "_U" cookie from bing.com cookie, debug: Config.debug }) // 异步就好了,不卡着这个context了 bingAIClient.createNewConversation().then(async res => { if (res.clientId) { logger.info('bing token 有效') } else { logger.error('bing token 无效', res) // 移除无效token if (await redis.exists('CHATGPT:BING_TOKENS') != 0) { let bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) const element = bingToken.findIndex(element => element.token === token) if (element >= 0) { bingToken[element].State = '异常' await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken)) } } await this.reply(`经检测,Bing Token无效。来自Bing的错误提示:${res.result?.message}`) } }) let bingToken = [] if (await redis.exists('CHATGPT:BING_TOKENS') != 0) { bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) if (!bingToken.some(element => element.token === token)) { bingToken.push({ Token: token, State: '正常', Usage: 0 }) } } else { bingToken = [{ Token: token, State: '正常', Usage: 0 }] } await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken)) await this.reply('Bing Token设置成功', true) this.finish('saveBingToken') } async deleteBingToken () { if (!this.e.msg) return let tokenId = this.e.msg if (await redis.exists('CHATGPT:BING_TOKENS') != 0) { let bingToken = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) if (tokenId >= 0 && tokenId < bingToken.length) { const removeToken = bingToken[tokenId].Token bingToken.splice(tokenId, 1) await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingToken)) await this.reply(`Token ${removeToken.substring(0, 5 / 2) + '...' + removeToken.substring(removeToken.length - 5 / 2, removeToken.length)} 移除成功`, true) this.finish('deleteBingToken') } else { await this.reply('Token编号错误!', true) this.finish('deleteBingToken') } } else { await this.reply('Token记录异常', true) this.finish('deleteBingToken') } } async saveToken () { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('ey') || token.length < 20) { await this.reply('ChatGPT AccessToken格式错误', true) this.finish('saveToken') return } await redis.set('CHATGPT:TOKEN', token) await this.reply('ChatGPT AccessToken设置成功', true) this.finish('saveToken') } async useBrowserBasedSolution (e) { await redis.set('CHATGPT:USE', 'browser') await this.reply('已切换到基于浏览器的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } async useOpenAIAPIBasedSolution (e) { let use = await redis.get('CHATGPT:USE') if (use !== 'api') { await redis.set('CHATGPT:USE', 'api') await this.reply('已切换到基于OpenAI API的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } else { await this.reply('当前已经是API模式了') } } async useChatGLMSolution (e) { await redis.set('CHATGPT:USE', 'chatglm') await this.reply('已切换到ChatGLM-6B解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } async useReversedAPIBasedSolution2 (e) { let use = await redis.get('CHATGPT:USE') if (use !== 'api3') { await redis.set('CHATGPT:USE', 'api3') await this.reply('已切换到基于第三方Reversed Conversastion API(API3)的解决方案') } else { await this.reply('当前已经是API3模式了') } } async useBingSolution (e) { let use = await redis.get('CHATGPT:USE') if (use !== 'bing') { await redis.set('CHATGPT:USE', 'bing') await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误') } else { await this.reply('当前已经是必应Bing模式了') } } async useClaudeBasedSolution (e) { let use = await redis.get('CHATGPT:USE') if (use !== 'poe') { await redis.set('CHATGPT:USE', 'poe') await this.reply('已切换到基于Quora\'s POE的解决方案') } else { await this.reply('当前已经是POE模式了') } } async useSlackClaudeBasedSolution () { let use = await redis.get('CHATGPT:USE') if (use !== 'claude') { await redis.set('CHATGPT:USE', 'claude') await this.reply('已切换到基于slack claude机器人的解决方案') } else { await this.reply('当前已经是claude模式了') } } async useXinghuoBasedSolution () { let use = await redis.get('CHATGPT:USE') if (use !== 'xh') { await redis.set('CHATGPT:USE', 'xh') await this.reply('已切换到基于星火的解决方案') } else { await this.reply('当前已经是星火模式了') } } async changeBingTone (e) { let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '') if (!tongStyle) { return } let map = { 精准: 'precise', 创意: 'creative', 均衡: 'balanced', Sydney: 'Sydney', sydney: 'Sydney', 悉尼: 'Sydney', 自设定: 'Custom', 自定义: 'Custom' } if (map[tongStyle]) { Config.toneStyle = map[tongStyle] await e.reply('切换成功') } else { await e.reply('没有这种风格。支持的风格:精准、创意、均衡、悉尼、自设定') } } async bingOpenSuggestedResponses (e) { Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1 await e.reply('操作成功') } async checkAuth (e) { if (!e.isMaster) { e.reply(`只有主人才能命令ChatGPT哦~ (*/ω\*)`) return false } return true } async versionChatGPTPlugin (e) { await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 } }) } async modeHelp () { let mode = await redis.get('CHATGPT:USE') const modeMap = { browser: '浏览器', // apiReverse: 'API2', api: 'API', bing: '必应', api3: 'API3', chatglm: 'ChatGLM-6B', claude: 'Claude', poe: 'Poe' } let modeText = modeMap[mode || 'api'] let message = `API模式和浏览器模式如何选择? API模式会调用 OpenAI 官方提供的 gpt-3.5-turbo API,只需要提供 API Key。一般情况下,该种方式响应速度更快,不会像 chatGPT 官网一样总出现不可用的现象,但要注意 gpt-3.5-turbo 的 API 调用是收费的,新用户有 $5 的试用金可用于支付,价格为 $0.0020/1K tokens。(问题和回答加起来算 token) API3 模式会调用官网反代 API,它会帮你绕过 CF 防护,需要提供 ChatGPT 的 Token。效果与官网和浏览器一致。设置 Token 指令:#chatgpt设置token。 浏览器模式通过在本地启动 Chrome 等浏览器模拟用户访问 ChatGPT 网站,使得获得和官方以及 API2 模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问 ChatGPT 的 IP 地址),且响应速度不如 API,而且高峰期容易无法使用。 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的 Bing 登录 Cookie 方可使用。#chatgpt设置必应 Token。 自建 ChatGLM 模式会调用自建的 ChatGLM-6B 服务器 API 进行对话,需要自建。参考 https://github.com/ikechan8370/SimpleChatGLM6BAPI。 Claude 模式会调用 Slack 中的 Claude 机器人进行对话,与其他模式不同的是全局共享一个对话。配置参考 https://ikechan8370.com/archives/chatgpt-plugin-for-yunzaipei-zhi-slack-claude。 Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie:#chatgpt设置 Poe Token。 星火 模式会调用科大讯飞推出的新一代认知智能大模型 '星火认知大模型' 进行对话。需要提供Cookie:#chatgpt设置星火token。 您可以使用 "#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe/星火" 来切换到指定模式。 当前为 ${modeText} 模式。` await this.reply(message) } async shutUp (e) { let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?(关闭|闭嘴|关机|休眠|下班)/, '') let scope let time = 3600000 if (duration === '永久') { time = 0 } else if (duration) { time = parseDuration(duration) } const match = e.msg.match(/#chatgpt群(\d+)?(关闭|闭嘴|关机|休眠|下班)(.*)/) if (e.msg.indexOf('本群') > -1) { if (e.isGroup) { scope = e.group.group_id if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) { await redis.del(`CHATGPT:SHUT_UP:${scope}`) await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time }) await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`) } else { await redis.set(`CHATGPT:SHUT_UP:${scope}`, '1', { EX: time }) await e.reply(`好的,已切换休眠状态:倒计时${formatDuration(time)}`) } } else { await e.reply('主人,这里好像不是群哦') return false } } else if (match) { const groupId = parseInt(match[1], 10) if (Bot.getGroupList().get(groupId)) { if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) { await redis.del(`CHATGPT:SHUT_UP:${groupId}`) await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time }) await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`) } else { await redis.set(`CHATGPT:SHUT_UP:${groupId}`, '1', { EX: time }) await e.reply(`好的,即将在群${groupId}中休眠${formatDuration(time)}`) } } else { await e.reply('主人还没告诉我群号呢') return false } } else { if (await redis.get('CHATGPT:SHUT_UP:ALL')) { await redis.del('CHATGPT:SHUT_UP:ALL') await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time }) await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`) } else { await redis.set('CHATGPT:SHUT_UP:ALL', '1', { EX: time }) await e.reply(`好的,我会延长休眠时间${formatDuration(time)}`) } } } async openMouth (e) { const match = e.msg.match(/^#chatgpt群(\d+)/) if (e.msg.indexOf('本群') > -1) { if (await redis.get('CHATGPT:SHUT_UP:ALL')) { await e.reply('当前为休眠模式,没办法做出回应呢') return false } if (e.isGroup) { let scope = e.group.group_id if (await redis.get(`CHATGPT:SHUT_UP:${scope}`)) { await redis.del(`CHATGPT:SHUT_UP:${scope}`) await e.reply('好的主人,我又可以和大家聊天啦') } else { await e.reply('主人,我已经启动过了哦') } } else { await e.reply('主人,这里好像不是群哦') return false } } else if (match) { if (await redis.get('CHATGPT:SHUT_UP:ALL')) { await e.reply('当前为休眠模式,没办法做出回应呢') return false } const groupId = parseInt(match[1], 10) if (Bot.getGroupList().get(groupId)) { if (await redis.get(`CHATGPT:SHUT_UP:${groupId}`)) { await redis.del(`CHATGPT:SHUT_UP:${groupId}`) await e.reply(`好的主人,我终于又可以在群${groupId}和大家聊天了`) } else { await e.reply(`主人,我在群${groupId}中已经是启动状态了哦`) } } else { await e.reply('主人还没告诉我群号呢') return false } } else { let keys = await redis.keys('CHATGPT:SHUT_UP:*') if (await redis.get('CHATGPT:SHUT_UP:ALL')) { await redis.del('CHATGPT:SHUT_UP:ALL') for (let i = 0; i < keys.length; i++) { await redis.del(keys[i]) } await e.reply('好的,我会开启所有群聊响应') } else if (keys || keys.length > 0) { for (let i = 0; i < keys.length; i++) { await redis.del(keys[i]) } await e.reply('已经开启过全群响应啦') } else { await e.reply('我没有在任何群休眠哦') } } } async listShutUp () { let keys = await redis.keys('CHATGPT:SHUT_UP:*') if (!keys || keys.length === 0) { await this.reply('已经开启过全群响应啦', true) } else { let list = [] for (let i = 0; i < keys.length; i++) { let key = keys[i] let groupId = key.replace('CHATGPT:SHUT_UP:', '') let ttl = await redis.ttl(key) let ttlFormat = formatDuration(ttl) list.push({ groupId, ttlFormat }) } await this.reply(list.map(item => item.groupId !== 'ALL' ? `群聊${item.groupId}: ${item.ttlFormat}` : `全局: ${item.ttlFormat}`).join('\n')) } } async setAPIKey (e) { this.setContext('saveAPIKey') await this.reply('请发送OpenAI API Key.', true) return false } async saveAPIKey () { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('sk-')) { await this.reply('OpenAI API Key格式错误', true) this.finish('saveAPIKey') return } // todo Config.apiKey = token await this.reply('OpenAI API Key设置成功', true) this.finish('saveAPIKey') } async setXinghuoToken () { this.setContext('saveXinghuoToken') await this.reply('请发送星火的ssoSessionId', true) return false } async saveXinghuoToken () { if (!this.e.msg) return let token = this.e.msg // todo Config.xinghuoToken = token await this.reply('星火ssoSessionId设置成功', true) this.finish('saveXinghuoToken') } async setAPIPromptPrefix (e) { this.setContext('saveAPIPromptPrefix') await this.reply('请发送用于API模式的设定', true) return false } async saveAPIPromptPrefix (e) { if (!this.e.msg) return if (this.e.msg === '取消') { await this.reply('已取消设置API设定', true) this.finish('saveAPIPromptPrefix') return } // todo Config.promptPrefixOverride = this.e.msg await this.reply('API模式的设定设置成功', true) this.finish('saveAPIPromptPrefix') } async setBingPromptPrefix (e) { this.setContext('saveBingPromptPrefix') await this.reply('请发送用于Bing Sydney模式的设定', true) return false } async saveBingPromptPrefix (e) { if (!this.e.msg) return if (this.e.msg === '取消') { await this.reply('已取消设置Sydney设定', true) this.finish('saveBingPromptPrefix') return } Config.sydney = this.e.msg await this.reply('Bing Sydney模式的设定设置成功', true) this.finish('saveBingPromptPrefix') } async switchDraw (e) { if (e.msg.indexOf('开启') > -1) { if (Config.enableDraw) { await this.reply('当前已经开启chatgpt画图功能', true) } else { Config.enableDraw = true await this.reply('chatgpt画图功能开启成功', true) } } else { if (!Config.enableDraw) { await this.reply('当前未开启chatgpt画图功能', true) } else { Config.enableDraw = false await this.reply('chatgpt画图功能关闭成功', true) } } } async queryAPIPromptPrefix (e) { await this.reply(Config.promptPrefixOverride, true) } async queryBingPromptPrefix (e) { await this.reply(Config.sydney, true) } async setAdminPassword (e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true } this.setContext('saveAdminPassword') await this.reply('请发送系统管理密码', true) return false } async setUserPassword (e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true } this.setContext('saveUserPassword') await this.reply('请发送系统用户密码', true) return false } async saveAdminPassword (e) { if (!this.e.msg) return const passwd = this.e.msg await redis.set('CHATGPT:ADMIN_PASSWD', md5(passwd)) await this.reply('设置成功', true) this.finish('saveAdminPassword') } async saveUserPassword (e) { if (!this.e.msg) return const passwd = this.e.msg const dir = 'resources/ChatGPTCache/user' const filename = `${this.e.user_id}.json` const filepath = path.join(dir, filename) fs.mkdirSync(dir, { recursive: true }) if (fs.existsSync(filepath)) { fs.readFile(filepath, 'utf8', (err, data) => { if (err) { console.error(err) return } const config = JSON.parse(data) config.passwd = md5(passwd) fs.writeFile(filepath, JSON.stringify(config), 'utf8', (err) => { if (err) { console.error(err) } }) }) } else { fs.writeFile(filepath, JSON.stringify({ user: this.e.user_id, passwd: md5(passwd), chat: [] }), 'utf8', (err) => { if (err) { console.error(err) } }) } await this.reply('设置完成', true) this.finish('saveUserPassword') } async adminPage (e) { if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) { await this.reply('请私聊发送命令', true) return true } const viewHost = Config.serverHost ? `http://${Config.serverHost}/` : `http://${await getPublicIP()}:${Config.serverPort || 3321}/` await this.reply(`请登录${viewHost + 'admin/settings'}进行系统配置`, true) } async userPage (e) { if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) { await this.reply('请私聊发送命令', true) return true } const viewHost = Config.serverHost ? `http://${Config.serverHost}/` : `http://${await getPublicIP()}:${Config.serverPort || 3321}/` await this.reply(`请登录${viewHost + 'admin/dashboard'}进行系统配置`, true) } async setOpenAIPlatformToken (e) { this.setContext('doSetOpenAIPlatformToken') await e.reply('请发送refreshToken\n你可以在已登录的platform.openai.com后台界面打开调试窗口,在终端中执行\nJSON.parse(localStorage.getItem(Object.keys(localStorage).filter(k => k.includes(\'auth0\'))[0])).body.refresh_token\n如果仍不能查看余额,请退出登录重新获取刷新令牌') } async doSetOpenAIPlatformToken () { let token = this.e.msg if (!token) { return false } Config.OpenAiPlatformRefreshToken = token.replaceAll('\'', '') await this.e.reply('设置成功') this.finish('doSetOpenAIPlatformToken') } async exportConfig (e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true } let redisConfig = {} if (await redis.exists('CHATGPT:BING_TOKENS') != 0) { let bingTokens = await redis.get('CHATGPT:BING_TOKENS') if (bingTokens) { bingTokens = JSON.parse(bingTokens) } else bingTokens = [] redisConfig.bingTokens = bingTokens } else { redisConfig.bingTokens = [] } if (await redis.exists('CHATGPT:CONFIRM') != 0) { redisConfig.turnConfirm = await redis.get('CHATGPT:CONFIRM') === 'on' } if (await redis.exists('CHATGPT:USE') != 0) { redisConfig.useMode = await redis.get('CHATGPT:USE') } const filepath = path.join('plugins/chatgpt-plugin/resources', 'view.json') const configView = JSON.parse(fs.readFileSync(filepath, 'utf8')) const configJson = JSON.stringify({ chatConfig: Config, redisConfig, view: configView }) console.log(configJson) const buf = Buffer.from(configJson) e.friend.sendFile(buf, `ChatGPT-Plugin Config ${new Date()}.json`) return true } async importConfig (e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true } this.setContext('doImportConfig') await e.reply('请发送配置文件') } async doImportConfig (e) { const file = this.e.message.find(item => item.type === 'file') if (file) { const fileUrl = await this.e.friend.getFileUrl(file.fid) if (fileUrl) { try { let changeConfig = [] const response = await fetch(fileUrl) const data = await response.json() const chatdata = data.chatConfig || {} for (let [keyPath, value] of Object.entries(chatdata)) { if (keyPath === 'blockWords' || keyPath === 'promptBlockWords' || keyPath === 'initiativeChatGroups') { value = value.toString().split(/[,,;;\|]/) } if (Config[keyPath] != value) { changeConfig.push({ item: keyPath, value: typeof (value) === 'object' ? JSON.stringify(value) : value, old: typeof (Config[keyPath]) === 'object' ? JSON.stringify(Config[keyPath]) : Config[keyPath], type: 'config' }) Config[keyPath] = value } } const redisConfig = data.redisConfig || {} if (redisConfig.bingTokens != null) { changeConfig.push({ item: 'bingTokens', value: JSON.stringify(redisConfig.bingTokens), old: await redis.get('CHATGPT:BING_TOKENS'), type: 'redis' }) await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(redisConfig.bingTokens)) } if (redisConfig.turnConfirm != null) { changeConfig.push({ item: 'turnConfirm', value: redisConfig.turnConfirm ? 'on' : 'off', old: await redis.get('CHATGPT:CONFIRM'), type: 'redis' }) await redis.set('CHATGPT:CONFIRM', redisConfig.turnConfirm ? 'on' : 'off') } if (redisConfig.useMode != null) { changeConfig.push({ item: 'useMode', value: redisConfig.useMode, old: await redis.get('CHATGPT:USE'), type: 'redis' }) await redis.set('CHATGPT:USE', redisConfig.useMode) } await this.reply(await makeForwardMsg(this.e, changeConfig.map(msg => `修改项:${msg.item}\n旧数据\n\n${msg.old}\n\n新数据\n ${msg.value}`))) } catch (error) { console.error(error) await e.reply('配置文件错误') } } } else { await this.reply('未找到配置文件', false) return false } this.finish('doImportConfig') } async switchSmartMode (e) { if (e.msg.includes('开启')) { if (Config.smartMode) { await e.reply('已经开启了') return } Config.smartMode = true await e.reply('好的,已经打开智能模式,注意API额度哦。配合开启读取群聊上下文效果更佳!') } else { if (!Config.smartMode) { await e.reply('已经是关闭得了') return } Config.smartMode = false await e.reply('好的,已经关闭智能模式') } } }