diff --git a/apps/management.js b/apps/management.js index 4394fed..437acef 100644 --- a/apps/management.js +++ b/apps/management.js @@ -21,7 +21,7 @@ import VoiceVoxTTS, { supportConfigurations as voxRoleList } from '../utils/tts/ import { supportConfigurations as azureRoleList } from '../utils/tts/microsoft-azure.js' export class ChatgptManagement extends plugin { - constructor (e) { + constructor(e) { super({ name: 'ChatGPT-Plugin 管理', dsc: '插件的管理项配置,让你轻松掌控各个功能的开闭和管理。包含各种实用的配置选项,让你的聊天更加便捷和高效!', @@ -251,7 +251,7 @@ export class ChatgptManagement extends plugin { }) } - async viewUserSetting (e) { + async viewUserSetting(e) { const userSetting = await getUserReplySetting(this.e) const replyMsg = `${this.e.sender.user_id}的回复设置: 图片模式: ${userSetting.usePicture === true ? '开启' : '关闭'} @@ -264,7 +264,7 @@ ${userSetting.useTTS === true ? '当前语音模式为' + Config.ttsMode : ''}` return true } - async getTTSRoleList (e) { + async getTTSRoleList(e) { const matchCommand = e.msg.match(/^#(chatgpt)?(vits|azure|vox)?语音(服务|角色列表)/) if (matchCommand[3] === '服务') { await this.reply(`当前支持vox、vits、azure语音服务,可使用'#(vox|azure|vits)语音角色列表'查看支持的语音角色。 @@ -315,7 +315,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, await this.reply(roleList) } - async ttsSwitch (e) { + async ttsSwitch(e) { let userReplySetting = await getUserReplySetting(this.e) if (!userReplySetting.useTTS) { let replyMsg @@ -342,11 +342,11 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return false } - async commandHelp (e) { + async commandHelp(e) { if (/^#(chatgpt)?指令表帮助$/.exec(e.msg.trim())) { await this.reply('#chatgpt指令表: 查看本插件的所有指令\n' + - '#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' + - '#chatgpt指令表搜索xxx: 查看包含对应关键词的指令') + '#chatgpt(对话|管理|娱乐|绘图|人物设定|聊天记录)指令表: 查看对应功能分类的指令表\n' + + '#chatgpt指令表搜索xxx: 查看包含对应关键词的指令') return false } const categories = { @@ -358,7 +358,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, 聊天记录: '聊天记录' } - function getCategory (e, plugin) { + function getCategory(e, plugin) { for (const key in categories) { if (e.msg.includes(key) && plugin.name.includes(categories[key])) { return '功能名称: ' @@ -423,13 +423,133 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return true } - async enablePrivateChat (e) { + async setList(e) { + this.setContext('saveList') + isWhiteList = e.msg.includes('白') + const listType = isWhiteList ? '对话白名单' : '对话黑名单' + await this.reply(`请发送需要添加的${listType}号码,默认设置为添加群号,需要添加QQ号时在前面添加^(例如:^123456)。`, e.isGroup) + return false + } + + async saveList(e) { + if (!this.e.msg) return + const listType = isWhiteList ? '对话白名单' : '对话黑名单' + const regex = /^\^?[1-9]\d{5,9}$/ + const wrongInput = [] + const inputSet = new Set() + const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => { + if (value.length > 11 || !regex.test(value)) { + wrongInput.push(value) + } else if (!inputSet.has(value)) { + inputSet.add(value) + acc.push(value) + } + return acc + }, []) + if (!inputList.length) { + let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。' + if (wrongInput.length) replyMsg += `\n${wrongInput.length ? '检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}` + await this.reply(replyMsg, e.isGroup) + return false + } + let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist) + whitelist = [...inputList, ...whitelist] + blacklist = [...inputList, ...blacklist] + if (listType === '对话白名单') { + Config.whitelist = Array.from(new Set(whitelist)) + } else { + Config.blacklist = Array.from(new Set(blacklist)) + } + let replyMsg = `${listType}已更新,可通过\n"#chatgpt查看${listType}" 查看最新名单\n"#chatgpt移除${listType}" 管理名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}` + if (e.isPrivate) { + replyMsg += `\n当前${listType}为:${listType === '对话白名单' ? Config.whitelist : Config.blacklist}` + } + await this.reply(replyMsg, e.isGroup) + this.finish('saveList') + } + + async checkList(e) { + if (e.msg.includes('帮助')) { + await this.reply('默认设置为添加群号,需要拉黑QQ号时在前面添加^(例如:^123456),可一次性混合输入多个配置号码,错误项会自动忽略。具体使用指令可通过 "#指令表搜索名单" 查看,白名单优先级高于黑名单。') + return true + } + isWhiteList = e.msg.includes('白') + const list = isWhiteList ? Config.whitelist : Config.blacklist + const listType = isWhiteList ? '白名单' : '黑名单' + const replyMsg = list.length ? `当前${listType}为:${list}` : `当前没有设置任何${listType}` + await this.reply(replyMsg, e.isGroup) + return false + } + + async delList(e) { + isWhiteList = e.msg.includes('白') + const listType = isWhiteList ? '对话白名单' : '对话黑名单' + let replyMsg = '' + if (Config.whitelist.length === 0 && Config.blacklist.length === 0) { + replyMsg = '当前对话(白|黑)名单都是空哒,请先添加吧~' + } else if ((listType === '对话白名单' && !Config.whitelist.length) || (listType === '对话黑名单' && !Config.blacklist.length)) { + replyMsg = `当前${listType}为空,请先添加吧~` + } + if (replyMsg) { + await this.reply(replyMsg, e.isGroup) + return false + } + this.setContext('confirmDelList') + await this.reply(`请发送需要删除的${listType}号码,号码间使用,隔开。输入‘全部删除’清空${listType}。${e.isPrivate ? '\n当前' + listType + '为:' + (listType === '对话白名单' ? Config.whitelist : Config.blacklist) : ''}`, e.isGroup) + return false + } + + async confirmDelList(e) { + if (!this.e.msg) return + const isAllDeleted = this.e.msg.trim() === '全部删除' + const regex = /^\^?[1-9]\d{5,9}$/ + const wrongInput = [] + const inputSet = new Set() + const inputList = this.e.msg.split(/[,,]/).reduce((acc, value) => { + if (value.length > 11 || !regex.test(value)) { + wrongInput.push(value) + } else if (!inputSet.has(value)) { + inputSet.add(value) + acc.push(value) + } + return acc + }, []) + if (!inputList.length && !isAllDeleted) { + let replyMsg = '名单更新失败,请在检查输入是否正确后重新输入。' + if (wrongInput.length) replyMsg += `${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}` + await this.reply(replyMsg, e.isGroup) + return false + } + let [whitelist, blacklist] = processList(Config.whitelist, Config.blacklist) + if (isAllDeleted) { + Config.whitelist = isWhiteList ? [] : whitelist + Config.blacklist = !isWhiteList ? [] : blacklist + } else { + for (const element of inputList) { + if (isWhiteList) { + Config.whitelist = whitelist.filter(item => item !== element) + } else { + Config.blacklist = blacklist.filter(item => item !== element) + } + } + } + const listType = isWhiteList ? '对话白名单' : '对话黑名单' + let replyMsg = `${listType}已更新,可通过 "#chatgpt查看${listType}" 命令查看最新名单${wrongInput.length ? '\n检测到以下错误输入:"' + wrongInput.join(',') + '",已自动忽略。' : ''}` + if (e.isPrivate) { + const list = isWhiteList ? Config.whitelist : Config.blacklist + replyMsg = list.length ? `\n当前${listType}为:${list}` : `当前没有设置任何${listType}` + } + await this.reply(replyMsg, e.isGroup) + this.finish('confirmDelList') + } + + async enablePrivateChat(e) { Config.enablePrivateChat = !!e.msg.match(/(允许|打开|同意)/) await this.reply('设置成功', e.isGroup) return false } - async enableGroupContext (e) { + async enableGroupContext(e) { const reg = /(关闭|打开)/ const match = e.msg.match(reg) if (match) { @@ -445,7 +565,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return false } - async setDefaultReplySetting (e) { + async setDefaultReplySetting(e) { const reg = /^#chatgpt(打开|关闭|设置)?全局((文本模式|图片模式|语音模式|((azure|vits|vox)?语音角色|角色语音|角色)(.*))|回复帮助)/ const matchCommand = e.msg.match(reg) const settingType = matchCommand[2] @@ -578,31 +698,31 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, await this.reply(replyMsg, true) } - async turnOnConfirm (e) { + async turnOnConfirm(e) { await redis.set('CHATGPT:CONFIRM', 'on') await this.reply('已开启消息确认', true) return false } - async turnOffConfirm (e) { + async turnOffConfirm(e) { await redis.set('CHATGPT:CONFIRM', 'off') await this.reply('已关闭消息确认', true) return false } - async setAccessToken (e) { + async setAccessToken(e) { this.setContext('saveToken') await this.reply('请发送ChatGPT AccessToken', true) return false } - async setPoeCookie () { + async setPoeCookie() { this.setContext('savePoeToken') await this.reply('请发送Poe Cookie', true) return false } - async savePoeToken (e) { + async savePoeToken(e) { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('p-b=')) { @@ -615,13 +735,13 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, this.finish('savePoeToken') } - async setBingAccessToken (e) { + async setBingAccessToken(e) { this.setContext('saveBingToken') await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true) return false } - async migrateBingAccessToken () { + async migrateBingAccessToken() { let token = await redis.get('CHATGPT:BING_TOKEN') if (token) { token = token.split('|') @@ -645,27 +765,27 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, await this.reply('迁移完成', true) } - async getBingAccessToken (e) { + 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)}` + `【${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) { + 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)}` + `【${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) @@ -673,7 +793,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return false } - async saveBingToken () { + async saveBingToken() { if (!this.e.msg) return let token = this.e.msg if (token.length < 100) { @@ -730,7 +850,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, this.finish('saveBingToken') } - async deleteBingToken () { + async deleteBingToken() { if (!this.e.msg) return let tokenId = this.e.msg if (await redis.exists('CHATGPT:BING_TOKENS') != 0) { @@ -751,7 +871,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async saveToken () { + async saveToken() { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('ey') || token.length < 20) { @@ -764,12 +884,12 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, this.finish('saveToken') } - async useBrowserBasedSolution (e) { + async useBrowserBasedSolution(e) { await redis.set('CHATGPT:USE', 'browser') await this.reply('已切换到基于浏览器的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } - async useOpenAIAPIBasedSolution (e) { + async useOpenAIAPIBasedSolution(e) { let use = await redis.get('CHATGPT:USE') if (use !== 'api') { await redis.set('CHATGPT:USE', 'api') @@ -779,12 +899,12 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async useChatGLMSolution (e) { + async useChatGLMSolution(e) { await redis.set('CHATGPT:USE', 'chatglm') await this.reply('已切换到ChatGLM-6B解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } - async useReversedAPIBasedSolution2 (e) { + async useReversedAPIBasedSolution2(e) { let use = await redis.get('CHATGPT:USE') if (use !== 'api3') { await redis.set('CHATGPT:USE', 'api3') @@ -794,7 +914,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async useBingSolution (e) { + async useBingSolution(e) { let use = await redis.get('CHATGPT:USE') if (use !== 'bing') { await redis.set('CHATGPT:USE', 'bing') @@ -804,7 +924,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async useClaudeBasedSolution (e) { + async useClaudeBasedSolution(e) { let use = await redis.get('CHATGPT:USE') if (use !== 'poe') { await redis.set('CHATGPT:USE', 'poe') @@ -814,7 +934,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async useSlackClaudeBasedSolution () { + async useSlackClaudeBasedSolution() { let use = await redis.get('CHATGPT:USE') if (use !== 'claude') { await redis.set('CHATGPT:USE', 'claude') @@ -824,7 +944,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async useXinghuoBasedSolution () { + async useXinghuoBasedSolution() { let use = await redis.get('CHATGPT:USE') if (use !== 'xh') { await redis.set('CHATGPT:USE', 'xh') @@ -834,7 +954,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async changeBingTone (e) { + async changeBingTone(e) { let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '') if (!tongStyle) { return @@ -857,12 +977,12 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } - async bingOpenSuggestedResponses (e) { + async bingOpenSuggestedResponses(e) { Config.enableSuggestedResponses = e.msg.indexOf('开启') > -1 await e.reply('操作成功') } - async checkAuth (e) { + async checkAuth(e) { if (!e.isMaster) { e.reply(`只有主人才能命令ChatGPT哦~ (*/ω\*)`) @@ -871,11 +991,11 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return true } - async versionChatGPTPlugin (e) { + async versionChatGPTPlugin(e) { await renderUrl(e, `http://127.0.0.1:${Config.serverPort || 3321}/version`, { Viewport: { width: 800, height: 600 } }) } - async modeHelp () { + async modeHelp() { let mode = await redis.get('CHATGPT:USE') const modeMap = { browser: '浏览器', @@ -912,7 +1032,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie await this.reply(message) } - async shutUp (e) { + async shutUp(e) { let duration = e.msg.replace(/^#chatgpt(本群)?(群\d+)?(关闭|闭嘴|关机|休眠|下班)/, '') let scope let time = 3600000 @@ -964,7 +1084,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie } } - async openMouth (e) { + async openMouth(e) { const match = e.msg.match(/^#chatgpt群(\d+)/) if (e.msg.indexOf('本群') > -1) { if (await redis.get('CHATGPT:SHUT_UP:ALL')) { @@ -1019,7 +1139,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie } } - async listShutUp () { + async listShutUp() { let keys = await redis.keys('CHATGPT:SHUT_UP:*') if (!keys || keys.length === 0) { await this.reply('已经开启过全群响应啦', true) @@ -1036,13 +1156,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie } } - async setAPIKey (e) { + async setAPIKey(e) { this.setContext('saveAPIKey') await this.reply('请发送OpenAI API Key.', true) return false } - async saveAPIKey () { + async saveAPIKey() { if (!this.e.msg) return let token = this.e.msg if (!token.startsWith('sk-')) { @@ -1056,13 +1176,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveAPIKey') } - async setXinghuoToken () { + async setXinghuoToken() { this.setContext('saveXinghuoToken') await this.reply('请发送星火的ssoSessionId', true) return false } - async saveXinghuoToken () { + async saveXinghuoToken() { if (!this.e.msg) return let token = this.e.msg // todo @@ -1071,13 +1191,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveXinghuoToken') } - async setAPIPromptPrefix (e) { + async setAPIPromptPrefix(e) { this.setContext('saveAPIPromptPrefix') await this.reply('请发送用于API模式的设定', true) return false } - async saveAPIPromptPrefix (e) { + async saveAPIPromptPrefix(e) { if (!this.e.msg) return if (this.e.msg === '取消') { await this.reply('已取消设置API设定', true) @@ -1090,13 +1210,13 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveAPIPromptPrefix') } - async setBingPromptPrefix (e) { + async setBingPromptPrefix(e) { this.setContext('saveBingPromptPrefix') await this.reply('请发送用于Bing Sydney模式的设定', true) return false } - async saveBingPromptPrefix (e) { + async saveBingPromptPrefix(e) { if (!this.e.msg) return if (this.e.msg === '取消') { await this.reply('已取消设置Sydney设定', true) @@ -1108,7 +1228,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveBingPromptPrefix') } - async switchDraw (e) { + async switchDraw(e) { if (e.msg.indexOf('开启') > -1) { if (Config.enableDraw) { await this.reply('当前已经开启chatgpt画图功能', true) @@ -1126,15 +1246,15 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie } } - async queryAPIPromptPrefix (e) { + async queryAPIPromptPrefix(e) { await this.reply(Config.promptPrefixOverride, true) } - async queryBingPromptPrefix (e) { + async queryBingPromptPrefix(e) { await this.reply(Config.sydney, true) } - async setAdminPassword (e) { + async setAdminPassword(e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true @@ -1144,7 +1264,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie return false } - async setUserPassword (e) { + async setUserPassword(e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true @@ -1154,7 +1274,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie return false } - async saveAdminPassword (e) { + async saveAdminPassword(e) { if (!this.e.msg) return const passwd = this.e.msg await redis.set('CHATGPT:ADMIN_PASSWD', md5(passwd)) @@ -1162,7 +1282,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveAdminPassword') } - async saveUserPassword (e) { + async saveUserPassword(e) { if (!this.e.msg) return const passwd = this.e.msg const dir = 'resources/ChatGPTCache/user' @@ -1198,7 +1318,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('saveUserPassword') } - async adminPage (e) { + async adminPage(e) { if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) { await this.reply('请私聊发送命令', true) return true @@ -1207,7 +1327,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie await this.reply(`请登录${viewHost + 'admin/settings'}进行系统配置`, true) } - async userPage (e) { + async userPage(e) { if (!Config.groupAdminPage && (e.isGroup || !e.isPrivate)) { await this.reply('请私聊发送命令', true) return true @@ -1216,12 +1336,12 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie await this.reply(`请登录${viewHost + 'admin/dashboard'}进行系统配置`, true) } - async setOpenAIPlatformToken (e) { + 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 () { + async doSetOpenAIPlatformToken() { let token = this.e.msg if (!token) { return false @@ -1231,7 +1351,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('doSetOpenAIPlatformToken') } - async exportConfig (e) { + async exportConfig(e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true @@ -1250,9 +1370,12 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie 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 + redisConfig, + view: configView }) console.log(configJson) const buf = Buffer.from(configJson) @@ -1260,7 +1383,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie return true } - async importConfig (e) { + async importConfig(e) { if (e.isGroup || !e.isPrivate) { await this.reply('请私聊发送命令', true) return true @@ -1269,7 +1392,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie await e.reply('请发送配置文件') } - async doImportConfig (e) { + async doImportConfig(e) { const file = this.e.message.find(item => item.type === 'file') if (file) { const fileUrl = await this.e.friend.getFileUrl(file.fid) @@ -1333,7 +1456,7 @@ Poe 模式会调用 Poe 中的 Claude-instant 进行对话。需要提供 Cookie this.finish('doImportConfig') } - async switchSmartMode (e) { + async switchSmartMode(e) { if (e.msg.includes('开启')) { if (Config.smartMode) { await e.reply('已经开启了') diff --git a/resources/view/setting_view.json b/resources/view/setting_view.json new file mode 100644 index 0000000..32f6b39 --- /dev/null +++ b/resources/view/setting_view.json @@ -0,0 +1,867 @@ +[ + { + "id": "GeneralSettings", + "title": "通用设置", + "view": [ + { + "type": "check", + "label": "图片识别OCR", + "data": "imgOcr" + }, + { + "type": "check", + "label": "允许其他模式", + "data": "allowOtherMode" + }, + { + "type": "check", + "label": "调试信息", + "data": "debug" + }, + { + "type": "check", + "label": "是否允许私聊机器人", + "data": "enablePrivateChat" + }, + { + "type": "check", + "label": "回复确认", + "model": "redisConfig", + "data": "turnConfirm" + }, + { + "type": "check", + "label": "新版帮助", + "data": "newhelp" + }, + { + "type": "number", + "label": "对话保留时长", + "placeholder": "每个人发起的对话保留时长", + "data": "conversationPreserveTime" + }, + { + "type": "url", + "label": "代理服务器地址", + "placeholder": "数据通过代理服务器发送,http或socks5代理", + "data": "proxy" + }, + { + "type": "select", + "label": "对话模式", + "model": "redisConfig", + "data": "useMode", + "items": [ + { + "label": "必应", + "value": "bing" + }, + { + "label": "ChatGPT API", + "value": "api" + }, + { + "label": "ChatGPT API3", + "value": "api3" + }, + { + "label": "Slack Claude", + "value": "claude" + }, + { + "label": "ChatGLM", + "value": "chatglm" + }, + { + "label": "星火", + "value": "xh" + }, + { + "label": "浏览器", + "value": "browser" + } + ] + }, + { + "type": "password", + "label": "高德APIKey", + "placeholder": "用于查询天气", + "data": "amapKey" + }, + { + "type": "url", + "label": "Azure search key", + "placeholder": "https://www.microsoft.com/en-us/bing/apis/bing-web-search-api", + "data": "azSerpKey" + }, + { + "type": "select", + "label": "搜索来源", + "data": "serpSource", + "items": [ + { + "label": "Azure", + "value": "azure" + }, + { + "label": "ikechan8370", + "value": "ikechan8370" + } + ] + }, + { + "type": "url", + "label": "额外工具url", + "placeholder": "测试期间提供一个公益接口,一段时间后撤掉", + "data": "extraUrl" + } + ] + }, + { + "id": "ChatSettings", + "title": "聊天设置", + "view": [ + { + "type": "tabs", + "id": "ChatSetting", + "tabs": [ + { + "title": "文本模式", + "icon": "mdi-format-text", + "tab": "text", + "view": [ + { + "type": "check", + "label": "长文本自动转图片", + "data": "autoUsePicture" + }, + { + "type": "check", + "label": "是否允许机器人真AT", + "data": "enableRobotAt" + }, + { + "type": "number", + "label": "自动转图片阈值", + "placeholder": "自动转图片的字数阈值", + "data": "autoUsePictureThreshold" + } + ] + }, + { + "title": "图片模式[基础参数]", + "icon": "mdi-image", + "tab": "image_base", + "view": [ + { + "type": "check", + "label": "全局图片模式", + "data": "defaultUsePicture" + }, + { + "type": "check", + "label": "图片引用消息", + "data": "quoteReply" + }, + { + "type": "check", + "label": "启用二维码", + "data": "showQRCode" + }, + { + "type": "text", + "label": "BOT命名", + "placeholder": "强制修改Bot命名", + "data": "chatViewBotName" + }, + { + "type": "url", + "label": "渲染服务器地址", + "placeholder": "可选择第三方渲染服务器", + "data": "viewHost" + }, + { + "type": "number", + "label": "图片渲染宽度", + "placeholder": "图片渲染宽度", + "data": "chatViewWidth" + }, + { + "type": "check", + "label": "云渲染", + "data": "cloudRender" + }, + { + "type": "number", + "label": "云渲染DPR", + "placeholder": "设置云渲染画面缩放,数值愈大越清晰", + "data": "cloudDPR" + } + ] + }, + { + "title": "图片模式[Live2D]", + "icon": "mdi-image", + "tab": "image_live2d", + "view": [ + { + "type": "check", + "label": "Live2D", + "data": "live2d" + }, + { + "type": "text", + "label": "Live2D模型", + "placeholder": "使用的Live2D模式文件", + "data": "live2dModel" + }, + { + "type": "number", + "label": "Live2D模型缩放", + "placeholder": "渲染live2d的模型大小", + "data": "live2dOption_scale" + }, + { + "type": "number", + "label": "Live2D模型位置X", + "placeholder": "Live2d模型在区域的位置X轴微调", + "data": "live2dOption_positionX" + }, + { + "type": "number", + "label": "Live2D模型位置Y", + "placeholder": "Live2d模型在区域的位置Y轴微调", + "data": "live2dOption_positionY" + }, + { + "type": "number", + "label": "Live2D模型旋转", + "placeholder": "Live2d模型在区域的旋转角度", + "data": "live2dOption_rotation" + }, + { + "type": "number", + "label": "Live2D模型透明度", + "placeholder": "Live2d模型的透明度", + "data": "live2dOption_alpha" + } + ] + }, + { + "title": "图片模式[旧渲染]", + "icon": "mdi-image", + "tab": "image_old", + "view": [ + { + "type": "check", + "label": "旧版本渲染", + "data": "oldview" + }, + { + "type": "check", + "label": "预制渲染服务器访问代码", + "data": "cacheEntry" + }, + { + "type": "url", + "label": "渲染服务器地址", + "placeholder": "可选择第三方渲染服务器", + "data": "cacheUrl" + } + ] + }, + { + "title": "语音模式", + "icon": "mdi-microphone", + "tab": "voice", + "view": [ + { + "type": "check", + "label": "全局语音模式", + "data": "defaultUseTTS" + }, + { + "type": "check", + "label": "语音同时发送文字", + "data": "alsoSendText" + }, + { + "type": "number", + "label": "语音转文字阈值", + "placeholder": "语音模式下,字数超过这个阈值就降级为文字", + "data": "ttsAutoFallbackThreshold" + }, + { + "type": "text", + "label": "语音过滤正则表达式", + "placeholder": "语音模式下,配置此项以过滤不想被读出来的内容", + "data": "ttsRegex" + }, + { + "type": "select", + "label": "语音模式源", + "data": "ttsMode", + "items": [ + { + "label": "Vits", + "value": "vits-uma-genshin-honkai" + }, + { + "label": "微软Azure", + "value": "azure" + }, + { + "label": "VoiceVox", + "value": "VoiceVox" + } + ] + }, + { + "type": "select", + "label": "云转码模式", + "data": "cloudMode", + "items": [ + { + "label": "文件", + "value": "file" + }, + { + "label": "链接", + "value": "url" + } + ] + } + ] + }, + { + "title": "语音模式[vits]", + "icon": "mdi-microphone", + "tab": "vits", + "view": [ + { + "type": "url", + "label": "语音转换API地址", + "placeholder": "前往duplicate空间查看api地址", + "data": "ttsSpace" + }, + { + "type": "url", + "label": "语音转换huggingface反代", + "data": "huggingFaceReverseProxy" + }, + { + "type": "number", + "label": "控制情感变化程度", + "data": "noiseScale" + }, + { + "type": "number", + "label": "控制音素发音长度", + "data": "noiseScaleW" + }, + { + "type": "number", + "label": "控制整体语速", + "data": "lengthScale" + }, + { + "type": "check", + "label": "日语输出", + "data": "autoJapanese" + } + ] + }, + { + "title": "语音模式[Azure]", + "icon": "mdi-microphone", + "tab": "azure", + "view": [ + { + "type": "password", + "label": "语音服务密钥", + "placeholder": "Azure的语音服务密钥", + "data": "azureTTSKey" + }, + { + "type": "text", + "label": "语音服务区域", + "placeholder": "Azure语音服务区域", + "data": "azureTTSRegion" + }, + { + "type": "number", + "label": "Azure情绪多样化", + "data": "azureTTSEmotion" + }, + { + "type": "number", + "label": "Azure情绪纠正", + "data": "enhanceAzureTTSEmotion" + } + ] + }, + { + "title": "语音模式[Voicevox]", + "icon": "mdi-microphone", + "tab": "voicevox", + "view": [ + { + "type": "url", + "label": "语音转换API地址", + "placeholder": "voicevox语音转换API地址", + "data": "voicevoxSpace" + } + ] + } + ] + } + ] + }, + { + "id": "ModelSettings", + "title": "模式设置", + "view": [ + { + "type": "tabs", + "id": "ChatSetting", + "tabs": [ + { + "title": "API", + "tab": "api", + "view": [ + { + "type": "check", + "label": "强制使用OpenAI反代", + "data": "openAiForceUseReverse" + }, + { + "type": "check", + "label": "智能模式", + "data": "smartMode" + }, + { + "type": "password", + "label": "OpenAI API Key", + "placeholder": "OpenAI的ApiKey,用于访问OpenAI的API接口", + "data": "apiKey" + }, + { + "type": "text", + "label": "OpenAI 模型", + "placeholder": "gpt-4, gpt-4-0314, gpt-4-32k, gpt-4-32k-0314, gpt-3.5-turbo, gpt-3.5-turbo-0301", + "data": "model" + }, + { + "type": "text", + "label": "AI名字", + "placeholder": "AI认为的自己的名字,当你问他你是谁是他会回答这里的名字", + "data": "assistantLabel" + }, + { + "type": "number", + "label": "temperature", + "placeholder": "用于控制回复内容的多样性,数值越大回复越加随机、多元化,数值越小回复越加保守", + "data": "temperature" + }, + { + "type": "url", + "label": "OpenAI API服务器地址", + "placeholder": "OpenAI的API服务器地址,注意要带上/v1。", + "data": "openAiBaseUrl" + }, + { + "type": "textarea", + "label": "AI风格", + "placeholder": "你可以在这里写入你希望AI回答的风格,比如希望优先回答中文,回答长一点等", + "data": "promptPrefixOverride" + } + ] + }, + { + "title": "API3", + "tab": "api3", + "view": [ + { + "type": "url", + "label": "ChatGPT API反代服务器地址", + "placeholder": "ChatGPT的API反代服务器,用于绕过Cloudflare访问ChatGPT API。", + "data": "api" + }, + { + "type": "url", + "label": "apiBaseUrl地址", + "data": "apiBaseUrl" + }, + { + "type": "password", + "label": "OpenAI refreshToken", + "placeholder": "OpenAI的refreshToken,用于刷新Access Token", + "data": "OpenAiPlatformRefreshToken" + }, + { + "type": "password", + "label": "OpenAI AccessToken", + "model": "redisConfig", + "data": "openAiPlatformAccessToken" + }, + { + "type": "check", + "label": "强制使用ChatGPT反代", + "data": "apiForceUseReverse" + }, + { + "type": "check", + "label": "使用GPT-4", + "data": "useGPT4" + } + ] + }, + { + "title": "浏览器", + "tab": "browser", + "view": [ + { + "type": "check", + "label": "无头模式", + "data": "headless" + }, + { + "type": "text", + "label": "用户名", + "placeholder": "OpenAI用户名", + "data": "username" + }, + { + "type": "password", + "label": "密码", + "placeholder": "OpenAI密码", + "data": "password" + }, + { + "type": "text", + "label": "Chrome路径", + "placeholder": "为空使用默认puppeteer的chromium,也可以传递自己本机安装的Chrome可执行文件地址,提高通过率。", + "data": "chromePath" + }, + { + "type": "textarea", + "label": "浏览器UA", + "placeholder": "模拟浏览器UA,无特殊需求保持默认即可", + "data": "UA" + }, + { + "type": "password", + "label": "验证码平台Token", + "placeholder": "可注册2captcha实现跳过验证码", + "data": "2captchaToken" + } + ] + }, + { + "title": "必应", + "tab": "bing", + "view": [ + { + "type": "select", + "label": "Bing模式", + "data": "toneStyle", + "items": [ + { + "label": "均衡", + "value": "balanced" + }, + { + "label": "创意", + "value": "creative" + }, + { + "label": "精确", + "value": "precise" + }, + { + "label": "Sydney(可能存在风险)", + "value": "Sydney" + }, + { + "label": "自设定(可能存在风险)", + "value": "Custom" + } + ] + }, + { + "type": "check", + "label": "是否开启建议回复", + "data": "enableSuggestedResponses" + }, + { + "type": "check", + "label": "是否允许机器人读取近期的群聊聊天记录", + "data": "enableGroupContext" + }, + { + "type": "number", + "label": "允许机器人读取近期的最多群聊聊天记录条数", + "placeholder": "允许机器人读取近期的最多群聊聊天记录条数。太多可能会超。", + "data": "groupContextLength" + }, + { + "type": "text", + "label": "机器人读取聊天记录时的后台prompt", + "data": "groupContextTip" + }, + { + "type": "check", + "label": "加强主人认知", + "data": "enforceMaster" + }, + { + "type": "check", + "label": "Bing抱歉是否不计入聊天记录", + "data": "sydneyApologyIgnored" + }, + { + "type": "check", + "label": "情感显示", + "data": "sydneyMood" + }, + { + "type": "textarea", + "label": "Custom的设定", + "placeholder": "仅自设定模式下有效。你可以自己改写设定,让Sydney变成你希望的样子", + "data": "sydney" + }, + { + "type": "textarea", + "label": "Bing的扩展资料", + "placeholder": "AI将会从你提供的扩展资料中学习到一些知识,帮助它更好地回答你的问题", + "data": "sydneyContext" + }, + { + "type": "textarea", + "label": "情感模式设定", + "placeholder": "情感显示开启的情况下AI将根据设定在正文中体现情感内容", + "data": "sydneyMoodTip" + }, + { + "type": "url", + "label": "sydney反代", + "placeholder": "仅悉尼和自设定模式下有效,用于创建对话(默认不用于正式对话)", + "data": "sydneyReverseProxy" + }, + { + "type": "check", + "label": "强制使用sydney反代", + "data": "sydneyReverseProxy" + }, + { + "type": "check", + "label": "对话使用sydney反代", + "data": "sydneyForceUseReverse" + }, + { + "type": "check", + "label": "允许生成图像等内容", + "data": "enableGenerateContents" + } + ] + }, + { + "title": "ChatGLM", + "tab": "chatglm", + "view": [ + { + "type": "url", + "label": "ChatGLM API地址", + "data": "chatglmBaseUrl" + } + ] + }, + { + "title": "Slack Claude", + "tab": "claude", + "view": [ + { + "type": "password", + "label": "Slack用户Token", + "placeholder": "slackUserToken,在OAuth&Permissions页面获取", + "data": "slackUserToken" + }, + { + "type": "password", + "label": "Slack Bot Token", + "placeholder": "slackBotUserToken,在OAuth&Permissions页面获取", + "data": "slackBotUserToken" + }, + { + "type": "text", + "label": "Slack成员id", + "placeholder": "在Slack中点击Claude头像查看详情,其中的成员ID复制过来", + "data": "slackClaudeUserId" + }, + { + "type": "password", + "label": "Slack签名密钥", + "placeholder": "Signing Secret。在Basic Information页面获取", + "data": "slackSigningSecret" + }, + { + "type": "check", + "label": "Claude使用全局设定", + "data": "slackClaudeEnableGlobalPreset" + }, + { + "type": "textarea", + "label": "Slack全局设定", + "placeholder": "若启用全局设定,每个人都会默认使用这里的设定", + "data": "slackClaudeGlobalPreset" + } + ] + }, + { + "title": "星火", + "tab": "xh", + "view": [ + { + "type": "password", + "label": "星火Cookie", + "data": "xinghuoToken" + } + ] + } + ] + } + ] + }, + { + "id": "DrawSettings", + "title": "绘图设置", + "view": [ + { + "type": "check", + "label": "绘图功能开关", + "data": "enableDraw" + }, + { + "type": "number", + "label": "绘图CD", + "placeholder": "绘图指令的CD时间", + "data": "drawCD" + }, + { + "type": "url", + "label": "emojiAPI地址", + "placeholder": "合成emoji的API地址", + "data": "emojiBaseURL" + } + ] + }, + { + "id": "GroupSettings", + "title": "群聊设置", + "view": [ + { + "type": "textarea", + "label": "打招呼prompt", + "placeholder": "将会用这段文字询问ChatGPT,由ChatGPT给出随机的打招呼文字", + "data": "helloPrompt" + }, + { + "type": "number", + "label": "打招呼间隔(小时)", + "data": "helloInterval" + }, + { + "type": "number", + "label": "打招呼的触发概率(%)", + "placeholder": "设置为100则每次经过间隔时间必定触发主动打招呼事件。", + "data": "helloProbability" + }, + { + "type": "select", + "label": "触发方式", + "data": "toggleMode", + "items": [ + { + "label": "at", + "value": "at" + }, + { + "label": "#chat", + "value": "prefix" + } + ] + } + ] + }, + { + "id": "TimeoutSettings", + "title": "服务超时配置", + "view": [ + { + "type": "number", + "label": "默认超时时间", + "placeholder": "各个地方的默认超时时间", + "data": "defaultTimeoutMs" + }, + { + "type": "number", + "label": "浏览器超时时间", + "placeholder": "浏览器默认超时,浏览器可能需要更高的超时时间", + "data": "chromeTimeoutMS" + }, + { + "type": "number", + "label": "Sydney模式接受首条信息超时时间", + "placeholder": "超过该时间阈值未收到Bing的任何消息,则断开本次连接并重试", + "data": "sydneyFirstMessageTimeout" + } + ] + }, + { + "id": "ReviewSettings", + "title": "违禁内容核查", + "view": [ + { + "type": "textarea", + "label": "输出黑名单", + "placeholder": "检查输出结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开", + "data": "blockWords" + }, + { + "type": "textarea", + "label": "输入黑名单", + "placeholder": "检查输入结果中是否有违禁词,如果存在黑名单中的违禁词则不输出。英文逗号隔开", + "data": "promptBlockWords" + } + ] + }, + { + "id": "GroupSettings", + "title": "群聊设置", + "view": [ + { + "type": "number", + "label": "系统Api服务端口", + "placeholder": "系统Api服务开启的端口号,如需外网访问请将系统防火墙和服务器防火墙对应端口开放", + "data": "ServerSettings" + }, + { + "type": "text", + "label": "系统服务访问域名", + "placeholder": "使用域名代替公网ip,适用于有服务器和域名的朋友避免暴露ip使用", + "data": "serverHost" + }, + { + "type": "url", + "label": "云服务API地址", + "placeholder": "目前支持node-silk语音转码、云图片渲染和页面生成缓存", + "data": "cloudTranscode" + }, + { + "type": "check", + "label": "允许群获取后台地址", + "data": "groupAdminPage" + } + ] + } +] \ No newline at end of file diff --git a/server/index.js b/server/index.js index 2a4e4f8..24f609b 100644 --- a/server/index.js +++ b/server/index.js @@ -8,13 +8,15 @@ import fs from 'fs' import path from 'path' import os from 'os' import schedule from 'node-schedule' +import websocketclient from 'ws' import { Config } from '../utils/config.js' import { UserInfo, GetUser } from './modules/user_data.js' -import { getPublicIP, getUserData } from '../utils/common.js' +import { getPublicIP, getUserData, getMasterQQ, randomString } from '../utils/common.js' import webRoute from './modules/web_route.js' import webUser from './modules/user.js' +import SettingView from './modules/setting_view.js' const __dirname = path.resolve() const server = fastify({ @@ -79,6 +81,92 @@ await server.register(websocket, { await server.register(fastifyCookie) await server.register(webRoute) await server.register(webUser) +await server.register(SettingView) + +// 无法访问端口的情况下创建与media的通讯 +async function mediaLink() { + const ip = await getPublicIP() + const testServer = await fetch(`${Config.cloudTranscode}/check`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + url: `http://${ip}:${Config.serverPort || 3321}/` + }) + }) + if (testServer.ok) { + const checkCloudData = await testServer.json() + if (checkCloudData.state != 'error') { + console.log('本地服务无法访问,开启media服务代理') + const serverurl = new URL(Config.cloudTranscode) + const ws = new websocketclient(`ws://${serverurl.hostname}${serverurl.port ? ':' + serverurl.port : ''}/ws`) + ws.on('open', () => { + ws.send(JSON.stringify({ + command: 'register', + region: Bot.uin, + type: 'server', + })) + }) + ws.on('message', async (message) => { + try { + const data = JSON.parse(message) + switch (data.command) { + case 'register': + if (data.state) { + let master = (await getMasterQQ())[0] + Bot.sendPrivateMsg(master, `当前chatgpt插件服务无法被外网访问,已启用代理链接,访问代码:${data.token}`, false) + } else { + console.log('注册区域失败') + } + break + case 'login': + if (data.token) { + const user = UserInfo(data.token) + if (user) { + ws.login = true + ws.send(JSON.stringify({ command: data.command, state: true, region: Bot.uin, type: 'server' })) + } else { + ws.send(JSON.stringify({ command: data.command, state: false, error: '权限验证失败', region: Bot.uin, type: 'server' })) + } + } + break + case 'post_login': + if (data.qq && data.passwd) { + const token = randomString(32) + if (data.qq == Bot.uin && await redis.get('CHATGPT:ADMIN_PASSWD') == data.passwd) { + AddUser({ user: data.qq, token: token, autho: 'admin' }) + ws.send(JSON.stringify({ command: data.command, state: true, autho: 'admin', token: token, region: Bot.uin, type: 'server' })) + + } else { + const user = await getUserData(data.qq) + if (user.passwd != '' && user.passwd === data.passwd) { + AddUser({ user: data.qq, token: token, autho: 'user' }) + ws.send(JSON.stringify({ command: data.command, state: true, autho: 'user', token: token, region: Bot.uin, type: 'server' })) + } else { + ws.send(JSON.stringify({ command: data.command, state: false, error: `用户名密码错误,如果忘记密码请私聊机器人输入 ${data.qq == Bot.uin ? '#修改管理密码' : '#修改用户密码'} 进行修改`, region: Bot.uin, type: 'server' })) + } + } + } else { + ws.send(JSON.stringify({ command: data.command, state: false, error: '未输入用户名或密码', region: Bot.uin, type: 'server' })) + } + break + } + } catch (error) { + console.log(error) + } + }) + + } else { + console.log('本地服务网络正常,无需开启通讯') + } + } else { + console.log('media服务器未响应') + } +} +// 未完工,暂不开启这个功能 +// mediaLink() export async function createServer() { // 页面数据获取 diff --git a/server/modules/setting_view.js b/server/modules/setting_view.js new file mode 100644 index 0000000..81aa570 --- /dev/null +++ b/server/modules/setting_view.js @@ -0,0 +1,23 @@ +import { UserInfo } from './user_data.js' +import fs from 'fs' +import path from 'path' + +async function SettingView(fastify, options) { + // 获取配置视图 + fastify.post('/settingView', async (request, reply) => { + const token = request.cookies.token || request.body?.token || 'unknown' + let user = UserInfo(token) + if (!user) { + reply.send({ err: '未登录' }) + } else if (user.autho === 'admin') { + const filepath = path.join('plugins/chatgpt-plugin/resources/view', 'setting_view.json') + const configView = JSON.parse(fs.readFileSync(filepath, 'utf8')) + reply.send(configView) + } else { + reply.send({ err: '权限不足' }) + } + return reply + }) +} + +export default SettingView \ No newline at end of file diff --git a/server/modules/user.js b/server/modules/user.js index a964ea2..5d8f57d 100644 --- a/server/modules/user.js +++ b/server/modules/user.js @@ -40,7 +40,8 @@ async function User(fastify, options) { reply.send({ verify: true, user: user.user, - autho: user.autho + autho: user.autho, + version: 10010, }) return reply })