diff --git a/README.md b/README.md
index a3fe36e..7d3f97e 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,33 @@
# Quora Poe
-This is a CLI tool to call the Quora Poe API through GraphQL. It is a work in progress, and currently supports the following:
+This is a tool to call the Quora Poe API through GraphQL. It is a work in progress, and currently supports the following:
- Auto login using temporary email, so you don't need to use your own email/phone number.
-- Semi auto login using your own email/phone number, you need to enter the OTP manually.
+- Manual login using your own email/phone number, you need to enter the OTP manually.
- Chat with 4 types of bots (Sage, Claude, ChatGPT, and Dragonfly).
- Stream responses support from the bot.
- Clear the chat history.
+- Auto re-login after session expires (only for auto login).
+- Module support, now you can use this tool as a module.
+
+# Demo's
+## CLI
+
+
+
+
+## Client module (after login)
+
+
+
+
+## Client module (before login)
+
+
+
+
+## Client module (auto re-login after session expires)
+
+
+
## Requirements
- NodeJS 16.0.0 or higher
@@ -19,19 +42,36 @@ npm install
```
## Usage
-
-To start, run:
-
+### Module
+Please see file [`./src/client_auto.ts`](./src/client_auto.ts) or [`./src/client_manual.ts`](./src/client_manual.ts) for example.
+Or you can try to run the following command:
```
-npm start
+node ./dist/client_auto.js
```
+### CLI
+```
+npm run cli
+```
+If you don't want stream responses, you can change the `stream_response` variable in the `config.json` file to `false`.
## TODO List
-- [ ] Make it modular, so it can be used as a library
-- [ ] Add support for re-login after session expires
- [ ] Add support for get chat history
- [ ] Add support for delete message
+## Notes
+- Since I have to work on this project in my free time, I can't guarantee that I will be able to update this project frequently.
+- I'm not have much experience with TypeScript, so if you have any suggestions, best practices, or anything, please let me know or create a pull request.
+- I'm not publishing this tool to NPM yet. If you want to use this tool as a module, you can clone this repo and build it yourself.
+- If you have any questions, please create an issue.
+
## Contributing
+To contribute to this repo, fork it first, then create a new branch, and create a pull request.
+
+## Disclaimer
+This tool is not affiliated with Quora in any way. I am not responsible for any misuse of this tool.
+Don't sue me.
+
+Also, please don't use auto login feature to spam the bot, like creating a lot of accounts for any purpose. I don't want this temporary email service to be banned.
-To contribute to this repo, fork first and create a pull request.
+## License
+[MIT](https://choosealicense.com/licenses/mit/)
\ No newline at end of file
diff --git a/config.example.json b/config.example.json
index fa7f775..b8ea8e3 100644
--- a/config.example.json
+++ b/config.example.json
@@ -1 +1,27 @@
-{"quora_formkey":"","quora_cookie":""}
\ No newline at end of file
+{
+ "stream_response": true,
+ "quora_formkey": "",
+ "quora_cookie": "",
+ "channel_name": "",
+ "app_settings": {
+ "formkey": "",
+ "tchannelData": {
+ "minSeq": "",
+ "channel": "",
+ "channelHash": "",
+ "boxName": "",
+ "baseHost": "",
+ "targetUrl": "",
+ "enableWebsocket": true
+ }
+ },
+ "chat_ids": {
+ "a2": 0,
+ "capybara": 0,
+ "nutria": 0,
+ "chinchilla": 0
+ },
+ "auto_login": true,
+ "email": "",
+ "sid_token": ""
+}
\ No newline at end of file
diff --git a/demos/demo-client-auto-after-login.gif b/demos/demo-client-auto-after-login.gif
new file mode 100644
index 0000000..6d59f1f
Binary files /dev/null and b/demos/demo-client-auto-after-login.gif differ
diff --git a/demos/demo-client-auto-before-login.gif b/demos/demo-client-auto-before-login.gif
new file mode 100644
index 0000000..81d5625
Binary files /dev/null and b/demos/demo-client-auto-before-login.gif differ
diff --git a/demos/demo-client-auto-re-login.gif b/demos/demo-client-auto-re-login.gif
new file mode 100644
index 0000000..2623928
Binary files /dev/null and b/demos/demo-client-auto-re-login.gif differ
diff --git a/demos/demo-converstation-cli.gif b/demos/demo-converstation-cli.gif
new file mode 100644
index 0000000..9aa3d65
Binary files /dev/null and b/demos/demo-converstation-cli.gif differ
diff --git a/dist/cli.js b/dist/cli.js
new file mode 100644
index 0000000..5daf215
--- /dev/null
+++ b/dist/cli.js
@@ -0,0 +1,3 @@
+import ChatBot from "./index.js";
+const bot = new ChatBot();
+await bot.startCli();
diff --git a/dist/client.js b/dist/client.js
new file mode 100644
index 0000000..fb6b744
--- /dev/null
+++ b/dist/client.js
@@ -0,0 +1,11 @@
+import ChatBot from "./index.js";
+const bot = new ChatBot();
+// Used to check if the formkey and cookie is available
+const isFormkeyAvailable = await bot.getCredentials();
+if (!isFormkeyAvailable) {
+ console.log("Formkey and cookie not available");
+ // Set the formkey, cookie and any other data needed and save it into config.json
+ await bot.setCredentials();
+ const chatId = await bot.getChatId("a2");
+ console.log(chatId);
+}
diff --git a/dist/client_auto.js b/dist/client_auto.js
new file mode 100644
index 0000000..08051f8
--- /dev/null
+++ b/dist/client_auto.js
@@ -0,0 +1,26 @@
+import ChatBot from "./index.js";
+const bot = new ChatBot();
+// Used to check if the formkey and cookie is available
+const isFormkeyAvailable = await bot.getCredentials();
+if (!isFormkeyAvailable) {
+ await bot.setCredentials();
+ await bot.subscribe(); // for websocket(stream response) purpose
+ await bot.login("auto");
+}
+const ai = "a2"; // bot list are in config.example.json, key "chat_ids"
+// If you want to clear the chat context, you can use this
+await bot.clearContext(ai);
+// If you want to get the response (with stream), you can use this
+// NOTE that you need to call this before you send the message
+// await getUpdatedSettings(bot.config.channel_name, bot.config.quora_cookie);
+// await bot.subscribe();
+// const ws = await connectWs();
+// If you want to send a message, you can use this
+await bot.sendMsg(ai, "Hello, who are you?");
+// If you want to get the response (without stream), you can use this
+const response = await bot.getResponse(ai);
+console.log(response);
+// // If you want to get the response (with stream), you can use this
+// process.stdout.write("Response: ");
+// await listenWs(ws);
+// console.log('\n');
diff --git a/dist/client_manual.js b/dist/client_manual.js
new file mode 100644
index 0000000..765ec9a
--- /dev/null
+++ b/dist/client_manual.js
@@ -0,0 +1,39 @@
+import ChatBot from "./index.js";
+const bot = new ChatBot();
+// Used to check if the formkey and cookie is available
+const isFormkeyAvailable = await bot.getCredentials();
+if (!isFormkeyAvailable) {
+ console.log("Formkey and cookie not available");
+ // Set the formkey, cookie and any other data needed and save it into config.json
+ await bot.setCredentials();
+ const myEmail = "myemail@mail.com";
+ const signInStatus = await bot.sendVerifCode(null, myEmail);
+ // After you get the verification code, you can use this step to log in
+ // then check signInStatus
+ let loginStatus = "invalid_verification_code";
+ while (loginStatus !== "success") {
+ if (signInStatus === 'user_with_confirmed_phone_number_not_found') {
+ loginStatus = await bot.signUpWithVerificationCode(myEmail, null, 123456); // 123456 is the verification code
+ }
+ else {
+ loginStatus = await bot.signInOrUp(myEmail, null, 123456); // 123456 is the verification code
+ }
+ }
+}
+const ai = "a2"; // bot list are in config.example.json, key "chat_ids"
+// If you want to clear the chat context, you can use this
+await bot.clearContext(ai);
+// If you want to get the response (with stream), you can use this
+// NOTE that you need to call this before you send the message
+// await getUpdatedSettings(bot.config.channel_name, bot.config.quora_cookie);
+// await bot.subscribe();
+// const ws = await connectWs();
+// If you want to send a message, you can use this
+await bot.sendMsg(ai, "Hello, who are you?");
+// If you want to get the response (without stream), you can use this
+const response = await bot.getResponse(ai);
+console.log(response);
+// // If you want to get the response (with stream), you can use this
+// process.stdout.write("Response: ");
+// await listenWs(ws);
+// console.log('\n');
diff --git a/dist/credential.js b/dist/credential.js
index 6c495a6..d5c813f 100644
--- a/dist/credential.js
+++ b/dist/credential.js
@@ -19,7 +19,7 @@ const getUpdatedSettings = async (channelName, pbCookie) => {
const appSettings = await _setting.json(), { tchannelData: { minSeq: minSeq } } = appSettings;
const credentials = JSON.parse(readFileSync("config.json", "utf8"));
credentials.app_settings.tchannelData.minSeq = minSeq;
- writeFile("config.json", JSON.stringify(credentials), function (err) {
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
if (err) {
console.log(err);
}
diff --git a/dist/index.js b/dist/index.js
index ec401f1..861c527 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -3,9 +3,10 @@ import prompts from "prompts";
import ora from "ora";
import * as dotenv from "dotenv";
import { readFileSync, writeFile } from "fs";
-import { scrape, getUpdatedSettings } from "./credential.js";
-import { listenWs, connectWs, disconnectWs } from "./websocket.js";
+import { getUpdatedSettings, scrape } from "./credential.js";
+import { connectWs, disconnectWs, listenWs } from "./websocket.js";
import * as mail from "./mail.js";
+import randomUseragent from 'random-useragent';
dotenv.config();
const spinner = ora({
color: "cyan",
@@ -20,53 +21,66 @@ const queries = {
signUpWithVerificationCodeMutation: readFileSync(gqlDir + "/SignupWithVerificationCodeMutation.graphql", "utf8"),
sendVerificationCodeMutation: readFileSync(gqlDir + "/SendVerificationCodeForLoginMutation.graphql", "utf8"),
};
-let [pbCookie, channelName, appSettings, formkey] = ["", "", "", ""];
class ChatBot {
constructor() {
+ this.config = JSON.parse(readFileSync("config.json", "utf8"));
this.headers = {
'Content-Type': 'application/json',
- 'Accept': '*/*',
'Host': 'poe.com',
- 'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Origin': 'https://poe.com',
+ 'User-Agent': randomUseragent.getRandom(),
};
this.chatId = 0;
this.bot = "";
+ this.reConnectWs = false;
}
async getCredentials() {
- const credentials = JSON.parse(readFileSync("config.json", "utf8"));
- const { quora_formkey, quora_cookie } = credentials;
+ const { quora_formkey, channel_name, quora_cookie } = this.config;
if (quora_formkey.length > 0 && quora_cookie.length > 0) {
- formkey = quora_formkey;
- pbCookie = quora_cookie;
- // For websocket later feature
- channelName = credentials.channel_name;
- appSettings = credentials.app_settings;
- this.headers["poe-formkey"] = formkey;
- this.headers["poe-tchannel"] = channelName;
- this.headers["Cookie"] = pbCookie;
+ this.headers["poe-formkey"] = quora_formkey;
+ this.headers["poe-tchannel"] = channel_name;
+ this.headers["Cookie"] = quora_cookie;
}
return quora_formkey.length > 0 && quora_cookie.length > 0;
}
+ async setChatIds() {
+ const [a2, capybara, nutria, chinchilla] = await Promise.all([
+ this.getChatId("a2"),
+ this.getChatId("capybara"),
+ this.getChatId("nutria"),
+ this.getChatId("chinchilla"),
+ ]);
+ const credentials = JSON.parse(readFileSync("config.json", "utf8"));
+ credentials.chat_ids = {
+ a2,
+ capybara,
+ nutria,
+ chinchilla,
+ };
+ this.config.chat_ids = {
+ a2,
+ capybara,
+ nutria,
+ chinchilla,
+ };
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ }
async setCredentials() {
let result = await scrape();
- const credentials = JSON.parse(readFileSync("config.json", "utf8"));
- credentials.quora_formkey = result.appSettings.formkey;
- credentials.quora_cookie = result.pbCookie;
- // For websocket later feature
- credentials.channel_name = result.channelName;
- credentials.app_settings = result.appSettings;
+ this.config.quora_formkey = result.appSettings.formkey;
+ this.config.quora_cookie = result.pbCookie;
+ this.config.channel_name = result.channelName;
+ this.config.app_settings = result.appSettings;
// set value
- formkey = result.appSettings.formkey;
- pbCookie = result.pbCookie;
- // For websocket later feature
- channelName = result.channelName;
- appSettings = result.appSettings;
- this.headers["poe-formkey"] = formkey;
- this.headers["poe-tchannel"] = channelName;
- this.headers["Cookie"] = pbCookie;
- writeFile("config.json", JSON.stringify(credentials), function (err) {
+ this.headers["poe-formkey"] = this.config.quora_formkey;
+ this.headers["poe-tchannel"] = this.config.channel_name;
+ this.headers["Cookie"] = this.config.quora_cookie;
+ writeFile("config.json", JSON.stringify(this.config, null, 4), function (err) {
if (err) {
console.log(err);
}
@@ -91,89 +105,7 @@ class ChatBot {
};
await this.makeRequest(query);
}
- async start() {
- const isFormkeyAvailable = await this.getCredentials();
- if (!isFormkeyAvailable) {
- const { mode } = await prompts({
- type: "select",
- name: "mode",
- message: "Select",
- choices: [
- { title: "Auto [This will use temp email to get Verification Code]", value: "auto" },
- { title: "Semi-Auto [Use you own email/phone number]", value: "semi" },
- { title: "exit", value: "exit" }
- ],
- });
- if (mode === "exit") {
- process.exit(0);
- }
- await this.setCredentials();
- await this.subscribe();
- await this.login(mode);
- }
- await getUpdatedSettings(channelName, pbCookie);
- await this.subscribe();
- const ws = await connectWs();
- const { bot } = await prompts({
- type: "select",
- name: "bot",
- message: "Select",
- choices: [
- { 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" },
- ],
- });
- await this.getChatId(bot);
- let helpMsg = "Available commands: !help !exit, !clear, !submit" +
- "\n!help - show this message" +
- "\n!exit - exit the chat" +
- "\n!clear - clear chat history" +
- "\n!submit - submit prompt";
- await this.clearContext();
- console.log(helpMsg);
- let submitedPrompt = "";
- while (true) {
- const { prompt } = await prompts({
- type: "text",
- name: "prompt",
- message: "Ask:",
- });
- if (prompt.length > 0) {
- if (prompt === "!help") {
- console.log(helpMsg);
- }
- else if (prompt === "!exit") {
- await disconnectWs(ws);
- process.exit(0);
- }
- else if (prompt === "!clear") {
- spinner.start("Clearing chat history...");
- await this.clearContext();
- submitedPrompt = "";
- spinner.stop();
- console.log("Chat history cleared");
- }
- else if (prompt === "!submit") {
- if (submitedPrompt.length === 0) {
- console.log("No prompt to submit");
- continue;
- }
- await this.sendMsg(submitedPrompt);
- process.stdout.write("Response: ");
- await listenWs(ws);
- console.log('\n');
- submitedPrompt = "";
- }
- else {
- submitedPrompt += prompt + "\n";
- }
- }
- }
- }
async makeRequest(request) {
- this.headers["Content-Length"] = Buffer.byteLength(JSON.stringify(request), 'utf8');
const response = await fetch('https://poe.com/api/gql_POST', {
method: 'POST',
headers: this.headers,
@@ -248,6 +180,7 @@ class ChatBot {
}
spinner.stop();
}
+ await this.setChatIds();
}
async signInOrUp(phoneNumber, email, verifyCode) {
console.log("Signing in/up...");
@@ -305,9 +238,19 @@ class ChatBot {
return status;
}
catch (e) {
- throw e;
+ console.log("Error sending verification code, please try again " + e);
+ await this.resetConfig();
}
}
+ async resetConfig() {
+ const defaultConfig = JSON.parse(readFileSync("config.example.json", "utf8"));
+ console.log("Resetting config...");
+ writeFile("config.json", JSON.stringify(defaultConfig, null, 4), function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ }
async getChatId(bot) {
try {
const { data: { chatOfBot: { chatId } } } = await this.makeRequest({
@@ -318,65 +261,235 @@ class ChatBot {
});
this.chatId = chatId;
this.bot = bot;
+ return chatId;
}
catch (e) {
+ console.log(e);
+ await this.resetConfig();
throw new Error("Could not get chat id, invalid formkey or cookie! Please remove the quora_formkey value from the config.json file and try again.");
}
}
- async clearContext() {
+ async clearContext(bot) {
try {
- await this.makeRequest({
+ const data = await this.makeRequest({
query: `${queries.addMessageBreakMutation}`,
- variables: { chatId: this.chatId },
+ 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..");
+ await this.reLogin();
+ await this.clearContext(bot);
+ }
+ return data;
}
catch (e) {
- throw new Error("Could not clear context");
+ this.reConnectWs = true; // for websocket purpose
+ console.log("ON CATCH! Could not clear context! Trying to reLogin..");
+ await this.reLogin();
+ await this.clearContext(bot);
+ return e;
}
}
- async sendMsg(query) {
+ async sendMsg(bot, query) {
try {
- await this.makeRequest({
+ const data = await this.makeRequest({
query: `${queries.addHumanMessageMutation}`,
variables: {
- bot: this.bot,
- chatId: this.chatId,
+ bot: bot,
+ chatId: this.config.chat_ids[bot],
query: query,
source: null,
withChatBreak: false
},
});
+ if (!data.data) {
+ this.reConnectWs = true; // for cli websocket purpose
+ console.log("Could not send message! Trying to reLogin..");
+ await this.reLogin();
+ await this.sendMsg(bot, query);
+ }
+ return data;
}
catch (e) {
- throw new Error("Could not send message");
+ this.reConnectWs = true; // for cli websocket purpose
+ console.log("ON CATCH! Could not send message! Trying to reLogin..");
+ await this.reLogin();
+ await this.sendMsg(bot, query);
+ return e;
}
}
- // Responce without stream
- async getResponse() {
+ 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: 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 reLogin() {
+ await this.setCredentials();
+ if (!this.config.email || !this.config.sid_token) {
+ console.log("No email or sid_token found, creating new email and sid_token..");
+ const { email, sid_token } = await mail.createNewEmail();
+ this.config.email = email;
+ this.config.sid_token = sid_token;
+ }
+ const status = await this.sendVerifCode(null, this.config.email);
+ spinner.start("Waiting for OTP code...");
+ const otp_code = await mail.getPoeOTPCode(this.config.sid_token);
+ spinner.stop();
+ if (status === 'user_with_confirmed_email_not_found') {
+ await this.signUpWithVerificationCode(null, this.config.email, otp_code);
+ }
+ else {
+ await this.signInOrUp(null, this.config.email, otp_code);
+ }
+ const newConfig = JSON.parse(readFileSync("config.json", "utf8"));
+ this.config = newConfig;
+ this.headers["poe-formkey"] = newConfig.quora_formkey;
+ this.headers["poe-tchannel"] = newConfig.channel_name;
+ this.headers["Cookie"] = newConfig.quora_cookie;
+ await this.setChatIds();
+ }
+ async startCli() {
+ const isFormkeyAvailable = await this.getCredentials();
+ if (!isFormkeyAvailable) {
+ const { mode } = await prompts({
+ type: "select",
+ name: "mode",
+ message: "Select",
+ choices: [
+ { title: "Auto [This will use temp email to get Verification Code]", value: "auto" },
+ { title: "Semi-Auto [Use you own email/phone number]", value: "semi" },
+ { title: "exit", value: "exit" }
+ ],
+ });
+ if (mode === "exit") {
+ process.exit(0);
+ }
+ await this.setCredentials();
+ await this.subscribe();
+ await this.login(mode);
+ }
+ let ws;
+ if (this.config.stream_response) {
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie);
+ await this.subscribe();
+ ws = await connectWs();
+ }
+ const { bot } = await prompts({
+ type: "select",
+ name: "bot",
+ message: "Select",
+ choices: [
+ { 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" },
+ ],
+ });
+ this.chatId = this.config.chat_ids[bot];
+ this.bot = bot;
+ let helpMsg = "Available commands: !help !exit, !clear, !submit" +
+ "\n!help - show this message" +
+ "\n!exit - exit the chat" +
+ "\n!clear - clear chat history" +
+ "\n!submit - submit prompt";
+ // await this.clearContext(this.chatId);
+ console.log(helpMsg);
+ let submitedPrompt = "";
while (true) {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- let response = await this.makeRequest({
- query: `${queries.chatPaginationQuery}`,
- variables: {
- before: null,
- bot: this.bot,
- last: 1,
- },
+ const { prompt } = await prompts({
+ type: "text",
+ name: "prompt",
+ message: "Ask:",
});
- 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 === this.bot) {
- break;
+ if (prompt.length > 0) {
+ if (prompt === "!help") {
+ console.log(helpMsg);
+ }
+ else if (prompt === "!exit") {
+ process.exit(0);
+ }
+ else if (prompt === "!clear") {
+ spinner.start("Clearing chat history...");
+ await this.clearContext(bot);
+ if (this.config.stream_response) {
+ if (this.reConnectWs) {
+ await disconnectWs(ws);
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie);
+ await this.subscribe();
+ ws = await connectWs();
+ this.reConnectWs = false;
+ }
+ }
+ submitedPrompt = "";
+ spinner.stop();
+ console.log("Chat history cleared");
+ }
+ else if (prompt === "!submit") {
+ if (submitedPrompt.length === 0) {
+ console.log("No prompt to submit");
+ continue;
+ }
+ await this.sendMsg(this.bot, submitedPrompt);
+ if (this.config.stream_response) {
+ if (this.reConnectWs) {
+ await disconnectWs(ws);
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie);
+ await this.subscribe();
+ ws = await connectWs();
+ this.reConnectWs = false;
+ }
+ process.stdout.write("Response: ");
+ await listenWs(ws);
+ console.log('\n');
+ }
+ else {
+ spinner.start("Waiting for response...");
+ let response = await this.getResponse(this.bot);
+ spinner.stop();
+ console.log(response.data);
+ }
+ submitedPrompt = "";
+ }
+ else {
+ submitedPrompt += prompt + "\n";
+ }
}
}
- return text;
}
}
-const chatBot = new ChatBot();
-await chatBot.start();
+export default ChatBot;
diff --git a/dist/mail.js b/dist/mail.js
index 1cdac77..5221d8f 100644
--- a/dist/mail.js
+++ b/dist/mail.js
@@ -7,7 +7,7 @@ const createNewEmail = async () => {
const credentials = JSON.parse(readFileSync("config.json", "utf8"));
credentials.email = response_json.email_addr;
credentials.sid_token = response_json.sid_token;
- writeFile("config.json", JSON.stringify(credentials), function (err) {
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
if (err) {
console.log(err);
}
@@ -30,7 +30,7 @@ const getLatestEmail = async (sid_token) => {
let emailList = await getEmailList(sid_token);
let emailListLength = emailList.list.length;
while (true) {
- await new Promise(r => setTimeout(r, 10000));
+ await new Promise(r => setTimeout(r, 15000));
emailList = await getEmailList(sid_token);
emailListLength = emailList.list.length;
if (emailListLength > 1) {
diff --git a/package-lock.json b/package-lock.json
index 8464571..998696e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "poe-node",
- "version": "1.3.1",
+ "version": "1.5.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "poe-node",
- "version": "1.3.1",
+ "version": "1.5.0",
"license": "ISC",
"dependencies": {
"cross-fetch": "^3.1.5",
@@ -14,6 +14,7 @@
"dotenv": "^16.0.3",
"ora": "^6.1.2",
"prompts": "^2.4.2",
+ "random-useragent": "^0.5.0",
"ws": "^8.12.1"
},
"devDependencies": {
@@ -234,6 +235,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+ },
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -313,6 +319,25 @@
"node": ">= 6"
}
},
+ "node_modules/random-seed": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/random-seed/-/random-seed-0.3.0.tgz",
+ "integrity": "sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==",
+ "dependencies": {
+ "json-stringify-safe": "^5.0.1"
+ },
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
+ "node_modules/random-useragent": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/random-useragent/-/random-useragent-0.5.0.tgz",
+ "integrity": "sha512-FUMkqVdZeoSff5tErNL3FFGYXElDWZ1bEuedhm5u9MdCFwANriJWbHvDRYrLTOzp/fBsBGu5J1cWtDgifa97aQ==",
+ "dependencies": {
+ "random-seed": "^0.3.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
@@ -573,6 +598,11 @@
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
"integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="
},
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="
+ },
"kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
@@ -625,6 +655,22 @@
"sisteransi": "^1.0.5"
}
},
+ "random-seed": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/random-seed/-/random-seed-0.3.0.tgz",
+ "integrity": "sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==",
+ "requires": {
+ "json-stringify-safe": "^5.0.1"
+ }
+ },
+ "random-useragent": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/random-useragent/-/random-useragent-0.5.0.tgz",
+ "integrity": "sha512-FUMkqVdZeoSff5tErNL3FFGYXElDWZ1bEuedhm5u9MdCFwANriJWbHvDRYrLTOzp/fBsBGu5J1cWtDgifa97aQ==",
+ "requires": {
+ "random-seed": "^0.3.0"
+ }
+ },
"readable-stream": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz",
diff --git a/package.json b/package.json
index 59b515c..0fd4cab 100644
--- a/package.json
+++ b/package.json
@@ -1,17 +1,30 @@
{
"name": "poe-node",
- "version": "1.4.0",
- "description": "A CLI tool to call the Quora Poe API through GraphQL",
+ "version": "1.5.0",
+ "description": "Work as CLI or module to call the Quora Poe API through GraphQL",
"main": "index.js",
"type": "module",
"scripts": {
"compile": "tsc",
"dev": "npm run compile && node ./dist/index.js",
- "start": "node ./dist/index.js",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "cli": "node ./dist/cli.js"
},
- "keywords": [],
- "author": "",
+ "keywords": [
+ "poe",
+ "quora",
+ "graphql",
+ "api",
+ "cli",
+ "library",
+ "chatbot",
+ "bot",
+ "openai",
+ "gpt3",
+ "gpt-3",
+ "gpt4",
+ "chatgpt"
+ ],
+ "author": "Ramdani",
"license": "ISC",
"dependencies": {
"cross-fetch": "^3.1.5",
@@ -19,6 +32,7 @@
"dotenv": "^16.0.3",
"ora": "^6.1.2",
"prompts": "^2.4.2",
+ "random-useragent": "^0.5.0",
"ws": "^8.12.1"
},
"devDependencies": {
diff --git a/src/cli.ts b/src/cli.ts
new file mode 100644
index 0000000..7ee64da
--- /dev/null
+++ b/src/cli.ts
@@ -0,0 +1,4 @@
+import ChatBot from "./index.js";
+
+const bot = new ChatBot();
+await bot.startCli();
\ No newline at end of file
diff --git a/src/client_auto.ts b/src/client_auto.ts
new file mode 100644
index 0000000..72fc55e
--- /dev/null
+++ b/src/client_auto.ts
@@ -0,0 +1,37 @@
+import ChatBot from "./index.js";
+import {getUpdatedSettings} from "./credential.js";
+import {connectWs, listenWs} from "./websocket.js";
+
+const bot = new ChatBot();
+
+// Used to check if the formkey and cookie is available
+const isFormkeyAvailable = await bot.getCredentials();
+
+if (!isFormkeyAvailable) {
+ await bot.setCredentials();
+ await bot.subscribe() // for websocket(stream response) purpose
+ await bot.login("auto");
+}
+
+const ai = "a2"; // bot list are in config.example.json, key "chat_ids"
+
+// If you want to clear the chat context, you can use this
+await bot.clearContext(ai);
+
+// If you want to get the response (with stream), you can use this
+// NOTE that you need to call this before you send the message
+// await getUpdatedSettings(bot.config.channel_name, bot.config.quora_cookie);
+// await bot.subscribe();
+// const ws = await connectWs();
+
+// If you want to send a message, you can use this
+await bot.sendMsg(ai, "Hello, who are you?")
+
+// If you want to get the response (without stream), you can use this
+const response = await bot.getResponse(ai);
+console.log(response);
+
+// // If you want to get the response (with stream), you can use this
+// process.stdout.write("Response: ");
+// await listenWs(ws);
+// console.log('\n');
\ No newline at end of file
diff --git a/src/client_manual.ts b/src/client_manual.ts
new file mode 100644
index 0000000..6f2f7ca
--- /dev/null
+++ b/src/client_manual.ts
@@ -0,0 +1,53 @@
+import ChatBot from "./index.js";
+import {getUpdatedSettings} from "./credential.js";
+import {connectWs, listenWs} from "./websocket.js";
+
+const bot = new ChatBot();
+
+// Used to check if the formkey and cookie is available
+const isFormkeyAvailable = await bot.getCredentials();
+
+if (!isFormkeyAvailable) {
+ console.log("Formkey and cookie not available");
+
+ // Set the formkey, cookie and any other data needed and save it into config.json
+ await bot.setCredentials();
+
+ const myEmail = "myemail@mail.com"
+ const signInStatus = await bot.sendVerifCode(null, myEmail);
+
+ // After you get the verification code, you can use this step to log in
+ // then check signInStatus
+ let loginStatus = "invalid_verification_code";
+ while (loginStatus !== "success") {
+ if (signInStatus === 'user_with_confirmed_phone_number_not_found') {
+ loginStatus = await bot.signUpWithVerificationCode(myEmail, null, 123456); // 123456 is the verification code
+ } else {
+ loginStatus = await bot.signInOrUp(myEmail, null, 123456); // 123456 is the verification code
+ }
+ }
+}
+
+
+const ai = "a2"; // bot list are in config.example.json, key "chat_ids"
+
+// If you want to clear the chat context, you can use this
+await bot.clearContext(ai);
+
+// If you want to get the response (with stream), you can use this
+// NOTE that you need to call this before you send the message
+// await getUpdatedSettings(bot.config.channel_name, bot.config.quora_cookie);
+// await bot.subscribe();
+// const ws = await connectWs();
+
+// If you want to send a message, you can use this
+await bot.sendMsg(ai, "Hello, who are you?")
+
+// If you want to get the response (without stream), you can use this
+const response = await bot.getResponse(ai);
+console.log(response);
+
+// // If you want to get the response (with stream), you can use this
+// process.stdout.write("Response: ");
+// await listenWs(ws);
+// console.log('\n');
\ No newline at end of file
diff --git a/src/credential.ts b/src/credential.ts
index 427064f..3e5cccc 100644
--- a/src/credential.ts
+++ b/src/credential.ts
@@ -28,7 +28,7 @@ const getUpdatedSettings = async (channelName, pbCookie) => {
{ tchannelData: { minSeq: minSeq } } = appSettings;
const credentials = JSON.parse(readFileSync("config.json", "utf8"));
credentials.app_settings.tchannelData.minSeq = minSeq
- writeFile("config.json", JSON.stringify(credentials), function (err) {
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
if (err) {
console.log(err);
}
diff --git a/src/index.ts b/src/index.ts
index cdec9ad..e6e0137 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,9 +3,10 @@ import prompts from "prompts";
import ora from "ora";
import * as dotenv from "dotenv";
import {readFileSync, writeFile} from "fs";
-import {scrape, getUpdatedSettings} from "./credential.js";
-import {listenWs, connectWs, disconnectWs} from "./websocket.js";
+import {getUpdatedSettings, scrape} from "./credential.js";
+import {connectWs, disconnectWs, listenWs} from "./websocket.js";
import * as mail from "./mail.js";
+import randomUseragent from 'random-useragent'
dotenv.config();
@@ -25,63 +26,83 @@ const queries = {
sendVerificationCodeMutation: readFileSync(gqlDir + "/SendVerificationCodeForLoginMutation.graphql", "utf8"),
};
-let [pbCookie, channelName, appSettings, formkey] = ["", "", "", ""];
-
class ChatBot {
+ public config = JSON.parse(readFileSync("config.json", "utf8"));
+
private headers = {
'Content-Type': 'application/json',
- 'Accept': '*/*',
'Host': 'poe.com',
- 'Accept-Encoding': 'gzip, deflate, br',
'Connection': 'keep-alive',
'Origin': 'https://poe.com',
+ 'User-Agent': randomUseragent.getRandom(),
}
- private chatId: number = 0;
- private bot: string = "";
+ public chatId: number = 0;
+ public bot: string = "";
- private async getCredentials() {
- const credentials = JSON.parse(readFileSync("config.json", "utf8"));
- const {quora_formkey, quora_cookie} = credentials;
+ public reConnectWs = false;
+
+ public async getCredentials() {
+ const {quora_formkey, channel_name, quora_cookie} = this.config;
if (quora_formkey.length > 0 && quora_cookie.length > 0) {
- formkey = quora_formkey;
- pbCookie = quora_cookie;
- // For websocket later feature
- channelName = credentials.channel_name;
- appSettings = credentials.app_settings;
- this.headers["poe-formkey"] = formkey;
- this.headers["poe-tchannel"] = channelName;
- this.headers["Cookie"] = pbCookie;
+ this.headers["poe-formkey"] = quora_formkey;
+ this.headers["poe-tchannel"] = channel_name;
+ this.headers["Cookie"] = quora_cookie;
}
return quora_formkey.length > 0 && quora_cookie.length > 0;
}
- private async setCredentials() {
- let result = await scrape();
+ public async setChatIds() {
+ const [a2, capybara, nutria, chinchilla] = await Promise.all([
+ this.getChatId("a2"),
+ this.getChatId("capybara"),
+ this.getChatId("nutria"),
+ this.getChatId("chinchilla"),
+ ]);
+
const credentials = JSON.parse(readFileSync("config.json", "utf8"));
- credentials.quora_formkey = result.appSettings.formkey;
- credentials.quora_cookie = result.pbCookie;
- // For websocket later feature
- credentials.channel_name = result.channelName;
- credentials.app_settings = result.appSettings;
+
+ credentials.chat_ids = {
+ a2,
+ capybara,
+ nutria,
+ chinchilla,
+ };
+
+ this.config.chat_ids = {
+ a2,
+ capybara,
+ nutria,
+ chinchilla,
+ }
+
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ }
+
+ public async setCredentials() {
+ let result = await scrape();
+ this.config.quora_formkey = result.appSettings.formkey;
+ this.config.quora_cookie = result.pbCookie;
+ this.config.channel_name = result.channelName;
+ this.config.app_settings = result.appSettings;
// set value
- formkey = result.appSettings.formkey;
- pbCookie = result.pbCookie;
- // For websocket later feature
- channelName = result.channelName;
- appSettings = result.appSettings;
- this.headers["poe-formkey"] = formkey;
- this.headers["poe-tchannel"] = channelName;
- this.headers["Cookie"] = pbCookie;
- writeFile("config.json", JSON.stringify(credentials), function (err) {
+ this.headers["poe-formkey"] = this.config.quora_formkey;
+ this.headers["poe-tchannel"] = this.config.channel_name;
+ this.headers["Cookie"] = this.config.quora_cookie;
+
+ writeFile("config.json", JSON.stringify(this.config, null, 4), function (err) {
if (err) {
console.log(err);
}
});
}
- private async subscribe() {
+ public async subscribe() {
const query = {
queryName: 'subscriptionsMutation',
variables: {
@@ -102,94 +123,7 @@ class ChatBot {
await this.makeRequest(query);
}
- public async start() {
- const isFormkeyAvailable = await this.getCredentials();
- if (!isFormkeyAvailable) {
- const {mode} = await prompts({
- type: "select",
- name: "mode",
- message: "Select",
- choices: [
- {title: "Auto [This will use temp email to get Verification Code]", value: "auto"},
- {title: "Semi-Auto [Use you own email/phone number]", value: "semi"},
- {title: "exit", value: "exit"}
- ],
- });
-
- if (mode === "exit") {
- process.exit(0);
- }
-
- await this.setCredentials();
- await this.subscribe();
- await this.login(mode);
- }
-
- await getUpdatedSettings(channelName, pbCookie);
- await this.subscribe();
- const ws = await connectWs();
- const {bot} = await prompts({
- type: "select",
- name: "bot",
- message: "Select",
- choices: [
- {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"},
- ],
- });
-
- await this.getChatId(bot);
-
- let helpMsg = "Available commands: !help !exit, !clear, !submit" +
- "\n!help - show this message" +
- "\n!exit - exit the chat" +
- "\n!clear - clear chat history" +
- "\n!submit - submit prompt";
-
- await this.clearContext();
- console.log(helpMsg)
- let submitedPrompt = "";
- while (true) {
- const {prompt} = await prompts({
- type: "text",
- name: "prompt",
- message: "Ask:",
- });
-
- if (prompt.length > 0) {
- if (prompt === "!help") {
- console.log(helpMsg);
- } else if (prompt === "!exit") {
- await disconnectWs(ws);
- process.exit(0);
- } else if (prompt === "!clear") {
- spinner.start("Clearing chat history...");
- await this.clearContext();
- submitedPrompt = "";
- spinner.stop();
- console.log("Chat history cleared");
- } else if (prompt === "!submit") {
- if (submitedPrompt.length === 0) {
- console.log("No prompt to submit");
- continue;
- }
- await this.sendMsg(submitedPrompt);
- process.stdout.write("Response: ");
- await listenWs(ws);
- console.log('\n');
- submitedPrompt = "";
- } else {
- submitedPrompt += prompt + "\n";
- }
- }
- }
- }
-
- private async makeRequest(request) {
- this.headers["Content-Length"] = Buffer.byteLength(JSON.stringify(request), 'utf8');
-
+ public async makeRequest(request) {
const response = await fetch('https://poe.com/api/gql_POST', {
method: 'POST',
headers: this.headers,
@@ -264,9 +198,11 @@ class ChatBot {
}
spinner.stop();
}
+
+ await this.setChatIds();
}
- private async signInOrUp(phoneNumber, email, verifyCode) {
+ public async signInOrUp(phoneNumber, email, verifyCode) {
console.log("Signing in/up...")
console.log("Phone number: " + phoneNumber)
console.log("Email: " + email)
@@ -291,7 +227,7 @@ class ChatBot {
}
}
- private async signUpWithVerificationCode(phoneNumber, email, verifyCode) {
+ public async signUpWithVerificationCode(phoneNumber, email, verifyCode) {
console.log("Signing in/up...")
console.log("Phone number: " + phoneNumber)
console.log("Email: " + email)
@@ -316,7 +252,7 @@ class ChatBot {
}
}
- private async sendVerifCode(phoneNumber, email) {
+ public async sendVerifCode(phoneNumber, email) {
try {
// status error case: success, user_with_confirmed_phone_number_not_found, user_with_confirmed_email_not_found
const {data: {sendVerificationCode: {status}}} = await this.makeRequest({
@@ -329,11 +265,22 @@ class ChatBot {
console.log("Verification code sent. Status: " + status)
return status;
} catch (e) {
- throw e;
+ console.log("Error sending verification code, please try again " + e)
+ await this.resetConfig();
}
}
- private async getChatId(bot: string) {
+ public async resetConfig() {
+ const defaultConfig = JSON.parse(readFileSync("config.example.json", "utf8"));
+ console.log("Resetting config...")
+ writeFile("config.json", JSON.stringify(defaultConfig, null, 4), function (err) {
+ if (err) {
+ console.log(err);
+ }
+ });
+ }
+
+ public async getChatId(bot: string) {
try {
const {data: {chatOfBot: {chatId}}} = await this.makeRequest({
query: `${queries.chatViewQuery}`,
@@ -343,67 +290,241 @@ class ChatBot {
});
this.chatId = chatId;
this.bot = bot;
+ return chatId;
} catch (e) {
+ console.log(e)
+ await this.resetConfig();
throw new Error("Could not get chat id, invalid formkey or cookie! Please remove the quora_formkey value from the config.json file and try again.");
}
}
- private async clearContext() {
+ public async clearContext(bot: string) {
try {
- await this.makeRequest({
+ const data = await this.makeRequest({
query: `${queries.addMessageBreakMutation}`,
- variables: {chatId: this.chatId},
+ 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..");
+ await this.reLogin();
+ await this.clearContext(bot);
+ }
+ return data
} catch (e) {
- throw new Error("Could not clear context");
+ this.reConnectWs = true; // for websocket purpose
+ console.log("ON CATCH! Could not clear context! Trying to reLogin..");
+ await this.reLogin();
+ await this.clearContext(bot);
+ return e
}
}
- private async sendMsg(query: string) {
+ public async sendMsg(bot: string, query: string) {
try {
- await this.makeRequest({
+ const data = await this.makeRequest({
query: `${queries.addHumanMessageMutation}`,
variables: {
- bot: this.bot,
- chatId: this.chatId,
+ bot: bot,
+ chatId: this.config.chat_ids[bot],
query: query,
source: null,
withChatBreak: false
},
});
+
+ if (!data.data) {
+ this.reConnectWs = true; // for cli websocket purpose
+ console.log("Could not send message! Trying to reLogin..");
+ await this.reLogin();
+ await this.sendMsg(bot, query);
+ }
+ return data
} catch (e) {
- throw new Error("Could not send message");
+ this.reConnectWs = true; // for cli websocket purpose
+ console.log("ON CATCH! Could not send message! Trying to reLogin..");
+ await this.reLogin();
+ await this.sendMsg(bot, query);
+ return e
}
}
- // Responce without stream
- private async getResponse(): Promise {
+ public async getResponse(bot: string): Promise {
let text: string
let state: string
let authorNickname: string
+ try {
+ while (true) {
+ await new Promise((resolve) => setTimeout(resolve, 2000));
+ let response = await this.makeRequest({
+ query: `${queries.chatPaginationQuery}`,
+ variables: {
+ before: null,
+ bot: 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,
+ };
+ }
+
+ public async reLogin() {
+ await this.setCredentials();
+ if (!this.config.email || !this.config.sid_token) {
+ console.log("No email or sid_token found, creating new email and sid_token..")
+ const {email, sid_token} = await mail.createNewEmail()
+ this.config.email = email;
+ this.config.sid_token = sid_token;
+ }
+ const status = await this.sendVerifCode(null, this.config.email);
+ spinner.start("Waiting for OTP code...");
+ const otp_code = await mail.getPoeOTPCode(this.config.sid_token);
+ spinner.stop();
+ if (status === 'user_with_confirmed_email_not_found') {
+ await this.signUpWithVerificationCode(null, this.config.email, otp_code)
+ } else {
+ await this.signInOrUp(null, this.config.email, otp_code)
+ }
+ const newConfig = JSON.parse(readFileSync("config.json", "utf8"));
+ this.config = newConfig;
+ this.headers["poe-formkey"] = newConfig.quora_formkey;
+ this.headers["poe-tchannel"] = newConfig.channel_name;
+ this.headers["Cookie"] = newConfig.quora_cookie;
+ await this.setChatIds();
+ }
+
+ public async startCli() {
+ const isFormkeyAvailable = await this.getCredentials();
+ if (!isFormkeyAvailable) {
+ const {mode} = await prompts({
+ type: "select",
+ name: "mode",
+ message: "Select",
+ choices: [
+ {title: "Auto [This will use temp email to get Verification Code]", value: "auto"},
+ {title: "Semi-Auto [Use you own email/phone number]", value: "semi"},
+ {title: "exit", value: "exit"}
+ ],
+ });
+
+ if (mode === "exit") {
+ process.exit(0);
+ }
+
+ await this.setCredentials();
+ await this.subscribe();
+ await this.login(mode);
+ }
+
+ let ws :any;
+ if (this.config.stream_response) {
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie);
+ await this.subscribe();
+ ws = await connectWs();
+ }
+ const {bot} = await prompts({
+ type: "select",
+ name: "bot",
+ message: "Select",
+ choices: [
+ {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"},
+ ],
+ });
+
+ this.chatId = this.config.chat_ids[bot];
+ this.bot = bot;
+
+ let helpMsg = "Available commands: !help !exit, !clear, !submit" +
+ "\n!help - show this message" +
+ "\n!exit - exit the chat" +
+ "\n!clear - clear chat history" +
+ "\n!submit - submit prompt";
+
+ // await this.clearContext(this.chatId);
+ console.log(helpMsg)
+ let submitedPrompt = "";
while (true) {
- await new Promise((resolve) => setTimeout(resolve, 2000));
- let response = await this.makeRequest({
- query: `${queries.chatPaginationQuery}`,
- variables: {
- before: null,
- bot: this.bot,
- last: 1,
- },
+ const {prompt} = await prompts({
+ type: "text",
+ name: "prompt",
+ message: "Ask:",
});
- 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 === this.bot) {
- break;
+
+ if (prompt.length > 0) {
+ if (prompt === "!help") {
+ console.log(helpMsg);
+ } else if (prompt === "!exit") {
+ process.exit(0);
+ } else if (prompt === "!clear") {
+ spinner.start("Clearing chat history...");
+ await this.clearContext(bot);
+ if (this.config.stream_response) {
+ if (this.reConnectWs) {
+ await disconnectWs(ws);
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie)
+ await this.subscribe();
+ ws = await connectWs();
+ this.reConnectWs = false;
+ }
+ }
+ submitedPrompt = "";
+ spinner.stop();
+ console.log("Chat history cleared");
+ } else if (prompt === "!submit") {
+ if (submitedPrompt.length === 0) {
+ console.log("No prompt to submit");
+ continue;
+ }
+ await this.sendMsg(this.bot, submitedPrompt);
+ if (this.config.stream_response) {
+ if (this.reConnectWs) {
+ await disconnectWs(ws);
+ await getUpdatedSettings(this.config.channel_name, this.config.quora_cookie)
+ await this.subscribe();
+ ws = await connectWs();
+ this.reConnectWs = false;
+ }
+ process.stdout.write("Response: ");
+ await listenWs(ws);
+ console.log('\n');
+ } else {
+ spinner.start("Waiting for response...");
+ let response = await this.getResponse(this.bot);
+ spinner.stop();
+ console.log(response.data);
+ }
+ submitedPrompt = "";
+ } else {
+ submitedPrompt += prompt + "\n";
+ }
}
}
- return text;
}
}
-const chatBot = new ChatBot();
-await chatBot.start();
+export default ChatBot;
diff --git a/src/mail.ts b/src/mail.ts
index 60cbde5..5666c8c 100644
--- a/src/mail.ts
+++ b/src/mail.ts
@@ -9,7 +9,7 @@ const createNewEmail = async () => {
const credentials = JSON.parse(readFileSync("config.json", "utf8"));
credentials.email = response_json.email_addr;
credentials.sid_token = response_json.sid_token;
- writeFile("config.json", JSON.stringify(credentials), function(err) {
+ writeFile("config.json", JSON.stringify(credentials, null, 4), function (err) {
if (err) {
console.log(err);
}
@@ -34,7 +34,7 @@ const getLatestEmail = async (sid_token) => {
let emailList = await getEmailList(sid_token);
let emailListLength = emailList.list.length;
while (true) {
- await new Promise(r => setTimeout(r, 10000));
+ await new Promise(r => setTimeout(r, 15000));
emailList = await getEmailList(sid_token);
emailListLength = emailList.list.length;
if (emailListLength > 1) {