Python Poe API
This is a reverse engineered API wrapper for Quora's Poe, which allows you free access to OpenAI's ChatGPT and GPT-4, as well as Anthropic's Claude.
Table of Contents:
Table of contents generated with markdown-toc.
Features:
- Log in with token
- Proxy requests + websocket
- Download bot list
- Send messages
- Stream bot responses
- Clear conversation context
- Download conversation history
- Delete messages
- Purge an entire conversation
- Create custom bots
- Edit your custom bots
- Use pre-existing third party bots
Installation:
You can install this library by running the following command:
pip3 install poe-api
Documentation:
Examples can be found in the /examples
directory. To run these examples, pass in your token as a command-line argument.
python3 examples/temporary_message.py "TOKEN_HERE"
Finding Your Token:
Log into Poe on any desktop web browser, then open your browser's developer tools (also known as "inspect") and look for the value of the p-b
cookie in the following menus:
- Chromium: Devtools > Application > Cookies > poe.com
- Firefox: Devtools > Storage > Cookies
- Safari: Devtools > Storage > Cookies
Using the Client:
To use this library, simply import poe
and create a poe.Client
instance. The Client class accepts the following arguments:
token
- The token to use.proxy = None
- The proxy to use, in the formatprotocol://host:port
. Thesocks5h
protocol is recommended, as it also proxies the DNS queries.device_id = None
- The device ID to use. If this is not specified, it will be randomly generated and stored on the disk.headers = headers
- The headers to use. This defaults to the headers specified inpoe.headers
.client_identifier = client_identifier
- The client identifier that will be passed into the TLS client library. This defaults to the one specified inpoe.client_identifier
.
Regular Example:
import poe
client = poe.Client("TOKEN_HERE")
Proxied Example:
import poe
client = poe.Client("TOKEN_HERE", proxy="socks5h://178.62.100.151:59166")
Note that the following examples assume client
is the name of your poe.Client
instance. If the token is invalid, a RuntimeError will be raised.
Downloading the Available Bots:
The client downloads all of the available bots upon initialization and stores them within client.bots
. A dictionary that maps bot codenames to their display names can be found at client.bot_names
. If you want to refresh these values, you can call client.get_bots
. This function takes the following arguments:
download_next_data = True
- Whether or not to re-download the__NEXT_DATA__
, which is required if the bot list has changed.
print(json.dumps(client.bot_names, indent=2))
"""
{
"capybara": "Sage",
"a2": "Claude-instant",
"nutria": "Dragonfly",
"a2_100k": "Claude-instant-100k",
"beaver": "GPT-4",
"chinchilla": "ChatGPT",
"a2_2": "Claude+"
}
"""
Note that, on free accounts, Claude+ (a2_2) has a limit of 3 messages per day and GPT-4 (beaver) has a limit of 1 message per day. Claude-instant-100k (c2_100k) is completely inaccessible for free accounts. For all the other chatbots, there seems to be a rate limit of 10 messages per minute.
Using 3rd Party Bots:
To get a list of 3rd party bots, use client.explore_bots
, which accepts the following arguments:
end_cursor = None
- The cursor to use when fetching the list.count = 25
- The number of bots that is returned.
The function will return a dict containing a list of bots and the cursor for the next page:
print(json.dumps(client.explore_bots(count=1), indent=2))
"""
{
"bots": [
{
"id": "Qm90OjEwMzI2MDI=",
"displayName": "leocooks",
"deletionState": "not_deleted",
"image": {
"__typename": "UrlBotImage",
"url": "https://qph.cf2.quoracdn.net/main-thumb-pb-1032602-200-uorvomwowfgmatdvrtwajtwwqlujmmgu.jpeg"
},
"botId": 1032602,
"followerCount": 1922,
"description": "Above average meals for average cooks, made simple by world-renowned chef, Leonardo",
"__typename": "Bot"
}
],
"end_cursor": "1000172"
}
"""
To get a specific third party bot, you can use client.get_bot_by_codename
, which accept's the bot's codename as its only argument.
client.get_bot_by_codename("JapaneseTutor")
Since display names are the same as the codenames for custom bots, you can simply pass the bot's display name into client.send_message
to send it a message.
Creating New Bots:
You can create a new bot using the client.create_bot
function, which accepts the following arguments:
handle
- The handle of the new bot.prompt = ""
- The prompt for the new bot.base_model = "chinchilla"
- The model that the new bot uses. This must be either"chinchilla"
(ChatGPT) or"a2"
(Claude).description = ""
- The description for the new bot.intro_message = ""
- The intro message for the new bot. If this is an empty string then the bot will not have an intro message.prompt_public = True
- Whether or not the prompt should be publicly visible.pfp_url = None
- The URL for the bot's profile picture. Currently, there is no way to actually upload a custom image using this library.linkification = False
- Whether or not the bot should turn some text in the response into clickable links.markdown_rendering = True
- Whether or not to enable markdown rendering for the bot's responses.suggested_replies = False
- Whether or not the bot should suggest possible replies after each response.private = False
- Whether or not the bot should be private.
Use these arguments if you want the new bot to use your own API (as detailed here):
api_key = None
- The API key for the new bot.api_bot = False
- Whether or not the bot has API functionally enabled.api_url = None
- The API URL for the new bot.
A full example of how to create and edit bots is located at examples/create_bot.py
.
new_bot = client.create_bot(bot_name, "prompt goes here", base_model="a2")
Editing a Bot:
You can edit a custom bot using the client.edit_bot
function, which accepts the following arguments:
bot_id
- ThebotId
of the bot to edit.handle
- The handle of the bot to edit.prompt
- The prompt for the new bot.base_model = "chinchilla"
- The new model that the bot uses. This must be either"chinchilla"
(ChatGPT) or"a2"
(Claude). Previously, it was possible to set this to"beaver"
(GPT-4), which would bypass the free account restrictions, but this is now patched.description = ""
- The new description for the bot.intro_message = ""
- The new intro message for the bot. If this is an empty string then the bot will not have an intro message.prompt_public = True
- Whether or not the prompt should be publicly visible.pfp_url = None
- The URL for the bot's profile picture. Currently, there is no way to actually upload a custom image using this library.linkification = False
- Whether or not the bot should turn some text in the response into clickable links.markdown_rendering = True
- Whether or not to enable markdown rendering for the bot's responses.suggested_replies = False
- Whether or not the bot should suggest possible replies after each response.private = False
- Whether or not the bot should be private.
Bot API related arguments:
api_key = None
- The new API key for the bot.api_url = None
- The new API URL for the bot.
A full example of how to create and edit bots is located at examples/create_bot.py
.
edit_result = client.edit_bot(1086981, "bot_handle_here", base_model="a2")
Sending Messages:
You can use the client.send_message
function to send a message to a chatbot, which accepts the following arguments:
chatbot
- The codename of the chatbot. (example:capybara
)message
- The message to send to the chatbot.with_chat_break = False
- Whether the conversation context should be cleared.timeout = 20
- The max number of seconds in between received chunks until aRuntimeError
is raised.
The function is a generator which returns the most recent version of the generated message whenever it is updated.
Streamed Example:
message = "Summarize the GNU GPL v3"
for chunk in client.send_message("capybara", message):
print(chunk["text_new"], end="", flush=True)
Non-Streamed Example:
message = "Summarize the GNU GPL v3"
for chunk in client.send_message("capybara", message):
pass
print(chunk["text"])
You can also send multiple messages in parallel using threading
and receive their responses separately, as demonstrated in /examples/parallel_messages.py
. Note that if you send messages too fast, the server will give an error, but the request will eventually succeed.
Clearing the Conversation Context:
If you want to clear the the context of a conversation without sending a message, you can use client.send_chat_break
. The only argument is the codename of the bot whose context will be cleared.
client.send_chat_break("capybara")
The function returns the message which represents the chat break.
Downloading Conversation History:
To download past messages in a conversation, use the client.get_message_history
function, which accepts the following arguments:
chatbot
- The codename of the chatbot.count = 25
- The number of messages to download.cursor = None
- The message ID to start at instead of the latest one.
Note that if you don't specify a cursor, the client will have to perform an extra request to determine what the latest cursor is.
The returned messages are ordered from oldest to newest.
message_history = client.get_message_history("capybara", count=10)
print(json.dumps(message_history, indent=2))
"""
[
{
"node": {
"id": "TWVzc2FnZToxMDEwNzYyODU=",
"messageId": 101076285,
"creationTime": 1679298157718888,
"text": "",
"author": "chat_break",
"linkifiedText": "",
"state": "complete",
"suggestedReplies": [],
"vote": null,
"voteReason": null,
"__typename": "Message"
},
"cursor": "101076285",
"id": "TWVzc2FnZUVkZ2U6MTAxMDc2Mjg1OjEwMTA3NjI4NQ=="
},
...
]
"""
Deleting Messages:
To delete messages, use the client.delete_message
function, which accepts a single argument. You can pass a single message ID into it to delete a single message, or you can pass a list of message IDs to delete multiple messages at once.
#delete a single message
client.delete_message(96105719)
#delete multiple messages at once
client.delete_message([96105719, 96097108, 96097078, 96084421, 96084402])
Purging a Conversation:
To purge an entire conversation, or just the last few messages, you can use the client.purge_conversation
function. This function accepts the following arguments:
chatbot
- The codename of the chatbot.count = -1
- The number of messages to be deleted, starting from the latest one. The default behavior is to delete every single message.
#purge just the last 10 messages
client.purge_conversation("capybara", count=10)
#purge the entire conversation
client.purge_conversation("capybara")
Purging All Conversations:
To purge every conversation in your account, use the client.purge_all_conversations
function. This function doesn't need any arguments.
>>> client.purge_all_conversations()
Getting the Remaining Messages:
To get the number of messages remaining in the quota for a conversation, use the client.get_remaining_messages
function. This function accepts the following arguments:
chatbot
- The codename of the chatbot.
The function will return the number of messages remaining, or None
if the bot does not have a quota.
>>> client.get_remaining_messages("beaver")
1
Misc:
Changing the Logging Level:
If you want to show debug messages, simply call poe.logger.setLevel
.
import poe
import logging
poe.logger.setLevel(logging.INFO)
Setting a Custom User-Agent:
If you want to change the headers that are spoofed, set poe.headers
after importing the library.
To use your browser's own headers, visit this site, and copy-paste its contents.
import poe
poe.headers = {
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.5",
"Te": "trailers",
"Upgrade-Insecure-Requests": "1"
}
The following headers will be ignored and overwritten:
{
"Referrer": "https://poe.com/",
"Origin": "https://poe.com",
"Host": "poe.com",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin"
}
Previously, this was done through poe.user_agent
, but that variable is now completely ignored.
You'd also want to change poe.client_identifier
to match the user-agent that you have set. See the Python-TLS-Client documentation for some sample values. Keep in mind that spoofing Chrome/Firefox versions >= 110 may be detectable.
poe.client_identifier = "chrome_107"
Setting a Custom Device ID:
If you want to change the device ID that is being spoofed, you can use the poe.set_device_id
, which accepts the following arguments:
user_id
- The user ID of the account you want to change the device ID for. The user ID can be found atclient.viewer["poeUser"]["id"]
.device_id
- The new device ID. This is a 32 character UUID string in the following format:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
poe.set_device_id("UGMlVXqlcLYyMOATMDsKNTMz", "6d659b04-043a-41f8-97c7-fb7d7fe9ad34")
The device IDs are saved to ~/.config/poe-api/device_id.json
on Unix-like systems, and C:\Users\<user>\AppData\Roaming\poe-api\device_id.json
on Windows.
Additionally, the poe.get_device_id
function or client.device_id
can be used to retrieve the saved device ID.
>>> poe.get_device_id("UGMlVXqlcLYyMOATMDsKNTMz")
#6d659b04-043a-41f8-97c7-fb7d7fe9ad34
>>> client.device_id
#6d659b04-043a-41f8-97c7-fb7d7fe9ad34
Copyright:
This program is licensed under the GNU GPL v3. Most code, with the exception of the GraphQL queries, has been written by me, ading2210.
Reverse engineering the poe-tag-id
header has been done by xtekky in PR #39.
The client.get_remaining_messages
function was written by Snowad14 in PR #46.
Detection avoidance and fetching the third party bots has been done by acheong08 in PR #79.
Most of the GraphQL queries are taken from muharamdani/poe, which is licensed under the ISC License.
Copyright Notice:
ading2210/poe-api: a reverse engineered Python API wrapper 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 <https://www.gnu.org/licenses/>.