Skip to content

Commit

Permalink
Update: temp fix for ratelimit (cloudflare)
Browse files Browse the repository at this point in the history
  • Loading branch information
snowby666 committed Jun 17, 2024
1 parent 2e8dbd3 commit ec35c27
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 112 deletions.
70 changes: 34 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<details close>
<summary>Authentication</summary><br>
<ul>
<li>Log in with your Quora's token or Poe's token</li>
<li>Log in with your Poe tokens</li>
<li>Auto Proxy requests</li>
<li>Specify Proxy context</li>
</ul>
Expand Down Expand Up @@ -126,12 +126,13 @@ Quick setup for Async Client:
from poe_api_wrapper import AsyncPoeApi
import asyncio
tokens = {
'b': ...,
'lat': ...
'p-b': ...,
'p-lat': ...,
'formkey': ...
}

async def main():
client = await AsyncPoeApi(cookie=tokens).create()
client = await AsyncPoeApi(tokens=tokens).create()
message = "Explain quantum computing in simple terms"
async for chunk in client.send_message(bot="gpt3_5", message=message):
print(chunk["response"], end='', flush=True)
Expand All @@ -142,19 +143,20 @@ asyncio.run(main())
```py
from poe_api_wrapper import PoeExample
tokens = {
'b': ...,
'lat': ...
'p-b': ...,
'p-lat': ...,
'formkey': ...
}
PoeExample(cookie=tokens).chat_with_bot()
PoeExample(tokens=tokens).chat_with_bot()
```
- This library also supports command-line interface:
```ShellSession
poe --b B_TOKEN --lat LAT_TOKEN
poe -b P-B_HERE -lat P-LAT_HERE -f FORMKEY_HERE
```
> [!TIP]
> Type `poe -h` for more info
<img src="https://i.imgur.com/ScZjzbx.png" width="100%" height="auto">
<img src="https://i.imgur.com/oAkTHfB.png" width="100%" height="auto">

## 🦄 Documentation
### Available Default Bots
Expand Down Expand Up @@ -190,18 +192,8 @@ poe --b B_TOKEN --lat LAT_TOKEN
> The data on token limits and word counts listed above are approximate and may not be entirely accurate, as the pre-prompt engineering process of poe.com is private and not publicly disclosed.
### How to get your Token
Poe API Wrapper accepts both quora.com and poe.com tokens. Pick one that works best for you.
#### Quora Tokens
Sign in at https://www.quora.com/

F12 for Devtools (Right-click + Inspect)
- Chromium: Devtools > Application > Cookies > quora.com
- Firefox: Devtools > Storage > Cookies
- Safari: Devtools > Storage > Cookies

Copy the values of `m-b` and `m-lat` cookies

#### Poe Tokens
#### Getting p-b and p-lat cookies
Sign in at https://poe.com/

F12 for Devtools (Right-click + Inspect)
Expand All @@ -211,28 +203,33 @@ F12 for Devtools (Right-click + Inspect)

Copy the values of `p-b` and `p-lat` cookies

> [!NOTE]
> Make sure you have logged in poe.com using **the same email** which registered on quora.com.
#### Getting formkey
There are two ways to get formkey:

F12 for Devtools (Right-click + Inspect)

- 1st Method: Devtools > Network > gql_POST > Headers > Poe-Formkey

Copy the value of `Poe-Formkey`

- 2nd Method: Devtools > Console > Type: `allow pasting` > Paste this script: `window.ereNdsRqhp2Rd3LEW()`

Copy the result

