[
  {
    "path": "LICENCE",
    "content": "MIT License\n\nCopyright (c) 2023-2024 ultrasev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "\n<div align=\"center\">\n <figure style=\"text-align: align;\">\n     <img src=\"https://s3.bmp.ovh/imgs/2024/03/15/2aa0a21860f0ded7.png\" width=189pt>\n </figure>\n<h2>ChatGPT API (W)rapper</h2>\n\n</div>\n\n把网页版 ChatGPT 封装为一个简单的 API，以便在代码中使用。\n\n> OpenAI 对逆向的限制越来越严格，逆向难度越来越大，且各平台的 API 已经很便宜了，像 Groq 的 Llama 3 API 还一直是免费的，不太建议大家继续研究这类项目了。如果特别需要，可以参考作者的另一个项目 [juchats](https://github.com/ultrasev/juchats)，有更多免费模型可以使用。\n\n# Installation\n```bash\npip3 install git+https://github.com/ultrasev/chatrapper.git\n```\n\n# Usage\n环境变量中设置 `TOKEN`，然后调用 `chat` 函数即可。\n```bash\nexport TOKEN=\"eyJhbGci...\"\n```\n\n在代码中使用 `Rapper`:\n```python\nimport os\nfrom chatrapper import Rapper\ntoken = os.environ.get(\"TOKEN\")\nrapper = Rapper(\n    access_token=token\n    model=\"text-davinci-002-render-sha\"\n)\nrapper(\"鲁迅为什么打周树人？\")\n```\n\n或者有异步需求的话，可以使用 `AsyncRapper`。这种情况下，最好有多个账号支持，单账号下，同一时间只支持一轮对话。\n\n```python\nimport os\nimport asyncio\nfrom chatrapper import AsyncRapper\n\ntoken = os.environ.get(\"TOKEN\")\nrapper = AsyncRapper(\n    access_token=token\n    model=\"text-davinci-002-render-sha\"\n)\nasync def main():\n    print(await rapper(\"鲁迅为什么打周树人？\"))\n\nasyncio.run(main())\n```\n\nDemo:\n\n<figure style=\"text-align: left;\">\n    <img src=\"https://s3.bmp.ovh/imgs/2024/03/15/25ea45935e95e00e.gif\" width=589pt>\n</figure>\n\n\n# Notes\n- 一定要保护好自己的 token，不要泄露给他人。\n- 合理使用 API，调用频率不宜过高，树大易招风，避免触发风控。\n"
  },
  {
    "path": "chatrapper/__init__.py",
    "content": "#!/usr/bin/env python\nimport asyncio\nimport base64\nimport json\nimport typing\nfrom uuid import uuid4\n\nimport httpx\nimport websockets\nimport logging\nfrom websockets.exceptions import ConnectionClosedError, ConnectionClosedOK\n\n\nclass MessageDeserializer(object):\n    def __init__(self, data: str) -> None:\n        self.data = data.lstrip(\"data: \").strip()\n\n    def __str__(self) -> str:\n        try:\n            js = json.loads(self.data)\n            return js['message']['content']['parts'][0]\n        except json.decoder.JSONDecodeError:\n            return \"\"\n        except KeyError:\n            logging.error(f\"Error: {self.data}\")\n            return \"\"\n\n\nclass AsyncRapper(object):\n    def __init__(self,\n                 access_token: str,\n                 model: str = \"text-davinci-002-render-sha\") -> None:\n        \"\"\" API (w)rapper for OpenAI's ChatGPT.\n        Args:\n            access_token (str): ChatGPT access token, acquired from https://chat.openai.com/api/auth/session\n            model (str): model name, options include:\n                - \"text-davinci-002-render-sha\", default model for ChatGPT-3.5\n                - \"GPT-4\", GPT-4 model\n        \"\"\"\n        self.access_token = access_token\n        self.model = model\n\n    async def _stream_from_wss(self, chunk: str) -> typing.AsyncGenerator[str, None]:\n        url = json.loads(chunk)['wss_url']\n        async with websockets.connect(url) as websocket:\n            while True:\n                try:\n                    response = await websocket.recv()\n                    body = json.loads(response)[\"body\"]\n                    body = base64.b64decode(body).decode('utf-8')\n                    if 'DONE' in body:\n                        break\n                    yield body\n                except ConnectionClosedOK:\n                    break\n                except ConnectionClosedError:\n                    break\n\n    async def stream(self,\n                     text: str) -> typing.AsyncGenerator[str, None]:\n        body = {\n            \"action\": \"next\",\n            \"arkose_token\": \"null\",\n            \"conversation_mode\": {\"kind\": \"primary_assistant\"},\n            \"force_paragen\": False,\n            \"force_rate_limit\": False,\n            \"history_and_training_disabled\": True,\n            \"messages\": [{\n                \"metadata\": {},\n                \"author\": {\n                    \"role\": \"user\"\n                },\n                \"content\": {\n                    \"content_type\": \"text\",\n                    \"parts\": [text]\n                }\n            }],\n            \"model\": self.model,\n            \"parent_message_id\": str(uuid4()),\n            \"timezone_offset_min\": -330,\n            \"stream\": True\n        }\n\n        async with httpx.AsyncClient() as client:\n            async with client.stream(\n                'POST',\n                url=\"https://chat.openai.com/backend-api/conversation\",\n                headers={\n                    \"accept\": \"text/event-stream\",\n                    \"accept-language\": \"en-US\",\n                    \"authorization\": f\"Bearer {self.access_token}\",\n                    \"content-type\": \"application/json\",\n                    \"sec-fetch-dest\": \"empty\",\n                    \"sec-fetch-mode\": \"cors\",\n                    \"sec-fetch-site\": \"same-origin\",\n                    \"Referer\": \"https://chat.openai.com/\",\n                    \"Referrer-Policy\": \"strict-origin-when-cross-origin\",\n                    \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36\"\n                },\n                    data=json.dumps(body)) as response:\n                async for chunk in response.aiter_text():\n                    chunk = chunk.lstrip(\"data: \").strip()\n                    if \"wss_url\" in chunk:\n                        async for x in self._stream_from_wss(chunk):\n                            yield str(MessageDeserializer(x))\n                    else:\n                        yield str(MessageDeserializer(chunk))\n\n    async def __call__(self, text: str) -> str:\n        prev = \"\"\n        async for x in self.stream(text):\n            print(x.replace(prev, \"\"), end=\"\", flush=True)\n            prev = max(prev, x, key=len)\n        return prev\n\n\nclass Rapper(object):\n    def __init__(self,\n                 access_token: str,\n                 model: str = \"text-davinci-002-render-sha\") -> None:\n        self._proxy = AsyncRapper(access_token, model)\n\n    def __call__(self, text: str) -> str:\n        return asyncio.run(self._proxy(text))\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = \"chatrapper\"\nversion = \"0.1.0\"\ndescription = \"\"\nauthors = [\"ultrasev <i@ultrasev.com>\"]\nreadme = \"README.md\"\npackages = [{include = \"chatrapper\"}]\n\n[tool.poetry.dependencies]\npython = \"^3.8\"\nuuid = \"^1.30\"\nhttpx = \"^0.27.0\"\nwebsockets = \"^12.0\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  }
]