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模式会调用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方法一样。
> * 浏览器模式通过在本地启动Chrome等浏览器模拟用户访问ChatGPT网站使得获得和官方以及API2模式一模一样的回复质量同时保证安全性。缺点是本方法对环境要求较高需要提供桌面环境和一个可用的代理能够访问ChatGPT的IP地址且响应速度不如API而且高峰期容易无法使用。一般作为API3的下位替代。
> * 必应Bing将调用微软新必应接口进行对话。需要在必应网页能够正常使用新必应且设置有效的Bing登录Cookie方可使用。

View file

@ -12,6 +12,15 @@ import { OfficialChatGPTClient } from '../utils/message.js'
import fetch from 'node-fetch'
import { deleteConversation, getConversations, getLatestMessageIdByConversationId } from '../utils/conversation.js'
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是保留上下文的超时后销毁对话再次对话创建新的对话
* 单位
@ -21,7 +30,20 @@ let version = Config.version
*/
// 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 newFetch = (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
}
export class chatgpt extends plugin {
constructor () {
let toggleMode = Config.toggleMode
@ -170,7 +192,7 @@ export class chatgpt extends plugin {
await this.reply('指令格式错误请同时加上对话id或@某人以删除他当前进行的对话', true)
return false
} else {
let deleteResponse = await deleteConversation(conversationId)
let deleteResponse = await deleteConversation(conversationId, newFetch)
console.log(deleteResponse)
let deleted = 0
let qcs = await redis.keys('CHATGPT:QQ_CONVERSATION:*')
@ -370,7 +392,7 @@ export class chatgpt extends plugin {
if (conversationId) {
let lastMessageId = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_ID:${conversationId}`)
if (!lastMessageId) {
lastMessageId = await getLatestMessageIdByConversationId(conversationId)
lastMessageId = await getLatestMessageIdByConversationId(conversationId, newFetch)
}
// let lastMessagePrompt = await redis.get(`CHATGPT:CONVERSATION_LAST_MESSAGE_PROMPT:${conversationId}`)
// let conversationCreateTime = await redis.get(`CHATGPT:CONVERSATION_CREATE_TIME:${conversationId}`)
@ -535,7 +557,7 @@ export class chatgpt extends plugin {
quotes: quote,
cache: cacheData,
version
},{retType: Config.quoteReply ? 'base64' : ''}), e.isGroup && Config.quoteReply)
}, { retType: Config.quoteReply ? 'base64' : '' }), e.isGroup && Config.quoteReply)
}
async sendMessage (prompt, conversation = {}, use, e) {
@ -683,7 +705,7 @@ export class chatgpt extends plugin {
systemMessage: promptPrefix,
completionParams,
assistantLabel: Config.assistantLabel,
fetch
fetch: newFetch
})
let option = {
timeoutMs: 120000,
@ -714,7 +736,7 @@ export class chatgpt extends plugin {
async getAllConversations (e) {
const use = await redis.get('CHATGPT:USE')
if (use === 'api3') {
let conversations = await getConversations(e.sender.user_id)
let conversations = await getConversations(e.sender.user_id, newFetch)
if (Config.debug) {
logger.mark('all conversations: ', conversations)
}
@ -779,7 +801,7 @@ export class chatgpt extends plugin {
return false
}
// 查询OpenAI API剩余试用额度
fetch(`${Config.openAiBaseUrl}/dashboard/billing/credit_grants`, {
newFetch(`${Config.openAiBaseUrl}/dashboard/billing/credit_grants`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',

View file

@ -14,7 +14,7 @@ export function supportGuoba () {
link: 'https://github.com/ikechan8370/chatgpt-plugin',
isV3: true,
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 这里进行搜索
icon: 'simple-icons:openai',

View file

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

View file

@ -1,12 +1,12 @@
import fetch from 'node-fetch'
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')
if (!accessToken) {
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',
headers: {
'Content-Type': 'application/json',
@ -33,7 +33,7 @@ export async function getConversations (qq = '') {
map[item.id] = cachedConversationLastMessage
} else {
// 缓存中没有就去查官方api
let conversationDetailResponse = await fetch(`${Config.apiBaseUrl}/api/conversation/${item.id}`, {
let conversationDetailResponse = await fetchFn(`${Config.apiBaseUrl}/api/conversation/${item.id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
@ -96,12 +96,12 @@ export async function getConversations (qq = '') {
return res
}
export async function getLatestMessageIdByConversationId (conversationId) {
export async function getLatestMessageIdByConversationId (conversationId, fetchFn = fetch) {
let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) {
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',
headers: {
'Content-Type': 'application/json',
@ -124,12 +124,12 @@ export async function getLatestMessageIdByConversationId (conversationId) {
}
// 调用chat.open.com删除某一个对话。该操作不可逆。
export async function deleteConversation (conversationId) {
export async function deleteConversation (conversationId, fetchFn = fetch) {
let accessToken = await redis.get('CHATGPT:TOKEN')
if (!accessToken) {
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',
headers: {
'Content-Type': 'application/json',

View file

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