### Basic Usage
- Connecting to the API
```py
# Using poe.com tokens
tokens = {
'b': 'p-b token here',
'lat': 'p-lat token here'
}
# Using quora.com tokens
tokens = {
'b': 'm-b token here',
'lat': 'm-lat token here'
'p-b': 'p-b cookie here',
'p-lat': 'p-lat cookie here'.
'formkey': 'formkey here'
}

from poe_api_wrapper import PoeApi
client = PoeApi(cookie=tokens)
client = PoeApi(tokens=tokens)

# Using Client with auto_proxy (default is False)
client = PoeApi(cookie=tokens, auto_proxy=True)
client = PoeApi(tokens=tokens, auto_proxy=True)

# Passing proxies manually
proxy_context = [
Expand All @@ -241,13 +238,14 @@ proxy_context = [
...
]

client = PoeApi(cookie=tokens, proxy=proxy_context)
client = PoeApi(tokens=tokens, proxy=proxy_context)

# Add cloudflare cookies to pass challenges
tokens = {
'b': 'p-b token here',
'lat': 'p-lat token here',
'__cf_bm': '__cf_bm cookie here,
'p-b': 'p-b cookie here',
'p-lat': 'p-lat cookie here',
'formkey': 'formkey here',
'__cf_bm': '__cf_bm cookie here',
'cf_clearance': 'cf_clearance cookie here'
}
```
Expand Down
44 changes: 27 additions & 17 deletions poe_api_wrapper/api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from time import sleep, time
import cloudscraper
from requests_toolbelt import MultipartEncoder
import os, secrets, string, random, websocket, json, threading, queue, ssl
import os, secrets, string, random, websocket, json, threading, queue, ssl, hashlib
from loguru import logger
from .queries import generate_payload
from .proxies import PROXY
Expand All @@ -28,13 +28,12 @@ class PoeApi:
HEADERS = HEADERS
FORMKEY_PATTERN = r'formkey": "(.*?)"'

def __init__(self, cookie: dict={}, proxy: list=[], auto_proxy: bool=False):
def __init__(self, tokens: dict={}, proxy: list=[], auto_proxy: bool=False):
self.client = None
if not {'b', 'lat'}.issubset(cookie):
raise ValueError("Please provide a valid p-b and p-lat cookies")

self.cookie = cookie
self.formkey = None
if not {'p-b', 'p-lat', 'formkey'}.issubset(tokens):
raise ValueError("Please provide valid p-b, p-lat and formkey")

self.tokens = tokens
self.ws_connecting = False
self.ws_connected = False
self.ws_error = False
Expand All @@ -45,13 +44,24 @@ def __init__(self, cookie: dict={}, proxy: list=[], auto_proxy: bool=False):
self.message_generating = True
self.ws_refresh = 3
self.groups = {}
self.formkey = self.tokens['formkey']

self.client = cloudscraper.create_scraper()
self.client.cookies.update({
'm-b': self.cookie['b'],
'm-lat': self.cookie['lat']
'p-b': self.tokens['p-b'],
'p-lat': self.tokens['p-lat']
})

if { '__cf_bm', 'cf_clearance'}.issubset(tokens):
self.client.cookies.update({
'__cf_bm': tokens['__cf_bm'],
'cf_clearance': tokens['cf_clearance']
})

self.client.headers.update({
'Poe-Formkey': self.formkey,
})

if proxy != [] or auto_proxy == True:
self.select_proxy(proxy, auto_proxy=auto_proxy)
elif proxy == [] and auto_proxy == False:
Expand Down Expand Up @@ -89,7 +99,7 @@ def select_proxy(self, proxy: list, auto_proxy: bool=False):
def send_request(self, path: str, query_name: str="", variables: dict={}, file_form: list=[], knowledge: bool=False):
payload = generate_payload(query_name, variables)
if file_form == []:
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
headers = {'Content-Type': 'application/json'}
else:
fields = {'queryInfo': payload}
if not knowledge:
Expand All @@ -102,7 +112,11 @@ def send_request(self, path: str, query_name: str="", variables: dict={}, file_f
)
headers = {'Content-Type': payload.content_type}
payload = payload.to_string()
response = self.client.post(f'{self.BASE_URL}/poe_api/{path}', data=payload, headers=headers, allow_redirects=True, timeout=30)

