From ec8262923841186bbc7f196942b43fa1de6914a3 Mon Sep 17 00:00:00 2001 From: ikechan8370 Date: Tue, 4 Apr 2023 16:08:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20api3=E4=BC=A0=E8=BE=93=E5=BD=A2=E5=BC=8F?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 6 +- utils/message.js | 249 ++++++++++++++++++----------------------------- 2 files changed, 100 insertions(+), 155 deletions(-) diff --git a/package.json b/package.json index 4dab165..2302f86 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "@waylaidwanderer/chatgpt-api": "^1.33.2", "chatgpt": "^5.1.1", "delay": "^5.0.0", + "eventsource": "^2.0.2", + "http": "0.0.1-security", "https-proxy-agent": "5.0.1", "keyv": "^4.5.2", "keyv-file": "^0.2.0", @@ -14,10 +16,10 @@ "random": "^4.1.0", "undici": "^5.21.0", "uuid": "^9.0.0", - "ws": "^8.13.0" + "ws": "^8.13.0", + "eventsource-parser": "^1.0.0" }, "optionalDependencies": { - "eventsource-parser": "^1.0.0", "jimp": "^0.22.7", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-recaptcha": "^3.6.8", diff --git a/utils/message.js b/utils/message.js index b1ef380..860dd33 100644 --- a/utils/message.js +++ b/utils/message.js @@ -1,62 +1,27 @@ import { v4 as uuidv4 } from 'uuid' import { Config, officialChatGPTAPI } from './config.js' -import fetch from 'node-fetch' -import delay from 'delay' -import _ from 'lodash' -// import { createParser } from 'eventsource-parser' -let createParser -try { - createParser = (await import('eventsource-parser')).createParser -} catch (e) { - console.warn('未安装eventsource-parser,请在插件目录下执行pnpm i') -} +import https from 'https' +import http from 'http' +import { createParser } from 'eventsource-parser' -let proxy -if (Config.proxy) { - try { - proxy = (await import('https-proxy-agent')).default - } catch (e) { - console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent') - } -} // API3 export class OfficialChatGPTClient { constructor (opts = {}) { const { accessToken, - apiReverseUrl, - timeoutMs + apiReverseUrl } = opts this._accessToken = accessToken this._apiReverseUrl = apiReverseUrl - this._timeoutMs = timeoutMs - this._fetch = (url, options = {}) => { - const defaultOptions = Config.proxy - ? { - agent: proxy(Config.proxy) - } - : {} - const mergedOptions = { - ...defaultOptions, - ...options - } - - return fetch(url, mergedOptions) - } } async sendMessage (prompt, opts = {}) { let { - timeoutMs = this._timeoutMs, conversationId, parentMessageId = uuidv4(), messageId = uuidv4(), action = 'next' } = opts - let abortController = null - if (timeoutMs) { - abortController = new AbortController() - } let url = this._apiReverseUrl || officialChatGPTAPI if (this._apiReverseUrl && Config.proxy && !Config.apiForceUseReverse) { // 如果配了proxy,而且有反代,但是没开启强制反代 @@ -80,96 +45,102 @@ export class OfficialChatGPTClient { if (conversationId) { body.conversation_id = conversationId } - let option = { - method: 'POST', - body: JSON.stringify(body), - signal: abortController?.signal, - headers: { - accept: 'text/event-stream', - 'x-openai-assistant-app-id': '', - authorization: `Bearer ${this._accessToken}`, - 'content-type': 'application/json', - referer: 'https://chat.openai.com/chat', - library: 'chatgpt-plugin' - }, - referrer: 'https://chat.openai.com/chat' - } - const res = await this._fetch(url, option) - if (res.status === 403) { - await delay(500) - return await this.sendMessage(prompt, opts) - } - if (res.status !== 200) { - let body = await res.text() - if (body.indexOf('Conversation not found') > -1) { - throw new Error('对话不存在,请使用指令”#结束对话“结束当前对话后重新开始对话。') - } else { - throw new Error(body) + let conversationResponse + let statusCode + let requestP = new Promise((resolve, reject) => { + let option = { + method: 'POST', + headers: { + accept: 'text/event-stream', + 'x-openai-assistant-app-id': '', + authorization: `Bearer ${this._accessToken}`, + 'content-type': 'application/json', + referer: 'https://chat.openai.com/chat', + library: 'chatgpt-plugin' + }, + referrer: 'https://chat.openai.com/chat' } - } - if (createParser) { - let conversationResponse - const responseP = new Promise( - // eslint-disable-next-line no-async-promise-executor - async (resolve, reject) => { - let response - function onMessage (data) { - if (data === '[DONE]') { - return resolve({ - error: null, - response, - conversationId, - messageId, - conversationResponse - }) - } - try { - const _checkJson = JSON.parse(data) - } catch (error) { - // console.log('warning: parse error.') - return - } - try { - const convoResponseEvent = JSON.parse(data) - conversationResponse = convoResponseEvent - if (convoResponseEvent.conversation_id) { - conversationId = convoResponseEvent.conversation_id - } - - if (convoResponseEvent.message?.id) { - messageId = convoResponseEvent.message.id - } - - const partialResponse = - convoResponseEvent.message?.content?.parts?.[0] - if (partialResponse) { - if (Config.debug) { - logger.info(JSON.stringify(convoResponseEvent)) - } - response = partialResponse - } - } catch (err) { - console.warn('fetchSSE onMessage unexpected error', err) - reject(err) - } + let requestLib = url.startsWith('https') ? https : http + const req = requestLib.request(url, option, (res) => { + statusCode = res.statusCode + let response + function onMessage (data) { + if (data === '[DONE]') { + return resolve({ + error: null, + response, + conversationId, + messageId, + conversationResponse + }) } + try { + const _checkJson = JSON.parse(data) + } catch (error) { + // console.log('warning: parse error.') + return + } + try { + const convoResponseEvent = JSON.parse(data) + conversationResponse = convoResponseEvent + if (convoResponseEvent.conversation_id) { + conversationId = convoResponseEvent.conversation_id + } - const parser = createParser((event) => { - if (event.type === 'event') { - onMessage(event.data) + if (convoResponseEvent.message?.id) { + messageId = convoResponseEvent.message.id } - }) - res.body.on('readable', async () => { - logger.mark('成功连接到chat.openai.com,准备读取数据流') - let chunk - while ((chunk = res.body.read()) !== null) { - let str = chunk.toString() - parser.feed(str) + + const partialResponse = + convoResponseEvent.message?.content?.parts?.[0] + if (partialResponse) { + if (Config.debug) { + logger.info(JSON.stringify(convoResponseEvent)) + } + response = partialResponse } - }) + } catch (err) { + console.warn('fetchSSE onMessage unexpected error', err) + reject(err) + } } - ) - let response = await responseP + + const parser = createParser((event) => { + if (event.type === 'event') { + onMessage(event.data) + } + }) + const errBody = [] + res.on('data', (chunk) => { + // logger.mark('成功连接到chat.openai.com,准备读取数据流') + if (statusCode === 200) { + let str = chunk.toString() + parser.feed(str) + } + errBody.push(chunk) + }) + + // const body = [] + // res.on('data', (chunk) => body.push(chunk)) + res.on('end', () => { + const resString = Buffer.concat(errBody).toString() + reject(resString) + }) + }) + req.on('error', (err) => { + reject(err) + }) + + req.on('timeout', () => { + req.destroy() + reject(new Error('Request time out')) + }) + + req.write(JSON.stringify(body)) + req.end() + }) + const response = await requestP + if (statusCode === 200) { return { text: response.response, conversationId: response.conversationId, @@ -177,36 +148,8 @@ export class OfficialChatGPTClient { parentMessageId } } else { - logger.warn('未安装eventsource-parser,强烈建议安装以提高API3响应性能,在插件目录下执行pnpm i或pnpm add -w eventsource-parser') - const decoder = new TextDecoder('utf-8') - const bodyBytes = await res.arrayBuffer() - const bodyText = decoder.decode(bodyBytes) - const events = bodyText.split('\n\n').filter(item => !_.isEmpty(item)) - let fullResponse - for (let i = 0; i < events.length; i++) { - let event = events[i] - event = _.trimStart(event, 'data: ') - try { - let tmp = JSON.parse(event) - if (tmp.message) { - fullResponse = tmp - } - } catch (err) { - // console.log(event) - } - } - if (Config.debug) { - logger.mark(JSON.stringify(fullResponse)) - } - if (!fullResponse?.message) { - throw new Error(bodyText || 'unkown error, please check log') - } - return { - text: fullResponse.message.content.parts[0], - conversationId: fullResponse.conversation_id, - id: fullResponse.message.id, - parentMessageId - } + console.log(response) + throw new Error(response) } } }