Skip to content
This repository has been archived by the owner on Aug 18, 2023. It is now read-only.

Commit

Permalink
Merge pull request #4 from muharamdani/refactor/automation-login
Browse files Browse the repository at this point in the history
Refactor/automation login
  • Loading branch information
muharamdani authored Mar 7, 2023
2 parents c0979b4 + 26bb4d5 commit a5b8fcf
Show file tree
Hide file tree
Showing 9 changed files with 452 additions and 2,393 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
QUORA_FORMKEY=your_formkey
MB_COOKIE=your_cookie
FORMKEY=your_formkey
COOKIE=your_cookie
23 changes: 7 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# 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:
- Auto login using temporary phone number, so you don't need to use your own email/phone number.
- 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.
- Manually login using formkey and cookie, set in the .env file on QUORA_FORMKEY and QUORA_COOKIE.
- Manually login using formkey and cookie, set in the .env file on FORMKEY and COOKIE.
- Chat with 4 types of bots (Sage, Claude, ChatGPT, and Dragonfly).
- Clear the chat history.

Expand All @@ -27,24 +27,15 @@ npm start

[Only for manual login]
To use this API, you will need to have the following:
- Quora-Formkey: This is obtained by logging in to Quora.com, viewing the page source, and finding the "formkey" dictionary key.
- Cookie: 'm-b=xxxx' - This is the value of the cookie with the key m-b, which is present in the list of cookies used on Quora.com, you can simply inspect cookies in Chrome to get it.
- Quora-Formkey: This is obtained by **logging in** to quora.com, and open https://www.quora.com/poe_api/settings, copy the value of the formkey field.
- Cookie: 'm-b=xxxx' - This is the value of the cookie with the key m-b, which is present in the list of cookies used on quora.com, you can simply inspect cookies in Chrome, open chrome dev tools-->Application tab-->Open Cookies dropdwon .
- Put the above two in a .env file in the root directory of the project

Note: Next plan is to add stream support, so you can get tha chat responses in real time.

## Dependencies
- @apollo/client
- @apollo/server
- chalk
- cheerio
- cli-spinners
- cross-fetch
- fetch-cookie
- graphql
- graphql-tag
- ora
- prompts
## TODO List
- [ ] Add support for relogin after session expires
- [ ] Add stream support

## Contributing

