This commit is contained in:
SillyLossy
2023-04-04 00:23:38 +03:00
40 changed files with 1316 additions and 42 deletions

29
package-lock.json generated
View File

@ -24,7 +24,8 @@
"png-chunks-encode": "^1.0.0", "png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0", "png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3" "sanitize-filename": "^1.6.3",
"ws": "^8.13.0"
}, },
"bin": { "bin": {
"TavernAI": "server.js" "TavernAI": "server.js"
@ -1964,6 +1965,26 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xhr": { "node_modules/xhr": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",
@ -3491,6 +3512,12 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}, },
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
},
"xhr": { "xhr": {
"version": "2.6.0", "version": "2.6.0",
"resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz",

View File

@ -16,7 +16,8 @@
"png-chunks-encode": "^1.0.0", "png-chunks-encode": "^1.0.0",
"png-chunks-extract": "^1.0.0", "png-chunks-extract": "^1.0.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"sanitize-filename": "^1.6.3" "sanitize-filename": "^1.6.3",
"ws": "^8.13.0"
}, },
"name": "TavernAI", "name": "TavernAI",
"version": "1.2.0", "version": "1.2.0",

21
poe-test.js Normal file
View File

@ -0,0 +1,21 @@
const poe = require('./poe');
async function test() {
const client = new poe.Client();
await client.init('pb-cookie');
const bots = client.get_bot_names();
console.log(bots);
await client.purge_conversation('a2', -1);
let reply;
for await (const mes of client.send_message('a2', 'Hello')) {
reply = mes.text;
}
console.log(reply);
client.disconnect_ws();
}
test();

403
poe.js Normal file
View File

@ -0,0 +1,403 @@
const WebSocket = require('ws');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
const parent_path = path.resolve(__dirname);
const queries_path = path.join(parent_path, "poe_graphql");
let queries = {};
const logger = console;
const user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0";
function load_queries() {
const files = fs.readdirSync(queries_path);
for (const filename of files) {
const ext = path.extname(filename);
if (ext !== '.graphql') {
continue;
}
const queryName = path.basename(filename, ext);
const query = fs.readFileSync(path.join(queries_path, filename), 'utf-8');
queries[queryName] = query;
}
}
function generate_payload(query_name, variables) {
return {
query: queries[query_name],
variables: variables,
}
}
async function request_with_retries(method, attempts = 10) {
const url = '';
for (let i = 0; i < attempts; i++) {
try {
const response = await method();
if (response.status === 200) {
return response;
}
logger.warn(`Server returned a status code of ${response.status} while downloading ${url}. Retrying (${i + 1}/${attempts})...`);
}
catch (err) {
console.log(err);
}
}
throw new Error(`Failed to download ${url} too many times.`);
}
class Client {
gql_url = "https://poe.com/api/gql_POST";
gql_recv_url = "https://poe.com/api/receive_POST";
home_url = "https://poe.com";
settings_url = "https://poe.com/api/settings";
formkey = "";
next_data = {};
bots = {};
active_messages = {};
message_queues = {};
bot_names = [];
ws = null;
ws_connected = false;
auto_reconnect = false;
constructor(auto_reconnect = false) {
this.auto_reconnect = auto_reconnect;
}
async init(token, proxy = null) {
this.proxy = proxy;
this.session = axios.default.create({
timeout: 60000,
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
});
if (proxy) {
this.session.defaults.proxy = {
"http": proxy,
"https": proxy,
};
logger.info(`Proxy enabled: ${proxy}`);
}
const cookies = `p-b=${token}; Domain=poe.com`;
this.headers = {
"User-Agent": user_agent,
"Referrer": "https://poe.com/",
"Origin": "https://poe.com",
"Cookie": cookies,
};
this.ws_domain = `tch${Math.floor(Math.random() * 1e6)}`;
this.session.defaults.headers.common = this.headers;
this.next_data = await this.get_next_data();
this.channel = await this.get_channel_data();
await this.connect_ws();
this.bots = await this.get_bots();
this.bot_names = this.get_bot_names();
this.gql_headers = {
"poe-formkey": this.formkey,
"poe-tchannel": this.channel["channel"],
...this.headers,
};
await this.subscribe();
}
async get_next_data() {
logger.info('Downloading next_data...');
const r = await request_with_retries(() => this.session.get(this.home_url));
const jsonRegex = /<script id="__NEXT_DATA__" type="application\/json">(.+?)<\/script>/;
const jsonText = jsonRegex.exec(r.data)[1];
const nextData = JSON.parse(jsonText);
this.formkey = nextData.props.formkey;
this.viewer = nextData.props.pageProps.payload.viewer;
return nextData;
}
async get_bots() {
const viewer = this.next_data.props.pageProps.payload.viewer;
if (!viewer.availableBots) {
throw new Error('Invalid token.');
}
const botList = viewer.availableBots;
const bots = {};
for (const bot of botList) {
const url = `https://poe.com/_next/data/${this.next_data.buildId}/${bot.displayName.toLowerCase()}.json`;
logger.info(`Downloading ${url}`);
const r = await request_with_retries(() => this.session.get(url));
const chatData = r.data.pageProps.payload.chatOfBotDisplayName;
bots[chatData.defaultBotObject.nickname] = chatData;
}
return bots;
}
get_bot_names() {
const botNames = {};
for (const botNickname in this.bots) {
const botObj = this.bots[botNickname].defaultBotObject;
botNames[botNickname] = botObj.displayName;
}
return botNames;
}
async get_channel_data(channel = null) {
logger.info('Downloading channel data...');
const r = await request_with_retries(() => this.session.get(this.settings_url));
const data = r.data;
this.formkey = data.formkey;
return data.tchannelData;
}
get_websocket_url(channel = null) {
if (!channel) {
channel = this.channel;
}
const query = `?min_seq=${channel.minSeq}&channel=${channel.channel}&hash=${channel.channelHash}`;
return `wss://${this.ws_domain}.tch.${channel.baseHost}/up/${channel.boxName}/updates${query}`;
}
async send_query(queryName, variables) {
for (let i = 0; i < 20; i++) {
const payload = generate_payload(queryName, variables);
const r = await request_with_retries(() => this.session.post(this.gql_url, payload, { headers: this.gql_headers }));
if (!r.data.data) {
logger.warn(`${queryName} returned an error: ${data.errors[0].message} | Retrying (${i + 1}/20)`);
await new Promise((resolve) => setTimeout(resolve, 2000));
continue;
}
return r.data;
}
throw new Error(`${queryName} failed too many times.`);
}
async subscribe() {
logger.info("Subscribing to mutations")
await this.send_query("SubscriptionsMutation", {
"subscriptions": [
{
"subscriptionName": "messageAdded",
"query": queries["MessageAddedSubscription"]
},
{
"subscriptionName": "viewerStateUpdated",
"query": queries["ViewerStateUpdatedSubscription"]
}
]
});
}
ws_run_thread() {
this.ws = new WebSocket(this.get_websocket_url(), {
headers: {
"User-Agent": user_agent
},
rejectUnauthorized: false
});
this.ws.on("open", () => {
this.on_ws_connect(this.ws);
});
this.ws.on('message', (message) => {
this.on_message(this.ws, message);
});
this.ws.on('close', () => {
this.ws_connected = false;
});
this.ws.on('error', (error) => {
this.on_ws_error(this.ws, error);
});
}
async connect_ws() {
this.ws_connected = false;
this.ws_run_thread();
while (!this.ws_connected) {
await new Promise(resolve => setTimeout(() => { resolve() }, 10));
}
}
disconnect_ws() {
if (this.ws) {
this.ws.close();
}
this.ws_connected = false;
}
on_ws_connect(ws) {
this.ws_connected = true;
}
on_ws_error(ws, error) {
logger.warn(`Websocket returned error: ${error}`);
this.disconnect_ws();
if (this.auto_reconnect) {
this.connect_ws();
}
}
on_message(ws, msg) {
const data = JSON.parse(msg);
const message = JSON.parse(data["messages"][0])["payload"]["data"]["messageAdded"];
const copiedDict = Object.assign({}, this.active_messages);
for (const [key, value] of Object.entries(copiedDict)) {
//add the message to the appropriate queue
if (value === message["messageId"] && key in this.message_queues) {
this.message_queues[key].push(message);
return;
}
//indicate that the response id is tied to the human message id
else if (key !== "pending" && value === null && message["state"] !== "complete") {
this.active_messages[key] = message["messageId"];
this.message_queues[key].push(message);
}
}
}
async *send_message(chatbot, message, with_chat_break = false, timeout = 20) {
//if there is another active message, wait until it has finished sending
while (Object.values(this.active_messages).includes(null)) {
await new Promise(resolve => setTimeout(resolve, 10));
}
//null indicates that a message is still in progress
this.active_messages["pending"] = null;
console.log(`Sending message to ${chatbot}: ${message}`);
const messageData = await this.send_query("AddHumanMessageMutation", {
"bot": chatbot,
"query": message,
"chatId": this.bots[chatbot]["chatId"],
"source": null,
"withChatBreak": with_chat_break
});
delete this.active_messages["pending"];
if (!messageData["data"]["messageCreateWithStatus"]["messageLimit"]["canSend"]) {
throw new Error(`Daily limit reached for ${chatbot}.`);
}
let humanMessageId;
try {
const humanMessage = messageData["data"]["messageCreateWithStatus"];
humanMessageId = humanMessage["message"]["messageId"];
} catch (error) {
throw new Error(`An unknown error occured. Raw response data: ${messageData}`);
}
//indicate that the current message is waiting for a response
this.active_messages[humanMessageId] = null;
this.message_queues[humanMessageId] = [];
let lastText = "";
let messageId;
while (true) {
try {
const message = this.message_queues[humanMessageId].shift();
if (!message) {
await new Promise(resolve => setTimeout(() => resolve(), 1000));
continue;
//throw new Error("Queue is empty");
}
//only break when the message is marked as complete
if (message["state"] === "complete") {
if (lastText && message["messageId"] === messageId) {
break;
} else {
continue;
}
}
//update info about response
message["text_new"] = message["text"].substring(lastText.length);
lastText = message["text"];
messageId = message["messageId"];
yield message;
} catch (error) {
delete this.active_messages[humanMessageId];
delete this.message_queues[humanMessageId];
throw new Error("Response timed out.");
}
}
delete this.active_messages[humanMessageId];
delete this.message_queues[humanMessageId];
}
async send_chat_break(chatbot) {
logger.info(`Sending chat break to ${chatbot}`);
const result = await this.send_query("AddMessageBreakMutation", {
"chatId": this.bots[chatbot]["chatId"]
});
return result["data"]["messageBreakCreate"]["message"];
}
async get_message_history(chatbot, count = 25, cursor = null) {
logger.info(`Downloading ${count} messages from ${chatbot}`);
const result = await this.send_query("ChatListPaginationQuery", {
"count": count,
"cursor": cursor,
"id": this.bots[chatbot]["id"]
});
return result["data"]["node"]["messagesConnection"]["edges"];
}
async delete_message(message_ids) {
logger.info(`Deleting messages: ${message_ids}`);
if (!Array.isArray(message_ids)) {
message_ids = [parseInt(message_ids)];
}
const result = await this.send_query("DeleteMessageMutation", {
"messageIds": message_ids
});
}
async purge_conversation(chatbot, count = -1) {
logger.info(`Purging messages from ${chatbot}`);
let last_messages = (await this.get_message_history(chatbot, count = 50)).reverse();
while (last_messages.length) {
const message_ids = [];
for (const message of last_messages) {
if (count === 0) {
break;
}
count--;
message_ids.push(message["node"]["messageId"]);
}
await this.delete_message(message_ids);
if (count === 0) {
return;
}
last_messages = (await this.get_message_history(chatbot, count = 50)).reverse();
}
logger.info("No more messages left to delete.");
}
}
load_queries();
module.exports = { Client };

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,316 @@
query ChatListPaginationQuery(
$count: Int = 5
$cursor: String
$id: ID!
) {
node(id: $id) {
__typename
...ChatPageMain_chat_1G22uz
id
}
}
fragment BotImage_bot on Bot {
image {
__typename
... on LocalBotImage {
localName
}
... on UrlBotImage {
url
}
}
displayName
}
fragment ChatMessageDownvotedButton_message on Message {
...MessageFeedbackReasonModal_message
...MessageFeedbackOtherModal_message
}
fragment ChatMessageDropdownMenu_message on Message {
id
messageId
vote
text
linkifiedText
...chatHelpers_isBotMessage
}
fragment ChatMessageFeedbackButtons_message on Message {
id
messageId
vote
voteReason
...ChatMessageDownvotedButton_message
}
fragment ChatMessageInputView_chat on Chat {
id
chatId
defaultBotObject {
nickname
messageLimit {
dailyBalance
shouldShowRemainingMessageCount
}
id
}
shouldShowDisclaimer
...chatHelpers_useSendMessage_chat
...chatHelpers_useSendChatBreak_chat
}
fragment ChatMessageInputView_edges on MessageEdge {
node {
...chatHelpers_isChatBreak
...chatHelpers_isHumanMessage
state
text
id
}
}
fragment ChatMessageOverflowButton_message on Message {
text
...ChatMessageDropdownMenu_message
...chatHelpers_isBotMessage
}
fragment ChatMessageSuggestedReplies_SuggestedReplyButton_chat on Chat {
...chatHelpers_useSendMessage_chat
}
fragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {
messageId
}
fragment ChatMessageSuggestedReplies_chat on Chat {
...ChatWelcomeView_chat
...ChatMessageSuggestedReplies_SuggestedReplyButton_chat
}
fragment ChatMessageSuggestedReplies_message on Message {
suggestedReplies
...ChatMessageSuggestedReplies_SuggestedReplyButton_message
}
fragment ChatMessage_chat on Chat {
defaultBotObject {
...ChatPageDisclaimer_bot
messageLimit {
...ChatPageRateLimitedBanner_messageLimit
}
id
}
...ChatMessageSuggestedReplies_chat
...ChatWelcomeView_chat
}
fragment ChatMessage_message on Message {
id
messageId
text
author
linkifiedText
state
...ChatMessageSuggestedReplies_message
...ChatMessageFeedbackButtons_message
...ChatMessageOverflowButton_message
...chatHelpers_isHumanMessage
...chatHelpers_isBotMessage
...chatHelpers_isChatBreak
...chatHelpers_useTimeoutLevel
...MarkdownLinkInner_message
}
fragment ChatMessagesView_chat on Chat {
...ChatMessage_chat
...ChatWelcomeView_chat
defaultBotObject {
messageLimit {
...ChatPageRateLimitedBanner_messageLimit
}
id
}
}
fragment ChatMessagesView_edges on MessageEdge {
node {
id
messageId
creationTime
...ChatMessage_message
...chatHelpers_isBotMessage
...chatHelpers_isHumanMessage
...chatHelpers_isChatBreak
}
}
fragment ChatPageDeleteFooter_chat on Chat {
...MessageDeleteConfirmationModal_chat
}
fragment ChatPageDisclaimer_bot on Bot {
disclaimer
}
fragment ChatPageMain_chat_1G22uz on Chat {
id
chatId
...ChatMessageInputView_chat
...ChatPageShareFooter_chat
...ChatPageDeleteFooter_chat
...ChatMessagesView_chat
...MarkdownLinkInner_chat
...chatHelpers_useUpdateStaleChat_chat
...ChatSubscriptionPaywallContextWrapper_chat
messagesConnection(last: $count, before: $cursor) {
edges {
...ChatMessagesView_edges
...ChatMessageInputView_edges
...MarkdownLinkInner_edges
node {
...chatHelpers_useUpdateStaleChat_message
id
__typename
}
cursor
id
}
pageInfo {
hasPreviousPage
startCursor
}
id
}
}
fragment ChatPageRateLimitedBanner_messageLimit on MessageLimit {
numMessagesRemaining
}
fragment ChatPageShareFooter_chat on Chat {
chatId
}
fragment ChatSubscriptionPaywallContextWrapper_chat on Chat {
defaultBotObject {
messageLimit {
numMessagesRemaining
shouldShowRemainingMessageCount
}
...SubscriptionPaywallModal_bot
id
}
}
fragment ChatWelcomeView_ChatWelcomeButton_chat on Chat {
...chatHelpers_useSendMessage_chat
}
fragment ChatWelcomeView_chat on Chat {
...ChatWelcomeView_ChatWelcomeButton_chat
defaultBotObject {
displayName
id
}
}
fragment MarkdownLinkInner_chat on Chat {
id
chatId
defaultBotObject {
nickname
id
}
...chatHelpers_useSendMessage_chat
}
fragment MarkdownLinkInner_edges on MessageEdge {
node {
state
id
}
}
fragment MarkdownLinkInner_message on Message {
messageId
}
fragment MessageDeleteConfirmationModal_chat on Chat {
id
}
fragment MessageFeedbackOtherModal_message on Message {
id
messageId
}
fragment MessageFeedbackReasonModal_message on Message {
id
messageId
}
fragment SubscriptionPaywallModal_bot on Bot {
displayName
messageLimit {
dailyLimit
numMessagesRemaining
shouldShowRemainingMessageCount
resetTime
}
...BotImage_bot
}
fragment chatHelpers_isBotMessage on Message {
...chatHelpers_isHumanMessage
...chatHelpers_isChatBreak
}
fragment chatHelpers_isChatBreak on Message {
author
}
fragment chatHelpers_isHumanMessage on Message {
author
}
fragment chatHelpers_useSendChatBreak_chat on Chat {
id
chatId
defaultBotObject {
nickname
introduction
model
id
}
shouldShowDisclaimer
}
fragment chatHelpers_useSendMessage_chat on Chat {
id
chatId
defaultBotObject {
nickname
id
}
shouldShowDisclaimer
}
fragment chatHelpers_useTimeoutLevel on Message {
id
state
text
messageId
}
fragment chatHelpers_useUpdateStaleChat_chat on Chat {
chatId
...chatHelpers_useSendChatBreak_chat
}
fragment chatHelpers_useUpdateStaleChat_message on Message {
creationTime
...chatHelpers_isChatBreak
}

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,100 @@
subscription messageAdded (
$chatId: BigInt!
) {
messageAdded(chatId: $chatId) {
id
messageId
creationTime
state
...ChatMessage_message
...chatHelpers_isBotMessage
}
}
fragment ChatMessageDownvotedButton_message on Message {
...MessageFeedbackReasonModal_message
...MessageFeedbackOtherModal_message
}
fragment ChatMessageDropdownMenu_message on Message {
id
messageId
vote
text
linkifiedText
...chatHelpers_isBotMessage
}
fragment ChatMessageFeedbackButtons_message on Message {
id
messageId
vote
voteReason
...ChatMessageDownvotedButton_message
}
fragment ChatMessageOverflowButton_message on Message {
text
...ChatMessageDropdownMenu_message
...chatHelpers_isBotMessage
}
fragment ChatMessageSuggestedReplies_SuggestedReplyButton_message on Message {
messageId
}
fragment ChatMessageSuggestedReplies_message on Message {
suggestedReplies
...ChatMessageSuggestedReplies_SuggestedReplyButton_message
}
fragment ChatMessage_message on Message {
id
messageId
text
author
linkifiedText
state
...ChatMessageSuggestedReplies_message
...ChatMessageFeedbackButtons_message
...ChatMessageOverflowButton_message
...chatHelpers_isHumanMessage
...chatHelpers_isBotMessage
...chatHelpers_isChatBreak
...chatHelpers_useTimeoutLevel
...MarkdownLinkInner_message
}
fragment MarkdownLinkInner_message on Message {
messageId
}
fragment MessageFeedbackOtherModal_message on Message {
id
messageId
}
fragment MessageFeedbackReasonModal_message on Message {
id
messageId
}
fragment chatHelpers_isBotMessage on Message {
...chatHelpers_isHumanMessage
...chatHelpers_isChatBreak
}
fragment chatHelpers_isChatBreak on Message {
author
}
fragment chatHelpers_isHumanMessage on Message {
author
}
fragment chatHelpers_useTimeoutLevel on Message {
id
state
text
messageId
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

@ -0,0 +1,43 @@
subscription viewerStateUpdated {
viewerStateUpdated {
id
...ChatPageBotSwitcher_viewer
}
}
fragment BotHeader_bot on Bot {
displayName
messageLimit {
dailyLimit
}
...BotImage_bot
}
fragment BotImage_bot on Bot {
image {
__typename
... on LocalBotImage {
localName
}
... on UrlBotImage {
url
}
}
displayName
}
fragment BotLink_bot on Bot {
displayName
}
fragment ChatPageBotSwitcher_viewer on Viewer {
availableBots {
id
messageLimit {
dailyLimit
}
...BotLink_bot
...BotHeader_bot
}
allowUserCreatedBots: booleanGate(gateName: "enable_user_created_bots")
}

View File

@ -124,6 +124,7 @@ export {
is_send_press, is_send_press,
api_server_textgenerationwebui, api_server_textgenerationwebui,
count_view_mes, count_view_mes,
max_context,
default_avatar, default_avatar,
system_message_types, system_message_types,
talkativeness_default, talkativeness_default,

View File

@ -1,12 +1,18 @@
import { getContext, getApiUrl } from "../../extensions.js"; import { getContext } from "../../extensions.js";
import { delay } from "../../utils.js"; import { delay } from "../../utils.js";
import { showSwipeButtons, hideSwipeButtons, getExtensionPrompt, extension_prompt_types } from "../../../script.js"; import {
showSwipeButtons,
hideSwipeButtons,
getExtensionPrompt,
extension_prompt_types,
token as csrf_token,
max_context,
} from "../../../script.js";
const TOKEN_KEY = 'extensions_poe_token'; const TOKEN_KEY = 'extensions_poe_token';
const BOT_KEY = 'extensions_poe_bot'; const BOT_KEY = 'extensions_poe_bot';
const RESPONSE_KEY = 'extensions_poe_response'; const RESPONSE_KEY = 'extensions_poe_response';
const MESSAGE_KEY = 'extensions_poe_message'; const MESSAGE_KEY = 'extensions_poe_message';
const MAX_CONTEXT_KEY = 'extensions_poe_max_context';
const DEFAULT_MAX_CONTEXT = 2000; const DEFAULT_MAX_CONTEXT = 2000;
const MAX_RETRIES_FOR_ACTIVATION = 5; const MAX_RETRIES_FOR_ACTIVATION = 5;
@ -30,7 +36,6 @@ let token;
let bot; let bot;
let jailbreak_response = DEFAULT_JAILBREAK_RESPONSE; let jailbreak_response = DEFAULT_JAILBREAK_RESPONSE;
let jailbreak_message = DEFAULT_JAILBREAK_MESSAGE; let jailbreak_message = DEFAULT_JAILBREAK_MESSAGE;
let max_context = DEFAULT_MAX_CONTEXT;
let jailbroken = false; let jailbroken = false;
let got_reply = false; let got_reply = false;
@ -39,10 +44,8 @@ function loadSettings() {
bot = localStorage.getItem(BOT_KEY); bot = localStorage.getItem(BOT_KEY);
jailbreak_response = localStorage.getItem(RESPONSE_KEY) ?? DEFAULT_JAILBREAK_RESPONSE; jailbreak_response = localStorage.getItem(RESPONSE_KEY) ?? DEFAULT_JAILBREAK_RESPONSE;
jailbreak_message = localStorage.getItem(MESSAGE_KEY) ?? DEFAULT_JAILBREAK_MESSAGE; jailbreak_message = localStorage.getItem(MESSAGE_KEY) ?? DEFAULT_JAILBREAK_MESSAGE;
max_context = Number(localStorage.getItem(MAX_CONTEXT_KEY) ?? DEFAULT_MAX_CONTEXT);
$('#poe_activation_response').val(jailbreak_response); $('#poe_activation_response').val(jailbreak_response);
$('#poe_activation_message').val(jailbreak_message); $('#poe_activation_message').val(jailbreak_message);
$('#poe_max_context').val(max_context);
$('#poe_token').val(token ?? ''); $('#poe_token').val(token ?? '');
selectBot(); selectBot();
@ -63,7 +66,6 @@ function saveSettings() {
localStorage.setItem(BOT_KEY, bot); localStorage.setItem(BOT_KEY, bot);
localStorage.setItem(RESPONSE_KEY, jailbreak_response); localStorage.setItem(RESPONSE_KEY, jailbreak_response);
localStorage.setItem(MESSAGE_KEY, jailbreak_message); localStorage.setItem(MESSAGE_KEY, jailbreak_message);
localStorage.setItem(MAX_CONTEXT_KEY, max_context);
} }
function onTokenInput() { function onTokenInput() {
@ -110,6 +112,7 @@ async function generate(type, chat2, storyString, mesExamplesArray, promptBias,
let messageSendString = ''; let messageSendString = '';
const allMessages = [...chat2, ...mesExamplesArray]; const allMessages = [...chat2, ...mesExamplesArray];
const maxContext = Math.min(Number(max_context), DEFAULT_MAX_CONTEXT);
for (let index = 0; index < allMessages.length; ++index) { for (let index = 0; index < allMessages.length; ++index) {
const item = allMessages[index]; const item = allMessages[index];
@ -117,7 +120,7 @@ async function generate(type, chat2, storyString, mesExamplesArray, promptBias,
const promptLength = context.encode(prompt + messageSendString + item + activator + extensionPrompt).length; const promptLength = context.encode(prompt + messageSendString + item + activator + extensionPrompt).length;
await delay(1); await delay(1);
if (promptLength >= Number(max_context)) { if (promptLength >= maxContext) {
break; break;
} }
@ -148,13 +151,9 @@ async function purgeConversation(count = -1) {
count, count,
}); });
const apiUrl = new URL(getApiUrl()); const response = await fetch('/purge_poe', {
apiUrl.pathname = '/api/poe/purge';
const response = await fetch(apiUrl, {
headers: { headers: {
'Bypass-Tunnel-Reminder': 'bypass', 'X-CSRF-Token': csrf_token,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: body, body: body,
@ -171,12 +170,9 @@ async function sendMessage(prompt) {
token, token,
}); });
const apiUrl = new URL(getApiUrl()); const response = await fetch('/generate_poe', {
apiUrl.pathname = '/api/poe/generate';
const response = await fetch(apiUrl, {
headers: { headers: {
'Bypass-Tunnel-Reminder': 'bypass', 'X-CSRF-Token': csrf_token,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: body, body: body,
@ -199,12 +195,9 @@ async function sendMessage(prompt) {
async function onConnectClick() { async function onConnectClick() {
const body = JSON.stringify({ token: token }); const body = JSON.stringify({ token: token });
const apiUrl = new URL(getApiUrl()); const response = await fetch('/status_poe', {
apiUrl.pathname = '/api/poe/status';
const response = await fetch(apiUrl, {
headers: { headers: {
'Bypass-Tunnel-Reminder': 'bypass', 'X-CSRF-Token': csrf_token,
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: body, body: body,
@ -248,11 +241,6 @@ function onMessageInput() {
saveSettings(); saveSettings();
} }
function onMaxContextInput() {
max_context = Number($(this).val());
saveSettings();
}
$('document').ready(function () { $('document').ready(function () {
function addExtensionControls() { function addExtensionControls() {
const settingsHtml = ` const settingsHtml = `
@ -271,9 +259,7 @@ $('document').ready(function () {
<label for="poe_activation_message">Jailbreak activation message</label> <label for="poe_activation_message">Jailbreak activation message</label>
<textarea id="poe_activation_message" rows="3"></textarea> <textarea id="poe_activation_message" rows="3"></textarea>
<label for="poe_activation_response">Jailbreak activation response</label> <label for="poe_activation_response">Jailbreak activation response</label>
<input id="poe_activation_response" class="text_pole" type="text /> <input id="poe_activation_response" class="text_pole" type="text" />
<label for="poe_max_context">Max context (in tokens)</label>
<input id="poe_max_context" class="text_pole" type="number" min="0" max="4096" />
<input id="poe_connect" class="menu_button" type="button" value="Connect" /> <input id="poe_connect" class="menu_button" type="button" value="Connect" />
<div> <div>
<label for="poe_bots">List of bots:</label> <label for="poe_bots">List of bots:</label>
@ -289,7 +275,6 @@ $('document').ready(function () {
$('#poe_connect').on('click', onConnectClick); $('#poe_connect').on('click', onConnectClick);
$('#poe_activation_response').on('input', onResponseInput); $('#poe_activation_response').on('input', onResponseInput);
$('#poe_activation_message').on('input', onMessageInput); $('#poe_activation_message').on('input', onMessageInput);
$('#poe_max_context').on('input', onMaxContextInput);
} }
addExtensionControls(); addExtensionControls();

View File

@ -1,9 +1,7 @@
{ {
"display_name": "poe.com generation", "display_name": "poe.com generation",
"loading_order": 0, "loading_order": 0,
"requires": [ "requires": [],
"poe"
],
"optional": [], "optional": [],
"js": "index.js", "js": "index.js",
"css": "style.css" "css": "style.css"

View File

@ -830,10 +830,10 @@ $(document).ready(function () {
} }
else { else {
openai_settings.push(presetBody); openai_settings.push(presetBody);
openai_setting_names[data.name] = openai_settings.length; openai_setting_names[data.name] = openai_settings.length - 1;
const option = document.createElement('option'); const option = document.createElement('option');
option.selected = true; option.selected = true;
option.value = openai_settings.length; option.value = openai_settings.length - 1;
option.innerText = data.name; option.innerText = data.name;
$('#settings_perset_openai').append(option).trigger('change'); $('#settings_perset_openai').append(option).trigger('change');
} }

View File

@ -2880,7 +2880,7 @@ filter: invert(20%) sepia(100%) saturate(2518%) hue-rotate(353deg) brightness(93
box-shadow: 0 0 20px black; box-shadow: 0 0 20px black;
min-width: 400px; min-width: 400px;
overflow-y: scroll; overflow-y: scroll;
max-height: calc(100vh - 70px); max-height: calc(100svh - 70px);
display: none; display: none;
position: absolute; position: absolute;
left:0; left:0;
@ -3020,7 +3020,7 @@ filter: invert(20%) sepia(100%) saturate(2518%) hue-rotate(353deg) brightness(93
.mes-text {padding-right: 25px;} .mes-text {padding-right: 25px;}
#right-nav-panel { #right-nav-panel {
height: calc(100vh - 70px); height: calc(100svh - 70px);
min-width: 0px; min-width: 0px;
width: calc(100vw - 10px); width: calc(100vw - 10px);
left: 5px !important; left: 5px !important;

View File

@ -35,6 +35,8 @@ const tiktoken = require('@dqbd/tiktoken');
var Client = require('node-rest-client').Client; var Client = require('node-rest-client').Client;
var client = new Client(); var client = new Client();
let poe = require('./poe');
var api_server = "http://0.0.0.0:5000"; var api_server = "http://0.0.0.0:5000";
var api_novelai = "https://api.novelai.net"; var api_novelai = "https://api.novelai.net";
let api_openai = "https://api.openai.com/v1"; let api_openai = "https://api.openai.com/v1";
@ -1614,6 +1616,62 @@ app.post('/deletegroup', jsonParser, async (request, response) => {
return response.send({ ok: true }); return response.send({ ok: true });
}); });
const POE_DEFAULT_BOT = 'a2';
async function getPoeClient(token) {
let client = new poe.Client();
await client.init(token);
return client;
}
app.post('/status_poe', jsonParser, async (request, response) => {
if (!request.body.token) {
return response.sendStatus(400);
}
const client = await getPoeClient(request.body.token);
const botNames = client.get_bot_names();
client.disconnect_ws();
return response.send({'bot_names': botNames});
});
app.post('/purge_poe', jsonParser, async (request, response) => {
if (!request.body.token) {
return response.sendStatus(400);
}
const token = request.body.token;
const bot = request.body.bot ?? POE_DEFAULT_BOT;
const count = request.body.count ?? -1;
const client = await getPoeClient(token);
await client.purge_conversation(bot, count)
client.disconnect_ws();
return response.send({"ok" : true});
});
app.post('/generate_poe', jsonParser, async (request, response) => {
if (!request.body.token || !request.body.prompt) {
return response.sendStatus(400);
}
const token = request.body.token;
const prompt = request.body.prompt;
const bot = request.body.bot ?? POE_DEFAULT_BOT;
const client = await getPoeClient(token);
let reply;
for await (const mes of client.send_message(bot, prompt)) {
reply = mes.text;
}
client.disconnect_ws();
return response.send({'reply': reply});
});
function getThumbnailFolder(type) { function getThumbnailFolder(type) {
let thumbnailFolder; let thumbnailFolder;