fix: remove useless thing

This commit is contained in:
ikechan8370 2024-03-09 23:37:47 +08:00
parent 5c544a5ca7
commit bd7aac0517
53 changed files with 350 additions and 2639 deletions

View file

@ -1,5 +1,5 @@
import plugin from '../../../lib/plugins/plugin.js'
import {Config} from "../utils/config.js";
import { Config } from '../utils/config.js'
const PLUGIN_CHAT = 'ChatGpt 对话'
const PLUGIN_MANAGEMENT = 'ChatGPT-Plugin 管理'

View file

@ -5,7 +5,6 @@ import { Config, defaultOpenAIAPI } from '../utils/config.js'
import { v4 as uuid } from 'uuid'
import { ChatGPTAPI } from '../utils/openai/chatgpt-api.js'
import SydneyAIClient from '../utils/SydneyAIClient.js'
import { PoeClient } from '../utils/poe/index.js'
import AzureTTS from '../utils/tts/microsoft-azure.js'
import VoiceVoxTTS from '../utils/tts/voicevox.js'
import {
@ -31,7 +30,6 @@ import {
renderUrl
} from '../utils/common.js'
import { ChatGPTPuppeteer } from '../utils/browser.js'
import { KeyvFile } from 'keyv-file'
import { OfficialChatGPTClient } from '../utils/message.js'
import fetch from 'node-fetch'
@ -39,7 +37,7 @@ import { deleteConversation, getConversations, getLatestMessageIdByConversationI
import { convertSpeaker, speakers } from '../utils/tts.js'
import ChatGLMClient from '../utils/chatglm.js'
import { convertFaces } from '../utils/face.js'
import { originalValues, ConversationManager } from '../model/conversation.js'
import { ConversationManager, originalValues } from '../model/conversation.js'
import BingDrawClient from '../utils/BingDraw.js'
import XinghuoClient from '../utils/xinghuo/xinghuo.js'
import Bard from '../utils/bard.js'
@ -795,10 +793,6 @@ export class chatgpt extends plugin {
key = `CHATGPT:CONVERSATIONS_CHATGLM:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'browser': {
key = `CHATGPT:CONVERSATIONS_BROWSER:${(e.isGroup && Config.groupMerge) ? e.group_id.toString() : e.sender.user_id}`
break
}
case 'claude2': {
key = `CHATGPT:CLAUDE2_CONVERSATION:${e.sender.user_id}`
break
@ -877,7 +871,7 @@ export class chatgpt extends plugin {
// 字数超限直接返回
return false
}
if (use !== 'api3' && use !== 'poe') {
if (use !== 'api3') {
previousConversation.conversation = {
conversationId: chatMessage.conversationId
}
@ -1534,49 +1528,9 @@ export class chatgpt extends plugin {
user: e.sender.user_id,
cache: cacheOptions
})
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
return sendMessageResult
} else if (use === 'poe') {
const cookie = await redis.get('CHATGPT:POE_TOKEN')
if (!cookie) {
throw new Error('未绑定Poe Cookie请使用#chatgpt设置Poe token命令绑定cookie')
}
let client = new PoeClient({
quora_cookie: cookie,
proxy: Config.proxy
})
await client.setCredentials()
await client.getChatId()
let ai = 'a2' // todo
await client.sendMsg(ai, prompt)
const response = await client.getResponse(ai)
return {
text: response.data
}
return await this.chatGPTApi.sendMessage(prompt, conversation)
} else if (use === 'claude') {
// slack已经不可用移除
// let client = new SlackClaudeClient({
// slackUserToken: Config.slackUserToken,
// slackChannelId: Config.slackChannelId
// })
// let conversationId = await redis.get(`CHATGPT:SLACK_CONVERSATION:${e.sender.user_id}`)
// if (!conversationId) {
// // 如果是新对话
// if (Config.slackClaudeEnableGlobalPreset && (useCast?.slack || Config.slackClaudeGlobalPreset)) {
// // 先发送设定
// let prompt = (useCast?.slack || Config.slackClaudeGlobalPreset)
// let emotion = await AzureTTS.getEmotionPrompt(e)
// if (emotion) {
// prompt = prompt + '\n' + emotion
// }
// await client.sendMessage(prompt, e)
// logger.info('claudeFirst:', prompt)
// }
// }
// let text = await client.sendMessage(prompt, e)
// return {
// text
// }
const client = new ClaudeAPIClient({
key: Config.claudeApiKey,
model: Config.claudeApiModel || 'claude-3-sonnet-20240229',
@ -2408,30 +2362,6 @@ export class chatgpt extends plugin {
this.reply('总额度:$' + hardLimit + '\n已经使用额度$' + totalUsage / 100 + '\n当前剩余额度$' + left + '\n到期日期(UTC)' + expiresAt)
}
/**
* #chatgpt
* @param prompt 问题
* @param conversation 对话
*/
async chatgptBrowserBased (prompt, conversation) {
let option = { markdown: true }
if (Config['2captchaToken']) {
option.captchaToken = Config['2captchaToken']
}
// option.debug = true
option.email = Config.username
option.password = Config.password
this.chatGPTApi = new ChatGPTPuppeteer(option)
logger.info(`chatgpt prompt: ${prompt}`)
let sendMessageOption = {
timeoutMs: 120000
}
if (conversation) {
sendMessageOption = Object.assign(sendMessageOption, conversation)
}
return await this.chatGPTApi.sendMessage(prompt, sendMessageOption)
}
/**
* 其他模式
* @param e

View file

@ -4,7 +4,6 @@ import { generateHello } from '../utils/randomMessage.js'
import { generateVitsAudio } from '../utils/tts.js'
import fs from 'fs'
import { emojiRegex, googleRequestUrl } from '../utils/emoj/index.js'
import fetch from 'node-fetch'
import { getImageOcrText, getImg, makeForwardMsg, mkdirs, renderUrl } from '../utils/common.js'
import uploadRecord from '../utils/uploadRecord.js'
import { makeWordcloud } from '../utils/wordcloud/wordcloud.js'

View file

@ -99,13 +99,8 @@ export class ChatgptManagement extends plugin {
permission: 'master'
},
{
reg: '^#chatgpt切换(Poe|poe)$',
fnc: 'useClaudeBasedSolution',
permission: 'master'
},
{
reg: '^#chatgpt切换(Claude|claude|slack)$',
fnc: 'useSlackClaudeBasedSolution',
reg: '^#chatgpt切换(Claude|claude)$',
fnc: 'useClaudeAPIBasedSolution',
permission: 'master'
},
{
@ -923,23 +918,13 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
}
}
async useClaudeBasedSolution (e) {
let use = await redis.get('CHATGPT:USE')
if (use !== 'poe') {
await redis.set('CHATGPT:USE', 'poe')
await this.reply('已切换到基于Quora\'s POE的解决方案')
} else {
await this.reply('当前已经是POE模式了')
}
}
async useSlackClaudeBasedSolution () {
async useClaudeAPIBasedSolution () {
let use = await redis.get('CHATGPT:USE')
if (use !== 'claude') {
await redis.set('CHATGPT:USE', 'claude')
await this.reply('已切换到基于slack claude机器人的解决方案')
await this.reply('已切换到基于ClaudeAPI的解决方案')
} else {
await this.reply('当前已经是claude模式了')
await this.reply('当前已经是Claude模式了')
}
}
@ -949,7 +934,7 @@ azure语音Azure 语音是微软 Azure 平台提供的一项语音服务,
await redis.set('CHATGPT:USE', 'claude2')
await this.reply('已切换到基于claude.ai的解决方案')
} else {
await this.reply('当前已经是claude2模式了')
await this.reply('当前已经是claude.ai模式了')
}
}

View file

@ -1,10 +1,8 @@
import plugin from '../../../lib/plugins/plugin.js'
import fs from 'fs'
import _ from 'lodash'
import { Config } from '../utils/config.js'
import { getMasterQQ, limitString, makeForwardMsg, maskQQ, getUin } from '../utils/common.js'
import { deleteOnePrompt, getPromptByName, readPrompts, saveOnePrompt } from '../utils/prompts.js'
import AzureTTS from "../utils/tts/microsoft-azure.js";
import AzureTTS from '../utils/tts/microsoft-azure.js'
export class help extends plugin {
constructor (e) {
super({

View file

@ -3,21 +3,11 @@ import plugin from '../../../lib/plugins/plugin.js'
import { createRequire } from 'module'
import _ from 'lodash'
import { Restart } from '../../other/restart.js'
import fs from 'fs'
import {} from '../utils/common.js'
const _path = process.cwd()
const require = createRequire(import.meta.url)
const { exec, execSync } = require('child_process')
const checkAuth = async function (e) {
if (!e.isMaster) {
e.reply('只有主人才能命令ChatGPT哦~(*/ω\*)')
return false
}
return true
}
// 是否在更新中
let uping = false

View file

@ -1,7 +1,7 @@
import plugin from '../../../lib/plugins/plugin.js'
import { SunoClient } from '../client/SunoClient.js'
import { Config } from '../utils/config.js'
import { downloadFile, maskEmail } from '../utils/common.js'
import { maskEmail } from '../utils/common.js'
import common from '../../../lib/common/common.js'
import lodash from 'lodash'

View file

@ -89,13 +89,6 @@
"whitelist": [],
"blacklist": [],
"ttsRegex": "/匹配规则/匹配模式",
"slackUserToken": "",
"slackBotUserToken": "",
"slackSigningSecret": "",
"slackClaudeUserId": "",
"slackClaudeEnableGlobalPreset": true,
"slackClaudeGlobalPreset": "",
"slackClaudeSpecifiedChannel": "",
"cloudTranscode": "https://silk.201666.xyz",
"cloudRender": false,
"cloudMode": "url",

View file

@ -9,7 +9,6 @@
"@fastify/static": "^6.9.0",
"@fastify/websocket": "^8.2.0",
"@google/generative-ai": "^0.1.1",
"@slack/bolt": "^3.13.2",
"asn1.js": "^5.0.0",
"diff": "^5.1.0",
"emoji-strip": "^1.0.1",
@ -40,9 +39,6 @@
"node-silk": "^0.1.0",
"nodejs-pptx": "^1.2.4",
"pdfjs-dist": "^3.11.174",
"puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
"puppeteer-extra-plugin-stealth": "^2.11.2",
"sharp": "^0.32.3",
"xlsx": "^0.18.5"
},

View file

@ -1,23 +1,23 @@
import fetch, {
Headers,
Request,
Response,
// Headers,
// Request,
// Response,
FormData
} from 'node-fetch'
import crypto from 'crypto'
import WebSocket from 'ws'
import { Config, pureSydneyInstruction } from './config.js'
import { Config } from './config.js'
import { formatDate, getMasterQQ, isCN, getUserData, limitString } from './common.js'
import moment from 'moment'
import { getProxy } from './proxy.js'
import common from '../../../lib/common/common.js'
if (!globalThis.fetch) {
globalThis.fetch = fetch
globalThis.Headers = Headers
globalThis.Request = Request
globalThis.Response = Response
}
//
// if (!globalThis.fetch) {
// globalThis.fetch = fetch
// globalThis.Headers = Headers
// globalThis.Request = Request
// globalThis.Response = Response
// }
// workaround for ver 7.x and ver 5.x
let proxy = getProxy()

View file

@ -1,215 +1,215 @@
// https://github.com/EvanZhouDev/bard-ai
class Bard {
static JSON = "json";
static MD = "markdown";
static JSON = 'json'
static MD = 'markdown'
// ID derived from Cookie
SNlM0e;
SNlM0e
// HTTPS Headers
#headers;
#headers
// Resolution status of initialization call
#initPromise;
#initPromise
#bardURL = "https://bard.google.com";
#bardURL = 'https://bard.google.com'
// Wether or not to log events to console
#verbose = false;
#verbose = false
// Fetch function
#fetch = fetch;
#fetch = fetch
constructor(cookie, config) {
constructor (cookie, config) {
// Register some settings
if (config?.verbose == true) this.#verbose = true;
if (config?.fetch) this.#fetch = config.fetch;
if (config?.verbose == true) this.#verbose = true
if (config?.fetch) this.#fetch = config.fetch
// 可变更访问地址,利用反向代理绕过区域限制
if (config?.bardURL) this.#bardURL = config.bardURL;
if (config?.bardURL) this.#bardURL = config.bardURL
// If a Cookie is provided, initialize
if (cookie) {
this.#initPromise = this.#init(cookie);
this.#initPromise = this.#init(cookie)
} else {
throw new Error("Please provide a Cookie when initializing Bard.");
throw new Error('Please provide a Cookie when initializing Bard.')
}
this.cookie = cookie;
this.cookie = cookie
}
// You can also choose to initialize manually
async #init(cookie) {
this.#verbose && console.log("🚀 Starting intialization");
async #init (cookie) {
this.#verbose && console.log('🚀 Starting intialization')
// Assign headers
this.#headers = {
Host: this.#bardURL.match(/^https?:\/\/([^\/]+)\/?$/)[1],
"X-Same-Domain": "1",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
'X-Same-Domain': '1',
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
Origin: this.#bardURL,
Referer: this.#bardURL,
Cookie: (typeof cookie === "object") ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join("")) : ("__Secure-1PSID=" + cookie),
};
Cookie: (typeof cookie === 'object') ? (Object.entries(cookie).map(([key, val]) => `${key}=${val};`).join('')) : ('__Secure-1PSID=' + cookie)
}
let responseText;
let responseText
// Attempt to retrieve SNlM0e
try {
this.#verbose &&
console.log("🔒 Authenticating your Google account");
console.log('🔒 Authenticating your Google account')
responseText = await this.#fetch(this.#bardURL, {
method: "GET",
method: 'GET',
headers: this.#headers,
credentials: "include",
credentials: 'include'
})
.then((response) => response.text())
} catch (e) {
// Failure to get server
throw new Error(
"Could not fetch Google Bard. You may be disconnected from internet: " +
'Could not fetch Google Bard. You may be disconnected from internet: ' +
e
);
)
}
try {
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1];
const SNlM0e = responseText.match(/SNlM0e":"(.*?)"/)[1]
// Assign SNlM0e and return it
this.SNlM0e = SNlM0e;
this.#verbose && console.log("✅ Initialization finished\n");
return SNlM0e;
this.SNlM0e = SNlM0e
this.#verbose && console.log('✅ Initialization finished\n')
return SNlM0e
} catch {
throw new Error(
"Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit."
);
'Could not use your Cookie. Make sure that you copied correctly the Cookie with name __Secure-1PSID exactly. If you are sure your cookie is correct, you may also have reached your rate limit.'
)
}
}
async #uploadImage(name, buffer) {
this.#verbose && console.log("🖼️ Starting image processing");
let size = buffer.byteLength;
async #uploadImage (name, buffer) {
this.#verbose && console.log('🖼️ Starting image processing')
let size = buffer.byteLength
let formBody = [
`${encodeURIComponent("File name")}=${encodeURIComponent([name])}`,
];
`${encodeURIComponent('File name')}=${encodeURIComponent([name])}`
]
try {
this.#verbose &&
console.log("💻 Finding Google server destination");
console.log('💻 Finding Google server destination')
let response = await this.#fetch(
"https://content-push.googleapis.com/upload/",
'https://content-push.googleapis.com/upload/',
{
method: "POST",
method: 'POST',
headers: {
"X-Goog-Upload-Command": "start",
"X-Goog-Upload-Protocol": "resumable",
"X-Goog-Upload-Header-Content-Length": size,
"X-Tenant-Id": "bard-storage",
"Push-Id": "feeds/mcudyrk2a4khkz",
'X-Goog-Upload-Command': 'start',
'X-Goog-Upload-Protocol': 'resumable',
'X-Goog-Upload-Header-Content-Length': size,
'X-Tenant-Id': 'bard-storage',
'Push-Id': 'feeds/mcudyrk2a4khkz'
},
body: formBody,
credentials: "include",
credentials: 'include'
}
);
)
const uploadUrl = response.headers.get("X-Goog-Upload-URL");
this.#verbose && console.log("📤 Sending your image");
const uploadUrl = response.headers.get('X-Goog-Upload-URL')
this.#verbose && console.log('📤 Sending your image')
response = await this.#fetch(uploadUrl, {
method: "POST",
method: 'POST',
headers: {
"X-Goog-Upload-Command": "upload, finalize",
"X-Goog-Upload-Offset": 0,
"X-Tenant-Id": "bard-storage",
'X-Goog-Upload-Command': 'upload, finalize',
'X-Goog-Upload-Offset': 0,
'X-Tenant-Id': 'bard-storage'
},
body: buffer,
credentials: "include",
});
credentials: 'include'
})
const imageFileLocation = await response.text();
const imageFileLocation = await response.text()
this.#verbose && console.log("✅ Image finished working\n");
return imageFileLocation;
this.#verbose && console.log('✅ Image finished working\n')
return imageFileLocation
} catch (e) {
throw new Error(
"Could not fetch Google Bard. You may be disconnected from internet: " +
'Could not fetch Google Bard. You may be disconnected from internet: ' +
e
);
)
}
}
// Query Bard
async #query(message, config) {
async #query (message, config) {
let formatMarkdown = (text, images) => {
if (!images) return text;
if (!images) return text
for (let imageData of images) {
const formattedTag = `!${imageData.tag}(${imageData.url})`;
const formattedTag = `!${imageData.tag}(${imageData.url})`
text = text.replace(
new RegExp(`(?!\\!)\\[${imageData.tag.slice(1, -1)}\\]`),
formattedTag
);
)
}
return text;
return text
}
let { ids, imageBuffer } = config;
let { ids, imageBuffer } = config
// Wait until after init
await this.#initPromise;
await this.#initPromise
this.#verbose && console.log("🔎 Starting Bard Query");
this.#verbose && console.log('🔎 Starting Bard Query')
// If user has not run init
if (!this.SNlM0e) {
throw new Error(
"Please initialize Bard first. If you haven't passed in your Cookie into the class, run Bard.init(cookie)."
);
)
}
this.#verbose && console.log("🏗️ Building Request");
this.#verbose && console.log('🏗️ Building Request')
// HTTPS parameters
const params = {
bl: "boq_assistant-bard-web-server_20230711.08_p0",
_reqID: ids?._reqID ?? "0",
rt: "c",
};
bl: 'boq_assistant-bard-web-server_20230711.08_p0',
_reqID: ids?._reqID ?? '0',
rt: 'c'
}
// If IDs are provided, but doesn't have every one of the expected IDs, error
const messageStruct = [
[message],
null,
[null, null, null],
];
[null, null, null]
]
if (imageBuffer) {
let imageLocation = await this.#uploadImage(
`bard-ai_upload`,
'bard-ai_upload',
imageBuffer
);
)
messageStruct[0].push(0, null, [
[[imageLocation, 1], "bard-ai_upload"],
]);
[[imageLocation, 1], 'bard-ai_upload']
])
}
if (ids) {
const { conversationID, responseID, choiceID } = ids;
messageStruct[2] = [conversationID, responseID, choiceID];
const { conversationID, responseID, choiceID } = ids
messageStruct[2] = [conversationID, responseID, choiceID]
}
// HTTPs data
const data = {
"f.req": JSON.stringify([null, JSON.stringify(messageStruct)]),
at: this.SNlM0e,
};
'f.req': JSON.stringify([null, JSON.stringify(messageStruct)]),
at: this.SNlM0e
}
// URL that we are submitting to
const url = new URL(
"/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate",
'/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate',
this.#bardURL
);
)
// Append parameters to the URL
for (const key in params) {
url.searchParams.append(key, params[key]);
url.searchParams.append(key, params[key])
}
// Encode the data
@ -220,30 +220,30 @@ class Bard {
value
)}`
)
.join("&");
.join('&')
this.#verbose && console.log("💭 Sending message to Bard");
this.#verbose && console.log('💭 Sending message to Bard')
// Send the fetch request
const chatData = await this.#fetch(url.toString(), {
method: "POST",
method: 'POST',
headers: this.#headers,
body: formBody,
credentials: "include",
credentials: 'include'
})
.then((response) => {
return response.text();
return response.text()
})
.then((text) => {
return JSON.parse(text.split("\n")[3])[0][2];
return JSON.parse(text.split('\n')[3])[0][2]
})
.then((rawData) => JSON.parse(rawData));
.then((rawData) => JSON.parse(rawData))
this.#verbose && console.log("🧩 Parsing output");
this.#verbose && console.log('🧩 Parsing output')
// Get first Bard-recommended answer
const answer = chatData[4][0];
const answer = chatData[4][0]
// Text of that answer
const text = answer[1][0];
const text = answer[1][0]
// Get data about images in that answer
const images =
@ -255,44 +255,44 @@ class Bard {
source: x[1][0][0],
alt: x[0][4],
website: x[1][1],
favicon: x[1][3],
},
})) ?? [];
favicon: x[1][3]
}
})) ?? []
this.#verbose && console.log("✅ All done!\n");
this.#verbose && console.log('✅ All done!\n')
// Put everything together and return
return {
content: formatMarkdown(text, images),
images: images,
images,
ids: {
conversationID: chatData[1][0],
responseID: chatData[1][1],
choiceID: answer[0],
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000),
},
};
_reqID: String(parseInt(ids?._reqID ?? 0) + 100000)
}
}
}
async #parseConfig(config) {
async #parseConfig (config) {
let result = {
useJSON: false,
imageBuffer: undefined, // Returns as {extension, filename}
ids: undefined,
};
ids: undefined
}
// Verify that format is one of the two types
if (config?.format) {
switch (config.format) {
case Bard.JSON:
result.useJSON = true;
break;
result.useJSON = true
break
case Bard.MD:
result.useJSON = false;
break;
result.useJSON = false
break
default:
throw new Error(
"Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output."
);
'Format can obly be Bard.JSON for JSON output or Bard.MD for Markdown output.'
)
}
}
@ -301,73 +301,73 @@ class Bard {
if (
config.image instanceof ArrayBuffer
) {
result.imageBuffer = config.image;
result.imageBuffer = config.image
} else if (
typeof config.image === "string" &&
typeof config.image === 'string' &&
/\.(jpeg|jpg|png|webp)$/.test(config.image)
) {
let fs;
let fs
try {
fs = await import("fs")
fs = await import('fs')
} catch {
throw new Error(
"Loading from an image file path is not supported in a browser environment.",
);
'Loading from an image file path is not supported in a browser environment.'
)
}
result.imageBuffer = fs.readFileSync(
config.image,
).buffer;
config.image
).buffer
} else {
throw new Error(
"Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer."
);
'Provide your image as a file path to a .jpeg, .jpg, .png, or .webp, or a Buffer.'
)
}
}
// Verify that all values in IDs exist
if (config?.ids) {
if (config.ids.conversationID && config.ids.responseID && config.ids.choiceID && config.ids._reqID) {
result.ids = config.ids;
result.ids = config.ids
} else {
throw new Error(
"Please provide the IDs exported exactly as given."
);
'Please provide the IDs exported exactly as given.'
)
}
}
return result;
return result
}
// Ask Bard a question!
async ask(message, config) {
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config);
let response = await this.#query(message, { imageBuffer, ids });
return useJSON ? response : response.content;
async ask (message, config) {
let { useJSON, imageBuffer, ids } = await this.#parseConfig(config)
let response = await this.#query(message, { imageBuffer, ids })
return useJSON ? response : response.content
}
createChat(ids) {
let bard = this;
createChat (ids) {
let bard = this
class Chat {
ids = ids;
ids = ids
async ask(message, config) {
let { useJSON, imageBuffer } = await bard.#parseConfig(config);
async ask (message, config) {
let { useJSON, imageBuffer } = await bard.#parseConfig(config)
let response = await bard.#query(message, {
imageBuffer,
ids: this.ids,
});
this.ids = response.ids;
return useJSON ? response : response.content;
ids: this.ids
})
this.ids = response.ids
return useJSON ? response : response.content
}
export() {
return this.ids;
export () {
return this.ids
}
}
return new Chat();
return new Chat()
}
}
export default Bard;
export default Bard

View file

@ -1,7 +1,7 @@
import fetch from 'node-fetch'
// this file is deprecated
import {Config} from './config.js'
import { Config } from './config.js'
import HttpsProxyAgent from 'https-proxy-agent'
const newFetch = (url, options = {}) => {

View file

@ -1,10 +1,5 @@
import lodash from 'lodash'
import { Config } from '../utils/config.js'
import StealthPlugin from 'puppeteer-extra-plugin-stealth'
import { getOpenAIAuth } from './openai-auth.js'
import { v4 as uuidv4 } from 'uuid'
import common from '../../../lib/common/common.js'
const chatUrl = 'https://chat.openai.com/chat'
import { Config } from './config.js'
let puppeteer = {}
class Puppeteer {
@ -48,19 +43,9 @@ class Puppeteer {
async initPupp () {
if (!lodash.isEmpty(puppeteer)) return puppeteer
puppeteer = (await import('puppeteer-extra')).default
const pluginStealth = StealthPlugin()
puppeteer.use(pluginStealth)
if (Config['2captchaToken']) {
const pluginCaptcha = (await import('puppeteer-extra-plugin-recaptcha')).default
puppeteer.use(pluginCaptcha({
provider: {
id: '2captcha',
token: Config['2captchaToken'] // REPLACE THIS WITH YOUR OWN 2CAPTCHA API KEY ⚡
},
visualFeedback: true
}))
}
puppeteer = (await import('puppeteer')).default
// const pluginStealth = StealthPlugin()
// puppeteer.use(pluginStealth)
return puppeteer
}
@ -109,25 +94,10 @@ export class ChatGPTPuppeteer extends Puppeteer {
constructor (opts = {}) {
super()
const {
email,
password,
markdown = true,
debug = false,
isGoogleLogin = false,
minimize = true,
captchaToken,
executablePath
debug = false
} = opts
this._email = email
this._password = password
this._markdown = !!markdown
this._debug = !!debug
this._isGoogleLogin = !!isGoogleLogin
this._minimize = !!minimize
this._captchaToken = captchaToken
this._executablePath = executablePath
}
async getBrowser () {
@ -138,394 +108,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
}
}
async init () {
// if (this.inited) {
// return true
// }
logger.info('init chatgpt browser')
try {
// this.browser = await getBrowser({
// captchaToken: this._captchaToken,
// executablePath: this._executablePath
// })
this.browser = await this.getBrowser()
this._page =
(await this.browser.pages())[0] || (await this.browser.newPage())
await maximizePage(this._page)
this._page.on('request', this._onRequest.bind(this))
this._page.on('response', this._onResponse.bind(this))
// bypass cloudflare and login
let preCookies = await redis.get('CHATGPT:RAW_COOKIES')
if (preCookies) {
await this._page.setCookie(...JSON.parse(preCookies))
}
// const url = this._page.url().replace(/\/$/, '')
// bypass annoying popup modals
await this._page.evaluateOnNewDocument(() => {
window.localStorage.setItem('oai/apps/hasSeenOnboarding/chat', 'true')
const chatGPTUpdateDates = ['2022-12-15', '2022-12-19', '2023-01-09', '2023-01-30', '2023-02-10']
chatGPTUpdateDates.forEach(date => {
window.localStorage.setItem(
`oai/apps/hasSeenReleaseAnnouncement/${date}`,
'true'
)
})
})
await this._page.goto(chatUrl, {
waitUntil: 'networkidle2'
})
let timeout = 30000
try {
while (timeout > 0 && (await this._page.title()).toLowerCase().indexOf('moment') > -1) {
// if meet captcha
if (Config['2captchaToken']) {
await this._page.solveRecaptchas()
}
await common.sleep(300)
timeout = timeout - 300
}
} catch (e) {
// navigation后获取title会报错报错说明已经在navigation了正合我意。
}
if (timeout < 0) {
logger.error('wait for cloudflare navigation timeout. 可能遇见验证码')
throw new Error('wait for cloudflare navigation timeout. 可能遇见验证码')
}
try {
await this._page.waitForNavigation({ timeout: 3000 })
} catch (e) {}
if (!await this.getIsAuthenticated()) {
await redis.del('CHATGPT:RAW_COOKIES')
logger.info('需要登录,准备进行自动化登录')
await getOpenAIAuth({
email: this._email,
password: this._password,
browser: this.browser,
page: this._page,
isGoogleLogin: this._isGoogleLogin
})
logger.info('登录完成')
} else {
logger.info('无需登录')
}
} catch (err) {
if (this.browser) {
await this.browser.close()
}
this.browser = null
this._page = null
throw err
}
const url = this._page.url().replace(/\/$/, '')
if (url !== chatUrl) {
await this._page.goto(chatUrl, {
waitUntil: 'networkidle2'
})
}
// dismiss welcome modal (and other modals)
do {
const modalSelector = '[data-headlessui-state="open"]'
if (!(await this._page.$(modalSelector))) {
break
}
try {
await this._page.click(`${modalSelector} button:last-child`)
} catch (err) {
// "next" button not found in welcome modal
break
}
await common.sleep(300)
} while (true)
if (!await this.getIsAuthenticated()) {
return false
}
if (this._minimize) {
await minimizePage(this._page)
}
return true
}
_onRequest = (request) => {
const url = request.url()
if (!isRelevantRequest(url)) {
return
}
const method = request.method()
let body
if (method === 'POST') {
body = request.postData()
try {
body = JSON.parse(body)
} catch (_) {
}
// if (url.endsWith('/conversation') && typeof body === 'object') {
// const conversationBody: types.ConversationJSONBody = body
// const conversationId = conversationBody.conversation_id
// const parentMessageId = conversationBody.parent_message_id
// const messageId = conversationBody.messages?.[0]?.id
// const prompt = conversationBody.messages?.[0]?.content?.parts?.[0]
// // TODO: store this info for the current sendMessage request
// }
}
if (this._debug) {
console.log('\nrequest', {
url,
method,
headers: request.headers(),
body
})
}
}
_onResponse = async (response) => {
const request = response.request()
const url = response.url()
if (!isRelevantRequest(url)) {
return
}
const status = response.status()
let body
try {
body = await response.json()
} catch (_) {
}
if (this._debug) {
console.log('\nresponse', {
url,
ok: response.ok(),
status,
statusText: response.statusText(),
headers: response.headers(),
body,
request: {
method: request.method(),
headers: request.headers(),
body: request.postData()
}
})
}
if (url.endsWith('/conversation')) {
if (status === 403) {
await this.handle403Error()
}
} else if (url.endsWith('api/auth/session')) {
if (status === 403) {
await this.handle403Error()
} else {
const session = body
if (session?.accessToken) {
this._accessToken = session.accessToken
}
}
}
}
async handle403Error () {
console.log(`ChatGPT "${this._email}" session expired; refreshing...`)
try {
await maximizePage(this._page)
await this._page.reload({
waitUntil: 'networkidle2',
timeout: Config.chromeTimeoutMS // 2 minutes
})
if (this._minimize) {
await minimizePage(this._page)
}
} catch (err) {
console.error(
`ChatGPT "${this._email}" error refreshing session`,
err.toString()
)
}
}
async getIsAuthenticated () {
try {
const inputBox = await this._getInputBox()
return !!inputBox
} catch (err) {
// can happen when navigating during login
return false
}
}
async sendMessage (
message,
opts = {}
) {
const {
conversationId,
parentMessageId = uuidv4(),
messageId = uuidv4(),
action = 'next',
// TODO
timeoutMs,
// onProgress,
onConversationResponse
} = opts
const inputBox = await this._getInputBox()
if (!inputBox || !this._accessToken) {
console.log(`chatgpt re-authenticating ${this._email}`)
let isAuthenticated = false
try {
isAuthenticated = await this.init()
} catch (err) {
console.warn(
`chatgpt error re-authenticating ${this._email}`,
err.toString()
)
throw err
}
let timeout = 100000
if (isAuthenticated) {
while (!this._accessToken) {
// wait for async response hook result
await common.sleep(300)
timeout = timeout - 300
if (timeout < 0) {
const error = new Error('Not signed in')
error.statusCode = 401
throw error
}
}
} else if (!this._accessToken) {
const error = new Error('Not signed in')
error.statusCode = 401
throw error
}
}
const url = 'https://chat.openai.com/backend-api/conversation'
const body = {
action,
messages: [
{
id: messageId,
role: 'user',
content: {
content_type: 'text',
parts: [message]
}
}
],
model: Config.plus ? Config.useGPT4 ? 'gpt-4' : 'text-davinci-002-render-sha' : 'text-davinci-002-render-sha',
parent_message_id: parentMessageId
}
if (conversationId) {
body.conversation_id = conversationId
}
// console.log('>>> EVALUATE', url, this._accessToken, body)
const result = await this._page.evaluate(
browserPostEventStream,
url,
this._accessToken,
body,
timeoutMs
)
// console.log('<<< EVALUATE', result)
if (result.error) {
const error = new Error(result.error.message)
error.statusCode = result.error.statusCode
error.statusText = result.error.statusText
if (error.statusCode === 403) {
await this.handle403Error()
}
throw error
}
// TODO: support sending partial response events
if (onConversationResponse) {
onConversationResponse(result.conversationResponse)
}
return {
text: result.response,
conversationId: result.conversationResponse.conversation_id,
id: messageId,
parentMessageId
}
// const lastMessage = await this.getLastMessage()
// await inputBox.focus()
// const paragraphs = message.split('\n')
// for (let i = 0; i < paragraphs.length; i++) {
// await inputBox.type(paragraphs[i], { delay: 0 })
// if (i < paragraphs.length - 1) {
// await this._page.keyboard.down('Shift')
// await inputBox.press('Enter')
// await this._page.keyboard.up('Shift')
// } else {
// await inputBox.press('Enter')
// }
// }
// const responseP = new Promise<string>(async (resolve, reject) => {
// try {
// do {
// await common.sleep(1000)
// // TODO: this logic needs some work because we can have repeat messages...
// const newLastMessage = await this.getLastMessage()
// if (
// newLastMessage &&
// lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase()
// ) {
// return resolve(newLastMessage)
// }
// } while (true)
// } catch (err) {
// return reject(err)
// }
// })
// if (timeoutMs) {
// return pTimeout(responseP, {
// milliseconds: timeoutMs
// })
// } else {
// return responseP
// }
}
async resetThread () {
try {
await this._page.click('nav > a:nth-child(1)')
} catch (err) {
// ignore for now
}
}
async close () {
if (this.browser) {
await this.browser.close()
@ -533,510 +115,6 @@ export class ChatGPTPuppeteer extends Puppeteer {
this._page = null
this.browser = null
}
protected
async _getInputBox () {
// [data-id="root"]
return this._page?.$('textarea')
}
}
export default new ChatGPTPuppeteer()
export async function minimizePage (page) {
const session = await page.target().createCDPSession()
const goods = await session.send('Browser.getWindowForTarget')
const { windowId } = goods
await session.send('Browser.setWindowBounds', {
windowId,
bounds: { windowState: 'minimized' }
})
}
export async function maximizePage (page) {
const session = await page.target().createCDPSession()
const goods = await session.send('Browser.getWindowForTarget')
const { windowId } = goods
await session.send('Browser.setWindowBounds', {
windowId,
bounds: { windowState: 'normal' }
})
}
export function isRelevantRequest (url) {
let pathname
try {
const parsedUrl = new URL(url)
pathname = parsedUrl.pathname
url = parsedUrl.toString()
} catch (_) {
return false
}
if (!url.startsWith('https://chat.openai.com')) {
return false
}
if (
!pathname.startsWith('/backend-api/') &&
!pathname.startsWith('/api/auth/session')
) {
return false
}
if (pathname.endsWith('backend-api/moderations')) {
return false
}
return true
}
/**
* This function is injected into the ChatGPT webapp page using puppeteer. It
* has to be fully self-contained, so we copied a few third-party sources and
* included them in here.
*/
export async function browserPostEventStream (
url,
accessToken,
body,
timeoutMs
) {
// Workaround for https://github.com/esbuild-kit/tsx/issues/113
globalThis.__name = () => undefined
const BOM = [239, 187, 191]
let conversationResponse
let conversationId = body?.conversation_id
let messageId = body?.messages?.[0]?.id
let response = ''
try {
console.log('browserPostEventStream', url, accessToken, body)
let abortController = null
if (timeoutMs) {
abortController = new AbortController()
}
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(body),
signal: abortController?.signal,
headers: {
accept: 'text/event-stream',
'x-openai-assistant-app-id': '',
authorization: `Bearer ${accessToken}`,
'content-type': 'application/json'
}
})
console.log('browserPostEventStream response', res)
if (!res.ok) {
return {
error: {
message: `ChatGPTAPI error ${res.status || res.statusText}`,
statusCode: res.status,
statusText: res.statusText
},
response: null,
conversationId,
messageId
}
}
const responseP = new Promise(
async (resolve, reject) => {
function onMessage (data) {
if (data === '[DONE]') {
return resolve({
error: null,
response,
conversationId,
messageId,
conversationResponse
})
}
try {
const _checkJson = JSON.parse(data)
} catch (error) {
console.log('warning: parse error.')
return
}
try {
const convoResponseEvent =
JSON.parse(data)
conversationResponse = convoResponseEvent
if (convoResponseEvent.conversation_id) {
conversationId = convoResponseEvent.conversation_id
}
if (convoResponseEvent.message?.id) {
messageId = convoResponseEvent.message.id
}
const partialResponse =
convoResponseEvent.message?.content?.parts?.[0]
if (partialResponse) {
response = partialResponse
}
} catch (err) {
console.warn('fetchSSE onMessage unexpected error', err)
reject(err)
}
}
const parser = createParser((event) => {
if (event.type === 'event') {
onMessage(event.data)
}
})
for await (const chunk of streamAsyncIterable(res.body)) {
const str = new TextDecoder().decode(chunk)
parser.feed(str)
}
}
)
if (timeoutMs) {
if (abortController) {
// This will be called when a timeout occurs in order for us to forcibly
// ensure that the underlying HTTP request is aborted.
responseP.cancel = () => {
abortController.abort()
}
}
console.log({ pTimeout })
return await pTimeout(responseP, {
milliseconds: timeoutMs,
message: 'ChatGPT timed out waiting for response'
})
} else {
return await responseP
}
} catch (err) {
const errMessageL = err.toString().toLowerCase()
if (
response &&
(errMessageL === 'error: typeerror: terminated' ||
errMessageL === 'typeerror: terminated')
) {
// OpenAI sometimes forcefully terminates the socket from their end before
// the HTTP request has resolved cleanly. In my testing, these cases tend to
// happen when OpenAI has already send the last `response`, so we can ignore
// the `fetch` error in this case.
return {
error: null,
response,
conversationId,
messageId,
conversationResponse
}
}
return {
error: {
message: err.toString(),
statusCode: err.statusCode || err.status || err.response?.statusCode,
statusText: err.statusText || err.response?.statusText
},
response: null,
conversationId,
messageId,
conversationResponse
}
}
// async function pTimeout (promise, option) {
// return await pTimeout(promise, option)
// }
async function * streamAsyncIterable (stream) {
const reader = stream.getReader()
try {
while (true) {
const { done, value } = await reader.read()
if (done) {
return
}
yield value
}
} finally {
reader.releaseLock()
}
}
// @see https://github.com/rexxars/eventsource-parser
function createParser (onParse) {
// Processing state
let isFirstChunk
let buffer
let startingPosition
let startingFieldLength
// Event state
let eventId
let eventName
let data
reset()
return { feed, reset }
function reset () {
isFirstChunk = true
buffer = ''
startingPosition = 0
startingFieldLength = -1
eventId = undefined
eventName = undefined
data = ''
}
function feed (chunk) {
buffer = buffer ? buffer + chunk : chunk
// Strip any UTF8 byte order mark (BOM) at the start of the stream.
// Note that we do not strip any non - UTF8 BOM, as eventsource streams are
// always decoded as UTF8 as per the specification.
if (isFirstChunk && hasBom(buffer)) {
buffer = buffer.slice(BOM.length)
}
isFirstChunk = false
// Set up chunk-specific processing state
const length = buffer.length
let position = 0
let discardTrailingNewline = false
// Read the current buffer byte by byte
while (position < length) {
// EventSource allows for carriage return + line feed, which means we
// need to ignore a linefeed character if the previous character was a
// carriage return
// @todo refactor to reduce nesting, consider checking previous byte?
// @todo but consider multiple chunks etc
if (discardTrailingNewline) {
if (buffer[position] === '\n') {
++position
}
discardTrailingNewline = false
}
let lineLength = -1
let fieldLength = startingFieldLength
let character
for (
let index = startingPosition;
lineLength < 0 && index < length;
++index
) {
character = buffer[index]
if (character === ':' && fieldLength < 0) {
fieldLength = index - position
} else if (character === '\r') {
discardTrailingNewline = true
lineLength = index - position
} else if (character === '\n') {
lineLength = index - position
}
}
if (lineLength < 0) {
startingPosition = length - position
startingFieldLength = fieldLength
break
} else {
startingPosition = 0
startingFieldLength = -1
}
parseEventStreamLine(buffer, position, fieldLength, lineLength)
position += lineLength + 1
}
if (position === length) {
// If we consumed the entire buffer to read the event, reset the buffer
buffer = ''
} else if (position > 0) {
// If there are bytes left to process, set the buffer to the unprocessed
// portion of the buffer only
buffer = buffer.slice(position)
}
}
function parseEventStreamLine (
lineBuffer,
index,
fieldLength,
lineLength
) {
if (lineLength === 0) {
// We reached the last line of this event
if (data.length > 0) {
onParse({
type: 'event',
id: eventId,
event: eventName || undefined,
data: data.slice(0, -1) // remove trailing newline
})
data = ''
eventId = undefined
}
eventName = undefined
return
}
const noValue = fieldLength < 0
const field = lineBuffer.slice(
index,
index + (noValue ? lineLength : fieldLength)
)
let step = 0
if (noValue) {
step = lineLength
} else if (lineBuffer[index + fieldLength + 1] === ' ') {
step = fieldLength + 2
} else {
step = fieldLength + 1
}
const position = index + step
const valueLength = lineLength - step
const value = lineBuffer
.slice(position, position + valueLength)
.toString()
if (field === 'data') {
data += value ? `${value}\n` : '\n'
} else if (field === 'event') {
eventName = value
} else if (field === 'id' && !value.includes('\u0000')) {
eventId = value
} else if (field === 'retry') {
const retry = parseInt(value, 10)
if (!Number.isNaN(retry)) {
onParse({ type: 'reconnect-interval', value: retry })
}
}
}
}
function hasBom (buffer) {
return BOM.every(
(charCode, index) => buffer.charCodeAt(index) === charCode
)
}
// @see https://github.com/sindresorhus/p-timeout
function pTimeout (
promise,
options
) {
const {
milliseconds,
fallback,
message,
customTimers = { setTimeout, clearTimeout }
} = options
let timer
const cancelablePromise = new Promise((resolve, reject) => {
if (typeof milliseconds !== 'number' || Math.sign(milliseconds) !== 1) {
throw new TypeError(
`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``
)
}
if (milliseconds === Number.POSITIVE_INFINITY) {
resolve(promise)
return
}
if (options.signal) {
const { signal } = options
if (signal.aborted) {
reject(getAbortedReason(signal))
}
signal.addEventListener('abort', () => {
reject(getAbortedReason(signal))
})
}
timer = customTimers.setTimeout.call(
undefined,
() => {
if (fallback) {
try {
resolve(fallback())
} catch (error) {
reject(error)
}
return
}
const errorMessage =
typeof message === 'string'
? message
: `Promise timed out after ${milliseconds} milliseconds`
const timeoutError =
message instanceof Error ? message : new Error(errorMessage)
if (typeof promise.cancel === 'function') {
promise.cancel()
}
reject(timeoutError)
},
milliseconds
)
;(async () => {
try {
resolve(await promise)
} catch (error) {
reject(error)
} finally {
customTimers.clearTimeout.call(undefined, timer)
}
})()
})
cancelablePromise.clear = () => {
customTimers.clearTimeout.call(undefined, timer)
timer = undefined
}
return cancelablePromise
}
/**
TODO: Remove below function and just 'reject(signal.reason)' when targeting Node 18.
*/
function getAbortedReason (signal) {
const reason =
signal.reason === undefined
? getDOMException('This operation was aborted.')
: signal.reason
return reason instanceof Error ? reason : getDOMException(reason)
}
/**
TODO: Remove AbortError and just throw DOMException when targeting Node 18.
*/
function getDOMException (errorMessage) {
return globalThis.DOMException === undefined
? new Error(errorMessage)
: new DOMException(errorMessage)
}
}

View file

@ -1,4 +1,3 @@
export async function getChatHistoryGroup (e, num) {
// if (e.adapter === 'shamrock') {
// return await e.group.getChatHistory(0, num, false)

View file

@ -187,7 +187,7 @@ const defaultConfig = {
claudeSystemPrompt: '', // claude api 设定
translateSource: 'openai',
enableMd: false, // 第三方md非QQBot。需要适配器实现segment.markdown和segment.button方可使用否则不建议开启会造成各种错误
enableToolbox: false, // 默认关闭工具箱节省占用和加速启动
enableToolbox: true, // 默认关闭工具箱节省占用和加速启动
version: 'v2.8.1'
}
const _path = process.cwd()

View file

@ -1,7 +1,7 @@
export function decrypt (jwtToken) {
const [encodedHeader, encodedPayload, signature] = jwtToken.split('.')
const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf-8')
// const decodedHeader = Buffer.from(encodedHeader, 'base64').toString('utf-8')
const decodedPayload = Buffer.from(encodedPayload, 'base64').toString('utf-8')
return decodedPayload

View file

@ -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
}
}

View file

@ -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 }

View file

@ -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
}
}
}

View file

@ -1,17 +0,0 @@
mutation AddMessageBreakMutation($chatId: BigInt!) {
messageBreakCreate(chatId: $chatId) {
message {
id
__typename
messageId
text
linkifiedText
authorNickname
state
vote
voteReason
creationTime
suggestedReplies
}
}
}

View file

@ -1,7 +0,0 @@
mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) {
autoSubscribe(subscriptions: $subscriptions) {
viewer {
id
}
}
}

View file

@ -1,8 +0,0 @@
fragment BioFragment on Viewer {
id
poeUser {
id
uid
bio
}
}

View file

@ -1,5 +0,0 @@
subscription ChatAddedSubscription {
chatAdded {
...ChatFragment
}
}

View file

@ -1,6 +0,0 @@
fragment ChatFragment on Chat {
id
chatId
defaultBotNickname
shouldShowDisclaimer
}

View file

@ -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
}
}
}
}
}

View file

@ -1,8 +0,0 @@
query ChatViewQuery($bot: String!) {
chatOfBot(bot: $bot) {
id
chatId
defaultBotNickname
shouldShowDisclaimer
}
}

View file

@ -1,7 +0,0 @@
mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) {
messagesDelete(messageIds: $messageIds) {
viewer {
id
}
}
}

View file

@ -1,8 +0,0 @@
fragment HandleFragment on Viewer {
id
poeUser {
id
uid
handle
}
}

View file

@ -1,13 +0,0 @@
mutation LoginWithVerificationCodeMutation(
$verificationCode: String!
$emailAddress: String
$phoneNumber: String
) {
loginWithVerificationCode(
verificationCode: $verificationCode
emailAddress: $emailAddress
phoneNumber: $phoneNumber
) {
status
}
}

View file

@ -1,5 +0,0 @@
subscription MessageAddedSubscription($chatId: BigInt!) {
messageAdded(chatId: $chatId) {
...MessageFragment
}
}

View file

@ -1,6 +0,0 @@
subscription MessageDeletedSubscription($chatId: BigInt!) {
messageDeleted(chatId: $chatId) {
id
messageId
}
}

View file

@ -1,13 +0,0 @@
fragment MessageFragment on Message {
id
__typename
messageId
text
linkifiedText
authorNickname
state
vote
voteReason
creationTime
suggestedReplies
}

View file

@ -1,7 +0,0 @@
mutation MessageRemoveVoteMutation($messageId: BigInt!) {
messageRemoveVote(messageId: $messageId) {
message {
...MessageFragment
}
}
}

View file

@ -1,7 +0,0 @@
mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) {
messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) {
message {
...MessageFragment
}
}
}

View file

@ -1,12 +0,0 @@
mutation SendVerificationCodeForLoginMutation(
$emailAddress: String
$phoneNumber: String
) {
sendVerificationCode(
verificationReason: login
emailAddress: $emailAddress
phoneNumber: $phoneNumber
) {
status
}
}

View file

@ -1,9 +0,0 @@
mutation ShareMessagesMutation(
$chatId: BigInt!
$messageIds: [BigInt!]!
$comment: String
) {
messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) {
shareCode
}
}

View file

@ -1,13 +0,0 @@
mutation SignupWithVerificationCodeMutation(
$verificationCode: String!
$emailAddress: String
$phoneNumber: String
) {
signupWithVerificationCode(
verificationCode: $verificationCode
emailAddress: $emailAddress
phoneNumber: $phoneNumber
) {
status
}
}

View file

@ -1,7 +0,0 @@
mutation StaleChatUpdateMutation($chatId: BigInt!) {
staleChatUpdate(chatId: $chatId) {
message {
...MessageFragment
}
}
}

View file

@ -1,3 +0,0 @@
query SummarizePlainPostQuery($comment: String!) {
summarizePlainPost(comment: $comment)
}

View file

@ -1,3 +0,0 @@
query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) {
summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId)
}

View file

@ -1,3 +0,0 @@
query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) {
summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds)
}

View file

@ -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
}

View file

@ -1,21 +0,0 @@
query ViewerInfoQuery {
viewer {
id
uid
...ViewerStateFragment
...BioFragment
...HandleFragment
hasCompletedMultiplayerNux
poeUser {
id
...UserSnippetFragment
}
messageLimit{
canSend
numMessagesRemaining
resetTime
shouldShowReminder
}
}
}

View file

@ -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
}
}

View file

@ -1,5 +0,0 @@
subscription ViewerStateUpdatedSubscription {
viewerStateUpdated {
...ViewerStateFragment
}
}

View file

@ -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
}
}
}

View file

@ -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)
})
}

View file

@ -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)
})
}

View file

@ -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
}
}
}

View file

@ -21,6 +21,7 @@ export class WebsiteTool extends AbstractTool {
func = async function (opts) {
let { url, mode, e } = opts
let browser
try {
// let res = await fetch(url, {
// headers: {
@ -34,7 +35,7 @@ export class WebsiteTool extends AbstractTool {
origin = true
}
let ppt = new ChatGPTPuppeteer()
let browser = await ppt.getBrowser()
browser = await ppt.getBrowser()
let page = await browser.newPage()
await page.goto(url, {
waitUntil: 'networkidle2'
@ -104,6 +105,12 @@ export class WebsiteTool extends AbstractTool {
}
} catch (err) {
return `failed to visit the website, error: ${err.toString()}`
} finally {
if (browser) {
try {
await browser.close()
} catch (err) {}
}
}
}

View file

@ -1,28 +1,5 @@
import { Config } from '../config.js'
let proxy
if (Config.proxy) {
try {
proxy = (await import('https-proxy-agent')).default
} catch (e) {
console.warn('未安装https-proxy-agent请在插件目录下执行pnpm add https-proxy-agent')
}
}
const newFetch = (url, options = {}) => {
const defaultOptions = Config.proxy
? {
agent: proxy(Config.proxy)
}
: {}
const mergedOptions = {
...defaultOptions,
...options
}
return fetch(url, mergedOptions)
}
import { newFetch } from '../proxy.js'
/**
* 生成voxTTSMode下的wav音频

View file

@ -1,15 +1,8 @@
import { Config } from '../config.js'
import fs from 'fs'
import nodejieba from '@node-rs/jieba'
let nodejieba
try {
nodejieba = (await import('@node-rs/jieba')).default
nodejieba.load()
} catch (err) {
logger.info('未安装@node-rs/jieba娱乐功能-词云统计不可用')
}
export class Tokenizer {
class Tokenizer {
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
if (!groupId) {
throw new Error('no valid group id')
@ -78,6 +71,10 @@ export class Tokenizer {
if (!nodejieba) {
throw new Error('未安装node-rs/jieba娱乐功能-词云统计不可用')
}
if (!this.loaded) {
nodejieba.load()
this.loaded = true
}
// duration represents the number of hours to go back, should in range [0, 24]
let chats = await this.getHistory(e, groupId, new Date(), duration, userId)
let durationStr = duration > 0 ? `${duration}小时` : '今日'
@ -139,7 +136,7 @@ export class Tokenizer {
}
}
export class ShamrockTokenizer extends Tokenizer {
class ShamrockTokenizer extends Tokenizer {
async getHistory (e, groupId, date = new Date(), duration = 0, userId) {
logger.mark('当前使用Shamrock适配器')
if (!groupId) {
@ -227,3 +224,8 @@ function isTimestampInDateRange (timestamp, startOfSpecifiedDate, endOfSpecified
// Step 5: Compare the given timestamp with the start and end of the specified date
return timestamp >= startOfSpecifiedDate && timestamp < endOfSpecifiedDate
}
export default {
default: new Tokenizer(),
shamrock: new ShamrockTokenizer()
}

View file

@ -1,4 +1,4 @@
import { ShamrockTokenizer, Tokenizer } from './tokenizer.js'
import Tokenizer from './tokenizer.js'
import { render } from '../common.js'
export async function makeWordcloud (e, groupId, duration = 0, userId) {
@ -12,8 +12,8 @@ export async function makeWordcloud (e, groupId, duration = 0, userId) {
function getTokenizer (e) {
if (e.adapter === 'shamrock') {
return new ShamrockTokenizer()
return Tokenizer.shamrock
} else {
return new Tokenizer()
return Tokenizer.default
}
}