headers.update({
"poe-tag-id": hashlib.md5((payload + self.formkey + "4LxgHM6KpFqokX0Ox").encode()).hexdigest(),
})
response = self.client.post(f'{self.BASE_URL}/api/{path}', data=payload, headers=headers, allow_redirects=True, timeout=30)
if response.status_code == 200:
for file in file_form:
try:
Expand All @@ -115,14 +129,10 @@ def send_request(self, path: str, query_name: str="", variables: dict={}, file_f
raise RuntimeError(f"An unknown error occurred. Raw response data: {response.text}")

def get_channel_settings(self):
response_json = self.client.get(f'{self.BASE_URL}/poe_api/settings', headers=self.HEADERS, timeout=30).json()
response_json = self.client.get(f'{self.BASE_URL}/api/settings', headers=self.HEADERS, timeout=30).json()
self.ws_domain = f"tch{random.randint(1, int(1e6))}"[:11]
self.formkey = response_json["formkey"]
self.client.headers.update({
'Quora-Formkey': self.formkey,
})
self.tchannel_data = response_json["tchannelData"]
self.client.headers["Quora-Tchannel"] = self.tchannel_data["channel"]
self.client.headers["Poe-Tchannel"] = self.tchannel_data["channel"]
self.channel_url = f'ws://{self.ws_domain}.tch.{self.tchannel_data["baseHost"]}/up/{self.tchannel_data["boxName"]}/updates?min_seq={self.tchannel_data["minSeq"]}&channel={self.tchannel_data["channel"]}&hash={self.tchannel_data["channelHash"]}'
return self.channel_url

Expand Down
42 changes: 27 additions & 15 deletions poe_api_wrapper/async_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
ASYNC = True
except ImportError:
ASYNC = False
import asyncio, json, queue, random, ssl, threading, websocket, string, secrets, os
import asyncio, json, queue, random, ssl, threading, websocket, string, secrets, os, hashlib
from time import time
from typing import AsyncIterator
from loguru import logger
Expand Down Expand Up @@ -37,16 +37,16 @@
class AsyncPoeApi:
BASE_URL = BASE_URL
HEADERS = HEADERS
def __init__(self, cookie: dict={}, proxy: list=[], auto_proxy: bool=False):
def __init__(self, tokens: dict={}, proxy: list=[], auto_proxy: bool=False):
self.client = None
if not ASYNC:
raise ImportError("Please install Async version using 'pip install poe-api-wrapper[async]'")
if not {'b', 'lat'}.issubset(cookie):
raise ValueError("Please provide a valid p-b and p-lat cookies")
if not {'p-b', 'p-lat', 'formkey'}.issubset(tokens):
raise ValueError("Please provide valid p-b, p-lat, and formkey")

self.proxy = proxy
self.auto_proxy = auto_proxy
self.cookie = cookie
self.tokens = tokens
self.formkey = None
self.ws_connecting = False
self.ws_connected = False
Expand All @@ -58,13 +58,25 @@ def __init__(self, cookie: dict={}, proxy: list=[], auto_proxy: bool=False):
self.message_generating = True
self.ws_refresh = 3
self.groups = {}
self.formkey = self.tokens['formkey']

self.client = AsyncClient(headers=self.HEADERS, timeout=180)
self.client.cookies.update({
'm-b': self.cookie['b'],
'm-lat': self.cookie['lat']
'p-b': self.tokens['p-b'],
'p-lat': self.tokens['p-lat']
})


if { '__cf_bm', 'cf_clearance'}.issubset(tokens):
self.client.cookies.update({
'__cf_bm': tokens['__cf_bm'],
'cf_clearance': tokens['cf_clearance']
})

self.client.headers.update({
'Poe-Formkey': self.formkey,
})

async def create(self):
if self.proxy != [] or self.auto_proxy == True:
await self.select_proxy(self.proxy, auto_proxy=self.auto_proxy)
Expand Down Expand Up @@ -101,7 +113,7 @@ async def select_proxy(self, proxy: list, auto_proxy: bool=False):
async def send_request(self, path: str, query_name: str="", variables: dict={}, file_form: list=[], knowledge: bool=False):
payload = generate_payload(query_name, variables)
if file_form == []:
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
headers = {'Content-Type': 'application/json'}
else:
fields = {'queryInfo': payload}
if not knowledge:
Expand All @@ -114,7 +126,11 @@ async def send_request(self, path: str, query_name: str="", variables: dict={},
)
headers = {'Content-Type': payload.content_type}
payload = payload.to_string()
response = await self.client.post(f'{self.BASE_URL}/poe_api/{path}', data=payload, headers=headers, follow_redirects=True)

headers.update({
"poe-tag-id": hashlib.md5((payload + self.formkey + "4LxgHM6KpFqokX0Ox").encode()).hexdigest(),
})
response = await self.client.post(f'{self.BASE_URL}/api/{path}', data=payload, headers=headers, follow_redirects=True)
if response.status_code == 200:
for file in file_form:
try:
Expand All @@ -127,15 +143,11 @@ async def send_request(self, path: str, query_name: str="", variables: dict={},
raise RuntimeError(f"An unknown error occurred. Raw response data: {response.text}")

async def get_channel_settings(self):
response = await self.client.get(f'{self.BASE_URL}/poe_api/settings', headers=self.HEADERS, follow_redirects=True)
response = await self.client.get(f'{self.BASE_URL}/api/settings', headers=self.HEADERS, follow_redirects=True)
response_json = response.json()
self.ws_domain = f"tch{random.randint(1, int(1e6))}"[:11]
self.formkey = response_json["formkey"]
self.client.headers.update({
'Quora-Formkey': self.formkey,
})
self.tchannel_data = response_json["tchannelData"]
self.client.headers["Quora-Tchannel"] = self.tchannel_data["channel"]
self.client.headers["Poe-Tchannel"] = self.tchannel_data["channel"]
self.channel_url = f'ws://{self.ws_domain}.tch.{self.tchannel_data["baseHost"]}/up/{self.tchannel_data["boxName"]}/updates?min_seq={self.tchannel_data["minSeq"]}&channel={self.tchannel_data["channel"]}&hash={self.tchannel_data["channelHash"]}'
return self.channel_url

Expand Down
11 changes: 6 additions & 5 deletions poe_api_wrapper/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import argparse
from importlib import metadata
from poe_api_wrapper import PoeExample

def main():
parser = argparse.ArgumentParser(prog='poe',description='Poe.com wrapper. Have free access to ChatGPT, Claude, Llama, Gemini, Google-PaLM and more!')
parser.add_argument('--b', help='p-b token for poe.com | m-b token for quora.com', required=True)
parser.add_argument('--lat', help='p-lat token for poe.com | m-lat token for quora.com', required=True)
parser.add_argument('-v','--version',action='version', version='v'+metadata.version('poe-api-wrapper'))
parser.add_argument('-b', help='p-b token for poe.com', required=True)
parser.add_argument('-lat', help='p-lat token for poe.com', required=True)
parser.add_argument('-f', help='formkey for poe.com')
parser.add_argument('-v', '--version',action='version', version='v'+metadata.version('poe-api-wrapper'))
args = parser.parse_args()
from poe_api_wrapper import PoeExample
PoeExample({'b': args.b, 'lat': args.lat}).chat_with_bot()
PoeExample({'p-b': args.b, 'p-lat': args.lat, 'formkey': args.f}).chat_with_bot()

if __name__=='__main__':
main()
6 changes: 3 additions & 3 deletions poe_api_wrapper/example.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@
\033[0m
"""
class PoeExample:
def __init__(self, cookie):
self.cookie = cookie
self.client = PoeApi(cookie=self.cookie)
def __init__(self, tokens):
self.tokens = tokens
self.client = PoeApi(tokens=self.tokens)

def select_bot(self):
bots = {}
Expand Down
20 changes: 11 additions & 9 deletions poe_api_wrapper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
from urllib.parse import urlparse
import cloudscraper

BASE_URL = 'https://www.quora.com'
BASE_URL = 'https://poe.com'
HEADERS = {
'Host': 'www.quora.com',
'Accept': '*/*',
'apollographql-client-version': '1.1.6-65',
'Accept-Language': 'en-US,en;q=0.9',
'User-Agent': 'Poe 1.1.6 rv:65 env:prod (iPhone14,2; iOS 16.2; en_US)',
'apollographql-client-name': 'com.quora.app.Experts-apollo-ios',
'Connection': 'keep-alive',
'Content-Type': 'application/json'
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Edg/115.0.1901.203",
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Sec-Ch-Ua": '"Microsoft Edge";v="123", "Not:A-Brand";v="8", "Chromium";v="123"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": '"Windows"',
"Upgrade-Insecure-Requests": "1",
"Origin": "https://poe.com",
"Referer": "https://poe.com/",
}

SubscriptionsMutation = {
Expand Down
Loading

0 comments on commit ec35c27

Please sign in to comment.