feat: api and api3 support proxy

This commit is contained in:
ikechan8370 2023-03-03 11:03:51 +08:00
parent 24c80d159b
commit f4a073372f
6 changed files with 69 additions and 27 deletions

View file

@ -23,7 +23,7 @@ Node.js >= 18 / Node.js >= 14(with node-fetch)
> #### API模式和浏览器模式如何选择 > #### API模式和浏览器模式如何选择
> >
> * API模式会调用OpenAI官方提供的GPT-3 LLM API只需要提供API Key。一般情况下该种方式响应速度更快可配置项多且不会像chatGPT官网一样总出现不可用的现象其聊天效果明显较官网差。但注意GPT-3的API调用是收费的新用户有18美元试用金可用于支付价格为`$0.0200/1K tokens`。(问题和回答**加起来**算token > * API模式会调用OpenAI官方提供的gpt-3.5-turbo APIChatGPT官网同款模型只需要提供API Key。一般情况下该种方式响应速度更快可配置项多且不会像chatGPT官网一样总出现不可用的现象注意API调用是收费的新用户有18美元试用金可用于支付价格为`$0.0020/1K tokens`。(问题和回答**加起来**算token
> * API3模式会调用第三方提供的官网反代API他会帮你绕过CF防护需要提供ChatGPT的Token。效果与官网和浏览器一致但稳定性不一定。设置token和API2方法一样。 > * API3模式会调用第三方提供的官网反代API他会帮你绕过CF防护需要提供ChatGPT的Token。效果与官网和浏览器一致但稳定性不一定。设置token和API2方法一样。
> * 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站使得获得和官方以及API2模式一模一样的回复质量同时保证安全性。缺点是本方法对环境要求较高需要提供桌面环境和一个可用的代理能够访问ChatGPT的IP地址且响应速度不如API而且高峰期容易无法使用。一般作为API3的下位替代。 > * 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站使得获得和官方以及API2模式一模一样的回复质量同时保证安全性。缺点是本方法对环境要求较高需要提供桌面环境和一个可用的代理能够访问ChatGPT的IP地址且响应速度不如API而且高峰期容易无法使用。一般作为API3的下位替代。
> * 必应Bing将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing登录Cookie方可使用。 > * 必应Bing将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing登录Cookie方可使用。

View file

@ -12,6 +12,15 @@ import { OfficialChatGPTClient } from '../utils/message.js'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js' import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js'
let version = Config.version let version = Config.version
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')
}
}
/** /**
* 每个对话保留的时长单个对话内ai是保留上下文的超时后销毁对话再次对话创建新的对话 * 每个对话保留的时长单个对话内ai是保留上下文的超时后销毁对话再次对话创建新的对话
* 单位 * 单位
@ -21,7 +30,20 @@ let version = Config.version
*/ */
// const CONVERSATION_PRESERVE_TIME = Config.conversationPreserveTime // const CONVERSATION_PRESERVE_TIME = Config.conversationPreserveTime
const defaultPropmtPrefix = 'You answer as concisely as possible for each response (e.g. dont be verbose). It is very important that you answer as concisely as possible, so please remember this. If you are generating a list, do not have too many items. Keep the number of items short.' const defaultPropmtPrefix = 'You answer as concisely as possible for each response (e.g. dont be verbose). It is very important that you answer as concisely as possible, so please remember this. If you are generating a list, do not have too many items. Keep the number of items short.'
const newFetch = (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
}
export class chatgpt extends plugin { export class chatgpt extends plugin {
constructor () { constructor () {
let toggleMode = Config.toggleMode let toggleMode = Config.toggleMode
@ -170,7 +192,7 @@ export class chatgpt extends plugin {
await this.reply('指令格式错误请同时加上对话id或@某人以删除他当前进行的对话', true) await this.reply('指令格式错误请同时加上对话id或@某人以删除他当前进行的对话', true)
return false return false
} else { } else {
let deleteResponse = await deleteConversation(conversationId) let deleteResponse = await deleteConversation(conversationId, newFetch)
console.log(deleteResponse) console.log(deleteResponse)
let deleted = 0 let deleted = 0
let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*') let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*')
@ -370,7 +392,7 @@ export class chatgpt extends plugin {
if (conversationId) { if (conversationId) {
let lastMessageId = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_ID:${conversationId}`) let lastMessageId = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_ID:${conversationId}`)
if (!lastMessageId) { if (!lastMessageId) {
lastMessageId = await getLatestMessageIdByConversationId(conversationId) lastMessageId = await getLatestMessageIdByConversationId(conversationId, newFetch)
} }
// let lastMessagePrompt = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${conversationId}`) // let lastMessagePrompt = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${conversationId}`)
// let conversationCreateTime = await redis.get(`CHATGPT:CONVERSATION_CREATE_TIME:${conversationId}`) // let conversationCreateTime = await redis.get(`CHATGPT:CONVERSATION_CREATE_TIME:${conversationId}`)
@ -683,7 +705,7 @@ export class chatgpt extends plugin {
systemMessage: promptPrefix, systemMessage: promptPrefix,
completionParams, completionParams,
assistantLabel: Config.assistantLabel, assistantLabel: Config.assistantLabel,
fetch fetch: newFetch
}) })
let option = { let option = {
timeoutMs: 120000, timeoutMs: 120000,
@ -714,7 +736,7 @@ export class chatgpt extends plugin {
async getAllConversations (e) { async getAllConversations (e) {
const use = await redis.get('CHATGPT:USE') const use = await redis.get('CHATGPT:USE')
if (use === 'api3') { if (use === 'api3') {
let conversations = await getConversations(e.sender.user_id) let conversations = await getConversations(e.sender.user_id, newFetch)
if (Config.debug) { if (Config.debug) {
logger.mark('all conversations: ', conversations) logger.mark('all conversations: ', conversations)
} }
@ -779,7 +801,7 @@ export class chatgpt extends plugin {
return false return false
} }
// 查询OpenAI API剩余试用额度 // 查询OpenAI API剩余试用额度
fetch(`${Config.openAiBaseUrl}/dashboard/billing/credit_grants`, { newFetch(`${Config.openAiBaseUrl}/dashboard/billing/credit_grants`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View file

@ -14,7 +14,7 @@ export function supportGuoba () {
link: 'https://github.com/ikechan8370/chatgpt-plugin', link: 'https://github.com/ikechan8370/chatgpt-plugin',
isV3: true, isV3: true,
isV2: false, isV2: false,
description: '基于OpenAI最新推出的chatgpt和微软的 New bing通过api进行问答的插件需自备openai账号或有New bing访问权限的必应账号', description: '基于OpenAI最新推出的chatgpt和微软的 New bing通过api进行聊天的插件需自备openai账号或有New bing访问权限的必应账号',
// 显示图标,此为个性化配置 // 显示图标,此为个性化配置
// 图标可在 https://icon-sets.iconify.design 这里进行搜索 // 图标可在 https://icon-sets.iconify.design 这里进行搜索
icon: 'simple-icons:openai', icon: 'simple-icons:openai',

View file

@ -3,8 +3,8 @@
"type": "module", "type": "module",
"author": "ikechan8370", "author": "ikechan8370",
"dependencies": { "dependencies": {
"@waylaidwanderer/chatgpt-api": "^1.22.5", "@waylaidwanderer/chatgpt-api": "^1.23.0",
"chatgpt": "^5.0.0", "chatgpt": "^5.0.5",
"delay": "^5.0.0", "delay": "^5.0.0",
"keyv-file": "^0.2.0", "keyv-file": "^0.2.0",
"node-fetch": "^3.3.0", "node-fetch": "^3.3.0",

View file

@ -1,12 +1,12 @@
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { Config } from '../utils/config.js' import { Config } from '../utils/config.js'
export async function getConversations (qq = '') { export async function getConversations (qq = '', fetchFn = fetch) {
let accessToken = await redis.get('CHATGPT:TOKEN') let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) { if (!accessToken) {
throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token') throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token')
} }
let response = await fetch(`${Config.apiBaseUrl}/conversations?offset=0&limit=20`, { let response = await fetchFn(`${Config.apiBaseUrl}/conversations?offset=0&limit=20`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -33,7 +33,7 @@ export async function getConversations (qq = '') {
map[item.id] = cachedConversationLastMessage map[item.id] = cachedConversationLastMessage
} else { } else {
// 缓存中没有就去查官方api // 缓存中没有就去查官方api
let conversationDetailResponse = await fetch(`${Config.apiBaseUrl}/api/conversation/${item.id}`, { let conversationDetailResponse = await fetchFn(`${Config.apiBaseUrl}/api/conversation/${item.id}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -96,12 +96,12 @@ export async function getConversations (qq = '') {
return res return res
} }
export async function getLatestMessageIdByConversationId (conversationId) { export async function getLatestMessageIdByConversationId (conversationId, fetchFn = fetch) {
let accessToken = await redis.get('CHATGPT:TOKEN') let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) { if (!accessToken) {
throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token') throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token')
} }
let conversationDetailResponse = await fetch(`${Config.apiBaseUrl}/api/conversation/${conversationId}`, { let conversationDetailResponse = await fetchFn(`${Config.apiBaseUrl}/api/conversation/${conversationId}`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -124,12 +124,12 @@ export async function getLatestMessageIdByConversationId (conversationId) {
} }
// 调用chat.open.com删除某一个对话。该操作不可逆。 // 调用chat.open.com删除某一个对话。该操作不可逆。
export async function deleteConversation (conversationId) { export async function deleteConversation (conversationId, fetchFn = fetch) {
let accessToken = await redis.get('CHATGPT:TOKEN') let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) { if (!accessToken) {
throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token') throw new Error('未绑定ChatGPT AccessToken请使用#chatgpt设置token命令绑定token')
} }
let response = await fetch(`${Config.apiBaseUrl}/api/conversation/${conversationId}`, { let response = await fetchFn(`${Config.apiBaseUrl}/api/conversation/${conversationId}`, {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',

View file

@ -3,6 +3,15 @@ import { Config } from '../utils/config.js'
import HttpsProxyAgent from 'https-proxy-agent' import HttpsProxyAgent from 'https-proxy-agent'
import _ from 'lodash' import _ from 'lodash'
import fetch from 'node-fetch' import fetch from 'node-fetch'
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')
}
}
export class OfficialChatGPTClient { export class OfficialChatGPTClient {
constructor (opts = {}) { constructor (opts = {}) {
const { const {
@ -13,6 +22,20 @@ export class OfficialChatGPTClient {
this._accessToken = accessToken this._accessToken = accessToken
this._apiReverseUrl = apiReverseUrl this._apiReverseUrl = apiReverseUrl
this._timeoutMs = timeoutMs 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 = {}) { async sendMessage (prompt, opts = {}) {
@ -59,10 +82,7 @@ export class OfficialChatGPTClient {
}, },
referrer: 'https://chat.openai.com/chat' referrer: 'https://chat.openai.com/chat'
} }
if (Config.proxy) { const res = await this._fetch(url, option)
option.agent = new HttpsProxyAgent(Config.proxy)
}
const res = await fetch(url, option)
const decoder = new TextDecoder('utf-8') const decoder = new TextDecoder('utf-8')
const bodyBytes = await res.arrayBuffer() const bodyBytes = await res.arrayBuffer()
const bodyText = decoder.decode(bodyBytes) const bodyText = decoder.decode(bodyBytes)