mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 13:27:08 +00:00
fix: remove useless thing
This commit is contained in:
parent
5c544a5ca7
commit
bd7aac0517
53 changed files with 350 additions and 2639 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import {Config} from "../utils/config.js";
|
||||
import { Config } from '../utils/config.js'
|
||||
|
||||
const PLUGIN_CHAT = 'ChatGpt 对话'
|
||||
const PLUGIN_MANAGEMENT = 'ChatGPT-Plugin 管理'
|
||||
|
|
|
|||
76
apps/chat.js
76
apps/chat.js
|
|
@ -5,7 +5,6 @@ import { Config, defaultOpenAIAPI } from '../utils/config.js'
|
|||
import { v4 as uuid } from 'uuid'
|
||||
import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
|
||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||
import { PoeClient } from '../utils/poe/index.js'
|
||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||
import {
|
||||
|
|
@ -31,7 +30,6 @@ import {
|
|||
renderUrl
|
||||
} from '../utils/common.js'
|
||||
|
||||
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
||||
import { KeyvFile } from 'keyv-file'
|
||||
import { OfficialChatGPTClient } from '../utils/message.js'
|
||||
import fetch from 'node-fetch'
|
||||
|
|
@ -39,7 +37,7 @@ import { deleteConversation, getConversations, getLatestMessageIdByConversationI
|
|||
import { convertSpeaker, speakers } from '../utils/tts.js'
|
||||
import ChatGLMClient from '../utils/chatglm.js'
|
||||
import { convertFaces } from '../utils/face.js'
|
||||
import { originalValues, ConversationManager } from '../model/conversation.js'
|
||||
import { ConversationManager, originalValues } from '../model/conversation.js'
|
||||
import BingDrawClient from '../utils/BingDraw.js'
|
||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||
import Bard from '../utils/bard.js'
|
||||
|
|
@ -795,10 +793,6 @@ export class chatgpt extends plugin {
|
|||
key = `CHATGPT:CONVERSATIONS_CHATGLM:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
break
|
||||
}
|
||||
case 'browser': {
|
||||
key = `CHATGPT:CONVERSATIONS_BROWSER:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||
break
|
||||
}
|
||||
case 'claude2': {
|
||||
key = `CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`
|
||||
break
|
||||
|
|
@ -877,7 +871,7 @@ export class chatgpt extends plugin {
|
|||
// 字数超限直接返回
|
||||
return false
|
||||
}
|
||||
if (use !== 'api3' && use !== 'poe') {
|
||||
if (use !== 'api3') {
|
||||
previousConversation.conversation = {
|
||||
conversationId: chatMessage.conversationId
|
||||
}
|
||||
|
|
@ -1534,49 +1528,9 @@ export class chatgpt extends plugin {
|
|||
user: e.sender.user_id,
|
||||
cache: cacheOptions
|
||||
})
|
||||
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
|
||||
return sendMessageResult
|
||||
} else if (use === 'poe') {
|
||||
const cookie = await redis.get('CHATGPT:POE_TOKEN')
|
||||
if (!cookie) {
|
||||
throw new Error('未绑定Poe Cookie,请使用#chatgpt设置Poe token命令绑定cookie')
|
||||
}
|
||||
let client = new PoeClient({
|
||||
quora_cookie: cookie,
|
||||
proxy: Config.proxy
|
||||
})
|
||||
await client.setCredentials()
|
||||
await client.getChatId()
|
||||
let ai = 'a2' // todo
|
||||
await client.sendMsg(ai, prompt)
|
||||
const response = await client.getResponse(ai)
|
||||
return {
|
||||
text: response.data
|
||||
}
|
||||
return await this.chatGPTApi.sendMessage(prompt, conversation)
|
||||
} else if (use === 'claude') {
|
||||
// slack已经不可用,移除
|
||||
// let client = new SlackClaudeClient({
|
||||
// slackUserToken: Config.slackUserToken,
|
||||
// slackChannelId: Config.slackChannelId
|
||||
// })
|
||||
// let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
|
||||
// if (!conversationId) {
|
||||
// // 如果是新对话
|
||||
// if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
|
||||
// // 先发送设定
|
||||
// let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
|
||||
// let emotion = await AzureTTS.getEmotionPrompt(e)
|
||||
// if (emotion) {
|
||||
// prompt = prompt + '\n' + emotion
|
||||
// }
|
||||
// await client.sendMessage(prompt, e)
|
||||
// logger.info('claudeFirst:', prompt)
|
||||
// }
|
||||
// }
|
||||
// let text = await client.sendMessage(prompt, e)
|
||||
// return {
|
||||
// text
|
||||
// }
|
||||
const client = new ClaudeAPIClient({
|
||||
key: Config.claudeApiKey,
|
||||
model: Config.claudeApiModel || 'claude-3-sonnet-20240229',
|
||||
|
|
@ -2408,30 +2362,6 @@ export class chatgpt extends plugin {
|
|||
this.reply('总额度:$' + hardLimit + '\n已经使用额度:$' + totalUsage / 100 + '\n当前剩余额度:$' + left + '\n到期日期(UTC):' + expiresAt)
|
||||
}
|
||||
|
||||
/**
|
||||
* #chatgpt
|
||||
* @param prompt 问题
|
||||
* @param conversation 对话
|
||||
*/
|
||||
async chatgptBrowserBased (prompt, conversation) {
|
||||
let option = { markdown: true }
|
||||
if (Config['2captchaToken']) {
|
||||
option.captchaToken = Config['2captchaToken']
|
||||
}
|
||||
// option.debug = true
|
||||
option.email = Config.username
|
||||
option.password = Config.password
|
||||
this.chatGPTApi = new ChatGPTPuppeteer(option)
|
||||
logger.info(`chatgpt prompt: ${prompt}`)
|
||||
let sendMessageOption = {
|
||||
timeoutMs: 120000
|
||||
}
|
||||
if (conversation) {
|
||||
sendMessageOption = Object.assign(sendMessageOption, conversation)
|
||||
}
|
||||
return await this.chatGPTApi.sendMessage(prompt, sendMessageOption)
|
||||
}
|
||||
|
||||
/**
|
||||
* 其他模式
|
||||
* @param e
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { generateHello } from '../utils/randomMessage.js'
|
|||
import { generateVitsAudio } from '../utils/tts.js'
|
||||
import fs from 'fs'
|
||||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||||
import fetch from 'node-fetch'
|
||||
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
|
||||
import uploadRecord from '../utils/uploadRecord.js'
|
||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||
|
|
|
|||
|
|
@ -99,13 +99,8 @@ export class ChatgptManagement extends plugin {
|
|||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt切换(Poe|poe)$',
|
||||
fnc: 'useClaudeBasedSolution',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
reg: '^#chatgpt切换(Claude|claude|slack)$',
|
||||
fnc: 'useSlackClaudeBasedSolution',
|
||||
reg: '^#chatgpt切换(Claude|claude)$',
|
||||
fnc: 'useClaudeAPIBasedSolution',
|
||||
permission: 'master'
|
||||
},
|
||||
{
|
||||
|
|
@ -923,23 +918,13 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
}
|
||||
}
|
||||
|
||||
async useClaudeBasedSolution (e) {
|
||||
let use = await redis.get('CHATGPT:USE')
|
||||
if (use !== 'poe') {
|
||||
await redis.set('CHATGPT:USE', 'poe')
|
||||
await this.reply('已切换到基于Quora\'s POE的解决方案')
|
||||
} else {
|
||||
await this.reply('当前已经是POE模式了')
|
||||
}
|
||||
}
|
||||
|
||||
async useSlackClaudeBasedSolution () {
|
||||
async useClaudeAPIBasedSolution () {
|
||||
let use = await redis.get('CHATGPT:USE')
|
||||
if (use !== 'claude') {
|
||||
await redis.set('CHATGPT:USE', 'claude')
|
||||
await this.reply('已切换到基于slack claude机器人的解决方案')
|
||||
await this.reply('已切换到基于ClaudeAPI的解决方案')
|
||||
} else {
|
||||
await this.reply('当前已经是claude模式了')
|
||||
await this.reply('当前已经是Claude模式了')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -949,7 +934,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
|||
await redis.set('CHATGPT:USE', 'claude2')
|
||||
await this.reply('已切换到基于claude.ai的解决方案')
|
||||
} else {
|
||||
await this.reply('当前已经是claude2模式了')
|
||||
await this.reply('当前已经是claude.ai模式了')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import fs from 'fs'
|
||||
import _ from 'lodash'
|
||||
import { Config } from '../utils/config.js'
|
||||
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
|
||||
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
|
||||
import AzureTTS from "../utils/tts/microsoft-azure.js";
|
||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||
export class help extends plugin {
|
||||
constructor (e) {
|
||||
super({
|
||||
|
|
|
|||
|
|
@ -3,21 +3,11 @@ import plugin from '../../../lib/plugins/plugin.js'
|
|||
import { createRequire } from 'module'
|
||||
import _ from 'lodash'
|
||||
import { Restart } from '../../other/restart.js'
|
||||
import fs from 'fs'
|
||||
import {} from '../utils/common.js'
|
||||
|
||||
const _path = process.cwd()
|
||||
const require = createRequire(import.meta.url)
|
||||
const { exec, execSync } = require('child_process')
|
||||
|
||||
const checkAuth = async function (e) {
|
||||
if (!e.isMaster) {
|
||||
e.reply('只有主人才能命令ChatGPT哦~(*/ω\*)')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 是否在更新中
|
||||
let uping = false
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import plugin from '../../../lib/plugins/plugin.js'
|
||||
import { SunoClient } from '../client/SunoClient.js'
|
||||
import { Config } from '../utils/config.js'
|
||||
import { downloadFile, maskEmail } from '../utils/common.js'
|
||||
import { maskEmail } from '../utils/common.js'
|
||||
import common from '../../../lib/common/common.js'
|
||||
import lodash from 'lodash'
|
||||
|
||||
|
|
|
|||
|
|
@ -89,13 +89,6 @@
|
|||
"whitelist": [],
|
||||
"blacklist": [],
|
||||
"ttsRegex": "/匹配规则/匹配模式",
|
||||
"slackUserToken": "",
|
||||
"slackBotUserToken": "",
|
||||
"slackSigningSecret": "",
|
||||
"slackClaudeUserId": "",
|
||||
"slackClaudeEnableGlobalPreset": true,
|
||||
"slackClaudeGlobalPreset": "",
|
||||
"slackClaudeSpecifiedChannel": "",
|
||||
"cloudTranscode": "https://silk.201666.xyz",
|
||||
"cloudRender": false,
|
||||
"cloudMode": "url",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
"@fastify/static": "^6.9.0",
|
||||
"@fastify/websocket": "^8.2.0",
|
||||
"@google/generative-ai": "^0.1.1",
|
||||
"@slack/bolt": "^3.13.2",
|
||||
"asn1.js": "^5.0.0",
|
||||
"diff": "^5.1.0",
|
||||
"emoji-strip": "^1.0.1",
|
||||
|
|
@ -40,9 +39,6 @@
|
|||
"node-silk": "^0.1.0",
|
||||
"nodejs-pptx": "^1.2.4",
|
||||
"pdfjs-dist": "^3.11.174",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
"sharp": "^0.32.3",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import fetch, {
|
||||
Headers,
|
||||
Request,
|
||||
Response,
|
||||
// Headers,
|
||||
// Request,
|
||||
// Response,
|
||||
FormData
|
||||
} from 'node-fetch'
|
||||
import crypto from 'crypto'
|
||||
import WebSocket from 'ws'
|
||||
import { Config, pureSydneyInstruction } from './config.js'
|
||||
import { Config } from './config.js'
|
||||
import { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js'
|
||||
import moment from 'moment'
|
||||
import { getProxy } from './proxy.js'
|
||||
import common from '../../../lib/common/common.js'
|
||||
|
||||
if (!globalThis.fetch) {
|
||||
globalThis.fetch = fetch
|
||||
globalThis.Headers = Headers
|
||||
globalThis.Request = Request
|
||||
globalThis.Response = Response
|
||||
}
|
||||
//
|
||||
// if (!globalThis.fetch) {
|
||||
// globalThis.fetch = fetch
|
||||
// globalThis.Headers = Headers
|
||||
// globalThis.Request = Request
|
||||
// globalThis.Response = Response
|
||||
// }
|
||||
// workaround for ver 7.x and ver 5.x
|
||||
let proxy = getProxy()
|
||||
|
||||
|
|
|
|||
298
utils/bard.js
298
utils/bard.js
|
|
@ -1,215 +1,215 @@
|
|||
// https://github.com/EvanZhouDev/bard-ai
|
||||
|
||||
class Bard {
|
||||
static JSON = "json";
|
||||
static MD = "markdown";
|
||||
static JSON = 'json'
|
||||
static MD = 'markdown'
|
||||
|
||||
// ID derived from Cookie
|
||||
SNlM0e;
|
||||
SNlM0e
|
||||
|
||||
// HTTPS Headers
|
||||
#headers;
|
||||
#headers
|
||||
|
||||
// Resolution status of initialization call
|
||||
#initPromise;
|
||||
#initPromise
|
||||
|
||||
#bardURL = "https://bard.google.com";
|
||||
#bardURL = 'https://bard.google.com'
|
||||
|
||||
// Wether or not to log events to console
|
||||
#verbose = false;
|
||||
#verbose = false
|
||||
|
||||
// Fetch function
|
||||
#fetch = fetch;
|
||||
#fetch = fetch
|
||||
|
||||
constructor(cookie, config) {
|
||||
constructor (cookie, config) {
|
||||
// Register some settings
|
||||
if (config?.verbose == true) this.#verbose = true;
|
||||
if (config?.fetch) this.#fetch = config.fetch;
|
||||
if (config?.verbose == true) this.#verbose = true
|
||||
if (config?.fetch) this.#fetch = config.fetch
|
||||
// 可变更访问地址,利用反向代理绕过区域限制
|
||||
if (config?.bardURL) this.#bardURL = config.bardURL;
|
||||
if (config?.bardURL) this.#bardURL = config.bardURL
|
||||
|
||||
// If a Cookie is provided, initialize
|
||||
if (cookie) {
|
||||
this.#initPromise = this.#init(cookie);
|
||||
this.#initPromise = this.#init(cookie)
|
||||
} else {
|
||||
throw new Error("Please provide a Cookie when initializing Bard.");
|
||||
throw new Error('Please provide a Cookie when initializing Bard.')
|
||||
}
|
||||
this.cookie = cookie;
|
||||
this.cookie = cookie
|
||||
}
|
||||
|
||||
// You can also choose to initialize manually
|
||||
async #init(cookie) {
|
||||
this.#verbose && console.log("🚀 Starting intialization");
|
||||
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",
|
||||
'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),
|
||||
};
|
||||
Cookie: (typeof cookie === 'object') ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join('')) : ('__Secure-1PSID=' + cookie)
|
||||
}
|
||||
|
||||
let responseText;
|
||||
let responseText
|
||||
// Attempt to retrieve SNlM0e
|
||||
try {
|
||||
this.#verbose &&
|
||||
console.log("🔒 Authenticating your Google account");
|
||||
console.log('🔒 Authenticating your Google account')
|
||||
responseText = await this.#fetch(this.#bardURL, {
|
||||
method: "GET",
|
||||
method: 'GET',
|
||||
headers: this.#headers,
|
||||
credentials: "include",
|
||||
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: " +
|
||||
'Could not fetch Google Bard. You may be disconnected from internet: ' +
|
||||
e
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1];
|
||||
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]
|
||||
// Assign SNlM0e and return it
|
||||
this.SNlM0e = SNlM0e;
|
||||
this.#verbose && console.log("✅ Initialization finished\n");
|
||||
return SNlM0e;
|
||||
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."
|
||||
);
|
||||
'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;
|
||||
async #uploadImage (name, buffer) {
|
||||
this.#verbose && console.log('🖼️ Starting image processing')
|
||||
let size = buffer.byteLength
|
||||
let formBody = [
|
||||
`${encodeURIComponent("File name")}=${encodeURIComponent([name])}`,
|
||||
];
|
||||
`${encodeURIComponent('File name')}=${encodeURIComponent([name])}`
|
||||
]
|
||||
|
||||
try {
|
||||
this.#verbose &&
|
||||
console.log("💻 Finding Google server destination");
|
||||
console.log('💻 Finding Google server destination')
|
||||
let response = await this.#fetch(
|
||||
"https://content-push.googleapis.com/upload/",
|
||||
'https://content-push.googleapis.com/upload/',
|
||||
{
|
||||
method: "POST",
|
||||
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",
|
||||
'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",
|
||||
credentials: 'include'
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
const uploadUrl = response.headers.get("X-Goog-Upload-URL");
|
||||
this.#verbose && console.log("📤 Sending your image");
|
||||
const uploadUrl = response.headers.get('X-Goog-Upload-URL')
|
||||
this.#verbose && console.log('📤 Sending your image')
|
||||
response = await this.#fetch(uploadUrl, {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: {
|
||||
"X-Goog-Upload-Command": "upload, finalize",
|
||||
"X-Goog-Upload-Offset": 0,
|
||||
"X-Tenant-Id": "bard-storage",
|
||||
'X-Goog-Upload-Command': 'upload, finalize',
|
||||
'X-Goog-Upload-Offset': 0,
|
||||
'X-Tenant-Id': 'bard-storage'
|
||||
},
|
||||
body: buffer,
|
||||
credentials: "include",
|
||||
});
|
||||
credentials: 'include'
|
||||
})
|
||||
|
||||
const imageFileLocation = await response.text();
|
||||
const imageFileLocation = await response.text()
|
||||
|
||||
this.#verbose && console.log("✅ Image finished working\n");
|
||||
return imageFileLocation;
|
||||
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: " +
|
||||
'Could not fetch Google Bard. You may be disconnected from internet: ' +
|
||||
e
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Query Bard
|
||||
async #query(message, config) {
|
||||
async #query (message, config) {
|
||||
let formatMarkdown = (text, images) => {
|
||||
if (!images) return text;
|
||||
if (!images) return text
|
||||
|
||||
for (let imageData of images) {
|
||||
const formattedTag = `!${imageData.tag}(${imageData.url})`;
|
||||
const formattedTag = `!${imageData.tag}(${imageData.url})`
|
||||
text = text.replace(
|
||||
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
|
||||
formattedTag
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
return text;
|
||||
return text
|
||||
}
|
||||
|
||||
let { ids, imageBuffer } = config;
|
||||
let { ids, imageBuffer } = config
|
||||
|
||||
// Wait until after init
|
||||
await this.#initPromise;
|
||||
await this.#initPromise
|
||||
|
||||
this.#verbose && console.log("🔎 Starting Bard Query");
|
||||
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");
|
||||
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",
|
||||
};
|
||||
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],
|
||||
];
|
||||
[null, null, null]
|
||||
]
|
||||
|
||||
if (imageBuffer) {
|
||||
let imageLocation = await this.#uploadImage(
|
||||
`bard-ai_upload`,
|
||||
'bard-ai_upload',
|
||||
imageBuffer
|
||||
);
|
||||
)
|
||||
messageStruct[0].push(0, null, [
|
||||
[[imageLocation, 1], "bard-ai_upload"],
|
||||
]);
|
||||
[[imageLocation, 1], 'bard-ai_upload']
|
||||
])
|
||||
}
|
||||
|
||||
if (ids) {
|
||||
const { conversationID, responseID, choiceID } = ids;
|
||||
messageStruct[2] = [conversationID, responseID, choiceID];
|
||||
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,
|
||||
};
|
||||
'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",
|
||||
'/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
|
||||
this.#bardURL
|
||||
);
|
||||
)
|
||||
|
||||
// Append parameters to the URL
|
||||
for (const key in params) {
|
||||
url.searchParams.append(key, params[key]);
|
||||
url.searchParams.append(key, params[key])
|
||||
}
|
||||
|
||||
// Encode the data
|
||||
|
|
@ -220,30 +220,30 @@ class Bard {
|
|||
value
|
||||
)}`
|
||||
)
|
||||
.join("&");
|
||||
.join('&')
|
||||
|
||||
this.#verbose && console.log("💭 Sending message to Bard");
|
||||
this.#verbose && console.log('💭 Sending message to Bard')
|
||||
// Send the fetch request
|
||||
const chatData = await this.#fetch(url.toString(), {
|
||||
method: "POST",
|
||||
method: 'POST',
|
||||
headers: this.#headers,
|
||||
body: formBody,
|
||||
credentials: "include",
|
||||
credentials: 'include'
|
||||
})
|
||||
.then((response) => {
|
||||
return response.text();
|
||||
return response.text()
|
||||
})
|
||||
.then((text) => {
|
||||
return JSON.parse(text.split("\n")[3])[0][2];
|
||||
return JSON.parse(text.split('\n')[3])[0][2]
|
||||
})
|
||||
.then((rawData) => JSON.parse(rawData));
|
||||
.then((rawData) => JSON.parse(rawData))
|
||||
|
||||
this.#verbose && console.log("🧩 Parsing output");
|
||||
this.#verbose && console.log('🧩 Parsing output')
|
||||
// Get first Bard-recommended answer
|
||||
const answer = chatData[4][0];
|
||||
const answer = chatData[4][0]
|
||||
|
||||
// Text of that answer
|
||||
const text = answer[1][0];
|
||||
const text = answer[1][0]
|
||||
|
||||
// Get data about images in that answer
|
||||
const images =
|
||||
|
|
@ -255,44 +255,44 @@ class Bard {
|
|||
source: x[1][0][0],
|
||||
alt: x[0][4],
|
||||
website: x[1][1],
|
||||
favicon: x[1][3],
|
||||
},
|
||||
})) ?? [];
|
||||
favicon: x[1][3]
|
||||
}
|
||||
})) ?? []
|
||||
|
||||
this.#verbose && console.log("✅ All done!\n");
|
||||
this.#verbose && console.log('✅ All done!\n')
|
||||
// Put everything together and return
|
||||
return {
|
||||
content: formatMarkdown(text, images),
|
||||
images: images,
|
||||
images,
|
||||
ids: {
|
||||
conversationID: chatData[1][0],
|
||||
responseID: chatData[1][1],
|
||||
choiceID: answer[0],
|
||||
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000),
|
||||
},
|
||||
};
|
||||
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #parseConfig(config) {
|
||||
async #parseConfig (config) {
|
||||
let result = {
|
||||
useJSON: false,
|
||||
imageBuffer: undefined, // Returns as {extension, filename}
|
||||
ids: undefined,
|
||||
};
|
||||
ids: undefined
|
||||
}
|
||||
|
||||
// Verify that format is one of the two types
|
||||
if (config?.format) {
|
||||
switch (config.format) {
|
||||
case Bard.JSON:
|
||||
result.useJSON = true;
|
||||
break;
|
||||
result.useJSON = true
|
||||
break
|
||||
case Bard.MD:
|
||||
result.useJSON = false;
|
||||
break;
|
||||
result.useJSON = false
|
||||
break
|
||||
default:
|
||||
throw new Error(
|
||||
"Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output."
|
||||
);
|
||||
'Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -301,73 +301,73 @@ class Bard {
|
|||
if (
|
||||
config.image instanceof ArrayBuffer
|
||||
) {
|
||||
result.imageBuffer = config.image;
|
||||
result.imageBuffer = config.image
|
||||
} else if (
|
||||
typeof config.image === "string" &&
|
||||
typeof config.image === 'string' &&
|
||||
/\.(jpeg|jpg|png|webp)$/.test(config.image)
|
||||
) {
|
||||
let fs;
|
||||
let fs
|
||||
|
||||
try {
|
||||
fs = await import("fs")
|
||||
fs = await import('fs')
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Loading from an image file path is not supported in a browser environment.",
|
||||
);
|
||||
'Loading from an image file path is not supported in a browser environment.'
|
||||
)
|
||||
}
|
||||
|
||||
result.imageBuffer = fs.readFileSync(
|
||||
config.image,
|
||||
).buffer;
|
||||
config.image
|
||||
).buffer
|
||||
} else {
|
||||
throw new Error(
|
||||
"Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer."
|
||||
);
|
||||
'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;
|
||||
result.ids = config.ids
|
||||
} else {
|
||||
throw new Error(
|
||||
"Please provide the IDs exported exactly as given."
|
||||
);
|
||||
'Please provide the IDs exported exactly as given.'
|
||||
)
|
||||
}
|
||||
}
|
||||
return result;
|
||||
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;
|
||||
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;
|
||||
createChat (ids) {
|
||||
let bard = this
|
||||
class Chat {
|
||||
ids = ids;
|
||||
ids = ids
|
||||
|
||||
async ask(message, config) {
|
||||
let { useJSON, imageBuffer } = await bard.#parseConfig(config);
|
||||
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;
|
||||
ids: this.ids
|
||||
})
|
||||
this.ids = response.ids
|
||||
return useJSON ? response : response.content
|
||||
}
|
||||
|
||||
export() {
|
||||
return this.ids;
|
||||
export () {
|
||||
return this.ids
|
||||
}
|
||||
}
|
||||
|
||||
return new Chat();
|
||||
return new Chat()
|
||||
}
|
||||
}
|
||||
|
||||
export default Bard;
|
||||
export default Bard
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import fetch from 'node-fetch'
|
||||
|
||||
// this file is deprecated
|
||||
import {Config} from './config.js'
|
||||
import { Config } from './config.js'
|
||||
import HttpsProxyAgent from 'https-proxy-agent'
|
||||
|
||||
const newFetch = (url, options = {}) => {
|
||||
|
|
|
|||
932
utils/browser.js
932
utils/browser.js
|
|
@ -1,10 +1,5 @@
|
|||
import lodash from 'lodash'
|
||||
import { Config } from '../utils/config.js'
|
||||
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
|
||||
import { getOpenAIAuth } from './openai-auth.js'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import common from '../../../lib/common/common.js'
|
||||
const chatUrl = 'https://chat.openai.com/chat'
|
||||
import { Config } from './config.js'
|
||||
let puppeteer = {}
|
||||
|
||||
class Puppeteer {
|
||||
|
|
@ -48,19 +43,9 @@ class Puppeteer {
|
|||
|
||||
async initPupp () {
|
||||
if (!lodash.isEmpty(puppeteer)) return puppeteer
|
||||
puppeteer = (await import('puppeteer-extra')).default
|
||||
const pluginStealth = StealthPlugin()
|
||||
puppeteer.use(pluginStealth)
|
||||
if (Config['2captchaToken']) {
|
||||
const pluginCaptcha = (await import('puppeteer-extra-plugin-recaptcha')).default
|
||||
puppeteer.use(pluginCaptcha({
|
||||
provider: {
|
||||
id: '2captcha',
|
||||
token: Config['2captchaToken'] // REPLACE THIS WITH YOUR OWN 2CAPTCHA API KEY ⚡
|
||||
},
|
||||
visualFeedback: true
|
||||
}))
|
||||
}
|
||||
puppeteer = (await import('puppeteer')).default
|
||||
// const pluginStealth = StealthPlugin()
|
||||
// puppeteer.use(pluginStealth)
|
||||
return puppeteer
|
||||
}
|
||||
|
||||
|
|
@ -109,25 +94,10 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
|||
constructor (opts = {}) {
|
||||
super()
|
||||
const {
|
||||
email,
|
||||
password,
|
||||
markdown = true,
|
||||
debug = false,
|
||||
isGoogleLogin = false,
|
||||
minimize = true,
|
||||
captchaToken,
|
||||
executablePath
|
||||
debug = false
|
||||
} = opts
|
||||
|
||||
this._email = email
|
||||
this._password = password
|
||||
|
||||
this._markdown = !!markdown
|
||||
this._debug = !!debug
|
||||
this._isGoogleLogin = !!isGoogleLogin
|
||||
this._minimize = !!minimize
|
||||
this._captchaToken = captchaToken
|
||||
this._executablePath = executablePath
|
||||
}
|
||||
|
||||
async getBrowser () {
|
||||
|
|
@ -138,394 +108,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
|||
}
|
||||
}
|
||||
|
||||
async init () {
|
||||
// if (this.inited) {
|
||||
// return true
|
||||
// }
|
||||
logger.info('init chatgpt browser')
|
||||
try {
|
||||
// this.browser = await getBrowser({
|
||||
// captchaToken: this._captchaToken,
|
||||
// executablePath: this._executablePath
|
||||
// })
|
||||
this.browser = await this.getBrowser()
|
||||
this._page =
|
||||
(await this.browser.pages())[0] || (await this.browser.newPage())
|
||||
await maximizePage(this._page)
|
||||
this._page.on('request', this._onRequest.bind(this))
|
||||
this._page.on('response', this._onResponse.bind(this))
|
||||
// bypass cloudflare and login
|
||||
let preCookies = await redis.get('CHATGPT:RAW_COOKIES')
|
||||
if (preCookies) {
|
||||
await this._page.setCookie(...JSON.parse(preCookies))
|
||||
}
|
||||
// const url = this._page.url().replace(/\/$/, '')
|
||||
// bypass annoying popup modals
|
||||
await this._page.evaluateOnNewDocument(() => {
|
||||
window.localStorage.setItem('oai/apps/hasSeenOnboarding/chat', 'true')
|
||||
const chatGPTUpdateDates = ['2022-12-15', '2022-12-19', '2023-01-09', '2023-01-30', '2023-02-10']
|
||||
chatGPTUpdateDates.forEach(date => {
|
||||
window.localStorage.setItem(
|
||||
`oai/apps/hasSeenReleaseAnnouncement/${date}`,
|
||||
'true'
|
||||
)
|
||||
})
|
||||
})
|
||||
await this._page.goto(chatUrl, {
|
||||
waitUntil: 'networkidle2'
|
||||
})
|
||||
let timeout = 30000
|
||||
try {
|
||||
while (timeout > 0 && (await this._page.title()).toLowerCase().indexOf('moment') > -1) {
|
||||
// if meet captcha
|
||||
if (Config['2captchaToken']) {
|
||||
await this._page.solveRecaptchas()
|
||||
}
|
||||
await common.sleep(300)
|
||||
timeout = timeout - 300
|
||||
}
|
||||
} catch (e) {
|
||||
// navigation后获取title会报错,报错说明已经在navigation了正合我意。
|
||||
}
|
||||
if (timeout < 0) {
|
||||
logger.error('wait for cloudflare navigation timeout. 可能遇见验证码')
|
||||
throw new Error('wait for cloudflare navigation timeout. 可能遇见验证码')
|
||||
}
|
||||
try {
|
||||
await this._page.waitForNavigation({ timeout: 3000 })
|
||||
} catch (e) {}
|
||||
|
||||
if (!await this.getIsAuthenticated()) {
|
||||
await redis.del('CHATGPT:RAW_COOKIES')
|
||||
logger.info('需要登录,准备进行自动化登录')
|
||||
await getOpenAIAuth({
|
||||
email: this._email,
|
||||
password: this._password,
|
||||
browser: this.browser,
|
||||
page: this._page,
|
||||
isGoogleLogin: this._isGoogleLogin
|
||||
})
|
||||
logger.info('登录完成')
|
||||
} else {
|
||||
logger.info('无需登录')
|
||||
}
|
||||
} catch (err) {
|
||||
if (this.browser) {
|
||||
await this.browser.close()
|
||||
}
|
||||
|
||||
this.browser = null
|
||||
this._page = null
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
const url = this._page.url().replace(/\/$/, '')
|
||||
|
||||
if (url !== chatUrl) {
|
||||
await this._page.goto(chatUrl, {
|
||||
waitUntil: 'networkidle2'
|
||||
})
|
||||
}
|
||||
|
||||
// dismiss welcome modal (and other modals)
|
||||
do {
|
||||
const modalSelector = '[data-headlessui-state="open"]'
|
||||
|
||||
if (!(await this._page.$(modalSelector))) {
|
||||
break
|
||||
}
|
||||
|
||||
try {
|
||||
await this._page.click(`${modalSelector} button:last-child`)
|
||||
} catch (err) {
|
||||
// "next" button not found in welcome modal
|
||||
break
|
||||
}
|
||||
|
||||
await common.sleep(300)
|
||||
} while (true)
|
||||
|
||||
if (!await this.getIsAuthenticated()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this._minimize) {
|
||||
await minimizePage(this._page)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_onRequest = (request) => {
|
||||
const url = request.url()
|
||||
if (!isRelevantRequest(url)) {
|
||||
return
|
||||
}
|
||||
|
||||
const method = request.method()
|
||||
let body
|
||||
|
||||
if (method === 'POST') {
|
||||
body = request.postData()
|
||||
|
||||
try {
|
||||
body = JSON.parse(body)
|
||||
} catch (_) {
|
||||
}
|
||||
|
||||
// if (url.endsWith('/conversation') && typeof body === 'object') {
|
||||
// const conversationBody: types.ConversationJSONBody = body
|
||||
// const conversationId = conversationBody.conversation_id
|
||||
// const parentMessageId = conversationBody.parent_message_id
|
||||
// const messageId = conversationBody.messages?.[0]?.id
|
||||
// const prompt = conversationBody.messages?.[0]?.content?.parts?.[0]
|
||||
|
||||
// // TODO: store this info for the current sendMessage request
|
||||
// }
|
||||
}
|
||||
|
||||
if (this._debug) {
|
||||
console.log('\nrequest', {
|
||||
url,
|
||||
method,
|
||||
headers: request.headers(),
|
||||
body
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_onResponse = async (response) => {
|
||||
const request = response.request()
|
||||
|
||||
const url = response.url()
|
||||
if (!isRelevantRequest(url)) {
|
||||
return
|
||||
}
|
||||
|
||||
const status = response.status()
|
||||
|
||||
let body
|
||||
try {
|
||||
body = await response.json()
|
||||
} catch (_) {
|
||||
}
|
||||
|
||||
if (this._debug) {
|
||||
console.log('\nresponse', {
|
||||
url,
|
||||
ok: response.ok(),
|
||||
status,
|
||||
statusText: response.statusText(),
|
||||
headers: response.headers(),
|
||||
body,
|
||||
request: {
|
||||
method: request.method(),
|
||||
headers: request.headers(),
|
||||
body: request.postData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (url.endsWith('/conversation')) {
|
||||
if (status === 403) {
|
||||
await this.handle403Error()
|
||||
}
|
||||
} else if (url.endsWith('api/auth/session')) {
|
||||
if (status === 403) {
|
||||
await this.handle403Error()
|
||||
} else {
|
||||
const session = body
|
||||
if (session?.accessToken) {
|
||||
this._accessToken = session.accessToken
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async handle403Error () {
|
||||
console.log(`ChatGPT "${this._email}" session expired; refreshing...`)
|
||||
try {
|
||||
await maximizePage(this._page)
|
||||
await this._page.reload({
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: Config.chromeTimeoutMS // 2 minutes
|
||||
})
|
||||
if (this._minimize) {
|
||||
await minimizePage(this._page)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`ChatGPT "${this._email}" error refreshing session`,
|
||||
err.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async getIsAuthenticated () {
|
||||
try {
|
||||
const inputBox = await this._getInputBox()
|
||||
return !!inputBox
|
||||
} catch (err) {
|
||||
// can happen when navigating during login
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage (
|
||||
message,
|
||||
opts = {}
|
||||
) {
|
||||
const {
|
||||
conversationId,
|
||||
parentMessageId = uuidv4(),
|
||||
messageId = uuidv4(),
|
||||
action = 'next',
|
||||
// TODO
|
||||
timeoutMs,
|
||||
// onProgress,
|
||||
onConversationResponse
|
||||
} = opts
|
||||
|
||||
const inputBox = await this._getInputBox()
|
||||
if (!inputBox || !this._accessToken) {
|
||||
console.log(`chatgpt re-authenticating ${this._email}`)
|
||||
let isAuthenticated = false
|
||||
|
||||
try {
|
||||
isAuthenticated = await this.init()
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`chatgpt error re-authenticating ${this._email}`,
|
||||
err.toString()
|
||||
)
|
||||
throw err
|
||||
}
|
||||
let timeout = 100000
|
||||
if (isAuthenticated) {
|
||||
while (!this._accessToken) {
|
||||
// wait for async response hook result
|
||||
await common.sleep(300)
|
||||
timeout = timeout - 300
|
||||
if (timeout < 0) {
|
||||
const error = new Error('Not signed in')
|
||||
error.statusCode = 401
|
||||
throw error
|
||||
}
|
||||
}
|
||||
} else if (!this._accessToken) {
|
||||
const error = new Error('Not signed in')
|
||||
error.statusCode = 401
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const url = 'https://chat.openai.com/backend-api/conversation'
|
||||
const body = {
|
||||
action,
|
||||
messages: [
|
||||
{
|
||||
id: messageId,
|
||||
role: 'user',
|
||||
content: {
|
||||
content_type: 'text',
|
||||
parts: [message]
|
||||
}
|
||||
}
|
||||
],
|
||||
model: Config.plus ? Config.useGPT4 ? 'gpt-4' : 'text-davinci-002-render-sha' : 'text-davinci-002-render-sha',
|
||||
parent_message_id: parentMessageId
|
||||
}
|
||||
|
||||
if (conversationId) {
|
||||
body.conversation_id = conversationId
|
||||
}
|
||||
|
||||
// console.log('>>> EVALUATE', url, this._accessToken, body)
|
||||
const result = await this._page.evaluate(
|
||||
browserPostEventStream,
|
||||
url,
|
||||
this._accessToken,
|
||||
body,
|
||||
timeoutMs
|
||||
)
|
||||
// console.log('<<< EVALUATE', result)
|
||||
|
||||
if (result.error) {
|
||||
const error = new Error(result.error.message)
|
||||
error.statusCode = result.error.statusCode
|
||||
error.statusText = result.error.statusText
|
||||
|
||||
if (error.statusCode === 403) {
|
||||
await this.handle403Error()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
// TODO: support sending partial response events
|
||||
if (onConversationResponse) {
|
||||
onConversationResponse(result.conversationResponse)
|
||||
}
|
||||
|
||||
return {
|
||||
text: result.response,
|
||||
conversationId: result.conversationResponse.conversation_id,
|
||||
id: messageId,
|
||||
parentMessageId
|
||||
}
|
||||
|
||||
// const lastMessage = await this.getLastMessage()
|
||||
|
||||
// await inputBox.focus()
|
||||
// const paragraphs = message.split('\n')
|
||||
// for (let i = 0; i < paragraphs.length; i++) {
|
||||
// await inputBox.type(paragraphs[i], { delay: 0 })
|
||||
// if (i < paragraphs.length - 1) {
|
||||
// await this._page.keyboard.down('Shift')
|
||||
// await inputBox.press('Enter')
|
||||
// await this._page.keyboard.up('Shift')
|
||||
// } else {
|
||||
// await inputBox.press('Enter')
|
||||
// }
|
||||
// }
|
||||
|
||||
// const responseP = new Promise<string>(async (resolve, reject) => {
|
||||
// try {
|
||||
// do {
|
||||
// await common.sleep(1000)
|
||||
|
||||
// // TODO: this logic needs some work because we can have repeat messages...
|
||||
// const newLastMessage = await this.getLastMessage()
|
||||
// if (
|
||||
// newLastMessage &&
|
||||
// lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase()
|
||||
// ) {
|
||||
// return resolve(newLastMessage)
|
||||
// }
|
||||
// } while (true)
|
||||
// } catch (err) {
|
||||
// return reject(err)
|
||||
// }
|
||||
// })
|
||||
|
||||
// if (timeoutMs) {
|
||||
// return pTimeout(responseP, {
|
||||
// milliseconds: timeoutMs
|
||||
// })
|
||||
// } else {
|
||||
// return responseP
|
||||
// }
|
||||
}
|
||||
|
||||
async resetThread () {
|
||||
try {
|
||||
await this._page.click('nav > a:nth-child(1)')
|
||||
} catch (err) {
|
||||
// ignore for now
|
||||
}
|
||||
}
|
||||
|
||||
async close () {
|
||||
if (this.browser) {
|
||||
await this.browser.close()
|
||||
|
|
@ -533,510 +115,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
|||
this._page = null
|
||||
this.browser = null
|
||||
}
|
||||
|
||||
protected
|
||||
|
||||
async _getInputBox () {
|
||||
// [data-id="root"]
|
||||
return this._page?.$('textarea')
|
||||
}
|
||||
}
|
||||
|
||||
export default new ChatGPTPuppeteer()
|
||||
|
||||
export async function minimizePage (page) {
|
||||
const session = await page.target().createCDPSession()
|
||||
const goods = await session.send('Browser.getWindowForTarget')
|
||||
const { windowId } = goods
|
||||
await session.send('Browser.setWindowBounds', {
|
||||
windowId,
|
||||
bounds: { windowState: 'minimized' }
|
||||
})
|
||||
}
|
||||
|
||||
export async function maximizePage (page) {
|
||||
const session = await page.target().createCDPSession()
|
||||
const goods = await session.send('Browser.getWindowForTarget')
|
||||
const { windowId } = goods
|
||||
await session.send('Browser.setWindowBounds', {
|
||||
windowId,
|
||||
bounds: { windowState: 'normal' }
|
||||
})
|
||||
}
|
||||
|
||||
export function isRelevantRequest (url) {
|
||||
let pathname
|
||||
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
pathname = parsedUrl.pathname
|
||||
url = parsedUrl.toString()
|
||||
} catch (_) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!url.startsWith('https://chat.openai.com')) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
!pathname.startsWith('/backend-api/') &&
|
||||
!pathname.startsWith('/api/auth/session')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (pathname.endsWith('backend-api/moderations')) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is injected into the ChatGPT webapp page using puppeteer. It
|
||||
* has to be fully self-contained, so we copied a few third-party sources and
|
||||
* included them in here.
|
||||
*/
|
||||
export async function browserPostEventStream (
|
||||
url,
|
||||
accessToken,
|
||||
body,
|
||||
timeoutMs
|
||||
) {
|
||||
// Workaround for https://github.com/esbuild-kit/tsx/issues/113
|
||||
globalThis.__name = () => undefined
|
||||
|
||||
const BOM = [239, 187, 191]
|
||||
|
||||
let conversationResponse
|
||||
let conversationId = body?.conversation_id
|
||||
let messageId = body?.messages?.[0]?.id
|
||||
let response = ''
|
||||
|
||||
try {
|
||||
console.log('browserPostEventStream', url, accessToken, body)
|
||||
|
||||
let abortController = null
|
||||
if (timeoutMs) {
|
||||
abortController = new AbortController()
|
||||
}
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
signal: abortController?.signal,
|
||||
headers: {
|
||||
accept: 'text/event-stream',
|
||||
'x-openai-assistant-app-id': '',
|
||||
authorization: `Bearer ${accessToken}`,
|
||||
'content-type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
console.log('browserPostEventStream response', res)
|
||||
|
||||
if (!res.ok) {
|
||||
return {
|
||||
error: {
|
||||
message: `ChatGPTAPI error ${res.status || res.statusText}`,
|
||||
statusCode: res.status,
|
||||
statusText: res.statusText
|
||||
},
|
||||
response: null,
|
||||
conversationId,
|
||||
messageId
|
||||
}
|
||||
}
|
||||
|
||||
const responseP = new Promise(
|
||||
async (resolve, reject) => {
|
||||
function onMessage (data) {
|
||||
if (data === '[DONE]') {
|
||||
return resolve({
|
||||
error: null,
|
||||
response,
|
||||
conversationId,
|
||||
messageId,
|
||||
conversationResponse
|
||||
})
|
||||
}
|
||||
try {
|
||||
const _checkJson = JSON.parse(data)
|
||||
} catch (error) {
|
||||
console.log('warning: parse error.')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const convoResponseEvent =
|
||||
JSON.parse(data)
|
||||
conversationResponse = convoResponseEvent
|
||||
if (convoResponseEvent.conversation_id) {
|
||||
conversationId = convoResponseEvent.conversation_id
|
||||
}
|
||||
|
||||
if (convoResponseEvent.message?.id) {
|
||||
messageId = convoResponseEvent.message.id
|
||||
}
|
||||
|
||||
const partialResponse =
|
||||
convoResponseEvent.message?.content?.parts?.[0]
|
||||
if (partialResponse) {
|
||||
response = partialResponse
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('fetchSSE onMessage unexpected error', err)
|
||||
reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
const parser = createParser((event) => {
|
||||
if (event.type === 'event') {
|
||||
onMessage(event.data)
|
||||
}
|
||||
})
|
||||
|
||||
for await (const chunk of streamAsyncIterable(res.body)) {
|
||||
const str = new TextDecoder().decode(chunk)
|
||||
parser.feed(str)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (timeoutMs) {
|
||||
if (abortController) {
|
||||
// This will be called when a timeout occurs in order for us to forcibly
|
||||
// ensure that the underlying HTTP request is aborted.
|
||||
responseP.cancel = () => {
|
||||
abortController.abort()
|
||||
}
|
||||
}
|
||||
console.log({ pTimeout })
|
||||
return await pTimeout(responseP, {
|
||||
milliseconds: timeoutMs,
|
||||
message: 'ChatGPT timed out waiting for response'
|
||||
})
|
||||
} else {
|
||||
return await responseP
|
||||
}
|
||||
} catch (err) {
|
||||
const errMessageL = err.toString().toLowerCase()
|
||||
|
||||
if (
|
||||
response &&
|
||||
(errMessageL === 'error: typeerror: terminated' ||
|
||||
errMessageL === 'typeerror: terminated')
|
||||
) {
|
||||
// OpenAI sometimes forcefully terminates the socket from their end before
|
||||
// the HTTP request has resolved cleanly. In my testing, these cases tend to
|
||||
// happen when OpenAI has already send the last `response`, so we can ignore
|
||||
// the `fetch` error in this case.
|
||||
return {
|
||||
error: null,
|
||||
response,
|
||||
conversationId,
|
||||
messageId,
|
||||
conversationResponse
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: {
|
||||
message: err.toString(),
|
||||
statusCode: err.statusCode || err.status || err.response?.statusCode,
|
||||
statusText: err.statusText || err.response?.statusText
|
||||
},
|
||||
response: null,
|
||||
conversationId,
|
||||
messageId,
|
||||
conversationResponse
|
||||
}
|
||||
}
|
||||
// async function pTimeout (promise, option) {
|
||||
// return await pTimeout(promise, option)
|
||||
// }
|
||||
async function * streamAsyncIterable (stream) {
|
||||
const reader = stream.getReader()
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read()
|
||||
if (done) {
|
||||
return
|
||||
}
|
||||
yield value
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock()
|
||||
}
|
||||
}
|
||||
|
||||
// @see https://github.com/rexxars/eventsource-parser
|
||||
function createParser (onParse) {
|
||||
// Processing state
|
||||
let isFirstChunk
|
||||
let buffer
|
||||
let startingPosition
|
||||
let startingFieldLength
|
||||
|
||||
// Event state
|
||||
let eventId
|
||||
let eventName
|
||||
let data
|
||||
|
||||
reset()
|
||||
return { feed, reset }
|
||||
|
||||
function reset () {
|
||||
isFirstChunk = true
|
||||
buffer = ''
|
||||
startingPosition = 0
|
||||
startingFieldLength = -1
|
||||
|
||||
eventId = undefined
|
||||
eventName = undefined
|
||||
data = ''
|
||||
}
|
||||
|
||||
function feed (chunk) {
|
||||
buffer = buffer ? buffer + chunk : chunk
|
||||
|
||||
// Strip any UTF8 byte order mark (BOM) at the start of the stream.
|
||||
// Note that we do not strip any non - UTF8 BOM, as eventsource streams are
|
||||
// always decoded as UTF8 as per the specification.
|
||||
if (isFirstChunk && hasBom(buffer)) {
|
||||
buffer = buffer.slice(BOM.length)
|
||||
}
|
||||
|
||||
isFirstChunk = false
|
||||
|
||||
// Set up chunk-specific processing state
|
||||
const length = buffer.length
|
||||
let position = 0
|
||||
let discardTrailingNewline = false
|
||||
|
||||
// Read the current buffer byte by byte
|
||||
while (position < length) {
|
||||
// EventSource allows for carriage return + line feed, which means we
|
||||
// need to ignore a linefeed character if the previous character was a
|
||||
// carriage return
|
||||
// @todo refactor to reduce nesting, consider checking previous byte?
|
||||
// @todo but consider multiple chunks etc
|
||||
if (discardTrailingNewline) {
|
||||
if (buffer[position] === '\n') {
|
||||
++position
|
||||
}
|
||||
discardTrailingNewline = false
|
||||
}
|
||||
|
||||
let lineLength = -1
|
||||
let fieldLength = startingFieldLength
|
||||
let character
|
||||
|
||||
for (
|
||||
let index = startingPosition;
|
||||
lineLength < 0 && index < length;
|
||||
++index
|
||||
) {
|
||||
character = buffer[index]
|
||||
if (character === ':' && fieldLength < 0) {
|
||||
fieldLength = index - position
|
||||
} else if (character === '\r') {
|
||||
discardTrailingNewline = true
|
||||
lineLength = index - position
|
||||
} else if (character === '\n') {
|
||||
lineLength = index - position
|
||||
}
|
||||
}
|
||||
|
||||
if (lineLength < 0) {
|
||||
startingPosition = length - position
|
||||
startingFieldLength = fieldLength
|
||||
break
|
||||
} else {
|
||||
startingPosition = 0
|
||||
startingFieldLength = -1
|
||||
}
|
||||
|
||||
parseEventStreamLine(buffer, position, fieldLength, lineLength)
|
||||
|
||||
position += lineLength + 1
|
||||
}
|
||||
|
||||
if (position === length) {
|
||||
// If we consumed the entire buffer to read the event, reset the buffer
|
||||
buffer = ''
|
||||
} else if (position > 0) {
|
||||
// If there are bytes left to process, set the buffer to the unprocessed
|
||||
// portion of the buffer only
|
||||
buffer = buffer.slice(position)
|
||||
}
|
||||
}
|
||||
|
||||
function parseEventStreamLine (
|
||||
lineBuffer,
|
||||
index,
|
||||
fieldLength,
|
||||
lineLength
|
||||
) {
|
||||
if (lineLength === 0) {
|
||||
// We reached the last line of this event
|
||||
if (data.length > 0) {
|
||||
onParse({
|
||||
type: 'event',
|
||||
id: eventId,
|
||||
event: eventName || undefined,
|
||||
data: data.slice(0, -1) // remove trailing newline
|
||||
})
|
||||
|
||||
data = ''
|
||||
eventId = undefined
|
||||
}
|
||||
eventName = undefined
|
||||
return
|
||||
}
|
||||
|
||||
const noValue = fieldLength < 0
|
||||
const field = lineBuffer.slice(
|
||||
index,
|
||||
index + (noValue ? lineLength : fieldLength)
|
||||
)
|
||||
let step = 0
|
||||
|
||||
if (noValue) {
|
||||
step = lineLength
|
||||
} else if (lineBuffer[index + fieldLength + 1] === ' ') {
|
||||
step = fieldLength + 2
|
||||
} else {
|
||||
step = fieldLength + 1
|
||||
}
|
||||
|
||||
const position = index + step
|
||||
const valueLength = lineLength - step
|
||||
const value = lineBuffer
|
||||
.slice(position, position + valueLength)
|
||||
.toString()
|
||||
|
||||
if (field === 'data') {
|
||||
data += value ? `${value}\n` : '\n'
|
||||
} else if (field === 'event') {
|
||||
eventName = value
|
||||
} else if (field === 'id' && !value.includes('\u0000')) {
|
||||
eventId = value
|
||||
} else if (field === 'retry') {
|
||||
const retry = parseInt(value, 10)
|
||||
if (!Number.isNaN(retry)) {
|
||||
onParse({ type: 'reconnect-interval', value: retry })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hasBom (buffer) {
|
||||
return BOM.every(
|
||||
(charCode, index) => buffer.charCodeAt(index) === charCode
|
||||
)
|
||||
}
|
||||
|
||||
// @see https://github.com/sindresorhus/p-timeout
|
||||
function pTimeout (
|
||||
promise,
|
||||
options
|
||||
) {
|
||||
const {
|
||||
milliseconds,
|
||||
fallback,
|
||||
message,
|
||||
customTimers = { setTimeout, clearTimeout }
|
||||
} = options
|
||||
|
||||
let timer
|
||||
|
||||
const cancelablePromise = new Promise((resolve, reject) => {
|
||||
if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
|
||||
throw new TypeError(
|
||||
`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``
|
||||
)
|
||||
}
|
||||
|
||||
if (milliseconds === Number.POSITIVE_INFINITY) {
|
||||
resolve(promise)
|
||||
return
|
||||
}
|
||||
|
||||
if (options.signal) {
|
||||
const { signal } = options
|
||||
if (signal.aborted) {
|
||||
reject(getAbortedReason(signal))
|
||||
}
|
||||
|
||||
signal.addEventListener('abort', () => {
|
||||
reject(getAbortedReason(signal))
|
||||
})
|
||||
}
|
||||
|
||||
timer = customTimers.setTimeout.call(
|
||||
undefined,
|
||||
() => {
|
||||
if (fallback) {
|
||||
try {
|
||||
resolve(fallback())
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const errorMessage =
|
||||
typeof message === 'string'
|
||||
? message
|
||||
: `Promise timed out after ${milliseconds} milliseconds`
|
||||
const timeoutError =
|
||||
message instanceof Error ? message : new Error(errorMessage)
|
||||
|
||||
if (typeof promise.cancel === 'function') {
|
||||
promise.cancel()
|
||||
}
|
||||
|
||||
reject(timeoutError)
|
||||
},
|
||||
milliseconds
|
||||
)
|
||||
;(async () => {
|
||||
try {
|
||||
resolve(await promise)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
} finally {
|
||||
customTimers.clearTimeout.call(undefined, timer)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
cancelablePromise.clear = () => {
|
||||
customTimers.clearTimeout.call(undefined, timer)
|
||||
timer = undefined
|
||||
}
|
||||
|
||||
return cancelablePromise
|
||||
}
|
||||
/**
|
||||
TODO: Remove below function and just 'reject(signal.reason)' when targeting Node 18.
|
||||
*/
|
||||
function getAbortedReason (signal) {
|
||||
const reason =
|
||||
signal.reason === undefined
|
||||
? getDOMException('This operation was aborted.')
|
||||
: signal.reason
|
||||
|
||||
return reason instanceof Error ? reason : getDOMException(reason)
|
||||
}
|
||||
/**
|
||||
TODO: Remove AbortError and just throw DOMException when targeting Node 18.
|
||||
*/
|
||||
function getDOMException (errorMessage) {
|
||||
return globalThis.DOMException === undefined
|
||||
? new Error(errorMessage)
|
||||
: new DOMException(errorMessage)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
export async function getChatHistoryGroup (e, num) {
|
||||
// if (e.adapter === 'shamrock') {
|
||||
// return await e.group.getChatHistory(0, num, false)
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ const defaultConfig = {
|
|||
claudeSystemPrompt: '', // claude api 设定
|
||||
translateSource: 'openai',
|
||||
enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误
|
||||
enableToolbox: false, // 默认关闭工具箱节省占用和加速启动
|
||||
enableToolbox: true, // 默认关闭工具箱节省占用和加速启动
|
||||
version: 'v2.8.1'
|
||||
}
|
||||
const _path = process.cwd()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
export function decrypt (jwtToken) {
|
||||
const [encodedHeader, encodedPayload, signature] = jwtToken.split('.')
|
||||
|
||||
const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf-8')
|
||||
// const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf-8')
|
||||
const decodedPayload = Buffer.from(encodedPayload, 'base64').toString('utf-8')
|
||||
|
||||
return decodedPayload
|
||||
|
|
|
|||
|
|
@ -1,281 +0,0 @@
|
|||
import { Config } from '../utils/config.js'
|
||||
import random from 'random'
|
||||
import common from '../../../lib/common/common.js'
|
||||
|
||||
let hasRecaptchaPlugin = !!Config['2captchaToken']
|
||||
|
||||
export async function getOpenAIAuth (opt) {
|
||||
let {
|
||||
email,
|
||||
password,
|
||||
browser,
|
||||
page,
|
||||
timeoutMs = Config.chromeTimeoutMS,
|
||||
isGoogleLogin = false,
|
||||
captchaToken = Config['2captchaToken'],
|
||||
executablePath = Config.chromePath
|
||||
} = opt
|
||||
const origBrowser = browser
|
||||
const origPage = page
|
||||
|
||||
try {
|
||||
const userAgent = await browser.userAgent()
|
||||
if (!page) {
|
||||
page = (await browser.pages())[0] || (await browser.newPage())
|
||||
page.setDefaultTimeout(timeoutMs)
|
||||
}
|
||||
await page.goto('https://chat.openai.com/auth/login', {
|
||||
waitUntil: 'networkidle2'
|
||||
})
|
||||
logger.mark('chatgpt checkForChatGPTAtCapacity')
|
||||
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
|
||||
// NOTE: this is where you may encounter a CAPTCHA
|
||||
if (hasRecaptchaPlugin) {
|
||||
logger.mark('RecaptchaPlugin key exists, try to solve recaptchas')
|
||||
await page.solveRecaptchas()
|
||||
}
|
||||
|
||||
logger.mark('chatgpt checkForChatGPTAtCapacity again')
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
|
||||
// once we get to this point, the Cloudflare cookies should be available
|
||||
|
||||
// login as well (optional)
|
||||
if (email && password) {
|
||||
let retry = 3
|
||||
while (retry > 0) {
|
||||
try {
|
||||
await waitForConditionOrAtCapacity(page, () =>
|
||||
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
|
||||
)
|
||||
} catch (e) {
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
}
|
||||
retry--
|
||||
}
|
||||
await waitForConditionOrAtCapacity(page, () =>
|
||||
page.waitForSelector('#__next .btn-primary', { timeout: timeoutMs / 3 })
|
||||
)
|
||||
await common.sleep(500)
|
||||
|
||||
// click login button and wait for navigation to finish
|
||||
do {
|
||||
await Promise.all([
|
||||
page.waitForNavigation({
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: timeoutMs
|
||||
}),
|
||||
page.click('#__next .btn-primary')
|
||||
])
|
||||
await common.sleep(1000)
|
||||
} while (page.url().endsWith('/auth/login'))
|
||||
logger.mark('进入登录页面')
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
|
||||
let submitP
|
||||
|
||||
if (isGoogleLogin) {
|
||||
await page.click('button[data-provider="google"]')
|
||||
await page.waitForSelector('input[type="email"]')
|
||||
await page.type('input[type="email"]', email, { delay: 10 })
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
await page.keyboard.press('Enter')
|
||||
])
|
||||
await page.waitForSelector('input[type="password"]', { visible: true })
|
||||
await page.type('input[type="password"]', password, { delay: 10 })
|
||||
submitP = () => page.keyboard.press('Enter')
|
||||
} else {
|
||||
await page.waitForSelector('#username')
|
||||
await page.type('#username', email, { delay: 20 })
|
||||
await common.sleep(100)
|
||||
|
||||
if (hasRecaptchaPlugin) {
|
||||
// console.log('solveRecaptchas()')
|
||||
const res = await page.solveRecaptchas()
|
||||
// console.log('solveRecaptchas result', res)
|
||||
}
|
||||
|
||||
await page.click('button[type="submit"]')
|
||||
await page.waitForSelector('#password', { timeout: timeoutMs })
|
||||
await page.type('#password', password, { delay: 10 })
|
||||
submitP = () => page.click('button[type="submit"]')
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
waitForConditionOrAtCapacity(page, () =>
|
||||
page.waitForNavigation({
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: timeoutMs
|
||||
})
|
||||
),
|
||||
submitP()
|
||||
])
|
||||
} else {
|
||||
await common.sleep(2000)
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
}
|
||||
|
||||
const pageCookies = await page.cookies()
|
||||
await redis.set('CHATGPT:RAW_COOKIES', JSON.stringify(pageCookies))
|
||||
const cookies = pageCookies.reduce(
|
||||
(map, cookie) => ({ ...map, [cookie.name]: cookie }),
|
||||
{}
|
||||
)
|
||||
|
||||
const authInfo = {
|
||||
userAgent,
|
||||
clearanceToken: cookies.cf_clearance?.value,
|
||||
sessionToken: cookies['__Secure-next-auth.session-token']?.value,
|
||||
cookies
|
||||
}
|
||||
logger.info('chatgpt登录成功')
|
||||
|
||||
return authInfo
|
||||
} catch (err) {
|
||||
throw err
|
||||
} finally {
|
||||
await page.screenshot({
|
||||
type: 'png',
|
||||
path: './error.png'
|
||||
})
|
||||
if (origBrowser) {
|
||||
if (page && page !== origPage) {
|
||||
await page.close()
|
||||
}
|
||||
} else if (browser) {
|
||||
await browser.close()
|
||||
}
|
||||
|
||||
page = null
|
||||
browser = null
|
||||
}
|
||||
}
|
||||
|
||||
async function checkForChatGPTAtCapacity (page, opts = {}) {
|
||||
const {
|
||||
timeoutMs = Config.chromeTimeoutMS, // 2 minutes
|
||||
pollingIntervalMs = 3000,
|
||||
retries = 10
|
||||
} = opts
|
||||
|
||||
// console.log('checkForChatGPTAtCapacity', page.url())
|
||||
let isAtCapacity = false
|
||||
let numTries = 0
|
||||
|
||||
do {
|
||||
try {
|
||||
await solveSimpleCaptchas(page)
|
||||
|
||||
const res = await page.$x("//div[contains(., 'ChatGPT is at capacity')]")
|
||||
isAtCapacity = !!res?.length
|
||||
|
||||
if (isAtCapacity) {
|
||||
if (++numTries >= retries) {
|
||||
break
|
||||
}
|
||||
|
||||
// try refreshing the page if chatgpt is at capacity
|
||||
await page.reload({
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: timeoutMs
|
||||
})
|
||||
|
||||
await common.sleep(pollingIntervalMs)
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore errors likely due to navigation
|
||||
++numTries
|
||||
break
|
||||
}
|
||||
} while (isAtCapacity)
|
||||
|
||||
if (isAtCapacity) {
|
||||
const error = new Error('ChatGPT is at capacity')
|
||||
error.statusCode = 503
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForConditionOrAtCapacity (
|
||||
page,
|
||||
condition,
|
||||
opts = {}
|
||||
) {
|
||||
const { pollingIntervalMs = 500 } = opts
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
async function waitForCapacityText () {
|
||||
if (resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await checkForChatGPTAtCapacity(page)
|
||||
|
||||
if (!resolved) {
|
||||
setTimeout(waitForCapacityText, pollingIntervalMs)
|
||||
}
|
||||
} catch (err) {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
return reject(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
condition()
|
||||
.then(() => {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!resolved) {
|
||||
resolved = true
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
|
||||
setTimeout(waitForCapacityText, pollingIntervalMs)
|
||||
})
|
||||
}
|
||||
|
||||
async function solveSimpleCaptchas (page) {
|
||||
try {
|
||||
const verifyYouAreHuman = await page.$('text=Verify you are human')
|
||||
if (verifyYouAreHuman) {
|
||||
logger.mark('encounter cloudflare simple captcha "Verify you are human"')
|
||||
await common.sleep(2000)
|
||||
await verifyYouAreHuman.click({
|
||||
delay: random.int(5, 25)
|
||||
})
|
||||
await common.sleep(1000)
|
||||
}
|
||||
const verifyYouAreHumanCN = await page.$('text=确认您是真人')
|
||||
if (verifyYouAreHumanCN) {
|
||||
logger.mark('encounter cloudflare simple captcha "确认您是真人"')
|
||||
await common.sleep(2000)
|
||||
await verifyYouAreHumanCN.click({
|
||||
delay: random.int(5, 25)
|
||||
})
|
||||
await common.sleep(1000)
|
||||
}
|
||||
|
||||
const cloudflareButton = await page.$('.hcaptcha-box')
|
||||
if (cloudflareButton) {
|
||||
await common.sleep(2000)
|
||||
await cloudflareButton.click({
|
||||
delay: random.int(5, 25)
|
||||
})
|
||||
await common.sleep(1000)
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import fetch from 'node-fetch'
|
||||
import { readFileSync, writeFile } from 'fs'
|
||||
|
||||
const scrape = async (pbCookie, proxy) => {
|
||||
let option = { headers: { cookie: `${pbCookie}` } }
|
||||
if (proxy) {
|
||||
option.agent = proxy
|
||||
}
|
||||
const _setting = await fetch(
|
||||
'https://poe.com/api/settings',
|
||||
option
|
||||
)
|
||||
if (_setting.status !== 200) throw new Error('Failed to fetch token')
|
||||
const appSettings = await _setting.json()
|
||||
console.log(appSettings)
|
||||
const { tchannelData: { channel: channelName } } = appSettings
|
||||
return {
|
||||
channelName,
|
||||
appSettings,
|
||||
formKey: appSettings.formKey
|
||||
}
|
||||
}
|
||||
|
||||
const getUpdatedSettings = async (channelName, pbCookie, proxy) => {
|
||||
let option = { headers: { cookie: `${pbCookie}` } }
|
||||
if (proxy) {
|
||||
option.agent = proxy
|
||||
}
|
||||
const _setting = await fetch(
|
||||
`https://poe.com/api/settings?channel=${channelName}`,
|
||||
option
|
||||
)
|
||||
if (_setting.status !== 200) throw new Error('Failed to fetch token')
|
||||
const appSettings = await _setting.json()
|
||||
const { tchannelData: { minSeq } } = appSettings
|
||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
||||
credentials.app_settings.tchannelData.minSeq = minSeq
|
||||
writeFile('config.json', JSON.stringify(credentials, null, 4), function (err) {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
}
|
||||
})
|
||||
return {
|
||||
minSeq
|
||||
}
|
||||
}
|
||||
|
||||
export { scrape, getUpdatedSettings }
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
mutation AddHumanMessageMutation(
|
||||
$chatId: BigInt!
|
||||
$bot: String!
|
||||
$query: String!
|
||||
$source: MessageSource
|
||||
$withChatBreak: Boolean! = false
|
||||
) {
|
||||
messageCreateWithStatus(
|
||||
chatId: $chatId
|
||||
bot: $bot
|
||||
query: $query
|
||||
source: $source
|
||||
withChatBreak: $withChatBreak
|
||||
) {
|
||||
message {
|
||||
id
|
||||
__typename
|
||||
messageId
|
||||
text
|
||||
linkifiedText
|
||||
authorNickname
|
||||
state
|
||||
vote
|
||||
voteReason
|
||||
creationTime
|
||||
suggestedReplies
|
||||
chat {
|
||||
id
|
||||
shouldShowDisclaimer
|
||||
}
|
||||
}
|
||||
messageLimit{
|
||||
canSend
|
||||
numMessagesRemaining
|
||||
resetTime
|
||||
shouldShowReminder
|
||||
}
|
||||
chatBreak {
|
||||
id
|
||||
__typename
|
||||
messageId
|
||||
text
|
||||
linkifiedText
|
||||
authorNickname
|
||||
state
|
||||
vote
|
||||
voteReason
|
||||
creationTime
|
||||
suggestedReplies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
mutation AddMessageBreakMutation($chatId: BigInt!) {
|
||||
messageBreakCreate(chatId: $chatId) {
|
||||
message {
|
||||
id
|
||||
__typename
|
||||
messageId
|
||||
text
|
||||
linkifiedText
|
||||
authorNickname
|
||||
state
|
||||
vote
|
||||
voteReason
|
||||
creationTime
|
||||
suggestedReplies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) {
|
||||
autoSubscribe(subscriptions: $subscriptions) {
|
||||
viewer {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
fragment BioFragment on Viewer {
|
||||
id
|
||||
poeUser {
|
||||
id
|
||||
uid
|
||||
bio
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
subscription ChatAddedSubscription {
|
||||
chatAdded {
|
||||
...ChatFragment
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
fragment ChatFragment on Chat {
|
||||
id
|
||||
chatId
|
||||
defaultBotNickname
|
||||
shouldShowDisclaimer
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
query ChatPaginationQuery($bot: String!, $before: String, $last: Int! = 10) {
|
||||
chatOfBot(bot: $bot) {
|
||||
id
|
||||
__typename
|
||||
messagesConnection(before: $before, last: $last) {
|
||||
pageInfo {
|
||||
hasPreviousPage
|
||||
}
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
__typename
|
||||
messageId
|
||||
text
|
||||
linkifiedText
|
||||
authorNickname
|
||||
state
|
||||
vote
|
||||
voteReason
|
||||
creationTime
|
||||
suggestedReplies
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
query ChatViewQuery($bot: String!) {
|
||||
chatOfBot(bot: $bot) {
|
||||
id
|
||||
chatId
|
||||
defaultBotNickname
|
||||
shouldShowDisclaimer
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) {
|
||||
messagesDelete(messageIds: $messageIds) {
|
||||
viewer {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
fragment HandleFragment on Viewer {
|
||||
id
|
||||
poeUser {
|
||||
id
|
||||
uid
|
||||
handle
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
mutation LoginWithVerificationCodeMutation(
|
||||
$verificationCode: String!
|
||||
$emailAddress: String
|
||||
$phoneNumber: String
|
||||
) {
|
||||
loginWithVerificationCode(
|
||||
verificationCode: $verificationCode
|
||||
emailAddress: $emailAddress
|
||||
phoneNumber: $phoneNumber
|
||||
) {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
subscription MessageAddedSubscription($chatId: BigInt!) {
|
||||
messageAdded(chatId: $chatId) {
|
||||
...MessageFragment
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
subscription MessageDeletedSubscription($chatId: BigInt!) {
|
||||
messageDeleted(chatId: $chatId) {
|
||||
id
|
||||
messageId
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
fragment MessageFragment on Message {
|
||||
id
|
||||
__typename
|
||||
messageId
|
||||
text
|
||||
linkifiedText
|
||||
authorNickname
|
||||
state
|
||||
vote
|
||||
voteReason
|
||||
creationTime
|
||||
suggestedReplies
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
mutation MessageRemoveVoteMutation($messageId: BigInt!) {
|
||||
messageRemoveVote(messageId: $messageId) {
|
||||
message {
|
||||
...MessageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) {
|
||||
messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) {
|
||||
message {
|
||||
...MessageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
mutation SendVerificationCodeForLoginMutation(
|
||||
$emailAddress: String
|
||||
$phoneNumber: String
|
||||
) {
|
||||
sendVerificationCode(
|
||||
verificationReason: login
|
||||
emailAddress: $emailAddress
|
||||
phoneNumber: $phoneNumber
|
||||
) {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
mutation ShareMessagesMutation(
|
||||
$chatId: BigInt!
|
||||
$messageIds: [BigInt!]!
|
||||
$comment: String
|
||||
) {
|
||||
messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) {
|
||||
shareCode
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
mutation SignupWithVerificationCodeMutation(
|
||||
$verificationCode: String!
|
||||
$emailAddress: String
|
||||
$phoneNumber: String
|
||||
) {
|
||||
signupWithVerificationCode(
|
||||
verificationCode: $verificationCode
|
||||
emailAddress: $emailAddress
|
||||
phoneNumber: $phoneNumber
|
||||
) {
|
||||
status
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
mutation StaleChatUpdateMutation($chatId: BigInt!) {
|
||||
staleChatUpdate(chatId: $chatId) {
|
||||
message {
|
||||
...MessageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
query SummarizePlainPostQuery($comment: String!) {
|
||||
summarizePlainPost(comment: $comment)
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) {
|
||||
summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId)
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) {
|
||||
summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds)
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
fragment UserSnippetFragment on PoeUser {
|
||||
id
|
||||
uid
|
||||
bio
|
||||
handle
|
||||
fullName
|
||||
viewerIsFollowing
|
||||
isPoeOnlyUser
|
||||
profilePhotoURLTiny: profilePhotoUrl(size: tiny)
|
||||
profilePhotoURLSmall: profilePhotoUrl(size: small)
|
||||
profilePhotoURLMedium: profilePhotoUrl(size: medium)
|
||||
profilePhotoURLLarge: profilePhotoUrl(size: large)
|
||||
isFollowable
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
query ViewerInfoQuery {
|
||||
viewer {
|
||||
id
|
||||
uid
|
||||
...ViewerStateFragment
|
||||
...BioFragment
|
||||
...HandleFragment
|
||||
hasCompletedMultiplayerNux
|
||||
poeUser {
|
||||
id
|
||||
...UserSnippetFragment
|
||||
}
|
||||
messageLimit{
|
||||
canSend
|
||||
numMessagesRemaining
|
||||
resetTime
|
||||
shouldShowReminder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
fragment ViewerStateFragment on Viewer {
|
||||
id
|
||||
__typename
|
||||
iosMinSupportedVersion: integerGate(gateName: "poe_ios_min_supported_version")
|
||||
iosMinEncouragedVersion: integerGate(
|
||||
gateName: "poe_ios_min_encouraged_version"
|
||||
)
|
||||
macosMinSupportedVersion: integerGate(
|
||||
gateName: "poe_macos_min_supported_version"
|
||||
)
|
||||
macosMinEncouragedVersion: integerGate(
|
||||
gateName: "poe_macos_min_encouraged_version"
|
||||
)
|
||||
showPoeDebugPanel: booleanGate(gateName: "poe_show_debug_panel")
|
||||
enableCommunityFeed: booleanGate(gateName: "enable_poe_shares_feed")
|
||||
linkifyText: booleanGate(gateName: "poe_linkify_response")
|
||||
enableSuggestedReplies: booleanGate(gateName: "poe_suggested_replies")
|
||||
removeInviteLimit: booleanGate(gateName: "poe_remove_invite_limit")
|
||||
enableInAppPurchases: booleanGate(gateName: "poe_enable_in_app_purchases")
|
||||
availableBots {
|
||||
nickname
|
||||
displayName
|
||||
profilePicture
|
||||
isDown
|
||||
disclaimer
|
||||
subtitle
|
||||
poweredBy
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
subscription ViewerStateUpdatedSubscription {
|
||||
viewerStateUpdated {
|
||||
...ViewerStateFragment
|
||||
}
|
||||
}
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
import { readFileSync } from 'fs'
|
||||
import { scrape } from './credential.js'
|
||||
import fetch from 'node-fetch'
|
||||
import crypto from 'crypto'
|
||||
import { Config } from '../config.js'
|
||||
|
||||
let proxy
|
||||
if (Config.proxy) {
|
||||
try {
|
||||
proxy = (await import('https-proxy-agent')).default
|
||||
} catch (e) {
|
||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||
}
|
||||
}
|
||||
// used when test as a single file
|
||||
// const _path = process.cwd()
|
||||
const _path = process.cwd() + '/plugins/chatgpt-plugin/utils/poe'
|
||||
const gqlDir = `${_path}/graphql`
|
||||
const queries = {
|
||||
// chatViewQuery: readFileSync(gqlDir + '/ChatViewQuery.graphql', 'utf8'),
|
||||
addMessageBreakMutation: readFileSync(gqlDir + '/AddMessageBreakMutation.graphql', 'utf8'),
|
||||
chatPaginationQuery: readFileSync(gqlDir + '/ChatPaginationQuery.graphql', 'utf8'),
|
||||
addHumanMessageMutation: readFileSync(gqlDir + '/AddHumanMessageMutation.graphql', 'utf8'),
|
||||
loginMutation: readFileSync(gqlDir + '/LoginWithVerificationCodeMutation.graphql', 'utf8'),
|
||||
signUpWithVerificationCodeMutation: readFileSync(gqlDir + '/SignupWithVerificationCodeMutation.graphql', 'utf8'),
|
||||
sendVerificationCodeMutation: readFileSync(gqlDir + '/SendVerificationCodeForLoginMutation.graphql', 'utf8')
|
||||
}
|
||||
const optionMap = [
|
||||
{ title: 'Claude (Powered by Anthropic)', value: 'a2' },
|
||||
{ title: 'Sage (Powered by OpenAI - logical)', value: 'capybara' },
|
||||
{ title: 'Dragonfly (Powered by OpenAI - simpler)', value: 'nutria' },
|
||||
{ title: 'ChatGPT (Powered by OpenAI - current)', value: 'chinchilla' },
|
||||
{ title: 'Claude+', value: 'a2_2' },
|
||||
{ title: 'GPT-4', value: 'beaver' }
|
||||
]
|
||||
export class PoeClient {
|
||||
constructor (props) {
|
||||
this.config = props
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
Referrer: 'https://poe.com/',
|
||||
Origin: 'https://poe.com',
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'
|
||||
}
|
||||
|
||||
chatId = 0
|
||||
bot = ''
|
||||
|
||||
reConnectWs = false
|
||||
|
||||
async setCredentials () {
|
||||
let result = await scrape(this.config.quora_cookie, this.config.proxy ? proxy(Config.proxy) : null)
|
||||
console.log(result)
|
||||
this.config.quora_formkey = result.appSettings.formkey
|
||||
this.config.channel_name = result.channelName
|
||||
this.config.app_settings = result.appSettings
|
||||
|
||||
// set value
|
||||
this.headers['poe-formkey'] = this.config.quora_formkey // unused
|
||||
this.headers['poe-tchannel'] = this.config.channel_name
|
||||
this.headers.Cookie = this.config.quora_cookie
|
||||
console.log(this.headers)
|
||||
}
|
||||
|
||||
async subscribe () {
|
||||
const query = {
|
||||
queryName: 'subscriptionsMutation',
|
||||
variables: {
|
||||
subscriptions: [
|
||||
{
|
||||
subscriptionName: 'messageAdded',
|
||||
query: 'subscription subscriptions_messageAdded_Subscription(\n $chatId: BigInt!\n) {\n messageAdded(chatId: $chatId) {\n id\n messageId\n creationTime\n state\n ...ChatMessage_message\n ...chatHelpers_isBotMessage\n }\n}\n\nfragment ChatMessageDownvotedButton_message on Message {\n ...MessageFeedbackReasonModal_message\n ...MessageFeedbackOtherModal_message\n}\n\nfragment ChatMessageDropdownMenu_message on Message {\n id\n messageId\n vote\n text\n ...chatHelpers_isBotMessage\n}\n\nfragment ChatMessageFeedbackButtons_message on Message {\n id\n messageId\n vote\n voteReason\n ...ChatMessageDownvotedButton_message\n}\n\nfragment ChatMessageOverflowButton_message on Message {\n text\n ...ChatMessageDropdownMenu_message\n ...chatHelpers_isBotMessage\n}\n\nfragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {\n messageId\n}\n\nfragment ChatMessageSuggestedReplies_message on Message {\n suggestedReplies\n ...ChatMessageSuggestedReplies_SuggestedReplyButton_message\n}\n\nfragment ChatMessage_message on Message {\n id\n messageId\n text\n author\n linkifiedText\n state\n ...ChatMessageSuggestedReplies_message\n ...ChatMessageFeedbackButtons_message\n ...ChatMessageOverflowButton_message\n ...chatHelpers_isHumanMessage\n ...chatHelpers_isBotMessage\n ...chatHelpers_isChatBreak\n ...chatHelpers_useTimeoutLevel\n ...MarkdownLinkInner_message\n}\n\nfragment MarkdownLinkInner_message on Message {\n messageId\n}\n\nfragment MessageFeedbackOtherModal_message on Message {\n id\n messageId\n}\n\nfragment MessageFeedbackReasonModal_message on Message {\n id\n messageId\n}\n\nfragment chatHelpers_isBotMessage on Message {\n ...chatHelpers_isHumanMessage\n ...chatHelpers_isChatBreak\n}\n\nfragment chatHelpers_isChatBreak on Message {\n author\n}\n\nfragment chatHelpers_isHumanMessage on Message {\n author\n}\n\nfragment chatHelpers_useTimeoutLevel on Message {\n id\n state\n text\n messageId\n}\n'
|
||||
},
|
||||
{
|
||||
subscriptionName: 'viewerStateUpdated',
|
||||
query: 'subscription subscriptions_viewerStateUpdated_Subscription {\n viewerStateUpdated {\n id\n ...ChatPageBotSwitcher_viewer\n }\n}\n\nfragment BotHeader_bot on Bot {\n displayName\n ...BotImage_bot\n}\n\nfragment BotImage_bot on Bot {\n profilePicture\n displayName\n}\n\nfragment BotLink_bot on Bot {\n displayName\n}\n\nfragment ChatPageBotSwitcher_viewer on Viewer {\n availableBots {\n id\n ...BotLink_bot\n ...BotHeader_bot\n }\n}\n'
|
||||
}
|
||||
]
|
||||
},
|
||||
query: 'mutation subscriptionsMutation(\n $subscriptions: [AutoSubscriptionQuery!]!\n) {\n autoSubscribe(subscriptions: $subscriptions) {\n viewer {\n id\n }\n }\n}\n'
|
||||
}
|
||||
|
||||
await this.makeRequest(query)
|
||||
}
|
||||
|
||||
async makeRequest (request) {
|
||||
let payload = JSON.stringify(request)
|
||||
let baseString = payload + this.headers['poe-formkey'] + 'WpuLMiXEKKE98j56k'
|
||||
const md5 = crypto.createHash('md5').update(baseString).digest('hex')
|
||||
let option = {
|
||||
method: 'POST',
|
||||
headers: Object.assign(this.headers, {
|
||||
'poe-tag-id': md5,
|
||||
'content-type': 'application/json'
|
||||
}),
|
||||
body: payload
|
||||
}
|
||||
if (this.config.proxy) {
|
||||
option.agent = proxy(Config.proxy)
|
||||
}
|
||||
const response = await fetch('https://poe.com/api/gql_POST', option)
|
||||
let text = await response.text()
|
||||
try {
|
||||
let result = JSON.parse(text)
|
||||
console.log({ result })
|
||||
return result
|
||||
} catch (e) {
|
||||
console.error(text)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async getBot (displayName) {
|
||||
let r
|
||||
let retry = 10
|
||||
while (retry >= 0) {
|
||||
let url = `https://poe.com/_next/data/${this.nextData.buildId}/${displayName}.json`
|
||||
let option = {
|
||||
headers: this.headers
|
||||
}
|
||||
if (this.config.proxy) {
|
||||
option.agent = proxy(Config.proxy)
|
||||
}
|
||||
let r = await fetch(url, option)
|
||||
let res = await r.text()
|
||||
try {
|
||||
let chatData = (JSON.parse(res)).pageProps.payload.chatOfBotDisplayName
|
||||
return chatData
|
||||
} catch (e) {
|
||||
r = res
|
||||
retry--
|
||||
}
|
||||
}
|
||||
throw new Error(r)
|
||||
}
|
||||
|
||||
async getChatId () {
|
||||
let option = {
|
||||
headers: this.headers
|
||||
}
|
||||
if (this.config.proxy) {
|
||||
option.agent = proxy(Config.proxy)
|
||||
}
|
||||
let r = await fetch('https://poe.com', option)
|
||||
let text = await r.text()
|
||||
const jsonRegex = /<script id="__NEXT_DATA__" type="application\/json">(.+?)<\/script>/
|
||||
const jsonText = text.match(jsonRegex)[1]
|
||||
const nextData = JSON.parse(jsonText)
|
||||
this.nextData = nextData
|
||||
this.viewer = nextData.props.pageProps.payload.viewer
|
||||
|
||||
this.formkey = this.extract_formkey(text)
|
||||
this.headers['poe-formkey'] = this.formkey
|
||||
let bots = this.viewer.availableBots
|
||||
this.bots = {}
|
||||
for (let i = 0; i < bots.length; i++) {
|
||||
let bot = bots[i]
|
||||
let chatData = await this.getBot(bot.displayName)
|
||||
this.bots[chatData.defaultBotObject.nickname] = chatData
|
||||
}
|
||||
console.log(this.bots)
|
||||
}
|
||||
|
||||
extract_formkey (html) {
|
||||
const scriptRegex = /<script>if\(.+\)throw new Error;(.+)<\/script>/
|
||||
const scriptText = html.match(scriptRegex)[1]
|
||||
const keyRegex = /var .="([0-9a-f]+)",/
|
||||
const keyText = scriptText.match(keyRegex)[1]
|
||||
const cipherRegex = /.\[(\d+)]=.\[(\d+)]/g
|
||||
const cipherPairs = scriptText.match(cipherRegex)
|
||||
|
||||
const formkeyList = Array(cipherPairs.length).fill('')
|
||||
for (const pair of cipherPairs) {
|
||||
const [formkeyIndex, keyIndex] = pair.match(/\d+/g).map(Number)
|
||||
formkeyList[formkeyIndex] = keyText[keyIndex]
|
||||
}
|
||||
const formkey = formkeyList.join('')
|
||||
|
||||
return formkey
|
||||
}
|
||||
|
||||
async clearContext (bot) {
|
||||
try {
|
||||
const data = await this.makeRequest({
|
||||
query: `${queries.addMessageBreakMutation}`,
|
||||
variables: { chatId: this.config.chat_ids[bot] }
|
||||
})
|
||||
|
||||
if (!data.data) {
|
||||
this.reConnectWs = true // for websocket purpose
|
||||
console.log('ON TRY! Could not clear context! Trying to reLogin..')
|
||||
}
|
||||
return data
|
||||
} catch (e) {
|
||||
this.reConnectWs = true // for websocket purpose
|
||||
console.log('ON CATCH! Could not clear context! Trying to reLogin..')
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
async sendMsg (bot, query) {
|
||||
try {
|
||||
const data = await this.makeRequest({
|
||||
query: `${queries.addHumanMessageMutation}`,
|
||||
variables: {
|
||||
bot,
|
||||
chatId: this.bots[bot].chatId,
|
||||
query,
|
||||
source: null,
|
||||
withChatBreak: false
|
||||
}
|
||||
})
|
||||
console.log(data)
|
||||
if (!data.data) {
|
||||
this.reConnectWs = true // for cli websocket purpose
|
||||
console.log('Could not send message! Trying to reLogin..')
|
||||
}
|
||||
return data
|
||||
} catch (e) {
|
||||
this.reConnectWs = true // for cli websocket purpose
|
||||
console.error(e)
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
async getHistory (bot) {
|
||||
try {
|
||||
let response = await this.makeRequest({
|
||||
query: `${queries.chatPaginationQuery}`,
|
||||
variables: {
|
||||
before: null,
|
||||
bot,
|
||||
last: 25
|
||||
}
|
||||
})
|
||||
|
||||
return response.data.chatOfBot.messagesConnection.edges
|
||||
.map(({ node: { messageId, text, authorNickname } }) => ({
|
||||
messageId,
|
||||
text,
|
||||
authorNickname
|
||||
}))
|
||||
} catch (e) {
|
||||
console.log('There has been an error while fetching your history!')
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMessages (msgIds) {
|
||||
await this.makeRequest({
|
||||
queryName: 'MessageDeleteConfirmationModal_deleteMessageMutation_Mutation',
|
||||
variables: {
|
||||
messageIds: msgIds
|
||||
},
|
||||
query: 'mutation MessageDeleteConfirmationModal_deleteMessageMutation_Mutation(\n $messageIds: [BigInt!]!\n){\n messagesDelete(messageIds: $messageIds) {\n edgeIds\n }\n}\n'
|
||||
})
|
||||
}
|
||||
|
||||
async getResponse (bot) {
|
||||
let text
|
||||
let state
|
||||
let authorNickname
|
||||
try {
|
||||
while (true) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
let response = await this.makeRequest({
|
||||
query: `${queries.chatPaginationQuery}`,
|
||||
variables: {
|
||||
before: null,
|
||||
bot,
|
||||
last: 1
|
||||
}
|
||||
})
|
||||
let base = response.data.chatOfBot.messagesConnection.edges
|
||||
let lastEdgeIndex = base.length - 1
|
||||
text = base[lastEdgeIndex].node.text
|
||||
authorNickname = base[lastEdgeIndex].node.authorNickname
|
||||
state = base[lastEdgeIndex].node.state
|
||||
if (state === 'complete' && authorNickname === bot) {
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Could not get response!')
|
||||
return {
|
||||
status: false,
|
||||
message: 'failed',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: true,
|
||||
message: 'success',
|
||||
data: text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import WebSocket from 'ws'
|
||||
import * as diff from 'diff'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const getSocketUrl = async () => {
|
||||
const tchRand = Math.floor(100000 + Math.random() * 900000) // They're surely using 6 digit random number for ws url.
|
||||
const socketUrl = `wss://tch${tchRand}.tch.quora.com`
|
||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
||||
const appSettings = credentials.app_settings.tchannelData
|
||||
const boxName = appSettings.boxName
|
||||
const minSeq = appSettings.minSeq
|
||||
const channel = appSettings.channel
|
||||
const hash = appSettings.channelHash
|
||||
return `${socketUrl}/up/${boxName}/updates?min_seq=${minSeq}&channel=${channel}&hash=${hash}`
|
||||
}
|
||||
|
||||
export const connectWs = async () => {
|
||||
const url = await getSocketUrl()
|
||||
const ws = new WebSocket(url)
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.on('open', function open () {
|
||||
console.log('Connected to websocket')
|
||||
return resolve(ws)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const disconnectWs = async (ws) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.on('close', function close () {
|
||||
return resolve(true)
|
||||
})
|
||||
ws.close()
|
||||
})
|
||||
}
|
||||
|
||||
export const listenWs = async (ws) => {
|
||||
let previousText = ''
|
||||
return new Promise((resolve, reject) => {
|
||||
const onMessage = function incoming (data) {
|
||||
let jsonData = JSON.parse(data)
|
||||
if (jsonData.messages && jsonData.messages.length > 0) {
|
||||
const messages = JSON.parse(jsonData.messages[0])
|
||||
const dataPayload = messages.payload.data
|
||||
const text = dataPayload.messageAdded.text
|
||||
const state = dataPayload.messageAdded.state
|
||||
if (state !== 'complete') {
|
||||
const differences = diff.diffChars(previousText, text)
|
||||
let result = ''
|
||||
differences.forEach((part) => {
|
||||
if (part.added) {
|
||||
result += part.value
|
||||
}
|
||||
})
|
||||
previousText = text
|
||||
process.stdout.write(result)
|
||||
} else {
|
||||
ws.removeListener('message', onMessage)
|
||||
return resolve(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.on('message', onMessage)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import WebSocket from 'ws'
|
||||
import * as diff from 'diff'
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const getSocketUrl = async () => {
|
||||
const tchRand = Math.floor(100000 + Math.random() * 900000) // They're surely using 6 digit random number for ws url.
|
||||
const socketUrl = `wss://tch${tchRand}.tch.quora.com`
|
||||
const credentials = JSON.parse(readFileSync('config.json', 'utf8'))
|
||||
const appSettings = credentials.app_settings.tchannelData
|
||||
const boxName = appSettings.boxName
|
||||
const minSeq = appSettings.minSeq
|
||||
const channel = appSettings.channel
|
||||
const hash = appSettings.channelHash
|
||||
return `${socketUrl}/up/${boxName}/updates?min_seq=${minSeq}&channel=${channel}&hash=${hash}`
|
||||
}
|
||||
|
||||
export const connectWs = async () => {
|
||||
const url = await getSocketUrl()
|
||||
const ws = new WebSocket(url)
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.on('open', function open () {
|
||||
console.log('Connected to websocket')
|
||||
return resolve(ws)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const disconnectWs = async (ws) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ws.on('close', function close () {
|
||||
return resolve(true)
|
||||
})
|
||||
ws.close()
|
||||
})
|
||||
}
|
||||
|
||||
export const listenWs = async (ws) => {
|
||||
let previousText = ''
|
||||
return new Promise((resolve, reject) => {
|
||||
const onMessage = function incoming (data) {
|
||||
let jsonData = JSON.parse(data)
|
||||
if (jsonData.messages && jsonData.messages.length > 0) {
|
||||
const messages = JSON.parse(jsonData.messages[0])
|
||||
const dataPayload = messages.payload.data
|
||||
const text = dataPayload.messageAdded.text
|
||||
const state = dataPayload.messageAdded.state
|
||||
if (state !== 'complete') {
|
||||
const differences = diff.diffChars(previousText, text)
|
||||
let result = ''
|
||||
differences.forEach((part) => {
|
||||
if (part.added) {
|
||||
result += part.value
|
||||
}
|
||||
})
|
||||
previousText = text
|
||||
process.stdout.write(result)
|
||||
} else {
|
||||
ws.removeListener('message', onMessage)
|
||||
return resolve(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
ws.on('message', onMessage)
|
||||
})
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
import { Config } from '../config.js'
|
||||
import slack from '@slack/bolt'
|
||||
import { limitString } from '../common.js'
|
||||
import common from '../../../../lib/common/common.js'
|
||||
let proxy
|
||||
if (Config.proxy) {
|
||||
try {
|
||||
proxy = (await import('https-proxy-agent')).default
|
||||
} catch (e) {
|
||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||
}
|
||||
}
|
||||
export class SlackClaudeClient {
|
||||
constructor (props) {
|
||||
this.config = props
|
||||
if (Config.slackSigningSecret && Config.slackBotUserToken && Config.slackUserToken) {
|
||||
let option = {
|
||||
signingSecret: Config.slackSigningSecret,
|
||||
token: Config.slackBotUserToken,
|
||||
// socketMode: true,
|
||||
appToken: Config.slackUserToken
|
||||
// port: 45912
|
||||
}
|
||||
if (Config.proxy) {
|
||||
option.agent = proxy(Config.proxy)
|
||||
}
|
||||
option.logLevel = Config.debug ? 'debug' : 'info'
|
||||
this.app = new slack.App(option)
|
||||
} else {
|
||||
throw new Error('未配置Slack信息')
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage (prompt, e, t = 0) {
|
||||
if (t > 10) {
|
||||
return 'claude 未响应'
|
||||
}
|
||||
if (prompt.length > 3990) {
|
||||
logger.warn('消息长度大于slack限制,长度剪切至3990')
|
||||
prompt = limitString(prompt, 3990, false)
|
||||
}
|
||||
let channel
|
||||
let qq = e.sender.user_id
|
||||
if (Config.slackClaudeSpecifiedChannel) {
|
||||
channel = { id: Config.slackClaudeSpecifiedChannel }
|
||||
} else {
|
||||
let channels = await this.app.client.conversations.list({
|
||||
token: this.config.slackUserToken,
|
||||
types: 'public_channel,private_channel'
|
||||
})
|
||||
channel = channels.channels.filter(c => c.name === '' + qq)
|
||||
if (!channel || channel.length === 0) {
|
||||
let createChannelResponse = await this.app.client.conversations.create({
|
||||
token: this.config.slackUserToken,
|
||||
name: qq + '',
|
||||
is_private: true
|
||||
})
|
||||
channel = createChannelResponse.channel
|
||||
await this.app.client.conversations.invite({
|
||||
token: this.config.slackUserToken,
|
||||
channel: channel.id,
|
||||
users: Config.slackClaudeUserId
|
||||
})
|
||||
await common.sleep(1000)
|
||||
} else {
|
||||
channel = channel[0]
|
||||
}
|
||||
}
|
||||
let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${qq}`)
|
||||
if (!conversationId) {
|
||||
let sendResponse = await this.app.client.chat.postMessage({
|
||||
as_user: true,
|
||||
text: `<@${Config.slackClaudeUserId}> ${prompt}`,
|
||||
token: this.config.slackUserToken,
|
||||
channel: channel.id
|
||||
})
|
||||
let ts = sendResponse.ts
|
||||
let response = '_Typing…_'
|
||||
let tryTimes = 0
|
||||
// 发完先等3喵
|
||||
await common.sleep(3000)
|
||||
while (response.trim().endsWith('_Typing…_')) {
|
||||
let replies = await this.app.client.conversations.replies({
|
||||
token: this.config.slackUserToken,
|
||||
channel: channel.id,
|
||||
limit: 1000,
|
||||
ts
|
||||
})
|
||||
await await redis.set(`CHATGPT:SLACK_CONVERSATION:${qq}`, `${ts}`)
|
||||
if (replies.messages.length > 0) {
|
||||
let formalMessages = replies.messages
|
||||
.filter(m => m.metadata?.event_type !== 'claude_moderation')
|
||||
.filter(m => !m.text.startsWith('_'))
|
||||
if (!formalMessages[formalMessages.length - 1].bot_profile) {
|
||||
// 问题的下一句不是bot回复的,这属于意料之外的问题,可能是多人同时问问题导致 再问一次吧
|
||||
return await this.sendMessage(prompt, e, t + 1)
|
||||
}
|
||||
let reply = formalMessages[formalMessages.length - 1]
|
||||
if (!reply.text.startsWith(`<@${Config.slackClaudeUserId}>`)) {
|
||||
response = reply.text
|
||||
if (Config.debug) {
|
||||
let text = response.replace('_Typing…_', '')
|
||||
if (text) {
|
||||
logger.info(response.replace('_Typing…_', ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await common.sleep(2000)
|
||||
tryTimes++
|
||||
if (tryTimes > 3 && response === '_Typing…_') {
|
||||
// 过了6秒还没任何回复,就重新发一下试试
|
||||
logger.warn('claude没有响应,重试中')
|
||||
return await this.sendMessage(prompt, e, t + 1)
|
||||
}
|
||||
}
|
||||
return response
|
||||
} else {
|
||||
let postResponse = await this.app.client.chat.postMessage({
|
||||
as_user: true,
|
||||
text: `<@${Config.slackClaudeUserId}> ${prompt}`,
|
||||
token: this.config.slackUserToken,
|
||||
channel: channel.id,
|
||||
thread_ts: conversationId
|
||||
})
|
||||
let postTs = postResponse.ts
|
||||
let response = '_Typing…_'
|
||||
let tryTimes = 0
|
||||
// 发完先等3喵
|
||||
await common.sleep(3000)
|
||||
while (response.trim().endsWith('_Typing…_')) {
|
||||
let replies = await this.app.client.conversations.replies({
|
||||
token: this.config.slackUserToken,
|
||||
channel: channel.id,
|
||||
limit: 1000,
|
||||
ts: conversationId,
|
||||
oldest: postTs
|
||||
})
|
||||
|
||||
if (replies.messages.length > 0) {
|
||||
let formalMessages = replies.messages
|
||||
.filter(m => m.metadata?.event_type !== 'claude_moderation')
|
||||
.filter(m => !m.text.startsWith('_'))
|
||||
if (!formalMessages[formalMessages.length - 1].bot_profile) {
|
||||
// 问题的下一句不是bot回复的,这属于意料之外的问题,可能是多人同时问问题导致 再问一次吧
|
||||
return await this.sendMessage(prompt, e, t + 1)
|
||||
}
|
||||
let reply = formalMessages[formalMessages.length - 1]
|
||||
if (!reply.text.startsWith(`<@${Config.slackClaudeUserId}>`)) {
|
||||
response = reply.text
|
||||
if (Config.debug) {
|
||||
let text = response.replace('_Typing…_', '')
|
||||
if (text) {
|
||||
logger.info(response.replace('_Typing…_', ''))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await common.sleep(2000)
|
||||
tryTimes++
|
||||
if (tryTimes > 3 && response === '_Typing…_') {
|
||||
// 过了6秒还没任何回复,就重新发一下试试
|
||||
logger.warn('claude没有响应,重试中')
|
||||
return await this.sendMessage(prompt, e, t + 1)
|
||||
}
|
||||
}
|
||||
return response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ export class WebsiteTool extends AbstractTool {
|
|||
|
||||
func = async function (opts) {
|
||||
let { url, mode, e } = opts
|
||||
let browser
|
||||
try {
|
||||
// let res = await fetch(url, {
|
||||
// headers: {
|
||||
|
|
@ -34,7 +35,7 @@ export class WebsiteTool extends AbstractTool {
|
|||
origin = true
|
||||
}
|
||||
let ppt = new ChatGPTPuppeteer()
|
||||
let browser = await ppt.getBrowser()
|
||||
browser = await ppt.getBrowser()
|
||||
let page = await browser.newPage()
|
||||
await page.goto(url, {
|
||||
waitUntil: 'networkidle2'
|
||||
|
|
@ -104,6 +105,12 @@ export class WebsiteTool extends AbstractTool {
|
|||
}
|
||||
} catch (err) {
|
||||
return `failed to visit the website, error: ${err.toString()}`
|
||||
} finally {
|
||||
if (browser) {
|
||||
try {
|
||||
await browser.close()
|
||||
} catch (err) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,5 @@
|
|||
import { Config } from '../config.js'
|
||||
|
||||
let proxy
|
||||
if (Config.proxy) {
|
||||
try {
|
||||
proxy = (await import('https-proxy-agent')).default
|
||||
} catch (e) {
|
||||
console.warn('未安装https-proxy-agent,请在插件目录下执行pnpm add https-proxy-agent')
|
||||
}
|
||||
}
|
||||
|
||||
const newFetch = (url, options = {}) => {
|
||||
const defaultOptions = Config.proxy
|
||||
? {
|
||||
agent: proxy(Config.proxy)
|
||||
}
|
||||
: {}
|
||||
|
||||
const mergedOptions = {
|
||||
...defaultOptions,
|
||||
...options
|
||||
}
|
||||
|
||||
return fetch(url, mergedOptions)
|
||||
}
|
||||
import { newFetch } from '../proxy.js'
|
||||
|
||||
/**
|
||||
* 生成voxTTSMode下的wav音频
|
||||
|
|
|
|||
|
|
@ -1,15 +1,8 @@
|
|||
import { Config } from '../config.js'
|
||||
import fs from 'fs'
|
||||
import nodejieba from '@node-rs/jieba'
|
||||
|
||||
let nodejieba
|
||||
try {
|
||||
nodejieba = (await import('@node-rs/jieba')).default
|
||||
nodejieba.load()
|
||||
} catch (err) {
|
||||
logger.info('未安装@node-rs/jieba,娱乐功能-词云统计不可用')
|
||||
}
|
||||
|
||||
export class Tokenizer {
|
||||
class Tokenizer {
|
||||
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||
if (!groupId) {
|
||||
throw new Error('no valid group id')
|
||||
|
|
@ -78,6 +71,10 @@ export class Tokenizer {
|
|||
if (!nodejieba) {
|
||||
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
||||
}
|
||||
if (!this.loaded) {
|
||||
nodejieba.load()
|
||||
this.loaded = true
|
||||
}
|
||||
// duration represents the number of hours to go back, should in range [0, 24]
|
||||
let chats = await this.getHistory(e, groupId, new Date(), duration, userId)
|
||||
let durationStr = duration > 0 ? `${duration}小时` : '今日'
|
||||
|
|
@ -139,7 +136,7 @@ export class Tokenizer {
|
|||
}
|
||||
}
|
||||
|
||||
export class ShamrockTokenizer extends Tokenizer {
|
||||
class ShamrockTokenizer extends Tokenizer {
|
||||
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||
logger.mark('当前使用Shamrock适配器')
|
||||
if (!groupId) {
|
||||
|
|
@ -227,3 +224,8 @@ function isTimestampInDateRange (timestamp, startOfSpecifiedDate, endOfSpecified
|
|||
// Step 5: Compare the given timestamp with the start and end of the specified date
|
||||
return timestamp >= startOfSpecifiedDate && timestamp < endOfSpecifiedDate
|
||||
}
|
||||
|
||||
export default {
|
||||
default: new Tokenizer(),
|
||||
shamrock: new ShamrockTokenizer()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ShamrockTokenizer, Tokenizer } from './tokenizer.js'
|
||||
import Tokenizer from './tokenizer.js'
|
||||
import { render } from '../common.js'
|
||||
|
||||
export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
||||
|
|
@ -12,8 +12,8 @@ export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
|||
|
||||
function getTokenizer (e) {
|
||||
if (e.adapter === 'shamrock') {
|
||||
return new ShamrockTokenizer()
|
||||
return Tokenizer.shamrock
|
||||
} else {
|
||||
return new Tokenizer()
|
||||
return Tokenizer.default
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue