fix: 复活Copilot但有代价

This commit is contained in:
ikechan8370 2025-02-04 23:11:07 +08:00
parent dcae426cfa
commit 243331aa2e
5 changed files with 201 additions and 398 deletions

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