From dcae426cfa79eee7a9dfbb2b06e9725532219d8f Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 4 Feb 2025 17:42:41 +0800 Subject: [PATCH 1/3] fix: increase max token for api mode --- utils/openai/chatgpt-api.js | 2 +- utils/openai/chatgpt-api.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/openai/chatgpt-api.js b/utils/openai/chatgpt-api.js index e3e54d9..56b5939 100644 --- a/utils/openai/chatgpt-api.js +++ b/utils/openai/chatgpt-api.js @@ -82,7 +82,7 @@ var ChatGPTAPI = /** @class */ (function () { * @param fetch - Optional override for the `fetch` implementation to use. Defaults to the global `fetch` function. */ function ChatGPTAPI(opts) { - var apiKey = opts.apiKey, apiOrg = opts.apiOrg, _a = opts.apiBaseUrl, apiBaseUrl = _a === void 0 ? 'https://api.openai.com/v1' : _a, _b = opts.debug, debug = _b === void 0 ? false : _b, messageStore = opts.messageStore, completionParams = opts.completionParams, systemMessage = opts.systemMessage, _c = opts.maxModelTokens, maxModelTokens = _c === void 0 ? 4000 : _c, _d = opts.maxResponseTokens, maxResponseTokens = _d === void 0 ? 1000 : _d, getMessageById = opts.getMessageById, upsertMessage = opts.upsertMessage, _e = opts.fetch, fetch = _e === void 0 ? globalFetch : _e; + var apiKey = opts.apiKey, apiOrg = opts.apiOrg, _a = opts.apiBaseUrl, apiBaseUrl = _a === void 0 ? 'https://api.openai.com/v1' : _a, _b = opts.debug, debug = _b === void 0 ? false : _b, messageStore = opts.messageStore, completionParams = opts.completionParams, systemMessage = opts.systemMessage, _c = opts.maxModelTokens, maxModelTokens = _c === void 0 ? 4000 : _c, _d = opts.maxResponseTokens, maxResponseTokens = _d === void 0 ? 8192 : _d, getMessageById = opts.getMessageById, upsertMessage = opts.upsertMessage, _e = opts.fetch, fetch = _e === void 0 ? globalFetch : _e; this._apiKey = apiKey; this._apiOrg = apiOrg; this._apiBaseUrl = apiBaseUrl; diff --git a/utils/openai/chatgpt-api.ts b/utils/openai/chatgpt-api.ts index c7ac533..084d405 100644 --- a/utils/openai/chatgpt-api.ts +++ b/utils/openai/chatgpt-api.ts @@ -59,7 +59,7 @@ export class ChatGPTAPI { completionParams, systemMessage, maxModelTokens = 4000, - maxResponseTokens = 1000, + maxResponseTokens = 8192, getMessageById, upsertMessage, fetch = globalFetch From 243331aa2e03420d59cda8a5815c47c49a29b9f5 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 4 Feb 2025 23:11:07 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E5=A4=8D=E6=B4=BBCopilot=E4=BD=86?= =?UTF-8?q?=E6=9C=89=E4=BB=A3=E4=BB=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/management.js | 23 +++ client/CopilotAIClient.js | 108 +++++++++--- guoba.support.js | 119 +++---------- model/core.js | 340 +++++++------------------------------- utils/config.js | 9 +- 5 files changed, 201 insertions(+), 398 deletions(-) diff --git a/apps/management.js b/apps/management.js index 82cf607..072248f 100644 --- a/apps/management.js +++ b/apps/management.js @@ -357,6 +357,10 @@ export class ChatgptManagement extends plugin { reg: '^#chatgpt(伪人|bym)切换', fnc: 'switchBYMModel', permission: 'master' + }, + { + reg: '^#(chatgpt)?(Copilot|Bing|必应)配置方法', + fnc: 'copilotSetting' } ] }) @@ -1880,6 +1884,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, await this.reply('切换成功') } + async copilotSetting (e) { + const code = 'let results = []\n' + + 'Object.keys(localStorage).forEach(key => {\n' + + ' try {\n' + + ' let value = JSON.parse(localStorage[key])\n' + + ' if (key.includes(\'accesstoken\') && value.target?.includes(\'ChatAI\')) {\n' + + ' results[\'accessToken\'] = value.secret\n' + + ' results[\'clientId\'] = value.clientId\n' + + ' results[\'scope\'] = value.target + \' openid profile offline_access\'\n' + + ' } else if (key.includes(\'refreshtoken\')) {\n' + + ' results[\'oid\'] = value.homeAccountId\n' + + ' results[\'refreshToken\'] = value.secret\n' + + ' }\n' + + ' } catch (err) {}\n' + + '})\n' + + 'console.log(results)' + e.reply(`可以在浏览器控制台使用以下代码获取相关配置。\n\`\`\`javacript\n${code}\n\`\`\``) + } + async geminiOpenSearchCE (e) { let msg = e.msg let open = msg.includes('开启') diff --git a/client/CopilotAIClient.js b/client/CopilotAIClient.js index 57beeb3..550e149 100644 --- a/client/CopilotAIClient.js +++ b/client/CopilotAIClient.js @@ -1,10 +1,15 @@ import WebSocket from 'ws' import common from '../../../lib/common/common.js' +import _ from 'lodash' +import { pTimeout } from '../utils/common.js' export class BingAIClient { - constructor (accessToken, baseUrl = 'wss://copilot.microsoft.com/c/api/chat', debug, _2captchaKey, clientId, scope, refreshToken, oid) { + constructor (accessToken, baseUrl = 'wss://copilot.microsoft.com', debug, _2captchaKey, clientId, scope, refreshToken, oid, reasoning = false) { this.accessToken = accessToken this.baseUrl = baseUrl + if (this.baseUrl.endsWith('/')) { + this.baseUrl = _.trimEnd(baseUrl, '/') + } this.ws = null this.conversationId = null this.partialMessages = new Map() @@ -14,6 +19,7 @@ export class BingAIClient { this.scope = scope this.refreshToken = refreshToken this.oid = oid + this.reasoning = reasoning } async sendMessage (text, options = {}) { @@ -21,7 +27,7 @@ export class BingAIClient { if (options.conversationId) { this.conversationId = options.conversationId } else { - this.conversationId = this._generateConversationId() + this.conversationId = await this._generateConversationId() } // 建立 WebSocket 连接 @@ -31,16 +37,32 @@ export class BingAIClient { await this.sendInitialMessage(text) // 等待并收集服务器的回复 - const responseText = await this.collectResponse() - return responseText + try { + const responseText = await pTimeout(await this.collectResponse(), { + milliseconds: 1000 * 60 * 5 + }) + return responseText + } catch (err) { + if (this.partialMessages.get(this.currentMessageId)) { + return this.partialMessages.get(this.currentMessageId).text + } else { + throw err + } + } } async connectWebSocket () { return new Promise((resolve, reject) => { - let url = `${this.baseUrl}?api-version=2` + let wsUrl = this.baseUrl + if (wsUrl.startsWith('http')) { + wsUrl = wsUrl.replace('https://', 'wss://') + .replace('http://', 'ws://') + } + let url = `${wsUrl}/c/api/chat?api-version=2` if (this.accessToken) { url += '&accessToken=' + this.accessToken } + logger.info('ws url: ' + url) this.ws = new WebSocket(url) this.ws.on('open', () => { @@ -50,13 +72,12 @@ export class BingAIClient { if (this.debug) { this.ws.on('message', (message) => { - logger.info(JSON.stringify(message)) + logger.info('received message', String(message)) }) } this.ws.on('close', (code, reason) => { console.log('WebSocket connection closed. Code:', code, 'Reason:', reason) - // 401 错误码通常是未授权,可以根据实际情况修改 if (code === 401) { logger.error('token expired. try to refresh with refresh token') this.doRefreshToken(this.clientId, this.scope, this.refreshToken, this.oid) @@ -71,33 +92,40 @@ export class BingAIClient { async sendInitialMessage (text) { return new Promise((resolve, reject) => { + const initMgs = { event: 'setOptions', supportedCards: ['image'], ads: null } + this.ws.send(JSON.stringify(initMgs)) + if (this.debug) { + logger.info('send msg: ', JSON.stringify(initMgs)) + } const messagePayload = { event: 'send', conversationId: this.conversationId, content: [{ type: 'text', text }], - mode: 'chat', + mode: this.reasoning ? 'reasoning' : 'chat', context: { edge: 'NonContextual' } } // 直接发送消息 this.ws.send(JSON.stringify(messagePayload)) - + if (this.debug) { + logger.info('send msg: ', JSON.stringify(messagePayload)) + } // 设置超时机制,防止长时间未收到消息 const timeout = setTimeout(() => { reject(new Error('No response from server within timeout period.')) }, 5000) // 设置 5 秒的超时时间 - // 一旦收到消息,处理逻辑 this.ws.once('message', (data) => { clearTimeout(timeout) // 清除超时定时器 const message = JSON.parse(data) - + logger.info(data) if (message.event === 'challenge') { - logger.info(JSON.stringify(message)) logger.warn('遇到turnstile验证码,尝试使用2captcha解决') // 如果收到 challenge,处理挑战 this.handleChallenge(message) - .then(resolve) + .then(() => { + resolve() + }) .catch(reject) } else { // 否则直接进入对话 @@ -152,14 +180,15 @@ export class BingAIClient { this.partialMessages.get(message.messageId).text += message.text // 如果是最后一部分,标记为完成 - if (message.partId === '0') { - this.partialMessages.get(message.messageId).done = true - } + // if (message.partId === '0') { + // this.partialMessages.get(message.messageId).done = true + // } checkMessageComplete(message.messageId) break case 'partCompleted': + this.partialMessages.get(message.messageId).done = true break case 'done': @@ -167,7 +196,7 @@ export class BingAIClient { break default: - console.warn('Unexpected event:', message.event) + // console.warn('Unexpected event:', message.event) break } }) @@ -203,6 +232,7 @@ export class BingAIClient { taskId, clientKey: this._2captchaKey }) + async function getTaskResult () { const requestOptions2 = { method: 'POST', @@ -213,12 +243,11 @@ export class BingAIClient { const response2 = await fetch('https://api.2captcha.com/getTaskResult', requestOptions2) const taskResponse = await response2.json() - if (this.debug) { - logger.info(JSON.stringify(taskResponse)) - } + logger.info(JSON.stringify(taskResponse)) const token = taskResponse?.solution?.token return token } + let retry = 90 let token = await getTaskResult() while (retry > 0 && !token) { @@ -232,8 +261,40 @@ export class BingAIClient { return token } - _generateConversationId () { - return 'conversation-' + Math.random().toString(36).substring(2, 15) + async _generateConversationId () { + const url = `${this.baseUrl}/c/api/conversations` + const createConversationRsp = await fetch(url, { + headers: { + authorization: `Bearer ${this.accessToken}`, + 'content-type': 'application/json', + origin: 'https://copilot.microsoft.com', + referer: 'https://copilot.microsoft.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0' + }, + method: 'POST' + }) + const conversation = await createConversationRsp.json() + return conversation.id + } + + async _getCurrentConversationId () { + const url = `${this.baseUrl}/c/api/start` + const createConversationRsp = await fetch(url, { + headers: { + authorization: `Bearer ${this.accessToken}`, + 'content-type': 'application/json', + origin: 'https://copilot.microsoft.com', + referer: 'https://copilot.microsoft.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0' + }, + method: 'POST', + body: JSON.stringify({ + timeZone: 'Asia/Shanghai', + teenSupportEnabled: true + }) + }) + const conversation = await createConversationRsp.json() + return conversation.currentConversationId } /** @@ -270,7 +331,7 @@ export class BingAIClient { urlencoded.append('x-ms-lib-capability', 'retry-after, h429') urlencoded.append('x-client-current-telemetry', '5|61,0,,,|,') urlencoded.append('x-client-last-telemetry', '5|3|||0,0') - urlencoded.append('client-request-id', '0193875c-0737-703c-a2e7-6730dd56aa4a') + urlencoded.append('client-request-id', crypto.randomUUID()) urlencoded.append('refresh_token', refreshToken) urlencoded.append('X-AnchorMailbox', 'Oid:' + oid) @@ -286,6 +347,7 @@ export class BingAIClient { if (this.debug) { logger.info(JSON.stringify(tokenJson)) } + return tokenJson } } diff --git a/guoba.support.js b/guoba.support.js index 406fc9f..fdf6b95 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -190,28 +190,9 @@ export function supportGuoba () { component: 'Divider' }, { - field: 'toneStyle', - label: 'Bing模式', - bottomHelpMessage: 'Copilot的应答风格。默认为创意,可切换为精准或均衡,均为GPT-turbo', - component: 'Select', - componentProps: { - options: [ - { label: '创意', value: 'Creative' }, - { label: '均衡', value: 'Balanced' }, - { label: '精准', value: 'Precise' } - ] - } - }, - { - field: 'sydneyEnableSearch', - label: '是否允许必应进行搜索', - bottomHelpMessage: '关闭后必应将禁用搜索', - component: 'Switch' - }, - { - field: 'enableSuggestedResponses', - label: '是否开启建议回复', - bottomHelpMessage: '开启了会像官网上一样,每个问题给出建议的用户问题', + field: 'bingReasoning', + label: 'Bing开启思考', + bottomHelpMessage: 'Copilot的思考功能。开启后无法搜索', component: 'Switch' }, { @@ -231,12 +212,6 @@ export function supportGuoba () { bottomHelpMessage: '加强主人认知。希望机器人认清主人,避免NTR可开启。开启后可能会与自设定的内容有部分冲突。sydney模式可以放心开启', component: 'Switch' }, - { - field: 'enableGenerateContents', - label: '允许生成图像等内容', - bottomHelpMessage: '开启后类似网页版能够发图。但是此选项会占用大量token,自设定等模式下容易爆token', - component: 'Switch' - }, { field: 'groupContextLength', label: '允许机器人读取近期的最多群聊聊天记录条数。', @@ -255,18 +230,6 @@ export function supportGuoba () { bottomHelpMessage: '你可以自己改写设定,让Copilot变成你希望的样子。可能存在不稳定的情况', component: 'InputTextArea' }, - { - field: 'sydneyApologyIgnored', - label: 'Bing抱歉是否不计入聊天记录', - bottomHelpMessage: '有时无限抱歉,就关掉这个再多问几次试试,可能有奇效', - component: 'Switch' - }, - { - field: 'sydneyContext', - label: 'Bing的扩展资料', - bottomHelpMessage: 'AI将会从你提供的扩展资料中学习到一些知识,帮助它更好地回答你的问题。实际相当于使用edge侧边栏Bing时读取的你当前浏览网页的内容。如果太长可能容易到达GPT-4的8192token上限', - component: 'InputTextArea' - }, { field: 'sydneyReverseProxy', label: '必应反代', @@ -274,70 +237,40 @@ export function supportGuoba () { component: 'Input' }, { - field: 'sydneyForceUseReverse', - label: '强制使用sydney反代', - bottomHelpMessage: '即使配置了proxy,创建对话时依然使用必应反代', - component: 'Switch' - }, - { - field: 'sydneyWebsocketUseProxy', - label: '对话使用必应反代', - bottomHelpMessage: '默认情况下仅创建对话走反代,对话时仍然直连微软。开启本选项将使对话过程也走反代,需反代支持。默认开启', - component: 'Switch' - }, - { - field: 'bingCaptchaOneShotUrl', - label: '必应验证码pass服务', - bottomHelpMessage: '必应出验证码会自动用该服务绕过', + field: 'bingAiToken', + label: '必应AccessToken', + bottomHelpMessage: 'Copilot的AccessToken,scope需为ChatAI.ReadWrite。可以发送`#Copilot配置方法`查看浏览器获取配置的方法。', component: 'Input' }, { - field: 'sydneyMood', - label: '情感显示', - bottomHelpMessage: '开启Sydney的情感显示,仅在图片模式下生效', - component: 'Switch' + field: 'bingAiClientId', + label: '必应ClientId', + bottomHelpMessage: '配合RefreshToken刷新AccessToken', + component: 'Input' }, { - field: 'sydneyImageRecognition', - label: '图片识别', - bottomHelpMessage: '开启Sydney的图片识别功能,建议和OCR只保留一个开启', - component: 'Switch' + field: 'bingAiScope', + label: '必应Auth Scope', + bottomHelpMessage: '配合RefreshToken刷新AccessToken', + component: 'Input' }, { - field: 'chatExampleUser1', - label: '前置对话第一轮(用户)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' + field: 'bingAiRefreshToken', + label: '必应RefreshToken', + bottomHelpMessage: '配合RefreshToken刷新AccessToken', + component: 'Input' }, { - field: 'chatExampleBot1', - label: '前置对话第一轮(AI)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' + field: 'bingAiOid', + label: '必应Oid', + bottomHelpMessage: '(homeAccountId)配合RefreshToken刷新AccessToken', + component: 'Input' }, { - field: 'chatExampleUser2', - label: '前置对话第二轮(用户)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' - }, - { - field: 'chatExampleBot2', - label: '前置对话第二轮(AI)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' - }, - { - field: 'chatExampleUser3', - label: '前置对话第三轮(用户)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' - }, - { - field: 'chatExampleBot3', - label: '前置对话第三轮(AI)', - bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉', - component: 'InputTextArea' + field: '_2captchaKey', + label: '2captcha API密钥', + bottomHelpMessage: '用于解除Copilot的验证码', + component: 'Input' }, { label: '以下为API3方式的配置', diff --git a/model/core.js b/model/core.js index 7b62716..7139787 100644 --- a/model/core.js +++ b/model/core.js @@ -4,21 +4,15 @@ import { formatDate, getImg, getMasterQQ, getMaxModelTokens, - getOrDownloadFile, getUin, getUserData, isCN } from '../utils/common.js' import { KeyvFile } from 'keyv-file' import SydneyAIClient from '../utils/SydneyAIClient.js' -import _ from 'lodash' import { getChatHistoryGroup } from '../utils/chat.js' import { APTool } from '../utils/tools/APTool.js' -import BingDrawClient from '../utils/BingDraw.js' -import BingSunoClient from '../utils/BingSuno.js' -import { solveCaptchaOneShot } from '../utils/bingCaptcha.js' import { OfficialChatGPTClient } from '../utils/message.js' -import ChatGLMClient from '../utils/chatglm.js' import { ClaudeAPIClient } from '../client/ClaudeAPIClient.js' import { ClaudeAIClient } from '../utils/claude.ai/index.js' import XinghuoClient from '../utils/xinghuo/xinghuo.js' @@ -26,8 +20,6 @@ import { getMessageById, upsertMessage } from '../utils/history.js' import { v4 as uuid } from 'uuid' import fetch from 'node-fetch' import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js' -import { resizeAndCropImage } from '../utils/dalle.js' -import fs from 'fs' import { QueryStarRailTool } from '../utils/tools/QueryStarRailTool.js' import { WebsiteTool } from '../utils/tools/WebsiteTool.js' import { SendPictureTool } from '../utils/tools/SendPictureTool.js' @@ -59,6 +51,9 @@ import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js' import { newFetch } from '../utils/proxy.js' import { ChatGLM4Client } from '../client/ChatGLM4Client.js' import { QwenApi } from '../utils/alibaba/qwen-api.js' +import { BingAIClient } from '../client/CopilotAIClient.js' +import Keyv from 'keyv' +import crypto from 'crypto' const roleMap = { owner: 'group owner', @@ -145,287 +140,70 @@ class Core { const userData = await getUserData(e.user_id) const useCast = userData.cast || {} 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 = '' - - 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) { - opt.context = useCast?.bing_resource || Config.sydneyContext + const conversationsCache = new Keyv(cacheOptions) + let client = new BingAIClient(Config.bingAiToken, Config.sydneyReverseProxy, Config.debug, Config._2captchaKey, Config.bingAiClientId, Config.bingAiScope, Config.bingAiRefreshToken, Config.bingAiOid, Config.bingReasoning) + const conversationKey = `SydneyUser_${e.sender.user_id}` + const conversations = (await conversationsCache.get(conversationKey)) || { + messages: [], + createdAt: Date.now() + } + logger.info(JSON.stringify(conversations)) + const previousCachedMessages = SydneyAIClient.getMessagesForConversation(conversations.messages, conversation.parentMessageId) + .map((message) => { + return { + text: message.message, + author: message.role === 'User' ? 'user' : 'bot' } - // 重新拿存储的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) { - 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 - 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) - } - } - } 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}`) - this.reply('绘图失败:' + err) - } - }) - } - } - opt.onSunoCreateRequest = prompt => { - logger.mark(`开始生成内容:Suno ${prompt.songtId || ''}`) - let client = new BingSunoClient({ - cookies: cookies - }) - redis.set(`CHATGPT:SUNO:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => { - try { - if (Config.bingSuno == 'local') { - // 调用本地Suno配置进行歌曲生成 - client.getLocalSuno(prompt, e) - } else if (Config.bingSuno == 'api' && Config.bingSunoApi) { - // 调用第三方Suno配置进行歌曲生成 - client.getApiSuno(prompt, e) - } else { - // 调用Bing Suno进行歌曲生成 - client.getSuno(prompt, e) - } - } catch (err) { - redis.del(`CHATGPT:SUNO:${e.sender.user_id}`) - this.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, '') + }) + let system = opt.system.bing + if (Config.enableGroupContext && e.isGroup) { + let chats = await getChatHistoryGroup(e, Config.groupContextLength) + const namePlaceholder = '[name]' + const defaultBotName = 'Copilot' + 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}` }) - // 有了新的引用属性 - // 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 this.reply('出现必应验证码,尝试解决中') - try { - let captchaResolveResult = await solveCaptchaOneShot(bingToken) - if (captchaResolveResult?.success) { - await this.reply('验证码已解决') - } else { - logger.error(captchaResolveResult) - errorMessage = message - await this.reply('验证码解决失败: ' + captchaResolveResult.error) - retry = 0 - } - } catch (err) { - logger.error(err) - await this.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 { - 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) { - 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 + .join('\n') } } + const msg = `System:\n${system}\n\nPrevious Messages:\n${JSON.stringify(previousCachedMessages)}\n\nUser: ${prompt}` + const response = await client.sendMessage(msg) + logger.info({ response }) + const userMessage = { + id: crypto.randomUUID(), + parentMessageId: conversation.parentMessageId, + role: 'User', + message: prompt + } + conversations.messages.push(userMessage) + const replyMessage = { + id: crypto.randomUUID(), + parentMessageId: userMessage.id, + role: 'Bing', + message: response + } + conversations.messages.push(replyMessage) + await conversationsCache.set(conversationKey, conversations) + return { + text: response, + parentMessageId: replyMessage.id + + } } else if (use === 'api3') { // official without cloudflare let accessToken = await redis.get('CHATGPT:TOKEN') diff --git a/utils/config.js b/utils/config.js index 771600e..1e447e1 100644 --- a/utils/config.js +++ b/utils/config.js @@ -156,7 +156,6 @@ const defaultConfig = { serpSource: 'ikechan8370', extraUrl: 'https://cpe.ikechan8370.com', smartMode: false, - bingCaptchaOneShotUrl: '', // claude2 claudeAIOrganizationId: '', claudeAISessionKey: '', @@ -219,6 +218,14 @@ const defaultConfig = { forwardReasoning: true, geminiEnableGoogleSearch: false, geminiEnableCodeExecution: false, + bingAiToken: '', // copilot.microsoft.com accessToken + bingAiClientId: '', + bingAiScope: '140e65af-45d1-4427-bf08-3e7295db6836/ChatAI.ReadWrite openid profile offline_access', + bingAiRefreshToken: '', + bingAiOid: '', + _2captchaKey: '', + bingReasoning: false, // 是否深度思考 + version: 'v2.8.3' } const _path = process.cwd() From d7d4acf382a7310e48123c843ce17f62738faa3d Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 4 Feb 2025 23:25:54 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=E8=BF=87=E7=A0=81=E7=AC=AC=E4=BA=8C?= =?UTF-8?q?=E9=81=8D=E6=89=8D=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/CopilotAIClient.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/CopilotAIClient.js b/client/CopilotAIClient.js index 550e149..40e6c88 100644 --- a/client/CopilotAIClient.js +++ b/client/CopilotAIClient.js @@ -110,6 +110,7 @@ export class BingAIClient { if (this.debug) { logger.info('send msg: ', JSON.stringify(messagePayload)) } + let _this = this // 设置超时机制,防止长时间未收到消息 const timeout = setTimeout(() => { reject(new Error('No response from server within timeout period.')) @@ -124,7 +125,10 @@ export class BingAIClient { // 如果收到 challenge,处理挑战 this.handleChallenge(message) .then(() => { - resolve() + setTimeout(() => { + _this.ws.send(JSON.stringify(messagePayload)) + resolve() + }, 500) }) .catch(reject) } else {