From 3ff591d2055a5b499e32cf86266395803244f93d Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 14 Feb 2023 11:24:09 +0800 Subject: [PATCH] feat: add support for bing(beta and WIP) --- README.md | 7 ++++--- apps/chat.js | 47 ++++++++++++++++++++++++++++++++++++---------- apps/help.js | 9 +++++++-- apps/management.js | 44 ++++++++++++++++++++++++++++++++++++++++--- config/index.js | 3 ++- 5 files changed, 91 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 3711916..832f3f6 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ * 支持问答图片截图 * 仅需OpenAI Api Key,开箱即用 * 提供基于浏览器的解决方案作为备选,有条件且希望得到更好回答质量可以选择使用浏览器模式。 +* 支持新Bing(Beta) ## 版本要求 Node.js >= 18 / Node.js >= 14(with node-fetch) +小白尽可能使用18版本以上的nodejs ## 安装 首先判断自己需要使用哪种模式,本插件支持官方API、第三方API和浏览器两种模式。也可以选择**我全都要**,通过qq发送命令`#chatgpt切换浏览器/API/API2`实时切换。对于轻量用户可以先使用API模式,有较高要求再转为使用其他模式。 @@ -15,9 +17,9 @@ Node.js >= 18 / Node.js >= 14(with node-fetch) > API模式和浏览器模式如何选择? > > * API模式会调用OpenAI官方提供的GPT-3 LLM API,只需要提供API Key。一般情况下,该种方式响应速度更快,可配置项多,且不会像chatGPT官网一样总出现不可用的现象,但其聊天效果明显较官网差。但注意GPT-3的API调用是收费的,新用户有18美元试用金可用于支付,价格为`$0.0200/ 1K tokens`.(问题和回答加起来算token) -> * API2模式会调用第三方提供的基于OpenAI text-davinci-002-render模型(官网同款)的API,需要提供ChatGPT的Token。效果比单纯的GPT-3 API好很多,但同时将Token提供给了第三方API,其中风险自行承担。 +> * API2模式会调用第三方提供的基于OpenAI text-davinci-002-render模型(官网同款)的API,需要提供ChatGPT的Token。效果比单纯的GPT-3 API好很多,但同时将Token提供给了第三方API,其中风险自行承担。#chatgpt设置token > * 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站,使得获得和官方以及API2模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问ChatGPT的IP地址),且响应速度不如API,而且高峰期容易无法使用。 - +> * 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用。#chatgpt设置必应token. ("_U" cookie from bing.com) 1. 进入 Yunzai根目录 2. 检查 Node.js 版本 @@ -74,7 +76,6 @@ git clone https://github.com/ikechan8370/chatgpt-plugin.git ./plugins/chatgpt-pl ## TODO * 更灵活的Conversation管理 -* 支持Bing版本 * 版本号和归档 * API2模式下自动获取/刷新Token diff --git a/apps/chat.js b/apps/chat.js index f215bae..88f8414 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -6,7 +6,7 @@ import mjAPI from 'mathjax-node' import { uuid } from 'oicq/lib/common.js' import delay from 'delay' import { ChatGPTAPI } from 'chatgpt' -import { ChatGPTClient } from '@waylaidwanderer/chatgpt-api' +import { ChatGPTClient, BingAIClient } from '@waylaidwanderer/chatgpt-api' import { getMessageById, tryTimes, upsertMessage } from '../utils/common.js' import { ChatGPTPuppeteer } from '../utils/browser.js' import { KeyvFile } from 'keyv-file' @@ -247,15 +247,25 @@ export class chatgpt extends plugin { previousConversation = JSON.parse(previousConversation) conversation = { conversationId: previousConversation.conversation.conversationId, - parentMessageId: previousConversation.conversation.parentMessageId + parentMessageId: previousConversation.conversation.parentMessageId, + clientId: previousConversation.clientId, + invocationId: previousConversation.invocationId, + conversationSignature: previousConversation.conversationSignature } } - + const use = await redis.get('CHATGPT:USE') try { - let chatMessage = await this.sendMessage(prompt, conversation) + let chatMessage = await this.sendMessage(prompt, conversation, use) previousConversation.conversation = { - conversationId: chatMessage.conversationId, - parentMessageId: chatMessage.id + conversationId: chatMessage.conversationId + } + if (use === 'bing') { + previousConversation.clientId = chatMessage.clientId + previousConversation.invocationId = chatMessage.invocationId + previousConversation.conversationSignature = chatMessage.conversationSignature + } else { + // 或许这样切换回来不会404? + previousConversation.conversation.parentMessageId = chatMessage.id } console.log(chatMessage) let response = chatMessage?.text @@ -284,7 +294,7 @@ export class chatgpt extends plugin { // !response.trimEnd().endsWith('!') && !response.trimEnd().endsWith('!') && !response.trimEnd().endsWith(']') && !response.trimEnd().endsWith('】') // ) { await this.reply('内容有点多,我正在奋笔疾书,请再等一会', true, { recallMsg: 5 }) - let responseAppend = await this.sendMessage('Continue', conversation) + let responseAppend = await this.sendMessage('Continue', conversation, use) previousConversation.conversation = { conversationId: responseAppend.conversationId, parentMessageId: responseAppend.id @@ -325,8 +335,8 @@ export class chatgpt extends plugin { } } - async sendMessage (prompt, conversation = {}) { - const use = await redis.get('CHATGPT:USE') + async sendMessage (prompt, conversation = {}, use) { + // console.log(use) if (use === 'browser') { return await this.chatgptBrowserBased(prompt, conversation) @@ -350,7 +360,7 @@ export class chatgpt extends plugin { // (Optional) Set a custom name for ChatGPT chatGptLabel: Config.assistantLabel, // (Optional) Set to true to enable `console.debug()` logging - debug: false + debug: Config.debug } const cacheOptions = { // Options for the Keyv cache, see https://www.npmjs.com/package/keyv @@ -371,6 +381,23 @@ export class chatgpt extends plugin { id: response.messageId, parentMessageId: conversation?.parentMessageId } + } else if (use === 'bing') { + let bingToken = await redis.get('CHATGPT:BING_TOKEN') + if (!bingToken) { + throw new Error('未绑定Bing Cookie,请使用#chatgpt设置Bing Cookie命令绑定Bing Cookie') + } + const bingAIClient = new BingAIClient({ + userToken: bingToken, // "_U" cookie from bing.com + debug: Config.debug + }) + let response = await bingAIClient.sendMessage(prompt, conversation) + return { + text: response.response, + conversationId: response.conversationId, + clientId: response.clientId, + invocationId: response.invocationId, + conversationSignature: response.conversationSignature + } } else { let completionParams = {} if (Config.model) { diff --git a/apps/help.js b/apps/help.js index 552c37b..81e3583 100644 --- a/apps/help.js +++ b/apps/help.js @@ -63,8 +63,13 @@ let helpData = [ }, { icon: 'switch', - title: '#chatgpt切换浏览器/API/API2', - desc: '切换使用的后端为浏览器或OpenAI API/第三方API' + title: '#chatgpt切换浏览器/API/API2/Bing', + desc: '切换使用的后端为浏览器或OpenAI API/第三方API/Bing' + }, + { + icon: 'help', + title: '#chatgpt设置(必应)token', + desc: '设置ChatGPT或bing的Token' }, { icon: 'help', diff --git a/apps/management.js b/apps/management.js index bf5dd89..8325ba2 100644 --- a/apps/management.js +++ b/apps/management.js @@ -23,6 +23,11 @@ export class ChatgptManagement extends plugin { fnc: 'setAccessToken', permission: 'master' }, + { + reg: '#chatgpt设置必应token', + fnc: 'setBingAccessToken', + permission: 'master' + }, { reg: '^#chatgpt切换浏览器$', fnc: 'useBrowserBasedSolution', @@ -38,6 +43,11 @@ export class ChatgptManagement extends plugin { fnc: 'useReversedAPIBasedSolution', permission: 'master' }, + { + reg: '^#chatgpt切换(必应|Bing)$', + fnc: 'useReversedBingSolution', + permission: 'master' + }, { reg: '^#chatgpt模式帮助$', fnc: 'modeHelp' @@ -68,9 +78,28 @@ export class ChatgptManagement extends plugin { return false } + async setBingAccessToken (e) { + this.setContext('saveBingToken') + await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true) + return false + } + + async saveBingToken () { + if (!this.e.msg) return + let token = this.e.msg + // todo 未知bing token是什么样的,有号的可以加个校验在这 + await redis.set('CHATGPT:BING_TOKEN', token) + await this.reply('Bing Token设置成功', true) + this.finish('saveBingToken') + } + async saveToken () { if (!this.e.msg) return let token = this.e.msg + if (!token.startsWith('ey') || token.length < 20) { + await this.reply('ChatGPT AccessToken格式错误', true) + this.finish('saveToken') + } await redis.set('CHATGPT:TOKEN', token) await this.reply('ChatGPT AccessToken设置成功', true) this.finish('saveToken') @@ -91,12 +120,18 @@ export class ChatgptManagement extends plugin { await this.reply('已切换到基于第三方Reversed API的解决方案,如果已经对话过建议执行`#结束对话`避免引起404错误') } + async useReversedBingSolution (e) { + await redis.set('CHATGPT:USE', 'bing') + await this.reply('已切换到基于第三方Reversed API的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误') + } + async modeHelp () { let mode = await redis.get('CHATGPT:USE') const modeMap = { browser: '浏览器', apiReverse: 'API2', - api: 'API' + api: 'API', + bing: '必应' } let modeText = modeMap[mode || 'api'] let message = ` API模式和浏览器模式如何选择? @@ -104,11 +139,14 @@ export class ChatgptManagement extends plugin { // eslint-disable-next-line no-irregular-whitespace API模式会调用OpenAI官方提供的GPT-3 LLM API,只需要提供API Key。一般情况下,该种方式响应速度更快,可配置项多,且不会像chatGPT官网一样总出现不可用的现象,但其聊天效果明显较官网差。但注意GPT-3的API调用是收费的,新用户有18美元试用金可用于支付,价格为$0.0200/ 1K tokens.(问题和回答加起来算token) - API2模式会调用第三方提供的基于OpenAI text-davinci-002-render模型(官网同款)的API,需要提供ChatGPT的Token。效果比单纯的GPT-3 API好很多,但同时将Token提供给了第三方API,其中风险自行承担。 + API2模式会调用第三方提供的基于OpenAI text-davinci-002-render模型(官网同款)的API,需要提供ChatGPT的Token。效果比单纯的GPT-3 API好很多,但同时将Token提供给了第三方API,其中风险自行承担。#chatgpt设置token 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站,使得获得和官方以及API2模式一模一样的回复质量,同时保证安全性。缺点是本方法对环境要求较高,需要提供桌面环境和一个可用的代理(能够访问ChatGPT的IP地址),且响应速度不如API,而且高峰期容易无法使用。 - 您可以使用‘#chatgpt切换浏览器/API/API2’来切换到指定模式。 + 必应(Bing)将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing 登录Cookie方可使用。#chatgpt设置必应token + + 您可以使用‘#chatgpt切换浏览器/API/API2/Bing’来切换到指定模式。 + 当前为${modeText}模式。 ` await this.reply(message) diff --git a/config/index.js b/config/index.js index 86032ae..1bc7c37 100644 --- a/config/index.js +++ b/config/index.js @@ -49,5 +49,6 @@ export const Config = { // 可注册2captcha实现跳过验证码,收费服务但很便宜。否则可能会遇到验证码而卡住。 '2captchaToken': '', // http或socks5代理 - proxy: PROXY + proxy: PROXY, + debug: false }