diff --git a/apps/chat.js b/apps/chat.js index 14dcd2f..81dd6bd 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -39,6 +39,7 @@ import { SlackClaudeClient } from '../utils/slack/slackClient.js' import { getPromptByName } from '../utils/prompts.js' import BingDrawClient from '../utils/BingDraw.js' import XinghuoClient from '../utils/xinghuo/xinghuo.js' +import Bard from '../utils/bard.js' import { JinyanTool } from '../utils/tools/JinyanTool.js' import { SendVideoTool } from '../utils/tools/SendBilibiliTool.js' import { KickOutTool } from '../utils/tools/KickOutTool.js' @@ -161,6 +162,14 @@ export class chatgpt extends plugin { /** 执行方法 */ fnc: 'xh' }, + { + reg: '^#星火助手', + fnc: 'newxhBotConversation' + }, + { + reg: '^#星火(搜索|查找)助手', + fnc: 'searchxhBot' + }, { /** 命令正则匹配 */ reg: toggleMode === 'at' ? '^[^#][sS]*' : '^#chat[^gpt][sS]*', @@ -248,7 +257,7 @@ export class chatgpt extends plugin { * @param e * @returns {Promise} */ - async bingCaptcha (e) { + async bingCaptcha(e) { let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) if (!bingTokens) { await e.reply('尚未绑定必应token:必应过码必须绑定token') @@ -302,36 +311,41 @@ export class chatgpt extends plugin { async destroyConversations(e) { const userData = await getUserData(e.user_id) const use = (userData.mode === 'default' ? null : userData.mode) || await redis.get('CHATGPT:USE') - await redis.del(`CHATGPT:WRONG_EMOTION:${e.sender.user_id}`) + await redis.del(`CHATGPT:WRONG_EMOTION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) if (use === 'claude') { // let client = new SlackClaudeClient({ // slackUserToken: Config.slackUserToken, // slackChannelId: Config.slackChannelId // }) // await client.endConversation() - await redis.del(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`) + await redis.del(`CHATGPT:SLACK_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) await e.reply('claude对话已结束') return } if (use === 'xh') { - await redis.del(`CHATGPT:CONVERSATIONS_XH:${e.sender.user_id}`) + await redis.del(`CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) await e.reply('星火对话已结束') return } + if (use === 'bard') { + await redis.del(`CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) + await e.reply('Bard对话已结束') + return + } let ats = e.message.filter(m => m.type === 'at') const isAtMode = Config.toggleMode === 'at' if (isAtMode) ats = ats.filter(item => item.qq !== Bot.uin) if (ats.length === 0) { if (use === 'api3') { - await redis.del(`CHATGPT:QQ_CONVERSATION:${e.sender.user_id}`) + await redis.del(`CHATGPT:QQ_CONVERSATION:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) await this.reply('已退出当前对话,该对话仍然保留。请@我进行聊天以开启新的对话', true) } else if (use === 'bing' && (Config.toneStyle === 'Sydney' || Config.toneStyle === 'Custom')) { - let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`) + let c = await redis.get(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) if (!c) { await this.reply('当前没有开启对话', true) return } else { - await redis.del(`CHATGPT:CONVERSATIONS_BING:${e.sender.user_id}`) + await redis.del(`CHATGPT:CONVERSATIONS_BING:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`) } const conversation = { store: new KeyvFile({ filename: 'cache.json' }), @@ -475,7 +489,18 @@ export class chatgpt extends plugin { for (let i = 0; i < cs.length; i++) { await redis.del(cs[i]) if (Config.debug) { - logger.info('delete slack conversation of qq: ' + cs[i]) + logger.info('delete xh conversation of qq: ' + cs[i]) + } + deleted++ + } + break + } + case 'bard': { + let cs = await redis.keys('CHATGPT:CONVERSATIONS_BARD:*') + for (let i = 0; i < cs.length; i++) { + await redis.del(cs[i]) + if (Config.debug) { + logger.info('delete bard conversation of qq: ' + cs[i]) } deleted++ } @@ -1005,6 +1030,10 @@ export class chatgpt extends plugin { key = `CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}` break } + case 'bard': { + key = `CHATGPT:CONVERSATIONS_BARD:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}` + break + } } let ctime = new Date() previousConversation = (key ? await redis.get(key) : null) || JSON.stringify({ @@ -1038,6 +1067,12 @@ export class chatgpt extends plugin { await e.reply([chatMessage.text, segment.image(`base64://${chatMessage.image}`)]) return false } + // 处理星火和bard图片 + if ((use === 'bard' || use === 'xh') && chatMessage?.images) { + chatMessage.images.forEach(async element => { + await e.reply([element.tag, segment.image(element.url)]) + }) + } if (use === 'api' && !chatMessage) { // 字数超限直接返回 return false @@ -1059,6 +1094,11 @@ export class chatgpt extends plugin { } else if (chatMessage.id) { previousConversation.parentMessageId = chatMessage.id } + if (use === 'bard' && !chatMessage.error) { + previousConversation.parentMessageId = chatMessage.responseID + previousConversation.clientId = chatMessage.choiceID + previousConversation.invocationId = chatMessage._reqID + } if (Config.debug) { logger.info(chatMessage) } @@ -1091,9 +1131,9 @@ export class chatgpt extends plugin { response.length / 2 < endIndex ? [response.substring(startIndex), response.substring(0, startIndex)] : [ - response.substring(0, endIndex + 1), - response.substring(endIndex + 1) - ] + response.substring(0, endIndex + 1), + response.substring(endIndex + 1) + ] const match = ttsArr[0].match(emotionReg) response = ttsArr[1].replace(/\n/, '').trim() if (match) { @@ -1647,7 +1687,6 @@ export class chatgpt extends plugin { }) } } - console.log(response) // 处理内容生成的图片 if (response.details.imageTag) { if (Config.debug) { @@ -1714,23 +1753,23 @@ export class chatgpt extends plugin { 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 (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)) + // token过期了 + // let bingTokens = JSON.parse(await redis.get('CHATGPT:BING_TOKENS')) + // const badBingToken = bingTokens.findIndex(element => element.Token === bingToken) + // // 可能是微软抽风,给三次机会 + // 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)) logger.warn(`token${bingToken}疑似不存在或已过期,再试试`) retry = retry - 0.1 } else { @@ -1845,16 +1884,71 @@ export class chatgpt extends plugin { } } case 'xh': { + const cacheOptions = { + namespace: 'xh', + store: new KeyvFile({ filename: 'cache.json' }) + } const ssoSessionId = Config.xinghuoToken if (!ssoSessionId) { throw new Error('未绑定星火token,请使用#chatgpt设置星火token命令绑定token。(获取对话页面的ssoSessionId cookie值)') } let client = new XinghuoClient({ - ssoSessionId + ssoSessionId, + cache: cacheOptions }) - let response = await client.sendMessage(prompt, conversation?.conversationId) + // 获取图片资源 + const image = await getImg(e) + let response = await client.sendMessage(prompt, conversation?.conversationId, image ? image[0] : undefined) return response } + case 'bard': { + // 处理cookie + const matchesPSID = /__Secure-1PSID=([^;]+)/.exec(Config.bardPsid) + const matchesPSIDTS = /__Secure-1PSIDTS=([^;]+)/.exec(Config.bardPsid) + const cookie = { + '__Secure-1PSID': matchesPSID[1], + '__Secure-1PSIDTS': matchesPSIDTS[1] + } + if (!matchesPSID[1] || !matchesPSIDTS[1]) { + throw new Error('未绑定bard') + } + // 处理图片 + const image = await getImg(e) + let imageBuff + if (image) { + try { + let imgResponse = await fetch(image[0]) + if (imgResponse.ok) { + imageBuff = await imgResponse.arrayBuffer() + } + } catch (error) { + logger.warn(`错误的图片链接${image[0]}`) + } + } + // 发送数据 + let bot = new Bard(cookie, { + fetch: fetch, + bardURL: Config.bardForceUseReverse ? Config.bardReverseProxy : 'https://bard.google.com' + }) + let chat = await bot.createChat(conversation?.conversationId ? { + conversationID: conversation.conversationId, + responseID: conversation.parentMessageId, + choiceID: conversation.clientId, + _reqID: conversation.invocationId + } : {}) + let response = await chat.ask(prompt, { + image: imageBuff, + format: Bard.JSON + }) + return { + conversationId: response.ids.conversationID, + responseID: response.ids.responseID, + choiceID: response.ids.choiceID, + _reqID: response.ids._reqID, + text: response.content, + images: response.images + } + } default: { let completionParams = {} if (Config.model) { @@ -2206,6 +2300,105 @@ export class chatgpt extends plugin { return true } + async newxhBotConversation(e) { + let botId = e.msg.replace(/^#星火助手/, '').trim() + if (Config.xhmode != 'web') { + await e.reply('星火助手仅支持体验版使用', true) + return true + } + if (!botId) { + await e.reply('无效助手id', true) + } else { + const ssoSessionId = Config.xinghuoToken + if (!ssoSessionId) { + await e.reply(`未绑定星火token,请使用#chatgpt设置星火token命令绑定token`, true) + return true + } + let client = new XinghuoClient({ + ssoSessionId, + cache: null + }) + try { + let chatId = await client.createChatList(botId) + let botInfoRes = await fetch(`https://xinghuo.xfyun.cn/iflygpt/bot/getBotInfo?chatId=${chatId.chatListId}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Cookie: 'ssoSessionId=' + ssoSessionId + ';', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/113.0.5672.69 Mobile/15E148 Safari/604.1', + } + }) + if (botInfoRes.ok) { + let botInfo = await botInfoRes.json() + if (botInfo.flag) { + let ctime = new Date() + await redis.set( + `CHATGPT:CONVERSATIONS_XH:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`, + JSON.stringify({ + sender: e.sender, + ctime, + utime: ctime, + num: 0, + conversation: { + conversationId: { + chatid: chatId.chatListId, + botid: botId + } + } + }), + Config.conversationPreserveTime > 0 ? { EX: Config.conversationPreserveTime } : {} + ) + await e.reply(`成功创建助手对话\n助手名称:${botInfo.data.bot_name}\n助手描述:${botInfo.data.bot_desc}`, true) + } else { + await e.reply(`创建助手对话失败,${botInfo.desc}`, true) + } + } else { + await e.reply(`创建助手对话失败,服务器异常`, true) + } + } catch (error) { + await e.reply(`创建助手对话失败 ${error}`, true) + } + } + return true + } + + async searchxhBot(e) { + let searchBot = e.msg.replace(/^#星火(搜索|查找)助手/, '').trim() + const ssoSessionId = Config.xinghuoToken + if (!ssoSessionId) { + await e.reply(`未绑定星火token,请使用#chatgpt设置星火token命令绑定token`, true) + return true + } + const cacheresOption = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: 'ssoSessionId=' + ssoSessionId + ';', + 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/113.0.5672.69 Mobile/15E148 Safari/604.1', + }, + body: JSON.stringify({ + botType: '', + pageIndex: 1, + pageSize: 45, + searchValue: searchBot + }) + } + const searchBots = await fetch('https://xinghuo.xfyun.cn/iflygpt/bot/page', cacheresOption) + const bots = await searchBots.json() + if (Config.debug) { + logger.info(bots) + } + if (bots.code === 0) { + if (bots.data.pageList.length > 0) { + this.reply(await makeForwardMsg(this.e, bots.data.pageList.map(msg => `${msg.bot.botId} - ${msg.bot.botName}`))) + } else { + await e.reply(`未查到相关助手`, true) + } + } else { + await e.reply(`搜索助手失败`, true) + } + } + async emptyQueue(e) { await redis.lTrim('CHATGPT:CHAT_QUEUE', 1, 0) await this.reply('已清空当前等待队列') @@ -2354,7 +2547,7 @@ export class chatgpt extends plugin { return await this.chatGPTApi.sendMessage(prompt, sendMessageOption) } - async solveBingCaptcha (e) { + async solveBingCaptcha(e) { try { let id = e.bingCaptchaId let regionId = e.regionId diff --git a/apps/management.js b/apps/management.js index 93aca06..763a2b5 100644 --- a/apps/management.js +++ b/apps/management.js @@ -108,6 +108,11 @@ export class ChatgptManagement extends plugin { fnc: 'useXinghuoBasedSolution', permission: 'master' }, + { + reg: '^#chatgpt切换(Bard|bard)$', + fnc: 'useBardBasedSolution', + permission: 'master' + }, { reg: '^#chatgpt(必应|Bing)切换', fnc: 'changeBingTone', @@ -834,6 +839,16 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务, } } + async useBardBasedSolution () { + let use = await redis.get('CHATGPT:USE') + if (use !== 'bard') { + await redis.set('CHATGPT:USE', 'bard') + await this.reply('已切换到基于Bard的解决方案') + } else { + await this.reply('当前已经是Bard模式了') + } + } + async changeBingTone (e) { let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '') if (!tongStyle) { diff --git a/guoba.support.js b/guoba.support.js index 8a1c3a4..148406f 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -611,12 +611,108 @@ export function supportGuoba () { label: '以下为星火方式的配置', component: 'Divider' }, + { + field: 'xhmode', + label: '星火模式', + bottomHelpMessage: '设置星火使用的对话模式', + component: 'Select', + componentProps: { + options: [ + { label: '体验版', value: 'web' }, + { label: '讯飞星火认知大模型V1.5', value: 'api' }, + { label: '讯飞星火认知大模型V2.0', value: 'apiv2' }, + { label: '讯飞星火助手', value: 'assistants' } + ] + } + }, { field: 'xinghuoToken', label: '星火Cookie', bottomHelpMessage: '获取对话页面的ssoSessionId cookie。不要带等号和分号', + component: 'InputPassword' + }, + { + field: 'xhAppId', + label: 'AppId', + bottomHelpMessage: '应用页面获取', component: 'Input' }, + { + field: 'xhAPISecret', + label: 'APISecret', + bottomHelpMessage: '应用页面获取', + component: 'InputPassword' + }, + { + field: 'xhAPIKey', + label: '星火APIKey', + bottomHelpMessage: '应用页面获取', + component: 'InputPassword' + }, + { + field: 'xhAssistants', + label: '助手接口', + bottomHelpMessage: '助手页面获取', + component: 'Input' + }, + { + field: 'xhTemperature', + label: '核采样阈值', + bottomHelpMessage: '核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高', + component: 'InputNumber' + }, + { + field: 'xhMaxTokens', + label: '最大Token', + bottomHelpMessage: '模型回答的tokens的最大长度', + component: 'InputNumber' + }, + { + field: 'xhPromptSerialize', + label: '序列化设定', + bottomHelpMessage: '是否将设定内容进行json序列化', + component: 'Switch' + }, + { + field: 'xhPrompt', + label: '设定', + bottomHelpMessage: '若开启序列化,请传入json数据,例如[{ \"role\": \"user\", \"content\": \"现在是10点\" },{ \"role\": \"assistant\", \"content\": \"了解,现在10点了\" }]', + component: 'InputTextArea' + }, + { + field: 'xhRetRegExp', + label: '回复替换正则', + bottomHelpMessage: '要替换文本的正则', + component: 'Input' + }, + { + field: 'xhRetReplace', + label: '回复内容替换', + bottomHelpMessage: '替换回复内容中的文本', + component: 'Input' + }, + { + label: '以下为Bard方式的配置', + component: 'Divider' + }, + { + field: 'bardPsid', + label: 'BardCookie', + bottomHelpMessage: '获取https://bard.google.com/页面的cookie,可完整输入,需至少包含__Secure-1PSID和__Secure-1PSIDTS', + component: 'Input' + }, + { + field: 'bardReverseProxy', + label: 'Bard反代地址', + bottomHelpMessage: 'bard反代服务器地址,用于绕过地区限制', + component: 'Input' + }, + { + field: 'bardForceUseReverse', + label: 'Bard使用反代', + bottomHelpMessage: '开启后将通过反代访问bard', + component: 'Switch' + }, { label: '以下为杂七杂八的配置', component: 'Divider' diff --git a/package.json b/package.json index c5aa66d..9171460 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "@waylaidwanderer/chatgpt-api": "^1.37.1", "asn1.js": "^5.0.0", "chatgpt": "^5.2.4", + "crypto": "^1.0.1", "delay": "^6.0.0", "diff": "^5.1.0", "emoji-strip": "^1.0.1", diff --git a/resources/view/setting_view.json b/resources/view/setting_view.json index 05f12c3..487dfd7 100644 --- a/resources/view/setting_view.json +++ b/resources/view/setting_view.json @@ -81,6 +81,10 @@ "label": "星火", "value": "xh" }, + { + "label": "Bard", + "value": "bard" + }, { "label": "浏览器", "value": "browser" @@ -709,10 +713,109 @@ "title": "星火", "tab": "xh", "view": [ + { + "type": "select", + "label": "模式", + "data": "xhmode", + "items": [ + { + "label": "讯飞星火认知大模型体验版", + "value": "web" + }, + { + "label": "讯飞星火认知大模型V1.5", + "value": "api" + }, + { + "label": "讯飞星火认知大模型V2.0", + "value": "apiv2" + }, + { + "label": "讯飞星火助手", + "value": "assistants" + } + ] + }, { "type": "password", - "label": "星火Cookie", + "label": "Cookie", "data": "xinghuoToken" + }, + { + "type": "text", + "label": "AppId", + "data": "xhAppId" + }, + { + "type": "password", + "label": "APISecret", + "data": "xhAPISecret" + }, + { + "type": "password", + "label": "APIKey", + "data": "xhAPIKey" + }, + { + "type": "text", + "label": "助手接口", + "data": "xhAssistants" + }, + { + "type": "number", + "label": "核采样阈值", + "placeholder": "核采样阈值。用于决定结果随机性,取值越高随机性越强即相同的问题得到的不同答案的可能性越高", + "data": "xhTemperature" + }, + { + "type": "number", + "label": "最大Token", + "placeholder": "模型回答的tokens的最大长度", + "data": "xhMaxTokens" + }, + { + "type": "check", + "label": "序列化设定", + "data": "xhPromptSerialize" + }, + { + "type": "textarea", + "label": "设定", + "placeholder": "若开启序列化,请传入json数据,例如[{ \"role\": \"user\", \"content\": \"现在是10点\" },{ \"role\": \"assistant\", \"content\": \"了解,现在10点了\" }]", + "data": "xhPrompt" + }, + { + "type": "text", + "label": "回复替换正则", + "placeholder": "要替换文本的正则", + "data": "xhRetRegExp" + }, + { + "type": "text", + "label": "回复内容替换", + "placeholder": "替换回复内容中的文本", + "data": "xhRetReplace" + } + ] + }, + { + "title": "Bard", + "tab": "bard", + "view": [ + { + "type": "password", + "label": "BardCookie", + "data": "bardPsid" + }, + { + "type": "url", + "label": "Bard反代地址", + "data": "bardReverseProxy" + }, + { + "type": "check", + "label": "使用Bard反代", + "data": "bardForceUseReverse" } ] } diff --git a/utils/bard.js b/utils/bard.js new file mode 100644 index 0000000..dff940c --- /dev/null +++ b/utils/bard.js @@ -0,0 +1,373 @@ +// https://github.com/EvanZhouDev/bard-ai + +class Bard { + static JSON = "json"; + static MD = "markdown"; + + // ID derived from Cookie + SNlM0e; + + // HTTPS Headers + #headers; + + // Resolution status of initialization call + #initPromise; + + #bardURL = "https://bard.google.com"; + + // Wether or not to log events to console + #verbose = false; + + // Fetch function + #fetch = fetch; + + constructor(cookie, config) { + // Register some settings + if (config?.verbose == true) this.#verbose = true; + if (config?.fetch) this.#fetch = config.fetch; + // 可变更访问地址,利用反向代理绕过区域限制 + if (config?.bardURL) this.#bardURL = config.bardURL; + + // If a Cookie is provided, initialize + if (cookie) { + this.#initPromise = this.#init(cookie); + } else { + throw new Error("Please provide a Cookie when initializing Bard."); + } + this.cookie = cookie; + } + + // You can also choose to initialize manually + async #init(cookie) { + this.#verbose && console.log("🚀 Starting intialization"); + // Assign headers + this.#headers = { + Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1], + "X-Same-Domain": "1", + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36", + "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", + Origin: this.#bardURL, + Referer: this.#bardURL, + Cookie: (typeof cookie === "object") ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join("")) : ("__Secure-1PSID=" + cookie), + }; + + let responseText; + // Attempt to retrieve SNlM0e + try { + this.#verbose && + console.log("🔒 Authenticating your Google account"); + responseText = await this.#fetch(this.#bardURL, { + method: "GET", + headers: this.#headers, + credentials: "include", + }) + .then((response) => response.text()) + } catch (e) { + // Failure to get server + throw new Error( + "Could not fetch Google Bard. You may be disconnected from internet: " + + e + ); + } + + try { + const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]; + // Assign SNlM0e and return it + this.SNlM0e = SNlM0e; + this.#verbose && console.log("✅ Initialization finished\n"); + return SNlM0e; + } catch { + throw new Error( + "Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit." + ); + } + } + + async #uploadImage(name, buffer) { + this.#verbose && console.log("🖼️ Starting image processing"); + let size = buffer.byteLength; + let formBody = [ + `${encodeURIComponent("File name")}=${encodeURIComponent([name])}`, + ]; + + try { + this.#verbose && + console.log("💻 Finding Google server destination"); + let response = await this.#fetch( + "https://content-push.googleapis.com/upload/", + { + method: "POST", + headers: { + "X-Goog-Upload-Command": "start", + "X-Goog-Upload-Protocol": "resumable", + "X-Goog-Upload-Header-Content-Length": size, + "X-Tenant-Id": "bard-storage", + "Push-Id": "feeds/mcudyrk2a4khkz", + }, + body: formBody, + credentials: "include", + } + ); + + const uploadUrl = response.headers.get("X-Goog-Upload-URL"); + this.#verbose && console.log("📤 Sending your image"); + response = await this.#fetch(uploadUrl, { + method: "POST", + headers: { + "X-Goog-Upload-Command": "upload, finalize", + "X-Goog-Upload-Offset": 0, + "X-Tenant-Id": "bard-storage", + }, + body: buffer, + credentials: "include", + }); + + const imageFileLocation = await response.text(); + + this.#verbose && console.log("✅ Image finished working\n"); + return imageFileLocation; + } catch (e) { + throw new Error( + "Could not fetch Google Bard. You may be disconnected from internet: " + + e + ); + } + } + + // Query Bard + async #query(message, config) { + let formatMarkdown = (text, images) => { + if (!images) return text; + + for (let imageData of images) { + const formattedTag = `!${imageData.tag}(${imageData.url})`; + text = text.replace( + new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`), + formattedTag + ); + } + + return text; + } + + let { ids, imageBuffer } = config; + + // Wait until after init + await this.#initPromise; + + this.#verbose && console.log("🔎 Starting Bard Query"); + + // If user has not run init + if (!this.SNlM0e) { + throw new Error( + "Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)." + ); + } + + this.#verbose && console.log("🏗️ Building Request"); + // HTTPS parameters + const params = { + bl: "boq_assistant-bard-web-server_20230711.08_p0", + _reqID: ids?._reqID ?? "0", + rt: "c", + }; + + // If IDs are provided, but doesn't have every one of the expected IDs, error + const messageStruct = [ + [message], + null, + [null, null, null], + ]; + + if (imageBuffer) { + let imageLocation = await this.#uploadImage( + `bard-ai_upload`, + imageBuffer + ); + messageStruct[0].push(0, null, [ + [[imageLocation, 1], "bard-ai_upload"], + ]); + } + + if (ids) { + const { conversationID, responseID, choiceID } = ids; + messageStruct[2] = [conversationID, responseID, choiceID]; + } + + // HTTPs data + const data = { + "f.req": JSON.stringify([null, JSON.stringify(messageStruct)]), + at: this.SNlM0e, + }; + + // URL that we are submitting to + const url = new URL( + "/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate", + this.#bardURL + ); + + // Append parameters to the URL + for (const key in params) { + url.searchParams.append(key, params[key]); + } + + // Encode the data + const formBody = Object.entries(data) + .map( + ([property, value]) => + `${encodeURIComponent(property)}=${encodeURIComponent( + value + )}` + ) + .join("&"); + + this.#verbose && console.log("💭 Sending message to Bard"); + // Send the fetch request + const chatData = await this.#fetch(url.toString(), { + method: "POST", + headers: this.#headers, + body: formBody, + credentials: "include", + }) + .then((response) => { + return response.text(); + }) + .then((text) => { + return JSON.parse(text.split("\n")[3])[0][2]; + }) + .then((rawData) => JSON.parse(rawData)); + + this.#verbose && console.log("🧩 Parsing output"); + // Get first Bard-recommended answer + const answer = chatData[4][0]; + + // Text of that answer + const text = answer[1][0]; + + // Get data about images in that answer + const images = + answer[4]?.map((x) => ({ + tag: x[2], + url: x[3][0][0], + info: { + raw: x[0][0][0], + source: x[1][0][0], + alt: x[0][4], + website: x[1][1], + favicon: x[1][3], + }, + })) ?? []; + + this.#verbose && console.log("✅ All done!\n"); + // Put everything together and return + return { + content: formatMarkdown(text, images), + images: images, + ids: { + conversationID: chatData[1][0], + responseID: chatData[1][1], + choiceID: answer[0], + _reqID: String(parseInt(ids?._reqID ?? 0) + 100000), + }, + }; + } + + async #parseConfig(config) { + let result = { + useJSON: false, + imageBuffer: undefined, // Returns as {extension, filename} + ids: undefined, + }; + + // Verify that format is one of the two types + if (config?.format) { + switch (config.format) { + case Bard.JSON: + result.useJSON = true; + break; + case Bard.MD: + result.useJSON = false; + break; + default: + throw new Error( + "Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output." + ); + } + } + + // Verify that the image passed in is either a path to a jpeg, jpg, png, or webp, or that it is a Buffer + if (config?.image) { + if ( + config.image instanceof ArrayBuffer + ) { + result.imageBuffer = config.image; + } else if ( + typeof config.image === "string" && + /\.(jpeg|jpg|png|webp)$/.test(config.image) + ) { + let fs; + + try { + fs = await import("fs") + } catch { + throw new Error( + "Loading from an image file path is not supported in a browser environment.", + ); + } + + result.imageBuffer = fs.readFileSync( + config.image, + ).buffer; + } else { + throw new Error( + "Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer." + ); + } + } + + // Verify that all values in IDs exist + if (config?.ids) { + if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) { + result.ids = config.ids; + } else { + throw new Error( + "Please provide the IDs exported exactly as given." + ); + } + } + return result; + } + + // Ask Bard a question! + async ask(message, config) { + let { useJSON, imageBuffer, ids } = await this.#parseConfig(config); + let response = await this.#query(message, { imageBuffer, ids }); + return useJSON ? response : response.content; + } + + createChat(ids) { + let bard = this; + class Chat { + ids = ids; + + async ask(message, config) { + let { useJSON, imageBuffer } = await bard.#parseConfig(config); + let response = await bard.#query(message, { + imageBuffer, + ids: this.ids, + }); + this.ids = response.ids; + return useJSON ? response : response.content; + } + + export() { + return this.ids; + } + } + + return new Chat(); + } +} + +export default Bard; diff --git a/utils/config.js b/utils/config.js index d410283..d4780b2 100644 --- a/utils/config.js +++ b/utils/config.js @@ -50,6 +50,17 @@ const defaultConfig = { plus: false, useGPT4: false, xinghuoToken: '', + xhmode: 'web', + xhAppId: '', + xhAPISecret: '', + xhAPIKey: '', + xhAssistants: '', + xhTemperature: 0.5, + xhMaxTokens: 1024, + xhPromptSerialize: false, + xhPrompt: '', + xhRetRegExp: '', + xhRetReplace: '', promptPrefixOverride: 'Your answer shouldn\'t be too verbose. Prefer to answer in Chinese.', assistantLabel: 'ChatGPT', // thinkingTips: true, @@ -113,6 +124,9 @@ const defaultConfig = { slackClaudeEnableGlobalPreset: true, slackClaudeGlobalPreset: '', slackClaudeSpecifiedChannel: '', + bardPsid: '', + bardReverseProxy: '', + bardForceUseReverse: false, cloudTranscode: 'https://silk.201666.xyz', cloudRender: false, cloudMode: 'url', diff --git a/utils/xinghuo/xinghuo.js b/utils/xinghuo/xinghuo.js index 8fecea0..49878e6 100644 --- a/utils/xinghuo/xinghuo.js +++ b/utils/xinghuo/xinghuo.js @@ -2,6 +2,8 @@ import fetch from 'node-fetch' import { Config } from '../config.js' import { createParser } from 'eventsource-parser' import https from 'https' +import WebSocket from 'ws' +import { config } from 'process' const referer = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNuL2NoYXQ/aWQ9') const origin = atob('aHR0cHM6Ly94aW5naHVvLnhmeXVuLmNu') @@ -13,8 +15,24 @@ try { } catch (err) { logger.warn('未安装form-data,无法使用星火模式') } +let crypto +try { + crypto = (await import('crypto')).default +} catch (err) { + logger.warn('未安装crypto,无法使用星火api模式') +} +async function getKeyv() { + let Keyv + try { + Keyv = (await import('keyv')).default + } catch (error) { + throw new Error('keyv依赖未安装,请使用pnpm install keyv安装') + } + return Keyv +} export default class XinghuoClient { - constructor (opts) { + constructor(opts) { + this.cache = opts.cache this.ssoSessionId = opts.ssoSessionId this.headers = { Referer: referer, @@ -24,19 +42,249 @@ export default class XinghuoClient { } } - async sendMessage (prompt, chatId) { + apiErrorInfo(code) { + switch (code) { + case 10000: return '升级为ws出现错误' + case 10001: return '通过ws读取用户的消息出错' + case 10002: return '通过ws向用户发送消息 错' + case 10003: return '用户的消息格式有错误' + case 10004: return '用户数据的schema错误' + case 10005: return '用户参数值有错误' + case 10006: return '用户并发错误:当前用户已连接,同一用户不能多处同时连接。' + case 10007: return '用户流量受限:服务正在处理用户当前的问题,需等待处理完成后再发送新的请求。(必须要等大模型完全回复之后,才能发送下一个问题)' + case 10008: return '服务容量不足,联系工作人员' + case 10009: return '和引擎建立连接失败' + case 10010: return '接收引擎数据的错误' + case 10011: return '发送数据给引擎的错误' + case 10012: return '引擎内部错误' + case 10013: return '输入内容审核不通过,涉嫌违规,请重新调整输入内容' + case 10014: return '输出内容涉及敏感信息,审核不通过,后续结果无法展示给用户' + case 10015: return 'appid在黑名单中' + case 10016: return 'appid授权类的错误。比如:未开通此功能,未开通对应版本,token不足,并发超过授权 等等' + case 10017: return '清除历史失败' + case 10019: return '表示本次会话内容有涉及违规信息的倾向;建议开发者收到此错误码后给用户一个输入涉及违规的提示' + case 10110: return '服务忙,请稍后再试' + case 10163: return '请求引擎的参数异常 引擎的schema 检查不通过' + case 10222: return '引擎网络异常' + case 10907: return 'token数量超过上限。对话历史+问题的字数太多,需要精简输入' + case 11200: return '授权错误:该appId没有相关功能的授权 或者 业务量超过限制' + case 11201: return '授权错误:日流控超限。超过当日最大访问量的限制' + case 11202: return '授权错误:秒级流控超限。秒级并发超过授权路数限制' + case 11203: return '授权错误:并发流控超限。并发路数超过授权路数限制' + default: return '无效错误代码' + } + } + + promptBypassPreset(prompt) { + switch (prompt) { + case '你是谁': + return '你是谁,叫什么' + case '你是谁啊': + return '你是谁啊,叫什么' + default: + return prompt + } + } + + async initCache() { + if (!this.conversationsCache) { + const cacheOptions = this.cache || {} + cacheOptions.namespace = cacheOptions.namespace || 'xh' + let Keyv = await getKeyv() + this.conversationsCache = new Keyv(cacheOptions) + } + } + + async getWsUrl() { + if (!crypto) return false + const APISecret = Config.xhAPISecret + const APIKey = Config.xhAPIKey + let APILink = '/v1.1/chat' + if (Config.xhmode == 'apiv2') { + APILink = '/v2.1/chat' + } + const date = new Date().toGMTString() + const algorithm = 'hmac-sha256' + const headers = 'host date request-line' + const signatureOrigin = `host: spark-api.xf-yun.com\ndate: ${date}\nGET ${APILink} HTTP/1.1` + const hmac = crypto.createHmac('sha256', APISecret) + hmac.update(signatureOrigin) + const signature = hmac.digest('base64') + const authorizationOrigin = `api_key="${APIKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"` + const authorization = Buffer.from(authorizationOrigin).toString('base64') + const v = { + authorization: authorization, + date: date, + host: "spark-api.xf-yun.com" + } + const url = `wss://spark-api.xf-yun.com${APILink}?${Object.keys(v).map(key => `${key}=${v[key]}`).join('&')}` + return url + } + + async uploadImage(url) { + // 获取图片 + let response = await fetch(url, { + method: 'GET', + }) + const blob = await response.blob() + const arrayBuffer = await blob.arrayBuffer() + const buffer = Buffer.from(arrayBuffer) + // 上传oss + const formData = new FormData() + formData.append('file', buffer, 'image.png') + const respOss = await fetch('https://xinghuo.xfyun.cn/iflygpt/oss/sign', { + method: 'POST', + headers: { + Cookie: 'ssoSessionId=' + this.ssoSessionId + ';', + }, + body: formData + }) + if (respOss.ok) { + const ossData = await respOss.json() + // 上传接口 + const sparkdeskUrl = `${ossData.data.url}&authorization=${Buffer.from(ossData.data.authorization).toString('base64')}&date=${ossData.data.date}&host=${ossData.data.host}` + const respSparkdes = await fetch(sparkdeskUrl, { + method: 'POST', + headers: { + Cookie: 'ssoSessionId=' + this.ssoSessionId + ';', + authorization: Buffer.from(ossData.data.authorization).toString('base64') + }, + body: buffer + }) + if (respSparkdes.ok) { + const sparkdesData = await respSparkdes.json() + return { + url: sparkdesData.data.link, + file: buffer + } + } else { + try { + const sparkdesData = await respSparkdes.json() + logger.error('星火图片Sparkdes:发送失败' + sparkdesData.desc) + } catch (error) { + logger.error('星火图片Sparkdes:发送失败') + } + return false + } + } else { + try { + const ossData = await respOss.json() + logger.error('星火图片OSS:上传失败' + ossData.desc) + } catch (error) { + logger.error('星火图片OSS:上传失败') + } + return false + } + } + + async apiMessage(prompt, chatId, ePrompt = []) { + if (!chatId) chatId = (Math.floor(Math.random() * 1000000) + 100000).toString() + + // 初始化缓存 + await this.initCache() + const conversationKey = `ChatXH_${chatId}` + const conversation = (await this.conversationsCache.get(conversationKey)) || { + messages: [], + createdAt: Date.now() + } + + // 获取ws链接 + const wsUrl = Config.xhmode == 'assistants' ? Config.xhAssistants : await this.getWsUrl() + if (!wsUrl) throw new Error('缺少依赖:crypto。请安装依赖后重试') + + // 编写消息内容 + const wsSendData = { + header: { + app_id: Config.xhAppId, + uid: chatId + }, + parameter: { + chat: { + domain: Config.xhmode == 'api' ? "general" : "generalv2", + temperature: Config.xhTemperature, // 核采样阈值 + max_tokens: Config.xhMaxTokens, // tokens最大长度 + chat_id: chatId + } + }, + payload: { + message: { + "text": [ + ...ePrompt, + ...conversation.messages, + { "role": "user", "content": prompt } + ] + } + } + } + if (Config.debug) { + logger.info(wsSendData.payload.message.text) + } + + return new Promise((resolve, reject) => { + const socket = new WebSocket(wsUrl) + let resMessage = '' + socket.on('open', () => { + socket.send(JSON.stringify(wsSendData)) + }) + socket.on('message', async (message) => { + try { + const messageData = JSON.parse(message) + if (messageData.header.code != 0) { + reject(`接口发生错误:Error Code ${messageData.header.code} ,${this.apiErrorInfo(messageData.header.code)}`) + } + if (messageData.header.status == 0 || messageData.header.status == 1) { + resMessage += messageData.payload.choices.text[0].content + } + if (messageData.header.status == 2) { + resMessage += messageData.payload.choices.text[0].content + conversation.messages.push({ + role: 'user', + content: prompt + }) + conversation.messages.push({ + role: 'assistant', + content: resMessage + }) + // 超过规定token去除一半曾经的对话记录 + if (messageData.payload.usage.text.total_tokens >= Config.xhMaxTokens) { + const half = Math.floor(conversation.messages.length / 2) + conversation.messages.splice(0, half) + } + await this.conversationsCache.set(conversationKey, conversation) + resolve(resMessage) + } + } catch (error) { + reject(new Error(error)) + } + }) + socket.on('error', (error) => { + reject(error) + }) + }) + } + + async webMessage(prompt, chatId, botId) { if (!FormData) { throw new Error('缺少依赖:form-data。请安装依赖后重试') } - if (!chatId) { - chatId = (await this.createChatList()).chatListId - } - let requestP = new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { let formData = new FormData() formData.setBoundary('----WebKitFormBoundarycATE2QFHDn9ffeWF') formData.append('clientType', '2') formData.append('chatId', chatId) - formData.append('text', prompt) + if (prompt.image) { + prompt.text = prompt.text.replace("[图片]", "") // 清理消息中中首个被使用的图片 + const imgdata = await this.uploadImage(prompt.image) + if (imgdata) { + formData.append('fileUrl', imgdata.url) + formData.append('file', imgdata.file, 'image.png') + } + } + formData.append('text', prompt.text) + if (botId) { + formData.append('isBot', '1') + formData.append('botId', botId) + } let randomNumber = Math.floor(Math.random() * 1000) let fd = '439' + randomNumber.toString().padStart(3, '0') formData.append('fd', fd) @@ -57,7 +305,7 @@ export default class XinghuoClient { logger.error('星火statusCode:' + statusCode) } let response = '' - function onMessage (data) { + function onMessage(data) { // console.log(data) if (data === '') { return resolve({ @@ -65,8 +313,18 @@ export default class XinghuoClient { response }) } + if (data.charAt(0) === '{') { + try { + response = JSON.parse(data).value + if (Config.debug) { + logger.info(response) + } + } catch (err) { + reject(err) + } + } try { - if (data) { + if (data && data !== '[error]') { response += atob(data.trim()) if (Config.debug) { logger.info(response) @@ -112,22 +370,81 @@ export default class XinghuoClient { // req.write(formData.stringify()) req.end() }) - const { response } = await requestP - // logger.info(response) - // let responseText = atob(response) - return { - conversationId: chatId, - text: response + } + + async sendMessage(prompt, chatId, image) { + // 对星火预设的问题进行重写,避免收到预设回答 + prompt = this.promptBypassPreset(prompt) + if (Config.xhmode == 'api' || Config.xhmode == 'apiv2' || Config.xhmode == 'assistants') { + if (!Config.xhAppId || !Config.xhAPISecret || !Config.xhAPIKey) throw new Error('未配置api') + let Prompt = [] + // 设定 + if (Config.xhPromptSerialize) { + try { + Prompt = JSON.parse(Config.xhPrompt) + } catch (error) { + Prompt = [] + logger.warn('星火设定序列化失败,本次对话不附带设定') + } + } else { + Prompt = Config.xhPrompt ? [{ "role": "user", "content": Config.xhPrompt }] : [] + } + let response = await this.apiMessage(prompt, chatId, Prompt) + if (Config.xhRetRegExp) { + response = response.replace(new RegExp(Config.xhRetRegExp, 'g'), Config.xhRetReplace) + } + return { + conversationId: chatId, + text: response + } + } else if (Config.xhmode == 'web') { + let botId = false + if (chatId && typeof chatId === 'object') { + chatId = chatId.chatid + botId = chatId.botid + } + if (!chatId) { + chatId = (await this.createChatList()).chatListId + } + let { response } = await this.webMessage({ text: prompt, image: image }, chatId, botId) + // logger.info(response) + // let responseText = atob(response) + // 处理图片 + let images + if (response.includes('multi_image_url')) { + images = [{ + tag: '', + url: JSON.parse(/{([^}]*)}/g.exec(response)[0]).url + }] + response = '我已经完成作品,欢迎您提出宝贵的意见和建议,帮助我快速进步~~' + } + if (botId) { + chatId = { + chatid: chatId, + botid: botId + } + } + if (Config.xhRetRegExp) { + response = response.replace(new RegExp(Config.xhRetRegExp, 'g'), Config.xhRetReplace) + } + return { + conversationId: chatId, + text: response, + images: images + } + } else { + throw new Error('星火模式错误') } } - async createChatList () { + async createChatList(bot = false) { let createChatListRes = await fetch(createChatUrl, { method: 'POST', headers: Object.assign(this.headers, { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + Botweb: bot ? 1 : 0 }), - body: '{}' + body: bot ? `{"BotWeb": 1, "botId": "${bot}"}` : '{}' }) if (createChatListRes.status !== 200) { let errorRes = await createChatListRes.text() @@ -139,8 +456,8 @@ export default class XinghuoClient { if (createChatListRes.data?.id) { logger.info('星火对话创建成功:' + createChatListRes.data.id) } else { - logger.error('星火对话创建成功: ' + JSON.stringify(createChatListRes)) - throw new Error('星火对话创建成功:' + JSON.stringify(createChatListRes)) + logger.error('星火对话创建失败: ' + JSON.stringify(createChatListRes)) + throw new Error('星火对话创建失败:' + JSON.stringify(createChatListRes)) } return { chatListId: createChatListRes.data?.id, @@ -149,6 +466,6 @@ export default class XinghuoClient { } } -function atob (s) { +function atob(s) { return Buffer.from(s, 'base64').toString() } diff --git a/yarn.lock b/yarn.lock index 56def67..3a3a2c6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1400,6 +1400,11 @@ cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crypto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" + integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== + data-uri-to-buffer@^4.0.0: version "4.0.1" resolved "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" @@ -1792,7 +1797,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.0.0, fast-fifo@^1.1.0, fast-fifo@^1.2.0: +fast-fifo@^1.0.0: version "1.2.0" resolved "https://registry.npmmirror.com/fast-fifo/-/fast-fifo-1.2.0.tgz" integrity sha512-NcvQXt7Cky1cNau15FWy64IjuO8X0JijhTBBrJj1YlxlDfRkJXNaK9RFUjwpfDPzMdv7wB38jr53l9tkNLxnWg== @@ -1836,7 +1841,7 @@ fastify-plugin@^4.0.0, fastify-plugin@^4.3.0: resolved "https://registry.npmmirror.com/fastify-plugin/-/fastify-plugin-4.5.0.tgz" integrity sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg== -fastify@^4.11.0, fastify@^4.18.0, fastify@>=4: +fastify@^4.11.0: version "4.18.0" resolved "https://registry.npmmirror.com/fastify/-/fastify-4.18.0.tgz" integrity sha512-L5o/2GEkBastQ3HV0dtKo7SUZ497Z1+q4fcqAoPyq6JCQ/8zdk1JQEoTQwnBWCp+EmA7AQa6mxNqSAEhzP0RwQ== @@ -2248,9 +2253,9 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -https-proxy-agent@7.0.1: +https-proxy-agent@7.0.1, https-proxy-agent@^7.0.0: version "7.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz#0277e28f13a07d45c663633841e20a40aaafe0ab" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz" integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== dependencies: agent-base "^7.0.2" @@ -2272,14 +2277,6 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" -https-proxy-agent@^7.0.0: - version "7.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.1.tgz" - integrity sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ== - dependencies: - agent-base "^7.0.2" - debug "4" - human-signals@^2.1.0: version "2.1.0" resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz" @@ -2733,13 +2730,6 @@ keyv@^4.5.2, keyv@^4.5.3: dependencies: json-buffer "3.0.1" -keyv@^4.5.3: - version "4.5.3" - resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" - integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== - dependencies: - json-buffer "3.0.1" - kind-of@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/kind-of/-/kind-of-2.0.1.tgz" @@ -3803,13 +3793,6 @@ semver@^7.3.4, semver@^7.3.5, semver@^7.3.8, semver@^7.5.0, semver@^7.5.4: dependencies: lru-cache "^6.0.0" -semver@^7.5.4: - version "7.5.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" - integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== - dependencies: - lru-cache "^6.0.0" - send@0.18.0: version "0.18.0" resolved "https://registry.npmmirror.com/send/-/send-0.18.0.tgz" @@ -3995,14 +3978,6 @@ streamx@^2.15.0: fast-fifo "^1.1.0" queue-tick "^1.0.1" -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - fast-fifo "^1.1.0" - queue-tick "^1.0.1" - "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"