mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
commit
04c6a5c8c7
7 changed files with 207 additions and 400 deletions
|
|
@ -357,6 +357,10 @@ export class ChatgptManagement extends plugin {
|
|||
reg: '^#chatgpt(伪人|bym)切换',
|
||||
fnc: 'switchBYMModel',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#(chatgpt)?(Copilot|Bing|必应)配置方法',
|
||||
fnc: 'copilotSetting'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
@ -1880,6 +1884,25 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
await this.reply('切换成功')
|
||||
}
|
||||
|
||||
async copilotSetting (e) {
|
||||
const code = 'let results = []\n' +
|
||||
'Object.keys(localStorage).forEach(key => {\n' +
|
||||
' try {\n' +
|
||||
' let value = JSON.parse(localStorage[key])\n' +
|
||||
' if (key.includes(\'accesstoken\') && value.target?.includes(\'ChatAI\')) {\n' +
|
||||
' results[\'accessToken\'] = value.secret\n' +
|
||||
' results[\'clientId\'] = value.clientId\n' +
|
||||
' results[\'scope\'] = value.target + \' openid profile offline_access\'\n' +
|
||||
' } else if (key.includes(\'refreshtoken\')) {\n' +
|
||||
' results[\'oid\'] = value.homeAccountId\n' +
|
||||
' results[\'refreshToken\'] = value.secret\n' +
|
||||
' }\n' +
|
||||
' } catch (err) {}\n' +
|
||||
'})\n' +
|
||||
'console.log(results)'
|
||||
e.reply(`可以在浏览器控制台使用以下代码获取相关配置。\n\`\`\`javacript\n${code}\n\`\`\``)
|
||||
}
|
||||
|
||||
async geminiOpenSearchCE (e) {
|
||||
let msg = e.msg
|
||||
let open = msg.includes('开启')
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import WebSocket from 'ws'
|
||||
import common from '../../../lib/common/common.js'
|
||||
import _ from 'lodash'
|
||||
import { pTimeout } from '../utils/common.js'
|
||||
|
||||
export class BingAIClient {
|
||||
constructor (accessToken, baseUrl = 'wss://copilot.microsoft.com/c/api/chat', debug, _2captchaKey, clientId, scope, refreshToken, oid) {
|
||||
constructor (accessToken, baseUrl = 'wss://copilot.microsoft.com', debug, _2captchaKey, clientId, scope, refreshToken, oid, reasoning = false) {
|
||||
this.accessToken = accessToken
|
||||
this.baseUrl = baseUrl
|
||||
if (this.baseUrl.endsWith('/')) {
|
||||
this.baseUrl = _.trimEnd(baseUrl, '/')
|
||||
}
|
||||
this.ws = null
|
||||
this.conversationId = null
|
||||
this.partialMessages = new Map()
|
||||
|
|
@ -14,6 +19,7 @@ export class BingAIClient {
|
|||
this.scope = scope
|
||||
this.refreshToken = refreshToken
|
||||
this.oid = oid
|
||||
this.reasoning = reasoning
|
||||
}
|
||||
|
||||
async sendMessage (text, options = {}) {
|
||||
|
|
@ -21,7 +27,7 @@ export class BingAIClient {
|
|||
if (options.conversationId) {
|
||||
this.conversationId = options.conversationId
|
||||
} else {
|
||||
this.conversationId = this._generateConversationId()
|
||||
this.conversationId = await this._generateConversationId()
|
||||
}
|
||||
|
||||
// 建立 WebSocket 连接
|
||||
|
|
@ -31,16 +37,32 @@ export class BingAIClient {
|
|||
await this.sendInitialMessage(text)
|
||||
|
||||
// 等待并收集服务器的回复
|
||||
const responseText = await this.collectResponse()
|
||||
return responseText
|
||||
try {
|
||||
const responseText = await pTimeout(await this.collectResponse(), {
|
||||
milliseconds: 1000 * 60 * 5
|
||||
})
|
||||
return responseText
|
||||
} catch (err) {
|
||||
if (this.partialMessages.get(this.currentMessageId)) {
|
||||
return this.partialMessages.get(this.currentMessageId).text
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async connectWebSocket () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let url = `${this.baseUrl}?api-version=2`
|
||||
let wsUrl = this.baseUrl
|
||||
if (wsUrl.startsWith('http')) {
|
||||
wsUrl = wsUrl.replace('https://', 'wss://')
|
||||
.replace('http://', 'ws://')
|
||||
}
|
||||
let url = `${wsUrl}/c/api/chat?api-version=2`
|
||||
if (this.accessToken) {
|
||||
url += '&accessToken=' + this.accessToken
|
||||
}
|
||||
logger.info('ws url: ' + url)
|
||||
this.ws = new WebSocket(url)
|
||||
|
||||
this.ws.on('open', () => {
|
||||
|
|
@ -50,13 +72,12 @@ export class BingAIClient {
|
|||
|
||||
if (this.debug) {
|
||||
this.ws.on('message', (message) => {
|
||||
logger.info(JSON.stringify(message))
|
||||
logger.info('received message', String(message))
|
||||
})
|
||||
}
|
||||
this.ws.on('close', (code, reason) => {
|
||||
console.log('WebSocket connection closed. Code:', code, 'Reason:', reason)
|
||||
|
||||
// 401 错误码通常是未授权,可以根据实际情况修改
|
||||
if (code === 401) {
|
||||
logger.error('token expired. try to refresh with refresh token')
|
||||
this.doRefreshToken(this.clientId, this.scope, this.refreshToken, this.oid)
|
||||
|
|
@ -71,33 +92,44 @@ export class BingAIClient {
|
|||
|
||||
async sendInitialMessage (text) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const initMgs = { event: 'setOptions', supportedCards: ['image'], ads: null }
|
||||
this.ws.send(JSON.stringify(initMgs))
|
||||
if (this.debug) {
|
||||
logger.info('send msg: ', JSON.stringify(initMgs))
|
||||
}
|
||||
const messagePayload = {
|
||||
event: 'send',
|
||||
conversationId: this.conversationId,
|
||||
content: [{ type: 'text', text }],
|
||||
mode: 'chat',
|
||||
mode: this.reasoning ? 'reasoning' : 'chat',
|
||||
context: { edge: 'NonContextual' }
|
||||
}
|
||||
|
||||
// 直接发送消息
|
||||
this.ws.send(JSON.stringify(messagePayload))
|
||||
|
||||
if (this.debug) {
|
||||
logger.info('send msg: ', JSON.stringify(messagePayload))
|
||||
}
|
||||
let _this = this
|
||||
// 设置超时机制,防止长时间未收到消息
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('No response from server within timeout period.'))
|
||||
}, 5000) // 设置 5 秒的超时时间
|
||||
|
||||
// 一旦收到消息,处理逻辑
|
||||
this.ws.once('message', (data) => {
|
||||
clearTimeout(timeout) // 清除超时定时器
|
||||
const message = JSON.parse(data)
|
||||
|
||||
logger.info(data)
|
||||
if (message.event === 'challenge') {
|
||||
logger.info(JSON.stringify(message))
|
||||
logger.warn('遇到turnstile验证码,尝试使用2captcha解决')
|
||||
// 如果收到 challenge,处理挑战
|
||||
this.handleChallenge(message)
|
||||
.then(resolve)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
_this.ws.send(JSON.stringify(messagePayload))
|
||||
resolve()
|
||||
}, 500)
|
||||
})
|
||||
.catch(reject)
|
||||
} else {
|
||||
// 否则直接进入对话
|
||||
|
|
@ -152,14 +184,15 @@ export class BingAIClient {
|
|||
this.partialMessages.get(message.messageId).text += message.text
|
||||
|
||||
// 如果是最后一部分,标记为完成
|
||||
if (message.partId === '0') {
|
||||
this.partialMessages.get(message.messageId).done = true
|
||||
}
|
||||
// if (message.partId === '0') {
|
||||
// this.partialMessages.get(message.messageId).done = true
|
||||
// }
|
||||
|
||||
checkMessageComplete(message.messageId)
|
||||
break
|
||||
|
||||
case 'partCompleted':
|
||||
this.partialMessages.get(message.messageId).done = true
|
||||
break
|
||||
|
||||
case 'done':
|
||||
|
|
@ -167,7 +200,7 @@ export class BingAIClient {
|
|||
break
|
||||
|
||||
default:
|
||||
console.warn('Unexpected event:', message.event)
|
||||
// console.warn('Unexpected event:', message.event)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
|
@ -203,6 +236,7 @@ export class BingAIClient {
|
|||
taskId,
|
||||
clientKey: this._2captchaKey
|
||||
})
|
||||
|
||||
async function getTaskResult () {
|
||||
const requestOptions2 = {
|
||||
method: 'POST',
|
||||
|
|
@ -213,12 +247,11 @@ export class BingAIClient {
|
|||
|
||||
const response2 = await fetch('https://api.2captcha.com/getTaskResult', requestOptions2)
|
||||
const taskResponse = await response2.json()
|
||||
if (this.debug) {
|
||||
logger.info(JSON.stringify(taskResponse))
|
||||
}
|
||||
logger.info(JSON.stringify(taskResponse))
|
||||
const token = taskResponse?.solution?.token
|
||||
return token
|
||||
}
|
||||
|
||||
let retry = 90
|
||||
let token = await getTaskResult()
|
||||
while (retry > 0 && !token) {
|
||||
|
|
@ -232,8 +265,40 @@ export class BingAIClient {
|
|||
return token
|
||||
}
|
||||
|
||||
_generateConversationId () {
|
||||
return 'conversation-' + Math.random().toString(36).substring(2, 15)
|
||||
async _generateConversationId () {
|
||||
const url = `${this.baseUrl}/c/api/conversations`
|
||||
const createConversationRsp = await fetch(url, {
|
||||
headers: {
|
||||
authorization: `Bearer ${this.accessToken}`,
|
||||
'content-type': 'application/json',
|
||||
origin: 'https://copilot.microsoft.com',
|
||||
referer: 'https://copilot.microsoft.com/',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'
|
||||
},
|
||||
method: 'POST'
|
||||
})
|
||||
const conversation = await createConversationRsp.json()
|
||||
return conversation.id
|
||||
}
|
||||
|
||||
async _getCurrentConversationId () {
|
||||
const url = `${this.baseUrl}/c/api/start`
|
||||
const createConversationRsp = await fetch(url, {
|
||||
headers: {
|
||||
authorization: `Bearer ${this.accessToken}`,
|
||||
'content-type': 'application/json',
|
||||
origin: 'https://copilot.microsoft.com',
|
||||
referer: 'https://copilot.microsoft.com/',
|
||||
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'
|
||||
},
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
timeZone: 'Asia/Shanghai',
|
||||
teenSupportEnabled: true
|
||||
})
|
||||
})
|
||||
const conversation = await createConversationRsp.json()
|
||||
return conversation.currentConversationId
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -270,7 +335,7 @@ export class BingAIClient {
|
|||
urlencoded.append('x-ms-lib-capability', 'retry-after, h429')
|
||||
urlencoded.append('x-client-current-telemetry', '5|61,0,,,|,')
|
||||
urlencoded.append('x-client-last-telemetry', '5|3|||0,0')
|
||||
urlencoded.append('client-request-id', '0193875c-0737-703c-a2e7-6730dd56aa4a')
|
||||
urlencoded.append('client-request-id', crypto.randomUUID())
|
||||
urlencoded.append('refresh_token', refreshToken)
|
||||
urlencoded.append('X-AnchorMailbox', 'Oid:' + oid)
|
||||
|
||||
|
|
@ -286,6 +351,7 @@ export class BingAIClient {
|
|||
if (this.debug) {
|
||||
logger.info(JSON.stringify(tokenJson))
|
||||
}
|
||||
|
||||
return tokenJson
|
||||
}
|
||||
}
|
||||
|
|
|
|||
119
guoba.support.js
119
guoba.support.js
|
|
@ -190,28 +190,9 @@ export function supportGuoba () {
|
|||
component: 'Divider'
|
||||
},
|
||||
{
|
||||
field: 'toneStyle',
|
||||
label: 'Bing模式',
|
||||
bottomHelpMessage: 'Copilot的应答风格。默认为创意,可切换为精准或均衡,均为GPT-turbo',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
options: [
|
||||
{ label: '创意', value: 'Creative' },
|
||||
{ label: '均衡', value: 'Balanced' },
|
||||
{ label: '精准', value: 'Precise' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'sydneyEnableSearch',
|
||||
label: '是否允许必应进行搜索',
|
||||
bottomHelpMessage: '关闭后必应将禁用搜索',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'enableSuggestedResponses',
|
||||
label: '是否开启建议回复',
|
||||
bottomHelpMessage: '开启了会像官网上一样,每个问题给出建议的用户问题',
|
||||
field: 'bingReasoning',
|
||||
label: 'Bing开启思考',
|
||||
bottomHelpMessage: 'Copilot的思考功能。开启后无法搜索',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
|
|
@ -231,12 +212,6 @@ export function supportGuoba () {
|
|||
bottomHelpMessage: '加强主人认知。希望机器人认清主人,避免NTR可开启。开启后可能会与自设定的内容有部分冲突。sydney模式可以放心开启',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'enableGenerateContents',
|
||||
label: '允许生成图像等内容',
|
||||
bottomHelpMessage: '开启后类似网页版能够发图。但是此选项会占用大量token,自设定等模式下容易爆token',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'groupContextLength',
|
||||
label: '允许机器人读取近期的最多群聊聊天记录条数。',
|
||||
|
|
@ -255,18 +230,6 @@ export function supportGuoba () {
|
|||
bottomHelpMessage: '你可以自己改写设定,让Copilot变成你希望的样子。可能存在不稳定的情况',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
field: 'sydneyApologyIgnored',
|
||||
label: 'Bing抱歉是否不计入聊天记录',
|
||||
bottomHelpMessage: '有时无限抱歉,就关掉这个再多问几次试试,可能有奇效',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'sydneyContext',
|
||||
label: 'Bing的扩展资料',
|
||||
bottomHelpMessage: 'AI将会从你提供的扩展资料中学习到一些知识,帮助它更好地回答你的问题。实际相当于使用edge侧边栏Bing时读取的你当前浏览网页的内容。如果太长可能容易到达GPT-4的8192token上限',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
field: 'sydneyReverseProxy',
|
||||
label: '必应反代',
|
||||
|
|
@ -274,70 +237,40 @@ export function supportGuoba () {
|
|||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'sydneyForceUseReverse',
|
||||
label: '强制使用sydney反代',
|
||||
bottomHelpMessage: '即使配置了proxy,创建对话时依然使用必应反代',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'sydneyWebsocketUseProxy',
|
||||
label: '对话使用必应反代',
|
||||
bottomHelpMessage: '默认情况下仅创建对话走反代,对话时仍然直连微软。开启本选项将使对话过程也走反代,需反代支持。默认开启',
|
||||
component: 'Switch'
|
||||
},
|
||||
{
|
||||
field: 'bingCaptchaOneShotUrl',
|
||||
label: '必应验证码pass服务',
|
||||
bottomHelpMessage: '必应出验证码会自动用该服务绕过',
|
||||
field: 'bingAiToken',
|
||||
label: '必应AccessToken',
|
||||
bottomHelpMessage: 'Copilot的AccessToken,scope需为ChatAI.ReadWrite。可以发送`#Copilot配置方法`查看浏览器获取配置的方法。',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'sydneyMood',
|
||||
label: '情感显示',
|
||||
bottomHelpMessage: '开启Sydney的情感显示,仅在图片模式下生效',
|
||||
component: 'Switch'
|
||||
field: 'bingAiClientId',
|
||||
label: '必应ClientId',
|
||||
bottomHelpMessage: '配合RefreshToken刷新AccessToken',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'sydneyImageRecognition',
|
||||
label: '图片识别',
|
||||
bottomHelpMessage: '开启Sydney的图片识别功能,建议和OCR只保留一个开启',
|
||||
component: 'Switch'
|
||||
field: 'bingAiScope',
|
||||
label: '必应Auth Scope',
|
||||
bottomHelpMessage: '配合RefreshToken刷新AccessToken',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleUser1',
|
||||
label: '前置对话第一轮(用户)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
field: 'bingAiRefreshToken',
|
||||
label: '必应RefreshToken',
|
||||
bottomHelpMessage: '配合RefreshToken刷新AccessToken',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleBot1',
|
||||
label: '前置对话第一轮(AI)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
field: 'bingAiOid',
|
||||
label: '必应Oid',
|
||||
bottomHelpMessage: '(homeAccountId)配合RefreshToken刷新AccessToken',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleUser2',
|
||||
label: '前置对话第二轮(用户)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleBot2',
|
||||
label: '前置对话第二轮(AI)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleUser3',
|
||||
label: '前置对话第三轮(用户)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
},
|
||||
{
|
||||
field: 'chatExampleBot3',
|
||||
label: '前置对话第三轮(AI)',
|
||||
bottomHelpMessage: '会强行插入该轮对话,能有效抑制抱歉',
|
||||
component: 'InputTextArea'
|
||||
field: '_2captchaKey',
|
||||
label: '2captcha API密钥',
|
||||
bottomHelpMessage: '用于解除Copilot的验证码',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
label: '以下为API3方式的配置',
|
||||
|
|
|
|||
340
model/core.js
340
model/core.js
|
|
@ -4,21 +4,15 @@ import {
|
|||
formatDate,
|
||||
getImg,
|
||||
getMasterQQ, getMaxModelTokens,
|
||||
getOrDownloadFile,
|
||||
getUin,
|
||||
getUserData,
|
||||
isCN
|
||||
} from '../utils/common.js'
|
||||
import { KeyvFile } from 'keyv-file'
|
||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||
import _ from 'lodash'
|
||||
import { getChatHistoryGroup } from '../utils/chat.js'
|
||||
import { APTool } from '../utils/tools/APTool.js'
|
||||
import BingDrawClient from '../utils/BingDraw.js'
|
||||
import BingSunoClient from '../utils/BingSuno.js'
|
||||
import { solveCaptchaOneShot } from '../utils/bingCaptcha.js'
|
||||
import { OfficialChatGPTClient } from '../utils/message.js'
|
||||
import ChatGLMClient from '../utils/chatglm.js'
|
||||
import { ClaudeAPIClient } from '../client/ClaudeAPIClient.js'
|
||||
import { ClaudeAIClient } from '../utils/claude.ai/index.js'
|
||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||
|
|
@ -26,8 +20,6 @@ import { getMessageById, upsertMessage } from '../utils/history.js'
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import fetch from 'node-fetch'
|
||||
import { CustomGoogleGeminiClient } from '../client/CustomGoogleGeminiClient.js'
|
||||
import { resizeAndCropImage } from '../utils/dalle.js'
|
||||
import fs from 'fs'
|
||||
import { QueryStarRailTool } from '../utils/tools/QueryStarRailTool.js'
|
||||
import { WebsiteTool } from '../utils/tools/WebsiteTool.js'
|
||||
import { SendPictureTool } from '../utils/tools/SendPictureTool.js'
|
||||
|
|
@ -59,6 +51,9 @@ import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
|
|||
import { newFetch } from '../utils/proxy.js'
|
||||
import { ChatGLM4Client } from '../client/ChatGLM4Client.js'
|
||||
import { QwenApi } from '../utils/alibaba/qwen-api.js'
|
||||
import { BingAIClient } from '../client/CopilotAIClient.js'
|
||||
import Keyv from 'keyv'
|
||||
import crypto from 'crypto'
|
||||
|
||||
const roleMap = {
|
||||
owner: 'group owner',
|
||||
|
|
@ -145,287 +140,70 @@ class Core {
|
|||
const userData = await getUserData(e.user_id)
|
||||
const useCast = userData.cast || {}
|
||||
if (use === 'bing') {
|
||||
let throttledTokens = []
|
||||
let {
|
||||
bingToken,
|
||||
allThrottled
|
||||
} = await getAvailableBingToken(conversation, throttledTokens)
|
||||
let cookies
|
||||
if (bingToken?.indexOf('=') > -1) {
|
||||
cookies = bingToken
|
||||
}
|
||||
let bingAIClient
|
||||
const cacheOptions = {
|
||||
namespace: Config.toneStyle,
|
||||
store: new KeyvFile({ filename: 'cache.json' })
|
||||
}
|
||||
bingAIClient = new SydneyAIClient({
|
||||
userToken: bingToken, // "_U" cookie from bing.com
|
||||
cookies,
|
||||
debug: Config.debug,
|
||||
cache: cacheOptions,
|
||||
user: e.sender.user_id,
|
||||
proxy: Config.proxy
|
||||
})
|
||||
// Sydney不实现上下文传递,删除上下文索引
|
||||
delete conversation.clientId
|
||||
delete conversation.invocationId
|
||||
delete conversation.conversationSignature
|
||||
let response
|
||||
let reply = ''
|
||||
let retry = 3
|
||||
let errorMessage = ''
|
||||
|
||||
do {
|
||||
try {
|
||||
let opt = _.cloneDeep(conversation) || {}
|
||||
opt.toneStyle = Config.toneStyle
|
||||
// 如果当前没有开启对话或者当前是Sydney模式、Custom模式,则本次对话携带拓展资料
|
||||
let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`)
|
||||
if (!c) {
|
||||
opt.context = useCast?.bing_resource || Config.sydneyContext
|
||||
const conversationsCache = new Keyv(cacheOptions)
|
||||
let client = new BingAIClient(Config.bingAiToken, Config.sydneyReverseProxy, Config.debug, Config._2captchaKey, Config.bingAiClientId, Config.bingAiScope, Config.bingAiRefreshToken, Config.bingAiOid, Config.bingReasoning)
|
||||
const conversationKey = `SydneyUser_${e.sender.user_id}`
|
||||
const conversations = (await conversationsCache.get(conversationKey)) || {
|
||||
messages: [],
|
||||
createdAt: Date.now()
|
||||
}
|
||||
logger.info(JSON.stringify(conversations))
|
||||
const previousCachedMessages = SydneyAIClient.getMessagesForConversation(conversations.messages, conversation.parentMessageId)
|
||||
.map((message) => {
|
||||
return {
|
||||
text: message.message,
|
||||
author: message.role === 'User' ? 'user' : 'bot'
|
||||
}
|
||||
// 重新拿存储的token,因为可能之前有过期的被删了
|
||||
let abtrs = await getAvailableBingToken(conversation, throttledTokens)
|
||||
bingToken = abtrs.bingToken
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
allThrottled = abtrs.allThrottled
|
||||
if (bingToken?.indexOf('=') > -1) {
|
||||
cookies = bingToken
|
||||
}
|
||||
if (!bingAIClient.opts) {
|
||||
bingAIClient.opts = {}
|
||||
}
|
||||
bingAIClient.opts.userToken = bingToken
|
||||
bingAIClient.opts.cookies = cookies
|
||||
// opt.messageType = allThrottled ? 'Chat' : 'SearchQuery'
|
||||
if (Config.enableGroupContext && e.isGroup) {
|
||||
try {
|
||||
opt.groupId = e.group_id
|
||||
opt.qq = e.sender.user_id
|
||||
opt.nickname = e.sender.card
|
||||
opt.groupName = e.group.name || e.group_name
|
||||
opt.botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname
|
||||
let master = (await getMasterQQ())[0]
|
||||
if (master && e.group) {
|
||||
opt.masterName = e.group.pickMember(parseInt(master)).card || e.group.pickMember(parseInt(master)).nickname
|
||||
}
|
||||
if (master && !e.group) {
|
||||
opt.masterName = e.bot.getFriendList().get(parseInt(master))?.nickname
|
||||
}
|
||||
opt.chats = await getChatHistoryGroup(e, Config.groupContextLength)
|
||||
} catch (err) {
|
||||
logger.warn('获取群聊聊天记录失败,本次对话不携带聊天记录', err)
|
||||
}
|
||||
}
|
||||
let toSummaryFileContent
|
||||
try {
|
||||
if (e.source) {
|
||||
let seq = e.isGroup ? e.source.seq : e.source.time
|
||||
if (e.adapter === 'shamrock') {
|
||||
seq = e.source.message_id
|
||||
}
|
||||
let msgs = e.isGroup ? await e.group.getChatHistory(seq, 1) : await e.friend.getChatHistory(seq, 1)
|
||||
let sourceMsg = msgs[msgs.length - 1]
|
||||
let fileMsgElem = sourceMsg.file || sourceMsg.message.find(msg => msg.type === 'file')
|
||||
if (fileMsgElem) {
|
||||
toSummaryFileContent = await extractContentFromFile(fileMsgElem, e)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.warn('读取文件内容出错, 忽略文件内容', err)
|
||||
}
|
||||
opt.toSummaryFileContent = toSummaryFileContent
|
||||
// 写入图片数据
|
||||
if (Config.sydneyImageRecognition) {
|
||||
const image = await getImg(e)
|
||||
opt.imageUrl = image ? image[0] : undefined
|
||||
}
|
||||
if (Config.enableGenerateContents) {
|
||||
opt.onImageCreateRequest = prompt => {
|
||||
logger.mark(`开始生成内容:${prompt}`)
|
||||
if (Config.bingAPDraw) {
|
||||
// 调用第三方API进行绘图
|
||||
let apDraw = new APTool()
|
||||
apDraw.func({
|
||||
prompt
|
||||
}, e)
|
||||
} else {
|
||||
let client = new BingDrawClient({
|
||||
baseUrl: Config.sydneyReverseProxy,
|
||||
userToken: bingToken
|
||||
})
|
||||
redis.set(`CHATGPT:DRAW:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => {
|
||||
try {
|
||||
client.getImages(prompt, e)
|
||||
} catch (err) {
|
||||
redis.del(`CHATGPT:DRAW:${e.sender.user_id}`)
|
||||
this.reply('绘图失败:' + err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
opt.onSunoCreateRequest = prompt => {
|
||||
logger.mark(`开始生成内容:Suno ${prompt.songtId || ''}`)
|
||||
let client = new BingSunoClient({
|
||||
cookies: cookies
|
||||
})
|
||||
redis.set(`CHATGPT:SUNO:${e.sender.user_id}`, 'c', { EX: 30 }).then(() => {
|
||||
try {
|
||||
if (Config.bingSuno == 'local') {
|
||||
// 调用本地Suno配置进行歌曲生成
|
||||
client.getLocalSuno(prompt, e)
|
||||
} else if (Config.bingSuno == 'api' && Config.bingSunoApi) {
|
||||
// 调用第三方Suno配置进行歌曲生成
|
||||
client.getApiSuno(prompt, e)
|
||||
} else {
|
||||
// 调用Bing Suno进行歌曲生成
|
||||
client.getSuno(prompt, e)
|
||||
}
|
||||
} catch (err) {
|
||||
redis.del(`CHATGPT:SUNO:${e.sender.user_id}`)
|
||||
this.reply('歌曲生成失败:' + err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
response = await bingAIClient.sendMessage(prompt, opt, (token) => {
|
||||
reply += token
|
||||
})
|
||||
if (response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim()) {
|
||||
if (response.response === undefined) {
|
||||
response.response = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.trim()
|
||||
}
|
||||
response.response = response.response.replace(/\[\^[0-9]+\^\]/g, (str) => {
|
||||
return str.replace(/[/^]/g, '')
|
||||
})
|
||||
let system = opt.system.bing
|
||||
if (Config.enableGroupContext && e.isGroup) {
|
||||
let chats = await getChatHistoryGroup(e, Config.groupContextLength)
|
||||
const namePlaceholder = '[name]'
|
||||
const defaultBotName = 'Copilot'
|
||||
const groupContextTip = Config.groupContextTip
|
||||
let botName = e.isGroup ? (e.group.pickMember(getUin(e)).card || e.group.pickMember(getUin(e)).nickname) : e.bot.nickname
|
||||
system = system.replaceAll(namePlaceholder, botName || defaultBotName) +
|
||||
((Config.enableGroupContext && e.group_id) ? groupContextTip : '')
|
||||
system += 'Attention, you are currently chatting in a qq group, then one who asks you now is' + `${e.sender.card || e.sender.nickname}(${e.sender.user_id}).`
|
||||
system += `the group name is ${e.group.name || e.group_name}, group id is ${e.group_id}.`
|
||||
system += `Your nickname is ${botName} in the group,`
|
||||
if (chats) {
|
||||
system += 'There is the conversation history in the group, you must chat according to the conversation history context"'
|
||||
system += chats
|
||||
.map(chat => {
|
||||
let sender = chat.sender || {}
|
||||
return `【${sender.card || sender.nickname}】(qq:${sender.user_id}, ${roleMap[sender.role] || 'normal user'},${sender.area ? 'from ' + sender.area + ', ' : ''} ${sender.age} years old, 群头衔:${sender.title}, gender: ${sender.sex}, time:${formatDate(new Date(chat.time * 1000))}, messageId: ${chat.message_id}) 说:${chat.raw_message}`
|
||||
})
|
||||
// 有了新的引用属性
|
||||
// response.quote = response.details.adaptiveCards?.[0]?.body?.[0]?.text?.replace(/\[\^[0-9]+\^\]/g, '').replace(response.response, '').split('\n')
|
||||
}
|
||||
response.suggestedResponses = response.details.suggestedResponses?.map(s => s.text).join('\n')
|
||||
// 新引用属性读取数据
|
||||
if (response.details.sourceAttributions) {
|
||||
response.quote = []
|
||||
for (let quote of response.details.sourceAttributions) {
|
||||
response.quote.push({
|
||||
text: quote.providerDisplayName || '',
|
||||
url: quote.seeMoreUrl,
|
||||
imageLink: quote.imageLink || ''
|
||||
})
|
||||
}
|
||||
}
|
||||
// 如果token曾经有异常,则清除异常
|
||||
let Tokens = JSON.parse((await redis.get('CHATGPT:BING_TOKENS')) || '[]')
|
||||
const TokenIndex = Tokens?.findIndex(element => element.Token === abtrs.bingToken)
|
||||
if (TokenIndex > 0 && Tokens[TokenIndex].exception) {
|
||||
delete Tokens[TokenIndex].exception
|
||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(Tokens))
|
||||
}
|
||||
errorMessage = ''
|
||||
break
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
const message = error?.message || error?.data?.message || error || '出错了'
|
||||
const { maxConv } = error
|
||||
if (message && typeof message === 'string' && message.indexOf('CaptchaChallenge') > -1) {
|
||||
if (bingToken) {
|
||||
if (maxConv >= 20 && Config.bingCaptchaOneShotUrl) {
|
||||
// maxConv为30说明token有效,可以通过解验证码码服务过码
|
||||
await this.reply('出现必应验证码,尝试解决中')
|
||||
try {
|
||||
let captchaResolveResult = await solveCaptchaOneShot(bingToken)
|
||||
if (captchaResolveResult?.success) {
|
||||
await this.reply('验证码已解决')
|
||||
} else {
|
||||
logger.error(captchaResolveResult)
|
||||
errorMessage = message
|
||||
await this.reply('验证码解决失败: ' + captchaResolveResult.error)
|
||||
retry = 0
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
await this.reply('验证码解决失败: ' + err)
|
||||
retry = 0
|
||||
}
|
||||
} else {
|
||||
// 未登录用户maxConv目前为5或10,出验证码是ip或MUID问题
|
||||
logger.warn(`token [${bingToken}] 出现必应验证码,请前往网页版或app手动解决`)
|
||||
errorMessage = message
|
||||
retry = 0
|
||||
}
|
||||
} else {
|
||||
retry = 0
|
||||
}
|
||||
} else if (message && typeof message === 'string' && message.indexOf('限流') > -1) {
|
||||
throttledTokens.push(bingToken)
|
||||
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||
const badBingToken = bingTokens.findIndex(element => element.Token === bingToken)
|
||||
const now = new Date()
|
||||
const hours = now.getHours()
|
||||
now.setHours(hours + 6)
|
||||
bingTokens[badBingToken].State = '受限'
|
||||
bingTokens[badBingToken].DisactivationTime = now
|
||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
||||
// 不减次数
|
||||
} else if (message && typeof message === 'string' && message.indexOf('UnauthorizedRequest') > -1) {
|
||||
// token过期了
|
||||
let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS'))
|
||||
const badBingToken = bingTokens.findIndex(element => element.Token === bingToken)
|
||||
if (badBingToken > 0) {
|
||||
// 可能是微软抽风,给三次机会
|
||||
if (bingTokens[badBingToken]?.exception) {
|
||||
if (bingTokens[badBingToken].exception <= 3) {
|
||||
bingTokens[badBingToken].exception += 1
|
||||
} else {
|
||||
bingTokens[badBingToken].exception = 0
|
||||
bingTokens[badBingToken].State = '过期'
|
||||
}
|
||||
} else {
|
||||
bingTokens[badBingToken].exception = 1
|
||||
}
|
||||
await redis.set('CHATGPT:BING_TOKENS', JSON.stringify(bingTokens))
|
||||
} else {
|
||||
retry = retry - 1
|
||||
}
|
||||
errorMessage = 'UnauthorizedRequest:必应token不正确或已过期'
|
||||
// logger.warn(`token${bingToken}疑似不存在或已过期,再试试`)
|
||||
// retry = retry - 1
|
||||
} else {
|
||||
retry--
|
||||
errorMessage = message === 'Timed out waiting for response. Try enabling debug mode to see more information.' ? (reply ? `${reply}\n不行了,我的大脑过载了,处理不过来了!` : '必应的小脑瓜不好使了,不知道怎么回答!') : message
|
||||
}
|
||||
}
|
||||
} while (retry > 0)
|
||||
if (errorMessage) {
|
||||
if (errorMessage.includes('CaptchaChallenge')) {
|
||||
if (bingToken) {
|
||||
errorMessage = '出现验证码,请使用当前账户前往https://www.bing.com/chat或Edge侧边栏或移动端APP手动解除验证码'
|
||||
} else {
|
||||
errorMessage = '未配置必应账户,建议绑定必应账户再使用必应模式'
|
||||
}
|
||||
}
|
||||
return {
|
||||
text: errorMessage,
|
||||
error: true
|
||||
}
|
||||
} else if (response?.response) {
|
||||
return {
|
||||
text: response?.response,
|
||||
quote: response?.quote,
|
||||
suggestedResponses: response.suggestedResponses,
|
||||
conversationId: response.conversationId,
|
||||
clientId: response.clientId,
|
||||
invocationId: response.invocationId,
|
||||
conversationSignature: response.conversationSignature,
|
||||
parentMessageId: response.apology ? conversation.parentMessageId : response.messageId,
|
||||
bingToken
|
||||
}
|
||||
} else {
|
||||
logger.debug('no message')
|
||||
return {
|
||||
noMsg: true
|
||||
.join('\n')
|
||||
}
|
||||
}
|
||||
const msg = `System:\n${system}\n\nPrevious Messages:\n${JSON.stringify(previousCachedMessages)}\n\nUser: ${prompt}`
|
||||
const response = await client.sendMessage(msg)
|
||||
logger.info({ response })
|
||||
const userMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
parentMessageId: conversation.parentMessageId,
|
||||
role: 'User',
|
||||
message: prompt
|
||||
}
|
||||
conversations.messages.push(userMessage)
|
||||
const replyMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
parentMessageId: userMessage.id,
|
||||
role: 'Bing',
|
||||
message: response
|
||||
}
|
||||
conversations.messages.push(replyMessage)
|
||||
await conversationsCache.set(conversationKey, conversations)
|
||||
return {
|
||||
text: response,
|
||||
parentMessageId: replyMessage.id
|
||||
|
||||
}
|
||||
} else if (use === 'api3') {
|
||||
// official without cloudflare
|
||||
let accessToken = await redis.get('CHATGPT:TOKEN')
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ const defaultConfig = {
|
|||
serpSource: 'ikechan8370',
|
||||
extraUrl: 'https://cpe.ikechan8370.com',
|
||||
smartMode: false,
|
||||
bingCaptchaOneShotUrl: '',
|
||||
// claude2
|
||||
claudeAIOrganizationId: '',
|
||||
claudeAISessionKey: '',
|
||||
|
|
@ -219,6 +218,14 @@ const defaultConfig = {
|
|||
forwardReasoning: true,
|
||||
geminiEnableGoogleSearch: false,
|
||||
geminiEnableCodeExecution: false,
|
||||
bingAiToken: '', // copilot.microsoft.com accessToken
|
||||
bingAiClientId: '',
|
||||
bingAiScope: '140e65af-45d1-4427-bf08-3e7295db6836/ChatAI.ReadWrite openid profile offline_access',
|
||||
bingAiRefreshToken: '',
|
||||
bingAiOid: '',
|
||||
_2captchaKey: '',
|
||||
bingReasoning: false, // 是否深度思考
|
||||
|
||||
version: 'v2.8.3'
|
||||
}
|
||||
const _path = process.cwd()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ var ChatGPTAPI = /** @class */ (function () {
|
|||
* @param fetch - Optional override for the `fetch` implementation to use. Defaults to the global `fetch` function.
|
||||
*/
|
||||
function ChatGPTAPI(opts) {
|
||||
var apiKey = opts.apiKey, apiOrg = opts.apiOrg, _a = opts.apiBaseUrl, apiBaseUrl = _a === void 0 ? 'https://api.openai.com/v1' : _a, _b = opts.debug, debug = _b === void 0 ? false : _b, messageStore = opts.messageStore, completionParams = opts.completionParams, systemMessage = opts.systemMessage, _c = opts.maxModelTokens, maxModelTokens = _c === void 0 ? 4000 : _c, _d = opts.maxResponseTokens, maxResponseTokens = _d === void 0 ? 1000 : _d, getMessageById = opts.getMessageById, upsertMessage = opts.upsertMessage, _e = opts.fetch, fetch = _e === void 0 ? globalFetch : _e;
|
||||
var apiKey = opts.apiKey, apiOrg = opts.apiOrg, _a = opts.apiBaseUrl, apiBaseUrl = _a === void 0 ? 'https://api.openai.com/v1' : _a, _b = opts.debug, debug = _b === void 0 ? false : _b, messageStore = opts.messageStore, completionParams = opts.completionParams, systemMessage = opts.systemMessage, _c = opts.maxModelTokens, maxModelTokens = _c === void 0 ? 4000 : _c, _d = opts.maxResponseTokens, maxResponseTokens = _d === void 0 ? 8192 : _d, getMessageById = opts.getMessageById, upsertMessage = opts.upsertMessage, _e = opts.fetch, fetch = _e === void 0 ? globalFetch : _e;
|
||||
this._apiKey = apiKey;
|
||||
this._apiOrg = apiOrg;
|
||||
this._apiBaseUrl = apiBaseUrl;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export class ChatGPTAPI {
|
|||
completionParams,
|
||||
systemMessage,
|
||||
maxModelTokens = 4000,
|
||||
maxResponseTokens = 1000,
|
||||
maxResponseTokens = 8192,
|
||||
getMessageById,
|
||||
upsertMessage,
|
||||
fetch = globalFetch
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue