mirror of
https://github.com/ikechan8370/chatgpt-plugin.git
synced 2025-12-16 21:37:11 +00:00
feat: add support for Claude and Poe(WIP)
This commit is contained in:
parent
a3a16bc5ff
commit
7ef6051da8
38 changed files with 882 additions and 19 deletions
33
apps/chat.js
33
apps/chat.js
|
|
@ -6,6 +6,7 @@ import delay from 'delay'
|
||||||
import { ChatGPTAPI } from 'chatgpt'
|
import { ChatGPTAPI } from 'chatgpt'
|
||||||
import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
|
import { BingAIClient } from '@waylaidwanderer/chatgpt-api'
|
||||||
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
import SydneyAIClient from '../utils/SydneyAIClient.js'
|
||||||
|
import { PoeClient } from '../utils/poe/index.js'
|
||||||
import {
|
import {
|
||||||
render, renderUrl,
|
render, renderUrl,
|
||||||
getMessageById,
|
getMessageById,
|
||||||
|
|
@ -25,6 +26,7 @@ import { convertSpeaker, generateAudio, 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 uploadRecord from '../utils/uploadRecord.js'
|
import uploadRecord from '../utils/uploadRecord.js'
|
||||||
|
import {SlackClaudeClient} from "../utils/slack/slackClient.js";
|
||||||
try {
|
try {
|
||||||
await import('keyv')
|
await import('keyv')
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -708,7 +710,7 @@ export class chatgpt extends plugin {
|
||||||
num: 0
|
num: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (use !== 'poe' && use === 'claude') {
|
||||||
switch (use) {
|
switch (use) {
|
||||||
case 'api': {
|
case 'api': {
|
||||||
key = `CHATGPT:CONVERSATIONS:${e.sender.user_id}`
|
key = `CHATGPT:CONVERSATIONS:${e.sender.user_id}`
|
||||||
|
|
@ -758,7 +760,7 @@ export class chatgpt extends plugin {
|
||||||
// 字数超限直接返回
|
// 字数超限直接返回
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (use !== 'api3') {
|
if (use !== 'api3' && use !== 'poe' && use !== 'claude') {
|
||||||
previousConversation.conversation = {
|
previousConversation.conversation = {
|
||||||
conversationId: chatMessage.conversationId
|
conversationId: chatMessage.conversationId
|
||||||
}
|
}
|
||||||
|
|
@ -1331,6 +1333,33 @@ export class chatgpt extends plugin {
|
||||||
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
|
let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation)
|
||||||
return sendMessageResult
|
return sendMessageResult
|
||||||
}
|
}
|
||||||
|
case '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
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'claude': {
|
||||||
|
let client = new SlackClaudeClient({
|
||||||
|
slackUserToken: Config.slackUserToken,
|
||||||
|
slackChannelId: Config.slackChannelId
|
||||||
|
})
|
||||||
|
let text = await client.sendMessage(prompt)
|
||||||
|
return {
|
||||||
|
text
|
||||||
|
}
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
let completionParams = {}
|
let completionParams = {}
|
||||||
if (Config.model) {
|
if (Config.model) {
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,8 @@ let helpData = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'switch',
|
icon: 'switch',
|
||||||
title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM',
|
title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe',
|
||||||
desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM'
|
desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM/Slack Claude/Poe'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'confirm',
|
icon: 'confirm',
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'setAccessToken',
|
fnc: 'setAccessToken',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '#chatgpt(设置|绑定)(Poe|POE)(token|Token)',
|
||||||
|
fnc: 'setPoeCookie',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '#chatgpt(设置|绑定|添加)(必应|Bing |bing )(token|Token)',
|
reg: '#chatgpt(设置|绑定|添加)(必应|Bing |bing )(token|Token)',
|
||||||
fnc: 'setBingAccessToken',
|
fnc: 'setBingAccessToken',
|
||||||
|
|
@ -77,6 +82,16 @@ export class ChatgptManagement extends plugin {
|
||||||
fnc: 'useBingSolution',
|
fnc: 'useBingSolution',
|
||||||
permission: 'master'
|
permission: 'master'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt切换(Poe|poe)$',
|
||||||
|
fnc: 'useClaudeBasedSolution',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
reg: '^#chatgpt切换(Claude|claude|slack)$',
|
||||||
|
fnc: 'useSlackClaudeBasedSolution',
|
||||||
|
permission: 'master'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
reg: '^#chatgpt(必应|Bing)切换',
|
reg: '^#chatgpt(必应|Bing)切换',
|
||||||
fnc: 'changeBingTone',
|
fnc: 'changeBingTone',
|
||||||
|
|
@ -404,6 +419,25 @@ export class ChatgptManagement extends plugin {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setPoeCookie () {
|
||||||
|
this.setContext('savePoeToken')
|
||||||
|
await this.reply('请发送Poe Cookie', true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async savePoeToken (e) {
|
||||||
|
if (!this.e.msg) return
|
||||||
|
let token = this.e.msg
|
||||||
|
if (!token.startsWith('p-b=')) {
|
||||||
|
await this.reply('Poe cookie格式错误', true)
|
||||||
|
this.finish('savePoeToken')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await redis.set('CHATGPT:POE_TOKEN', token)
|
||||||
|
await this.reply('Poe cookie设置成功', true)
|
||||||
|
this.finish('savePoeToken')
|
||||||
|
}
|
||||||
|
|
||||||
async setBingAccessToken (e) {
|
async setBingAccessToken (e) {
|
||||||
this.setContext('saveBingToken')
|
this.setContext('saveBingToken')
|
||||||
await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true)
|
await this.reply('请发送Bing Cookie Token.("_U" cookie from bing.com)', true)
|
||||||
|
|
@ -587,22 +621,32 @@ export class ChatgptManagement extends plugin {
|
||||||
let use = await redis.get('CHATGPT:USE')
|
let use = await redis.get('CHATGPT:USE')
|
||||||
if (use !== 'bing') {
|
if (use !== 'bing') {
|
||||||
await redis.set('CHATGPT:USE', 'bing')
|
await redis.set('CHATGPT:USE', 'bing')
|
||||||
// 结束所有人的对话
|
|
||||||
const keys = await redis.keys('CHATGPT:CONVERSATIONS:*')
|
|
||||||
if (keys.length) {
|
|
||||||
const response = await redis.del(keys)
|
|
||||||
if (Config.debug) {
|
|
||||||
console.log('Deleted keys:', response)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No keys matched the pattern')
|
|
||||||
}
|
|
||||||
await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
|
await this.reply('已切换到基于微软新必应的解决方案,如果已经对话过务必执行`#结束对话`避免引起404错误')
|
||||||
} else {
|
} else {
|
||||||
await this.reply('当前已经是必应Bing模式了')
|
await this.reply('当前已经是必应Bing模式了')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 () {
|
||||||
|
let use = await redis.get('CHATGPT:USE')
|
||||||
|
if (use !== 'claude') {
|
||||||
|
await redis.set('CHATGPT:USE', 'claude')
|
||||||
|
await this.reply('已切换到基于slack claude机器人的解决方案')
|
||||||
|
} else {
|
||||||
|
await this.reply('当前已经是claude模式了')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async changeBingTone (e) {
|
async changeBingTone (e) {
|
||||||
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '')
|
||||||
if (!tongStyle) {
|
if (!tongStyle) {
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,34 @@ export function supportGuoba () {
|
||||||
bottomHelpMessage: '为空使用默认puppeteer的chromium,也可以传递自己本机安装的Chrome可执行文件地址,提高通过率。windows可以是‘C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe’,linux通过which查找路径',
|
bottomHelpMessage: '为空使用默认puppeteer的chromium,也可以传递自己本机安装的Chrome可执行文件地址,提高通过率。windows可以是‘C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe’,linux通过which查找路径',
|
||||||
component: 'Input'
|
component: 'Input'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: '以下为Slack Claude方式的配置',
|
||||||
|
component: 'Divider'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'slackUserToken',
|
||||||
|
label: 'Slack用户Token',
|
||||||
|
bottomHelpMessage: 'slackUserToken,在OAuth&Permissions页面获取。需要具有channels:history, chat:write, groups:history, im:history, mpim:history 这几个scope',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'slackBotUserToken',
|
||||||
|
label: 'Slack Bot Token',
|
||||||
|
bottomHelpMessage: 'slackBotUserToken,在OAuth&Permissions页面获取。需要channels:history,groups:history,im:history 这几个scope',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'slackChannelId',
|
||||||
|
label: 'Slack私聊频道号',
|
||||||
|
bottomHelpMessage: '在Slack中与Claude机器人私聊的频道号。如果页面URL为https://app.slack.com/client/TXXXXXXXX/DXXXXXXXXX/,则频道号就是DXXXXXXXXX',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'slackSigningSecret',
|
||||||
|
label: 'Slack签名密钥',
|
||||||
|
bottomHelpMessage: 'Signing Secret。在Basic Information页面获取',
|
||||||
|
component: 'Input'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: '以下为ChatGLM方式的配置',
|
label: '以下为ChatGLM方式的配置',
|
||||||
component: 'Divider'
|
component: 'Divider'
|
||||||
|
|
@ -584,7 +612,7 @@ export function supportGuoba () {
|
||||||
label: '允许群获取后台地址',
|
label: '允许群获取后台地址',
|
||||||
bottomHelpMessage: '是否允许群获取后台地址,关闭后将只能私聊获取',
|
bottomHelpMessage: '是否允许群获取后台地址,关闭后将只能私聊获取',
|
||||||
component: 'Switch'
|
component: 'Switch'
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
// 获取配置数据方法(用于前端填充显示数据)
|
// 获取配置数据方法(用于前端填充显示数据)
|
||||||
getConfigData () {
|
getConfigData () {
|
||||||
|
|
|
||||||
1
index.js
1
index.js
|
|
@ -30,7 +30,6 @@ for (let i in files) {
|
||||||
|
|
||||||
// 启动服务器
|
// 启动服务器
|
||||||
await createServer()
|
await createServer()
|
||||||
|
|
||||||
logger.info('**************************************')
|
logger.info('**************************************')
|
||||||
logger.info('chatgpt-plugin加载成功')
|
logger.info('chatgpt-plugin加载成功')
|
||||||
logger.info(`当前版本${Config.version}`)
|
logger.info(`当前版本${Config.version}`)
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "ikechan8370",
|
"author": "ikechan8370",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fastify/cookie": "^8.3.0",
|
||||||
"@fastify/cors": "^8.2.0",
|
"@fastify/cors": "^8.2.0",
|
||||||
"@fastify/static": "^6.9.0",
|
"@fastify/static": "^6.9.0",
|
||||||
"@fastify/cookie": "^8.3.0",
|
"@slack/bolt": "^3.13.0",
|
||||||
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
"@waylaidwanderer/chatgpt-api": "^1.33.2",
|
||||||
"chatgpt": "^5.1.1",
|
"chatgpt": "^5.1.1",
|
||||||
"delay": "^5.0.0",
|
"delay": "^5.0.0",
|
||||||
|
"diff": "^5.1.0",
|
||||||
"eventsource": "^2.0.2",
|
"eventsource": "^2.0.2",
|
||||||
"eventsource-parser": "^1.0.0",
|
"eventsource-parser": "^1.0.0",
|
||||||
"fastify": "^4.13.0",
|
"fastify": "^4.13.0",
|
||||||
|
|
@ -24,8 +26,8 @@
|
||||||
"ws": "^8.13.0"
|
"ws": "^8.13.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"node-silk": "^0.1.0",
|
|
||||||
"jimp": "^0.22.7",
|
"jimp": "^0.22.7",
|
||||||
|
"node-silk": "^0.1.0",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,11 @@ const defaultConfig = {
|
||||||
groupWhitelist: [],
|
groupWhitelist: [],
|
||||||
groupBlacklist: [],
|
groupBlacklist: [],
|
||||||
ttsRegex: '/匹配规则/匹配模式',
|
ttsRegex: '/匹配规则/匹配模式',
|
||||||
version: 'v2.5.2'
|
slackUserToken: '',
|
||||||
|
slackBotUserToken: '',
|
||||||
|
slackChannelId: '',
|
||||||
|
slackSigningSecret: '',
|
||||||
|
version: 'v2.5.3'
|
||||||
}
|
}
|
||||||
const _path = process.cwd()
|
const _path = process.cwd()
|
||||||
let config = {}
|
let config = {}
|
||||||
|
|
|
||||||
40
utils/poe/credential.js
Normal file
40
utils/poe/credential.js
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import { readFileSync, writeFile } from 'fs'
|
||||||
|
|
||||||
|
const scrape = async (pbCookie) => {
|
||||||
|
const _setting = await fetch(
|
||||||
|
'https://poe.com/api/settings',
|
||||||
|
{ headers: { cookie: `${pbCookie}` } }
|
||||||
|
)
|
||||||
|
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) => {
|
||||||
|
const _setting = await fetch(
|
||||||
|
`https://poe.com/api/settings?channel=${channelName}`,
|
||||||
|
{ headers: { cookie: `${pbCookie}` } }
|
||||||
|
)
|
||||||
|
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 }
|
||||||
52
utils/poe/graphql/AddHumanMessageMutation.graphql
Normal file
52
utils/poe/graphql/AddHumanMessageMutation.graphql
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
utils/poe/graphql/AddMessageBreakMutation.graphql
Normal file
17
utils/poe/graphql/AddMessageBreakMutation.graphql
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
mutation AddMessageBreakMutation($chatId: BigInt!) {
|
||||||
|
messageBreakCreate(chatId: $chatId) {
|
||||||
|
message {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
messageId
|
||||||
|
text
|
||||||
|
linkifiedText
|
||||||
|
authorNickname
|
||||||
|
state
|
||||||
|
vote
|
||||||
|
voteReason
|
||||||
|
creationTime
|
||||||
|
suggestedReplies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
utils/poe/graphql/AutoSubscriptionMutation.graphql
Normal file
7
utils/poe/graphql/AutoSubscriptionMutation.graphql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) {
|
||||||
|
autoSubscribe(subscriptions: $subscriptions) {
|
||||||
|
viewer {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
utils/poe/graphql/BioFragment.graphql
Normal file
8
utils/poe/graphql/BioFragment.graphql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fragment BioFragment on Viewer {
|
||||||
|
id
|
||||||
|
poeUser {
|
||||||
|
id
|
||||||
|
uid
|
||||||
|
bio
|
||||||
|
}
|
||||||
|
}
|
||||||
5
utils/poe/graphql/ChatAddedSubscription.graphql
Normal file
5
utils/poe/graphql/ChatAddedSubscription.graphql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
subscription ChatAddedSubscription {
|
||||||
|
chatAdded {
|
||||||
|
...ChatFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
6
utils/poe/graphql/ChatFragment.graphql
Normal file
6
utils/poe/graphql/ChatFragment.graphql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
fragment ChatFragment on Chat {
|
||||||
|
id
|
||||||
|
chatId
|
||||||
|
defaultBotNickname
|
||||||
|
shouldShowDisclaimer
|
||||||
|
}
|
||||||
26
utils/poe/graphql/ChatPaginationQuery.graphql
Normal file
26
utils/poe/graphql/ChatPaginationQuery.graphql
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
utils/poe/graphql/ChatViewQuery.graphql
Normal file
8
utils/poe/graphql/ChatViewQuery.graphql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
query ChatViewQuery($bot: String!) {
|
||||||
|
chatOfBot(bot: $bot) {
|
||||||
|
id
|
||||||
|
chatId
|
||||||
|
defaultBotNickname
|
||||||
|
shouldShowDisclaimer
|
||||||
|
}
|
||||||
|
}
|
||||||
7
utils/poe/graphql/DeleteHumanMessagesMutation.graphql
Normal file
7
utils/poe/graphql/DeleteHumanMessagesMutation.graphql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) {
|
||||||
|
messagesDelete(messageIds: $messageIds) {
|
||||||
|
viewer {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
utils/poe/graphql/HandleFragment.graphql
Normal file
8
utils/poe/graphql/HandleFragment.graphql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
fragment HandleFragment on Viewer {
|
||||||
|
id
|
||||||
|
poeUser {
|
||||||
|
id
|
||||||
|
uid
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
}
|
||||||
13
utils/poe/graphql/LoginWithVerificationCodeMutation.graphql
Normal file
13
utils/poe/graphql/LoginWithVerificationCodeMutation.graphql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
mutation LoginWithVerificationCodeMutation(
|
||||||
|
$verificationCode: String!
|
||||||
|
$emailAddress: String
|
||||||
|
$phoneNumber: String
|
||||||
|
) {
|
||||||
|
loginWithVerificationCode(
|
||||||
|
verificationCode: $verificationCode
|
||||||
|
emailAddress: $emailAddress
|
||||||
|
phoneNumber: $phoneNumber
|
||||||
|
) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
5
utils/poe/graphql/MessageAddedSubscription.graphql
Normal file
5
utils/poe/graphql/MessageAddedSubscription.graphql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
subscription MessageAddedSubscription($chatId: BigInt!) {
|
||||||
|
messageAdded(chatId: $chatId) {
|
||||||
|
...MessageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
6
utils/poe/graphql/MessageDeletedSubscription.graphql
Normal file
6
utils/poe/graphql/MessageDeletedSubscription.graphql
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
subscription MessageDeletedSubscription($chatId: BigInt!) {
|
||||||
|
messageDeleted(chatId: $chatId) {
|
||||||
|
id
|
||||||
|
messageId
|
||||||
|
}
|
||||||
|
}
|
||||||
13
utils/poe/graphql/MessageFragment.graphql
Normal file
13
utils/poe/graphql/MessageFragment.graphql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
fragment MessageFragment on Message {
|
||||||
|
id
|
||||||
|
__typename
|
||||||
|
messageId
|
||||||
|
text
|
||||||
|
linkifiedText
|
||||||
|
authorNickname
|
||||||
|
state
|
||||||
|
vote
|
||||||
|
voteReason
|
||||||
|
creationTime
|
||||||
|
suggestedReplies
|
||||||
|
}
|
||||||
7
utils/poe/graphql/MessageRemoveVoteMutation.graphql
Normal file
7
utils/poe/graphql/MessageRemoveVoteMutation.graphql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation MessageRemoveVoteMutation($messageId: BigInt!) {
|
||||||
|
messageRemoveVote(messageId: $messageId) {
|
||||||
|
message {
|
||||||
|
...MessageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
utils/poe/graphql/MessageSetVoteMutation.graphql
Normal file
7
utils/poe/graphql/MessageSetVoteMutation.graphql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) {
|
||||||
|
messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) {
|
||||||
|
message {
|
||||||
|
...MessageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
mutation SendVerificationCodeForLoginMutation(
|
||||||
|
$emailAddress: String
|
||||||
|
$phoneNumber: String
|
||||||
|
) {
|
||||||
|
sendVerificationCode(
|
||||||
|
verificationReason: login
|
||||||
|
emailAddress: $emailAddress
|
||||||
|
phoneNumber: $phoneNumber
|
||||||
|
) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
9
utils/poe/graphql/ShareMessagesMutation.graphql
Normal file
9
utils/poe/graphql/ShareMessagesMutation.graphql
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
mutation ShareMessagesMutation(
|
||||||
|
$chatId: BigInt!
|
||||||
|
$messageIds: [BigInt!]!
|
||||||
|
$comment: String
|
||||||
|
) {
|
||||||
|
messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) {
|
||||||
|
shareCode
|
||||||
|
}
|
||||||
|
}
|
||||||
13
utils/poe/graphql/SignupWithVerificationCodeMutation.graphql
Normal file
13
utils/poe/graphql/SignupWithVerificationCodeMutation.graphql
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
mutation SignupWithVerificationCodeMutation(
|
||||||
|
$verificationCode: String!
|
||||||
|
$emailAddress: String
|
||||||
|
$phoneNumber: String
|
||||||
|
) {
|
||||||
|
signupWithVerificationCode(
|
||||||
|
verificationCode: $verificationCode
|
||||||
|
emailAddress: $emailAddress
|
||||||
|
phoneNumber: $phoneNumber
|
||||||
|
) {
|
||||||
|
status
|
||||||
|
}
|
||||||
|
}
|
||||||
7
utils/poe/graphql/StaleChatUpdateMutation.graphql
Normal file
7
utils/poe/graphql/StaleChatUpdateMutation.graphql
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
mutation StaleChatUpdateMutation($chatId: BigInt!) {
|
||||||
|
staleChatUpdate(chatId: $chatId) {
|
||||||
|
message {
|
||||||
|
...MessageFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
utils/poe/graphql/SummarizePlainPostQuery.graphql
Normal file
3
utils/poe/graphql/SummarizePlainPostQuery.graphql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
query SummarizePlainPostQuery($comment: String!) {
|
||||||
|
summarizePlainPost(comment: $comment)
|
||||||
|
}
|
||||||
3
utils/poe/graphql/SummarizeQuotePostQuery.graphql
Normal file
3
utils/poe/graphql/SummarizeQuotePostQuery.graphql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) {
|
||||||
|
summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId)
|
||||||
|
}
|
||||||
3
utils/poe/graphql/SummarizeSharePostQuery.graphql
Normal file
3
utils/poe/graphql/SummarizeSharePostQuery.graphql
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) {
|
||||||
|
summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds)
|
||||||
|
}
|
||||||
14
utils/poe/graphql/UserSnippetFragment.graphql
Normal file
14
utils/poe/graphql/UserSnippetFragment.graphql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
21
utils/poe/graphql/ViewerInfoQuery.graphql
Normal file
21
utils/poe/graphql/ViewerInfoQuery.graphql
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
query ViewerInfoQuery {
|
||||||
|
viewer {
|
||||||
|
id
|
||||||
|
uid
|
||||||
|
...ViewerStateFragment
|
||||||
|
...BioFragment
|
||||||
|
...HandleFragment
|
||||||
|
hasCompletedMultiplayerNux
|
||||||
|
poeUser {
|
||||||
|
id
|
||||||
|
...UserSnippetFragment
|
||||||
|
}
|
||||||
|
messageLimit{
|
||||||
|
canSend
|
||||||
|
numMessagesRemaining
|
||||||
|
resetTime
|
||||||
|
shouldShowReminder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
30
utils/poe/graphql/ViewerStateFragment.graphql
Normal file
30
utils/poe/graphql/ViewerStateFragment.graphql
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
5
utils/poe/graphql/ViewerStateUpdatedSubscription.graphql
Normal file
5
utils/poe/graphql/ViewerStateUpdatedSubscription.graphql
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
subscription ViewerStateUpdatedSubscription {
|
||||||
|
viewerStateUpdated {
|
||||||
|
...ViewerStateFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
278
utils/poe/index.js
Normal file
278
utils/poe/index.js
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
import { readFileSync } from 'fs'
|
||||||
|
import { scrape } from './credential.js'
|
||||||
|
import fetch from 'node-fetch'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
// 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)
|
||||||
|
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
|
||||||
|
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')
|
||||||
|
const response = await fetch('https://poe.com/api/gql_POST', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: Object.assign(this.headers, {
|
||||||
|
'poe-tag-id': md5,
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}),
|
||||||
|
body: payload
|
||||||
|
})
|
||||||
|
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 r = await fetch(url, {
|
||||||
|
headers: this.headers
|
||||||
|
})
|
||||||
|
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 r = await fetch('https://poe.com', {
|
||||||
|
headers: this.headers
|
||||||
|
})
|
||||||
|
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 = nextData.props.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testPoe () {
|
||||||
|
// const key = 'deb04db9f2332a3287b7d2545061af62'
|
||||||
|
// const channel = 'poe-chan55-8888-ujygckefewomybvkqfrp'
|
||||||
|
const cookie = 'p-b=WSvmyvjHVJoMtQVkirtn-A%3D%3D'
|
||||||
|
let client = new PoeClient({
|
||||||
|
// quora_formkey: key,
|
||||||
|
// channel_name: channel,
|
||||||
|
quora_cookie: cookie
|
||||||
|
})
|
||||||
|
await client.setCredentials()
|
||||||
|
await client.getChatId()
|
||||||
|
let ai = 'a2'
|
||||||
|
await client.sendMsg(ai, '你说话不是很通顺啊')
|
||||||
|
const response = await client.getResponse(ai)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
// testPoe().then(res => {
|
||||||
|
// console.log(res)
|
||||||
|
// })
|
||||||
65
utils/poe/websocket.js
Normal file
65
utils/poe/websocket.js
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
59
utils/slack/slackClient.js
Normal file
59
utils/slack/slackClient.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Config } from '../config.js'
|
||||||
|
import slack from '@slack/bolt'
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
this.app = new slack.App(option)
|
||||||
|
} else {
|
||||||
|
throw new Error('未配置Slack信息')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMessage (prompt) {
|
||||||
|
let sendResponse = await this.app.client.chat.postMessage({
|
||||||
|
as_user: true,
|
||||||
|
text: `${prompt}`,
|
||||||
|
token: this.config.slackUserToken,
|
||||||
|
channel: this.config.slackChannelId
|
||||||
|
})
|
||||||
|
let ts = sendResponse.ts
|
||||||
|
let response = '_Typing…_'
|
||||||
|
while (response.trim().endsWith('_Typing…_')) {
|
||||||
|
let replies = await this.app.client.conversations.history({
|
||||||
|
token: this.config.slackUserToken,
|
||||||
|
channel: this.config.slackChannelId,
|
||||||
|
limit: 1,
|
||||||
|
oldest: ts
|
||||||
|
})
|
||||||
|
if (replies.messages.length > 0) {
|
||||||
|
response = replies.messages[0].text
|
||||||
|
if (Config.debug) {
|
||||||
|
let text = response.replace('_Typing…_', '')
|
||||||
|
if (text) {
|
||||||
|
logger.info(response.replace('_Typing…_', ''))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue