Merge pull request #5 from ikechan8370/v2

up
This commit is contained in:
ycxom 2025-02-05 09:49:07 +08:00 committed by GitHub
commit 04c6a5c8c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 207 additions and 400 deletions

View file

@ -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('开启')

View file

@ -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
}
}

View file

@ -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的AccessTokenscope需为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方式的配置',

View file

@ -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')

View file

@ -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()

View file

@ -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;

View file

@ -59,7 +59,7 @@ export class ChatGPTAPI {
completionParams,
systemMessage,
maxModelTokens = 4000,
maxResponseTokens = 1000,
maxResponseTokens = 8192,
getMessageById,
upsertMessage,
fetch = globalFetch