Skip to content

Commit

Permalink
feat: api and api3 support proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
ikechan8370 committed Mar 3, 2023
1 parent 24c80d1 commit f4a0733
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 27 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 API,ChatGPT官网同款模型,只需要提供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方可使用。
Expand Down Expand Up @@ -71,11 +71,11 @@ pnpm i
> 3. 使用vnc客户端连接至云桌面
>
> 右键Applications > Shells > Bash打开终端,然后进入Yunzai目录下运行node app即可。
>
>
> 4. 执行pnpm i时,sharp安装失败
>
> sharp不影响chatgpt聊天,仅影响Dalle2绘图功能。ubuntu可以执行`apt install libvips-dev`之后再`pnpm i`
>
>
> 实测该方案资源占用低,运行稳定,基本1核2G的轻量云服务器就足够了。
---
Expand Down Expand Up @@ -142,7 +142,7 @@ pnpm i
>
> 登录www.bing.com,刷新一下网页,按F12或直接打开开发者模式,点击Application/存储,点击左侧Storage下的Cookies,展开找到[https://www.bing.com](https://www.bing.com/) 项,在右侧列表Name项下找到"\_U",_U的value即为必应Token
>
>
>
>
> 其他问题可以参考使用的api库 https://github.com/transitive-bullshit/chatgpt-api 以及 https://github.com/waylaidwanderer/node-chatgpt-api
Expand Down Expand Up @@ -251,7 +251,7 @@ git stash pop

> 一般情况下请按照 [安装](#安装) 小节的内容重新安装依赖即可
>
>
>
>
> 最多的问题:载入插件错误:chat
>
Expand All @@ -260,8 +260,8 @@ git stash pop
> 原因:没装依赖
>
> 解决方式:请参考文档在本插件目录下用`pnmp install`安装依赖,安装完就不报错了


## 感谢

Expand Down
34 changes: 28 additions & 6 deletions apps/chat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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是保留上下文的。超时后销毁对话,再次对话创建新的对话。
* 单位:秒
Expand All @@ -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. don’t 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
Expand Down Expand Up @@ -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:*')
Expand Down Expand Up @@ -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}`)
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -683,7 +705,7 @@ export class chatgpt extends plugin {
systemMessage: promptPrefix,
completionParams,
assistantLabel: Config.assistantLabel,
fetch
fetch: newFetch
})
let option = {
timeoutMs: 120000,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion guoba.support.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 7 additions & 7 deletions utils/conversation.js
Original file line number Diff line number Diff line change
@@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand Down
28 changes: 24 additions & 4 deletions utils/message.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 = {}) {
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit f4a0733

Please sign in to comment.