diff --git a/apps/chat.js b/apps/chat.js index 1598027..6a5c809 100644 --- a/apps/chat.js +++ b/apps/chat.js @@ -6,6 +6,7 @@ import delay from 'delay' import { ChatGPTAPI } from 'chatgpt' import { BingAIClient } from '@waylaidwanderer/chatgpt-api' import SydneyAIClient from '../utils/SydneyAIClient.js' +import { PoeClient } from '../utils/poe/index.js' import { render, renderUrl, getMessageById, @@ -25,6 +26,7 @@ import { convertSpeaker, generateAudio, speakers } from '../utils/tts.js' import ChatGLMClient from '../utils/chatglm.js' import { convertFaces } from '../utils/face.js' import uploadRecord from '../utils/uploadRecord.js' +import {SlackClaudeClient} from "../utils/slack/slackClient.js"; try { await import('keyv') } catch (err) { @@ -708,7 +710,7 @@ export class chatgpt extends plugin { num: 0 } } - } else { + } else if (use !== 'poe' && use === 'claude') { switch (use) { case 'api': { key = `CHATGPT:CONVERSATIONS:${e.sender.user_id}` @@ -758,7 +760,7 @@ export class chatgpt extends plugin { // 字数超限直接返回 return false } - if (use !== 'api3') { + if (use !== 'api3' && use !== 'poe' && use !== 'claude') { previousConversation.conversation = { conversationId: chatMessage.conversationId } @@ -1331,6 +1333,33 @@ export class chatgpt extends plugin { let sendMessageResult = await this.chatGPTApi.sendMessage(prompt, conversation) 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: { let completionParams = {} if (Config.model) { diff --git a/apps/help.js b/apps/help.js index cf8e697..cb67474 100644 --- a/apps/help.js +++ b/apps/help.js @@ -138,8 +138,8 @@ let helpData = [ }, { icon: 'switch', - title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM', - desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM' + title: '#chatgpt切换浏览器/API/API3/Bing/ChatGLM/Claude/Poe', + desc: '切换使用的后端为浏览器或OpenAI API/反代官网API/Bing/自建ChatGLM/Slack Claude/Poe' }, { icon: 'confirm', diff --git a/apps/management.js b/apps/management.js index 7545e5b..e8c8d29 100644 --- a/apps/management.js +++ b/apps/management.js @@ -32,6 +32,11 @@ export class ChatgptManagement extends plugin { fnc: 'setAccessToken', permission: 'master' }, + { + reg: '#chatgpt(设置|绑定)(Poe|POE)(token|Token)', + fnc: 'setPoeCookie', + permission: 'master' + }, { reg: '#chatgpt(设置|绑定|添加)(必应|Bing |bing )(token|Token)', fnc: 'setBingAccessToken', @@ -77,6 +82,16 @@ export class ChatgptManagement extends plugin { fnc: 'useBingSolution', permission: 'master' }, + { + reg: '^#chatgpt切换(Poe|poe)$', + fnc: 'useClaudeBasedSolution', + permission: 'master' + }, + { + reg: '^#chatgpt切换(Claude|claude|slack)$', + fnc: 'useSlackClaudeBasedSolution', + permission: 'master' + }, { reg: '^#chatgpt(必应|Bing)切换', fnc: 'changeBingTone', @@ -404,6 +419,25 @@ export class ChatgptManagement extends plugin { 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) { this.setContext('saveBingToken') 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') if (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错误') } else { 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) { let tongStyle = e.msg.replace(/^#chatgpt(必应|Bing)切换/, '') if (!tongStyle) { diff --git a/guoba.support.js b/guoba.support.js index 1993726..87c47f1 100644 --- a/guoba.support.js +++ b/guoba.support.js @@ -441,6 +441,34 @@ export function supportGuoba () { bottomHelpMessage: '为空使用默认puppeteer的chromium,也可以传递自己本机安装的Chrome可执行文件地址,提高通过率。windows可以是‘C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe’,linux通过which查找路径', 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方式的配置', component: 'Divider' @@ -584,7 +612,7 @@ export function supportGuoba () { label: '允许群获取后台地址', bottomHelpMessage: '是否允许群获取后台地址,关闭后将只能私聊获取', component: 'Switch' - }, + } ], // 获取配置数据方法(用于前端填充显示数据) getConfigData () { diff --git a/index.js b/index.js index 16d0194..7a96402 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,6 @@ for (let i in files) { // 启动服务器 await createServer() - logger.info('**************************************') logger.info('chatgpt-plugin加载成功') logger.info(`当前版本${Config.version}`) diff --git a/package.json b/package.json index 12eb33d..5d58117 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,14 @@ "type": "module", "author": "ikechan8370", "dependencies": { + "@fastify/cookie": "^8.3.0", "@fastify/cors": "^8.2.0", "@fastify/static": "^6.9.0", - "@fastify/cookie": "^8.3.0", + "@slack/bolt": "^3.13.0", "@waylaidwanderer/chatgpt-api": "^1.33.2", "chatgpt": "^5.1.1", "delay": "^5.0.0", + "diff": "^5.1.0", "eventsource": "^2.0.2", "eventsource-parser": "^1.0.0", "fastify": "^4.13.0", @@ -24,8 +26,8 @@ "ws": "^8.13.0" }, "optionalDependencies": { - "node-silk": "^0.1.0", "jimp": "^0.22.7", + "node-silk": "^0.1.0", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-recaptcha": "^3.6.8", "puppeteer-extra-plugin-stealth": "^2.11.2", diff --git a/utils/config.js b/utils/config.js index 767a513..b224bcf 100644 --- a/utils/config.js +++ b/utils/config.js @@ -92,7 +92,11 @@ const defaultConfig = { groupWhitelist: [], groupBlacklist: [], ttsRegex: '/匹配规则/匹配模式', - version: 'v2.5.2' + slackUserToken: '', + slackBotUserToken: '', + slackChannelId: '', + slackSigningSecret: '', + version: 'v2.5.3' } const _path = process.cwd() let config = {} diff --git a/utils/poe/credential.js b/utils/poe/credential.js new file mode 100644 index 0000000..2cf5fcf --- /dev/null +++ b/utils/poe/credential.js @@ -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 } diff --git a/utils/poe/graphql/AddHumanMessageMutation.graphql b/utils/poe/graphql/AddHumanMessageMutation.graphql new file mode 100644 index 0000000..01e6bc8 --- /dev/null +++ b/utils/poe/graphql/AddHumanMessageMutation.graphql @@ -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 + } + } +} diff --git a/utils/poe/graphql/AddMessageBreakMutation.graphql b/utils/poe/graphql/AddMessageBreakMutation.graphql new file mode 100644 index 0000000..b28d990 --- /dev/null +++ b/utils/poe/graphql/AddMessageBreakMutation.graphql @@ -0,0 +1,17 @@ +mutation AddMessageBreakMutation($chatId: BigInt!) { + messageBreakCreate(chatId: $chatId) { + message { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies + } + } +} diff --git a/utils/poe/graphql/AutoSubscriptionMutation.graphql b/utils/poe/graphql/AutoSubscriptionMutation.graphql new file mode 100644 index 0000000..6cf7bf7 --- /dev/null +++ b/utils/poe/graphql/AutoSubscriptionMutation.graphql @@ -0,0 +1,7 @@ +mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) { + autoSubscribe(subscriptions: $subscriptions) { + viewer { + id + } + } +} diff --git a/utils/poe/graphql/BioFragment.graphql b/utils/poe/graphql/BioFragment.graphql new file mode 100644 index 0000000..c421803 --- /dev/null +++ b/utils/poe/graphql/BioFragment.graphql @@ -0,0 +1,8 @@ +fragment BioFragment on Viewer { + id + poeUser { + id + uid + bio + } +} diff --git a/utils/poe/graphql/ChatAddedSubscription.graphql b/utils/poe/graphql/ChatAddedSubscription.graphql new file mode 100644 index 0000000..664b107 --- /dev/null +++ b/utils/poe/graphql/ChatAddedSubscription.graphql @@ -0,0 +1,5 @@ +subscription ChatAddedSubscription { + chatAdded { + ...ChatFragment + } +} diff --git a/utils/poe/graphql/ChatFragment.graphql b/utils/poe/graphql/ChatFragment.graphql new file mode 100644 index 0000000..605645f --- /dev/null +++ b/utils/poe/graphql/ChatFragment.graphql @@ -0,0 +1,6 @@ +fragment ChatFragment on Chat { + id + chatId + defaultBotNickname + shouldShowDisclaimer +} diff --git a/utils/poe/graphql/ChatPaginationQuery.graphql b/utils/poe/graphql/ChatPaginationQuery.graphql new file mode 100644 index 0000000..f2452cd --- /dev/null +++ b/utils/poe/graphql/ChatPaginationQuery.graphql @@ -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 + } + } + } + } +} diff --git a/utils/poe/graphql/ChatViewQuery.graphql b/utils/poe/graphql/ChatViewQuery.graphql new file mode 100644 index 0000000..c330107 --- /dev/null +++ b/utils/poe/graphql/ChatViewQuery.graphql @@ -0,0 +1,8 @@ +query ChatViewQuery($bot: String!) { + chatOfBot(bot: $bot) { + id + chatId + defaultBotNickname + shouldShowDisclaimer + } +} diff --git a/utils/poe/graphql/DeleteHumanMessagesMutation.graphql b/utils/poe/graphql/DeleteHumanMessagesMutation.graphql new file mode 100644 index 0000000..42692c6 --- /dev/null +++ b/utils/poe/graphql/DeleteHumanMessagesMutation.graphql @@ -0,0 +1,7 @@ +mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) { + messagesDelete(messageIds: $messageIds) { + viewer { + id + } + } +} diff --git a/utils/poe/graphql/HandleFragment.graphql b/utils/poe/graphql/HandleFragment.graphql new file mode 100644 index 0000000..f53c484 --- /dev/null +++ b/utils/poe/graphql/HandleFragment.graphql @@ -0,0 +1,8 @@ +fragment HandleFragment on Viewer { + id + poeUser { + id + uid + handle + } +} diff --git a/utils/poe/graphql/LoginWithVerificationCodeMutation.graphql b/utils/poe/graphql/LoginWithVerificationCodeMutation.graphql new file mode 100644 index 0000000..723b1f4 --- /dev/null +++ b/utils/poe/graphql/LoginWithVerificationCodeMutation.graphql @@ -0,0 +1,13 @@ +mutation LoginWithVerificationCodeMutation( + $verificationCode: String! + $emailAddress: String + $phoneNumber: String +) { + loginWithVerificationCode( + verificationCode: $verificationCode + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/utils/poe/graphql/MessageAddedSubscription.graphql b/utils/poe/graphql/MessageAddedSubscription.graphql new file mode 100644 index 0000000..0492baa --- /dev/null +++ b/utils/poe/graphql/MessageAddedSubscription.graphql @@ -0,0 +1,5 @@ +subscription MessageAddedSubscription($chatId: BigInt!) { + messageAdded(chatId: $chatId) { + ...MessageFragment + } +} diff --git a/utils/poe/graphql/MessageDeletedSubscription.graphql b/utils/poe/graphql/MessageDeletedSubscription.graphql new file mode 100644 index 0000000..54c1c16 --- /dev/null +++ b/utils/poe/graphql/MessageDeletedSubscription.graphql @@ -0,0 +1,6 @@ +subscription MessageDeletedSubscription($chatId: BigInt!) { + messageDeleted(chatId: $chatId) { + id + messageId + } +} diff --git a/utils/poe/graphql/MessageFragment.graphql b/utils/poe/graphql/MessageFragment.graphql new file mode 100644 index 0000000..cc86081 --- /dev/null +++ b/utils/poe/graphql/MessageFragment.graphql @@ -0,0 +1,13 @@ +fragment MessageFragment on Message { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies +} diff --git a/utils/poe/graphql/MessageRemoveVoteMutation.graphql b/utils/poe/graphql/MessageRemoveVoteMutation.graphql new file mode 100644 index 0000000..d5e6e61 --- /dev/null +++ b/utils/poe/graphql/MessageRemoveVoteMutation.graphql @@ -0,0 +1,7 @@ +mutation MessageRemoveVoteMutation($messageId: BigInt!) { + messageRemoveVote(messageId: $messageId) { + message { + ...MessageFragment + } + } +} diff --git a/utils/poe/graphql/MessageSetVoteMutation.graphql b/utils/poe/graphql/MessageSetVoteMutation.graphql new file mode 100644 index 0000000..76000df --- /dev/null +++ b/utils/poe/graphql/MessageSetVoteMutation.graphql @@ -0,0 +1,7 @@ +mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) { + messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) { + message { + ...MessageFragment + } + } +} diff --git a/utils/poe/graphql/SendVerificationCodeForLoginMutation.graphql b/utils/poe/graphql/SendVerificationCodeForLoginMutation.graphql new file mode 100644 index 0000000..45af479 --- /dev/null +++ b/utils/poe/graphql/SendVerificationCodeForLoginMutation.graphql @@ -0,0 +1,12 @@ +mutation SendVerificationCodeForLoginMutation( + $emailAddress: String + $phoneNumber: String +) { + sendVerificationCode( + verificationReason: login + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/utils/poe/graphql/ShareMessagesMutation.graphql b/utils/poe/graphql/ShareMessagesMutation.graphql new file mode 100644 index 0000000..92e80db --- /dev/null +++ b/utils/poe/graphql/ShareMessagesMutation.graphql @@ -0,0 +1,9 @@ +mutation ShareMessagesMutation( + $chatId: BigInt! + $messageIds: [BigInt!]! + $comment: String +) { + messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) { + shareCode + } +} diff --git a/utils/poe/graphql/SignupWithVerificationCodeMutation.graphql b/utils/poe/graphql/SignupWithVerificationCodeMutation.graphql new file mode 100644 index 0000000..06b2826 --- /dev/null +++ b/utils/poe/graphql/SignupWithVerificationCodeMutation.graphql @@ -0,0 +1,13 @@ +mutation SignupWithVerificationCodeMutation( + $verificationCode: String! + $emailAddress: String + $phoneNumber: String +) { + signupWithVerificationCode( + verificationCode: $verificationCode + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/utils/poe/graphql/StaleChatUpdateMutation.graphql b/utils/poe/graphql/StaleChatUpdateMutation.graphql new file mode 100644 index 0000000..de203d4 --- /dev/null +++ b/utils/poe/graphql/StaleChatUpdateMutation.graphql @@ -0,0 +1,7 @@ +mutation StaleChatUpdateMutation($chatId: BigInt!) { + staleChatUpdate(chatId: $chatId) { + message { + ...MessageFragment + } + } +} diff --git a/utils/poe/graphql/SummarizePlainPostQuery.graphql b/utils/poe/graphql/SummarizePlainPostQuery.graphql new file mode 100644 index 0000000..afa2a84 --- /dev/null +++ b/utils/poe/graphql/SummarizePlainPostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizePlainPostQuery($comment: String!) { + summarizePlainPost(comment: $comment) +} diff --git a/utils/poe/graphql/SummarizeQuotePostQuery.graphql b/utils/poe/graphql/SummarizeQuotePostQuery.graphql new file mode 100644 index 0000000..5147c3c --- /dev/null +++ b/utils/poe/graphql/SummarizeQuotePostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) { + summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId) +} diff --git a/utils/poe/graphql/SummarizeSharePostQuery.graphql b/utils/poe/graphql/SummarizeSharePostQuery.graphql new file mode 100644 index 0000000..cb4a623 --- /dev/null +++ b/utils/poe/graphql/SummarizeSharePostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) { + summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds) +} diff --git a/utils/poe/graphql/UserSnippetFragment.graphql b/utils/poe/graphql/UserSnippetFragment.graphql new file mode 100644 index 0000000..17fc842 --- /dev/null +++ b/utils/poe/graphql/UserSnippetFragment.graphql @@ -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 +} diff --git a/utils/poe/graphql/ViewerInfoQuery.graphql b/utils/poe/graphql/ViewerInfoQuery.graphql new file mode 100644 index 0000000..1ecaf9e --- /dev/null +++ b/utils/poe/graphql/ViewerInfoQuery.graphql @@ -0,0 +1,21 @@ +query ViewerInfoQuery { + viewer { + id + uid + ...ViewerStateFragment + ...BioFragment + ...HandleFragment + hasCompletedMultiplayerNux + poeUser { + id + ...UserSnippetFragment + } + messageLimit{ + canSend + numMessagesRemaining + resetTime + shouldShowReminder + } + } +} + diff --git a/utils/poe/graphql/ViewerStateFragment.graphql b/utils/poe/graphql/ViewerStateFragment.graphql new file mode 100644 index 0000000..3cd83e9 --- /dev/null +++ b/utils/poe/graphql/ViewerStateFragment.graphql @@ -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 + } +} + diff --git a/utils/poe/graphql/ViewerStateUpdatedSubscription.graphql b/utils/poe/graphql/ViewerStateUpdatedSubscription.graphql new file mode 100644 index 0000000..dd6d2d1 --- /dev/null +++ b/utils/poe/graphql/ViewerStateUpdatedSubscription.graphql @@ -0,0 +1,5 @@ +subscription ViewerStateUpdatedSubscription { + viewerStateUpdated { + ...ViewerStateFragment + } +} diff --git a/utils/poe/index.js b/utils/poe/index.js new file mode 100644 index 0000000..a01189a --- /dev/null +++ b/utils/poe/index.js @@ -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 = /