Expand Down
161 changes: 106 additions & 55 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { readFileSync, writeFile } from "fs";
import axios from 'axios';
import { JSDOM } from 'jsdom';
import scrape from "./puppet.js";
import * as mail from "./mail.js";
dotenv.config();
const spinner = ora({
color: "cyan",
Expand All @@ -17,6 +18,7 @@ const queries = {
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"),
};
let [pbCookie, channelName, appSettings, formkey] = ["", "", "", ""];
Expand All @@ -41,8 +43,9 @@ class ChatBot {
}
}
else {
this.headers["poe-formkey"] = process.env.QUORA_FORMKEY || "";
this.headers["Cookie"] = process.env.PB_COOKIE || "";
this.headers["poe-formkey"] = process.env.FORMKEY || "";
this.headers["Quora-Formkey"] = process.env.FORMKEY || "";
this.headers["Cookie"] = process.env.COOKIE || "";
}
}
async getCredentials() {
Expand Down Expand Up @@ -90,9 +93,9 @@ class ChatBot {
name: "mode",
message: "Select",
choices: [
{ title: "Auto [This will use temp phone number to get Verification Code]", value: "auto" },
{ 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: "Manual [Input QUORA_FORMKEY and PB_COOKIE in .env manually]", value: "manual" },
{ title: "Manual [Input FORMKEY and COOKIE in .env manually]", value: "manual" },
{ title: "exit", value: "exit" }
],
});
Expand Down Expand Up @@ -161,7 +164,6 @@ class ChatBot {
}
async makeRequest(request) {
this.headers["Content-Length"] = Buffer.byteLength(JSON.stringify(request), 'utf8');
console.log("THIS HEADERS: ", this.headers);
const response = await fetch('https://poe.com/api/gql_POST', {
method: 'POST',
headers: this.headers,
Expand All @@ -171,30 +173,19 @@ class ChatBot {
}
async login(mode) {
if (mode === "auto") {
const { phoneNumber, smsCodeUrl } = await this.getPhoneNumber();
console.log("Your phone number: " + phoneNumber);
console.log("Your SMS code URL: " + smsCodeUrl);
let smsCode = await this.getSMSCode(smsCodeUrl);
if (smsCode.length === 0) {
await this.sendVerifCode(phoneNumber, null);
}
while (smsCode.length === 0) {
await new Promise(resolve => setTimeout(resolve, 5000));
smsCode = await this.getSMSCode(smsCodeUrl);
const { email, sid_token } = await mail.createNewEmail();
console.log("EMAIL: " + email);
console.log("SID_TOKEN: " + sid_token);
const status = await this.sendVerifCode(null, email);
spinner.start("Waiting for OTP code...");
const otp_code = await mail.getPoeOTPCode(sid_token);
spinner.stop();
if (status === 'user_with_confirmed_email_not_found') {
await this.signUpWithVerificationCode(null, email, otp_code);
}
let loginStatus = "invalid_verification_code";
spinner.start("Waiting for SMS code...");
let retryCount = 0;
while (loginStatus === "invalid_verification_code") {
await new Promise(resolve => setTimeout(resolve, 60000));
smsCode = await this.getSMSCode(smsCodeUrl);
loginStatus = await this.signInOrUp(phoneNumber, null, smsCode[0]);
retryCount++;
if (retryCount == 2) {
await this.sendVerifCode(phoneNumber, null);
}
else {
await this.signInOrUp(null, email, otp_code);
}
spinner.stop();
}
else if (mode === "semi") {
const { type } = await prompts({
Expand All @@ -215,11 +206,12 @@ class ChatBot {
name: "credentials",
message: "Enter your " + type + ":",
});
let status = '';
if (type === "email") {
await this.sendVerifCode(null, credentials);
status = await this.sendVerifCode(null, credentials);
}
else {
await this.sendVerifCode(credentials, null);
status = await this.sendVerifCode(credentials, null);
}
const { verifyCode } = await prompts({
type: "text",
Expand All @@ -230,10 +222,20 @@ class ChatBot {
let loginStatus = "invalid_verification_code";
while (loginStatus !== "success") {
if (type === "email") {
loginStatus = await this.signInOrUp(null, credentials, verifyCode);
if (status === 'user_with_confirmed_email_not_found') {
loginStatus = await this.signUpWithVerificationCode(null, credentials, verifyCode);
}
else {
loginStatus = await this.signInOrUp(null, credentials, verifyCode);
}
}
else if (type === "phone") {
loginStatus = await this.signInOrUp(credentials, null, verifyCode);
if (status === 'user_with_confirmed_phone_number_not_found') {
loginStatus = await this.signUpWithVerificationCode(credentials, null, verifyCode);
}
else {
loginStatus = await this.signInOrUp(credentials, null, verifyCode);
}
}
}
spinner.stop();
Expand All @@ -247,46 +249,96 @@ class ChatBot {
console.log("Phone number: " + phoneNumber);
console.log("Email: " + email);
console.log("Verification code: " + verifyCode);
const { data: { loginWithVerificationCode: { status: loginStatus }, }, } = await this.makeRequest({
query: `${queries.loginMutation}`,
variables: {
verificationCode: verifyCode,
emailAddress: email,
phoneNumber: phoneNumber
},
});
console.log("Login Status: " + loginStatus);
return loginStatus;
try {
const { data: { loginWithVerificationCode: { status: loginStatus }, }, } = await this.makeRequest({
query: `${queries.loginMutation}`,
variables: {
verificationCode: verifyCode,
emailAddress: email,
phoneNumber: phoneNumber
},
});
console.log("Login Status: " + loginStatus);
return loginStatus;
}
catch (e) {
throw e;
}
}
async signUpWithVerificationCode(phoneNumber, email, verifyCode) {
console.log("Signing in/up...");
console.log("Phone number: " + phoneNumber);
console.log("Email: " + email);
console.log("Verification code: " + verifyCode);
try {
const { data: { signupWithVerificationCode: { status: loginStatus }, }, } = await this.makeRequest({
query: `${queries.signUpWithVerificationCodeMutation}`,
variables: {
verificationCode: verifyCode,
emailAddress: email,
phoneNumber: phoneNumber
},
});
console.log("Login Status: " + loginStatus);
return loginStatus;
}
catch (e) {
throw e;
}
}
async sendVerifCode(phoneNumber, email) {
const data = await this.makeRequest({
query: `${queries.sendVerificationCodeMutation}`,
variables: {
emailAddress: email,
phoneNumber: phoneNumber
},
});
console.log("DATA: " + JSON.stringify(data));
// console.log("Send Status: " + codeSendStatus)
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({
query: `${queries.sendVerificationCodeMutation}`,
variables: {
emailAddress: email,
phoneNumber: phoneNumber
},
});
console.log("Verification code sent. Status: " + status);
return status;
}
catch (e) {
throw e;
}
}
// DEPRECATED
async getPhoneNumber() {
const freeSmsBaseUrl = 'https://www.receivesms.co';
const url = freeSmsBaseUrl + '/us-phone-numbers/us/';
const url = freeSmsBaseUrl + '/active-numbers/';
let smsCodeUrl = '';
let phoneNumber = '';
let code = '';
try {
const response = await axios.get(url);
const dom = new JSDOM(response.data);
phoneNumber = dom.window.document.querySelector('a.btn').getAttribute('data-clipboard-text');
const code = dom.window.document.querySelector('td a[target="_self"]').getAttribute('href');
const buttons = dom.window.document.querySelectorAll('a.btn');
const phoneNumbers = [];
buttons.forEach(button => {
const phoneNumber = button.getAttribute('data-clipboard-text');
phoneNumbers.push(phoneNumber);
});
const codes = dom.window.document.querySelectorAll('td a[target="_self"]');
const phoneCodes = [];
codes.forEach(code => {
const codeText = code.getAttribute('href');
phoneCodes.push(codeText);
});
const position = Math.floor(Math.random() * phoneNumbers.length);
phoneNumber = phoneNumbers[position];
code = phoneCodes[position];
smsCodeUrl = freeSmsBaseUrl + code;
}
catch (error) {
console.log(error);
}
console.log("Your phone number: " + phoneNumber);
console.log("Your SMS code URL: " + smsCodeUrl);
return { phoneNumber, smsCodeUrl };
}
async getSMSCode(smsCodeUrl) {
// DEPRECATED
async getOTP(smsCodeUrl) {
let latestCode = [];
try {
const res = await fetch(smsCodeUrl);
Expand All @@ -307,7 +359,6 @@ class ChatBot {
catch (error) {
console.log("Error:", error);
}
console.log("Verification code: " + latestCode);
return latestCode;
}
// Safe
Expand Down
48 changes: 48 additions & 0 deletions dist/mail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import fetch from "cross-fetch";
import { readFileSync, writeFile } from "fs";
const BASEURL = 'https://api.guerrillamail.com/ajax.php';
const createNewEmail = async () => {
const response = await fetch(`${BASEURL}?f=get_email_address`);
const response_json = await response.json();
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) {
if (err) {
console.log(err);
}
});
return {
email: response_json.email_addr,
sid_token: response_json.sid_token,
alias: response_json.alias,
email_timestamp: response_json.email_timestamp
};
};
const getEmailList = async (sid_token) => {
const response = await fetch(`${BASEURL}?f=get_email_list&offset=0&sid_token=${sid_token}`);
const response_json = await response.json();
return {
list: response_json.list,
};
};
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));
emailList = await getEmailList(sid_token);
emailListLength = emailList.list.length;
if (emailListLength > 1) {
break;
}
}
return emailList.list[0];
};
const getPoeOTPCode = async (sid_token) => {
const emailData = await getLatestEmail(sid_token);
// Example email subject: "Your verification code 123456"
console.log("OTP CODE: " + emailData.mail_subject.split(' ')[3]);
return emailData.mail_subject.split(' ')[3];
};
export { createNewEmail, getEmailList, getLatestEmail, getPoeOTPCode };
13 changes: 13 additions & 0 deletions graphql/SignupWithVerificationCodeMutation.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mutation SignupWithVerificationCodeMutation(
$verificationCode: String!
$emailAddress: String
$phoneNumber: String
) {
signupWithVerificationCode(
verificationCode: $verificationCode
emailAddress: $emailAddress
phoneNumber: $phoneNumber
) {
status
}
}
Loading

0 comments on commit a5b8fcf

Please sign in to comment.