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 plugin from '../../../lib/plugins/plugin.js'
|
||||||
import {Config} from "../utils/config.js";
|
import { Config } from '../utils/config.js'
|
||||||
|
|
||||||
const PLUGIN_CHAT = 'ChatGpt 对话'
|
const PLUGIN_CHAT = 'ChatGpt 对话'
|
||||||
const PLUGIN_MANAGEMENT = 'ChatGPT-Plugin 管理'
|
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 { v4 as uuid } from 'uuid'
|
||||||
import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
|
import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
|
||||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
import { PoeClient } from '../utils/poe/index.js'
|
|
||||||
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
import AzureTTS from '../utils/tts/microsoft-azure.js'
|
||||||
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
import VoiceVoxTTS from '../utils/tts/voicevox.js'
|
||||||
import {
|
import {
|
||||||
|
|
@ -31,7 +30,6 @@ import {
|
||||||
renderUrl
|
renderUrl
|
||||||
} from '../utils/common.js'
|
} from '../utils/common.js'
|
||||||
|
|
||||||
import { ChatGPTPuppeteer } from '../utils/browser.js'
|
|
||||||
import { KeyvFile } from 'keyv-file'
|
import { KeyvFile } from 'keyv-file'
|
||||||
import { OfficialChatGPTClient } from '../utils/message.js'
|
import { OfficialChatGPTClient } from '../utils/message.js'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
|
@ -39,7 +37,7 @@ import { deleteConversation, getConversations, getLatestMessageIdByConversationI
|
||||||
import { convertSpeaker, speakers } from '../utils/tts.js'
|
import { convertSpeaker, speakers } from '../utils/tts.js'
|
||||||
import ChatGLMClient from '../utils/chatglm.js'
|
import ChatGLMClient from '../utils/chatglm.js'
|
||||||
import { convertFaces } from '../utils/face.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 BingDrawClient from '../utils/BingDraw.js'
|
||||||
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
|
||||||
import Bard from '../utils/bard.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}`
|
key = `CHATGPT:CONVERSATIONS_CHATGLM:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'browser': {
|
|
||||||
key = `CHATGPT:CONVERSATIONS_BROWSER:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'claude2': {
|
case 'claude2': {
|
||||||
key = `CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`
|
key = `CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`
|
||||||
break
|
break
|
||||||
|
|
@ -877,7 +871,7 @@ export class chatgpt extends plugin {
|
||||||
// 字数超限直接返回
|
// 字数超限直接返回
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (use !== 'api3' && use !== 'poe') {
|
if (use !== 'api3') {
|
||||||
previousConversation.conversation = {
|
previousConversation.conversation = {
|
||||||
conversationId: chatMessage.conversationId
|
conversationId: chatMessage.conversationId
|
||||||
}
|
}
|
||||||
|
|
@ -1534,49 +1528,9 @@ export class chatgpt extends plugin {
|
||||||
user: e.sender.user_id,
|
user: e.sender.user_id,
|
||||||
cache: cacheOptions
|
cache: cacheOptions
|
||||||
})
|
})
|
||||||
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
|
return 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
|
|
||||||
}
|
|
||||||
} else if (use === 'claude') {
|
} else if (use === 'claude') {
|
||||||
// slack已经不可用,移除
|
// 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({
|
const client = new ClaudeAPIClient({
|
||||||
key: Config.claudeApiKey,
|
key: Config.claudeApiKey,
|
||||||
model: Config.claudeApiModel || 'claude-3-sonnet-20240229',
|
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)
|
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
|
* @param e
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import { generateHello } from '../utils/randomMessage.js'
|
||||||
import { generateVitsAudio } from '../utils/tts.js'
|
import { generateVitsAudio } from '../utils/tts.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
|
||||||
import fetch from 'node-fetch'
|
|
||||||
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
|
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
|
||||||
import uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'
|
||||||
|
|
|
||||||
|
|
@ -99,13 +99,8 @@ export class ChatgptManagement extends plugin {
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt切换(Poe|poe)$',
|
reg: '^#chatgpt切换(Claude|claude)$',
|
||||||
fnc: 'useClaudeBasedSolution',
|
fnc: 'useClaudeAPIBasedSolution',
|
||||||
permission: 'master'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
reg: '^#chatgpt切换(Claude|claude|slack)$',
|
|
||||||
fnc: 'useSlackClaudeBasedSolution',
|
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -923,23 +918,13 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async useClaudeBasedSolution (e) {
|
async useClaudeAPIBasedSolution () {
|
||||||
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 () {
|
|
||||||
let use = await redis.get('CHATGPT:USE')
|
let use = await redis.get('CHATGPT:USE')
|
||||||
if (use !== 'claude') {
|
if (use !== 'claude') {
|
||||||
await redis.set('CHATGPT:USE', 'claude')
|
await redis.set('CHATGPT:USE', 'claude')
|
||||||
await this.reply('已切换到基于slack claude机器人的解决方案')
|
await this.reply('已切换到基于ClaudeAPI的解决方案')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是claude模式了')
|
await this.reply('当前已经是Claude模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -949,7 +934,7 @@ azure语音:Azure 语音是微软 Azure 平台提供的一项语音服务,
|
||||||
await redis.set('CHATGPT:USE', 'claude2')
|
await redis.set('CHATGPT:USE', 'claude2')
|
||||||
await this.reply('已切换到基于claude.ai的解决方案')
|
await this.reply('已切换到基于claude.ai的解决方案')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是claude2模式了')
|
await this.reply('当前已经是claude.ai模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import plugin from '../../../lib/plugins/plugin.js'
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import fs from 'fs'
|
|
||||||
import _ from 'lodash'
|
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from '../utils/config.js'
|
||||||
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
|
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
|
||||||
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.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 {
|
export class help extends plugin {
|
||||||
constructor (e) {
|
constructor (e) {
|
||||||
super({
|
super({
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,11 @@ import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { Restart } from '../../other/restart.js'
|
import { Restart } from '../../other/restart.js'
|
||||||
import fs from 'fs'
|
|
||||||
import {} from '../utils/common.js'
|
import {} from '../utils/common.js'
|
||||||
|
|
||||||
const _path = process.cwd()
|
|
||||||
const require = createRequire(import.meta.url)
|
const require = createRequire(import.meta.url)
|
||||||
const { exec, execSync } = require('child_process')
|
const { exec, execSync } = require('child_process')
|
||||||
|
|
||||||
const checkAuth = async function (e) {
|
|
||||||
if (!e.isMaster) {
|
|
||||||
e.reply('只有主人才能命令ChatGPT哦~(*/ω\*)')
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 是否在更新中
|
// 是否在更新中
|
||||||
let uping = false
|
let uping = false
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import plugin from '../../../lib/plugins/plugin.js'
|
import plugin from '../../../lib/plugins/plugin.js'
|
||||||
import { SunoClient } from '../client/SunoClient.js'
|
import { SunoClient } from '../client/SunoClient.js'
|
||||||
import { Config } from '../utils/config.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 common from '../../../lib/common/common.js'
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,13 +89,6 @@
|
||||||
"whitelist": [],
|
"whitelist": [],
|
||||||
"blacklist": [],
|
"blacklist": [],
|
||||||
"ttsRegex": "/匹配规则/匹配模式",
|
"ttsRegex": "/匹配规则/匹配模式",
|
||||||
"slackUserToken": "",
|
|
||||||
"slackBotUserToken": "",
|
|
||||||
"slackSigningSecret": "",
|
|
||||||
"slackClaudeUserId": "",
|
|
||||||
"slackClaudeEnableGlobalPreset": true,
|
|
||||||
"slackClaudeGlobalPreset": "",
|
|
||||||
"slackClaudeSpecifiedChannel": "",
|
|
||||||
"cloudTranscode": "https://silk.201666.xyz",
|
"cloudTranscode": "https://silk.201666.xyz",
|
||||||
"cloudRender": false,
|
"cloudRender": false,
|
||||||
"cloudMode": "url",
|
"cloudMode": "url",
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@
|
||||||
"@fastify/static": "^6.9.0",
|
"@fastify/static": "^6.9.0",
|
||||||
"@fastify/websocket": "^8.2.0",
|
"@fastify/websocket": "^8.2.0",
|
||||||
"@google/generative-ai": "^0.1.1",
|
"@google/generative-ai": "^0.1.1",
|
||||||
"@slack/bolt": "^3.13.2",
|
|
||||||
"asn1.js": "^5.0.0",
|
"asn1.js": "^5.0.0",
|
||||||
"diff": "^5.1.0",
|
"diff": "^5.1.0",
|
||||||
"emoji-strip": "^1.0.1",
|
"emoji-strip": "^1.0.1",
|
||||||
|
|
@ -40,9 +39,6 @@
|
||||||
"node-silk": "^0.1.0",
|
"node-silk": "^0.1.0",
|
||||||
"nodejs-pptx": "^1.2.4",
|
"nodejs-pptx": "^1.2.4",
|
||||||
"pdfjs-dist": "^3.11.174",
|
"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",
|
"sharp": "^0.32.3",
|
||||||
"xlsx": "^0.18.5"
|
"xlsx": "^0.18.5"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,23 @@
|
||||||
import fetch, {
|
import fetch, {
|
||||||
Headers,
|
// Headers,
|
||||||
Request,
|
// Request,
|
||||||
Response,
|
// Response,
|
||||||
FormData
|
FormData
|
||||||
} from 'node-fetch'
|
} from 'node-fetch'
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import WebSocket from 'ws'
|
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 { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import { getProxy } from './proxy.js'
|
import { getProxy } from './proxy.js'
|
||||||
import common from '../../../lib/common/common.js'
|
import common from '../../../lib/common/common.js'
|
||||||
|
//
|
||||||
if (!globalThis.fetch) {
|
// if (!globalThis.fetch) {
|
||||||
globalThis.fetch = fetch
|
// globalThis.fetch = fetch
|
||||||
globalThis.Headers = Headers
|
// globalThis.Headers = Headers
|
||||||
globalThis.Request = Request
|
// globalThis.Request = Request
|
||||||
globalThis.Response = Response
|
// globalThis.Response = Response
|
||||||
}
|
// }
|
||||||
// workaround for ver 7.x and ver 5.x
|
// workaround for ver 7.x and ver 5.x
|
||||||
let proxy = getProxy()
|
let proxy = getProxy()
|
||||||
|
|
||||||
|
|
|
||||||
590
utils/bard.js
590
utils/bard.js
|
|
@ -1,373 +1,373 @@
|
||||||
// https://github.com/EvanZhouDev/bard-ai
|
// https://github.com/EvanZhouDev/bard-ai
|
||||||
|
|
||||||
class Bard {
|
class Bard {
|
||||||
static JSON = "json";
|
static JSON = 'json'
|
||||||
static MD = "markdown";
|
static MD = 'markdown'
|
||||||
|
|
||||||
// ID derived from Cookie
|
// ID derived from Cookie
|
||||||
SNlM0e;
|
SNlM0e
|
||||||
|
|
||||||
// HTTPS Headers
|
// HTTPS Headers
|
||||||
#headers;
|
#headers
|
||||||
|
|
||||||
// Resolution status of initialization call
|
// 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
|
// Wether or not to log events to console
|
||||||
#verbose = false;
|
#verbose = false
|
||||||
|
|
||||||
// Fetch function
|
// Fetch function
|
||||||
#fetch = fetch;
|
#fetch = fetch
|
||||||
|
|
||||||
constructor(cookie, config) {
|
constructor (cookie, config) {
|
||||||
// Register some settings
|
// Register some settings
|
||||||
if (config?.verbose == true) this.#verbose = true;
|
if (config?.verbose == true) this.#verbose = true
|
||||||
if (config?.fetch) this.#fetch = config.fetch;
|
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 a Cookie is provided, initialize
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
this.#initPromise = this.#init(cookie);
|
this.#initPromise = this.#init(cookie)
|
||||||
} else {
|
} 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
|
// You can also choose to initialize manually
|
||||||
async #init(cookie) {
|
async #init (cookie) {
|
||||||
this.#verbose && console.log("🚀 Starting intialization");
|
this.#verbose && console.log('🚀 Starting intialization')
|
||||||
// Assign headers
|
// Assign headers
|
||||||
this.#headers = {
|
this.#headers = {
|
||||||
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
|
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
|
||||||
"X-Same-Domain": "1",
|
'X-Same-Domain': '1',
|
||||||
"User-Agent":
|
'User-Agent':
|
||||||
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
'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",
|
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||||
Origin: this.#bardURL,
|
Origin: this.#bardURL,
|
||||||
Referer: 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
|
// Attempt to retrieve SNlM0e
|
||||||
try {
|
try {
|
||||||
this.#verbose &&
|
this.#verbose &&
|
||||||
console.log("🔒 Authenticating your Google account");
|
console.log('🔒 Authenticating your Google account')
|
||||||
responseText = await this.#fetch(this.#bardURL, {
|
responseText = await this.#fetch(this.#bardURL, {
|
||||||
method: "GET",
|
method: 'GET',
|
||||||
headers: this.#headers,
|
headers: this.#headers,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
})
|
})
|
||||||
.then((response) => response.text())
|
.then((response) => response.text())
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Failure to get server
|
// Failure to get server
|
||||||
throw new Error(
|
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
|
e
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1];
|
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]
|
||||||
// Assign SNlM0e and return it
|
// Assign SNlM0e and return it
|
||||||
this.SNlM0e = SNlM0e;
|
this.SNlM0e = SNlM0e
|
||||||
this.#verbose && console.log("✅ Initialization finished\n");
|
this.#verbose && console.log('✅ Initialization finished\n')
|
||||||
return SNlM0e;
|
return SNlM0e
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
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) {
|
async #uploadImage (name, buffer) {
|
||||||
this.#verbose && console.log("🖼️ Starting image processing");
|
this.#verbose && console.log('🖼️ Starting image processing')
|
||||||
let size = buffer.byteLength;
|
let size = buffer.byteLength
|
||||||
let formBody = [
|
let formBody = [
|
||||||
`${encodeURIComponent("File name")}=${encodeURIComponent([name])}`,
|
`${encodeURIComponent('File name')}=${encodeURIComponent([name])}`
|
||||||
];
|
]
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.#verbose &&
|
this.#verbose &&
|
||||||
console.log("💻 Finding Google server destination");
|
console.log('💻 Finding Google server destination')
|
||||||
let response = await this.#fetch(
|
let response = await this.#fetch(
|
||||||
"https://content-push.googleapis.com/upload/",
|
'https://content-push.googleapis.com/upload/',
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"X-Goog-Upload-Command": "start",
|
'X-Goog-Upload-Command': 'start',
|
||||||
"X-Goog-Upload-Protocol": "resumable",
|
'X-Goog-Upload-Protocol': 'resumable',
|
||||||
"X-Goog-Upload-Header-Content-Length": size,
|
'X-Goog-Upload-Header-Content-Length': size,
|
||||||
"X-Tenant-Id": "bard-storage",
|
'X-Tenant-Id': 'bard-storage',
|
||||||
"Push-Id": "feeds/mcudyrk2a4khkz",
|
'Push-Id': 'feeds/mcudyrk2a4khkz'
|
||||||
},
|
},
|
||||||
body: formBody,
|
body: formBody,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const uploadUrl = response.headers.get("X-Goog-Upload-URL");
|
const uploadUrl = response.headers.get('X-Goog-Upload-URL')
|
||||||
this.#verbose && console.log("📤 Sending your image");
|
this.#verbose && console.log('📤 Sending your image')
|
||||||
response = await this.#fetch(uploadUrl, {
|
response = await this.#fetch(uploadUrl, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"X-Goog-Upload-Command": "upload, finalize",
|
'X-Goog-Upload-Command': 'upload, finalize',
|
||||||
"X-Goog-Upload-Offset": 0,
|
'X-Goog-Upload-Offset': 0,
|
||||||
"X-Tenant-Id": "bard-storage",
|
'X-Tenant-Id': 'bard-storage'
|
||||||
},
|
},
|
||||||
body: buffer,
|
body: buffer,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
});
|
})
|
||||||
|
|
||||||
const imageFileLocation = await response.text();
|
const imageFileLocation = await response.text()
|
||||||
|
|
||||||
this.#verbose && console.log("✅ Image finished working\n");
|
this.#verbose && console.log('✅ Image finished working\n')
|
||||||
return imageFileLocation;
|
return imageFileLocation
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
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
|
e
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query Bard
|
// Query Bard
|
||||||
async #query(message, config) {
|
async #query (message, config) {
|
||||||
let formatMarkdown = (text, images) => {
|
let formatMarkdown = (text, images) => {
|
||||||
if (!images) return text;
|
if (!images) return text
|
||||||
|
|
||||||
for (let imageData of images) {
|
for (let imageData of images) {
|
||||||
const formattedTag = `!${imageData.tag}(${imageData.url})`;
|
const formattedTag = `!${imageData.tag}(${imageData.url})`
|
||||||
text = text.replace(
|
text = text.replace(
|
||||||
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
|
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
|
||||||
formattedTag
|
formattedTag
|
||||||
);
|
)
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let { ids, imageBuffer } = config;
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
// Wait until after init
|
let { ids, imageBuffer } = config
|
||||||
await this.#initPromise;
|
|
||||||
|
|
||||||
this.#verbose && console.log("🔎 Starting Bard Query");
|
// Wait until after init
|
||||||
|
await this.#initPromise
|
||||||
|
|
||||||
// If user has not run init
|
this.#verbose && console.log('🔎 Starting Bard Query')
|
||||||
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");
|
// If user has not run init
|
||||||
// HTTPS parameters
|
if (!this.SNlM0e) {
|
||||||
const params = {
|
throw new Error(
|
||||||
bl: "boq_assistant-bard-web-server_20230711.08_p0",
|
"Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)."
|
||||||
_reqID: ids?._reqID ?? "0",
|
)
|
||||||
rt: "c",
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// If IDs are provided, but doesn't have every one of the expected IDs, error
|
this.#verbose && console.log('🏗️ Building Request')
|
||||||
const messageStruct = [
|
// HTTPS parameters
|
||||||
[message],
|
const params = {
|
||||||
null,
|
bl: 'boq_assistant-bard-web-server_20230711.08_p0',
|
||||||
[null, null, null],
|
_reqID: ids?._reqID ?? '0',
|
||||||
];
|
rt: 'c'
|
||||||
|
}
|
||||||
|
|
||||||
if (imageBuffer) {
|
// If IDs are provided, but doesn't have every one of the expected IDs, error
|
||||||
let imageLocation = await this.#uploadImage(
|
const messageStruct = [
|
||||||
`bard-ai_upload`,
|
[message],
|
||||||
imageBuffer
|
null,
|
||||||
);
|
[null, null, null]
|
||||||
messageStruct[0].push(0, null, [
|
]
|
||||||
[[imageLocation, 1], "bard-ai_upload"],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids) {
|
if (imageBuffer) {
|
||||||
const { conversationID, responseID, choiceID } = ids;
|
let imageLocation = await this.#uploadImage(
|
||||||
messageStruct[2] = [conversationID, responseID, choiceID];
|
'bard-ai_upload',
|
||||||
}
|
imageBuffer
|
||||||
|
)
|
||||||
|
messageStruct[0].push(0, null, [
|
||||||
|
[[imageLocation, 1], 'bard-ai_upload']
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
// HTTPs data
|
if (ids) {
|
||||||
const data = {
|
const { conversationID, responseID, choiceID } = ids
|
||||||
"f.req": JSON.stringify([null, JSON.stringify(messageStruct)]),
|
messageStruct[2] = [conversationID, responseID, choiceID]
|
||||||
at: this.SNlM0e,
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// URL that we are submitting to
|
// HTTPs data
|
||||||
const url = new URL(
|
const data = {
|
||||||
"/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
|
'f.req': JSON.stringify([null, JSON.stringify(messageStruct)]),
|
||||||
this.#bardURL
|
at: this.SNlM0e
|
||||||
);
|
}
|
||||||
|
|
||||||
// Append parameters to the URL
|
// URL that we are submitting to
|
||||||
for (const key in params) {
|
const url = new URL(
|
||||||
url.searchParams.append(key, params[key]);
|
'/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
|
||||||
}
|
this.#bardURL
|
||||||
|
)
|
||||||
|
|
||||||
// Encode the data
|
// Append parameters to the URL
|
||||||
const formBody = Object.entries(data)
|
for (const key in params) {
|
||||||
.map(
|
url.searchParams.append(key, params[key])
|
||||||
([property, value]) =>
|
}
|
||||||
|
|
||||||
|
// Encode the data
|
||||||
|
const formBody = Object.entries(data)
|
||||||
|
.map(
|
||||||
|
([property, value]) =>
|
||||||
`${encodeURIComponent(property)}=${encodeURIComponent(
|
`${encodeURIComponent(property)}=${encodeURIComponent(
|
||||||
value
|
value
|
||||||
)}`
|
)}`
|
||||||
)
|
)
|
||||||
.join("&");
|
.join('&')
|
||||||
|
|
||||||
this.#verbose && console.log("💭 Sending message to Bard");
|
this.#verbose && console.log('💭 Sending message to Bard')
|
||||||
// Send the fetch request
|
// Send the fetch request
|
||||||
const chatData = await this.#fetch(url.toString(), {
|
const chatData = await this.#fetch(url.toString(), {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: this.#headers,
|
headers: this.#headers,
|
||||||
body: formBody,
|
body: formBody,
|
||||||
credentials: "include",
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
return response.text()
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((text) => {
|
||||||
return response.text();
|
return JSON.parse(text.split('\n')[3])[0][2]
|
||||||
})
|
})
|
||||||
.then((text) => {
|
.then((rawData) => JSON.parse(rawData))
|
||||||
return JSON.parse(text.split("\n")[3])[0][2];
|
|
||||||
})
|
|
||||||
.then((rawData) => JSON.parse(rawData));
|
|
||||||
|
|
||||||
this.#verbose && console.log("🧩 Parsing output");
|
this.#verbose && console.log('🧩 Parsing output')
|
||||||
// Get first Bard-recommended answer
|
// Get first Bard-recommended answer
|
||||||
const answer = chatData[4][0];
|
const answer = chatData[4][0]
|
||||||
|
|
||||||
// Text of that answer
|
// Text of that answer
|
||||||
const text = answer[1][0];
|
const text = answer[1][0]
|
||||||
|
|
||||||
// Get data about images in that answer
|
// Get data about images in that answer
|
||||||
const images =
|
const images =
|
||||||
answer[4]?.map((x) => ({
|
answer[4]?.map((x) => ({
|
||||||
tag: x[2],
|
tag: x[2],
|
||||||
url: x[3][0][0],
|
url: x[3][0][0],
|
||||||
info: {
|
info: {
|
||||||
raw: x[0][0][0],
|
raw: x[0][0][0],
|
||||||
source: x[1][0][0],
|
source: x[1][0][0],
|
||||||
alt: x[0][4],
|
alt: x[0][4],
|
||||||
website: x[1][1],
|
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
|
// Put everything together and return
|
||||||
return {
|
return {
|
||||||
content: formatMarkdown(text, images),
|
content: formatMarkdown(text, images),
|
||||||
images: images,
|
images,
|
||||||
ids: {
|
ids: {
|
||||||
conversationID: chatData[1][0],
|
conversationID: chatData[1][0],
|
||||||
responseID: chatData[1][1],
|
responseID: chatData[1][1],
|
||||||
choiceID: answer[0],
|
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 = {
|
let result = {
|
||||||
useJSON: false,
|
useJSON: false,
|
||||||
imageBuffer: undefined, // Returns as {extension, filename}
|
imageBuffer: undefined, // Returns as {extension, filename}
|
||||||
ids: undefined,
|
ids: undefined
|
||||||
};
|
}
|
||||||
|
|
||||||
// Verify that format is one of the two types
|
// Verify that format is one of the two types
|
||||||
if (config?.format) {
|
if (config?.format) {
|
||||||
switch (config.format) {
|
switch (config.format) {
|
||||||
case Bard.JSON:
|
case Bard.JSON:
|
||||||
result.useJSON = true;
|
result.useJSON = true
|
||||||
break;
|
break
|
||||||
case Bard.MD:
|
case Bard.MD:
|
||||||
result.useJSON = false;
|
result.useJSON = false
|
||||||
break;
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(
|
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.'
|
||||||
);
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the image passed in is either a path to a jpeg, jpg, png, or webp, or that it is a Buffer
|
// Verify that the image passed in is either a path to a jpeg, jpg, png, or webp, or that it is a Buffer
|
||||||
if (config?.image) {
|
if (config?.image) {
|
||||||
if (
|
if (
|
||||||
config.image instanceof ArrayBuffer
|
config.image instanceof ArrayBuffer
|
||||||
) {
|
) {
|
||||||
result.imageBuffer = config.image;
|
result.imageBuffer = config.image
|
||||||
} else if (
|
} else if (
|
||||||
typeof config.image === "string" &&
|
typeof config.image === 'string' &&
|
||||||
/\.(jpeg|jpg|png|webp)$/.test(config.image)
|
/\.(jpeg|jpg|png|webp)$/.test(config.image)
|
||||||
) {
|
) {
|
||||||
let fs;
|
let fs
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fs = await import("fs")
|
fs = await import('fs')
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(
|
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(
|
result.imageBuffer = fs.readFileSync(
|
||||||
config.image,
|
config.image
|
||||||
).buffer;
|
).buffer
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
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
|
// Verify that all values in IDs exist
|
||||||
if (config?.ids) {
|
if (config?.ids) {
|
||||||
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
|
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
|
||||||
result.ids = config.ids;
|
result.ids = config.ids
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
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!
|
// Ask Bard a question!
|
||||||
async ask(message, config) {
|
async ask (message, config) {
|
||||||
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config);
|
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config)
|
||||||
let response = await this.#query(message, { imageBuffer, ids });
|
let response = await this.#query(message, { imageBuffer, ids })
|
||||||
return useJSON ? response : response.content;
|
return useJSON ? response : response.content
|
||||||
}
|
}
|
||||||
|
|
||||||
createChat(ids) {
|
createChat (ids) {
|
||||||
let bard = this;
|
let bard = this
|
||||||
class Chat {
|
class Chat {
|
||||||
ids = ids;
|
ids = ids
|
||||||
|
|
||||||
async ask(message, config) {
|
async ask (message, config) {
|
||||||
let { useJSON, imageBuffer } = await bard.#parseConfig(config);
|
let { useJSON, imageBuffer } = await bard.#parseConfig(config)
|
||||||
let response = await bard.#query(message, {
|
let response = await bard.#query(message, {
|
||||||
imageBuffer,
|
imageBuffer,
|
||||||
ids: this.ids,
|
ids: this.ids
|
||||||
});
|
})
|
||||||
this.ids = response.ids;
|
this.ids = response.ids
|
||||||
return useJSON ? response : response.content;
|
return useJSON ? response : response.content
|
||||||
}
|
|
||||||
|
|
||||||
export() {
|
|
||||||
return this.ids;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Chat();
|
export () {
|
||||||
|
return this.ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Chat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Bard;
|
export default Bard
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
|
|
||||||
// this file is deprecated
|
// this file is deprecated
|
||||||
import {Config} from './config.js'
|
import { Config } from './config.js'
|
||||||
import HttpsProxyAgent from 'https-proxy-agent'
|
import HttpsProxyAgent from 'https-proxy-agent'
|
||||||
|
|
||||||
const newFetch = (url, options = {}) => {
|
const newFetch = (url, options = {}) => {
|
||||||
|
|
|
||||||
932
utils/browser.js
932
utils/browser.js
|
|
@ -1,10 +1,5 @@
|
||||||
import lodash from 'lodash'
|
import lodash from 'lodash'
|
||||||
import { Config } from '../utils/config.js'
|
import { Config } from './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'
|
|
||||||
let puppeteer = {}
|
let puppeteer = {}
|
||||||
|
|
||||||
class Puppeteer {
|
class Puppeteer {
|
||||||
|
|
@ -48,19 +43,9 @@ class Puppeteer {
|
||||||
|
|
||||||
async initPupp () {
|
async initPupp () {
|
||||||
if (!lodash.isEmpty(puppeteer)) return puppeteer
|
if (!lodash.isEmpty(puppeteer)) return puppeteer
|
||||||
puppeteer = (await import('puppeteer-extra')).default
|
puppeteer = (await import('puppeteer')).default
|
||||||
const pluginStealth = StealthPlugin()
|
// const pluginStealth = StealthPlugin()
|
||||||
puppeteer.use(pluginStealth)
|
// 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
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
return puppeteer
|
return puppeteer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,25 +94,10 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
||||||
constructor (opts = {}) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
const {
|
const {
|
||||||
email,
|
debug = false
|
||||||
password,
|
|
||||||
markdown = true,
|
|
||||||
debug = false,
|
|
||||||
isGoogleLogin = false,
|
|
||||||
minimize = true,
|
|
||||||
captchaToken,
|
|
||||||
executablePath
|
|
||||||
} = opts
|
} = opts
|
||||||
|
|
||||||
this._email = email
|
|
||||||
this._password = password
|
|
||||||
|
|
||||||
this._markdown = !!markdown
|
|
||||||
this._debug = !!debug
|
this._debug = !!debug
|
||||||
this._isGoogleLogin = !!isGoogleLogin
|
|
||||||
this._minimize = !!minimize
|
|
||||||
this._captchaToken = captchaToken
|
|
||||||
this._executablePath = executablePath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBrowser () {
|
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 () {
|
async close () {
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
await this.browser.close()
|
await this.browser.close()
|
||||||
|
|
@ -533,510 +115,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
|
||||||
this._page = null
|
this._page = null
|
||||||
this.browser = null
|
this.browser = null
|
||||||
}
|
}
|
||||||
|
|
||||||
protected
|
|
||||||
|
|
||||||
async _getInputBox () {
|
|
||||||
// [data-id="root"]
|
|
||||||
return this._page?.$('textarea')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new ChatGPTPuppeteer()
|
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) {
|
export async function getChatHistoryGroup (e, num) {
|
||||||
// if (e.adapter === 'shamrock') {
|
// if (e.adapter === 'shamrock') {
|
||||||
// return await e.group.getChatHistory(0, num, false)
|
// return await e.group.getChatHistory(0, num, false)
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ const defaultConfig = {
|
||||||
claudeSystemPrompt: '', // claude api 设定
|
claudeSystemPrompt: '', // claude api 设定
|
||||||
translateSource: 'openai',
|
translateSource: 'openai',
|
||||||
enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误
|
enableMd: false, // 第三方md,非QQBot。需要适配器实现segment.markdown和segment.button方可使用,否则不建议开启,会造成各种错误
|
||||||
enableToolbox: false, // 默认关闭工具箱节省占用和加速启动
|
enableToolbox: true, // 默认关闭工具箱节省占用和加速启动
|
||||||
version: 'v2.8.1'
|
version: 'v2.8.1'
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
export function decrypt (jwtToken) {
|
export function decrypt (jwtToken) {
|
||||||
const [encodedHeader, encodedPayload, signature] = jwtToken.split('.')
|
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')
|
const decodedPayload = Buffer.from(encodedPayload, 'base64').toString('utf-8')
|
||||||
|
|
||||||
return decodedPayload
|
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) {
|
func = async function (opts) {
|
||||||
let { url, mode, e } = opts
|
let { url, mode, e } = opts
|
||||||
|
let browser
|
||||||
try {
|
try {
|
||||||
// let res = await fetch(url, {
|
// let res = await fetch(url, {
|
||||||
// headers: {
|
// headers: {
|
||||||
|
|
@ -34,7 +35,7 @@ export class WebsiteTool extends AbstractTool {
|
||||||
origin = true
|
origin = true
|
||||||
}
|
}
|
||||||
let ppt = new ChatGPTPuppeteer()
|
let ppt = new ChatGPTPuppeteer()
|
||||||
let browser = await ppt.getBrowser()
|
browser = await ppt.getBrowser()
|
||||||
let page = await browser.newPage()
|
let page = await browser.newPage()
|
||||||
await page.goto(url, {
|
await page.goto(url, {
|
||||||
waitUntil: 'networkidle2'
|
waitUntil: 'networkidle2'
|
||||||
|
|
@ -104,6 +105,12 @@ export class WebsiteTool extends AbstractTool {
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return `failed to visit the website, error: ${err.toString()}`
|
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'
|
import { Config } from '../config.js'
|
||||||
|
import { newFetch } from '../proxy.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成voxTTSMode下的wav音频
|
* 生成voxTTSMode下的wav音频
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,8 @@
|
||||||
import { Config } from '../config.js'
|
import { Config } from '../config.js'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
|
import nodejieba from '@node-rs/jieba'
|
||||||
|
|
||||||
let nodejieba
|
class Tokenizer {
|
||||||
try {
|
|
||||||
nodejieba = (await import('@node-rs/jieba')).default
|
|
||||||
nodejieba.load()
|
|
||||||
} catch (err) {
|
|
||||||
logger.info('未安装@node-rs/jieba,娱乐功能-词云统计不可用')
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Tokenizer {
|
|
||||||
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||||
if (!groupId) {
|
if (!groupId) {
|
||||||
throw new Error('no valid group id')
|
throw new Error('no valid group id')
|
||||||
|
|
@ -78,6 +71,10 @@ export class Tokenizer {
|
||||||
if (!nodejieba) {
|
if (!nodejieba) {
|
||||||
throw new Error('未安装node-rs/jieba,娱乐功能-词云统计不可用')
|
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]
|
// 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 chats = await this.getHistory(e, groupId, new Date(), duration, userId)
|
||||||
let durationStr = duration > 0 ? `${duration}小时` : '今日'
|
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) {
|
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
|
||||||
logger.mark('当前使用Shamrock适配器')
|
logger.mark('当前使用Shamrock适配器')
|
||||||
if (!groupId) {
|
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
|
// Step 5: Compare the given timestamp with the start and end of the specified date
|
||||||
return timestamp >= startOfSpecifiedDate && timestamp < endOfSpecifiedDate
|
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'
|
import { render } from '../common.js'
|
||||||
|
|
||||||
export async function makeWordcloud (e, groupId, duration = 0, userId) {
|
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) {
|
function getTokenizer (e) {
|
||||||
if (e.adapter === 'shamrock') {
|
if (e.adapter === 'shamrock') {
|
||||||
return new ShamrockTokenizer()
|
return Tokenizer.shamrock
|
||||||
} else {
|
} else {
|
||||||
return new Tokenizer()
|
return Tokenizer.default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue