mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-17 05:47:11 +00:00
fix: 复活Copilot但有代价
This commit is contained in:
parent
dcae426cfa
commit
243331aa2e
5 changed files with 201 additions and 398 deletions
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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue