[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: DO NOT OPEN A DUPLICATE\ntitle: \"[Bug]: \"\n\nbody:\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the bug you encountered, and that it hasn't been fixed in a recent build/commit.\n      options:\n        - label: I have searched the existing issues and checked the recent builds/commits\n          required: true\n  - type: markdown\n    attributes:\n      value: |\n        *Please fill this form with as much information as possible, don't forget to fill \"What OS...\" and \"What browsers\" and *provide screenshots if possible**\n  - type: textarea\n    id: what-did\n    attributes:\n      label: What happened?\n      description: Tell us what happened in a very clear and simple way\n    validations:\n      required: true\n  - type: textarea\n    id: steps\n    attributes:\n      label: Steps to reproduce the problem\n      description: Please provide us with precise step by step information on how to reproduce the bug\n      value: |\n        1. Go to ....\n        2. Press ....\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    id: what-should\n    attributes:\n      label: What should have happened?\n      description: Tell what you think the normal behavior should be\n    validations:\n      required: true\n  - type: textarea\n    id: version\n    attributes:\n      label: Version where the problem happens\n      description: \"`python3 -m pip show revChatGPT`\"\n    validations:\n      required: true\n  - type: input\n    id: python-version\n    attributes:\n      label: What Python version are you running this with?\n      description: \"`python3 -V`\"\n  - type: dropdown\n    id: platforms\n    attributes:\n      label: What is your operating system ?\n      multiple: true\n      options:\n        - Windows\n        - Linux\n        - MacOS\n        - iOS\n        - Android\n        - Other/Cloud\n  - type: textarea\n    id: cmdargs\n    attributes:\n      label: Command Line Arguments\n      description: Are you using any launching parameters/command line arguments (modified webui-user .bat/.sh) ? If yes, please write them below. Write \"No\" otherwise.\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: logs\n    attributes:\n      label: Console logs\n      description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after your bug happened. If it's very long, provide a link to pastebin or similar service.\n      render: Shell\n    validations:\n      required: true\n  - type: textarea\n    id: misc\n    attributes:\n      label: Additional information\n      description: Please provide us with any relevant additional info or context.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Discussions, questions, and niche issues in Github\n    url: https://github.com/acheong08/EdgeGPT/discussions/new/choose\n    about: For anything that is not an explicit bug\n  - name: Discuss in the Discord\n    url: https://discord.gg/9K2BvbXEHT\n    about: 'Personal server: Chill discussions and more detailed updates'\n  - name: Communicate in the Discord\n    url: https://discord.gg/WMNtbDUjUv\n    about: 'Public server: Large technical community with many high profile members like @transitive-bullshit'\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/custom.md",
    "content": "---\nname: \"Are you sure you are not opening a duplicate? From now on, I will not be reading all issues. Use 👍 reaction on an issue to upvote it. I will be reading the top 5 issues each day.\"\nabout: \"DO NOT PRESS THIS\"\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project\ntitle: \"[Feature Request]: \"\n\nbody:\n  - type: checkboxes\n    attributes:\n      label: Is there an existing issue for this?\n      description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.\n      options:\n        - label: I have searched the existing issues and checked the recent builds/commits\n          required: true\n  - type: markdown\n    attributes:\n      value: |\n        *Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*\n  - type: textarea\n    id: feature\n    attributes:\n      label: What would your feature do ?\n      description: Tell us about your feature in a very clear and simple way, and what problem it would solve\n    validations:\n      required: true\n  - type: textarea\n    id: workflow\n    attributes:\n      label: Proposed workflow\n      description: Please provide us with step by step information on how you'd like the feature to be accessed and used\n      value: |\n        1. Go to ....\n        2. Press ....\n        3. ...\n    validations:\n      required: true\n  - type: textarea\n    id: misc\n    attributes:\n      label: Additional information\n      description: Add any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/GHCR.yml",
    "content": "name: Publish Docker 🐳 images 📦 to GitHub Container Registry\n\non:\n  release:\n    types: [published]\n\njobs:\n  build-release-and-publish-to-ghcr:\n    if: github.event_name == 'release'\n    # Explicitly grant the `secrets.GITHUB_TOKEN` permissions.\n    permissions:\n      # Grant the ability to write to GitHub Packages (push Docker images to\n      # GitHub Container Registry).\n      packages: write\n    name: Build and publish Release Docker 🐳 images 📦 to GitHub Container Registry\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout main\n        uses: actions/checkout@v3\n        with:\n             fetch-depth: 0\n      - name: Get actual patch version\n        id: actual_patch_version\n        run: echo ::set-output name=ACTUAL_PATCH_VERSION::$(echo $GITHUB_REF | cut -d / -f 3 )\n      - name: Get actual minor version\n        id: actual_minor_version\n        run: echo ::set-output name=ACTUAL_MINOR_VERSION::$(echo $GITHUB_REF | cut -d / -f 3  | cut -d \".\" -f 1,2)\n      - name: Get actual major version\n        id: actual_major_version\n        run: echo ::set-output name=ACTUAL_MAJOR_VERSION::$(echo $GITHUB_REF | cut -d / -f 3  | cut -d \".\" -f 1)\n      - name: Docker compliant registry for image pushes\n        id: registry\n        run: echo \"REGISTRY=$(echo 'ghcr.io/${{github.repository }}' | tr '[:upper:]' '[:lower:]')\" >> $GITHUB_ENV\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v4\n        with:\n          images: ghcr.io/${{ github.repository }}\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v2\n        with:\n          platforms: 'arm64'\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v2\n\n      - name: Login to GitHub Container Registry\n        uses: docker/login-action@v2\n        with:\n          registry: ghcr.io\n          # This is the user that triggered the Workflow. In this case, it will\n          # either be the user whom created the Release or manually triggered\n          # the workflow_dispatch.\n          username: ${{ github.actor }}\n          # `secrets.GITHUB_TOKEN` is a secret that's automatically generated by\n          # GitHub Actions at the start of a workflow run to identify the job.\n          # This is used to authenticate against GitHub Container Registry.\n          # See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret\n          # for more detailed information.\n          password: ${{ secrets.GH_TOKEN }}\n\n      - name: Build and push alpine based image\n        uses: docker/build-push-action@v4\n        with:\n          file: Dockerfile\n          context: .\n          push: true # push the image to ghcr\n          tags: |\n            ${{ env.REGISTRY }}:latest\n            ${{ env.REGISTRY }}:${{ steps.actual_patch_version.outputs.ACTUAL_PATCH_VERSION }},\n            ${{ env.REGISTRY }}:${{ steps.actual_minor_version.outputs.ACTUAL_MINOR_VERSION }}\n            ${{ env.REGISTRY }}:${{ steps.actual_major_version.outputs.ACTUAL_MAJOR_VERSION }}\n            ${{ env.REGISTRY }}:${{github.sha}}\n          labels: ${{ steps.meta.outputs.labels }}\n          platforms: linux/amd64,linux/arm64\n          cache-from: type=gha\n          cache-to: type=gha,mode=max\n"
  },
  {
    "path": ".github/workflows/PyPi.yml",
    "content": "name: Upload Python Package\n\non:\n  release:\n    types: [published]\n\npermissions:\n  contents: read\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    if: github.event_name == 'release'\n    steps:\n    - uses: actions/checkout@v3\n    - name: Set up Python\n      uses: actions/setup-python@v4\n      with:\n        python-version: '3.11.0'\n    - name: Install dependencies\n      run: make\n    - name: Build package\n      run: make build\n    - name: Publish package\n      uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29\n      with:\n        user: __token__\n        password: ${{ secrets.PYPI_API_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: tests\n\non:\n  push:\n  pull_request:\n  pull_request_target:\n    types: [ ready_for_review, review_requested, edited]\n\n\njobs:\n  build:\n\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [3.8, 3.11 ]\n\n    steps:\n    - uses: actions/checkout@v2\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions/setup-python@v2\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      env:\n        EDGE_COOKIES: ${{ secrets.EDGE_COOKIES }}\n      run: |\n        python -m pip install pytest pytest-asyncio\n        python -m pip install .\n    - name: Test with pytest\n      run: |\n        pytest . -s\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\n#Pipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\ncookies.json\noutput/*\nREADME.md\n*.json\n\n# Pycharm\n.idea/*\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n  - repo: https://github.com/asottile/reorder_python_imports\n    rev: v3.9.0\n    hooks:\n      - id: reorder-python-imports\n        args: [--py37-plus]\n  - repo: https://github.com/asottile/add-trailing-comma\n    rev: v2.3.0\n    hooks:\n      - id: add-trailing-comma\n        args: [--py36-plus]\n  - repo: https://github.com/asottile/pyupgrade\n    rev: v3.3.1\n    hooks:\n      - id: pyupgrade\n        args: [--py37-plus]\n\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.4.0\n    hooks:\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-yaml\n      - id: debug-statements\n      - id: double-quote-string-fixer\n      - id: name-tests-test\n      - id: requirements-txt-fixer\n  - repo: https://github.com/psf/black\n    rev: 22.10.0\n    hooks:\n      - id: black\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM docker.io/library/python:3.11.3-alpine3.16@sha256:0ba61d06b14e5438aa3428ee46c7ccdc8df5b63483bc91ae050411407eb5cbf4 AS builder\n\nWORKDIR /EdgeGPT\n\nCOPY requirements.txt requirements.txt\n\nRUN pip3 install --no-cache-dir -r requirements.txt\n\nCOPY . .\n\nRUN apk add --no-cache make && \\\nmake init && make build && make ci && apk del make && \\\nrm -Rf /root/.cache/pip\n\n\nENTRYPOINT [\"python3\", \"-m\" , \"EdgeGPT.EdgeGPT\"]\n"
  },
  {
    "path": "LICENSE",
    "content": "This is free and unencumbered software released into the public domain.\n\nAnyone is free to copy, modify, publish, use, compile, sell, or\ndistribute this software, either in source code form or as a compiled\nbinary, for any purpose, commercial or non-commercial, and by any\nmeans.\n\nIn jurisdictions that recognize copyright laws, the author or authors\nof this software dedicate any and all copyright interest in the\nsoftware to the public domain. We make this dedication for the benefit\nof the public at large and to the detriment of our heirs and\nsuccessors. We intend this dedication to be an overt act of\nrelinquishment in perpetuity of all present and future rights to this\nsoftware under copyright law.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\nIN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR\nOTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR\nOTHER DEALINGS IN THE SOFTWARE.\n\nFor more information, please refer to <https://unlicense.org>\n"
  },
  {
    "path": "Makefile",
    "content": ".PHONY: docs\ninit:\n\tpython -m pip install --upgrade pip\n\tpython -m pip install -r ./requirements.txt --upgrade\n\tpython -m pip install build setuptools wheel flake8 --upgrade\nbuild:\n\tpython -m build\nci:\n\tpython -m flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics\n\tpython -m flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics\n\tpython setup.py install\n"
  },
  {
    "path": "docs/CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nacheong@student.dalat.org.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "docs/README_es.md",
    "content": "<div align=\"center\">\n  <img src=\"https://socialify.git.ci/acheong08/EdgeGPT/image?font=Inter&language=1&logo=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F9%2F9c%2FBing_Fluent_Logo.svg&owner=1&pattern=Floating%20Cogs&theme=Auto\" alt=\"EdgeGPT\" width=\"640\" height=\"320\" />\n\n# Edge GPT\n\n_Ingeniería inversa al nuevo chat integrado en Bing_\n\n<a href=\"./README.md\">English</a> -\n<a href=\"./README_zh-cn.md\">简体中文</a> -\n<a href=\"./README_zh-tw.md\">繁體中文</a> -\n<a>Español</a> -\n<a href=\"./README_ja.md\">日本語</a>\n\n</div>\n\n<p align=\"center\">\n  <a href=\"https://github.com/acheong08/EdgeGPT\">\n    <img alt=\"PyPI version\" src=\"https://img.shields.io/pypi/v/EdgeGPT\">\n  </a>\n  <img alt=\"Python version\" src=\"https://img.shields.io/badge/python-3.8+-blue.svg\">\n  <img alt=\"Total downloads\" src=\"https://static.pepy.tech/badge/edgegpt\">\n\n</p>\n\n<details open>\n\n<summary>\n\n# Configuración\n\n</summary>\n\n## Instalación\n\n```bash\npython3 -m pip install EdgeGPT --upgrade\n```\n\n## Requisitos\n\n\n- python 3.8+\n- Una cuenta de Microsoft con acceso a <https://bing.com/chat> (Opcional, dependiendo de tu país)\n- Estar localizado en un país con acceso al nuevo Bing (VPN necesaria para usarios de China)\n- [Selenium](https://pypi.org/project/selenium/) (para la recolección automática de cookies)\n\n## Autenticación\n\n!!! ES PROBABLE QUE YA NO SEA NECESARIO !!!\n\n**En algunas regiones**, Microsoft ha **abierto** la función de chat para todos,\npor lo que es posible que **puedas saltarte este paso**.\nPuedes comprobarlo con un navegador (con el user-agent modificado para parecer Edge),\n**intentando abrir un chat sin haber iniciado sesión**.\n\nSe ha encontrado que es posible que sea **dependiente de tu dirección IP**.\nPor ejemplo, si intentas acceder a la función de chat desde una dirección IP que se conoce\nque **pertenece al rango de un centro de datos** (vServers, servidores raíz, VPN, proxies conocidos, ...),\n**es posible que tengas que iniciar sesión** y sin embargo puedas acceder sin problemas a las funciones\ndesde tu casa.\n\nSi recibes el siguiente error, puedes probar **usando cookies** y viendo si funciona:\n\n`Exception: Authentication failed. You have not been accepted into the beta.`\n\n### Recolección de cookies\n\n1. Necesitas un navegador que _parezca_ Microsoft Edge.\n\n * a) (Fácil) Instala la última versión de Microsoft Edge\n * b) (Avanzado) De forma alternativa, puedes usar cualquier navegador y\n   cambiar el user-agent para que parezca que estás usando Edge\n   (e.g., `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51`).\n   Puedes hacer esto con extensiones del estilo \"User-Agent Switcher and Manager\" para [Chrome](https://chrome.google.com/webstore/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg)\n   o [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/).\n\n2. Abrir [bing.com/chat](https://bing.com/chat)\n3. Si ves la nueva función de chat, es que puedes continuar...\n4. Instala la extensión cookie editor para [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) o\n   [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/)\n5. Ve a [bing.com](https://bing.com)\n6. Abre la extensión\n7. Pulsa en \"Export\" abajo a la derecha, luego \"Export as JSON\" (Esto guarda la cookie en el portapapeles)\n8. Pega el contenido en un fichero `bing_cookies_*.json`.\n   * NOTA: Los **ficheros de cookies DEBEN seguir el siguiente formato de nombre `bing_cookies_*.json`**,\n   de manera que puedan ser reconocidos por los mecanismos internos de procesado de cookies.\n\n\n\n### Uso de la cookie en la librería:\n```python\ncookies = json.loads(open(\"./path/to/cookies.json\", encoding=\"utf-8\").read())  # might omit cookies option\nbot = await Chatbot.create(cookies=cookies)\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# Cómo usar Chatbot\n\n</summary>\n\n## Ejecución desde la línea de comandos\n\n```\n $ python3 -m EdgeGPT.EdgeGPT -h\n\n        EdgeGPT - A demo of reverse engineering the Bing GPT chatbot\n        Repo: github.com/acheong08/EdgeGPT\n        By: Antonio Cheong\n\n        !help for help\n\n        Type !exit to exit\n\nusage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK]\n                  [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE]\n                  [--history-file HISTORY_FILE] [--locale LOCALE]\n\noptions:\n  -h, --help            show this help message and exit\n  --enter-once\n  --search-result\n  --no-stream\n  --rich\n  --proxy PROXY         Proxy URL (e.g. socks5://127.0.0.1:1080)\n  --wss-link WSS_LINK   WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)\n  --style {creative,balanced,precise}\n  --prompt PROMPT       prompt to start with\n  --cookie-file COOKIE_FILE\n                        path to cookie file\n  --history-file HISTORY_FILE\n                        path to history file\n  --locale LOCALE       your locale (e.g. en-US, zh-CN, en-IE, en-GB)\n```\n(China/US/UK/Norway disponen de mejor soporte para la localización)\n\n## Ejecución en Python\n\n### 1. La clase `Chatbot` y la librería `asyncio` para un control más exhaustivo\n\nUsa Async para una mejor experiencia, por ejemplo:\n\n```python\nimport asyncio, json\nfrom EdgeGPT.EdgeGPT import Chatbot, ConversationStyle\n\nasync def main():\n    bot = await Chatbot.create() # Passing cookies is \"optional\", as explained above\n    response = await bot.ask(prompt=\"Hello world\", conversation_style=ConversationStyle.creative, simplify_response=True)\n    print(json.dumps(response, indent=2)) # Returns\n    \"\"\"\n    {\n        \"text\": str\n        \"author\": str\n        \"sources\": list[dict]\n        \"sources_text\": str\n        \"suggestions\": list[str]\n        \"messages_left\": int\n    }\n    \"\"\"\n    await bot.close()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n### 2) Las clases auxiliares `Query` y `Cookie`\n\nRealiza una petición a la IA de Bing Chat (usa el estilo 'precise' de conversación por defecto)\ny devuelve la respuesta final sin ver todo el resultado de la API:\n\nRecuerda almacenar las cookies en el formato: `bing_cookies_*.json`.\n\n```python\nfrom EdgeGPT.EdgeUtils import Query, Cookie\n\nq = Query(\"What are you? Give your answer as Python code\")\nprint(q)\n```\n\nTambién puedes cambiar el estilo de conversación o el fichero de cookie a usar:\n\n```python\nq = Query(\n  \"What are you? Give your answer as Python code\",\n  style=\"creative\",  # or: 'balanced', 'precise'\n  cookie_file=\"./bing_cookies_alternative.json\"\n)\n```\n\nObtén rápidamente la respuesta, fragmentos de código, lista de fuentes/referencias,\no las preguntas sugeridas usando los siguientes atributos:\n\n```python\nq.output\nq.code\nq.suggestions\nq.sources       # for the full json output\nq.sources_dict  # for a dictionary of titles and urls\n```\n\nObtén la pregunta inicial o el estilo de conversación usado:\n\n```python\nq.prompt\nq.style\nrepr(q)\n```\n\nAccede a la lista de peticiones realizadas:\n\n```python\nQuery.index  # A list of Query objects; updated dynamically\nQuery.request_count  # A tally of requests made using each cookie file\n```\n\nFinalmente, la clase `Cookie` admite múltiples ficheros de cookie, de manera que\nsi has creado ficheros adicionales de cookies usando el formato de nombrado\n`bing_cookies_*.json`, las peticiones intentarán usar automáticamente el siguiente\nfichero (alfabéticamente) si has sobrepasado el límite diario de peticiones (actualmente limitado a 200).\n\nPrincipales atributos que tienes a disposición:\n\n```python\nCookie.current_file_index\nCookie.dirpath\nCookie.search_pattern  # default is `bing_cookies_*.json`\nCookie.files()  # list as files that match .search_pattern\nCookie.current_filepath\nCookie.current_data\nCookie.import_next()\nCookie.image_token\nCookie.ignore_files\n```\n\n---\n\n## Ejecución en Docker\n\nEste ejemplo asume que dispones de un fichero `cookies.json` en tu directorio actual\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt\n```\n\nPuedes añadir argumentos adicionales de la siguiente manera\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative\n```\n\n</details>\n\n</details>\n\n<details open>\n\n<summary>\n\n# Cómo usar Image generator\n\n</summary>\n\n## Ejecución desde la línea de comandos\n\n```bash\n$ python3 -m ImageGen.ImageGen -h\nusage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -U U                  Auth cookie from browser\n  --cookie-file COOKIE_FILE\n                        File containing auth cookie\n  --prompt PROMPT       Prompt to generate images for\n  --output-dir OUTPUT_DIR\n                        Output directory\n  --quiet               Disable pipeline messages\n  --asyncio             Run ImageGen using asyncio\n```\n\n## Ejecución en Python\n\n### 1) La clase auxiliar `ImageQuery`\n\nGenera imágenes de acuerdo a la petición y las descarga en el directorio actual\n\n```python\nfrom EdgeGPT.EdgeUtils import ImageQuery\n\nq=ImageQuery(\"Meerkats at a garden party in Devon\")\n```\n\nCambia el directorio de descarga para las demás imágenes que se descarguen durante el resto de sesión\n\n```\nQuery.image_dirpath = Path(\"./to_another_folder\")\n```\n\n### 2) Usa las clases `ImageGen` y `asyncio` para un control más minucioso\n\n```python\nfrom EdgeGPT.ImageGen import ImageGen\nimport argparse\nimport json\n\nasync def async_image_gen(args) -> None:\n    async with ImageGenAsync(args.U, args.quiet) as image_generator:\n        images = await image_generator.get_images(args.prompt)\n        await image_generator.save_images(images, output_dir=args.output_dir)\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"-U\", help=\"Auth cookie from browser\", type=str)\n    parser.add_argument(\"--cookie-file\", help=\"File containing auth cookie\", type=str)\n    parser.add_argument(\n        \"--prompt\",\n        help=\"Prompt to generate images for\",\n        type=str,\n        required=True,\n    )\n    parser.add_argument(\n        \"--output-dir\",\n        help=\"Output directory\",\n        type=str,\n        default=\"./output\",\n    )\n    parser.add_argument(\n        \"--quiet\", help=\"Disable pipeline messages\", action=\"store_true\"\n    )\n    parser.add_argument(\n        \"--asyncio\", help=\"Run ImageGen using asyncio\", action=\"store_true\"\n    )\n    args = parser.parse_args()\n    # Load auth cookie\n    with open(args.cookie_file, encoding=\"utf-8\") as file:\n        cookie_json = json.load(file)\n        for cookie in cookie_json:\n            if cookie.get(\"name\") == \"_U\":\n                args.U = cookie.get(\"value\")\n                break\n\n    if args.U is None:\n        raise Exception(\"Could not find auth cookie\")\n\n    if not args.asyncio:\n        # Create image generator\n        image_generator = ImageGen(args.U, args.quiet)\n        image_generator.save_images(\n            image_generator.get_images(args.prompt),\n            output_dir=args.output_dir,\n        )\n    else:\n        asyncio.run(async_image_gen(args))\n\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# Historial de estrellas\n\n</summary>\n\n[![Gráfica historial de estrellas](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date)\n\n</details>\n\n<details open>\n\n<summary>\n\n# Contribuidores\n\n</summary>\n\nEste proyecto existe gracias a todas las personas que apoyan y contribuyen.\n\n <a href=\"https://github.com/acheong08/EdgeGPT/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=acheong08/EdgeGPT\" />\n </a>\n\n</details>\n"
  },
  {
    "path": "docs/README_ja.md",
    "content": "<div align=\"center\">\n  <img src=\"https://socialify.git.ci/acheong08/EdgeGPT/image?font=Inter&language=1&logo=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F9%2F9c%2FBing_Fluent_Logo.svg&owner=1&pattern=Floating%20Cogs&theme=Auto\" alt=\"EdgeGPT\" width=\"640\" height=\"320\" />\n\n# Edge GPT\n\n_Bing の新バージョンのチャット機能のリバースエンジニアリング_\n\n<a href=\"./README.md\">English</a> -\n<a href=\"./README_zh-cn.md\">简体中文</a> -\n<a href=\"./README_zh-tw.md\">繁體中文</a> -\n<a href=\"./README_es.md\">Español</a> -\n<a>日本語</a>\n\n</div>\n\n<p align=\"center\">\n  <a href=\"https://github.com/acheong08/EdgeGPT\">\n    <img alt=\"PyPI version\" src=\"https://img.shields.io/pypi/v/EdgeGPT\">\n  </a>\n  <img alt=\"Python version\" src=\"https://img.shields.io/badge/python-3.8+-blue.svg\">\n\n  <img alt=\"Total downloads\" src=\"https://static.pepy.tech/badge/edgegpt\">\n\n</p>\n\n---\n\n## 設定\n\n### パッケージをインストール\n\n```bash\npython3 -m pip install EdgeGPT --upgrade\n```\n\n### 要件\n\n- python 3.8+\n- <https://bing.com/chat> に早期アクセスできる Microsoft アカウント（必須）\n- New Bing のサポート国で必要（中国本土のVPNは必須）\n\n<details>\n  <summary>\n\n### アクセスの確認 (必須)\n\n  </summary>\n\n- Microsoft Edge の最新バージョンをインストール\n- また、任意のブラウザを使用し、ユーザーエージェントを Edge を使用しているように設定することもできます（例：`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0 Safari/537.36 Edg/111.0.1661.51`）。[Chrome](https://chrome.google.com/webstore/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg) や [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/) の\"User-Agent Switcher and Manager\"のような拡張機能を使えば、簡単に行えます。\n- [bing.com/chat](https://bing.com/chat) を開く\n- チャット機能が表示されたら、準備完了\n\n</details>\n\n<details>\n  <summary>\n\n### 認証の取得 (必須)\n\n  </summary>\n\n- [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) または [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) の Cookie エディター拡張機能をインストール\n- `bing.com` へ移動\n- 拡張機能を開く\n- 右下の\"エクスポート\"から\"JSONとしてエクスポート\"をクリック（これで Cookie がクリップボードに保存されます）\n- クッキーをファイル `cookies.json` に貼り付け\n\n</details>\n\n<details>\n\n<summary>\n\n## チャットボット\n\n</summary>\n\n## 使用方法\n\n### クイックスタート\n\n```\n $ python3 -m EdgeGPT.EdgeGPT -h\n\n        EdgeGPT - A demo of reverse engineering the Bing GPT chatbot\n        Repo: github.com/acheong08/EdgeGPT\n        By: Antonio Cheong\n\n        !help for help\n\n        Type !exit to exit\n        Enter twice to send message or set --enter-once to send one line message\n\nusage: EdgeGPT.py [-h] [--enter-once] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] [--style {creative,balanced,precise}]\n                  [--cookie-file COOKIE_FILE]\n\noptions:\n  -h, --help            show this help message and exit\n  --enter-once\n  --no-stream\n  --rich\n  --proxy PROXY         Proxy URL (e.g. socks5://127.0.0.1:1080)\n  --wss-link WSS_LINK   WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)\n  --style {creative,balanced,precise}\n  --cookie-file COOKIE_FILE\n                        needed if environment variable COOKIE_FILE is not set\n```\n\n---\n\n## Docker での実行\n\nこれは、現在の作業ディレクトリに cookies.json ファイルがあることを前提としています\n\n``` bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt\n```\n\n次のように追加のフラグを追加できます\n\n``` bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative\n```\n\n### 開発者デモ\n\nCookie を渡す 3 つの方法:\n\n- 環境変数: `export COOKIE_FILE=/path/to/cookies.json` 。\n- 引数 `cookie_path` には、次のように `cookies.json` へのパスを指定する:\n\n  ```python\n  bot = Chatbot(cookie_path='./cookies.json')\n  ```\n\n- 次のように、引数 `cookies` で直接クッキーを渡します:\n\n  ```python\n  with open('./cookies.json', 'r') as f:\n      cookies = json.load(f)\n  bot = Chatbot(cookies=cookies)\n  ```\n\n最高のエクスペリエンスを得るには Async を使用してください\n\nより高度な使用例の参照コード:\n\n```python\nimport asyncio\nfrom EdgeGPT.EdgeGPT import Chatbot, ConversationStyle\n\nasync def main():\n    bot = await Chatbot.create()\n    print(await bot.ask(prompt=\"Hello world\", conversation_style=ConversationStyle.creative, wss_link=\"wss://sydney.bing.com/sydney/ChatHub\"))\n    await bot.close()\n\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n\n```\n\n</details>\n\n<details>\n\n<summary>\n\n## 画像ジェネレーター\n\n</summary>\n\n```bash\n$ python3 -m ImageGen.ImageGen -h\nusage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -U U                  Auth cookie from browser\n  --cookie-file COOKIE_FILE\n                        File containing auth cookie\n  --prompt PROMPT       Prompt to generate images for\n  --output-dir OUTPUT_DIR\n                        Output directory\n  --quiet               Disable pipeline messages\n  --asyncio             Run ImageGen using asyncio\n```\n\n### 開発者デモ\n\n```python\nfrom EdgeGPT.ImageGen import ImageGen\nimport argparse\nimport json\n\nasync def async_image_gen(args) -> None:\n    async with ImageGenAsync(args.U, args.quiet) as image_generator:\n        images = await image_generator.get_images(args.prompt)\n        await image_generator.save_images(images, output_dir=args.output_dir)\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"-U\", help=\"Auth cookie from browser\", type=str)\n    parser.add_argument(\"--cookie-file\", help=\"File containing auth cookie\", type=str)\n    parser.add_argument(\n        \"--prompt\",\n        help=\"Prompt to generate images for\",\n        type=str,\n        required=True,\n    )\n    parser.add_argument(\n        \"--output-dir\",\n        help=\"Output directory\",\n        type=str,\n        default=\"./output\",\n    )\n    parser.add_argument(\n        \"--quiet\", help=\"Disable pipeline messages\", action=\"store_true\"\n    )\n    parser.add_argument(\n        \"--asyncio\", help=\"Run ImageGen using asyncio\", action=\"store_true\"\n    )\n    args = parser.parse_args()\n    # 認証クッキーを読み込む\n    with open(args.cookie_file, encoding=\"utf-8\") as file:\n        cookie_json = json.load(file)\n        for cookie in cookie_json:\n            if cookie.get(\"name\") == \"_U\":\n                args.U = cookie.get(\"value\")\n                break\n\n    if args.U is None:\n        raise Exception(\"Could not find auth cookie\")\n\n    if not args.asyncio:\n        # 画像ジェネレーターの作成\n        image_generator = ImageGen(args.U, args.quiet)\n        image_generator.save_images(\n            image_generator.get_images(args.prompt),\n            output_dir=args.output_dir,\n        )\n    else:\n        asyncio.run(async_image_gen(args))\n\n```\n\n</details>\n\n## Star ヒストリー\n\n[![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date)\n\n## コントリビューター\n\nこのプロジェクトが存在するのはコントリビュートしてくださるすべての方々のおかげです。\n\n <a href=\"https://github.com/acheong08/EdgeGPT/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=acheong08/EdgeGPT\" />\n </a>\n"
  },
  {
    "path": "docs/README_zh-cn.md",
    "content": "<div align=\"center\">\n  <img src=\"https://socialify.git.ci/acheong08/EdgeGPT/image?font=Inter&language=1&logo=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F9%2F9c%2FBing_Fluent_Logo.svg&owner=1&pattern=Floating%20Cogs&theme=Auto\" alt=\"EdgeGPT\" width=\"640\" height=\"320\" />\n\n# Edge GPT\n\n_新必应聊天功能的逆向工程_\n\n<a href=\"./README.md\">English</a> -\n<a>简体中文</a> -\n<a href=\"./README_zh-tw.md\">繁體中文</a> -\n<a href=\"./README_es.md\">Español</a> -\n<a href=\"./README_ja.md\">日本語</a>\n\n</div>\n\n<p align=\"center\">\n  <a href=\"https://github.com/acheong08/EdgeGPT\">\n    <img alt=\"PyPI version\" src=\"https://img.shields.io/pypi/v/EdgeGPT\">\n  </a>\n  <img alt=\"Python version\" src=\"https://img.shields.io/badge/python-3.8+-blue.svg\">\n  <img alt=\"Total downloads\" src=\"https://static.pepy.tech/badge/edgegpt\">\n\n</p>\n\n<details open>\n\n<summary>\n\n# 设置\n\n</summary>\n\n## 安装模块\n\n```bash\npython3 -m pip install EdgeGPT --upgrade\n```\n\n## 要求\n\n- python 3.8+\n- 一个可以访问必应聊天的微软账户 <https://bing.com/chat> (可选，视所在地区而定)\n- 需要在 New Bing 支持的国家或地区（中国大陆需使用VPN）\n- [Selenium](https://pypi.org/project/selenium/) (对于需要自动配置cookie的情况)\n\n## 身份验证\n\n基本上不需要了。\n\n**在部分地区**，微软已将聊天功能**开放**给所有人，或许可以**省略这一步**了。可以使用浏览器来确认（将 UA 设置为能表示为 Edge 的），**试一下能不能不登录就可以开始聊天**。\n\n可能也得**看当前所在 IP 地址**。例如，如果试图从一个已知**属于数据中心范围**的 IP 来访问聊天功能（虚拟服务器、根服务器、虚拟专网、公共代理等），**可能就需要登录**；但是要是用家里的 IP 地址访问聊天功能，就没有问题。\n\n如果收到这样的错误，可以试试**提供一个 cookie** 看看能不能解决：\n\n`Exception: Authentication failed. You have not been accepted into the beta.`\n\n### 收集 cookie\n\n1. 获取一个看着像 Microsoft Edge 的浏览器。\n\n- a) (简单) 安装最新版本的 Microsoft Edge\n- b) (高级) 或者, 您可以使用任何浏览器并将用户代理设置为Edge的用户代理 (例如 `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51`). 您可以使用像 \"User-Agent Switcher and Manager\"  [Chrome](https://chrome.google.com/webstore/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg) 和 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/) 这样的扩展轻松完成此操作。\n\n2. 打开 [bing.com/chat](https://bing.com/chat)\n3. 如果您看到聊天功能，就接着下面的步骤...\n4. 安装 [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) 或 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 的 cookie editor 扩展\n5. 移步到 [bing.com](https://bing.com)\n6. 打开扩展程序\n7. 点击右下角的\"导出\" ，然后点击\"导出为 JSON\" (将会把内容保存到你的剪贴板上)\n8. 把你剪贴板上的内容粘贴到 `bing_cookies_*.json` 文件中\n   - 注意：**Cookie 文件名必须遵守正则表达式 `bing_cookies_*.json`**，这样才能让本模块的 cookie 处理程序识别到。\n\n### 在代码中使用 cookie：\n\n```python\ncookies = json.loads(open(\"./path/to/cookies.json\", encoding=\"utf-8\").read()) # 可能会忽略 cookie 选项\nbot = await Chatbot.create(cookies=cookies)\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# 如何使用聊天机器人\n\n</summary>\n\n## 从命令行运行\n\n```\n $ python3 -m EdgeGPT.EdgeGPT -h\n\n        EdgeGPT - A demo of reverse engineering the Bing GPT chatbot\n        Repo: github.com/acheong08/EdgeGPT\n        By: Antonio Cheong\n\n        !help for help\n\n        Type !exit to exit\n\nusage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK]\n                  [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE]\n                  [--history-file HISTORY_FILE] [--locale LOCALE]\n\noptions:\n  -h, --help            show this help message and exit\n  --enter-once\n  --search-result\n  --no-stream\n  --rich\n  --proxy PROXY         Proxy URL (e.g. socks5://127.0.0.1:1080)\n  --wss-link WSS_LINK   WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)\n  --style {creative,balanced,precise}\n  --prompt PROMPT       prompt to start with\n  --cookie-file COOKIE_FILE\n                        path to cookie file\n  --history-file HISTORY_FILE\n                        path to history file\n  --locale LOCALE       your locale (e.g. en-US, zh-CN, en-IE, en-GB)\n```\n（中/美/英/挪具有更好的本地化支持）\n\n## 在 Python 运行\n\n### 1. 使用 `Chatbot` 类和 `asyncio` 类以进行更精细的控制\n\n使用 async 获得最佳体验，例如:\n\n```python\nimport asyncio, json\nfrom EdgeGPT.EdgeGPT import Chatbot, ConversationStyle\n\nasync def main():\n    bot = await Chatbot.create() # 导入 cookie 是“可选”的，如前所述\n    response = await bot.ask(prompt=\"Hello world\", conversation_style=ConversationStyle.creative, simplify_response=True)\n    print(json.dumps(response, indent=2)) # 返回下面这些\n    \"\"\"\n{\n    \"text\": str,\n    \"author\": str,\n    \"sources\": list[dict],\n    \"sources_text\": str,\n    \"suggestions\": list[str],\n    \"messages_left\": int\n}\n    \"\"\"\n    await bot.close()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n\n### 2)  `Query` 和 `Cookie` 助手类\n\n创建一个简易的必应聊天 AI 查询（默认使用精确模式），这样可以只查看主要的文字输出，而不会打出整个 API 响应内容：\n\n注意按照特定格式保存 cookie：`bing_cookies_*.json`.\n\n```python\nfrom EdgeGPT.EdgeUtils import Query, Cookie\n\nq = Query(\"你是谁？用python代码给出回答\")\nprint(q)\n```\n\n保存 cookie 文件的默认目录是 `HOME/bing_cookies`，不过可以这样修改：\n\n```python\nCookie.dir_path = Path(r\"...\")\n```\n\n也可以修改对话模式，或者指定要使用的 cookie 文件：\n\n```python\nq = Query(\n  \"你是谁？用python代码给出回答\",\n  style=\"creative\",  # 或者平衡模式 'balanced' 或者精确模式 'precise'\n  cookie_file=\"./bing_cookies_alternative.json\"\n)\n\n# 使用 `help(Query)` 查看其他支持的参数。\n```\n\n使用以下属性快速提取文本输出、代码片段、来源/参考列表或建议的后续问题：\n\n```python\nq.output  # 或者: print(q)\nq.sources\nq.sources_dict\nq.suggestions\nq.code\nq.code_blocks\nq.code_block_formatsgiven)\n```\n\n获得原始 prompt 和指定的对话模式：\n\n```python\nq.prompt\nq.ignore_cookies\nq.style\nq.simplify_response\nq.locale\nrepr(q)\n```\n\n通过 import `Query` 获取之前的查询:\n\n```python\nQuery.index  # 一个查询对象的列表；是动态更新的\nQuery.image_dir_path\n\n```\n\n最后，`Cookie` 类支持多个 Cookie 文件，因此，如果您使用命名约定 `bing_cookies_*.json` 创建其他 Cookie 文件，那么如果超过了每日请求配额（目前设置为 200），查询将自动尝试使用下一个文件（按字母顺序）。\n\n这些是可以访问的主要属性:\n\n```python\nCookie.current_file_index\nCookie.current_file_path\nCookie.current_data\nCookie.dir_path\nCookie.search_pattern\nCookie.files\nCookie.image_token\nCookie.import_next\nCookie.rotate_cookies\nCookie.ignore_files\nCookie.supplied_files\nCookie.request_count\n```\n\n---\n\n## 使用 Docker 运行\n\n假设当前工作目录有一个文件 `cookies.json`\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt\n```\n\n可以像这样添加任意参数\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative\n```\n\n</details>\n\n</details>\n\n<details open>\n\n<summary>\n\n# 如何使用图片生成器\n\n</summary>\n\n## 从命令行运行\n\n```bash\n$ python3 -m ImageGen.ImageGen -h\nusage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -U U                  Auth cookie from browser\n  --cookie-file COOKIE_FILE\n                        File containing auth cookie\n  --prompt PROMPT       Prompt to generate images for\n  --output-dir OUTPUT_DIR\n                        Output directory\n  --quiet               Disable pipeline messages\n  --asyncio             Run ImageGen using asyncio\n```\n\n## 在 Python 运行\n\n### 1)  `ImageQuery` 助手类\n\n使用一个简易提示生成图像，并下载到当前工作目录：\n\n```python\nfrom EdgeGPT.EdgeUtils import ImageQuery\n\nq=ImageQuery(\"Meerkats at a garden party in Devon\")\n```\n\n在此会话中修改所有后续图像的下载目录：\n\n```\nQuery.image_dirpath = Path(\"./to_another_folder\")\n```\n\n### 2) 使用 `ImageGen` 类和 `asyncio` 类以进行更精细的控制\n\n```python\nfrom EdgeGPT.ImageGen import ImageGen\nimport argparse\nimport json\n\nasync def async_image_gen(args) -> None:\n    async with ImageGenAsync(args.U, args.quiet) as image_generator:\n        images = await image_generator.get_images(args.prompt)\n        await image_generator.save_images(images, output_dir=args.output_dir)\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"-U\", help=\"从浏览器获取的身份认证 cookie\", type=str)\n    parser.add_argument(\"--cookie-file\", help=\"包含认证 cookie 的文件\", type=str)\n    parser.add_argument(\n        \"--prompt\",\n        help=\"用于生成图片的 prompt\",\n        type=str,\n        required=True,\n    )\n    parser.add_argument(\n        \"--output-dir\",\n        help=\"输出目录\",\n        type=str,\n        default=\"./output\",\n    )\n    parser.add_argument(\n        \"--quiet\", help=\"禁用管道消息\", action=\"store_true\"\n    )\n    parser.add_argument(\n        \"--asyncio\", help=\"使用 asyncio 运行 ImageGen\", action=\"store_true\"\n    )\n    args = parser.parse_args()\n    # 加载认证 cookie\n    with open(args.cookie_file, encoding=\"utf-8\") as file:\n        cookie_json = json.load(file)\n        for cookie in cookie_json:\n            if cookie.get(\"name\") == \"_U\":\n                args.U = cookie.get(\"value\")\n                break\n\n    if args.U is None:\n        raise Exception(\"未能找到认证 Cookie\")\n\n    if not args.asyncio:\n        # 创建图像生成器\n        image_generator = ImageGen(args.U, args.quiet)\n        image_generator.save_images(\n            image_generator.get_images(args.prompt),\n            output_dir=args.output_dir,\n        )\n    else:\n        asyncio.run(async_image_gen(args))\n\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# Star 历史\n\n</summary>\n\n[![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date)\n\n</details>\n\n<details open>\n\n<summary>\n\n# 贡献者\n\n</summary>\n\n这个项目的存在要归功于所有做出贡献的人。\n\n <a href=\"https://github.com/acheong08/EdgeGPT/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=acheong08/EdgeGPT\" />\n </a>\n\n</details>\n"
  },
  {
    "path": "docs/README_zh-tw.md",
    "content": "<div align=\"center\">\n  <img src=\"https://socialify.git.ci/acheong08/EdgeGPT/image?font=Inter&language=1&logo=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2F9%2F9c%2FBing_Fluent_Logo.svg&owner=1&pattern=Floating%20Cogs&theme=Auto\" alt=\"EdgeGPT\" width=\"640\" height=\"320\" />\n\n# Edge GPT\n\n_新必應聊天功能的逆向工程_\n\n<a href=\"./README.md\">English</a> -\n<a href=\"./README_zh-cn.md\">简体中文</a> -\n<a>繁體中文</a> -\n<a href=\"./README_es.md\">Español</a> -\n<a href=\"./README_ja.md\">日本語</a>\n\n</div>\n\n<p align=\"center\">\n  <a href=\"https://github.com/acheong08/EdgeGPT\">\n    <img alt=\"PyPI version\" src=\"https://img.shields.io/pypi/v/EdgeGPT\">\n  </a>\n  <img alt=\"Python version\" src=\"https://img.shields.io/badge/python-3.8+-blue.svg\">\n  <img alt=\"Total downloads\" src=\"https://static.pepy.tech/badge/edgegpt\">\n\n</p>\n\n<details open>\n\n<summary>\n\n# 設置\n\n</summary>\n\n## 安裝模組\n\n```bash\npython3 -m pip install EdgeGPT --upgrade\n```\n\n## 要求\n\n- python 3.8+\n- 一個可以訪問必應聊天的微軟帳戶 <https://bing.com/chat> (可選，取決於所在地區)\n- 需要在 New Bing 支持的國家或地區（中國大陸需使用VPN）\n- [Selenium](https://pypi.org/project/selenium/) (對於需要自動配置cookie的情況)\n\n## 認證\n\n基本上不需要了。\n\n**在某些地區**，微軟已將聊天功能**開放**給所有人，或許可以**省略這一步**了。您可以使用瀏覽器進行檢查（將 UA 設置為能表示為 Edge 的），**嘗試能否在不登錄的情況下開始聊天**。\n\n可能也得**看當前所在 IP 位址**。例如，如果試圖從一個已知**屬於數據中心範圍**的 IP 來訪問聊天功能（虛擬伺服器、根伺服器、虛擬專網、公共代理等），**可能就需要登錄**；但是要是用家裡的 IP 位址訪問聊天功能，就沒有問題。\n\n如果收到這樣的錯誤，可以試試**提供一個 cookie** 看看能不能解決：\n\n`Exception: Authentication failed. You have not been accepted into the beta.`\n\n### 收集 cookie\n\n1. 獲取一個看著像 Microsoft Edge 的瀏覽器。\n\n * a) (簡單) 安裝最新版本的 Microsoft Edge\n * b) (高級) 或者, 您可以使用任何瀏覽器並將用戶代理設置為Edge的用戶代理 (例如 `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1661.51`). 您可以使用像 \"User-Agent Switcher and Manager\"  [Chrome](https://chrome.google.com/webstore/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg) 和 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/) 這樣的擴展輕鬆完成此操作.\n\n2. 打開 [bing.com/chat](https://bing.com/chat)\n3. 如果您看到聊天功能，就接著下面的步驟...\n4. 安裝 [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) 或 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 的 cookie editor 擴展\n5. 轉到 [bing.com](https://bing.com)\n6. 打開擴展程式\n7. 單擊右下角的「匯出」，然後按「匯出為 JSON」（這會將您的 cookie 保存到剪貼簿）\n8. 將您剪貼簿上的 cookie 粘貼到檔 `bing_cookies_*.json` 中\n   * 注意：**cookie 檔名必須遵循正則表示式 `bing_cookies_*.json`**，這樣才能讓本模組的 cookie 處理程式識別到。\n\n### 在代碼中使用 cookie：\n\n```python\ncookies = json.loads(open(\"./path/to/cookies.json\", encoding=\"utf-8\").read()) # 可能会忽略 cookie 选项\nbot = await Chatbot.create(cookies=cookies)\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# 如何使用聊天機器人\n\n</summary>\n\n## 從命令列運行\n\n```\n $ python3 -m EdgeGPT.EdgeGPT -h\n\n        EdgeGPT - A demo of reverse engineering the Bing GPT chatbot\n        Repo: github.com/acheong08/EdgeGPT\n        By: Antonio Cheong\n\n        !help for help\n\n        Type !exit to exit\n\nusage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK]\n                  [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE]\n                  [--history-file HISTORY_FILE] [--locale LOCALE]\n\noptions:\n  -h, --help            show this help message and exit\n  --enter-once\n  --search-result\n  --no-stream\n  --rich\n  --proxy PROXY         Proxy URL (e.g. socks5://127.0.0.1:1080)\n  --wss-link WSS_LINK   WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)\n  --style {creative,balanced,precise}\n  --prompt PROMPT       prompt to start with\n  --cookie-file COOKIE_FILE\n                        path to cookie file\n  --history-file HISTORY_FILE\n                        path to history file\n  --locale LOCALE       your locale (e.g. en-US, zh-CN, en-IE, en-GB)\n```\n（中/美/英/挪具有更好的本地化支援）\n\n## 在 Python 運行\n\n### 1. 使用 `Chatbot` 類和 `asyncio` 類以進行更精細的控制\n\n使用 async 獲得最佳體驗，例如:\n\n```python\nimport asyncio, json\nfrom EdgeGPT.EdgeGPT import Chatbot, ConversationStyle\n\nasync def main():\n    bot = await Chatbot.create() # 導入 cookie 是“可選”的，如前所述\n    response = await bot.ask(prompt=\"Hello world\", conversation_style=ConversationStyle.creative, simplify_response=True)\n    print(json.dumps(response, indent=2)) # 返回如下\n    \"\"\"\n{\n    \"text\": str,\n    \"author\": str,\n    \"sources\": list[dict],\n    \"sources_text\": str,\n    \"suggestions\": list[str],\n    \"messages_left\": int\n}\n    \"\"\"\n    await bot.close()\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n\n### 2)  `Query` 和 `Cookie` 助手類\n\n創建一個簡單的必應聊天 AI 查詢（預設情況下使用“精確”對話樣式），這樣可以僅查看主要文本輸出，而不是整個 API 回應：\n\n注意按照特定格式儲存 cookie： ```bing_cookies_*.json```。\n\n```python\nfrom EdgeGPT.EdgeUtils import Query, Cookie\n\nq = Query(\"你是誰？用python代碼給出回答\")\nprint(q)\n```\n\n儲存 Cookie 檔的預設目錄是 `HOME/bing_cookies`，但您可以通過以下方式更改它：\n\n```python\nCookie.dir_path = Path(r\"...\")\n```\n\n或者更改要使用的對話風格或 Cookie 檔：\n\n```python\nq = Query(\n  \"你是誰？用python代碼給出回答\",\n  style=\"creative\",  # 或者平衡模式 'balanced' 或者精確模式 'precise'\n  cookies=\"./bing_cookies_alternative.json\"\n)\n\n# 使用 `help(Query)` 查看其他支持的參數。\n```\n\n使用以下屬性快速提取文字輸出、代碼片段、來源/參考清單或建議的後續問題：\n\n```python\nq.output  # 或者: print(q)\nq.sources\nq.sources_dict\nq.suggestions\nq.code\nq.code_blocks\nq.code_block_formatsgiven)\n```\n\n抓取原始 prompt 與您指定的對話風格：\n\n```python\nq.prompt\nq.ignore_cookies\nq.style\nq.simplify_response\nq.locale\nrepr(q)\n```\n\n通過 import `Query` 獲取進行的先前查詢：\n\n```python\nQuery.index  # 一个查詢物件的串列；是動態更新的\nQuery.image_dir_path\n\n```\n\n最後，`Cookie` 類支援多個 cookie 檔，因此，如果您使用命名約定 `bing_cookies_*.json` 創建其他 cookie 檔，則如果您的請求數已超出每日配額（當前設置為 200），您的查詢將自動嘗試使用下一個檔（按字母順序）。\n\n以下是您可以獲得的主要屬性：\n\n```python\nCookie.current_file_index\nCookie.current_file_path\nCookie.current_data\nCookie.dir_path\nCookie.search_pattern\nCookie.files\nCookie.image_token\nCookie.import_next\nCookie.rotate_cookies\nCookie.ignore_files\nCookie.supplied_files\nCookie.request_count\n```\n\n---\n\n## 使用 Docker 運行\n\n假設在當前工作目錄中有一個檔 `cookies.json`\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt\n```\n\n可以像這樣添加任意參數\n\n```bash\n\ndocker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative\n```\n\n</details>\n\n</details>\n\n<details open>\n\n<summary>\n\n# 如何使用圖像生成器\n\n</summary>\n\n## 從命令列運行\n\n```bash\n$ python3 -m ImageGen.ImageGen -h\nusage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio]\n\noptional arguments:\n  -h, --help            show this help message and exit\n  -U U                  Auth cookie from browser\n  --cookie-file COOKIE_FILE\n                        File containing auth cookie\n  --prompt PROMPT       Prompt to generate images for\n  --output-dir OUTPUT_DIR\n                        Output directory\n  --quiet               Disable pipeline messages\n  --asyncio             Run ImageGen using asyncio\n```\n\n## 在 Python 運行\n\n### 1)  `ImageQuery` 助手類\n\n根據一個簡單的提示產生圖像並下載到目前工作目錄：\n\n```python\nfrom EdgeGPT.EdgeUtils import ImageQuery\n\nq=ImageQuery(\"Meerkats at a garden party in Devon\")\n```\n\n在此工作階段中修改所有後續圖像的下載目錄：\n\n```\nQuery.image_dirpath = Path(\"./to_another_folder\")\n```\n\n### 2) 使用 `ImageGen` 類和 `asyncio` 類以進行更精細的控制\n\n```python\nfrom EdgeGPT.ImageGen import ImageGen\nimport argparse\nimport json\n\nasync def async_image_gen(args) -> None:\n    async with ImageGenAsync(args.U, args.quiet) as image_generator:\n        images = await image_generator.get_images(args.prompt)\n        await image_generator.save_images(images, output_dir=args.output_dir)\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"-U\", help=\"來自瀏覽器的身份驗證 cookie\", type=str)\n    parser.add_argument(\"--cookie-file\", help=\"包含身份驗證 cookie 的檔\", type=str)\n    parser.add_argument(\n        \"--prompt\",\n        help=\"用于產生圖像的 prompt\",\n        type=str,\n        required=True,\n    )\n    parser.add_argument(\n        \"--output-dir\",\n        help=\"輸出目錄\",\n        type=str,\n        default=\"./output\",\n    )\n    parser.add_argument(\n        \"--quiet\", help=\"禁用管道消息\", action=\"store_true\"\n    )\n    parser.add_argument(\n        \"--asyncio\", help=\"使用 asyncio 運行 ImageGen\", action=\"store_true\"\n    )\n    args = parser.parse_args()\n    # 載入身份驗證 cookie\n    with open(args.cookie_file, encoding=\"utf-8\") as file:\n        cookie_json = json.load(file)\n        for cookie in cookie_json:\n            if cookie.get(\"name\") == \"_U\":\n                args.U = cookie.get(\"value\")\n                break\n\n    if args.U is None:\n        raise Exception(\"找不到身份驗證 Cookie\")\n\n    if not args.asyncio:\n        # 創建圖片生成器\n        image_generator = ImageGen(args.U, args.quiet)\n        image_generator.save_images(\n            image_generator.get_images(args.prompt),\n            output_dir=args.output_dir,\n        )\n    else:\n        asyncio.run(async_image_gen(args))\n\n```\n\n</details>\n\n<details open>\n\n<summary>\n\n# Star 歷史\n\n</summary>\n\n[![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date)\n\n</details>\n\n<details open>\n\n<summary>\n\n# 貢獻者\n\n</summary>\n\n這個專案的存在要歸功於所有做出貢獻的人。\n\n <a href=\"https://github.com/acheong08/EdgeGPT/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=acheong08/EdgeGPT\" />\n </a>\n\n</details>\n"
  },
  {
    "path": "docs/wiki/EdgeGPT.md",
    "content": "<a id=\"EdgeGPT.EdgeGPT\"></a>\n\n# EdgeGPT.EdgeGPT\n\nMain.py\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot\"></a>\n\n## Chatbot Objects\n\n```python\nclass Chatbot()\n```\n\nCombines everything to make it seamless\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.save_conversation\"></a>\n\n#### save\\_conversation\n\n```python\nasync def save_conversation(filename: str) -> None\n```\n\nSave the conversation to a file\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.load_conversation\"></a>\n\n#### load\\_conversation\n\n```python\nasync def load_conversation(filename: str) -> None\n```\n\nLoad the conversation from a file\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.get_conversation\"></a>\n\n#### get\\_conversation\n\n```python\nasync def get_conversation() -> dict\n```\n\nGets the conversation history from conversation_id (requires load_conversation)\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.get_activity\"></a>\n\n#### get\\_activity\n\n```python\nasync def get_activity() -> dict\n```\n\nGets the recent activity (requires cookies)\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.ask\"></a>\n\n#### ask\n\n```python\nasync def ask(prompt: str,\n              wss_link: str = \"wss://sydney.bing.com/sydney/ChatHub\",\n              conversation_style: CONVERSATION_STYLE_TYPE = None,\n              webpage_context: str | None = None,\n              search_result: bool = False,\n              locale: str = guess_locale(),\n              simplify_response: bool = False) -> dict\n```\n\nAsk a question to the bot\nResponse:\n    {\n        item (dict):\n            messages (list[dict]):\n                adaptiveCards (list[dict]):\n                    body (list[dict]):\n                        text (str): Response\n    }\nTo get the response, you can do:\n    response[\"item\"][\"messages\"][1][\"adaptiveCards\"][0][\"body\"][0][\"text\"]\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.ask_stream\"></a>\n\n#### ask\\_stream\n\n```python\nasync def ask_stream(\n    prompt: str,\n    wss_link: str = \"wss://sydney.bing.com/sydney/ChatHub\",\n    conversation_style: CONVERSATION_STYLE_TYPE = None,\n    raw: bool = False,\n    webpage_context: str | None = None,\n    search_result: bool = False,\n    locale: str = guess_locale()\n) -> Generator[bool, dict | str, None]\n```\n\nAsk a question to the bot\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.close\"></a>\n\n#### close\n\n```python\nasync def close() -> None\n```\n\nClose the connection\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.delete_conversation\"></a>\n\n#### delete\\_conversation\n\n```python\nasync def delete_conversation(conversation_id: str = None,\n                              conversation_signature: str = None,\n                              client_id: str = None) -> None\n```\n\nDelete the chat in the server and close the connection\n\n<a id=\"EdgeGPT.EdgeGPT.Chatbot.reset\"></a>\n\n#### reset\n\n```python\nasync def reset(delete=False) -> None\n```\n\nReset the conversation\n"
  },
  {
    "path": "docs/wiki/Home.md",
    "content": "Welcome to the EdgeGPT wiki!\n\n## Languages\n\n- English\n- [简体中文](https://github.com/CoolPlayLin/ChatGPT-Wiki/tree/main/docs/EdgeGPT)\n"
  },
  {
    "path": "docs/wiki/ImageGen.md",
    "content": "<a id=\"ImageGen\"></a>\n\n# ImageGen\n\n<a id=\"ImageGen.ImageGen\"></a>\n\n## ImageGen Objects\n\n```python\nclass ImageGen()\n```\n\nImage generation by Microsoft Bing\n\n**Arguments**:\n\n- `auth_cookie` - str\n\n<a id=\"ImageGen.ImageGen.get_images\"></a>\n\n#### get\\_images\n\n```python\ndef get_images(prompt: str) -> list\n```\n\nFetches image links from Bing\n\n**Arguments**:\n\n- `prompt` - str\n\n<a id=\"ImageGen.ImageGen.save_images\"></a>\n\n#### save\\_images\n\n```python\ndef save_images(links: list, output_dir: str) -> None\n```\n\nSaves images to output directory\n"
  },
  {
    "path": "example.env",
    "content": "export BING_U=\"<COOKIE_VALUE>\"\n"
  },
  {
    "path": "requirements.txt",
    "content": "aiohttp\nBingImageCreator\ncertifi\nhttpx\nprompt_toolkit\nrequests\nrich\n"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\ndescription_file=README.md\nlicense_files=LICENSE\n"
  },
  {
    "path": "setup.py",
    "content": "from pathlib import Path\n\nfrom setuptools import find_packages\nfrom setuptools import setup\n\nDOCS_PATH = Path(__file__).parents[0] / \"docs/README.md\"\nPATH = Path(\"README.md\")\nif not PATH.exists():\n    with Path.open(DOCS_PATH, encoding=\"utf-8\") as f1:\n        with Path.open(PATH, \"w+\", encoding=\"utf-8\") as f2:\n            f2.write(f1.read())\n\nsetup(\n    name=\"EdgeGPT\",\n    version=\"0.13.2\",\n    license=\"The Unlicense\",\n    author=\"Antonio Cheong\",\n    author_email=\"acheong@student.dalat.org\",\n    description=\"Reverse engineered Edge Chat API\",\n    packages=find_packages(\"src\"),\n    package_dir={\"\": \"src\"},\n    url=\"https://github.com/acheong08/EdgeGPT\",\n    project_urls={\"Bug Report\": \"https://github.com/acheong08/EdgeGPT/issues/new\"},\n    entry_points={\n        \"console_scripts\": [\n            \"edge-gpt = EdgeGPT.main:main\",\n            \"edge-gpt-image = EdgeGPT.ImageGen:main\",\n        ],\n    },\n    install_requires=[\n        \"httpx[socks]>=0.24.0\",\n        \"aiohttp\",\n        \"websockets\",\n        \"rich\",\n        \"certifi\",\n        \"prompt_toolkit\",\n        \"requests\",\n        \"BingImageCreator>=0.4.4\",\n    ],\n    long_description=Path.open(PATH, encoding=\"utf-8\").read(),\n    long_description_content_type=\"text/markdown\",\n    py_modules=[\"EdgeGPT\", \"EdgeUtils\", \"ImageGen\"],\n    classifiers=[\n        \"License :: OSI Approved :: The Unlicense (Unlicense)\",\n        \"Intended Audience :: Developers\",\n        \"Topic :: Software Development :: Libraries :: Python Modules\",\n        \"Programming Language :: Python :: 3.8\",\n        \"Programming Language :: Python :: 3.9\",\n        \"Programming Language :: Python :: 3.10\",\n        \"Programming Language :: Python :: 3.11\",\n    ],\n)\n"
  },
  {
    "path": "src/EdgeGPT/EdgeGPT.py",
    "content": "\"\"\"\nMain.py\n\"\"\"\nfrom __future__ import annotations\n\nimport json\nfrom pathlib import Path\nfrom typing import Generator\n\nfrom .chathub import *\nfrom .conversation import *\nfrom .conversation_style import *\nfrom .request import *\nfrom .utilities import *\n\n\nclass Chatbot:\n    \"\"\"\n    Combines everything to make it seamless\n    \"\"\"\n\n    def __init__(\n        self,\n        proxy: str | None = None,\n        cookies: list[dict] | None = None,\n    ) -> None:\n        self.proxy: str | None = proxy\n        self.chat_hub: ChatHub = ChatHub(\n            Conversation(self.proxy, cookies=cookies),\n            proxy=self.proxy,\n            cookies=cookies,\n        )\n\n    @staticmethod\n    async def create(\n        proxy: str | None = None,\n        cookies: list[dict] | None = None,\n    ) -> Chatbot:\n        self = Chatbot.__new__(Chatbot)\n        self.proxy = proxy\n        self.chat_hub = ChatHub(\n            await Conversation.create(self.proxy, cookies=cookies),\n            proxy=self.proxy,\n            cookies=cookies,\n        )\n        return self\n\n    async def save_conversation(self, filename: str) -> None:\n        \"\"\"\n        Save the conversation to a file\n        \"\"\"\n        with open(filename, \"w\") as f:\n            conversation_id = self.chat_hub.request.conversation_id\n            conversation_signature = self.chat_hub.request.conversation_signature\n            client_id = self.chat_hub.request.client_id\n            invocation_id = self.chat_hub.request.invocation_id\n            f.write(\n                json.dumps(\n                    {\n                        \"conversation_id\": conversation_id,\n                        \"conversation_signature\": conversation_signature,\n                        \"client_id\": client_id,\n                        \"invocation_id\": invocation_id,\n                    },\n                ),\n            )\n\n    async def load_conversation(self, filename: str) -> None:\n        \"\"\"\n        Load the conversation from a file\n        \"\"\"\n        with open(filename) as f:\n            conversation = json.load(f)\n            self.chat_hub.request = ChatHubRequest(\n                conversation_signature=conversation[\"conversation_signature\"],\n                client_id=conversation[\"client_id\"],\n                conversation_id=conversation[\"conversation_id\"],\n                invocation_id=conversation[\"invocation_id\"],\n            )\n\n    async def get_conversation(self) -> dict:\n        \"\"\"\n        Gets the conversation history from conversation_id (requires load_conversation)\n        \"\"\"\n        return await self.chat_hub.get_conversation()\n\n    async def get_activity(self) -> dict:\n        \"\"\"\n        Gets the recent activity (requires cookies)\n        \"\"\"\n        return await self.chat_hub.get_activity()\n\n    async def ask(\n        self,\n        prompt: str,\n        wss_link: str = \"wss://sydney.bing.com/sydney/ChatHub\",\n        conversation_style: CONVERSATION_STYLE_TYPE = None,\n        webpage_context: str | None = None,\n        search_result: bool = False,\n        locale: str = guess_locale(),\n        simplify_response: bool = False,\n    ) -> dict:\n        \"\"\"\n        Ask a question to the bot\n        Response:\n            {\n                item (dict):\n                    messages (list[dict]):\n                        adaptiveCards (list[dict]):\n                            body (list[dict]):\n                                text (str): Response\n            }\n        To get the response, you can do:\n            response[\"item\"][\"messages\"][1][\"adaptiveCards\"][0][\"body\"][0][\"text\"]\n        \"\"\"\n        async for final, response in self.chat_hub.ask_stream(\n            prompt=prompt,\n            conversation_style=conversation_style,\n            wss_link=wss_link,\n            webpage_context=webpage_context,\n            search_result=search_result,\n            locale=locale,\n        ):\n            if final:\n                if not simplify_response:\n                    return response\n                messages_left = response[\"item\"][\"throttling\"][\n                    \"maxNumUserMessagesInConversation\"\n                ] - response[\"item\"][\"throttling\"].get(\n                    \"numUserMessagesInConversation\",\n                    0,\n                )\n                if messages_left == 0:\n                    raise Exception(\"Max messages reached\")\n                message = \"\"\n                for msg in reversed(response[\"item\"][\"messages\"]):\n                    if msg.get(\"adaptiveCards\") and msg[\"adaptiveCards\"][0][\"body\"][\n                        0\n                    ].get(\"text\"):\n                        message = msg\n                        break\n                if not message:\n                    raise Exception(\"No message found\")\n                suggestions = [\n                    suggestion[\"text\"]\n                    for suggestion in message.get(\"suggestedResponses\", [])\n                ]\n                adaptive_cards = message.get(\"adaptiveCards\", [])\n                adaptive_text = (\n                    adaptive_cards[0][\"body\"][0].get(\"text\") if adaptive_cards else None\n                )\n                sources = (\n                    adaptive_cards[0][\"body\"][0].get(\"text\") if adaptive_cards else None\n                )\n                sources_text = (\n                    adaptive_cards[0][\"body\"][-1].get(\"text\")\n                    if adaptive_cards\n                    else None\n                )\n                return {\n                    \"text\": message[\"text\"],\n                    \"author\": message[\"author\"],\n                    \"sources\": sources,\n                    \"sources_text\": sources_text,\n                    \"suggestions\": suggestions,\n                    \"messages_left\": messages_left,\n                    \"max_messages\": response[\"item\"][\"throttling\"][\n                        \"maxNumUserMessagesInConversation\"\n                    ],\n                    \"adaptive_text\": adaptive_text,\n                }\n        return {}\n\n    async def ask_stream(\n        self,\n        prompt: str,\n        wss_link: str = \"wss://sydney.bing.com/sydney/ChatHub\",\n        conversation_style: CONVERSATION_STYLE_TYPE = None,\n        raw: bool = False,\n        webpage_context: str | None = None,\n        search_result: bool = False,\n        locale: str = guess_locale(),\n    ) -> Generator[bool, dict | str, None]:\n        \"\"\"\n        Ask a question to the bot\n        \"\"\"\n        async for response in self.chat_hub.ask_stream(\n            prompt=prompt,\n            conversation_style=conversation_style,\n            wss_link=wss_link,\n            raw=raw,\n            webpage_context=webpage_context,\n            search_result=search_result,\n            locale=locale,\n        ):\n            yield response\n\n    async def close(self) -> None:\n        \"\"\"\n        Close the connection\n        \"\"\"\n        await self.chat_hub.close()\n\n    async def delete_conversation(\n        self,\n        conversation_id: str = None,\n        conversation_signature: str = None,\n        client_id: str = None,\n    ) -> None:\n        \"\"\"\n        Delete the chat in the server\n        \"\"\"\n        await self.chat_hub.delete_conversation(\n            conversation_id=conversation_id,\n            conversation_signature=conversation_signature,\n            client_id=client_id,\n        )\n\n    async def reset(self, delete=False) -> None:\n        \"\"\"\n        Reset the conversation\n        \"\"\"\n        if delete:\n            await self.remove_and_close()\n        else:\n            await self.close()\n        self.chat_hub = ChatHub(\n            await Conversation.create(self.proxy, cookies=self.chat_hub.cookies),\n            proxy=self.proxy,\n            cookies=self.chat_hub.cookies,\n        )\n\n\nif __name__ == \"__main__\":\n    from .main import main\n\n    main()\n"
  },
  {
    "path": "src/EdgeGPT/EdgeUtils.py",
    "content": "import asyncio\nimport contextlib\nimport json\nimport re\nfrom pathlib import Path\n\nfrom log2d import Log\n\nfrom .EdgeGPT import Chatbot\nfrom .EdgeGPT import ConversationStyle\nfrom .ImageGen import ImageGen\n\nLog(\"BingChat\")\nlog = Log.BingChat.debug  # shortcut to create a log entry\n\n\nclass Cookie:\n    \"\"\"\n    Convenience class for Bing Cookie files, data, and configuration. This Class\n    is updated dynamically by the Query class to allow cycling through >1\n    cookie/credentials file e.g. when daily request limits (current 200 per\n    account per day) are exceeded.\n    \"\"\"\n\n    current_file_index = 0\n    dir_path = Path.home().resolve() / \"bing_cookies\"\n    current_file_path = dir_path  # Avoid Path errors when no cookie file used\n    search_pattern = \"bing_cookies_*.json\"\n    ignore_files = set()\n    request_count = {}\n    supplied_files = set()\n    rotate_cookies = True\n\n    @classmethod\n    def files(cls: type) -> list[Path]:\n        \"\"\"\n        Return a sorted list of all cookie files matching .search_pattern in\n        cls.dir_path, plus any supplied files, minus any ignored files.\n        \"\"\"\n        all_files = set(Path(cls.dir_path).glob(cls.search_pattern))\n        if hasattr(cls, \"supplied_files\"):\n            supplied_files = {x for x in cls.supplied_files if x.is_file()}\n            all_files.update(supplied_files)\n        return sorted(all_files - cls.ignore_files)\n\n    @classmethod\n    def import_data(cls: type) -> None:\n        \"\"\"\n        Read the active cookie file and populate the following attributes:\n\n          .current_file_path\n          .current_data\n          .image_token\n        \"\"\"\n        if not cls.files():\n            log(\"No files in Cookie.dir_path\")\n            return\n        try:\n            cls.current_file_path = cls.files()[cls.current_file_index]\n        except IndexError:\n            log(f\"Invalid file index [{cls.current_file_index}]\")\n            log(\"Files in Cookie.dir_path:\")\n            for file in cls.files():\n                log(f\"{file}\")\n            return\n        log(f\"Importing cookies from: {cls.current_file_path.name}\")\n        with open(cls.current_file_path, encoding=\"utf-8\") as file:\n            cls.current_data = json.load(file)\n        cls.image_token = [\n            x for x in cls.current_data if x.get(\"name\").startswith(\"_U\")\n        ]\n        cls.image_token = cls.image_token[0].get(\"value\")\n\n    @classmethod\n    def import_next(cls: type, discard: bool = False) -> None:\n        \"\"\"\n        Cycle through to the next cookies file then import it.\n\n        discard (bool): True -Mark the previous file to be ignored for the remainder of the current session.  Otherwise cycle through all available\n        cookie files (sharing the workload and 'resting' when not in use).\n        \"\"\"\n        if not hasattr(cls, \"current_file_path\"):\n            cls.import_data()\n            return\n        with contextlib.suppress(AttributeError):\n            # Will fail on first instantiation because no current_file_path\n            if discard:\n                cls.ignore_files.add(cls.current_file_path)\n            else:\n                Cookie.current_file_index += 1\n        if Cookie.current_file_index >= len(cls.files()):\n            Cookie.current_file_index = 0\n        Cookie.import_data()\n\n\nclass Query:\n    \"\"\"\n    A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input,\n    config, and output all together.  Relies on Cookie class for authentication\n    unless ignore_cookies=True\n    \"\"\"\n\n    index = []\n    image_dir_path = Path.cwd().resolve() / \"bing_images\"\n\n    def __init__(\n        self,\n        prompt: str,\n        style: ConversationStyle = \"precise\",\n        content_type: str = \"text\",\n        cookie_files: set[Path] = None,\n        ignore_cookies: bool = False,\n        echo: bool = True,\n        echo_prompt: bool = False,\n        locale: str = \"en-GB\",\n        simplify_response: bool = True,\n    ) -> None:\n        \"\"\"\n        Arguments:\n\n        prompt: Text to enter into Bing Chat\n        style: creative, balanced, or precise\n        content_type: \"text\" for Bing Chat; \"image\" for Dall-e\n        ignore_cookies (bool): Ignore cookie data altogether\n        echo: Print something to confirm request made\n        echo_prompt: Print confirmation of the evaluated prompt\n        simplify_response: True -> single simplified prompt/response exchange\n        cookie_files: iterable of Paths or strings of cookie files (json)\n\n        Files in Cookie.dir_path will also be used if they exist.  This defaults\n        to the current working directory, so set Cookie.dir_path before\n        creating a Query if your cookie files are elsewhere.\n        \"\"\"\n        self.__class__.index += [self]\n        self.prompt = prompt\n        self.locale = locale\n        self.simplify_response = simplify_response\n        self.ignore_cookies = ignore_cookies\n        if not ignore_cookies:\n            if cookie_files:\n                # Convert singular argument to an iterable:\n                if isinstance(cookie_files, (str, Path)):\n                    cookie_files = {cookie_files}\n                # Check all elements exist and are Paths:\n                cookie_files = {\n                    Path(x).resolve()\n                    for x in cookie_files\n                    if isinstance(x, (str, Path)) and x\n                }\n                Cookie.supplied_files = cookie_files\n            files = Cookie.files()  # includes .supplied_files\n            if Cookie.rotate_cookies:\n                Cookie.import_next()\n            else:\n                Cookie.import_data()\n        if content_type == \"text\":\n            self.style = style\n            self.log_and_send_query(echo, echo_prompt)\n        if content_type == \"image\":\n            self.create_image()\n\n    def log_and_send_query(self, echo: bool, echo_prompt: bool) -> None:\n        self.response = asyncio.run(self.send_to_bing(echo, echo_prompt))\n        if not hasattr(Cookie, \"current_data\"):\n            name = \"<no_cookies>\"\n        else:\n            name = Cookie.current_file_path.name\n        if not Cookie.request_count.get(name):\n            Cookie.request_count[name] = 1\n        else:\n            Cookie.request_count[name] += 1\n\n    def create_image(self) -> None:\n        image_generator = ImageGen(Cookie.image_token)\n        image_generator.save_images(\n            image_generator.get_images(self.prompt),\n            output_dir=self.__class__.image_dir_path,\n        )\n\n    async def send_to_bing(self, echo: bool = True, echo_prompt: bool = False) -> str:\n        \"\"\"Creat, submit, then close a Chatbot instance.  Return the response\"\"\"\n        retries = len(Cookie.files()) or 1\n        while retries:\n            if not hasattr(Cookie, \"current_data\"):\n                bot = await Chatbot.create()\n            else:\n                bot = await Chatbot.create(cookies=Cookie.current_data)\n            if echo_prompt:\n                log(f\"{self.prompt=}\")\n            if echo:\n                log(\"Waiting for response...\")\n            if self.style.lower() not in \"creative balanced precise\".split():\n                self.style = \"precise\"\n            try:\n                return await bot.ask(\n                    prompt=self.prompt,\n                    conversation_style=getattr(ConversationStyle, self.style),\n                    simplify_response=self.simplify_response,\n                    locale=self.locale,\n                )\n            except Exception as ex:\n                log(\n                    f\"Exception: [{Cookie.current_file_path.name} may have exceeded the daily limit]\\n{ex}\",\n                )\n                Cookie.import_next(discard=True)\n                retries -= 1\n            finally:\n                await bot.close()\n        return None\n\n    @property\n    def output(self) -> str:\n        \"\"\"The response from a completed Chatbot request\"\"\"\n        if not self.simplify_response:\n            return [\n                x.get(\"text\") or x.get(\"hiddenText\")\n                for x in self.response[\"item\"][\"messages\"]\n                if x[\"author\"] == \"bot\"\n            ]\n        try:\n            return self.response[\"text\"]\n        except TypeError as te:\n            raise TypeError(\n                f\"{te}\\n(No response received - probably rate throttled...)\",\n            ) from te\n\n    @property\n    def sources(self) -> list[list[dict]]:\n        \"\"\"The source names and details parsed from a completed Chatbot request\"\"\"\n        if self.simplify_response:\n            return self.response[\"sources_text\"]\n        return [\n            x.get(\"sourceAttributions\") or []\n            for x in self.response[\"item\"][\"messages\"]\n            if x[\"author\"] == \"bot\"\n        ]\n\n    @property\n    def sources_dict(self) -> dict[int, str]:\n        \"\"\"The source names and details as a dictionary\"\"\"\n        if self.simplify_response:\n            text = self.response[\"sources_text\"]\n            sources = enumerate(re.findall(r\"\\((http.*?)\\)\", text))\n            return {index + 1: value for index, value in sources}\n        all_sources = []\n        name = \"providerDisplayName\"\n        url = \"seeMoreUrl\"\n        for sources in self.sources:\n            if not sources:\n                continue\n            data = {\n                index + 1: source[url]\n                for index, source in enumerate(sources)\n                if name in source and url in source\n            }\n            all_sources += [data]\n        return all_sources\n\n    @property\n    def code_block_formats(self) -> list[str]:\n        \"\"\"\n        Extract a list of programming languages/formats used in code blocks\n        \"\"\"\n        regex = r\"``` *(\\b\\w+\\b\\+*) *\"\n        if self.simplify_response:\n            return re.findall(regex, self.output)\n        return re.findall(regex, \"\\n\".join(self.output))\n\n    @property\n    def code_blocks(self) -> list[str]:\n        \"\"\"\n        Return a list of code blocks (```) or snippets (`) as strings.\n\n        If the response contains a mix of snippets and code blocks, return the\n        code blocks only.\n\n        This method is not suitable if the main text response includes either of\n        the delimiters but not as part of an actual snippet or code block.\n\n        For example:\n        'In Markdown, the back-tick (`) is used to denote a code snippet'\n\n        \"\"\"\n\n        final_blocks = []\n        if isinstance(self.output, str):  # I.e. simplify_response is True\n            separator = \"```\" if \"```\" in self.output else \"`\"\n            code_blocks = self.output.split(separator)[1:-1:2]\n            if separator == \"`\":\n                return code_blocks\n        else:\n            code_blocks = []\n            for response in self.output:\n                separator = \"```\" if \"```\" in response else \"`\"\n                code_blocks.extend(response.split(separator)[1:-1:2])\n            code_blocks = [x for x in code_blocks if x]\n        # Remove language name if present:\n        for block in code_blocks:\n            lines = block.splitlines()\n            code = lines[1:] if re.match(\" *\\\\w+ *\", lines[0]) else lines\n            final_blocks += [\"\\n\".join(code).removeprefix(separator)]\n        return [x for x in final_blocks if x]\n\n    @property\n    def code(self) -> str:\n        \"\"\"\n        Extract and join any snippets of code or formatted data in the response\n        \"\"\"\n        return \"\\n\\n\".join(self.code_blocks)\n\n    @property\n    def suggestions(self) -> list[str]:\n        \"\"\"Follow-on questions suggested by the Chatbot\"\"\"\n        if self.simplify_response:\n            return self.response[\"suggestions\"]\n        try:\n            return [\n                x[\"text\"]\n                for x in self.response[\"item\"][\"messages\"][1][\"suggestedResponses\"]\n            ]\n        except KeyError:\n            return None\n\n    def __repr__(self) -> str:\n        return f\"<EdgeGPT.Query: {self.prompt}>\"\n\n    def __str__(self) -> str:\n        return self.output if self.simplify_response else \"\\n\\n\".join(self.output)\n\n\nclass ImageQuery(Query):\n    def __init__(self, prompt: str, **kwargs) -> None:\n        kwargs[\"content_type\"] = \"image\"\n        super().__init__(prompt, **kwargs)\n\n    def __repr__(self) -> str:\n        return f\"<EdgeGPT.ImageQuery: {self.prompt}>\"\n\n\ndef test_cookie_rotation() -> None:\n    for i in range(1, 50):\n        q = Query(\n            f\"What is {i} in Roman numerals?  Give the answer in JSON\",\n            style=\"precise\",\n        )\n        log(f\"{i}: {Cookie.current_file_path.name}\")\n        log(q.code)\n        log(f\"Cookie count: {Cookie.request_count.get(Cookie.current_file_path.name)}\")\n\n\ndef test_features() -> Query:\n    try:\n        q = Query(\n            f\"What is {i} in Roman numerals?  Give the answer in JSON\",\n            style=\"precise\",\n        )\n        log(f\"{i}: {Cookie.current_file_path.name}\")\n        print(f\"{Cookie.current_file_index=}\")\n        print(f\"{Cookie.current_file_path=}\")\n        print(f\"{Cookie.current_data=}\")\n        print(f\"{Cookie.dir_path=}\")\n        print(f\"{Cookie.search_pattern=}\")\n        print(f\"{Cookie.files()=}\")\n        print(f\"{Cookie.image_token=}\")\n        print(f\"{Cookie.import_next(discard=True)=}\")\n        print(f\"{Cookie.rotate_cookies=}\")\n        print(f\"{Cookie.files()=}\")\n        print(f\"{Cookie.ignore_files=}\")\n        print(f\"{Cookie.supplied_files=}\")\n        print(\n            f\"{Cookie.request_count=}\"\n        )  # Keeps a tally of requests made in using each cookie file during this session\n        print(f\"{q=}\")\n        print(f\"{q.prompt=}\")\n        print(f\"{q.ignore_cookies=}\")\n        print(f\"{q.style=}\")\n        print(f\"{q.simplify_response=}\")\n        print(f\"{q.locale=}\")\n        print(f\"{q.output=}\")\n        print(q)\n        print(f\"{q.sources=}\")\n        print(f\"{q.sources_dict=}\")\n        print(f\"{q.suggestions=}\")\n        print(f\"{q.code=}\")  # All code as a single string\n        print(f\"{q.code_blocks=}\")  # Individual code blocks\n        print(\n            f\"{q.code_block_formats=}\"\n        )  # The language/format of each code block (if given)\n        print(f\"{Query.index=}\")  # Keeps an index of Query objects created\n        print(f\"{Query.image_dir_path=}\")\n    except Exception as E:\n        raise Exception(E) from E\n    finally:\n        return q\n"
  },
  {
    "path": "src/EdgeGPT/ImageGen.py",
    "content": "# Open pull requests and issues at https://github.com/acheong08/BingImageCreator\nimport BingImageCreator\n\nImageGen = BingImageCreator.ImageGen\n\nImageGenAsync = BingImageCreator.ImageGenAsync\n\nmain = BingImageCreator.main\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/EdgeGPT/__init__.py",
    "content": ""
  },
  {
    "path": "src/EdgeGPT/chathub.py",
    "content": "import asyncio\nimport json\nimport os\nimport ssl\nimport sys\nfrom time import time\nfrom typing import Generator\nfrom typing import List\nfrom typing import Union\n\nimport aiohttp\nimport certifi\nimport httpx\nfrom BingImageCreator import ImageGenAsync\n\nfrom .constants import DELIMITER\nfrom .constants import HEADERS\nfrom .constants import HEADERS_INIT_CONVER\nfrom .conversation import Conversation\nfrom .conversation_style import CONVERSATION_STYLE_TYPE\nfrom .request import ChatHubRequest\nfrom .utilities import append_identifier\nfrom .utilities import get_ran_hex\nfrom .utilities import guess_locale\n\nssl_context = ssl.create_default_context()\nssl_context.load_verify_locations(certifi.where())\n\n\nclass ChatHub:\n    def __init__(\n        self,\n        conversation: Conversation,\n        proxy: str = None,\n        cookies: Union[List[dict], None] = None,\n    ) -> None:\n        self.aio_session = None\n        self.request: ChatHubRequest\n        self.loop: bool\n        self.task: asyncio.Task\n        self.request = ChatHubRequest(\n            conversation_signature=conversation.struct[\"conversationSignature\"],\n            client_id=conversation.struct[\"clientId\"],\n            conversation_id=conversation.struct[\"conversationId\"],\n        )\n        self.cookies = cookies\n        self.proxy: str = proxy\n        proxy = (\n            proxy\n            or os.environ.get(\"all_proxy\")\n            or os.environ.get(\"ALL_PROXY\")\n            or os.environ.get(\"https_proxy\")\n            or os.environ.get(\"HTTPS_PROXY\")\n            or None\n        )\n        if proxy is not None and proxy.startswith(\"socks5h://\"):\n            proxy = \"socks5://\" + proxy[len(\"socks5h://\") :]\n        self.session = httpx.AsyncClient(\n            proxies=proxy,\n            timeout=900,\n            headers=HEADERS_INIT_CONVER,\n        )\n\n    async def get_conversation(\n        self,\n        conversation_id: str = None,\n        conversation_signature: str = None,\n        client_id: str = None,\n    ) -> dict:\n        conversation_id = conversation_id or self.request.conversation_id\n        conversation_signature = (\n            conversation_signature or self.request.conversation_signature\n        )\n        client_id = client_id or self.request.client_id\n        url = f\"https://sydney.bing.com/sydney/GetConversation?conversationId={conversation_id}&source=cib&participantId={client_id}&conversationSignature={conversation_signature}&traceId={get_ran_hex()}\"\n        response = await self.session.get(url)\n        return response.json()\n\n    async def get_activity(self) -> dict:\n        url = \"https://www.bing.com/turing/conversation/chats\"\n        headers = HEADERS_INIT_CONVER.copy()\n        if self.cookies is not None:\n            for cookie in self.cookies:\n                if cookie[\"name\"] == \"_U\":\n                    headers[\"Cookie\"] = f\"SUID=A; _U={cookie['value']};\"\n                    break\n        response = await self.session.get(url, headers=headers)\n        return response.json()\n\n    async def ask_stream(\n        self,\n        prompt: str,\n        wss_link: str = None,\n        conversation_style: CONVERSATION_STYLE_TYPE = None,\n        raw: bool = False,\n        webpage_context: Union[str, None] = None,\n        search_result: bool = False,\n        locale: str = guess_locale(),\n    ) -> Generator[bool, Union[dict, str], None]:\n        \"\"\" \"\"\"\n        cookies = {}\n        if self.cookies is not None:\n            for cookie in self.cookies:\n                cookies[cookie[\"name\"]] = cookie[\"value\"]\n        self.aio_session = aiohttp.ClientSession(cookies=cookies)\n        # Check if websocket is closed\n        wss = await self.aio_session.ws_connect(\n            wss_link or \"wss://sydney.bing.com/sydney/ChatHub\",\n            ssl=ssl_context,\n            headers=HEADERS,\n            proxy=self.proxy,\n        )\n        await self._initial_handshake(wss)\n        # Construct a ChatHub request\n        self.request.update(\n            prompt=prompt,\n            conversation_style=conversation_style,\n            webpage_context=webpage_context,\n            search_result=search_result,\n            locale=locale,\n        )\n        # Send request\n        await wss.send_str(append_identifier(self.request.struct))\n        draw = False\n        resp_txt = \"\"\n        result_text = \"\"\n        resp_txt_no_link = \"\"\n        retry_count = 5\n        while not wss.closed:\n            msg = await wss.receive_str()\n            if not msg:\n                retry_count -= 1\n                if retry_count == 0:\n                    raise Exception(\"No response from server\")\n                continue\n            if isinstance(msg, str):\n                objects = msg.split(DELIMITER)\n            else:\n                continue\n            for obj in objects:\n                if int(time()) % 6 == 0:\n                    await wss.send_str(append_identifier({\"type\": 6}))\n                if obj is None or not obj:\n                    continue\n                response = json.loads(obj)\n                # print(response)\n                if response.get(\"type\") == 1 and response[\"arguments\"][0].get(\n                    \"messages\",\n                ):\n                    if not draw:\n                        if (\n                            response[\"arguments\"][0][\"messages\"][0].get(\n                                \"messageType\",\n                            )\n                            == \"GenerateContentQuery\"\n                        ):\n                            try:\n                                async with ImageGenAsync(\n                                    all_cookies=self.cookies,\n                                ) as image_generator:\n                                    images = await image_generator.get_images(\n                                        response[\"arguments\"][0][\"messages\"][0][\"text\"],\n                                    )\n                                for i, image in enumerate(images):\n                                    resp_txt = f\"{resp_txt}\\n![image{i}]({image})\"\n                                draw = True\n                            except Exception as e:\n                                print(e)\n                                continue\n                        if (\n                            (\n                                response[\"arguments\"][0][\"messages\"][0][\"contentOrigin\"]\n                                != \"Apology\"\n                            )\n                            and not draw\n                            and not raw\n                        ):\n                            resp_txt = result_text + response[\"arguments\"][0][\n                                \"messages\"\n                            ][0][\"adaptiveCards\"][0][\"body\"][0].get(\"text\", \"\")\n                            resp_txt_no_link = result_text + response[\"arguments\"][0][\n                                \"messages\"\n                            ][0].get(\"text\", \"\")\n                            if response[\"arguments\"][0][\"messages\"][0].get(\n                                \"messageType\",\n                            ):\n                                resp_txt = (\n                                    resp_txt\n                                    + response[\"arguments\"][0][\"messages\"][0][\n                                        \"adaptiveCards\"\n                                    ][0][\"body\"][0][\"inlines\"][0].get(\"text\")\n                                    + \"\\n\"\n                                )\n                                result_text = (\n                                    result_text\n                                    + response[\"arguments\"][0][\"messages\"][0][\n                                        \"adaptiveCards\"\n                                    ][0][\"body\"][0][\"inlines\"][0].get(\"text\")\n                                    + \"\\n\"\n                                )\n                        if not raw:\n                            yield False, resp_txt\n\n                elif response.get(\"type\") == 2:\n                    if response[\"item\"][\"result\"].get(\"error\"):\n                        await self.close()\n                        raise Exception(\n                            f\"{response['item']['result']['value']}: {response['item']['result']['message']}\",\n                        )\n                    if draw:\n                        cache = response[\"item\"][\"messages\"][1][\"adaptiveCards\"][0][\n                            \"body\"\n                        ][0][\"text\"]\n                        response[\"item\"][\"messages\"][1][\"adaptiveCards\"][0][\"body\"][0][\n                            \"text\"\n                        ] = (cache + resp_txt)\n                    if (\n                        response[\"item\"][\"messages\"][-1][\"contentOrigin\"] == \"Apology\"\n                        and resp_txt\n                    ):\n                        response[\"item\"][\"messages\"][-1][\"text\"] = resp_txt_no_link\n                        response[\"item\"][\"messages\"][-1][\"adaptiveCards\"][0][\"body\"][0][\n                            \"text\"\n                        ] = resp_txt\n                        print(\n                            \"Preserved the message from being deleted\",\n                            file=sys.stderr,\n                        )\n                    await wss.close()\n                    if not self.aio_session.closed:\n                        await self.aio_session.close()\n                    yield True, response\n                    return\n                if response.get(\"type\") != 2:\n                    if response.get(\"type\") == 6:\n                        await wss.send_str(append_identifier({\"type\": 6}))\n                    elif response.get(\"type\") == 7:\n                        await wss.send_str(append_identifier({\"type\": 7}))\n                    elif raw:\n                        yield False, response\n\n    async def _initial_handshake(self, wss) -> None:\n        await wss.send_str(append_identifier({\"protocol\": \"json\", \"version\": 1}))\n        await wss.receive_str()\n        await wss.send_str(append_identifier({\"type\": 6}))\n\n    async def delete_conversation(\n        self,\n        conversation_id: str = None,\n        conversation_signature: str = None,\n        client_id: str = None,\n    ) -> None:\n        conversation_id = conversation_id or self.request.conversation_id\n        conversation_signature = (\n            conversation_signature or self.request.conversation_signature\n        )\n        client_id = client_id or self.request.client_id\n        url = \"https://sydney.bing.com/sydney/DeleteSingleConversation\"\n        await self.session.post(\n            url,\n            json={\n                \"conversationId\": conversation_id,\n                \"conversationSignature\": conversation_signature,\n                \"participant\": {\"id\": client_id},\n                \"source\": \"cib\",\n                \"optionsSets\": [\"autosave\"],\n            },\n        )\n\n    async def close(self) -> None:\n        await self.session.aclose()\n"
  },
  {
    "path": "src/EdgeGPT/constants.py",
    "content": "import socket\nimport uuid\n\ntake_ip_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\ntake_ip_socket.connect((\"8.8.8.8\", 80))\nFORWARDED_IP: str = take_ip_socket.getsockname()[0]\ntake_ip_socket.close()\n\nDELIMITER = \"\\x1e\"\n\nHEADERS = {\n    \"accept\": \"application/json\",\n    \"accept-language\": \"en-US;q=0.9\",\n    \"accept-encoding\": \"gzip, deflate, br, zsdch\",\n    \"content-type\": \"application/json\",\n    \"sec-ch-ua\": '\"Not/A)Brand\";v=\"99\", \"Microsoft Edge\";v=\"115\", \"Chromium\";v=\"115\"',\n    \"sec-ch-ua-arch\": '\"x86\"',\n    \"sec-ch-ua-bitness\": '\"64\"',\n    \"sec-ch-ua-full-version\": '\"115.0.1901.188\"',\n    \"sec-ch-ua-full-version-list\": '\"Not/A)Brand\";v=\"99.0.0.0\", \"Microsoft Edge\";v=\"115.0.1901.188\", \"Chromium\";v=\"115.0.5790.114\"',\n    \"sec-ch-ua-mobile\": \"?0\",\n    \"sec-ch-ua-model\": \"\",\n    \"sec-ch-ua-platform\": '\"Windows\"',\n    \"sec-ch-ua-platform-version\": '\"15.0.0\"',\n    \"sec-fetch-dest\": \"empty\",\n    \"sec-fetch-mode\": \"cors\",\n    \"sec-fetch-site\": \"same-origin\",\n    \"sec-ms-gec-version\": \"1-115.0.1901.188\",\n    \"x-ms-client-request-id\": str(uuid.uuid4()),\n    \"x-ms-useragent\": \"azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/Windows\",\n    \"Referer\": \"https://www.bing.com/search?\",\n    \"Referrer-Policy\": \"origin-when-cross-origin\",\n    \"x-forwarded-for\": FORWARDED_IP,\n}\n\nHEADERS_INIT_CONVER = {\n    \"authority\": \"www.bing.com\",\n    \"accept\": \"application/json\",\n    \"accept-language\": \"en-US;q=0.9\",\n    \"cache-control\": \"max-age=0\",\n    \"sec-ch-ua\": '\"Not/A)Brand\";v=\"99\", \"Microsoft Edge\";v=\"115\", \"Chromium\";v=\"115\"',\n    \"sec-ch-ua-arch\": '\"x86\"',\n    \"sec-ch-ua-bitness\": '\"64\"',\n    \"sec-ch-ua-full-version\": '\"115.0.1901.188\"',\n    \"sec-ch-ua-full-version-list\": '\"Not/A)Brand\";v=\"99.0.0.0\", \"Microsoft Edge\";v=\"115.0.1901.188\", \"Chromium\";v=\"115.0.5790.114\"',\n    \"sec-ch-ua-mobile\": \"?0\",\n    \"sec-ch-ua-model\": '\"\"',\n    \"sec-ch-ua-platform\": '\"Windows\"',\n    \"sec-ch-ua-platform-version\": '\"15.0.0\"',\n    \"upgrade-insecure-requests\": \"1\",\n    \"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.188\",\n    \"x-edge-shopping-flag\": \"1\",\n    \"x-forwarded-for\": FORWARDED_IP,\n}\n"
  },
  {
    "path": "src/EdgeGPT/conversation.py",
    "content": "import json\nimport os\nfrom typing import List\nfrom typing import Union\n\nimport httpx\n\nfrom .constants import HEADERS_INIT_CONVER\nfrom .exceptions import NotAllowedToAccess\n\n\nclass Conversation:\n    def __init__(\n        self,\n        proxy: Union[str, None] = None,\n        async_mode: bool = False,\n        cookies: Union[List[dict], None] = None,\n    ) -> None:\n        if async_mode:\n            return\n        self.struct: dict = {\n            \"conversationId\": None,\n            \"clientId\": None,\n            \"conversationSignature\": None,\n            \"result\": {\"value\": \"Success\", \"message\": None},\n        }\n        self.proxy = proxy\n        proxy = (\n            proxy\n            or os.environ.get(\"all_proxy\")\n            or os.environ.get(\"ALL_PROXY\")\n            or os.environ.get(\"https_proxy\")\n            or os.environ.get(\"HTTPS_PROXY\")\n            or None\n        )\n        if proxy is not None and proxy.startswith(\"socks5h://\"):\n            proxy = \"socks5://\" + proxy[len(\"socks5h://\") :]\n        self.session = httpx.Client(\n            proxies=proxy,\n            timeout=900,\n            headers=HEADERS_INIT_CONVER,\n        )\n        if cookies:\n            for cookie in cookies:\n                self.session.cookies.set(cookie[\"name\"], cookie[\"value\"])\n        # Send GET request\n        response = self.session.get(\n            url=os.environ.get(\"BING_PROXY_URL\")\n            or \"https://edgeservices.bing.com/edgesvc/turing/conversation/create\",\n        )\n        if response.status_code != 200:\n            print(f\"Status code: {response.status_code}\")\n            print(response.text)\n            print(response.url)\n            raise Exception(\"Authentication failed\")\n        try:\n            self.struct = response.json()\n        except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:\n            raise Exception(\n                \"Authentication failed. You have not been accepted into the beta.\",\n            ) from exc\n        if self.struct[\"result\"][\"value\"] == \"UnauthorizedRequest\":\n            raise NotAllowedToAccess(self.struct[\"result\"][\"message\"])\n\n    @staticmethod\n    async def create(\n        proxy: Union[str, None] = None,\n        cookies: Union[List[dict], None] = None,\n    ) -> \"Conversation\":\n        self = Conversation(async_mode=True)\n        self.struct = {\n            \"conversationId\": None,\n            \"clientId\": None,\n            \"conversationSignature\": None,\n            \"result\": {\"value\": \"Success\", \"message\": None},\n        }\n        self.proxy = proxy\n        proxy = (\n            proxy\n            or os.environ.get(\"all_proxy\")\n            or os.environ.get(\"ALL_PROXY\")\n            or os.environ.get(\"https_proxy\")\n            or os.environ.get(\"HTTPS_PROXY\")\n            or None\n        )\n        if proxy is not None and proxy.startswith(\"socks5h://\"):\n            proxy = \"socks5://\" + proxy[len(\"socks5h://\") :]\n        transport = httpx.AsyncHTTPTransport(retries=900)\n        # Convert cookie format to httpx format\n        formatted_cookies = None\n        if cookies:\n            formatted_cookies = httpx.Cookies()\n            for cookie in cookies:\n                formatted_cookies.set(cookie[\"name\"], cookie[\"value\"])\n        async with httpx.AsyncClient(\n            proxies=proxy,\n            timeout=30,\n            headers=HEADERS_INIT_CONVER,\n            transport=transport,\n            cookies=formatted_cookies,\n        ) as client:\n            # Send GET request\n            response = await client.get(\n                url=os.environ.get(\"BING_PROXY_URL\")\n                or \"https://www.bing.com/turing/conversation/create\",\n                follow_redirects=True,\n            )\n        if response.status_code != 200:\n            print(f\"Status code: {response.status_code}\")\n            print(response.text)\n            print(response.url)\n            raise Exception(\"Authentication failed\")\n        try:\n            self.struct = response.json()\n        except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc:\n            print(response.text)\n            raise Exception(\n                \"Authentication failed. You have not been accepted into the beta.\",\n            ) from exc\n        if self.struct[\"result\"][\"value\"] == \"UnauthorizedRequest\":\n            raise NotAllowedToAccess(self.struct[\"result\"][\"message\"])\n        return self\n"
  },
  {
    "path": "src/EdgeGPT/conversation_style.py",
    "content": "from enum import Enum\n\ntry:\n    from typing import Literal, Union\nexcept ImportError:\n    from typing_extensions import Literal\nfrom typing import Optional\n\n\nclass ConversationStyle(Enum):\n    creative = [\n        \"nlu_direct_response_filter\",\n        \"deepleo\",\n        \"disable_emoji_spoken_text\",\n        \"responsible_ai_policy_235\",\n        \"enablemm\",\n        \"h3imaginative\",\n        \"objopinion\",\n        \"dsblhlthcrd\",\n        \"dv3sugg\",\n        \"autosave\",\n        \"clgalileo\",\n        \"gencontentv3\",\n    ]\n    balanced = [\n        \"nlu_direct_response_filter\",\n        \"deepleo\",\n        \"disable_emoji_spoken_text\",\n        \"responsible_ai_policy_235\",\n        \"enablemm\",\n        \"galileo\",\n        \"saharagenconv5\",\n        \"objopinion\",\n        \"dsblhlthcrd\",\n        \"dv3sugg\",\n        \"autosave\",\n    ]\n    precise = [\n        \"nlu_direct_response_filter\",\n        \"deepleo\",\n        \"disable_emoji_spoken_text\",\n        \"responsible_ai_policy_235\",\n        \"enablemm\",\n        \"h3precise\",\n        \"objopinion\",\n        \"dsblhlthcrd\",\n        \"dv3sugg\",\n        \"autosave\",\n        \"clgalileo\",\n        \"gencontentv3\",\n    ]\n\n\nCONVERSATION_STYLE_TYPE = Optional[\n    Union[ConversationStyle, Literal[\"creative\", \"balanced\", \"precise\"]]\n]\n"
  },
  {
    "path": "src/EdgeGPT/exceptions.py",
    "content": "class NotAllowedToAccess(Exception):\n    pass\n"
  },
  {
    "path": "src/EdgeGPT/locale.py",
    "content": "from enum import Enum\n\ntry:\n    from typing import Literal, Union\nexcept ImportError:\n    from typing_extensions import Literal\nfrom typing import Optional\n\n\nclass LocationHint(Enum):\n    USA = {\n        \"locale\": \"en-US\",\n        \"LocationHint\": [\n            {\n                \"country\": \"United States\",\n                \"state\": \"California\",\n                \"city\": \"Los Angeles\",\n                \"timezoneoffset\": 8,\n                \"countryConfidence\": 8,\n                \"Center\": {\n                    \"Latitude\": 34.0536909,\n                    \"Longitude\": -118.242766,\n                },\n                \"RegionType\": 2,\n                \"SourceType\": 1,\n            },\n        ],\n    }\n    CHINA = {\n        \"locale\": \"zh-CN\",\n        \"LocationHint\": [\n            {\n                \"country\": \"China\",\n                \"state\": \"\",\n                \"city\": \"Beijing\",\n                \"timezoneoffset\": 8,\n                \"countryConfidence\": 8,\n                \"Center\": {\n                    \"Latitude\": 39.9042,\n                    \"Longitude\": 116.4074,\n                },\n                \"RegionType\": 2,\n                \"SourceType\": 1,\n            },\n        ],\n    }\n    EU = {\n        \"locale\": \"en-IE\",\n        \"LocationHint\": [\n            {\n                \"country\": \"Norway\",\n                \"state\": \"\",\n                \"city\": \"Oslo\",\n                \"timezoneoffset\": 1,\n                \"countryConfidence\": 8,\n                \"Center\": {\n                    \"Latitude\": 59.9139,\n                    \"Longitude\": 10.7522,\n                },\n                \"RegionType\": 2,\n                \"SourceType\": 1,\n            },\n        ],\n    }\n    UK = {\n        \"locale\": \"en-GB\",\n        \"LocationHint\": [\n            {\n                \"country\": \"United Kingdom\",\n                \"state\": \"\",\n                \"city\": \"London\",\n                \"timezoneoffset\": 0,\n                \"countryConfidence\": 8,\n                \"Center\": {\n                    \"Latitude\": 51.5074,\n                    \"Longitude\": -0.1278,\n                },\n                \"RegionType\": 2,\n                \"SourceType\": 1,\n            },\n        ],\n    }\n\n\nLOCATION_HINT_TYPES = Optional[Union[LocationHint, Literal[\"USA\", \"CHINA\", \"EU\", \"UK\"]]]\n"
  },
  {
    "path": "src/EdgeGPT/main.py",
    "content": "import argparse\nimport asyncio\nimport json\nimport re\nimport sys\nfrom pathlib import Path\n\nfrom EdgeGPT.EdgeGPT import Chatbot\nfrom prompt_toolkit import PromptSession\nfrom prompt_toolkit.auto_suggest import AutoSuggestFromHistory\nfrom prompt_toolkit.completion import WordCompleter\nfrom prompt_toolkit.history import InMemoryHistory\nfrom prompt_toolkit.key_binding import KeyBindings\nfrom rich.live import Live\nfrom rich.markdown import Markdown\n\n\ndef create_session() -> PromptSession:\n    kb = KeyBindings()\n\n    @kb.add(\"enter\")\n    def _(event) -> None:\n        buffer_text = event.current_buffer.text\n        if buffer_text.startswith(\"!\"):\n            event.current_buffer.validate_and_handle()\n        else:\n            event.current_buffer.insert_text(\"\\n\")\n\n    @kb.add(\"escape\")\n    def _(event) -> None:\n        if event.current_buffer.complete_state:\n            # event.current_buffer.cancel_completion()\n            event.current_buffer.text = \"\"\n\n    return PromptSession(key_bindings=kb, history=InMemoryHistory())\n\n\ndef create_completer(commands: list, pattern_str: str = \"$\") -> WordCompleter:\n    return WordCompleter(words=commands, pattern=re.compile(pattern_str))\n\n\ndef _create_history_logger(f) -> callable:\n    def logger(*args, **kwargs) -> None:\n        tmp = sys.stdout\n        sys.stdout = f\n        print(*args, **kwargs, flush=True)\n        sys.stdout = tmp\n\n    return logger\n\n\nasync def get_input_async(\n    session: PromptSession = None,\n    completer: WordCompleter = None,\n) -> str:\n    \"\"\"\n    Multiline input function.\n    \"\"\"\n    return await session.prompt_async(\n        completer=completer,\n        multiline=True,\n        auto_suggest=AutoSuggestFromHistory(),\n    )\n\n\nasync def async_main(args: argparse.Namespace) -> None:\n    \"\"\"\n    Main function\n    \"\"\"\n    print(\"Initializing...\")\n    print(\"Enter `alt+enter` or `escape+enter` to send a message\")\n    # Read and parse cookies\n    cookies = None\n    if args.cookie_file:\n        file_path = Path(args.cookie_file)\n        if file_path.exists():\n            with file_path.open(\"r\", encoding=\"utf-8\") as f:\n                cookies = json.load(f)\n    bot = await Chatbot.create(proxy=args.proxy, cookies=cookies)\n    session = create_session()\n    completer = create_completer([\"!help\", \"!exit\", \"!reset\"])\n    initial_prompt = args.prompt\n\n    # Log chat history\n    def p_hist(*args, **kwargs) -> None:\n        pass\n\n    if args.history_file:\n        history_file_path = Path(args.history_file)\n        f = history_file_path.open(\"a+\", encoding=\"utf-8\")\n        p_hist = _create_history_logger(f)\n\n    while True:\n        print(\"\\nYou:\")\n        p_hist(\"\\nYou:\")\n        if initial_prompt:\n            question = initial_prompt\n            print(question)\n            initial_prompt = None\n        else:\n            question = (\n                input()\n                if args.enter_once\n                else await get_input_async(session=session, completer=completer)\n            )\n        print()\n        p_hist(question + \"\\n\")\n        if question == \"!exit\":\n            await bot.close()\n            break\n        if question == \"!help\":\n            print(\n                \"\"\"\n            !help - Show this help message\n            !exit - Exit the program\n            !reset - Reset the conversation\n            \"\"\",\n            )\n            continue\n        if question == \"!reset\":\n            await bot.reset()\n            continue\n        print(\"Bot:\")\n        p_hist(\"Bot:\")\n        if args.no_stream:\n            response = (\n                await bot.ask(\n                    prompt=question,\n                    conversation_style=args.style,\n                    wss_link=args.wss_link,\n                    search_result=args.search_result,\n                    locale=args.locale,\n                )\n            )[\"item\"][\"messages\"][-1][\"adaptiveCards\"][0][\"body\"][0][\"text\"]\n            print(response)\n            p_hist(response)\n        else:\n            wrote = 0\n            if args.rich:\n                md = Markdown(\"\")\n                with Live(md, auto_refresh=False) as live:\n                    async for final, response in bot.ask_stream(\n                        prompt=question,\n                        conversation_style=args.style,\n                        wss_link=args.wss_link,\n                        search_result=args.search_result,\n                        locale=args.locale,\n                    ):\n                        if not final:\n                            if not wrote:\n                                p_hist(response, end=\"\")\n                            else:\n                                p_hist(response[wrote:], end=\"\")\n                            if wrote > len(response):\n                                print(md)\n                                print(Markdown(\"***Bing revoked the response.***\"))\n                            wrote = len(response)\n                            md = Markdown(response)\n                            live.update(md, refresh=True)\n            else:\n                async for final, response in bot.ask_stream(\n                    prompt=question,\n                    conversation_style=args.style,\n                    wss_link=args.wss_link,\n                    search_result=args.search_result,\n                    locale=args.locale,\n                ):\n                    if not final:\n                        if not wrote:\n                            print(response, end=\"\", flush=True)\n                            p_hist(response, end=\"\")\n                        else:\n                            print(response[wrote:], end=\"\", flush=True)\n                            p_hist(response[wrote:], end=\"\")\n                        wrote = len(response)\n                print()\n                p_hist()\n    if args.history_file:\n        f.close()\n    await bot.close()\n\n\ndef main() -> None:\n    print(\n        \"\"\"\n        EdgeGPT - A demo of reverse engineering the Bing GPT chatbot\n        Repo: github.com/acheong08/EdgeGPT\n        By: Antonio Cheong\n\n        !help for help\n\n        Type !exit to exit\n    \"\"\",\n    )\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--enter-once\", action=\"store_true\")\n    parser.add_argument(\"--search-result\", action=\"store_true\")\n    parser.add_argument(\"--no-stream\", action=\"store_true\")\n    parser.add_argument(\"--rich\", action=\"store_true\")\n    parser.add_argument(\n        \"--proxy\",\n        help=\"Proxy URL (e.g. socks5://127.0.0.1:1080)\",\n        type=str,\n    )\n    parser.add_argument(\n        \"--wss-link\",\n        help=\"WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)\",\n        type=str,\n        default=\"wss://sydney.bing.com/sydney/ChatHub\",\n    )\n    parser.add_argument(\n        \"--style\",\n        choices=[\"creative\", \"balanced\", \"precise\"],\n        default=\"balanced\",\n    )\n    parser.add_argument(\n        \"--prompt\",\n        type=str,\n        default=\"\",\n        required=False,\n        help=\"prompt to start with\",\n    )\n    parser.add_argument(\n        \"--cookie-file\",\n        type=str,\n        default=\"\",\n        required=False,\n        help=\"path to cookie file\",\n    )\n    parser.add_argument(\n        \"--history-file\",\n        type=str,\n        default=\"\",\n        required=False,\n        help=\"path to history file\",\n    )\n    parser.add_argument(\n        \"--locale\",\n        type=str,\n        default=\"en-US\",\n        required=False,\n        help=\"your locale\",\n    )\n    args = parser.parse_args()\n    asyncio.run(async_main(args))\n"
  },
  {
    "path": "src/EdgeGPT/request.py",
    "content": "import uuid\nfrom datetime import datetime\nfrom typing import Union\n\nfrom .conversation_style import CONVERSATION_STYLE_TYPE\nfrom .conversation_style import ConversationStyle\nfrom .utilities import get_location_hint_from_locale\nfrom .utilities import get_ran_hex\nfrom .utilities import guess_locale\n\n\nclass ChatHubRequest:\n    def __init__(\n        self,\n        conversation_signature: str,\n        client_id: str,\n        conversation_id: str,\n        invocation_id: int = 3,\n    ) -> None:\n        self.struct: dict = {}\n\n        self.client_id: str = client_id\n        self.conversation_id: str = conversation_id\n        self.conversation_signature: str = conversation_signature\n        self.invocation_id: int = invocation_id\n\n    def update(\n        self,\n        prompt: str,\n        conversation_style: CONVERSATION_STYLE_TYPE,\n        webpage_context: Union[str, None] = None,\n        search_result: bool = False,\n        locale: str = guess_locale(),\n    ) -> None:\n        options = [\n            \"deepleo\",\n            \"enable_debug_commands\",\n            \"disable_emoji_spoken_text\",\n            \"enablemm\",\n        ]\n        if conversation_style:\n            if not isinstance(conversation_style, ConversationStyle):\n                conversation_style = getattr(ConversationStyle, conversation_style)\n            options = conversation_style.value\n        message_id = str(uuid.uuid4())\n        # Get the current local time\n        now_local = datetime.now()\n\n        # Get the current UTC time\n        now_utc = datetime.utcnow()\n\n        # Calculate the time difference between local and UTC time\n        timezone_offset = now_local - now_utc\n\n        # Get the offset in hours and minutes\n        offset_hours = int(timezone_offset.total_seconds() // 3600)\n        offset_minutes = int((timezone_offset.total_seconds() % 3600) // 60)\n\n        # Format the offset as a string\n        offset_string = f\"{offset_hours:+03d}:{offset_minutes:02d}\"\n\n        # Get current time\n        timestamp = datetime.now().strftime(\"%Y-%m-%dT%H:%M:%S\") + offset_string\n        self.struct = {\n            \"arguments\": [\n                {\n                    \"source\": \"cib\",\n                    \"optionsSets\": options,\n                    \"allowedMessageTypes\": [\n                        \"ActionRequest\",\n                        \"Chat\",\n                        \"Context\",\n                        \"InternalSearchQuery\",\n                        \"InternalSearchResult\",\n                        \"Disengaged\",\n                        \"InternalLoaderMessage\",\n                        \"Progress\",\n                        \"RenderCardRequest\",\n                        \"AdsQuery\",\n                        \"SemanticSerp\",\n                        \"GenerateContentQuery\",\n                        \"SearchQuery\",\n                    ],\n                    \"sliceIds\": [\n                        \"winmuid1tf\",\n                        \"styleoff\",\n                        \"ccadesk\",\n                        \"smsrpsuppv4cf\",\n                        \"ssrrcache\",\n                        \"contansperf\",\n                        \"crchatrev\",\n                        \"winstmsg2tf\",\n                        \"creatgoglt\",\n                        \"creatorv2t\",\n                        \"sydconfigoptt\",\n                        \"adssqovroff\",\n                        \"530pstho\",\n                        \"517opinion\",\n                        \"418dhlth\",\n                        \"512sprtic1s0\",\n                        \"emsgpr\",\n                        \"525ptrcps0\",\n                        \"529rweas0\",\n                        \"515oscfing2s0\",\n                        \"524vidansgs0\",\n                    ],\n                    \"verbosity\": \"verbose\",\n                    \"traceId\": get_ran_hex(32),\n                    \"isStartOfSession\": self.invocation_id == 3,\n                    \"message\": {\n                        \"locale\": locale,\n                        \"market\": locale,\n                        \"region\": locale[-2:],  # en-US -> US\n                        \"locationHints\": get_location_hint_from_locale(locale),\n                        \"timestamp\": timestamp,\n                        \"author\": \"user\",\n                        \"inputMethod\": \"Keyboard\",\n                        \"text\": prompt,\n                        \"messageType\": \"Chat\",\n                        \"messageId\": message_id,\n                        \"requestId\": message_id,\n                    },\n                    \"tone\": conversation_style.name.capitalize(),  # Make first letter uppercase\n                    \"requestId\": message_id,\n                    \"conversationSignature\": self.conversation_signature,\n                    \"participant\": {\n                        \"id\": self.client_id,\n                    },\n                    \"conversationId\": self.conversation_id,\n                },\n            ],\n            \"invocationId\": str(self.invocation_id),\n            \"target\": \"chat\",\n            \"type\": 4,\n        }\n        if search_result:\n            have_search_result = [\n                \"InternalSearchQuery\",\n                \"InternalSearchResult\",\n                \"InternalLoaderMessage\",\n                \"RenderCardRequest\",\n            ]\n            self.struct[\"arguments\"][0][\"allowedMessageTypes\"] += have_search_result\n        if webpage_context:\n            self.struct[\"arguments\"][0][\"previousMessages\"] = [\n                {\n                    \"author\": \"user\",\n                    \"description\": webpage_context,\n                    \"contextType\": \"WebPage\",\n                    \"messageType\": \"Context\",\n                    \"messageId\": \"discover-web--page-ping-mriduna-----\",\n                },\n            ]\n        self.invocation_id += 1\n\n        # print(timestamp)\n"
  },
  {
    "path": "src/EdgeGPT/utilities.py",
    "content": "import json\nimport locale\nimport random\nimport sys\nfrom typing import Union\n\nfrom .constants import DELIMITER\nfrom .locale import LocationHint\n\n\ndef append_identifier(msg: dict) -> str:\n    # Convert dict to json string\n    return json.dumps(msg, ensure_ascii=False) + DELIMITER\n\n\ndef get_ran_hex(length: int = 32) -> str:\n    return \"\".join(random.choice(\"0123456789abcdef\") for _ in range(length))\n\n\ndef get_location_hint_from_locale(locale: str) -> Union[dict, None]:\n    locale = locale.lower()\n    if locale == \"en-gb\":\n        hint = LocationHint.UK.value\n    elif locale == \"en-ie\":\n        hint = LocationHint.EU.value\n    elif locale == \"zh-cn\":\n        hint = LocationHint.CHINA.value\n    else:\n        hint = LocationHint.USA.value\n    return hint.get(\"LocationHint\")\n\n\ndef guess_locale() -> str:\n    if sys.platform.startswith(\"win\"):\n        return \"en-us\"\n    loc, _ = locale.getlocale()\n    return loc.replace(\"_\", \"-\") if loc else \"en-us\"\n"
  },
  {
    "path": "test_base.py",
    "content": "import json\n\nimport pytest\nfrom EdgeGPT.EdgeGPT import Chatbot\nfrom EdgeGPT.EdgeGPT import ConversationStyle\n\npytest_plugins = (\"pytest_asyncio\",)\n\nfrom os import getenv\n\n\n@pytest.mark.asyncio()\nasync def test_ask() -> None:\n    bot = await Chatbot.create(cookies=getenv(\"EDGE_COOKIES\"))\n    response = await bot.ask(\n        prompt=\"find me some information about the new ai released by meta.\",\n        conversation_style=ConversationStyle.balanced,\n        simplify_response=True,\n    )\n    await bot.close()\n    print(json.dumps(response, indent=2))\n    assert response\n"
  }
]