From a00956ef437c5a137aa1080d8482f95cc8394dc1 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Thu, 28 Dec 2023 13:03:57 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20#chatgpt=E5=BF=85=E5=BA=94=E7=A6=81?= =?UTF-8?q?=E7=94=A8=E6=90=9C=E7=B4=A2=20#chatgpt=E5=BF=85=E5=BA=94?= =?UTF-8?q?=E5=BC=80=E5=90=AF=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/management.js | 14 ++++++++++-- guoba.support.js | 6 +++++ utils/SydneyAIClient.js | 49 +++++++++++++++++++++-------------------- utils/config.js | 1 + 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/apps/management.js b/apps/management.js index ff74a30..64c30ee 100644 --- a/apps/management.js +++ b/apps/management.js @@ -327,8 +327,8 @@ export class ChatgptManagement extends plugin { permission: 'master' }, { - reg: '^#chatgpt修补Gemini$', - fnc: 'patchGemini', + reg: '^#chatgpt必应(禁用|禁止|关闭|启用|开启)搜索$', + fnc: 'switchBingSearch', permission: 'master' } ] @@ -1672,6 +1672,16 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, return false } + async switchBingSearch (e) { + if (e.msg.includes('启用') || e.msg.includes('开启')) { + Config.sydneyEnableSearch = true + await e.reply('已开启必应搜索') + } else { + Config.sydneyEnableSearch = false + await e.reply('已禁用必应搜索') + } + } + async saveXinghuoModel (e) { if (!this.e.msg) return let token = this.e.msg diff --git a/guoba.support.js b/guoba.support.js index c5c0c30..23ee28e 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -374,6 +374,12 @@ export function supportGuoba () { ] } }, + { + field: 'sydneyEnableSearch', + label: '是否允许必应进行搜索', + bottomHelpMessage: '关闭后必应将禁用搜索', + component: 'Switch' + }, { field: 'enableSuggestedResponses', label: '是否开启建议回复', diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index acf692b..f452bb5 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -101,12 +101,12 @@ export default class SydneyAIClient { this.opts.host = 'https://edgeservices.bing.com/edgesvc' } logger.mark('使用host:' + this.opts.host) - let response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1366.5`, fetchOptions) + let response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1381.12`, fetchOptions) let text = await response.text() let retry = 10 while (retry >= 0 && response.status === 200 && !text) { await delay(400) - response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1366.5`, fetchOptions) + response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1381.12`, fetchOptions) text = await response.text() retry-- } @@ -362,7 +362,7 @@ export default class SydneyAIClient { // 'dagslnv1', // 'sportsansgnd', // 'dl_edge_desc', - 'noknowimg', + // 'noknowimg', // 'dtappid', // 'cricinfo', // 'cricinfov2', @@ -370,21 +370,21 @@ export default class SydneyAIClient { // 'gencontentv3', 'iycapbing', 'iyxapbing', - 'revimglnk', - 'revimgsrc1', - 'revimgur', + // 'revimglnk', + // 'revimgsrc1', + // 'revimgur', + 'clgalileo', 'eredirecturl' ] if (Config.enableGenerateContents) { optionsSets.push(...['gencontentv3']) } + if (!Config.sydneyEnableSearch || toSummaryFileContent?.content) { + optionsSets.push(...['nosearchall']) + } let maxConv = Config.maxNumUserMessagesInConversation const currentDate = moment().format('YYYY-MM-DDTHH:mm:ssZ') const imageDate = await this.kblobImage(opts.imageUrl) - if (toSummaryFileContent?.content) { - // message = `请不要进行搜索,用户的问题是:"${message}"` - messageType = 'Chat' - } let argument0 = { source: 'cib', optionsSets, @@ -392,17 +392,17 @@ export default class SydneyAIClient { // 'InternalSearchQuery', 'InternalSearchResult', 'Disengaged', 'InternalLoaderMessage', 'Progress', 'RenderCardRequest', 'AdsQuery', 'InvokeAction', 'SemanticSerp', 'GenerateContentQuery', 'SearchQuery'], sliceIds: [ - 'e2eperf', - 'gbacf', - 'srchqryfix', - 'caccnctacf', - 'translref', - 'fluxnosearchc', - 'fluxnosearch', - '1115rai289s0', - '1130deucs0', - '1116pythons0', - 'cacmuidarb' + // 'e2eperf', + // 'gbacf', + // 'srchqryfix', + // 'caccnctacf', + // 'translref', + // 'fluxnosearchc', + // 'fluxnosearch', + // '1115rai289s0', + // '1130deucs0', + // '1116pythons0', + // 'cacmuidarb' ], requestId: crypto.randomUUID(), traceId: genRanHex(32), @@ -476,9 +476,9 @@ export default class SydneyAIClient { conversationId, previousMessages, plugins: [ - { - id: 'c310c353-b9f0-4d76-ab0d-1dd5e979cf68' - } + // { + // id: 'c310c353-b9f0-4d76-ab0d-1dd5e979cf68' + // } ] } if (encryptedconversationsignature) { @@ -799,6 +799,7 @@ export default class SydneyAIClient { message, conversationExpiryTime: event?.item?.conversationExpiryTime }) + break } default: } diff --git a/utils/config.js b/utils/config.js index 2ce7dd4..ff50aa0 100644 --- a/utils/config.js +++ b/utils/config.js @@ -42,6 +42,7 @@ const defaultConfig = { sydneyImageRecognition: false, sydneyMoodTip: 'Your response should be divided into two parts, namely, the text and your mood. The mood available to you can only include: blandness, happy, shy, frustrated, disgusted, and frightened.All content should be replied in this format {"text": "", "mood": ""}.All content except mood should be placed in text, It is important to ensure that the content you reply to can be parsed by json.', enableSuggestedResponses: false, + sydneyEnableSearch: false, api: defaultChatGPTAPI, apiBaseUrl: 'https://chat3.avocado.wiki/backend-api', apiForceUseReverse: false, From 35c27884639006b5e5601392640794984ed6c54e Mon Sep 17 00:00:00 2001 From: misaka20002 <40714502+misaka20002@users.noreply.github.com> Date: Sun, 31 Dec 2023 11:28:54 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20#chatgpt(=E6=9C=AC?= =?UTF-8?q?=E7=BE=A4)=3F(=E7=BE=A4\\d+)=3F(=E9=97=AD=E5=98=B4|=E5=85=B3?= =?UTF-8?q?=E6=9C=BA|=E4=BC=91=E7=9C=A0|=E4=B8=8B=E7=8F=AD)=20=E4=B8=8E=20?= =?UTF-8?q?#chatgpt=E5=85=B3=E9=97=AD=E7=94=BB=E5=9B=BE=20=E6=AD=A3?= =?UTF-8?q?=E5=88=99=E8=A1=A8=E8=BE=BE=E5=BC=8F=E5=86=B2=E7=AA=81=20(#632)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/management.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/management.js b/apps/management.js index 64c30ee..0ee651b 100644 --- a/apps/management.js +++ b/apps/management.js @@ -171,17 +171,17 @@ export class ChatgptManagement extends plugin { fnc: 'versionChatGPTPlugin' }, { - reg: '^#chatgpt(本群)?(群\\d+)?(关闭|闭嘴|关机|休眠|下班)', + reg: '^#chatgpt(本群)?(群\\d+)?(闭嘴|关机|休眠|下班)', fnc: 'shutUp', permission: 'master' }, { - reg: '^#chatgpt(本群)?(群\\d+)?(开启|启动|激活|张嘴|开口|说话|上班)$', + reg: '^#chatgpt(本群)?(群\\d+)?(张嘴|开口|说话|上班)$', fnc: 'openMouth', permission: 'master' }, { - reg: '^#chatgpt查看?(关闭|闭嘴|关机|休眠|下班|休眠)列表$', + reg: '^#chatgpt查看?(闭嘴|关机|休眠|下班)列表$', fnc: 'listShutUp', permission: 'master' }, From 5e412057289601a7d363a0220f767f36520ea109 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Mon, 1 Jan 2024 20:01:01 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=E6=B8=85=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/chat.js | 1867 ++++++++++++++++++------------------ package.json | 1 - utils/SydneyAIClient.js | 15 +- utils/browser.js | 9 +- utils/openai-auth.js | 24 +- utils/slack/slackClient.js | 14 +- 6 files changed, 951 insertions(+), 979 deletions(-) diff --git a/apps/chat.js b/apps/chat.js index 87a0dd0..b7de8bf 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -1,8 +1,8 @@ import plugin from '../../../lib/plugins/plugin.js' +import common from '../../../lib/common/common.js' import _ from 'lodash' import { Config, defaultOpenAIAPI } from '../utils/config.js' import { v4 as uuid } from 'uuid' -import delay from 'delay' import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js' import SydneyAIClient from '../utils/SydneyAIClient.js' import { PoeClient } from '../utils/poe/index.js' @@ -10,28 +10,28 @@ import AzureTTS from '../utils/tts/microsoft-azure.js' import VoiceVoxTTS from '../utils/tts/voicevox.js' import Version from '../utils/version.js' import { - completeJSON, - extractContentFromFile, - formatDate, - formatDate2, - generateAudio, - getDefaultReplySetting, - getImageOcrText, - getImg, - getMasterQQ, - getMaxModelTokens, - getMessageById, - getOrDownloadFile, - getUin, - getUserData, - getUserReplySetting, - isCN, - isImage, - makeForwardMsg, - randomString, - render, - renderUrl, - upsertMessage + completeJSON, + extractContentFromFile, + formatDate, + formatDate2, + generateAudio, + getDefaultReplySetting, + getImageOcrText, + getImg, + getMasterQQ, + getMaxModelTokens, + getMessageById, + getOrDownloadFile, + getUin, + getUserData, + getUserReplySetting, + isCN, + isImage, + makeForwardMsg, + randomString, + render, + renderUrl, + upsertMessage } from '../utils/common.js' import { ChatGPTPuppeteer } from '../utils/browser.js' import { KeyvFile } from 'keyv-file' @@ -1041,7 +1041,7 @@ export class chatgpt extends plugin { logger.info(`问题超时已弹出,chatgpt队列前方还有${length}个问题。管理员可通过#清空队列来强制清除所有等待的问题。`) } } - await delay(1500) + await common.sleep(1500) } } } @@ -1525,948 +1525,919 @@ export class chatgpt extends plugin { } const userData = await getUserData(e.user_id) const useCast = userData.cast || {} - if (use === 'browser') { - { - return await this.chatgptBrowserBased(prompt, conversation) + if (use === 'bing') { + let throttledTokens = [] + let { + bingToken, + allThrottled + } = await getAvailableBingToken(conversation, throttledTokens) + let cookies + if (bingToken?.indexOf('=') > -1) { + cookies = bingToken } - } else if (use === 'bing') { - { - let throttledTokens = [] - let { - bingToken, - allThrottled - } = await getAvailableBingToken(conversation, throttledTokens) - let cookies - if (bingToken?.indexOf('=') > -1) { - cookies = bingToken - } - let bingAIClient - const cacheOptions = { - namespace: Config.toneStyle, - store: new KeyvFile({ filename: 'cache.json' }) - } - bingAIClient = new SydneyAIClient({ - userToken: bingToken, // "_U" cookie from bing.com - cookies, - debug: Config.debug, - cache: cacheOptions, - user: e.sender.user_id, - proxy: Config.proxy - }) - // Sydney不实现上下文传递,删除上下文索引 - delete conversation.clientId - delete conversation.invocationId - delete conversation.conversationSignature - let response - let reply = '' - let retry = 3 - let errorMessage = '' + let bingAIClient + const cacheOptions = { + namespace: Config.toneStyle, + store: new KeyvFile({ filename: 'cache.json' }) + } + bingAIClient = new SydneyAIClient({ + userToken: bingToken, // "_U" cookie from bing.com + cookies, + debug: Config.debug, + cache: cacheOptions, + user: e.sender.user_id, + proxy: Config.proxy + }) + // Sydney不实现上下文传递,删除上下文索引 + delete conversation.clientId + delete conversation.invocationId + delete conversation.conversationSignature + let response + let reply = '' + let retry = 3 + let errorMessage = '' - do { - try { - let opt = _.cloneDeep(conversation) || {} - opt.toneStyle = Config.toneStyle - // 如果当前没有开启对话或者当前是Sydney模式、Custom模式,则本次对话携带拓展资料 - let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`) - if (!c || Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') { - opt.context = useCast?.bing_resource || Config.sydneyContext - } - // 重新拿存储的token,因为可能之前有过期的被删了 - let abtrs = await getAvailableBingToken(conversation, throttledTokens) - bingToken = abtrs.bingToken - // eslint-disable-next-line no-unused-vars - allThrottled = abtrs.allThrottled - if (bingToken?.indexOf('=') > -1) { - cookies = bingToken - } - if (!bingAIClient.opts) { - bingAIClient.opts = {} - } - bingAIClient.opts.userToken = bingToken - bingAIClient.opts.cookies = cookies - // opt.messageType = allThrottled ? 'Chat' : 'SearchQuery' - if (Config.enableGroupContext && e.isGroup && typeof e.group.getMemberMap === 'function') { - try { - opt.groupId = e.group_id - opt.qq = e.sender.user_id - opt.nickname = e.sender.card - opt.groupName = e.group.name || e.group_name - opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname - let master = (await getMasterQQ())[0] - if (master && e.group) { - opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname - } - if (master && !e.group) { - opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname - } - opt.chats = await getChatHistoryGroup(e, Config.groupContextLength) - } catch (err) { - logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) - } - } - let toSummaryFileContent + do { + try { + let opt = _.cloneDeep(conversation) || {} + opt.toneStyle = Config.toneStyle + // 如果当前没有开启对话或者当前是Sydney模式、Custom模式,则本次对话携带拓展资料 + let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`) + if (!c || Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom') { + opt.context = useCast?.bing_resource || Config.sydneyContext + } + // 重新拿存储的token,因为可能之前有过期的被删了 + let abtrs = await getAvailableBingToken(conversation, throttledTokens) + bingToken = abtrs.bingToken + // eslint-disable-next-line no-unused-vars + allThrottled = abtrs.allThrottled + if (bingToken?.indexOf('=') > -1) { + cookies = bingToken + } + if (!bingAIClient.opts) { + bingAIClient.opts = {} + } + bingAIClient.opts.userToken = bingToken + bingAIClient.opts.cookies = cookies + // opt.messageType = allThrottled ? 'Chat' : 'SearchQuery' + if (Config.enableGroupContext && e.isGroup && typeof e.group.getMemberMap === 'function') { try { - if (e.source) { - let seq = e.isGroup ? e.source.seq : e.source.time - if (e.adapter === 'shamrock') { - seq = e.source.message_id - } - let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1) - let sourceMsg = msgs[msgs.length - 1] - let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file') - if (fileMsgElem) { - toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) - } + opt.groupId = e.group_id + opt.qq = e.sender.user_id + opt.nickname = e.sender.card + opt.groupName = e.group.name || e.group_name + opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname + let master = (await getMasterQQ())[0] + if (master && e.group) { + opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname } + if (master && !e.group) { + opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname + } + opt.chats = await getChatHistoryGroup(e, Config.groupContextLength) } catch (err) { - logger.warn('读取文件内容出错, 忽略文件内容', err) - } - opt.toSummaryFileContent = toSummaryFileContent - // 写入图片数据 - if (Config.sydneyImageRecognition) { - const image = await getImg(e) - opt.imageUrl = image ? image[0] : undefined - } - if (Config.enableGenerateContents) { - opt.onImageCreateRequest = prompt => { - logger.mark(`开始生成内容:${prompt}`) - if (Config.bingAPDraw) { - // 调用第三方API进行绘图 - let apDraw = new APTool() - apDraw.func({ - prompt - }, e) - } else { - let client = new BingDrawClient({ - baseUrl: Config.sydneyReverseProxy, - userToken: bingToken - }) - redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { - try { - client.getImages(prompt, e) - } catch (err) { - redis.del(`CHATGPT:DRAW:${e.sender.user_id}`) - e.reply('绘图失败:' + err) - } - }) - } - } - } - response = await bingAIClient.sendMessage(prompt, opt, (token) => { - reply += token - }) - if (response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim()) { - if (response.response === undefined) { - response.response = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim() - } - response.response = response.response.replace(/\[\^[0-9]+\^\]/g, (str) => { - return str.replace(/[/^]/g, '') - }) - // 有了新的引用属性 - // response.quote = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.replace(/\[\^[0-9]+\^\]/g, '').replace(response.response, '').split('\n') - } - response.suggestedResponses = response.details.suggestedResponses?.map(s => s.text).join('\n') - // 新引用属性读取数据 - if (response.details.sourceAttributions) { - response.quote = [] - for (let quote of response.details.sourceAttributions) { - response.quote.push({ - text: quote.providerDisplayName || '', - url: quote.seeMoreUrl, - imageLink: quote.imageLink || '' - }) - } - } - // 如果token曾经有异常,则清除异常 - let Tokens = JSON.parse((await redis.get('CHATGPT:BING_TOKENS')) || '[]') - const TokenIndex = Tokens?.findIndex(element => element.Token === abtrs.bingToken) - if (TokenIndex > 0 && Tokens[TokenIndex].exception) { - delete Tokens[TokenIndex].exception - await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(Tokens)) - } - errorMessage = '' - break - } catch (error) { - logger.error(error) - const message = error?.message || error?.data?.message || error || '出错了' - const { maxConv } = error - if (message && typeof message === 'string' && message.indexOf('CaptchaChallenge') > -1) { - if (bingToken) { - if (maxConv >= 20 && Config.bingCaptchaOneShotUrl) { - // maxConv为30说明token有效,可以通过解验证码码服务过码 - await e.reply('出现必应验证码,尝试解决中') - try { - let captchaResolveResult = await solveCaptchaOneShot(bingToken) - if (captchaResolveResult?.success) { - await e.reply('验证码已解决') - } else { - logger.error(captchaResolveResult) - errorMessage = message - await e.reply('验证码解决失败: ' + captchaResolveResult.error) - retry = 0 - } - } catch (err) { - logger.error(err) - await e.reply('验证码解决失败: ' + err) - retry = 0 - } - } else { - // 未登录用户maxConv目前为5或10,出验证码没救 - logger.warn(`token [${bingToken}] 出现必应验证码,请前往网页版或app手动解决`) - errorMessage = message - retry = 0 - } - } else { - retry = 0 - } - } else if (message && typeof message === 'string' && message.indexOf('限流') > -1) { - throttledTokens.push(bingToken) - let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) - const badBingToken = bingTokens.findIndex(element => element.Token === bingToken) - const now = new Date() - const hours = now.getHours() - now.setHours(hours + 6) - bingTokens[badBingToken].State = '受限' - bingTokens[badBingToken].DisactivationTime = now - await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens)) - // 不减次数 - } else if (message && typeof message === 'string' && message.indexOf('UnauthorizedRequest') > -1) { - // token过期了 - let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) - const badBingToken = bingTokens.findIndex(element => element.Token === bingToken) - if (badBingToken > 0) { - // 可能是微软抽风,给三次机会 - if (bingTokens[badBingToken]?.exception) { - if (bingTokens[badBingToken].exception <= 3) { - bingTokens[badBingToken].exception += 1 - } else { - bingTokens[badBingToken].exception = 0 - bingTokens[badBingToken].State = '过期' - } - } else { - bingTokens[badBingToken].exception = 1 - } - await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens)) - } else { - retry = retry - 1 - } - errorMessage = 'UnauthorizedRequest:必应token不正确或已过期' - // logger.warn(`token${bingToken}疑似不存在或已过期,再试试`) - // retry = retry - 1 - } else { - retry-- - errorMessage = message === 'Timed out waiting for response. Try enabling debug mode to see more information.' ? (reply ? `${reply}\n不行了,我的大脑过载了,处理不过来了!` : '必应的小脑瓜不好使了,不知道怎么回答!') : message - } - } - } while (retry > 0) - if (errorMessage) { - response = response || {} - if (errorMessage.includes('CaptchaChallenge')) { - if (bingToken) { - errorMessage = '出现验证码,请使用当前账户前往https://www.bing.com/chat或Edge侧边栏或移动端APP手动解除验证码' - } else { - errorMessage = '未配置必应账户,请绑定必应账户再使用必应模式' - } - } - return { - text: errorMessage, - error: true - } - } else if (response?.response) { - return { - text: response?.response, - quote: response?.quote, - suggestedResponses: response.suggestedResponses, - conversationId: response.conversationId, - clientId: response.clientId, - invocationId: response.invocationId, - conversationSignature: response.conversationSignature, - parentMessageId: response.apology ? conversation.parentMessageId : response.messageId, - bingToken - } - } else { - logger.debug('no message') - return { - noMsg: true - } - } - } - } else if (use === 'api3') { - { - // official without cloudflare - let accessToken = await redis.get('CHATGPT:TOKEN') - if (!accessToken) { - throw new Error('未绑定ChatGPT AccessToken,请使用#chatgpt设置token命令绑定token') - } - this.chatGPTApi = new OfficialChatGPTClient({ - accessToken, - apiReverseUrl: Config.api, - timeoutMs: 120000 - }) - let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) - // 更新最后一条prompt - await redis.set(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${sendMessageResult.conversationId}`, prompt) - // 更新最后一条messageId - await redis.set(`CHATGPT:CONVERSATION_LAST_MESSAGE_ID:${sendMessageResult.conversationId}`, sendMessageResult.id) - await redis.set(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`, sendMessageResult.conversationId) - if (!conversation.conversationId) { - // 如果是对话的创建者 - await redis.set(`CHATGPT:CONVERSATION_CREATER_ID:${sendMessageResult.conversationId}`, e.sender.user_id) - await redis.set(`CHATGPT:CONVERSATION_CREATER_NICK_NAME:${sendMessageResult.conversationId}`, e.sender.card) - } - return sendMessageResult - } - } else if (use === 'chatglm') { - { - const cacheOptions = { - namespace: 'chatglm_6b', - store: new KeyvFile({ filename: 'cache.json' }) - } - this.chatGPTApi = new ChatGLMClient({ - user: e.sender.user_id, - cache: cacheOptions - }) - let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) - return sendMessageResult - } - } else if (use === 'poe') { - { - const cookie = await redis.get('CHATGPT:POE_TOKEN') - if (!cookie) { - throw new Error('未绑定Poe Cookie,请使用#chatgpt设置Poe token命令绑定cookie') - } - let client = new PoeClient({ - quora_cookie: cookie, - proxy: Config.proxy - }) - await client.setCredentials() - await client.getChatId() - let ai = 'a2' // todo - await client.sendMsg(ai, prompt) - const response = await client.getResponse(ai) - return { - text: response.data - } - } - } else if (use === 'claude') { - { - let client = new SlackClaudeClient({ - slackUserToken: Config.slackUserToken, - slackChannelId: Config.slackChannelId - }) - let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`) - if (!conversationId) { - // 如果是新对话 - if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) { - // 先发送设定 - let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset) - let emotion = await AzureTTS.getEmotionPrompt(e) - if (emotion) { - prompt = prompt + '\n' + emotion - } - await client.sendMessage(prompt, e) - logger.info('claudeFirst:', prompt) - } - } - let text = await client.sendMessage(prompt, e) - return { - text - } - } - } else if (use === 'claude2') { - { - let { conversationId } = conversation - let client = new ClaudeAIClient({ - organizationId: Config.claudeAIOrganizationId, - sessionKey: Config.claudeAISessionKey, - debug: Config.debug, - proxy: Config.proxy - }) - let toSummaryFileContent - try { - if (e.source) { - let msgs = e.isGroup ? await e.group.getChatHistory(e.source.seq, 1) : await e.friend.getChatHistory(e.source.time, 1) - let sourceMsg = msgs[0] - let fileMsgElem = sourceMsg.message.find(msg => msg.type === 'file') - if (fileMsgElem) { - toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) - } - } - } catch (err) { - logger.warn('读取文件内容出错, 忽略文件内容', err) - } - - let attachments = [] - if (toSummaryFileContent?.content) { - attachments.push({ - extracted_content: toSummaryFileContent.content, - file_name: toSummaryFileContent.name, - file_type: 'pdf', - file_size: 200312, - totalPages: 20 - }) - logger.info(toSummaryFileContent.content) - } - if (conversationId) { - return await client.sendMessage(prompt, conversationId, attachments) - } else { - let conv = await client.createConversation() - return await client.sendMessage(prompt, conv.uuid, attachments) - } - } - } else if (use === 'xh') { - { - const cacheOptions = { - namespace: 'xh', - store: new KeyvFile({ filename: 'cache.json' }) - } - const ssoSessionId = Config.xinghuoToken - if (!ssoSessionId) { - // throw new Error('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)') - logger.warn('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)') - } - let client = new XinghuoClient({ - ssoSessionId, - cache: cacheOptions - }) - // 获取图片资源 - const image = await getImg(e) - let response = await client.sendMessage(prompt, { - e, - chatId: conversation?.conversationId, - image: image ? image[0] : undefined - }) - return response - } - } else if (use === 'azure') { - { - let azureModel - try { - azureModel = await import('@azure/openai') - } catch (error) { - throw new Error('未安装@azure/openai包,请执行pnpm install @azure/openai安装') - } - let OpenAIClient = azureModel.OpenAIClient - let AzureKeyCredential = azureModel.AzureKeyCredential - let msg = conversation.messages - let content = { - role: 'user', - content: prompt - } - msg.push(content) - const client = new OpenAIClient(Config.azureUrl, new AzureKeyCredential(Config.azApiKey)) - const deploymentName = Config.azureDeploymentName - const { choices } = await client.getChatCompletions(deploymentName, msg) - let completion = choices[0].message - return { - text: completion.content, - message: completion - } - } - } else if (use === 'qwen') { - { - let completionParams = { - parameters: { - top_p: Config.qwenTopP || 0.5, - top_k: Config.qwenTopK || 50, - seed: Config.qwenSeed > 0 ? Config.qwenSeed : Math.floor(Math.random() * 114514), - temperature: Config.qwenTemperature || 1, - enable_search: !!Config.qwenEnableSearch - } - } - if (Config.qwenModel) { - completionParams.model = Config.qwenModel - } - const currentDate = new Date().toISOString().split('T')[0] - - async function um(message) { - return await upsertMessage(message, 'QWEN') - } - - async function gm(id) { - return await getMessageById(id, 'QWEN') - } - - let opts = { - apiKey: Config.qwenApiKey, - debug: false, - upsertMessage: um, - getMessageById: gm, - systemMessage: `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix} - Current date: ${currentDate}`, - completionParams, - assistantLabel: Config.assistantLabel, - fetch: newFetch - } - this.qwenApi = new QwenApi(opts) - let option = { - timeoutMs: 600000, - completionParams - } - if (conversation) { - if (!conversation.conversationId) { - conversation.conversationId = uuid() - } - option = Object.assign(option, conversation) - } - let msg - try { - msg = await this.qwenApi.sendMessage(prompt, option) - } catch (err) { - logger.error(err) - throw new Error(err) - } - return msg - } - } else if (use === 'bard') { - { - // 处理cookie - const matchesPSID = /__Secure-1PSID=([^;]+)/.exec(Config.bardPsid) - const matchesPSIDTS = /__Secure-1PSIDTS=([^;]+)/.exec(Config.bardPsid) - const cookie = { - '__Secure-1PSID': matchesPSID[1], - '__Secure-1PSIDTS': matchesPSIDTS[1] - } - if (!matchesPSID[1] || !matchesPSIDTS[1]) { - throw new Error('未绑定bard') - } - // 处理图片 - const image = await getImg(e) - let imageBuff - if (image) { - try { - let imgResponse = await fetch(image[0]) - if (imgResponse.ok) { - imageBuff = await imgResponse.arrayBuffer() - } - } catch (error) { - logger.warn(`错误的图片链接${image[0]}`) - } - } - // 发送数据 - let bot = new Bard(cookie, { - fetch, - bardURL: Config.bardForceUseReverse ? Config.bardReverseProxy : 'https://bard.google.com' - }) - let chat = await bot.createChat(conversation?.conversationId - ? { - conversationID: conversation.conversationId, - responseID: conversation.parentMessageId, - choiceID: conversation.clientId, - _reqID: conversation.invocationId - } - : {}) - let response = await chat.ask(prompt, { - image: imageBuff, - format: Bard.JSON - }) - return { - conversationId: response.ids.conversationID, - responseID: response.ids.responseID, - choiceID: response.ids.choiceID, - _reqID: response.ids._reqID, - text: response.content, - images: response.images - } - } - } else if (use === 'gemini') { - { - let client = new CustomGoogleGeminiClient({ - e, - userId: e.sender.user_id, - key: Config.geminiKey, - model: Config.geminiModel, - baseUrl: Config.geminiBaseUrl, - debug: Config.debug - }) - let option = { - stream: false, - onProgress: (data) => { - if (Config.debug) { - logger.info(data) - } - }, - parentMessageId: conversation.parentMessageId, - conversationId: conversation.conversationId - } - if (Config.geminiModel.includes('vision')) { - const image = await getImg(e) - let imageUrl = image ? image[0] : undefined - if (imageUrl) { - let md5 = imageUrl.split(/[/-]/).find(s => s.length === 32)?.toUpperCase() - let imageLoc = await getOrDownloadFile(`ocr/${md5}.png`, imageUrl) - let outputLoc = imageLoc.replace(`${md5}.png`, `${md5}_512.png`) - await resizeAndCropImage(imageLoc, outputLoc, 512) - let buffer = fs.readFileSync(outputLoc) - option.image = buffer.toString('base64') - } - } - if (Config.smartMode) { - /** - * @type {AbstractTool[]} - */ - let tools = [ - new QueryStarRailTool(), - new WebsiteTool(), - new SendPictureTool(), - new SendVideoTool(), - // new ImageCaptionTool(), - new SearchVideoTool(), - new SendAvatarTool(), - new SerpImageTool(), - new SearchMusicTool(), - new SendMusicTool(), - // new SerpIkechan8370Tool(), - // new SerpTool(), - new SendAudioMessageTool(), - // new ProcessPictureTool(), - new APTool(), - // new HandleMessageMsgTool(), - new SendMessageToSpecificGroupOrUserTool(), - // new SendDiceTool(), - new QueryGenshinTool() - ] - if (Config.amapKey) { - tools.push(new WeatherTool()) - } - if (e.isGroup) { - tools.push(new QueryUserinfoTool()) - // let self = e.group.pickMember(e.self_id) - if (e.group.is_admin || e.group.is_owner) { - tools.push(new EditCardTool()) - tools.push(new JinyanTool()) - tools.push(new KickOutTool()) - } - if (e.group.is_owner) { - tools.push(new SetTitleTool()) - } - } - switch (Config.serpSource) { - case 'ikechan8370': { - tools.push(new SerpIkechan8370Tool()) - break - } - case 'azure': { - if (!Config.azSerpKey) { - logger.warn('未配置bing搜索密钥,转为使用ikechan8370搜索源') - tools.push(new SerpIkechan8370Tool()) - } else { - tools.push(new SerpTool()) - } - break - } - default: { - tools.push(new SerpIkechan8370Tool()) - } - } - client.addTools(tools) - } - let system = Config.geminiPrompt - if (Config.enableGroupContext && e.isGroup) { - let chats = await getChatHistoryGroup(e, Config.groupContextLength) - const namePlaceholder = '[name]' - const defaultBotName = 'GeminiPro' - const groupContextTip = Config.groupContextTip - let botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname - system = system.replaceAll(namePlaceholder, botName || defaultBotName) + - ((Config.enableGroupContext && e.group_id) ? groupContextTip : '') - system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${e.sender.card || e.sender.nickname}(${e.sender.user_id}).` - system += `the group name is ${e.group.name || e.group_name}, group id is ${e.group_id}.` - system += `Your nickname is ${botName} in the group,` - if (chats) { - system += 'There is the conversation history in the group, you must chat according to the conversation history context"' - system += chats - .map(chat => { - let sender = chat.sender || {} - return `【${sender.card || sender.nickname}】(qq:${sender.user_id}, ${roleMap[sender.role] || 'normal user'},${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time:${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}` - }) - .join('\n') - } - } - option.system = system - return await client.sendMessage(prompt, option) - } - } else { - { - // openai api - let completionParams = {} - if (Config.model) { - completionParams.model = Config.model - } - const currentDate = new Date().toISOString().split('T')[0] - let promptPrefix = `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix} - Current date: ${currentDate}` - let maxModelTokens = getMaxModelTokens(completionParams.model) - let system = promptPrefix - if (maxModelTokens >= 16000 && Config.enableGroupContext) { - try { - let opt = {} - opt.groupId = e.group_id - opt.qq = e.sender.user_id - opt.nickname = e.sender.card - opt.groupName = e.group.name || e.group_name - opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname - let master = (await getMasterQQ())[0] - if (master && e.group) { - opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname - } - if (master && !e.group) { - opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname - } - let chats = await getChatHistoryGroup(e, Config.groupContextLength) - opt.chats = chats - const namePlaceholder = '[name]' - const defaultBotName = 'ChatGPT' - const groupContextTip = Config.groupContextTip - system = system.replaceAll(namePlaceholder, opt.botName || defaultBotName) + - ((Config.enableGroupContext && opt.groupId) ? groupContextTip : '') - system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${opt.nickname}(${opt.qq})。` - system += `the group name is ${opt.groupName}, group id is ${opt.groupId}。` - if (opt.botName) { - system += `Your nickname is ${opt.botName} in the group,` - } - if (chats) { - system += 'There is the conversation history in the group, you must chat according to the conversation history context"' - system += chats - .map(chat => { - let sender = chat.sender || {} - // if (sender.user_id === e.bot.uin && chat.raw_message.startsWith('建议的回复')) { - if (chat.raw_message.startsWith('建议的回复')) { - // 建议的回复太容易污染设定导致对话太固定跑偏了 - return '' - } - return `【${sender.card || sender.nickname}】(qq:${sender.user_id}, ${roleMap[sender.role] || 'normal user'},${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time:${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}` - }) - .join('\n') - } - } catch (err) { - if (e.isGroup) { logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) } } - // logger.info(system) - } - let opts = { - apiBaseUrl: Config.openAiBaseUrl, - apiKey: Config.apiKey, - debug: false, - upsertMessage, - getMessageById, - systemMessage: system, - completionParams, - assistantLabel: Config.assistantLabel, - fetch: newFetch, - maxModelTokens - } - let openAIAccessible = (Config.proxy || !(await isCN())) // 配了代理或者服务器在国外,默认认为不需要反代 - if (opts.apiBaseUrl !== defaultOpenAIAPI && openAIAccessible && !Config.openAiForceUseReverse) { - // 如果配了proxy(或者不在国内),而且有反代,但是没开启强制反代,将baseurl删掉 - delete opts.apiBaseUrl - } - this.chatGPTApi = new ChatGPTAPI(opts) - let option = { - timeoutMs: 600000, - completionParams, - stream: true, - onProgress: (data) => { - if (Config.debug) { - logger.info(data?.text || data.functionCall || data) - } - } - // systemMessage: promptPrefix - } - option.systemMessage = system - if (conversation) { - if (!conversation.conversationId) { - conversation.conversationId = uuid() - } - option = Object.assign(option, conversation) - } - if (Config.smartMode) { - let isAdmin = e.sender.role === 'admin' || e.sender.role === 'owner' - let sender = e.sender.user_id - let serpTool - switch (Config.serpSource) { - case 'ikechan8370': { - serpTool = new SerpIkechan8370Tool() - break - } - case 'azure': { - if (!Config.azSerpKey) { - logger.warn('未配置bing搜索密钥,转为使用ikechan8370搜索源') - serpTool = new SerpIkechan8370Tool() - } else { - serpTool = new SerpTool() - } - break - } - default: { - serpTool = new SerpIkechan8370Tool() - } - } - let fullTools = [ - new EditCardTool(), - new QueryStarRailTool(), - new WebsiteTool(), - new JinyanTool(), - new KickOutTool(), - new WeatherTool(), - new SendPictureTool(), - new SendVideoTool(), - new ImageCaptionTool(), - new SearchVideoTool(), - new SendAvatarTool(), - new SerpImageTool(), - new SearchMusicTool(), - new SendMusicTool(), - new SerpIkechan8370Tool(), - new SerpTool(), - new SendAudioMessageTool(), - new ProcessPictureTool(), - new APTool(), - new HandleMessageMsgTool(), - new QueryUserinfoTool(), - new EliMusicTool(), - new EliMovieTool(), - new SendMessageToSpecificGroupOrUserTool(), - new SendDiceTool(), - new QueryGenshinTool(), - new SetTitleTool() - ] - // todo 3.0再重构tool的插拔和管理 - let tools = [ - new SendAvatarTool(), - new SendDiceTool(), - new SendMessageToSpecificGroupOrUserTool(), - // new EditCardTool(), - new QueryStarRailTool(), - new QueryGenshinTool(), - new ProcessPictureTool(), - new WebsiteTool(), - // new JinyanTool(), - // new KickOutTool(), - new WeatherTool(), - new SendPictureTool(), - new SendAudioMessageTool(), - new APTool(), - // new HandleMessageMsgTool(), - serpTool, - new QueryUserinfoTool() - ] + let toSummaryFileContent try { - await import('../../avocado-plugin/apps/avocado.js') - tools.push(...[new EliMusicTool(), new EliMovieTool()]) + if (e.source) { + let seq = e.isGroup ? e.source.seq : e.source.time + if (e.adapter === 'shamrock') { + seq = e.source.message_id + } + let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1) + let sourceMsg = msgs[msgs.length - 1] + let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file') + if (fileMsgElem) { + toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) + } + } } catch (err) { - tools.push(...[new SendMusicTool(), new SearchMusicTool()]) - logger.mark(logger.green('【ChatGPT-Plugin】插件avocado-plugin未安装') + ',安装后可查看最近热映电影与体验可玩性更高的点歌工具。\n可前往 https://github.com/Qz-Sean/avocado-plugin 获取') + logger.warn('读取文件内容出错, 忽略文件内容', err) } - if (e.isGroup) { - let botInfo = await e.bot.getGroupMemberInfo(e.group_id, getUin(e), true) - if (botInfo.role !== 'member') { - // 管理员才给这些工具 - tools.push(...[new EditCardTool(), new JinyanTool(), new KickOutTool(), new HandleMessageMsgTool(), new SetTitleTool()]) - // 用于撤回和加精的id - if (e.source?.seq) { - let source = (await e.group.getChatHistory(e.source?.seq, 1)).pop() - option.systemMessage += `\nthe last message is replying to ${source.message_id}"\n` + opt.toSummaryFileContent = toSummaryFileContent + // 写入图片数据 + if (Config.sydneyImageRecognition) { + const image = await getImg(e) + opt.imageUrl = image ? image[0] : undefined + } + if (Config.enableGenerateContents) { + opt.onImageCreateRequest = prompt => { + logger.mark(`开始生成内容:${prompt}`) + if (Config.bingAPDraw) { + // 调用第三方API进行绘图 + let apDraw = new APTool() + apDraw.func({ + prompt + }, e) } else { - option.systemMessage += `\nthe last message id is ${e.message_id}. ` + let client = new BingDrawClient({ + baseUrl: Config.sydneyReverseProxy, + userToken: bingToken + }) + redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { + try { + client.getImages(prompt, e) + } catch (err) { + redis.del(`CHATGPT:DRAW:${e.sender.user_id}`) + e.reply('绘图失败:' + err) + } + }) } } } - let img = await getImg(e) - if (img?.length > 0 && Config.extraUrl) { - tools.push(new ImageCaptionTool()) - tools.push(new ProcessPictureTool()) - prompt += `\nthe url of the picture(s) above: ${img.join(', ')}` + response = await bingAIClient.sendMessage(prompt, opt, (token) => { + reply += token + }) + if (response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim()) { + if (response.response === undefined) { + response.response = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim() + } + response.response = response.response.replace(/\[\^[0-9]+\^\]/g, (str) => { + return str.replace(/[/^]/g, '') + }) + // 有了新的引用属性 + // response.quote = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.replace(/\[\^[0-9]+\^\]/g, '').replace(response.response, '').split('\n') + } + response.suggestedResponses = response.details.suggestedResponses?.map(s => s.text).join('\n') + // 新引用属性读取数据 + if (response.details.sourceAttributions) { + response.quote = [] + for (let quote of response.details.sourceAttributions) { + response.quote.push({ + text: quote.providerDisplayName || '', + url: quote.seeMoreUrl, + imageLink: quote.imageLink || '' + }) + } + } + // 如果token曾经有异常,则清除异常 + let Tokens = JSON.parse((await redis.get('CHATGPT:BING_TOKENS')) || '[]') + const TokenIndex = Tokens?.findIndex(element => element.Token === abtrs.bingToken) + if (TokenIndex > 0 && Tokens[TokenIndex].exception) { + delete Tokens[TokenIndex].exception + await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(Tokens)) + } + errorMessage = '' + break + } catch (error) { + logger.error(error) + const message = error?.message || error?.data?.message || error || '出错了' + const { maxConv } = error + if (message && typeof message === 'string' && message.indexOf('CaptchaChallenge') > -1) { + if (bingToken) { + if (maxConv >= 20 && Config.bingCaptchaOneShotUrl) { + // maxConv为30说明token有效,可以通过解验证码码服务过码 + await e.reply('出现必应验证码,尝试解决中') + try { + let captchaResolveResult = await solveCaptchaOneShot(bingToken) + if (captchaResolveResult?.success) { + await e.reply('验证码已解决') + } else { + logger.error(captchaResolveResult) + errorMessage = message + await e.reply('验证码解决失败: ' + captchaResolveResult.error) + retry = 0 + } + } catch (err) { + logger.error(err) + await e.reply('验证码解决失败: ' + err) + retry = 0 + } + } else { + // 未登录用户maxConv目前为5或10,出验证码是ip或MUID问题 + logger.warn(`token [${bingToken}] 出现必应验证码,请前往网页版或app手动解决`) + errorMessage = message + retry = 0 + } + } else { + retry = 0 + } + } else if (message && typeof message === 'string' && message.indexOf('限流') > -1) { + throttledTokens.push(bingToken) + let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) + const badBingToken = bingTokens.findIndex(element => element.Token === bingToken) + const now = new Date() + const hours = now.getHours() + now.setHours(hours + 6) + bingTokens[badBingToken].State = '受限' + bingTokens[badBingToken].DisactivationTime = now + await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens)) + // 不减次数 + } else if (message && typeof message === 'string' && message.indexOf('UnauthorizedRequest') > -1) { + // token过期了 + let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) + const badBingToken = bingTokens.findIndex(element => element.Token === bingToken) + if (badBingToken > 0) { + // 可能是微软抽风,给三次机会 + if (bingTokens[badBingToken]?.exception) { + if (bingTokens[badBingToken].exception <= 3) { + bingTokens[badBingToken].exception += 1 + } else { + bingTokens[badBingToken].exception = 0 + bingTokens[badBingToken].State = '过期' + } + } else { + bingTokens[badBingToken].exception = 1 + } + await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens)) + } else { + retry = retry - 1 + } + errorMessage = 'UnauthorizedRequest:必应token不正确或已过期' + // logger.warn(`token${bingToken}疑似不存在或已过期,再试试`) + // retry = retry - 1 } else { - tools.push(new SerpImageTool()) - tools.push(...[new SearchVideoTool(), - new SendVideoTool()]) + retry-- + errorMessage = message === 'Timed out waiting for response. Try enabling debug mode to see more information.' ? (reply ? `${reply}\n不行了,我的大脑过载了,处理不过来了!` : '必应的小脑瓜不好使了,不知道怎么回答!') : message } - let funcMap = {} - let fullFuncMap = {} - tools.forEach(tool => { - funcMap[tool.name] = { - exec: tool.func, - function: tool.function() - } - }) - fullTools.forEach(tool => { - fullFuncMap[tool.name] = { - exec: tool.func, - function: tool.function() - } - }) - if (!option.completionParams) { - option.completionParams = {} - } - option.completionParams.functions = Object.keys(funcMap).map(k => funcMap[k].function) - let msg - try { - msg = await this.chatGPTApi.sendMessage(prompt, option) - logger.info(msg) - while (msg.functionCall) { - if (msg.text) { - await e.reply(msg.text.replace('\n\n\n', '\n')) - } - let { - name, - arguments: args - } = msg.functionCall - args = JSON.parse(args) - // 感觉换成targetGroupIdOrUserQQNumber这种表意比较清楚的变量名,效果会好一丢丢 - if (!args.groupId) { - args.groupId = e.group_id + '' || e.sender.user_id + '' - } - try { - parseInt(args.groupId) - } catch (err) { - args.groupId = e.group_id + '' || e.sender.user_id + '' - } - let functionResult = await fullFuncMap[name.trim()].exec(Object.assign({ - isAdmin, - sender - }, args), e) - logger.mark(`function ${name} execution result: ${functionResult}`) - option.parentMessageId = msg.id - option.name = name - // 不然普通用户可能会被openai限速 - await delay(300) - msg = await this.chatGPTApi.sendMessage(functionResult, option, 'function') - logger.info(msg) - } - } catch (err) { - if (err.message?.indexOf('context_length_exceeded') > 0) { - logger.warn(err) - await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) - await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`) - await e.reply('字数超限啦,将为您自动结束本次对话。') - return null - } else { - logger.error(err) - throw new Error(err) - } - } - return msg - } else { - let msg - try { - msg = await this.chatGPTApi.sendMessage(prompt, option) - } catch (err) { - if (err.message?.indexOf('context_length_exceeded') > 0) { - logger.warn(err) - await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) - await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`) - await e.reply('字数超限啦,将为您自动结束本次对话。') - return null - } else { - logger.error(err) - throw new Error(err) - } - } - return msg } + } while (retry > 0) + if (errorMessage) { + if (errorMessage.includes('CaptchaChallenge')) { + if (bingToken) { + errorMessage = '出现验证码,请使用当前账户前往https://www.bing.com/chat或Edge侧边栏或移动端APP手动解除验证码' + } else { + errorMessage = '未配置必应账户,建议绑定必应账户再使用必应模式' + } + } + return { + text: errorMessage, + error: true + } + } else if (response?.response) { + return { + text: response?.response, + quote: response?.quote, + suggestedResponses: response.suggestedResponses, + conversationId: response.conversationId, + clientId: response.clientId, + invocationId: response.invocationId, + conversationSignature: response.conversationSignature, + parentMessageId: response.apology ? conversation.parentMessageId : response.messageId, + bingToken + } + } else { + logger.debug('no message') + return { + noMsg: true + } + } + } else if (use === 'api3') { + // official without cloudflare + let accessToken = await redis.get('CHATGPT:TOKEN') + if (!accessToken) { + throw new Error('未绑定ChatGPT AccessToken,请使用#chatgpt设置token命令绑定token') + } + this.chatGPTApi = new OfficialChatGPTClient({ + accessToken, + apiReverseUrl: Config.api, + timeoutMs: 120000 + }) + let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) + // 更新最后一条prompt + await redis.set(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${sendMessageResult.conversationId}`, prompt) + // 更新最后一条messageId + await redis.set(`CHATGPT:CONVERSATION_LAST_MESSAGE_ID:${sendMessageResult.conversationId}`, sendMessageResult.id) + await redis.set(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`, sendMessageResult.conversationId) + if (!conversation.conversationId) { + // 如果是对话的创建者 + await redis.set(`CHATGPT:CONVERSATION_CREATER_ID:${sendMessageResult.conversationId}`, e.sender.user_id) + await redis.set(`CHATGPT:CONVERSATION_CREATER_NICK_NAME:${sendMessageResult.conversationId}`, e.sender.card) + } + return sendMessageResult + } else if (use === 'chatglm') { + const cacheOptions = { + namespace: 'chatglm_6b', + store: new KeyvFile({ filename: 'cache.json' }) + } + this.chatGPTApi = new ChatGLMClient({ + user: e.sender.user_id, + cache: cacheOptions + }) + let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) + return sendMessageResult + } else if (use === 'poe') { + const cookie = await redis.get('CHATGPT:POE_TOKEN') + if (!cookie) { + throw new Error('未绑定Poe Cookie,请使用#chatgpt设置Poe token命令绑定cookie') + } + let client = new PoeClient({ + quora_cookie: cookie, + proxy: Config.proxy + }) + await client.setCredentials() + await client.getChatId() + let ai = 'a2' // todo + await client.sendMsg(ai, prompt) + const response = await client.getResponse(ai) + return { + text: response.data + } + } else if (use === 'claude') { + let client = new SlackClaudeClient({ + slackUserToken: Config.slackUserToken, + slackChannelId: Config.slackChannelId + }) + let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`) + if (!conversationId) { + // 如果是新对话 + if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) { + // 先发送设定 + let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset) + let emotion = await AzureTTS.getEmotionPrompt(e) + if (emotion) { + prompt = prompt + '\n' + emotion + } + await client.sendMessage(prompt, e) + logger.info('claudeFirst:', prompt) + } + } + let text = await client.sendMessage(prompt, e) + return { + text + } + } else if (use === 'claude2') { + let { conversationId } = conversation + let client = new ClaudeAIClient({ + organizationId: Config.claudeAIOrganizationId, + sessionKey: Config.claudeAISessionKey, + debug: Config.debug, + proxy: Config.proxy + }) + let toSummaryFileContent + try { + if (e.source) { + let msgs = e.isGroup ? await e.group.getChatHistory(e.source.seq, 1) : await e.friend.getChatHistory(e.source.time, 1) + let sourceMsg = msgs[0] + let fileMsgElem = sourceMsg.message.find(msg => msg.type === 'file') + if (fileMsgElem) { + toSummaryFileContent = await extractContentFromFile(fileMsgElem, e) + } + } + } catch (err) { + logger.warn('读取文件内容出错, 忽略文件内容', err) + } + + let attachments = [] + if (toSummaryFileContent?.content) { + attachments.push({ + extracted_content: toSummaryFileContent.content, + file_name: toSummaryFileContent.name, + file_type: 'pdf', + file_size: 200312, + totalPages: 20 + }) + logger.info(toSummaryFileContent.content) + } + if (conversationId) { + return await client.sendMessage(prompt, conversationId, attachments) + } else { + let conv = await client.createConversation() + return await client.sendMessage(prompt, conv.uuid, attachments) + } + } else if (use === 'xh') { + const cacheOptions = { + namespace: 'xh', + store: new KeyvFile({ filename: 'cache.json' }) + } + const ssoSessionId = Config.xinghuoToken + if (!ssoSessionId) { + // throw new Error('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)') + logger.warn('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)') + } + let client = new XinghuoClient({ + ssoSessionId, + cache: cacheOptions + }) + // 获取图片资源 + const image = await getImg(e) + let response = await client.sendMessage(prompt, { + e, + chatId: conversation?.conversationId, + image: image ? image[0] : undefined + }) + return response + } else if (use === 'azure') { + let azureModel + try { + azureModel = await import('@azure/openai') + } catch (error) { + throw new Error('未安装@azure/openai包,请执行pnpm install @azure/openai安装') + } + let OpenAIClient = azureModel.OpenAIClient + let AzureKeyCredential = azureModel.AzureKeyCredential + let msg = conversation.messages + let content = { + role: 'user', + content: prompt + } + msg.push(content) + const client = new OpenAIClient(Config.azureUrl, new AzureKeyCredential(Config.azApiKey)) + const deploymentName = Config.azureDeploymentName + const { choices } = await client.getChatCompletions(deploymentName, msg) + let completion = choices[0].message + return { + text: completion.content, + message: completion + } + } else if (use === 'qwen') { + let completionParams = { + parameters: { + top_p: Config.qwenTopP || 0.5, + top_k: Config.qwenTopK || 50, + seed: Config.qwenSeed > 0 ? Config.qwenSeed : Math.floor(Math.random() * 114514), + temperature: Config.qwenTemperature || 1, + enable_search: !!Config.qwenEnableSearch + } + } + if (Config.qwenModel) { + completionParams.model = Config.qwenModel + } + const currentDate = new Date().toISOString().split('T')[0] + + async function um (message) { + return await upsertMessage(message, 'QWEN') + } + + async function gm (id) { + return await getMessageById(id, 'QWEN') + } + + let opts = { + apiKey: Config.qwenApiKey, + debug: false, + upsertMessage: um, + getMessageById: gm, + systemMessage: `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix} + Current date: ${currentDate}`, + completionParams, + assistantLabel: Config.assistantLabel, + fetch: newFetch + } + this.qwenApi = new QwenApi(opts) + let option = { + timeoutMs: 600000, + completionParams + } + if (conversation) { + if (!conversation.conversationId) { + conversation.conversationId = uuid() + } + option = Object.assign(option, conversation) + } + let msg + try { + msg = await this.qwenApi.sendMessage(prompt, option) + } catch (err) { + logger.error(err) + throw new Error(err) + } + return msg + } else if (use === 'bard') { + // 处理cookie + const matchesPSID = /__Secure-1PSID=([^;]+)/.exec(Config.bardPsid) + const matchesPSIDTS = /__Secure-1PSIDTS=([^;]+)/.exec(Config.bardPsid) + const cookie = { + '__Secure-1PSID': matchesPSID[1], + '__Secure-1PSIDTS': matchesPSIDTS[1] + } + if (!matchesPSID[1] || !matchesPSIDTS[1]) { + throw new Error('未绑定bard') + } + // 处理图片 + const image = await getImg(e) + let imageBuff + if (image) { + try { + let imgResponse = await fetch(image[0]) + if (imgResponse.ok) { + imageBuff = await imgResponse.arrayBuffer() + } + } catch (error) { + logger.warn(`错误的图片链接${image[0]}`) + } + } + // 发送数据 + let bot = new Bard(cookie, { + fetch, + bardURL: Config.bardForceUseReverse ? Config.bardReverseProxy : 'https://bard.google.com' + }) + let chat = await bot.createChat(conversation?.conversationId + ? { + conversationID: conversation.conversationId, + responseID: conversation.parentMessageId, + choiceID: conversation.clientId, + _reqID: conversation.invocationId + } + : {}) + let response = await chat.ask(prompt, { + image: imageBuff, + format: Bard.JSON + }) + return { + conversationId: response.ids.conversationID, + responseID: response.ids.responseID, + choiceID: response.ids.choiceID, + _reqID: response.ids._reqID, + text: response.content, + images: response.images + } + } else if (use === 'gemini') { + let client = new CustomGoogleGeminiClient({ + e, + userId: e.sender.user_id, + key: Config.geminiKey, + model: Config.geminiModel, + baseUrl: Config.geminiBaseUrl, + debug: Config.debug + }) + let option = { + stream: false, + onProgress: (data) => { + if (Config.debug) { + logger.info(data) + } + }, + parentMessageId: conversation.parentMessageId, + conversationId: conversation.conversationId + } + if (Config.geminiModel.includes('vision')) { + const image = await getImg(e) + let imageUrl = image ? image[0] : undefined + if (imageUrl) { + let md5 = imageUrl.split(/[/-]/).find(s => s.length === 32)?.toUpperCase() + let imageLoc = await getOrDownloadFile(`ocr/${md5}.png`, imageUrl) + let outputLoc = imageLoc.replace(`${md5}.png`, `${md5}_512.png`) + await resizeAndCropImage(imageLoc, outputLoc, 512) + let buffer = fs.readFileSync(outputLoc) + option.image = buffer.toString('base64') + } + } + if (Config.smartMode) { + /** + * @type {AbstractTool[]} + */ + let tools = [ + new QueryStarRailTool(), + new WebsiteTool(), + new SendPictureTool(), + new SendVideoTool(), + // new ImageCaptionTool(), + new SearchVideoTool(), + new SendAvatarTool(), + new SerpImageTool(), + new SearchMusicTool(), + new SendMusicTool(), + // new SerpIkechan8370Tool(), + // new SerpTool(), + new SendAudioMessageTool(), + // new ProcessPictureTool(), + new APTool(), + // new HandleMessageMsgTool(), + new SendMessageToSpecificGroupOrUserTool(), + // new SendDiceTool(), + new QueryGenshinTool() + ] + if (Config.amapKey) { + tools.push(new WeatherTool()) + } + if (e.isGroup) { + tools.push(new QueryUserinfoTool()) + // let self = e.group.pickMember(e.self_id) + if (e.group.is_admin || e.group.is_owner) { + tools.push(new EditCardTool()) + tools.push(new JinyanTool()) + tools.push(new KickOutTool()) + } + if (e.group.is_owner) { + tools.push(new SetTitleTool()) + } + } + switch (Config.serpSource) { + case 'ikechan8370': { + tools.push(new SerpIkechan8370Tool()) + break + } + case 'azure': { + if (!Config.azSerpKey) { + logger.warn('未配置bing搜索密钥,转为使用ikechan8370搜索源') + tools.push(new SerpIkechan8370Tool()) + } else { + tools.push(new SerpTool()) + } + break + } + default: { + tools.push(new SerpIkechan8370Tool()) + } + } + client.addTools(tools) + } + let system = Config.geminiPrompt + if (Config.enableGroupContext && e.isGroup) { + let chats = await getChatHistoryGroup(e, Config.groupContextLength) + const namePlaceholder = '[name]' + const defaultBotName = 'GeminiPro' + const groupContextTip = Config.groupContextTip + let botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname + system = system.replaceAll(namePlaceholder, botName || defaultBotName) + + ((Config.enableGroupContext && e.group_id) ? groupContextTip : '') + system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${e.sender.card || e.sender.nickname}(${e.sender.user_id}).` + system += `the group name is ${e.group.name || e.group_name}, group id is ${e.group_id}.` + system += `Your nickname is ${botName} in the group,` + if (chats) { + system += 'There is the conversation history in the group, you must chat according to the conversation history context"' + system += chats + .map(chat => { + let sender = chat.sender || {} + return `【${sender.card || sender.nickname}】(qq:${sender.user_id}, ${roleMap[sender.role] || 'normal user'},${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time:${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}` + }) + .join('\n') + } + } + option.system = system + return await client.sendMessage(prompt, option) + } else { + // openai api + let completionParams = {} + if (Config.model) { + completionParams.model = Config.model + } + const currentDate = new Date().toISOString().split('T')[0] + let promptPrefix = `You are ${Config.assistantLabel} ${useCast?.api || Config.promptPrefixOverride || defaultPropmtPrefix} + Current date: ${currentDate}` + let maxModelTokens = getMaxModelTokens(completionParams.model) + let system = promptPrefix + if (maxModelTokens >= 16000 && Config.enableGroupContext) { + try { + let opt = {} + opt.groupId = e.group_id + opt.qq = e.sender.user_id + opt.nickname = e.sender.card + opt.groupName = e.group.name || e.group_name + opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname + let master = (await getMasterQQ())[0] + if (master && e.group) { + opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname + } + if (master && !e.group) { + opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname + } + let chats = await getChatHistoryGroup(e, Config.groupContextLength) + opt.chats = chats + const namePlaceholder = '[name]' + const defaultBotName = 'ChatGPT' + const groupContextTip = Config.groupContextTip + system = system.replaceAll(namePlaceholder, opt.botName || defaultBotName) + + ((Config.enableGroupContext && opt.groupId) ? groupContextTip : '') + system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${opt.nickname}(${opt.qq})。` + system += `the group name is ${opt.groupName}, group id is ${opt.groupId}。` + if (opt.botName) { + system += `Your nickname is ${opt.botName} in the group,` + } + if (chats) { + system += 'There is the conversation history in the group, you must chat according to the conversation history context"' + system += chats + .map(chat => { + let sender = chat.sender || {} + // if (sender.user_id === e.bot.uin && chat.raw_message.startsWith('建议的回复')) { + if (chat.raw_message.startsWith('建议的回复')) { + // 建议的回复太容易污染设定导致对话太固定跑偏了 + return '' + } + return `【${sender.card || sender.nickname}】(qq:${sender.user_id}, ${roleMap[sender.role] || 'normal user'},${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time:${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}` + }) + .join('\n') + } + } catch (err) { + if (e.isGroup) { + logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err) + } + } + // logger.info(system) + } + let opts = { + apiBaseUrl: Config.openAiBaseUrl, + apiKey: Config.apiKey, + debug: false, + upsertMessage, + getMessageById, + systemMessage: system, + completionParams, + assistantLabel: Config.assistantLabel, + fetch: newFetch, + maxModelTokens + } + let openAIAccessible = (Config.proxy || !(await isCN())) // 配了代理或者服务器在国外,默认认为不需要反代 + if (opts.apiBaseUrl !== defaultOpenAIAPI && openAIAccessible && !Config.openAiForceUseReverse) { + // 如果配了proxy(或者不在国内),而且有反代,但是没开启强制反代,将baseurl删掉 + delete opts.apiBaseUrl + } + this.chatGPTApi = new ChatGPTAPI(opts) + let option = { + timeoutMs: 600000, + completionParams, + stream: true, + onProgress: (data) => { + if (Config.debug) { + logger.info(data?.text || data.functionCall || data) + } + } + // systemMessage: promptPrefix + } + option.systemMessage = system + if (conversation) { + if (!conversation.conversationId) { + conversation.conversationId = uuid() + } + option = Object.assign(option, conversation) + } + if (Config.smartMode) { + let isAdmin = e.sender.role === 'admin' || e.sender.role === 'owner' + let sender = e.sender.user_id + let serpTool + switch (Config.serpSource) { + case 'ikechan8370': { + serpTool = new SerpIkechan8370Tool() + break + } + case 'azure': { + if (!Config.azSerpKey) { + logger.warn('未配置bing搜索密钥,转为使用ikechan8370搜索源') + serpTool = new SerpIkechan8370Tool() + } else { + serpTool = new SerpTool() + } + break + } + default: { + serpTool = new SerpIkechan8370Tool() + } + } + let fullTools = [ + new EditCardTool(), + new QueryStarRailTool(), + new WebsiteTool(), + new JinyanTool(), + new KickOutTool(), + new WeatherTool(), + new SendPictureTool(), + new SendVideoTool(), + new ImageCaptionTool(), + new SearchVideoTool(), + new SendAvatarTool(), + new SerpImageTool(), + new SearchMusicTool(), + new SendMusicTool(), + new SerpIkechan8370Tool(), + new SerpTool(), + new SendAudioMessageTool(), + new ProcessPictureTool(), + new APTool(), + new HandleMessageMsgTool(), + new QueryUserinfoTool(), + new EliMusicTool(), + new EliMovieTool(), + new SendMessageToSpecificGroupOrUserTool(), + new SendDiceTool(), + new QueryGenshinTool(), + new SetTitleTool() + ] + // todo 3.0再重构tool的插拔和管理 + let tools = [ + new SendAvatarTool(), + new SendDiceTool(), + new SendMessageToSpecificGroupOrUserTool(), + // new EditCardTool(), + new QueryStarRailTool(), + new QueryGenshinTool(), + new ProcessPictureTool(), + new WebsiteTool(), + // new JinyanTool(), + // new KickOutTool(), + new WeatherTool(), + new SendPictureTool(), + new SendAudioMessageTool(), + new APTool(), + // new HandleMessageMsgTool(), + serpTool, + new QueryUserinfoTool() + ] + try { + await import('../../avocado-plugin/apps/avocado.js') + tools.push(...[new EliMusicTool(), new EliMovieTool()]) + } catch (err) { + tools.push(...[new SendMusicTool(), new SearchMusicTool()]) + logger.mark(logger.green('【ChatGPT-Plugin】插件avocado-plugin未安装') + ',安装后可查看最近热映电影与体验可玩性更高的点歌工具。\n可前往 https://github.com/Qz-Sean/avocado-plugin 获取') + } + if (e.isGroup) { + let botInfo = await e.bot.getGroupMemberInfo(e.group_id, getUin(e), true) + if (botInfo.role !== 'member') { + // 管理员才给这些工具 + tools.push(...[new EditCardTool(), new JinyanTool(), new KickOutTool(), new HandleMessageMsgTool(), new SetTitleTool()]) + // 用于撤回和加精的id + if (e.source?.seq) { + let source = (await e.group.getChatHistory(e.source?.seq, 1)).pop() + option.systemMessage += `\nthe last message is replying to ${source.message_id}"\n` + } else { + option.systemMessage += `\nthe last message id is ${e.message_id}. ` + } + } + } + let img = await getImg(e) + if (img?.length > 0 && Config.extraUrl) { + tools.push(new ImageCaptionTool()) + tools.push(new ProcessPictureTool()) + prompt += `\nthe url of the picture(s) above: ${img.join(', ')}` + } else { + tools.push(new SerpImageTool()) + tools.push(...[new SearchVideoTool(), + new SendVideoTool()]) + } + let funcMap = {} + let fullFuncMap = {} + tools.forEach(tool => { + funcMap[tool.name] = { + exec: tool.func, + function: tool.function() + } + }) + fullTools.forEach(tool => { + fullFuncMap[tool.name] = { + exec: tool.func, + function: tool.function() + } + }) + if (!option.completionParams) { + option.completionParams = {} + } + option.completionParams.functions = Object.keys(funcMap).map(k => funcMap[k].function) + let msg + try { + msg = await this.chatGPTApi.sendMessage(prompt, option) + logger.info(msg) + while (msg.functionCall) { + if (msg.text) { + await e.reply(msg.text.replace('\n\n\n', '\n')) + } + let { + name, + arguments: args + } = msg.functionCall + args = JSON.parse(args) + // 感觉换成targetGroupIdOrUserQQNumber这种表意比较清楚的变量名,效果会好一丢丢 + if (!args.groupId) { + args.groupId = e.group_id + '' || e.sender.user_id + '' + } + try { + parseInt(args.groupId) + } catch (err) { + args.groupId = e.group_id + '' || e.sender.user_id + '' + } + let functionResult = await fullFuncMap[name.trim()].exec(Object.assign({ + isAdmin, + sender + }, args), e) + logger.mark(`function ${name} execution result: ${functionResult}`) + option.parentMessageId = msg.id + option.name = name + // 不然普通用户可能会被openai限速 + await common.sleep(300) + msg = await this.chatGPTApi.sendMessage(functionResult, option, 'function') + logger.info(msg) + } + } catch (err) { + if (err.message?.indexOf('context_length_exceeded') > 0) { + logger.warn(err) + await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) + await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`) + await e.reply('字数超限啦,将为您自动结束本次对话。') + return null + } else { + logger.error(err) + throw new Error(err) + } + } + return msg + } else { + let msg + try { + msg = await this.chatGPTApi.sendMessage(prompt, option) + } catch (err) { + if (err.message?.indexOf('context_length_exceeded') > 0) { + logger.warn(err) + await redis.del(`CHATGPT:CONVERSATIONS:${e.sender.user_id}`) + await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`) + await e.reply('字数超限啦,将为您自动结束本次对话。') + return null + } else { + logger.error(err) + throw new Error(err) + } + } + return msg } } } diff --git a/package.json b/package.json index 688590f..44f2ee1 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "@google/generative-ai": "^0.1.1", "@slack/bolt": "^3.13.2", "asn1.js": "^5.0.0", - "delay": "^6.0.0", "diff": "^5.1.0", "emoji-strip": "^1.0.1", "eventsource": "^2.0.2", diff --git a/utils/SydneyAIClient.js b/utils/SydneyAIClient.js index f452bb5..16e9836 100644 --- a/utils/SydneyAIClient.js +++ b/utils/SydneyAIClient.js @@ -8,10 +8,9 @@ import crypto from 'crypto' import WebSocket from 'ws' import { Config, pureSydneyInstruction } from './config.js' import { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js' -import delay from 'delay' import moment from 'moment' import { getProxy } from './proxy.js' -import Version from './version.js' +import common from '../../../lib/common/common.js' if (!globalThis.fetch) { globalThis.fetch = fetch @@ -87,9 +86,12 @@ export default class SydneyAIClient { // 'x-forwarded-for': '1.1.1.1' } } - if (this.opts.cookies || this.opts.userToken) { + let initCk = 'SRCHD=AF=NOFORM; PPLState=1; SRCHHPGUSR=HV=' + new Date().getTime() + ';' + if (this.opts.userToken) { // 疑似无需token了 - fetchOptions.headers.cookie = this.opts.cookies || `_U=${this.opts.userToken}` + fetchOptions.headers.cookie = `${initCk} _U=${this.opts.userToken}` + } else { + fetchOptions.headers.cookie = initCk } if (this.opts.proxy) { fetchOptions.agent = proxy(Config.proxy) @@ -105,7 +107,7 @@ export default class SydneyAIClient { let text = await response.text() let retry = 10 while (retry >= 0 && response.status === 200 && !text) { - await delay(400) + await common.sleep(400) response = await fetch(`${this.opts.host}/turing/conversation/create?bundleVersion=1.1381.12`, fetchOptions) text = await response.text() retry-- @@ -308,8 +310,7 @@ export default class SydneyAIClient { const text = (pureSydney ? pureSydneyInstruction : (useCast?.bing || Config.sydney)).replaceAll(namePlaceholder, botName || defaultBotName) + ((Config.enableGroupContext && groupId) ? groupContextTip : '') + ((Config.enforceMaster && master) ? masterTip : '') + - (Config.sydneyMood ? moodTip : '') + - (Config.sydneySystemCode ? '' : '') + (Config.sydneyMood ? moodTip : '') // logger.info(text) if (pureSydney) { previousMessages = invocationId === 0 diff --git a/utils/browser.js b/utils/browser.js index 288341f..0c193f9 100644 --- a/utils/browser.js +++ b/utils/browser.js @@ -4,6 +4,7 @@ import StealthPlugin from 'puppeteer-extra-plugin-stealth' import { getOpenAIAuth } from './openai-auth.js' import delay from 'delay' import { v4 as uuidv4 } from 'uuid' +import common from '../../../lib/common/common.js' const chatUrl = 'https://chat.openai.com/chat' let puppeteer = {} @@ -181,7 +182,7 @@ export class ChatGPTPuppeteer extends Puppeteer { if (Config['2captchaToken']) { await this._page.solveRecaptchas() } - await delay(300) + await common.sleep(300) timeout = timeout - 300 } } catch (e) { @@ -243,7 +244,7 @@ export class ChatGPTPuppeteer extends Puppeteer { break } - await delay(300) + await common.sleep(300) } while (true) if (!await this.getIsAuthenticated()) { @@ -405,7 +406,7 @@ export class ChatGPTPuppeteer extends Puppeteer { if (isAuthenticated) { while (!this._accessToken) { // wait for async response hook result - await delay(300) + await common.sleep(300) timeout = timeout - 300 if (timeout < 0) { const error = new Error('Not signed in') @@ -493,7 +494,7 @@ export class ChatGPTPuppeteer extends Puppeteer { // const responseP = new Promise(async (resolve, reject) => { // try { // do { - // await delay(1000) + // await common.sleep(1000) // // TODO: this logic needs some work because we can have repeat messages... // const newLastMessage = await this.getLastMessage() diff --git a/utils/openai-auth.js b/utils/openai-auth.js index 6653548..a10709d 100644 --- a/utils/openai-auth.js +++ b/utils/openai-auth.js @@ -1,6 +1,6 @@ import { Config } from '../utils/config.js' -import delay from 'delay' import random from 'random' +import common from '../../../lib/common/common.js' let hasRecaptchaPlugin = !!Config['2captchaToken'] @@ -58,7 +58,7 @@ export async function getOpenAIAuth (opt) { await waitForConditionOrAtCapacity(page, () => page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 }) ) - await delay(500) + await common.sleep(500) // click login button and wait for navigation to finish do { @@ -69,7 +69,7 @@ export async function getOpenAIAuth (opt) { }), page.click('#__next .btn-primary') ]) - await delay(1000) + await common.sleep(1000) } while (page.url().endsWith('/auth/login')) logger.mark('进入登录页面') await checkForChatGPTAtCapacity(page) @@ -90,7 +90,7 @@ export async function getOpenAIAuth (opt) { } else { await page.waitForSelector('#username') await page.type('#username', email, { delay: 20 }) - await delay(100) + await common.sleep(100) if (hasRecaptchaPlugin) { // console.log('solveRecaptchas()') @@ -114,7 +114,7 @@ export async function getOpenAIAuth (opt) { submitP() ]) } else { - await delay(2000) + await common.sleep(2000) await checkForChatGPTAtCapacity(page) } @@ -183,7 +183,7 @@ async function checkForChatGPTAtCapacity (page, opts = {}) { timeout: timeoutMs }) - await delay(pollingIntervalMs) + await common.sleep(pollingIntervalMs) } } catch (err) { // ignore errors likely due to navigation @@ -251,29 +251,29 @@ async function solveSimpleCaptchas (page) { const verifyYouAreHuman = await page.$('text=Verify you are human') if (verifyYouAreHuman) { logger.mark('encounter cloudflare simple captcha "Verify you are human"') - await delay(2000) + await common.sleep(2000) await verifyYouAreHuman.click({ delay: random.int(5, 25) }) - await delay(1000) + await common.sleep(1000) } const verifyYouAreHumanCN = await page.$('text=确认您是真人') if (verifyYouAreHumanCN) { logger.mark('encounter cloudflare simple captcha "确认您是真人"') - await delay(2000) + await common.sleep(2000) await verifyYouAreHumanCN.click({ delay: random.int(5, 25) }) - await delay(1000) + await common.sleep(1000) } const cloudflareButton = await page.$('.hcaptcha-box') if (cloudflareButton) { - await delay(2000) + await common.sleep(2000) await cloudflareButton.click({ delay: random.int(5, 25) }) - await delay(1000) + await common.sleep(1000) } } catch (err) { // ignore errors diff --git a/utils/slack/slackClient.js b/utils/slack/slackClient.js index abfd7ee..f86a8ed 100644 --- a/utils/slack/slackClient.js +++ b/utils/slack/slackClient.js @@ -1,7 +1,7 @@ import { Config } from '../config.js' import slack from '@slack/bolt' -import delay from 'delay' import { limitString } from '../common.js' +import common from '../../../../lib/common/common.js' let proxy if (Config.proxy) { try { @@ -24,7 +24,7 @@ export class SlackClaudeClient { if (Config.proxy) { option.agent = proxy(Config.proxy) } - option.logLevel = Config.debug ? 'debug': 'info' + option.logLevel = Config.debug ? 'debug' : 'info' this.app = new slack.App(option) } else { throw new Error('未配置Slack信息') @@ -61,7 +61,7 @@ export class SlackClaudeClient { channel: channel.id, users: Config.slackClaudeUserId }) - await delay(1000) + await common.sleep(1000) } else { channel = channel[0] } @@ -78,7 +78,7 @@ export class SlackClaudeClient { let response = '_Typing…_' let tryTimes = 0 // 发完先等3喵 - await delay(3000) + await common.sleep(3000) while (response.trim().endsWith('_Typing…_')) { let replies = await this.app.client.conversations.replies({ token: this.config.slackUserToken, @@ -106,7 +106,7 @@ export class SlackClaudeClient { } } } - await delay(2000) + await common.sleep(2000) tryTimes++ if (tryTimes > 3 && response === '_Typing…_') { // 过了6秒还没任何回复,就重新发一下试试 @@ -127,7 +127,7 @@ export class SlackClaudeClient { let response = '_Typing…_' let tryTimes = 0 // 发完先等3喵 - await delay(3000) + await common.sleep(3000) while (response.trim().endsWith('_Typing…_')) { let replies = await this.app.client.conversations.replies({ token: this.config.slackUserToken, @@ -156,7 +156,7 @@ export class SlackClaudeClient { } } } - await delay(2000) + await common.sleep(2000) tryTimes++ if (tryTimes > 3 && response === '_Typing…_') { // 过了6秒还没任何回复,就重新发一下试试 From 3709166b2de5aa2b792ca74a9061ef2cf3f74537 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 2 Jan 2024 20:43:07 +0800 Subject: [PATCH 4/4] fix #633 --- utils/browser.js | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/browser.js b/utils/browser.js index 0c193f9..5866790 100644 --- a/utils/browser.js +++ b/utils/browser.js @@ -2,7 +2,6 @@ import lodash from 'lodash' import { Config } from '../utils/config.js' import StealthPlugin from 'puppeteer-extra-plugin-stealth' import { getOpenAIAuth } from './openai-auth.js' -import delay from 'delay' import { v4 as uuidv4 } from 'uuid' import common from '../../../lib/common/common.js' const chatUrl = 'https://chat.openai.com/chat'