From 3be905b742b040c7f497727f9e765a8d709baa44 Mon Sep 17 00:00:00 2001 From: "t.me/xtekky" <98614666+xtekky@users.noreply.github.com> Date: Mon, 10 Apr 2023 01:09:29 +0200 Subject: rename poe api ( gpt 4 ) to quora due to having same name as poe-api, I changed the name for it to be used with the other package --- README.md | 24 +- poe/__init__.py | 213 ----------- poe/api.py | 401 --------------------- poe/cookies.txt | 7 - poe/graphql/AddHumanMessageMutation.graphql | 52 --- poe/graphql/AddMessageBreakMutation.graphql | 17 - poe/graphql/AutoSubscriptionMutation.graphql | 7 - poe/graphql/BioFragment.graphql | 8 - poe/graphql/ChatAddedSubscription.graphql | 5 - poe/graphql/ChatFragment.graphql | 6 - poe/graphql/ChatListPaginationQuery.graphql | 378 ------------------- poe/graphql/ChatPaginationQuery.graphql | 26 -- poe/graphql/ChatViewQuery.graphql | 8 - poe/graphql/DeleteHumanMessagesMutation.graphql | 7 - poe/graphql/DeleteMessageMutation.graphql | 7 - poe/graphql/HandleFragment.graphql | 8 - .../LoginWithVerificationCodeMutation.graphql | 13 - poe/graphql/MessageAddedSubscription.graphql | 100 ----- poe/graphql/MessageDeletedSubscription.graphql | 6 - poe/graphql/MessageFragment.graphql | 13 - poe/graphql/MessageRemoveVoteMutation.graphql | 7 - poe/graphql/MessageSetVoteMutation.graphql | 7 - poe/graphql/SendMessageMutation.graphql | 40 -- .../SendVerificationCodeForLoginMutation.graphql | 12 - poe/graphql/ShareMessagesMutation.graphql | 9 - .../SignupWithVerificationCodeMutation.graphql | 13 - poe/graphql/StaleChatUpdateMutation.graphql | 7 - poe/graphql/SubscriptionsMutation.graphql | 9 - poe/graphql/SummarizePlainPostQuery.graphql | 3 - poe/graphql/SummarizeQuotePostQuery.graphql | 3 - poe/graphql/SummarizeSharePostQuery.graphql | 3 - poe/graphql/UserSnippetFragment.graphql | 14 - poe/graphql/ViewerInfoQuery.graphql | 21 -- poe/graphql/ViewerStateFragment.graphql | 30 -- poe/graphql/ViewerStateUpdatedSubscription.graphql | 43 --- poe/graphql/__init__.py | 0 poe/mail.py | 62 ---- quora/__init__.py | 213 +++++++++++ quora/api.py | 401 +++++++++++++++++++++ quora/cookies.txt | 9 + quora/graphql/AddHumanMessageMutation.graphql | 52 +++ quora/graphql/AddMessageBreakMutation.graphql | 17 + quora/graphql/AutoSubscriptionMutation.graphql | 7 + quora/graphql/BioFragment.graphql | 8 + quora/graphql/ChatAddedSubscription.graphql | 5 + quora/graphql/ChatFragment.graphql | 6 + quora/graphql/ChatListPaginationQuery.graphql | 378 +++++++++++++++++++ quora/graphql/ChatPaginationQuery.graphql | 26 ++ quora/graphql/ChatViewQuery.graphql | 8 + quora/graphql/DeleteHumanMessagesMutation.graphql | 7 + quora/graphql/DeleteMessageMutation.graphql | 7 + quora/graphql/HandleFragment.graphql | 8 + .../LoginWithVerificationCodeMutation.graphql | 13 + quora/graphql/MessageAddedSubscription.graphql | 100 +++++ quora/graphql/MessageDeletedSubscription.graphql | 6 + quora/graphql/MessageFragment.graphql | 13 + quora/graphql/MessageRemoveVoteMutation.graphql | 7 + quora/graphql/MessageSetVoteMutation.graphql | 7 + quora/graphql/SendMessageMutation.graphql | 40 ++ .../SendVerificationCodeForLoginMutation.graphql | 12 + quora/graphql/ShareMessagesMutation.graphql | 9 + .../SignupWithVerificationCodeMutation.graphql | 13 + quora/graphql/StaleChatUpdateMutation.graphql | 7 + quora/graphql/SubscriptionsMutation.graphql | 9 + quora/graphql/SummarizePlainPostQuery.graphql | 3 + quora/graphql/SummarizeQuotePostQuery.graphql | 3 + quora/graphql/SummarizeSharePostQuery.graphql | 3 + quora/graphql/UserSnippetFragment.graphql | 14 + quora/graphql/ViewerInfoQuery.graphql | 21 ++ quora/graphql/ViewerStateFragment.graphql | 30 ++ .../graphql/ViewerStateUpdatedSubscription.graphql | 43 +++ quora/graphql/__init__.py | 0 quora/mail.py | 62 ++++ quora/poe_test.py | 13 + testing/poe_test.py | 13 - 75 files changed, 1592 insertions(+), 1590 deletions(-) delete mode 100644 poe/__init__.py delete mode 100644 poe/api.py delete mode 100644 poe/cookies.txt delete mode 100644 poe/graphql/AddHumanMessageMutation.graphql delete mode 100644 poe/graphql/AddMessageBreakMutation.graphql delete mode 100644 poe/graphql/AutoSubscriptionMutation.graphql delete mode 100644 poe/graphql/BioFragment.graphql delete mode 100644 poe/graphql/ChatAddedSubscription.graphql delete mode 100644 poe/graphql/ChatFragment.graphql delete mode 100644 poe/graphql/ChatListPaginationQuery.graphql delete mode 100644 poe/graphql/ChatPaginationQuery.graphql delete mode 100644 poe/graphql/ChatViewQuery.graphql delete mode 100644 poe/graphql/DeleteHumanMessagesMutation.graphql delete mode 100644 poe/graphql/DeleteMessageMutation.graphql delete mode 100644 poe/graphql/HandleFragment.graphql delete mode 100644 poe/graphql/LoginWithVerificationCodeMutation.graphql delete mode 100644 poe/graphql/MessageAddedSubscription.graphql delete mode 100644 poe/graphql/MessageDeletedSubscription.graphql delete mode 100644 poe/graphql/MessageFragment.graphql delete mode 100644 poe/graphql/MessageRemoveVoteMutation.graphql delete mode 100644 poe/graphql/MessageSetVoteMutation.graphql delete mode 100644 poe/graphql/SendMessageMutation.graphql delete mode 100644 poe/graphql/SendVerificationCodeForLoginMutation.graphql delete mode 100644 poe/graphql/ShareMessagesMutation.graphql delete mode 100644 poe/graphql/SignupWithVerificationCodeMutation.graphql delete mode 100644 poe/graphql/StaleChatUpdateMutation.graphql delete mode 100644 poe/graphql/SubscriptionsMutation.graphql delete mode 100644 poe/graphql/SummarizePlainPostQuery.graphql delete mode 100644 poe/graphql/SummarizeQuotePostQuery.graphql delete mode 100644 poe/graphql/SummarizeSharePostQuery.graphql delete mode 100644 poe/graphql/UserSnippetFragment.graphql delete mode 100644 poe/graphql/ViewerInfoQuery.graphql delete mode 100644 poe/graphql/ViewerStateFragment.graphql delete mode 100644 poe/graphql/ViewerStateUpdatedSubscription.graphql delete mode 100644 poe/graphql/__init__.py delete mode 100644 poe/mail.py create mode 100644 quora/__init__.py create mode 100644 quora/api.py create mode 100644 quora/cookies.txt create mode 100644 quora/graphql/AddHumanMessageMutation.graphql create mode 100644 quora/graphql/AddMessageBreakMutation.graphql create mode 100644 quora/graphql/AutoSubscriptionMutation.graphql create mode 100644 quora/graphql/BioFragment.graphql create mode 100644 quora/graphql/ChatAddedSubscription.graphql create mode 100644 quora/graphql/ChatFragment.graphql create mode 100644 quora/graphql/ChatListPaginationQuery.graphql create mode 100644 quora/graphql/ChatPaginationQuery.graphql create mode 100644 quora/graphql/ChatViewQuery.graphql create mode 100644 quora/graphql/DeleteHumanMessagesMutation.graphql create mode 100644 quora/graphql/DeleteMessageMutation.graphql create mode 100644 quora/graphql/HandleFragment.graphql create mode 100644 quora/graphql/LoginWithVerificationCodeMutation.graphql create mode 100644 quora/graphql/MessageAddedSubscription.graphql create mode 100644 quora/graphql/MessageDeletedSubscription.graphql create mode 100644 quora/graphql/MessageFragment.graphql create mode 100644 quora/graphql/MessageRemoveVoteMutation.graphql create mode 100644 quora/graphql/MessageSetVoteMutation.graphql create mode 100644 quora/graphql/SendMessageMutation.graphql create mode 100644 quora/graphql/SendVerificationCodeForLoginMutation.graphql create mode 100644 quora/graphql/ShareMessagesMutation.graphql create mode 100644 quora/graphql/SignupWithVerificationCodeMutation.graphql create mode 100644 quora/graphql/StaleChatUpdateMutation.graphql create mode 100644 quora/graphql/SubscriptionsMutation.graphql create mode 100644 quora/graphql/SummarizePlainPostQuery.graphql create mode 100644 quora/graphql/SummarizeQuotePostQuery.graphql create mode 100644 quora/graphql/SummarizeSharePostQuery.graphql create mode 100644 quora/graphql/UserSnippetFragment.graphql create mode 100644 quora/graphql/ViewerInfoQuery.graphql create mode 100644 quora/graphql/ViewerStateFragment.graphql create mode 100644 quora/graphql/ViewerStateUpdatedSubscription.graphql create mode 100644 quora/graphql/__init__.py create mode 100644 quora/mail.py create mode 100644 quora/poe_test.py delete mode 100644 testing/poe_test.py diff --git a/README.md b/README.md index af03cac3..0fcbfc79 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repository provides reverse-engineered language models from various sources - [ ] implement poe.com create bot feature (4) - [ ] poe.com chat history management (3) -- [ ] renaming the 'poe' module to 'quora' (2) +- [x] renaming the 'poe' module to 'quora' (2) - [x] add you.com api (1) @@ -17,7 +17,7 @@ This repository provides reverse-engineered language models from various sources - [Current Sites (No Authentication / Easy Account Creation)](#current-sites) - [Sites with Authentication (Will Reverse Engineer but Need Account Access)](#sites-with-authentication) - [Usage Examples](#usage-examples) - - [`poe`](#example-poe) + - [`quora (poe)`](#example-poe) - [`t3nsor`](#example-t3nsor) - [`ora`](#example-ora) - [`writesonic`](#example-writesonic) @@ -44,15 +44,15 @@ These sites will be reverse engineered but need account access: ## Usage Examples -### Example: `poe` (use like openai pypi package) - GPT-4 +### Example: `quora (poe)` (use like openai pypi package) - GPT-4 ```python -# Import poe -import poe +# Import quora (poe) +import quora -# poe.Account.create -# poe.Completion.create -# poe.StreamCompletion.create +# quora.Account.create +# quora.Completion.create +# quora.StreamCompletion.create [...] @@ -60,14 +60,14 @@ import poe #### Create Token (3-6s) ```python -token = poe.Account.create(logging = True) +token = quora.Account.create(logging = True) print('token', token) ``` #### Streaming Response ```python -for response in poe.StreamingCompletion.create(model = 'gpt-4', +for response in quora.StreamingCompletion.create(model = 'gpt-4', prompt = 'hello world', token = token): @@ -77,7 +77,7 @@ for response in poe.StreamingCompletion.create(model = 'gpt-4', #### Normal Response: ```python -response = poe.Completion.create(model = 'gpt-4', +response = quora.Completion.create(model = 'gpt-4', prompt = 'hello world', token = token) @@ -273,7 +273,7 @@ You can install these packages using the provided `requirements.txt` file. ## Repository structure: . ├── ora/ - ├── poe/ + ├── quora/ (/poe) ├── t3nsor/ ├── testing/ ├── writesonic/ diff --git a/poe/__init__.py b/poe/__init__.py deleted file mode 100644 index d1cb8ae5..00000000 --- a/poe/__init__.py +++ /dev/null @@ -1,213 +0,0 @@ -from poe.api import Client as PoeClient -from poe.mail import Mail -from requests import Session -from re import search, findall -from json import loads -from time import sleep, time -from pathlib import Path -from random import choice -from urllib import parse - -class PoeResponse: - - class Completion: - - class Choices: - def __init__(self, choice: dict) -> None: - self.text = choice['text'] - self.content = self.text.encode() - self.index = choice['index'] - self.logprobs = choice['logprobs'] - self.finish_reason = choice['finish_reason'] - - def __repr__(self) -> str: - return f'''<__main__.APIResponse.Completion.Choices(\n text = {self.text.encode()},\n index = {self.index},\n logprobs = {self.logprobs},\n finish_reason = {self.finish_reason})object at 0x1337>''' - - def __init__(self, choices: dict) -> None: - self.choices = [self.Choices(choice) for choice in choices] - - class Usage: - def __init__(self, usage_dict: dict) -> None: - self.prompt_tokens = usage_dict['prompt_tokens'] - self.completion_tokens = usage_dict['completion_tokens'] - self.total_tokens = usage_dict['total_tokens'] - - def __repr__(self): - return f'''<__main__.APIResponse.Usage(\n prompt_tokens = {self.prompt_tokens},\n completion_tokens = {self.completion_tokens},\n total_tokens = {self.total_tokens})object at 0x1337>''' - - def __init__(self, response_dict: dict) -> None: - - self.response_dict = response_dict - self.id = response_dict['id'] - self.object = response_dict['object'] - self.created = response_dict['created'] - self.model = response_dict['model'] - self.completion = self.Completion(response_dict['choices']) - self.usage = self.Usage(response_dict['usage']) - - def json(self) -> dict: - return self.response_dict - -class Account: - def create(proxy: None or str = None, logging: bool = False): - - client = Session() - client.proxies = { - 'http': f'http://{proxy}', - 'https': f'http://{proxy}'} if proxy else None - - mail = Mail(client.proxies) - mail_token = None - mail_address = mail.get_mail() - - if logging: print('email', mail_address) - - client.headers = { - "host" : "poe.com", - "connection" : "keep-alive", - "cache-control" : "max-age=0", - "sec-ch-ua" : "\"Microsoft Edge\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"", - "sec-ch-ua-mobile" : "?0", - "sec-ch-ua-platform": "\"macOS\"", - "user-agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54", - "accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "sec-fetch-site" : "same-origin", - "sec-fetch-mode" : "navigate", - "sec-fetch-user" : "?1", - "sec-fetch-dest" : "document", - "accept-encoding" : "gzip, deflate, br", - "accept-language" : "en-GB,en;q=0.9,en-US;q=0.8", - "upgrade-insecure-requests": "1", - } - - init = client.get('https://poe.com/login') - next_data = loads(search(r'json">(.+?)', init.text).group(1)) - - client.headers["poe-formkey"] = next_data['props']['formkey'] - client.headers["poe-tchannel"] = client.get('https://poe.com/api/settings').json()['tchannelData']['channel'] - - payload = { - "queryName": "MainSignupLoginSection_sendVerificationCodeMutation_Mutation", - "variables": { - "emailAddress": mail_address, - "phoneNumber" : None - }, - "query": "mutation MainSignupLoginSection_sendVerificationCodeMutation_Mutation(\n $emailAddress: String\n $phoneNumber: String\n) {\n sendVerificationCode(verificationReason: login, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n" - } - - response = client.post('https://poe.com/api/gql_POST', json=payload) - if 'Bad Request' in response.text: - if logging: print('bad request, retrying...' , response.json()) - Account.create(proxy = proxy, logging = logging) - - if logging: print('send_code' ,response.json()) - - while True: - sleep(1) - inbox = mail.fetch_inbox() - - for _ in inbox: - content = mail.get_message(_["id"]) - mail_token = findall(r';">(\d{6,7})', content['html'][0])[0] - - if mail_token: - break - - if logging: print('code', mail_token) - - payload = { - "queryName": "SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation", - "variables": { - "verificationCode" : mail_token, - "emailAddress" : mail_address, - "phoneNumber" : None - }, - "query": "mutation SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation(\n $verificationCode: String!\n $emailAddress: String\n $phoneNumber: String\n) {\n signupWithVerificationCode(verificationCode: $verificationCode, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n" - } - - response = client.post('https://poe.com/api/gql_POST', json = payload) - if logging: print('verify_code', response.json()) - - token = parse.unquote(client.cookies.get_dict()['p-b']) - - with open(Path(__file__).resolve().parent / 'cookies.txt', 'a') as f: - f.write(f'{token}\n') - - return token - - def get(): - cookies = open(Path(__file__).resolve().parent / 'cookies.txt', 'r').read().splitlines() - return choice(cookies) - -class StreamingCompletion: - def create( - model : str = 'gpt-4', - prompt: str = 'hello world', - token : str = ''): - - models = { - 'sage' : 'capybara', - 'gpt-4' : 'beaver', - 'claude+': 'a2_2', - 'claude' : 'a2', - 'gpt-3.5': 'chinchilla' - } - - client = PoeClient(token) - - for chunk in client.send_message(models[model], prompt): - - yield PoeResponse({ - 'id' : chunk["messageId"], - 'object' : 'text_completion', - 'created': chunk['creationTime'], - 'model' : models[model], - 'choices': [{ - 'text' : chunk["text_new"], - 'index' : 0, - 'logprobs' : None, - 'finish_reason' : 'stop' - }], - 'usage': { - 'prompt_tokens' : len(prompt), - 'completion_tokens' : len(chunk["text_new"]), - 'total_tokens' : len(prompt) + len(chunk["text_new"]) - } - }) - -class Completion: - def create( - model : str = 'gpt-4', - prompt: str = 'hello world', - token : str = ''): - - models = { - 'sage' : 'capybara', - 'gpt-4' : 'beaver', - 'claude+': 'a2_2', - 'claude' : 'a2', - 'gpt-3.5': 'chinchilla' - } - - client = PoeClient(token) - - for chunk in client.send_message(models[model], prompt): - pass - - return PoeResponse({ - 'id' : chunk["messageId"], - 'object' : 'text_completion', - 'created': chunk['creationTime'], - 'model' : models[model], - 'choices': [{ - 'text' : chunk["text"], - 'index' : 0, - 'logprobs' : None, - 'finish_reason' : 'stop' - }], - 'usage': { - 'prompt_tokens' : len(prompt), - 'completion_tokens' : len(chunk["text"]), - 'total_tokens' : len(prompt) + len(chunk["text"]) - } - }) \ No newline at end of file diff --git a/poe/api.py b/poe/api.py deleted file mode 100644 index e4c5d166..00000000 --- a/poe/api.py +++ /dev/null @@ -1,401 +0,0 @@ -# ading2210/poe-api: a reverse engineered Python API wrapepr for Quora's Poe -# Copyright (C) 2023 ading2210 - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import requests -import re -import json -import random -import logging -import time -import queue -import threading -import traceback -import websocket -from pathlib import Path -from urllib.parse import urlparse - -parent_path = Path(__file__).resolve().parent -queries_path = parent_path / "graphql" -queries = {} - -logging.basicConfig() -logger = logging.getLogger() - -user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0" - - -def load_queries(): - for path in queries_path.iterdir(): - if path.suffix != ".graphql": - continue - with open(path) as f: - queries[path.stem] = f.read() - - -def generate_payload(query_name, variables): - return { - "query": queries[query_name], - "variables": variables - } - - -def request_with_retries(method, *args, **kwargs): - attempts = kwargs.get("attempts") or 10 - url = args[0] - for i in range(attempts): - r = method(*args, **kwargs) - if r.status_code == 200: - return r - logger.warn( - f"Server returned a status code of {r.status_code} while downloading {url}. Retrying ({i+1}/{attempts})...") - - raise RuntimeError(f"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 = {} - ws = None - ws_connected = False - - def __init__(self, token, proxy=None): - self.proxy = proxy - self.session = requests.Session() - - if proxy: - self.session.proxies = { - "http": self.proxy, - "https": self.proxy - } - logger.info(f"Proxy enabled: {self.proxy}") - - self.session.cookies.set("p-b", token, domain="poe.com") - self.headers = { - "User-Agent": user_agent, - "Referrer": "https://poe.com/", - "Origin": "https://poe.com", - } - self.ws_domain = f"tch{random.randint(1, 1e6)}" - - self.session.headers.update(self.headers) - self.next_data = self.get_next_data() - self.channel = self.get_channel_data() - self.connect_ws() - self.bots = self.get_bots() - self.bot_names = self.get_bot_names() - - self.gql_headers = { - "poe-formkey": self.formkey, - "poe-tchannel": self.channel["channel"], - } - self.gql_headers = {**self.gql_headers, **self.headers} - self.subscribe() - - def get_next_data(self): - logger.info("Downloading next_data...") - - r = request_with_retries(self.session.get, self.home_url) - json_regex = r'' - json_text = re.search(json_regex, r.text).group(1) - next_data = json.loads(json_text) - - self.formkey = next_data["props"]["formkey"] - self.viewer = next_data["props"]["pageProps"]["payload"]["viewer"] - - return next_data - - def get_bot(self, display_name): - url = f'https://poe.com/_next/data/{self.next_data["buildId"]}/{display_name}.json' - logger.info("Downloading "+url) - - r = request_with_retries(self.session.get, url) - - chat_data = r.json()["pageProps"]["payload"]["chatOfBotDisplayName"] - return chat_data - - def get_bots(self): - viewer = self.next_data["props"]["pageProps"]["payload"]["viewer"] - if not "availableBots" in viewer: - raise RuntimeError("Invalid token.") - bot_list = viewer["availableBots"] - - bots = {} - for bot in bot_list: - chat_data = self.get_bot(bot["displayName"].lower()) - bots[chat_data["defaultBotObject"]["nickname"]] = chat_data - - return bots - - def get_bot_names(self): - bot_names = {} - for bot_nickname in self.bots: - bot_obj = self.bots[bot_nickname]["defaultBotObject"] - bot_names[bot_nickname] = bot_obj["displayName"] - return bot_names - - def get_channel_data(self, channel=None): - logger.info("Downloading channel data...") - r = request_with_retries(self.session.get, self.settings_url) - data = r.json() - - self.formkey = data["formkey"] - return data["tchannelData"] - - def get_websocket_url(self, channel=None): - if channel is None: - channel = self.channel - query = f'?min_seq={channel["minSeq"]}&channel={channel["channel"]}&hash={channel["channelHash"]}' - return f'wss://{self.ws_domain}.tch.{channel["baseHost"]}/up/{channel["boxName"]}/updates'+query - - def send_query(self, query_name, variables): - for i in range(20): - payload = generate_payload(query_name, variables) - r = request_with_retries( - self.session.post, self.gql_url, json=payload, headers=self.gql_headers) - data = r.json() - if data["data"] == None: - logger.warn( - f'{query_name} returned an error: {data["errors"][0]["message"]} | Retrying ({i+1}/20)') - time.sleep(2) - continue - - return r.json() - - raise RuntimeError(f'{query_name} failed too many times.') - - def subscribe(self): - logger.info("Subscribing to mutations") - result = self.send_query("SubscriptionsMutation", { - "subscriptions": [ - { - "subscriptionName": "messageAdded", - "query": queries["MessageAddedSubscription"] - }, - { - "subscriptionName": "viewerStateUpdated", - "query": queries["ViewerStateUpdatedSubscription"] - } - ] - }) - - def ws_run_thread(self): - kwargs = {} - if self.proxy: - proxy_parsed = urlparse(self.proxy) - kwargs = { - "proxy_type": proxy_parsed.scheme, - "http_proxy_host": proxy_parsed.hostname, - "http_proxy_port": proxy_parsed.port - } - - self.ws.run_forever(**kwargs) - - def connect_ws(self): - self.ws = websocket.WebSocketApp( - self.get_websocket_url(), - header={"User-Agent": user_agent}, - on_message=self.on_message, - on_open=self.on_ws_connect, - on_error=self.on_ws_error, - on_close=self.on_ws_close - ) - t = threading.Thread(target=self.ws_run_thread, daemon=True) - t.start() - while not self.ws_connected: - time.sleep(0.01) - - def disconnect_ws(self): - if self.ws: - self.ws.close() - self.ws_connected = False - - def on_ws_connect(self, ws): - self.ws_connected = True - - def on_ws_close(self, ws, close_status_code): - self.ws_connected = False - logger.warn(f"Websocket closed with status {close_status_code}") - - def on_ws_error(self, ws, error): - self.disconnect_ws() - self.connect_ws() - - def on_message(self, ws, msg): - try: - data = json.loads(msg) - - if not "messages" in data: - return - - for message_str in data["messages"]: - message_data = json.loads(message_str) - if message_data["message_type"] != "subscriptionUpdate": - continue - message = message_data["payload"]["data"]["messageAdded"] - - copied_dict = self.active_messages.copy() - for key, value in copied_dict.items(): - # add the message to the appropriate queue - if value == message["messageId"] and key in self.message_queues: - self.message_queues[key].put(message) - return - - # indicate that the response id is tied to the human message id - elif key != "pending" and value == None and message["state"] != "complete": - self.active_messages[key] = message["messageId"] - self.message_queues[key].put(message) - return - - except Exception: - logger.error(traceback.format_exc()) - self.disconnect_ws() - self.connect_ws() - - def send_message(self, chatbot, message, with_chat_break=False, timeout=20): - # if there is another active message, wait until it has finished sending - while None in self.active_messages.values(): - time.sleep(0.01) - - # None indicates that a message is still in progress - self.active_messages["pending"] = None - - logger.info(f"Sending message to {chatbot}: {message}") - # reconnect websocket - if not self.ws_connected: - self.disconnect_ws() - self.connect_ws() - message_data = self.send_query("SendMessageMutation", { - "bot": chatbot, - "query": message, - "chatId": self.bots[chatbot]["chatId"], - "source": None, - "withChatBreak": with_chat_break - }) - del self.active_messages["pending"] - - if not message_data["data"]["messageEdgeCreate"]["message"]: - raise RuntimeError(f"Daily limit reached for {chatbot}.") - try: - human_message = message_data["data"]["messageEdgeCreate"]["message"] - human_message_id = human_message["node"]["messageId"] - except TypeError: - raise RuntimeError( - f"An unknown error occured. Raw response data: {message_data}") - - # indicate that the current message is waiting for a response - self.active_messages[human_message_id] = None - self.message_queues[human_message_id] = queue.Queue() - - last_text = "" - message_id = None - while True: - try: - message = self.message_queues[human_message_id].get( - timeout=timeout) - except queue.Empty: - del self.active_messages[human_message_id] - del self.message_queues[human_message_id] - raise RuntimeError("Response timed out.") - - # only break when the message is marked as complete - if message["state"] == "complete": - if last_text and message["messageId"] == message_id: - break - else: - continue - - # update info about response - message["text_new"] = message["text"][len(last_text):] - last_text = message["text"] - message_id = message["messageId"] - - yield message - - del self.active_messages[human_message_id] - del self.message_queues[human_message_id] - - def send_chat_break(self, chatbot): - logger.info(f"Sending chat break to {chatbot}") - result = self.send_query("AddMessageBreakMutation", { - "chatId": self.bots[chatbot]["chatId"] - }) - return result["data"]["messageBreakCreate"]["message"] - - def get_message_history(self, chatbot, count=25, cursor=None): - logger.info(f"Downloading {count} messages from {chatbot}") - - if cursor == None: - chat_data = self.get_bot(self.bot_names[chatbot]) - if not chat_data["messagesConnection"]["edges"]: - return [] - cursor = chat_data["messagesConnection"]["edges"][-1]["cursor"] - - cursor = str(cursor) - if count > 50: - messages = self.get_message_history( - chatbot, count=50, cursor=cursor) - while count > 0: - new_cursor = messages[0]["cursor"] - new_messages = self.get_message_history( - chatbot, min(50, count), cursor=new_cursor) - messages = new_messages + messages - count -= 50 - return messages - - result = self.send_query("ChatListPaginationQuery", { - "count": count, - "cursor": cursor, - "id": self.bots[chatbot]["id"] - }) - return result["data"]["node"]["messagesConnection"]["edges"] - - def delete_message(self, message_ids): - logger.info(f"Deleting messages: {message_ids}") - if not type(message_ids) is list: - message_ids = [int(message_ids)] - - result = self.send_query("DeleteMessageMutation", { - "messageIds": message_ids - }) - - def purge_conversation(self, chatbot, count=-1): - logger.info(f"Purging messages from {chatbot}") - last_messages = self.get_message_history(chatbot, count=50)[::-1] - while last_messages: - message_ids = [] - for message in last_messages: - if count == 0: - break - count -= 1 - message_ids.append(message["node"]["messageId"]) - - self.delete_message(message_ids) - - if count == 0: - return - last_messages = self.get_message_history(chatbot, count=50)[::-1] - logger.info(f"No more messages left to delete.") - - -load_queries() \ No newline at end of file diff --git a/poe/cookies.txt b/poe/cookies.txt deleted file mode 100644 index 0cbd6ca9..00000000 --- a/poe/cookies.txt +++ /dev/null @@ -1,7 +0,0 @@ -SmPiNXZI9hBTuf3viz74PA== -zw7RoKQfeEehiaelYMRWeA== -NEttgJ_rRQdO05Tppx6hFw== -3OnmC0r9njYdNWhWszdQJg== -8hZKR7MxwUTEHvO45TEViw== -Eea6BqK0AmosTKzoI3AAow== -pUEbtxobN_QUSpLIR8RGww== diff --git a/poe/graphql/AddHumanMessageMutation.graphql b/poe/graphql/AddHumanMessageMutation.graphql deleted file mode 100644 index 01e6bc8c..00000000 --- a/poe/graphql/AddHumanMessageMutation.graphql +++ /dev/null @@ -1,52 +0,0 @@ -mutation AddHumanMessageMutation( - $chatId: BigInt! - $bot: String! - $query: String! - $source: MessageSource - $withChatBreak: Boolean! = false -) { - messageCreateWithStatus( - chatId: $chatId - bot: $bot - query: $query - source: $source - withChatBreak: $withChatBreak - ) { - message { - id - __typename - messageId - text - linkifiedText - authorNickname - state - vote - voteReason - creationTime - suggestedReplies - chat { - id - shouldShowDisclaimer - } - } - messageLimit{ - canSend - numMessagesRemaining - resetTime - shouldShowReminder - } - chatBreak { - id - __typename - messageId - text - linkifiedText - authorNickname - state - vote - voteReason - creationTime - suggestedReplies - } - } -} diff --git a/poe/graphql/AddMessageBreakMutation.graphql b/poe/graphql/AddMessageBreakMutation.graphql deleted file mode 100644 index b28d9903..00000000 --- a/poe/graphql/AddMessageBreakMutation.graphql +++ /dev/null @@ -1,17 +0,0 @@ -mutation AddMessageBreakMutation($chatId: BigInt!) { - messageBreakCreate(chatId: $chatId) { - message { - id - __typename - messageId - text - linkifiedText - authorNickname - state - vote - voteReason - creationTime - suggestedReplies - } - } -} diff --git a/poe/graphql/AutoSubscriptionMutation.graphql b/poe/graphql/AutoSubscriptionMutation.graphql deleted file mode 100644 index 6cf7bf74..00000000 --- a/poe/graphql/AutoSubscriptionMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) { - autoSubscribe(subscriptions: $subscriptions) { - viewer { - id - } - } -} diff --git a/poe/graphql/BioFragment.graphql b/poe/graphql/BioFragment.graphql deleted file mode 100644 index c4218030..00000000 --- a/poe/graphql/BioFragment.graphql +++ /dev/null @@ -1,8 +0,0 @@ -fragment BioFragment on Viewer { - id - poeUser { - id - uid - bio - } -} diff --git a/poe/graphql/ChatAddedSubscription.graphql b/poe/graphql/ChatAddedSubscription.graphql deleted file mode 100644 index 664b107f..00000000 --- a/poe/graphql/ChatAddedSubscription.graphql +++ /dev/null @@ -1,5 +0,0 @@ -subscription ChatAddedSubscription { - chatAdded { - ...ChatFragment - } -} diff --git a/poe/graphql/ChatFragment.graphql b/poe/graphql/ChatFragment.graphql deleted file mode 100644 index 605645ff..00000000 --- a/poe/graphql/ChatFragment.graphql +++ /dev/null @@ -1,6 +0,0 @@ -fragment ChatFragment on Chat { - id - chatId - defaultBotNickname - shouldShowDisclaimer -} diff --git a/poe/graphql/ChatListPaginationQuery.graphql b/poe/graphql/ChatListPaginationQuery.graphql deleted file mode 100644 index 6d9ae884..00000000 --- a/poe/graphql/ChatListPaginationQuery.graphql +++ /dev/null @@ -1,378 +0,0 @@ -query ChatListPaginationQuery( - $count: Int = 5 - $cursor: String - $id: ID! -) { - node(id: $id) { - __typename - ...ChatPageMain_chat_1G22uz - id - } -} - -fragment BotImage_bot on Bot { - displayName - ...botHelpers_useDeletion_bot - ...BotImage_useProfileImage_bot -} - -fragment BotImage_useProfileImage_bot on Bot { - image { - __typename - ... on LocalBotImage { - localName - } - ... on UrlBotImage { - url - } - } - ...botHelpers_useDeletion_bot -} - -fragment ChatMessageDownvotedButton_message on Message { - ...MessageFeedbackReasonModal_message - ...MessageFeedbackOtherModal_message -} - -fragment ChatMessageDropdownMenu_message on Message { - id - messageId - vote - text - author - ...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 - } - hasClearContext - isDown - ...botHelpers_useDeletion_bot - 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 - defaultBotObject { - hasWelcomeTopics - id - } -} - -fragment ChatMessageSuggestedReplies_message on Message { - suggestedReplies - ...ChatMessageSuggestedReplies_SuggestedReplyButton_message -} - -fragment ChatMessage_chat on Chat { - defaultBotObject { - hasWelcomeTopics - hasSuggestedReplies - disclaimerText - messageLimit { - ...ChatPageRateLimitedBanner_messageLimit - } - ...ChatPageDisclaimer_bot - id - } - ...ChatMessageSuggestedReplies_chat - ...ChatWelcomeView_chat -} - -fragment ChatMessage_message on Message { - id - messageId - text - author - linkifiedText - state - contentType - ...ChatMessageSuggestedReplies_message - ...ChatMessageFeedbackButtons_message - ...ChatMessageOverflowButton_message - ...chatHelpers_isHumanMessage - ...chatHelpers_isBotMessage - ...chatHelpers_isChatBreak - ...chatHelpers_useTimeoutLevel - ...MarkdownLinkInner_message - ...IdAnnotation_node -} - -fragment ChatMessagesView_chat on Chat { - ...ChatMessage_chat - ...ChatWelcomeView_chat - ...IdAnnotation_node - defaultBotObject { - hasWelcomeTopics - 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 { - disclaimerText -} - -fragment ChatPageMainFooter_chat on Chat { - defaultBotObject { - ...ChatPageMainFooter_useAccessMessage_bot - id - } - ...ChatMessageInputView_chat - ...ChatPageShareFooter_chat - ...ChatPageDeleteFooter_chat -} - -fragment ChatPageMainFooter_edges on MessageEdge { - ...ChatMessageInputView_edges -} - -fragment ChatPageMainFooter_useAccessMessage_bot on Bot { - ...botHelpers_useDeletion_bot - ...botHelpers_useViewerCanAccessPrivateBot -} - -fragment ChatPageMain_chat_1G22uz on Chat { - id - chatId - ...ChatPageShareFooter_chat - ...ChatPageDeleteFooter_chat - ...ChatMessagesView_chat - ...MarkdownLinkInner_chat - ...chatHelpers_useUpdateStaleChat_chat - ...ChatSubscriptionPaywallContextWrapper_chat - ...ChatPageMainFooter_chat - messagesConnection(last: $count, before: $cursor) { - edges { - ...ChatMessagesView_edges - ...ChatPageMainFooter_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 IdAnnotation_node on Node { - __isNode: __typename - 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 botHelpers_useDeletion_bot on Bot { - deletionState -} - -fragment botHelpers_useViewerCanAccessPrivateBot on Bot { - isPrivateBot - viewerIsCreator -} - -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 { - id - nickname - } - shouldShowDisclaimer -} - -fragment chatHelpers_useTimeoutLevel on Message { - id - state - text - messageId - chat { - chatId - defaultBotNickname - id - } -} - -fragment chatHelpers_useUpdateStaleChat_chat on Chat { - chatId - defaultBotObject { - contextClearWindowSecs - id - } - ...chatHelpers_useSendChatBreak_chat -} - -fragment chatHelpers_useUpdateStaleChat_message on Message { - creationTime - ...chatHelpers_isChatBreak -} diff --git a/poe/graphql/ChatPaginationQuery.graphql b/poe/graphql/ChatPaginationQuery.graphql deleted file mode 100644 index f2452cd6..00000000 --- a/poe/graphql/ChatPaginationQuery.graphql +++ /dev/null @@ -1,26 +0,0 @@ -query ChatPaginationQuery($bot: String!, $before: String, $last: Int! = 10) { - chatOfBot(bot: $bot) { - id - __typename - messagesConnection(before: $before, last: $last) { - pageInfo { - hasPreviousPage - } - edges { - node { - id - __typename - messageId - text - linkifiedText - authorNickname - state - vote - voteReason - creationTime - suggestedReplies - } - } - } - } -} diff --git a/poe/graphql/ChatViewQuery.graphql b/poe/graphql/ChatViewQuery.graphql deleted file mode 100644 index c330107d..00000000 --- a/poe/graphql/ChatViewQuery.graphql +++ /dev/null @@ -1,8 +0,0 @@ -query ChatViewQuery($bot: String!) { - chatOfBot(bot: $bot) { - id - chatId - defaultBotNickname - shouldShowDisclaimer - } -} diff --git a/poe/graphql/DeleteHumanMessagesMutation.graphql b/poe/graphql/DeleteHumanMessagesMutation.graphql deleted file mode 100644 index 42692c6e..00000000 --- a/poe/graphql/DeleteHumanMessagesMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) { - messagesDelete(messageIds: $messageIds) { - viewer { - id - } - } -} diff --git a/poe/graphql/DeleteMessageMutation.graphql b/poe/graphql/DeleteMessageMutation.graphql deleted file mode 100644 index 7b9e36d4..00000000 --- a/poe/graphql/DeleteMessageMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation deleteMessageMutation( - $messageIds: [BigInt!]! -) { - messagesDelete(messageIds: $messageIds) { - edgeIds - } -} \ No newline at end of file diff --git a/poe/graphql/HandleFragment.graphql b/poe/graphql/HandleFragment.graphql deleted file mode 100644 index f53c484b..00000000 --- a/poe/graphql/HandleFragment.graphql +++ /dev/null @@ -1,8 +0,0 @@ -fragment HandleFragment on Viewer { - id - poeUser { - id - uid - handle - } -} diff --git a/poe/graphql/LoginWithVerificationCodeMutation.graphql b/poe/graphql/LoginWithVerificationCodeMutation.graphql deleted file mode 100644 index 723b1f44..00000000 --- a/poe/graphql/LoginWithVerificationCodeMutation.graphql +++ /dev/null @@ -1,13 +0,0 @@ -mutation LoginWithVerificationCodeMutation( - $verificationCode: String! - $emailAddress: String - $phoneNumber: String -) { - loginWithVerificationCode( - verificationCode: $verificationCode - emailAddress: $emailAddress - phoneNumber: $phoneNumber - ) { - status - } -} diff --git a/poe/graphql/MessageAddedSubscription.graphql b/poe/graphql/MessageAddedSubscription.graphql deleted file mode 100644 index 8dc9499c..00000000 --- a/poe/graphql/MessageAddedSubscription.graphql +++ /dev/null @@ -1,100 +0,0 @@ -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 -} diff --git a/poe/graphql/MessageDeletedSubscription.graphql b/poe/graphql/MessageDeletedSubscription.graphql deleted file mode 100644 index 54c1c164..00000000 --- a/poe/graphql/MessageDeletedSubscription.graphql +++ /dev/null @@ -1,6 +0,0 @@ -subscription MessageDeletedSubscription($chatId: BigInt!) { - messageDeleted(chatId: $chatId) { - id - messageId - } -} diff --git a/poe/graphql/MessageFragment.graphql b/poe/graphql/MessageFragment.graphql deleted file mode 100644 index cc860811..00000000 --- a/poe/graphql/MessageFragment.graphql +++ /dev/null @@ -1,13 +0,0 @@ -fragment MessageFragment on Message { - id - __typename - messageId - text - linkifiedText - authorNickname - state - vote - voteReason - creationTime - suggestedReplies -} diff --git a/poe/graphql/MessageRemoveVoteMutation.graphql b/poe/graphql/MessageRemoveVoteMutation.graphql deleted file mode 100644 index d5e6e610..00000000 --- a/poe/graphql/MessageRemoveVoteMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation MessageRemoveVoteMutation($messageId: BigInt!) { - messageRemoveVote(messageId: $messageId) { - message { - ...MessageFragment - } - } -} diff --git a/poe/graphql/MessageSetVoteMutation.graphql b/poe/graphql/MessageSetVoteMutation.graphql deleted file mode 100644 index 76000df0..00000000 --- a/poe/graphql/MessageSetVoteMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) { - messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) { - message { - ...MessageFragment - } - } -} diff --git a/poe/graphql/SendMessageMutation.graphql b/poe/graphql/SendMessageMutation.graphql deleted file mode 100644 index 4b0a4383..00000000 --- a/poe/graphql/SendMessageMutation.graphql +++ /dev/null @@ -1,40 +0,0 @@ -mutation chatHelpers_sendMessageMutation_Mutation( - $chatId: BigInt! - $bot: String! - $query: String! - $source: MessageSource - $withChatBreak: Boolean! -) { - messageEdgeCreate(chatId: $chatId, bot: $bot, query: $query, source: $source, withChatBreak: $withChatBreak) { - chatBreak { - cursor - node { - id - messageId - text - author - suggestedReplies - creationTime - state - } - id - } - message { - cursor - node { - id - messageId - text - author - suggestedReplies - creationTime - state - chat { - shouldShowDisclaimer - id - } - } - id - } - } -} diff --git a/poe/graphql/SendVerificationCodeForLoginMutation.graphql b/poe/graphql/SendVerificationCodeForLoginMutation.graphql deleted file mode 100644 index 45af4799..00000000 --- a/poe/graphql/SendVerificationCodeForLoginMutation.graphql +++ /dev/null @@ -1,12 +0,0 @@ -mutation SendVerificationCodeForLoginMutation( - $emailAddress: String - $phoneNumber: String -) { - sendVerificationCode( - verificationReason: login - emailAddress: $emailAddress - phoneNumber: $phoneNumber - ) { - status - } -} diff --git a/poe/graphql/ShareMessagesMutation.graphql b/poe/graphql/ShareMessagesMutation.graphql deleted file mode 100644 index 92e80db5..00000000 --- a/poe/graphql/ShareMessagesMutation.graphql +++ /dev/null @@ -1,9 +0,0 @@ -mutation ShareMessagesMutation( - $chatId: BigInt! - $messageIds: [BigInt!]! - $comment: String -) { - messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) { - shareCode - } -} diff --git a/poe/graphql/SignupWithVerificationCodeMutation.graphql b/poe/graphql/SignupWithVerificationCodeMutation.graphql deleted file mode 100644 index 06b2826f..00000000 --- a/poe/graphql/SignupWithVerificationCodeMutation.graphql +++ /dev/null @@ -1,13 +0,0 @@ -mutation SignupWithVerificationCodeMutation( - $verificationCode: String! - $emailAddress: String - $phoneNumber: String -) { - signupWithVerificationCode( - verificationCode: $verificationCode - emailAddress: $emailAddress - phoneNumber: $phoneNumber - ) { - status - } -} diff --git a/poe/graphql/StaleChatUpdateMutation.graphql b/poe/graphql/StaleChatUpdateMutation.graphql deleted file mode 100644 index de203d47..00000000 --- a/poe/graphql/StaleChatUpdateMutation.graphql +++ /dev/null @@ -1,7 +0,0 @@ -mutation StaleChatUpdateMutation($chatId: BigInt!) { - staleChatUpdate(chatId: $chatId) { - message { - ...MessageFragment - } - } -} diff --git a/poe/graphql/SubscriptionsMutation.graphql b/poe/graphql/SubscriptionsMutation.graphql deleted file mode 100644 index b864bd60..00000000 --- a/poe/graphql/SubscriptionsMutation.graphql +++ /dev/null @@ -1,9 +0,0 @@ -mutation subscriptionsMutation( - $subscriptions: [AutoSubscriptionQuery!]! -) { - autoSubscribe(subscriptions: $subscriptions) { - viewer { - id - } - } -} \ No newline at end of file diff --git a/poe/graphql/SummarizePlainPostQuery.graphql b/poe/graphql/SummarizePlainPostQuery.graphql deleted file mode 100644 index afa2a84c..00000000 --- a/poe/graphql/SummarizePlainPostQuery.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query SummarizePlainPostQuery($comment: String!) { - summarizePlainPost(comment: $comment) -} diff --git a/poe/graphql/SummarizeQuotePostQuery.graphql b/poe/graphql/SummarizeQuotePostQuery.graphql deleted file mode 100644 index 5147c3c5..00000000 --- a/poe/graphql/SummarizeQuotePostQuery.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) { - summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId) -} diff --git a/poe/graphql/SummarizeSharePostQuery.graphql b/poe/graphql/SummarizeSharePostQuery.graphql deleted file mode 100644 index cb4a623c..00000000 --- a/poe/graphql/SummarizeSharePostQuery.graphql +++ /dev/null @@ -1,3 +0,0 @@ -query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) { - summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds) -} diff --git a/poe/graphql/UserSnippetFragment.graphql b/poe/graphql/UserSnippetFragment.graphql deleted file mode 100644 index 17fc8426..00000000 --- a/poe/graphql/UserSnippetFragment.graphql +++ /dev/null @@ -1,14 +0,0 @@ -fragment UserSnippetFragment on PoeUser { - id - uid - bio - handle - fullName - viewerIsFollowing - isPoeOnlyUser - profilePhotoURLTiny: profilePhotoUrl(size: tiny) - profilePhotoURLSmall: profilePhotoUrl(size: small) - profilePhotoURLMedium: profilePhotoUrl(size: medium) - profilePhotoURLLarge: profilePhotoUrl(size: large) - isFollowable -} diff --git a/poe/graphql/ViewerInfoQuery.graphql b/poe/graphql/ViewerInfoQuery.graphql deleted file mode 100644 index 1ecaf9e8..00000000 --- a/poe/graphql/ViewerInfoQuery.graphql +++ /dev/null @@ -1,21 +0,0 @@ -query ViewerInfoQuery { - viewer { - id - uid - ...ViewerStateFragment - ...BioFragment - ...HandleFragment - hasCompletedMultiplayerNux - poeUser { - id - ...UserSnippetFragment - } - messageLimit{ - canSend - numMessagesRemaining - resetTime - shouldShowReminder - } - } -} - diff --git a/poe/graphql/ViewerStateFragment.graphql b/poe/graphql/ViewerStateFragment.graphql deleted file mode 100644 index 3cd83e9c..00000000 --- a/poe/graphql/ViewerStateFragment.graphql +++ /dev/null @@ -1,30 +0,0 @@ -fragment ViewerStateFragment on Viewer { - id - __typename - iosMinSupportedVersion: integerGate(gateName: "poe_ios_min_supported_version") - iosMinEncouragedVersion: integerGate( - gateName: "poe_ios_min_encouraged_version" - ) - macosMinSupportedVersion: integerGate( - gateName: "poe_macos_min_supported_version" - ) - macosMinEncouragedVersion: integerGate( - gateName: "poe_macos_min_encouraged_version" - ) - showPoeDebugPanel: booleanGate(gateName: "poe_show_debug_panel") - enableCommunityFeed: booleanGate(gateName: "enable_poe_shares_feed") - linkifyText: booleanGate(gateName: "poe_linkify_response") - enableSuggestedReplies: booleanGate(gateName: "poe_suggested_replies") - removeInviteLimit: booleanGate(gateName: "poe_remove_invite_limit") - enableInAppPurchases: booleanGate(gateName: "poe_enable_in_app_purchases") - availableBots { - nickname - displayName - profilePicture - isDown - disclaimer - subtitle - poweredBy - } -} - diff --git a/poe/graphql/ViewerStateUpdatedSubscription.graphql b/poe/graphql/ViewerStateUpdatedSubscription.graphql deleted file mode 100644 index 03fc73d1..00000000 --- a/poe/graphql/ViewerStateUpdatedSubscription.graphql +++ /dev/null @@ -1,43 +0,0 @@ -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") -} diff --git a/poe/graphql/__init__.py b/poe/graphql/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/poe/mail.py b/poe/mail.py deleted file mode 100644 index d4aeb564..00000000 --- a/poe/mail.py +++ /dev/null @@ -1,62 +0,0 @@ -from requests import Session -from string import ascii_letters -from random import choices - -class Mail: - def __init__(self, proxies: dict = None) -> None: - self.client = Session() - self.client.proxies = None #proxies - self.client.headers = { - "host": "api.mail.tm", - "connection": "keep-alive", - "sec-ch-ua": "\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"", - "accept": "application/json, text/plain, */*", - "content-type": "application/json", - "sec-ch-ua-mobile": "?0", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36", - "sec-ch-ua-platform": "\"macOS\"", - "origin": "https://mail.tm", - "sec-fetch-site": "same-site", - "sec-fetch-mode": "cors", - "sec-fetch-dest": "empty", - "referer": "https://mail.tm/", - "accept-encoding": "gzip, deflate, br", - "accept-language": "en-GB,en-US;q=0.9,en;q=0.8" - } - - def get_mail(self) -> str: - token = ''.join(choices(ascii_letters, k=10)).lower() - - init = self.client.post("https://api.mail.tm/accounts", json={ - "address" : f"{token}@bugfoo.com", - "password": token - }) - - if init.status_code == 201: - resp = self.client.post("https://api.mail.tm/token", json = { - **init.json(), - "password": token - }) - - self.client.headers['authorization'] = 'Bearer ' + resp.json()['token'] - - return f"{token}@bugfoo.com" - - else: - raise Exception("Failed to create email") - - def fetch_inbox(self): - return self.client.get(f"https://api.mail.tm/messages").json()["hydra:member"] - - def get_message(self, message_id: str): - return self.client.get(f"https://api.mail.tm/messages/{message_id}").json() - - def get_message_content(self, message_id: str): - return self.get_message(message_id)["text"] - - -# if __name__ == "__main__": -# client = Mail() -# client.get_mail() - - \ No newline at end of file diff --git a/quora/__init__.py b/quora/__init__.py new file mode 100644 index 00000000..5cae5905 --- /dev/null +++ b/quora/__init__.py @@ -0,0 +1,213 @@ +from quora.api import Client as PoeClient +from quora.mail import Mail +from requests import Session +from re import search, findall +from json import loads +from time import sleep, time +from pathlib import Path +from random import choice +from urllib import parse + +class PoeResponse: + + class Completion: + + class Choices: + def __init__(self, choice: dict) -> None: + self.text = choice['text'] + self.content = self.text.encode() + self.index = choice['index'] + self.logprobs = choice['logprobs'] + self.finish_reason = choice['finish_reason'] + + def __repr__(self) -> str: + return f'''<__main__.APIResponse.Completion.Choices(\n text = {self.text.encode()},\n index = {self.index},\n logprobs = {self.logprobs},\n finish_reason = {self.finish_reason})object at 0x1337>''' + + def __init__(self, choices: dict) -> None: + self.choices = [self.Choices(choice) for choice in choices] + + class Usage: + def __init__(self, usage_dict: dict) -> None: + self.prompt_tokens = usage_dict['prompt_tokens'] + self.completion_tokens = usage_dict['completion_tokens'] + self.total_tokens = usage_dict['total_tokens'] + + def __repr__(self): + return f'''<__main__.APIResponse.Usage(\n prompt_tokens = {self.prompt_tokens},\n completion_tokens = {self.completion_tokens},\n total_tokens = {self.total_tokens})object at 0x1337>''' + + def __init__(self, response_dict: dict) -> None: + + self.response_dict = response_dict + self.id = response_dict['id'] + self.object = response_dict['object'] + self.created = response_dict['created'] + self.model = response_dict['model'] + self.completion = self.Completion(response_dict['choices']) + self.usage = self.Usage(response_dict['usage']) + + def json(self) -> dict: + return self.response_dict + +class Account: + def create(proxy: None or str = None, logging: bool = False): + + client = Session() + client.proxies = { + 'http': f'http://{proxy}', + 'https': f'http://{proxy}'} if proxy else None + + mail = Mail(client.proxies) + mail_token = None + mail_address = mail.get_mail() + + if logging: print('email', mail_address) + + client.headers = { + "host" : "poe.com", + "connection" : "keep-alive", + "cache-control" : "max-age=0", + "sec-ch-ua" : "\"Microsoft Edge\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"", + "sec-ch-ua-mobile" : "?0", + "sec-ch-ua-platform": "\"macOS\"", + "user-agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.54", + "accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "sec-fetch-site" : "same-origin", + "sec-fetch-mode" : "navigate", + "sec-fetch-user" : "?1", + "sec-fetch-dest" : "document", + "accept-encoding" : "gzip, deflate, br", + "accept-language" : "en-GB,en;q=0.9,en-US;q=0.8", + "upgrade-insecure-requests": "1", + } + + init = client.get('https://poe.com/login') + next_data = loads(search(r'json">(.+?)', init.text).group(1)) + + client.headers["poe-formkey"] = next_data['props']['formkey'] + client.headers["poe-tchannel"] = client.get('https://poe.com/api/settings').json()['tchannelData']['channel'] + + payload = { + "queryName": "MainSignupLoginSection_sendVerificationCodeMutation_Mutation", + "variables": { + "emailAddress": mail_address, + "phoneNumber" : None + }, + "query": "mutation MainSignupLoginSection_sendVerificationCodeMutation_Mutation(\n $emailAddress: String\n $phoneNumber: String\n) {\n sendVerificationCode(verificationReason: login, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n" + } + + response = client.post('https://poe.com/api/gql_POST', json=payload) + if 'Bad Request' in response.text: + if logging: print('bad request, retrying...' , response.json()) + Account.create(proxy = proxy, logging = logging) + + if logging: print('send_code' ,response.json()) + + while True: + sleep(1) + inbox = mail.fetch_inbox() + + for _ in inbox: + content = mail.get_message(_["id"]) + mail_token = findall(r';">(\d{6,7})', content['html'][0])[0] + + if mail_token: + break + + if logging: print('code', mail_token) + + payload = { + "queryName": "SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation", + "variables": { + "verificationCode" : mail_token, + "emailAddress" : mail_address, + "phoneNumber" : None + }, + "query": "mutation SignupOrLoginWithCodeSection_signupWithVerificationCodeMutation_Mutation(\n $verificationCode: String!\n $emailAddress: String\n $phoneNumber: String\n) {\n signupWithVerificationCode(verificationCode: $verificationCode, emailAddress: $emailAddress, phoneNumber: $phoneNumber) {\n status\n errorMessage\n }\n}\n" + } + + response = client.post('https://poe.com/api/gql_POST', json = payload) + if logging: print('verify_code', response.json()) + + token = parse.unquote(client.cookies.get_dict()['p-b']) + + with open(Path(__file__).resolve().parent / 'cookies.txt', 'a') as f: + f.write(f'{token}\n') + + return token + + def get(): + cookies = open(Path(__file__).resolve().parent / 'cookies.txt', 'r').read().splitlines() + return choice(cookies) + +class StreamingCompletion: + def create( + model : str = 'gpt-4', + prompt: str = 'hello world', + token : str = ''): + + models = { + 'sage' : 'capybara', + 'gpt-4' : 'beaver', + 'claude+': 'a2_2', + 'claude' : 'a2', + 'gpt-3.5': 'chinchilla' + } + + client = PoeClient(token) + + for chunk in client.send_message(models[model], prompt): + + yield PoeResponse({ + 'id' : chunk["messageId"], + 'object' : 'text_completion', + 'created': chunk['creationTime'], + 'model' : models[model], + 'choices': [{ + 'text' : chunk["text_new"], + 'index' : 0, + 'logprobs' : None, + 'finish_reason' : 'stop' + }], + 'usage': { + 'prompt_tokens' : len(prompt), + 'completion_tokens' : len(chunk["text_new"]), + 'total_tokens' : len(prompt) + len(chunk["text_new"]) + } + }) + +class Completion: + def create( + model : str = 'gpt-4', + prompt: str = 'hello world', + token : str = ''): + + models = { + 'sage' : 'capybara', + 'gpt-4' : 'beaver', + 'claude+': 'a2_2', + 'claude' : 'a2', + 'gpt-3.5': 'chinchilla' + } + + client = PoeClient(token) + + for chunk in client.send_message(models[model], prompt): + pass + + return PoeResponse({ + 'id' : chunk["messageId"], + 'object' : 'text_completion', + 'created': chunk['creationTime'], + 'model' : models[model], + 'choices': [{ + 'text' : chunk["text"], + 'index' : 0, + 'logprobs' : None, + 'finish_reason' : 'stop' + }], + 'usage': { + 'prompt_tokens' : len(prompt), + 'completion_tokens' : len(chunk["text"]), + 'total_tokens' : len(prompt) + len(chunk["text"]) + } + }) \ No newline at end of file diff --git a/quora/api.py b/quora/api.py new file mode 100644 index 00000000..e4c5d166 --- /dev/null +++ b/quora/api.py @@ -0,0 +1,401 @@ +# ading2210/poe-api: a reverse engineered Python API wrapepr for Quora's Poe +# Copyright (C) 2023 ading2210 + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import requests +import re +import json +import random +import logging +import time +import queue +import threading +import traceback +import websocket +from pathlib import Path +from urllib.parse import urlparse + +parent_path = Path(__file__).resolve().parent +queries_path = parent_path / "graphql" +queries = {} + +logging.basicConfig() +logger = logging.getLogger() + +user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0" + + +def load_queries(): + for path in queries_path.iterdir(): + if path.suffix != ".graphql": + continue + with open(path) as f: + queries[path.stem] = f.read() + + +def generate_payload(query_name, variables): + return { + "query": queries[query_name], + "variables": variables + } + + +def request_with_retries(method, *args, **kwargs): + attempts = kwargs.get("attempts") or 10 + url = args[0] + for i in range(attempts): + r = method(*args, **kwargs) + if r.status_code == 200: + return r + logger.warn( + f"Server returned a status code of {r.status_code} while downloading {url}. Retrying ({i+1}/{attempts})...") + + raise RuntimeError(f"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 = {} + ws = None + ws_connected = False + + def __init__(self, token, proxy=None): + self.proxy = proxy + self.session = requests.Session() + + if proxy: + self.session.proxies = { + "http": self.proxy, + "https": self.proxy + } + logger.info(f"Proxy enabled: {self.proxy}") + + self.session.cookies.set("p-b", token, domain="poe.com") + self.headers = { + "User-Agent": user_agent, + "Referrer": "https://poe.com/", + "Origin": "https://poe.com", + } + self.ws_domain = f"tch{random.randint(1, 1e6)}" + + self.session.headers.update(self.headers) + self.next_data = self.get_next_data() + self.channel = self.get_channel_data() + self.connect_ws() + self.bots = self.get_bots() + self.bot_names = self.get_bot_names() + + self.gql_headers = { + "poe-formkey": self.formkey, + "poe-tchannel": self.channel["channel"], + } + self.gql_headers = {**self.gql_headers, **self.headers} + self.subscribe() + + def get_next_data(self): + logger.info("Downloading next_data...") + + r = request_with_retries(self.session.get, self.home_url) + json_regex = r'' + json_text = re.search(json_regex, r.text).group(1) + next_data = json.loads(json_text) + + self.formkey = next_data["props"]["formkey"] + self.viewer = next_data["props"]["pageProps"]["payload"]["viewer"] + + return next_data + + def get_bot(self, display_name): + url = f'https://poe.com/_next/data/{self.next_data["buildId"]}/{display_name}.json' + logger.info("Downloading "+url) + + r = request_with_retries(self.session.get, url) + + chat_data = r.json()["pageProps"]["payload"]["chatOfBotDisplayName"] + return chat_data + + def get_bots(self): + viewer = self.next_data["props"]["pageProps"]["payload"]["viewer"] + if not "availableBots" in viewer: + raise RuntimeError("Invalid token.") + bot_list = viewer["availableBots"] + + bots = {} + for bot in bot_list: + chat_data = self.get_bot(bot["displayName"].lower()) + bots[chat_data["defaultBotObject"]["nickname"]] = chat_data + + return bots + + def get_bot_names(self): + bot_names = {} + for bot_nickname in self.bots: + bot_obj = self.bots[bot_nickname]["defaultBotObject"] + bot_names[bot_nickname] = bot_obj["displayName"] + return bot_names + + def get_channel_data(self, channel=None): + logger.info("Downloading channel data...") + r = request_with_retries(self.session.get, self.settings_url) + data = r.json() + + self.formkey = data["formkey"] + return data["tchannelData"] + + def get_websocket_url(self, channel=None): + if channel is None: + channel = self.channel + query = f'?min_seq={channel["minSeq"]}&channel={channel["channel"]}&hash={channel["channelHash"]}' + return f'wss://{self.ws_domain}.tch.{channel["baseHost"]}/up/{channel["boxName"]}/updates'+query + + def send_query(self, query_name, variables): + for i in range(20): + payload = generate_payload(query_name, variables) + r = request_with_retries( + self.session.post, self.gql_url, json=payload, headers=self.gql_headers) + data = r.json() + if data["data"] == None: + logger.warn( + f'{query_name} returned an error: {data["errors"][0]["message"]} | Retrying ({i+1}/20)') + time.sleep(2) + continue + + return r.json() + + raise RuntimeError(f'{query_name} failed too many times.') + + def subscribe(self): + logger.info("Subscribing to mutations") + result = self.send_query("SubscriptionsMutation", { + "subscriptions": [ + { + "subscriptionName": "messageAdded", + "query": queries["MessageAddedSubscription"] + }, + { + "subscriptionName": "viewerStateUpdated", + "query": queries["ViewerStateUpdatedSubscription"] + } + ] + }) + + def ws_run_thread(self): + kwargs = {} + if self.proxy: + proxy_parsed = urlparse(self.proxy) + kwargs = { + "proxy_type": proxy_parsed.scheme, + "http_proxy_host": proxy_parsed.hostname, + "http_proxy_port": proxy_parsed.port + } + + self.ws.run_forever(**kwargs) + + def connect_ws(self): + self.ws = websocket.WebSocketApp( + self.get_websocket_url(), + header={"User-Agent": user_agent}, + on_message=self.on_message, + on_open=self.on_ws_connect, + on_error=self.on_ws_error, + on_close=self.on_ws_close + ) + t = threading.Thread(target=self.ws_run_thread, daemon=True) + t.start() + while not self.ws_connected: + time.sleep(0.01) + + def disconnect_ws(self): + if self.ws: + self.ws.close() + self.ws_connected = False + + def on_ws_connect(self, ws): + self.ws_connected = True + + def on_ws_close(self, ws, close_status_code): + self.ws_connected = False + logger.warn(f"Websocket closed with status {close_status_code}") + + def on_ws_error(self, ws, error): + self.disconnect_ws() + self.connect_ws() + + def on_message(self, ws, msg): + try: + data = json.loads(msg) + + if not "messages" in data: + return + + for message_str in data["messages"]: + message_data = json.loads(message_str) + if message_data["message_type"] != "subscriptionUpdate": + continue + message = message_data["payload"]["data"]["messageAdded"] + + copied_dict = self.active_messages.copy() + for key, value in copied_dict.items(): + # add the message to the appropriate queue + if value == message["messageId"] and key in self.message_queues: + self.message_queues[key].put(message) + return + + # indicate that the response id is tied to the human message id + elif key != "pending" and value == None and message["state"] != "complete": + self.active_messages[key] = message["messageId"] + self.message_queues[key].put(message) + return + + except Exception: + logger.error(traceback.format_exc()) + self.disconnect_ws() + self.connect_ws() + + def send_message(self, chatbot, message, with_chat_break=False, timeout=20): + # if there is another active message, wait until it has finished sending + while None in self.active_messages.values(): + time.sleep(0.01) + + # None indicates that a message is still in progress + self.active_messages["pending"] = None + + logger.info(f"Sending message to {chatbot}: {message}") + # reconnect websocket + if not self.ws_connected: + self.disconnect_ws() + self.connect_ws() + message_data = self.send_query("SendMessageMutation", { + "bot": chatbot, + "query": message, + "chatId": self.bots[chatbot]["chatId"], + "source": None, + "withChatBreak": with_chat_break + }) + del self.active_messages["pending"] + + if not message_data["data"]["messageEdgeCreate"]["message"]: + raise RuntimeError(f"Daily limit reached for {chatbot}.") + try: + human_message = message_data["data"]["messageEdgeCreate"]["message"] + human_message_id = human_message["node"]["messageId"] + except TypeError: + raise RuntimeError( + f"An unknown error occured. Raw response data: {message_data}") + + # indicate that the current message is waiting for a response + self.active_messages[human_message_id] = None + self.message_queues[human_message_id] = queue.Queue() + + last_text = "" + message_id = None + while True: + try: + message = self.message_queues[human_message_id].get( + timeout=timeout) + except queue.Empty: + del self.active_messages[human_message_id] + del self.message_queues[human_message_id] + raise RuntimeError("Response timed out.") + + # only break when the message is marked as complete + if message["state"] == "complete": + if last_text and message["messageId"] == message_id: + break + else: + continue + + # update info about response + message["text_new"] = message["text"][len(last_text):] + last_text = message["text"] + message_id = message["messageId"] + + yield message + + del self.active_messages[human_message_id] + del self.message_queues[human_message_id] + + def send_chat_break(self, chatbot): + logger.info(f"Sending chat break to {chatbot}") + result = self.send_query("AddMessageBreakMutation", { + "chatId": self.bots[chatbot]["chatId"] + }) + return result["data"]["messageBreakCreate"]["message"] + + def get_message_history(self, chatbot, count=25, cursor=None): + logger.info(f"Downloading {count} messages from {chatbot}") + + if cursor == None: + chat_data = self.get_bot(self.bot_names[chatbot]) + if not chat_data["messagesConnection"]["edges"]: + return [] + cursor = chat_data["messagesConnection"]["edges"][-1]["cursor"] + + cursor = str(cursor) + if count > 50: + messages = self.get_message_history( + chatbot, count=50, cursor=cursor) + while count > 0: + new_cursor = messages[0]["cursor"] + new_messages = self.get_message_history( + chatbot, min(50, count), cursor=new_cursor) + messages = new_messages + messages + count -= 50 + return messages + + result = self.send_query("ChatListPaginationQuery", { + "count": count, + "cursor": cursor, + "id": self.bots[chatbot]["id"] + }) + return result["data"]["node"]["messagesConnection"]["edges"] + + def delete_message(self, message_ids): + logger.info(f"Deleting messages: {message_ids}") + if not type(message_ids) is list: + message_ids = [int(message_ids)] + + result = self.send_query("DeleteMessageMutation", { + "messageIds": message_ids + }) + + def purge_conversation(self, chatbot, count=-1): + logger.info(f"Purging messages from {chatbot}") + last_messages = self.get_message_history(chatbot, count=50)[::-1] + while last_messages: + message_ids = [] + for message in last_messages: + if count == 0: + break + count -= 1 + message_ids.append(message["node"]["messageId"]) + + self.delete_message(message_ids) + + if count == 0: + return + last_messages = self.get_message_history(chatbot, count=50)[::-1] + logger.info(f"No more messages left to delete.") + + +load_queries() \ No newline at end of file diff --git a/quora/cookies.txt b/quora/cookies.txt new file mode 100644 index 00000000..3209efe9 --- /dev/null +++ b/quora/cookies.txt @@ -0,0 +1,9 @@ +SmPiNXZI9hBTuf3viz74PA== +zw7RoKQfeEehiaelYMRWeA== +NEttgJ_rRQdO05Tppx6hFw== +3OnmC0r9njYdNWhWszdQJg== +8hZKR7MxwUTEHvO45TEViw== +Eea6BqK0AmosTKzoI3AAow== +pUEbtxobN_QUSpLIR8RGww== +9_dUWxKkHHhpQRSvCvBk2Q== +UV45rvGwUwi2qV9QdIbMcw== diff --git a/quora/graphql/AddHumanMessageMutation.graphql b/quora/graphql/AddHumanMessageMutation.graphql new file mode 100644 index 00000000..01e6bc8c --- /dev/null +++ b/quora/graphql/AddHumanMessageMutation.graphql @@ -0,0 +1,52 @@ +mutation AddHumanMessageMutation( + $chatId: BigInt! + $bot: String! + $query: String! + $source: MessageSource + $withChatBreak: Boolean! = false +) { + messageCreateWithStatus( + chatId: $chatId + bot: $bot + query: $query + source: $source + withChatBreak: $withChatBreak + ) { + message { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies + chat { + id + shouldShowDisclaimer + } + } + messageLimit{ + canSend + numMessagesRemaining + resetTime + shouldShowReminder + } + chatBreak { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies + } + } +} diff --git a/quora/graphql/AddMessageBreakMutation.graphql b/quora/graphql/AddMessageBreakMutation.graphql new file mode 100644 index 00000000..b28d9903 --- /dev/null +++ b/quora/graphql/AddMessageBreakMutation.graphql @@ -0,0 +1,17 @@ +mutation AddMessageBreakMutation($chatId: BigInt!) { + messageBreakCreate(chatId: $chatId) { + message { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies + } + } +} diff --git a/quora/graphql/AutoSubscriptionMutation.graphql b/quora/graphql/AutoSubscriptionMutation.graphql new file mode 100644 index 00000000..6cf7bf74 --- /dev/null +++ b/quora/graphql/AutoSubscriptionMutation.graphql @@ -0,0 +1,7 @@ +mutation AutoSubscriptionMutation($subscriptions: [AutoSubscriptionQuery!]!) { + autoSubscribe(subscriptions: $subscriptions) { + viewer { + id + } + } +} diff --git a/quora/graphql/BioFragment.graphql b/quora/graphql/BioFragment.graphql new file mode 100644 index 00000000..c4218030 --- /dev/null +++ b/quora/graphql/BioFragment.graphql @@ -0,0 +1,8 @@ +fragment BioFragment on Viewer { + id + poeUser { + id + uid + bio + } +} diff --git a/quora/graphql/ChatAddedSubscription.graphql b/quora/graphql/ChatAddedSubscription.graphql new file mode 100644 index 00000000..664b107f --- /dev/null +++ b/quora/graphql/ChatAddedSubscription.graphql @@ -0,0 +1,5 @@ +subscription ChatAddedSubscription { + chatAdded { + ...ChatFragment + } +} diff --git a/quora/graphql/ChatFragment.graphql b/quora/graphql/ChatFragment.graphql new file mode 100644 index 00000000..605645ff --- /dev/null +++ b/quora/graphql/ChatFragment.graphql @@ -0,0 +1,6 @@ +fragment ChatFragment on Chat { + id + chatId + defaultBotNickname + shouldShowDisclaimer +} diff --git a/quora/graphql/ChatListPaginationQuery.graphql b/quora/graphql/ChatListPaginationQuery.graphql new file mode 100644 index 00000000..6d9ae884 --- /dev/null +++ b/quora/graphql/ChatListPaginationQuery.graphql @@ -0,0 +1,378 @@ +query ChatListPaginationQuery( + $count: Int = 5 + $cursor: String + $id: ID! +) { + node(id: $id) { + __typename + ...ChatPageMain_chat_1G22uz + id + } +} + +fragment BotImage_bot on Bot { + displayName + ...botHelpers_useDeletion_bot + ...BotImage_useProfileImage_bot +} + +fragment BotImage_useProfileImage_bot on Bot { + image { + __typename + ... on LocalBotImage { + localName + } + ... on UrlBotImage { + url + } + } + ...botHelpers_useDeletion_bot +} + +fragment ChatMessageDownvotedButton_message on Message { + ...MessageFeedbackReasonModal_message + ...MessageFeedbackOtherModal_message +} + +fragment ChatMessageDropdownMenu_message on Message { + id + messageId + vote + text + author + ...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 + } + hasClearContext + isDown + ...botHelpers_useDeletion_bot + 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 + defaultBotObject { + hasWelcomeTopics + id + } +} + +fragment ChatMessageSuggestedReplies_message on Message { + suggestedReplies + ...ChatMessageSuggestedReplies_SuggestedReplyButton_message +} + +fragment ChatMessage_chat on Chat { + defaultBotObject { + hasWelcomeTopics + hasSuggestedReplies + disclaimerText + messageLimit { + ...ChatPageRateLimitedBanner_messageLimit + } + ...ChatPageDisclaimer_bot + id + } + ...ChatMessageSuggestedReplies_chat + ...ChatWelcomeView_chat +} + +fragment ChatMessage_message on Message { + id + messageId + text + author + linkifiedText + state + contentType + ...ChatMessageSuggestedReplies_message + ...ChatMessageFeedbackButtons_message + ...ChatMessageOverflowButton_message + ...chatHelpers_isHumanMessage + ...chatHelpers_isBotMessage + ...chatHelpers_isChatBreak + ...chatHelpers_useTimeoutLevel + ...MarkdownLinkInner_message + ...IdAnnotation_node +} + +fragment ChatMessagesView_chat on Chat { + ...ChatMessage_chat + ...ChatWelcomeView_chat + ...IdAnnotation_node + defaultBotObject { + hasWelcomeTopics + 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 { + disclaimerText +} + +fragment ChatPageMainFooter_chat on Chat { + defaultBotObject { + ...ChatPageMainFooter_useAccessMessage_bot + id + } + ...ChatMessageInputView_chat + ...ChatPageShareFooter_chat + ...ChatPageDeleteFooter_chat +} + +fragment ChatPageMainFooter_edges on MessageEdge { + ...ChatMessageInputView_edges +} + +fragment ChatPageMainFooter_useAccessMessage_bot on Bot { + ...botHelpers_useDeletion_bot + ...botHelpers_useViewerCanAccessPrivateBot +} + +fragment ChatPageMain_chat_1G22uz on Chat { + id + chatId + ...ChatPageShareFooter_chat + ...ChatPageDeleteFooter_chat + ...ChatMessagesView_chat + ...MarkdownLinkInner_chat + ...chatHelpers_useUpdateStaleChat_chat + ...ChatSubscriptionPaywallContextWrapper_chat + ...ChatPageMainFooter_chat + messagesConnection(last: $count, before: $cursor) { + edges { + ...ChatMessagesView_edges + ...ChatPageMainFooter_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 IdAnnotation_node on Node { + __isNode: __typename + 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 botHelpers_useDeletion_bot on Bot { + deletionState +} + +fragment botHelpers_useViewerCanAccessPrivateBot on Bot { + isPrivateBot + viewerIsCreator +} + +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 { + id + nickname + } + shouldShowDisclaimer +} + +fragment chatHelpers_useTimeoutLevel on Message { + id + state + text + messageId + chat { + chatId + defaultBotNickname + id + } +} + +fragment chatHelpers_useUpdateStaleChat_chat on Chat { + chatId + defaultBotObject { + contextClearWindowSecs + id + } + ...chatHelpers_useSendChatBreak_chat +} + +fragment chatHelpers_useUpdateStaleChat_message on Message { + creationTime + ...chatHelpers_isChatBreak +} diff --git a/quora/graphql/ChatPaginationQuery.graphql b/quora/graphql/ChatPaginationQuery.graphql new file mode 100644 index 00000000..f2452cd6 --- /dev/null +++ b/quora/graphql/ChatPaginationQuery.graphql @@ -0,0 +1,26 @@ +query ChatPaginationQuery($bot: String!, $before: String, $last: Int! = 10) { + chatOfBot(bot: $bot) { + id + __typename + messagesConnection(before: $before, last: $last) { + pageInfo { + hasPreviousPage + } + edges { + node { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies + } + } + } + } +} diff --git a/quora/graphql/ChatViewQuery.graphql b/quora/graphql/ChatViewQuery.graphql new file mode 100644 index 00000000..c330107d --- /dev/null +++ b/quora/graphql/ChatViewQuery.graphql @@ -0,0 +1,8 @@ +query ChatViewQuery($bot: String!) { + chatOfBot(bot: $bot) { + id + chatId + defaultBotNickname + shouldShowDisclaimer + } +} diff --git a/quora/graphql/DeleteHumanMessagesMutation.graphql b/quora/graphql/DeleteHumanMessagesMutation.graphql new file mode 100644 index 00000000..42692c6e --- /dev/null +++ b/quora/graphql/DeleteHumanMessagesMutation.graphql @@ -0,0 +1,7 @@ +mutation DeleteHumanMessagesMutation($messageIds: [BigInt!]!) { + messagesDelete(messageIds: $messageIds) { + viewer { + id + } + } +} diff --git a/quora/graphql/DeleteMessageMutation.graphql b/quora/graphql/DeleteMessageMutation.graphql new file mode 100644 index 00000000..7b9e36d4 --- /dev/null +++ b/quora/graphql/DeleteMessageMutation.graphql @@ -0,0 +1,7 @@ +mutation deleteMessageMutation( + $messageIds: [BigInt!]! +) { + messagesDelete(messageIds: $messageIds) { + edgeIds + } +} \ No newline at end of file diff --git a/quora/graphql/HandleFragment.graphql b/quora/graphql/HandleFragment.graphql new file mode 100644 index 00000000..f53c484b --- /dev/null +++ b/quora/graphql/HandleFragment.graphql @@ -0,0 +1,8 @@ +fragment HandleFragment on Viewer { + id + poeUser { + id + uid + handle + } +} diff --git a/quora/graphql/LoginWithVerificationCodeMutation.graphql b/quora/graphql/LoginWithVerificationCodeMutation.graphql new file mode 100644 index 00000000..723b1f44 --- /dev/null +++ b/quora/graphql/LoginWithVerificationCodeMutation.graphql @@ -0,0 +1,13 @@ +mutation LoginWithVerificationCodeMutation( + $verificationCode: String! + $emailAddress: String + $phoneNumber: String +) { + loginWithVerificationCode( + verificationCode: $verificationCode + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/quora/graphql/MessageAddedSubscription.graphql b/quora/graphql/MessageAddedSubscription.graphql new file mode 100644 index 00000000..8dc9499c --- /dev/null +++ b/quora/graphql/MessageAddedSubscription.graphql @@ -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 +} diff --git a/quora/graphql/MessageDeletedSubscription.graphql b/quora/graphql/MessageDeletedSubscription.graphql new file mode 100644 index 00000000..54c1c164 --- /dev/null +++ b/quora/graphql/MessageDeletedSubscription.graphql @@ -0,0 +1,6 @@ +subscription MessageDeletedSubscription($chatId: BigInt!) { + messageDeleted(chatId: $chatId) { + id + messageId + } +} diff --git a/quora/graphql/MessageFragment.graphql b/quora/graphql/MessageFragment.graphql new file mode 100644 index 00000000..cc860811 --- /dev/null +++ b/quora/graphql/MessageFragment.graphql @@ -0,0 +1,13 @@ +fragment MessageFragment on Message { + id + __typename + messageId + text + linkifiedText + authorNickname + state + vote + voteReason + creationTime + suggestedReplies +} diff --git a/quora/graphql/MessageRemoveVoteMutation.graphql b/quora/graphql/MessageRemoveVoteMutation.graphql new file mode 100644 index 00000000..d5e6e610 --- /dev/null +++ b/quora/graphql/MessageRemoveVoteMutation.graphql @@ -0,0 +1,7 @@ +mutation MessageRemoveVoteMutation($messageId: BigInt!) { + messageRemoveVote(messageId: $messageId) { + message { + ...MessageFragment + } + } +} diff --git a/quora/graphql/MessageSetVoteMutation.graphql b/quora/graphql/MessageSetVoteMutation.graphql new file mode 100644 index 00000000..76000df0 --- /dev/null +++ b/quora/graphql/MessageSetVoteMutation.graphql @@ -0,0 +1,7 @@ +mutation MessageSetVoteMutation($messageId: BigInt!, $voteType: VoteType!, $reason: String) { + messageSetVote(messageId: $messageId, voteType: $voteType, reason: $reason) { + message { + ...MessageFragment + } + } +} diff --git a/quora/graphql/SendMessageMutation.graphql b/quora/graphql/SendMessageMutation.graphql new file mode 100644 index 00000000..4b0a4383 --- /dev/null +++ b/quora/graphql/SendMessageMutation.graphql @@ -0,0 +1,40 @@ +mutation chatHelpers_sendMessageMutation_Mutation( + $chatId: BigInt! + $bot: String! + $query: String! + $source: MessageSource + $withChatBreak: Boolean! +) { + messageEdgeCreate(chatId: $chatId, bot: $bot, query: $query, source: $source, withChatBreak: $withChatBreak) { + chatBreak { + cursor + node { + id + messageId + text + author + suggestedReplies + creationTime + state + } + id + } + message { + cursor + node { + id + messageId + text + author + suggestedReplies + creationTime + state + chat { + shouldShowDisclaimer + id + } + } + id + } + } +} diff --git a/quora/graphql/SendVerificationCodeForLoginMutation.graphql b/quora/graphql/SendVerificationCodeForLoginMutation.graphql new file mode 100644 index 00000000..45af4799 --- /dev/null +++ b/quora/graphql/SendVerificationCodeForLoginMutation.graphql @@ -0,0 +1,12 @@ +mutation SendVerificationCodeForLoginMutation( + $emailAddress: String + $phoneNumber: String +) { + sendVerificationCode( + verificationReason: login + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/quora/graphql/ShareMessagesMutation.graphql b/quora/graphql/ShareMessagesMutation.graphql new file mode 100644 index 00000000..92e80db5 --- /dev/null +++ b/quora/graphql/ShareMessagesMutation.graphql @@ -0,0 +1,9 @@ +mutation ShareMessagesMutation( + $chatId: BigInt! + $messageIds: [BigInt!]! + $comment: String +) { + messagesShare(chatId: $chatId, messageIds: $messageIds, comment: $comment) { + shareCode + } +} diff --git a/quora/graphql/SignupWithVerificationCodeMutation.graphql b/quora/graphql/SignupWithVerificationCodeMutation.graphql new file mode 100644 index 00000000..06b2826f --- /dev/null +++ b/quora/graphql/SignupWithVerificationCodeMutation.graphql @@ -0,0 +1,13 @@ +mutation SignupWithVerificationCodeMutation( + $verificationCode: String! + $emailAddress: String + $phoneNumber: String +) { + signupWithVerificationCode( + verificationCode: $verificationCode + emailAddress: $emailAddress + phoneNumber: $phoneNumber + ) { + status + } +} diff --git a/quora/graphql/StaleChatUpdateMutation.graphql b/quora/graphql/StaleChatUpdateMutation.graphql new file mode 100644 index 00000000..de203d47 --- /dev/null +++ b/quora/graphql/StaleChatUpdateMutation.graphql @@ -0,0 +1,7 @@ +mutation StaleChatUpdateMutation($chatId: BigInt!) { + staleChatUpdate(chatId: $chatId) { + message { + ...MessageFragment + } + } +} diff --git a/quora/graphql/SubscriptionsMutation.graphql b/quora/graphql/SubscriptionsMutation.graphql new file mode 100644 index 00000000..b864bd60 --- /dev/null +++ b/quora/graphql/SubscriptionsMutation.graphql @@ -0,0 +1,9 @@ +mutation subscriptionsMutation( + $subscriptions: [AutoSubscriptionQuery!]! +) { + autoSubscribe(subscriptions: $subscriptions) { + viewer { + id + } + } +} \ No newline at end of file diff --git a/quora/graphql/SummarizePlainPostQuery.graphql b/quora/graphql/SummarizePlainPostQuery.graphql new file mode 100644 index 00000000..afa2a84c --- /dev/null +++ b/quora/graphql/SummarizePlainPostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizePlainPostQuery($comment: String!) { + summarizePlainPost(comment: $comment) +} diff --git a/quora/graphql/SummarizeQuotePostQuery.graphql b/quora/graphql/SummarizeQuotePostQuery.graphql new file mode 100644 index 00000000..5147c3c5 --- /dev/null +++ b/quora/graphql/SummarizeQuotePostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizeQuotePostQuery($comment: String, $quotedPostId: BigInt!) { + summarizeQuotePost(comment: $comment, quotedPostId: $quotedPostId) +} diff --git a/quora/graphql/SummarizeSharePostQuery.graphql b/quora/graphql/SummarizeSharePostQuery.graphql new file mode 100644 index 00000000..cb4a623c --- /dev/null +++ b/quora/graphql/SummarizeSharePostQuery.graphql @@ -0,0 +1,3 @@ +query SummarizeSharePostQuery($comment: String!, $chatId: BigInt!, $messageIds: [BigInt!]!) { + summarizeSharePost(comment: $comment, chatId: $chatId, messageIds: $messageIds) +} diff --git a/quora/graphql/UserSnippetFragment.graphql b/quora/graphql/UserSnippetFragment.graphql new file mode 100644 index 00000000..17fc8426 --- /dev/null +++ b/quora/graphql/UserSnippetFragment.graphql @@ -0,0 +1,14 @@ +fragment UserSnippetFragment on PoeUser { + id + uid + bio + handle + fullName + viewerIsFollowing + isPoeOnlyUser + profilePhotoURLTiny: profilePhotoUrl(size: tiny) + profilePhotoURLSmall: profilePhotoUrl(size: small) + profilePhotoURLMedium: profilePhotoUrl(size: medium) + profilePhotoURLLarge: profilePhotoUrl(size: large) + isFollowable +} diff --git a/quora/graphql/ViewerInfoQuery.graphql b/quora/graphql/ViewerInfoQuery.graphql new file mode 100644 index 00000000..1ecaf9e8 --- /dev/null +++ b/quora/graphql/ViewerInfoQuery.graphql @@ -0,0 +1,21 @@ +query ViewerInfoQuery { + viewer { + id + uid + ...ViewerStateFragment + ...BioFragment + ...HandleFragment + hasCompletedMultiplayerNux + poeUser { + id + ...UserSnippetFragment + } + messageLimit{ + canSend + numMessagesRemaining + resetTime + shouldShowReminder + } + } +} + diff --git a/quora/graphql/ViewerStateFragment.graphql b/quora/graphql/ViewerStateFragment.graphql new file mode 100644 index 00000000..3cd83e9c --- /dev/null +++ b/quora/graphql/ViewerStateFragment.graphql @@ -0,0 +1,30 @@ +fragment ViewerStateFragment on Viewer { + id + __typename + iosMinSupportedVersion: integerGate(gateName: "poe_ios_min_supported_version") + iosMinEncouragedVersion: integerGate( + gateName: "poe_ios_min_encouraged_version" + ) + macosMinSupportedVersion: integerGate( + gateName: "poe_macos_min_supported_version" + ) + macosMinEncouragedVersion: integerGate( + gateName: "poe_macos_min_encouraged_version" + ) + showPoeDebugPanel: booleanGate(gateName: "poe_show_debug_panel") + enableCommunityFeed: booleanGate(gateName: "enable_poe_shares_feed") + linkifyText: booleanGate(gateName: "poe_linkify_response") + enableSuggestedReplies: booleanGate(gateName: "poe_suggested_replies") + removeInviteLimit: booleanGate(gateName: "poe_remove_invite_limit") + enableInAppPurchases: booleanGate(gateName: "poe_enable_in_app_purchases") + availableBots { + nickname + displayName + profilePicture + isDown + disclaimer + subtitle + poweredBy + } +} + diff --git a/quora/graphql/ViewerStateUpdatedSubscription.graphql b/quora/graphql/ViewerStateUpdatedSubscription.graphql new file mode 100644 index 00000000..03fc73d1 --- /dev/null +++ b/quora/graphql/ViewerStateUpdatedSubscription.graphql @@ -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") +} diff --git a/quora/graphql/__init__.py b/quora/graphql/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/quora/mail.py b/quora/mail.py new file mode 100644 index 00000000..d4aeb564 --- /dev/null +++ b/quora/mail.py @@ -0,0 +1,62 @@ +from requests import Session +from string import ascii_letters +from random import choices + +class Mail: + def __init__(self, proxies: dict = None) -> None: + self.client = Session() + self.client.proxies = None #proxies + self.client.headers = { + "host": "api.mail.tm", + "connection": "keep-alive", + "sec-ch-ua": "\"Google Chrome\";v=\"111\", \"Not(A:Brand\";v=\"8\", \"Chromium\";v=\"111\"", + "accept": "application/json, text/plain, */*", + "content-type": "application/json", + "sec-ch-ua-mobile": "?0", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36", + "sec-ch-ua-platform": "\"macOS\"", + "origin": "https://mail.tm", + "sec-fetch-site": "same-site", + "sec-fetch-mode": "cors", + "sec-fetch-dest": "empty", + "referer": "https://mail.tm/", + "accept-encoding": "gzip, deflate, br", + "accept-language": "en-GB,en-US;q=0.9,en;q=0.8" + } + + def get_mail(self) -> str: + token = ''.join(choices(ascii_letters, k=10)).lower() + + init = self.client.post("https://api.mail.tm/accounts", json={ + "address" : f"{token}@bugfoo.com", + "password": token + }) + + if init.status_code == 201: + resp = self.client.post("https://api.mail.tm/token", json = { + **init.json(), + "password": token + }) + + self.client.headers['authorization'] = 'Bearer ' + resp.json()['token'] + + return f"{token}@bugfoo.com" + + else: + raise Exception("Failed to create email") + + def fetch_inbox(self): + return self.client.get(f"https://api.mail.tm/messages").json()["hydra:member"] + + def get_message(self, message_id: str): + return self.client.get(f"https://api.mail.tm/messages/{message_id}").json() + + def get_message_content(self, message_id: str): + return self.get_message(message_id)["text"] + + +# if __name__ == "__main__": +# client = Mail() +# client.get_mail() + + \ No newline at end of file diff --git a/quora/poe_test.py b/quora/poe_test.py new file mode 100644 index 00000000..cfc94966 --- /dev/null +++ b/quora/poe_test.py @@ -0,0 +1,13 @@ +import quora +from time import sleep + +token = quora.Account.create(proxy = 'xtekky:ogingoi2n3g@geo.iproyal.com:12321',logging = True) +print('token', token) + +sleep(2) + +for response in quora.StreamingCompletion.create(model = 'gpt-4', + prompt = 'hello world', + token = token): + + print(response.completion.choices[0].text, end="", flush=True) \ No newline at end of file diff --git a/testing/poe_test.py b/testing/poe_test.py deleted file mode 100644 index 99c3bea4..00000000 --- a/testing/poe_test.py +++ /dev/null @@ -1,13 +0,0 @@ -import poe -from time import sleep - -token = poe.Account.create(proxy = 'xtekky:ogingoi2n3g@geo.iproyal.com:12321',logging = True) -print('token', token) - -sleep(2) - -for response in poe.StreamingCompletion.create(model = 'gpt-4', - prompt = 'hello world', - token = token): - - print(response.completion.choices[0].text, end="", flush=True) \ No newline at end of file -- cgit v1.2.3