Home

Awesome

Important:

This project has been unmaintained for over a month and does not work. See issue #231 for more details.

Python Poe API

PyPi Version

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:

Installation:

You can install this library by running the following command:

pip3 install poe-api

This library depends on quickjs, which does not have prebuilt binaries available for Python 3.11. Pip will attempt to compile it, but will fail if python-dev is not installed.

On Linux, you can install it via the instructions listed here: https://stackoverflow.com/questions/21530577/fatal-error-python-h-no-such-file-or-directory

On Windows and MacOS, python-dev should be included with your existing Python installation.

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:

Note that excessive usage of this library may lead to your account getting banned. It is recommended that you set your own rate limits, and that you use an alt account that you don't value. See issue #118 for more details. If your requests are infrequent, the risk for a ban is very low.

Using the Client:

To use this library, simply import poe and create a poe.Client instance. The Client class accepts the following arguments:

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:

print(json.dumps(client.bot_names, indent=2))
"""
{
  "chinchilla": "ChatGPT",
  "a2": "Claude-instant",
  "capybara": "Assistant",
  "a2_100k": "Claude-instant-100k",
  "llama_2_7b_chat": "Llama-2-7b",
  "llama_2_13b_chat": "Llama-2-13b",
  "a2_2": "Claude-2-100k",
  "llama_2_70b_chat": "Llama-2-70b",
  "agouti": "ChatGPT-16k",
  "beaver": "GPT-4",
  "vizcacha": "GPT-4-32k",
  "acouchy": "Google-PaLM"
}
"""

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:

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:

Use these arguments if you want the new bot to use your own API (as detailed here):

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 API related arguments:

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:

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.

The client.is_busy function can be used to check if there is currently a message being received.

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:

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:

#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:

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/webp,image/apng,*/*q=0.8,application/signed-exchange;v=b3;q=0.7',
  "Accept-Encoding": "gzip, deflate, br",
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
  "Upgrade-Insecure-Requests": "1"
}

The following headers will be ignored and overwritten:

{
  "Referrer": "https://poe.com/",
  "Origin": "https://poe.com",
  "Host": "poe.com",
  "Cache-Control": "no-cache",
  "Sec-Fetch-Dest": "document",
  "Sec-Fetch-Mode": "navigate",
  "Sec-Fetch-Site": "same-origin",
  "Sec-Fetch-User": "?1",
}

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:

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/>.