├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ ├── custom.md │ └── feature_request.yml ├── tagger-pr.json ├── tagger.json └── workflows │ ├── GHCR.yml │ ├── PyPi.yml │ └── tests.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── Makefile ├── docs ├── CODE_OF_CONDUCT.md ├── README.md ├── README_es.md ├── README_ja.md ├── README_zh-cn.md ├── README_zh-tw.md └── wiki │ ├── EdgeGPT.md │ ├── Home.md │ └── ImageGen.md ├── example.env ├── requirements.txt ├── setup.cfg ├── setup.py ├── src └── EdgeGPT │ ├── EdgeGPT.py │ ├── EdgeUtils.py │ ├── ImageGen.py │ ├── __init__.py │ ├── chathub.py │ ├── constants.py │ ├── conversation.py │ ├── conversation_style.py │ ├── exceptions.py │ ├── locale.py │ ├── main.py │ ├── request.py │ └── utilities.py └── test_base.py /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: DO NOT OPEN A DUPLICATE 3 | title: "[Bug]: " 4 | 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | 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. 10 | options: 11 | - label: I have searched the existing issues and checked the recent builds/commits 12 | required: true 13 | - type: markdown 14 | attributes: 15 | value: | 16 | *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** 17 | - type: textarea 18 | id: what-did 19 | attributes: 20 | label: What happened? 21 | description: Tell us what happened in a very clear and simple way 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: steps 26 | attributes: 27 | label: Steps to reproduce the problem 28 | description: Please provide us with precise step by step information on how to reproduce the bug 29 | value: | 30 | 1. Go to .... 31 | 2. Press .... 32 | 3. ... 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: what-should 37 | attributes: 38 | label: What should have happened? 39 | description: Tell what you think the normal behavior should be 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: version 44 | attributes: 45 | label: Version where the problem happens 46 | description: "`python3 -m pip show revChatGPT`" 47 | validations: 48 | required: true 49 | - type: input 50 | id: python-version 51 | attributes: 52 | label: What Python version are you running this with? 53 | description: "`python3 -V`" 54 | - type: dropdown 55 | id: platforms 56 | attributes: 57 | label: What is your operating system ? 58 | multiple: true 59 | options: 60 | - Windows 61 | - Linux 62 | - MacOS 63 | - iOS 64 | - Android 65 | - Other/Cloud 66 | - type: textarea 67 | id: cmdargs 68 | attributes: 69 | label: Command Line Arguments 70 | description: Are you using any launching parameters/command line arguments (modified webui-user .bat/.sh) ? If yes, please write them below. Write "No" otherwise. 71 | render: Shell 72 | validations: 73 | required: true 74 | - type: textarea 75 | id: logs 76 | attributes: 77 | label: Console logs 78 | 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. 79 | render: Shell 80 | validations: 81 | required: true 82 | - type: textarea 83 | id: misc 84 | attributes: 85 | label: Additional information 86 | description: Please provide us with any relevant additional info or context. 87 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Discussions, questions, and niche issues in Github 4 | url: https://github.com/acheong08/EdgeGPT/discussions/new/choose 5 | about: For anything that is not an explicit bug 6 | - name: Discuss in the Discord 7 | url: https://discord.gg/9K2BvbXEHT 8 | about: 'Personal server: Chill discussions and more detailed updates' 9 | - name: Communicate in the Discord 10 | url: https://discord.gg/WMNtbDUjUv 11 | about: 'Public server: Large technical community with many high profile members like @transitive-bullshit' 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "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." 3 | about: "DO NOT PRESS THIS" 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest an idea for this project 3 | title: "[Feature Request]: " 4 | 5 | body: 6 | - type: checkboxes 7 | attributes: 8 | label: Is there an existing issue for this? 9 | 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. 10 | options: 11 | - label: I have searched the existing issues and checked the recent builds/commits 12 | required: true 13 | - type: markdown 14 | attributes: 15 | value: | 16 | *Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible* 17 | - type: textarea 18 | id: feature 19 | attributes: 20 | label: What would your feature do ? 21 | description: Tell us about your feature in a very clear and simple way, and what problem it would solve 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: workflow 26 | attributes: 27 | label: Proposed workflow 28 | description: Please provide us with step by step information on how you'd like the feature to be accessed and used 29 | value: | 30 | 1. Go to .... 31 | 2. Press .... 32 | 3. ... 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: misc 37 | attributes: 38 | label: Additional information 39 | description: Add any other context or screenshots about the feature request here. 40 | -------------------------------------------------------------------------------- /.github/tagger-pr.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "tag": "bugfix", 4 | "keywords": [ 5 | "Bug", 6 | "bug", 7 | "error", 8 | "Error", 9 | "fix", 10 | "Fix", 11 | "repair", 12 | "Repair" 13 | ] 14 | }, 15 | { 16 | "tag": "vulnerability", 17 | "keywords": ["vulnerability", "Vulnerability", "leak", "Leak"] 18 | }, 19 | { 20 | "tag": "enhancement", 21 | "keywords": [ 22 | "enhancement", 23 | "Enhancement", 24 | "Add", 25 | "add", 26 | "improve", 27 | "Improve" 28 | ] 29 | }, 30 | { 31 | "tag": "documentation", 32 | "keywords": ["docs", "documentation", "Wiki", "wiki"] 33 | }, 34 | { 35 | "tag": "authentication", 36 | "keywords": [ 37 | "auth", 38 | "Auth", 39 | "token", 40 | "Token", 41 | "TOKEN", 42 | "config", 43 | "Config", 44 | "CONFIG", 45 | "Unauthorized", 46 | "unauthorized", 47 | "401", 48 | "403", 49 | "502", 50 | "503", 51 | "forbidden", 52 | "Forbidden", 53 | "Access", 54 | "access", 55 | "block", 56 | "Block" 57 | ] 58 | }, 59 | { 60 | "tag": "network", 61 | "keywords": [ 62 | "bing.com", 63 | "ssl", 64 | "SSL", 65 | "HTTPS", 66 | "Connection", 67 | "connection", 68 | "proxy", 69 | "Proxy", 70 | "PROXY", 71 | "VPN" 72 | ] 73 | }, 74 | { 75 | "tag": "CLI", 76 | "keywords": [ 77 | "command program", 78 | "Command Program", 79 | "CLI", 80 | "keybindings", 81 | "Keybindings" 82 | ] 83 | }, 84 | { "tag": "API", "keywords": ["api", "API"] } 85 | ] 86 | -------------------------------------------------------------------------------- /.github/tagger.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "tag": "bug", "keywords": ["Bug", "bug", "error", "Error"] }, 3 | { 4 | "tag": "vulnerability", 5 | "keywords": ["vulnerability", "Vulnerability", "leak", "Leak"] 6 | }, 7 | { 8 | "tag": "enhancement", 9 | "keywords": [ 10 | "suggestion", 11 | "enhancement", 12 | "feature request", 13 | "Feature request", 14 | "Feature Request" 15 | ] 16 | }, 17 | { 18 | "tag": "documentation", 19 | "keywords": ["docs", "documentation", "Wiki", "wiki"] 20 | }, 21 | { "tag": "help wanted", "keywords": ["help wanted"] }, 22 | { 23 | "tag": "question", 24 | "keywords": ["question", "Question", "Why", "why", "How", "how"] 25 | }, 26 | { 27 | "tag": "authentication", 28 | "keywords": [ 29 | "auth", 30 | "Auth", 31 | "token", 32 | "Token", 33 | "TOKEN", 34 | "config", 35 | "Config", 36 | "CONFIG", 37 | "Unauthorized", 38 | "unauthorized", 39 | "401", 40 | "403", 41 | "502", 42 | "503", 43 | "forbidden", 44 | "Forbidden", 45 | "Access", 46 | "access", 47 | "block", 48 | "Block" 49 | ] 50 | }, 51 | { 52 | "tag": "network", 53 | "keywords": [ 54 | "bing.com", 55 | "ssl", 56 | "SSL", 57 | "HTTPS", 58 | "Connection", 59 | "connection", 60 | "proxy", 61 | "Proxy", 62 | "PROXY", 63 | "VPN" 64 | ] 65 | }, 66 | { 67 | "tag": "CLI", 68 | "keywords": [ 69 | "command program", 70 | "Command Program", 71 | "CLI", 72 | "keybindings", 73 | "Keybindings" 74 | ] 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /.github/workflows/GHCR.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker 🐳 images 📦 to GitHub Container Registry 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build-release-and-publish-to-ghcr: 9 | if: github.event_name == 'release' 10 | # Explicitly grant the `secrets.GITHUB_TOKEN` permissions. 11 | permissions: 12 | # Grant the ability to write to GitHub Packages (push Docker images to 13 | # GitHub Container Registry). 14 | packages: write 15 | name: Build and publish Release Docker 🐳 images 📦 to GitHub Container Registry 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout main 19 | uses: actions/checkout@v3 20 | with: 21 | fetch-depth: 0 22 | - name: Get actual patch version 23 | id: actual_patch_version 24 | run: echo ::set-output name=ACTUAL_PATCH_VERSION::$(echo $GITHUB_REF | cut -d / -f 3 ) 25 | - name: Get actual minor version 26 | id: actual_minor_version 27 | run: echo ::set-output name=ACTUAL_MINOR_VERSION::$(echo $GITHUB_REF | cut -d / -f 3 | cut -d "." -f 1,2) 28 | - name: Get actual major version 29 | id: actual_major_version 30 | run: echo ::set-output name=ACTUAL_MAJOR_VERSION::$(echo $GITHUB_REF | cut -d / -f 3 | cut -d "." -f 1) 31 | - name: Docker compliant registry for image pushes 32 | id: registry 33 | run: echo "REGISTRY=$(echo 'ghcr.io/${{github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV 34 | - name: Extract metadata (tags, labels) for Docker 35 | id: meta 36 | uses: docker/metadata-action@v4 37 | with: 38 | images: ghcr.io/${{ github.repository }} 39 | - name: Set up QEMU 40 | uses: docker/setup-qemu-action@v2 41 | with: 42 | platforms: 'arm64' 43 | - name: Set up Docker Buildx 44 | uses: docker/setup-buildx-action@v2 45 | 46 | - name: Login to GitHub Container Registry 47 | uses: docker/login-action@v2 48 | with: 49 | registry: ghcr.io 50 | # This is the user that triggered the Workflow. In this case, it will 51 | # either be the user whom created the Release or manually triggered 52 | # the workflow_dispatch. 53 | username: ${{ github.actor }} 54 | # `secrets.GITHUB_TOKEN` is a secret that's automatically generated by 55 | # GitHub Actions at the start of a workflow run to identify the job. 56 | # This is used to authenticate against GitHub Container Registry. 57 | # See https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret 58 | # for more detailed information. 59 | password: ${{ secrets.GH_TOKEN }} 60 | 61 | - name: Build and push alpine based image 62 | uses: docker/build-push-action@v4 63 | with: 64 | file: Dockerfile 65 | context: . 66 | push: true # push the image to ghcr 67 | tags: | 68 | ${{ env.REGISTRY }}:latest 69 | ${{ env.REGISTRY }}:${{ steps.actual_patch_version.outputs.ACTUAL_PATCH_VERSION }}, 70 | ${{ env.REGISTRY }}:${{ steps.actual_minor_version.outputs.ACTUAL_MINOR_VERSION }} 71 | ${{ env.REGISTRY }}:${{ steps.actual_major_version.outputs.ACTUAL_MAJOR_VERSION }} 72 | ${{ env.REGISTRY }}:${{github.sha}} 73 | labels: ${{ steps.meta.outputs.labels }} 74 | platforms: linux/amd64,linux/arm64 75 | cache-from: type=gha 76 | cache-to: type=gha,mode=max 77 | -------------------------------------------------------------------------------- /.github/workflows/PyPi.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | permissions: 8 | contents: read 9 | 10 | jobs: 11 | deploy: 12 | runs-on: ubuntu-latest 13 | if: github.event_name == 'release' 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Set up Python 17 | uses: actions/setup-python@v4 18 | with: 19 | python-version: '3.11.0' 20 | - name: Install dependencies 21 | run: make 22 | - name: Build package 23 | run: make build 24 | - name: Publish package 25 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 26 | with: 27 | user: __token__ 28 | password: ${{ secrets.PYPI_API_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | pull_request_target: 7 | types: [ ready_for_review, review_requested, edited] 8 | 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | python-version: [3.8, 3.11 ] 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Set up Python ${{ matrix.python-version }} 21 | uses: actions/setup-python@v2 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Install dependencies 25 | env: 26 | EDGE_COOKIES: ${{ secrets.EDGE_COOKIES }} 27 | run: | 28 | python -m pip install pytest pytest-asyncio 29 | python -m pip install . 30 | - name: Test with pytest 31 | run: | 32 | pytest . -s 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | cookies.json 131 | output/* 132 | README.md 133 | *.json 134 | 135 | # Pycharm 136 | .idea/* 137 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/asottile/reorder_python_imports 3 | rev: v3.9.0 4 | hooks: 5 | - id: reorder-python-imports 6 | args: [--py37-plus] 7 | - repo: https://github.com/asottile/add-trailing-comma 8 | rev: v2.3.0 9 | hooks: 10 | - id: add-trailing-comma 11 | args: [--py36-plus] 12 | - repo: https://github.com/asottile/pyupgrade 13 | rev: v3.3.1 14 | hooks: 15 | - id: pyupgrade 16 | args: [--py37-plus] 17 | 18 | - repo: https://github.com/pre-commit/pre-commit-hooks 19 | rev: v4.4.0 20 | hooks: 21 | - id: trailing-whitespace 22 | - id: end-of-file-fixer 23 | - id: check-yaml 24 | - id: debug-statements 25 | - id: double-quote-string-fixer 26 | - id: name-tests-test 27 | - id: requirements-txt-fixer 28 | - repo: https://github.com/psf/black 29 | rev: 22.10.0 30 | hooks: 31 | - id: black 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "websockets" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM docker.io/library/python:3.11.3-alpine3.16@sha256:0ba61d06b14e5438aa3428ee46c7ccdc8df5b63483bc91ae050411407eb5cbf4 AS builder 2 | 3 | WORKDIR /EdgeGPT 4 | 5 | COPY requirements.txt requirements.txt 6 | 7 | RUN pip3 install --no-cache-dir -r requirements.txt 8 | 9 | COPY . . 10 | 11 | RUN apk add --no-cache make && \ 12 | make init && make build && make ci && apk del make && \ 13 | rm -Rf /root/.cache/pip 14 | 15 | 16 | ENTRYPOINT ["python3", "-m" , "EdgeGPT.EdgeGPT"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: docs 2 | init: 3 | python -m pip install --upgrade pip 4 | python -m pip install -r ./requirements.txt --upgrade 5 | python -m pip install build setuptools wheel flake8 --upgrade 6 | build: 7 | python -m build 8 | ci: 9 | python -m flake8 src --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 10 | python -m flake8 src --count --select=E9,F63,F7,F82 --show-source --statistics 11 | python setup.py install 12 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | acheong@student.dalat.org. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | > # This project has been archived. Due to personal circumstances, I lack the time to maintain this repository. 2 | 3 |
4 | EdgeGPT 5 | 6 | # Edge GPT 7 | 8 | _The reverse engineering the chat feature of the new version of Bing_ 9 | 10 | English - 11 | 简体中文 - 12 | 繁體中文 - 13 | Español - 14 | 日本語 15 | 16 |
17 | 18 |

19 | 20 | PyPI version 21 | 22 | Python version 23 | Total downloads 24 | 25 |

26 | 27 |
28 | 29 | 30 | 31 | # Setup 32 | 33 | 34 | 35 | ## Install package 36 | 37 | ```bash 38 | python3 -m pip install EdgeGPT --upgrade 39 | ``` 40 | 41 | ## Requirements 42 | 43 | - python 3.8+ 44 | - A Microsoft Account with access to (Optional, depending on your region) 45 | - Required in a supported country or region with New Bing (Chinese mainland VPN required) 46 | - [Selenium](https://pypi.org/project/selenium/) (for automatic cookie setup) 47 | 48 | ## Authentication 49 | 50 | !!! POSSIBLY NOT REQUIRED ANYMORE !!! 51 | 52 | **In some regions**, Microsoft has made the chat feature **available** to everyone, so you might be able to **skip this step**. You can check this with a browser (with user-agent set to reflect Edge), by **trying to start a chat without logging in**. 53 | 54 | It was also found that it might **depend on your IP address**. For example, if you try to access the chat features from an IP that is known to **belong to a datacenter range** (vServers, root servers, VPN, common proxies, ...), **you might be required to log in** while being able to access the features just fine from your home IP address. 55 | 56 | If you receive the following error, you can try **providing a cookie** and see if it works then: 57 | 58 | `Exception: Authentication failed. You have not been accepted into the beta.` 59 | 60 | ### Collect cookies 61 | 62 | 1. Get a browser that looks like Microsoft Edge. 63 | 64 | - a) (Easy) Install the latest version of Microsoft Edge 65 | - b) (Advanced) Alternatively, you can use any browser and set the user-agent to look like you're using Edge (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`). You can do this easily with an extension like "User-Agent Switcher and Manager" for [Chrome](https://chrome.google.com/webstore/detail/user-agent-switcher-and-m/bhchdcejhohfmigjafbampogmaanbfkg) and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/). 66 | 67 | 2. Open [bing.com/chat](https://bing.com/chat) 68 | 3. If you see a chat feature, you are good to continue... 69 | 4. Install the cookie editor extension for [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) or [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 70 | 5. Go to [bing.com](https://bing.com) 71 | 6. Open the extension 72 | 7. Click "Export" on the bottom right, then "Export as JSON" (This saves your cookies to clipboard) 73 | 8. Paste your cookies into a file `bing_cookies_*.json`. 74 | - NOTE: The **cookies file name MUST follow the regex pattern `bing_cookies_*.json`**, so that they could be recognized by internal cookie processing mechanisms 75 | 76 | ### Use cookies in code: 77 | 78 | ```python 79 | cookies = json.loads(open("./path/to/cookies.json", encoding="utf-8").read()) # might omit cookies option 80 | bot = await Chatbot.create(cookies=cookies) 81 | ``` 82 | 83 |
84 | 85 |
86 | 87 | 88 | 89 | # How to use Chatbot 90 | 91 | 92 | 93 | ## Run from Command Line 94 | 95 | ``` 96 | $ python3 -m EdgeGPT.EdgeGPT -h 97 | 98 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 99 | Repo: github.com/acheong08/EdgeGPT 100 | By: Antonio Cheong 101 | 102 | !help for help 103 | 104 | Type !exit to exit 105 | 106 | usage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] 107 | [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE] 108 | [--history-file HISTORY_FILE] [--locale LOCALE] 109 | 110 | options: 111 | -h, --help show this help message and exit 112 | --enter-once 113 | --search-result 114 | --no-stream 115 | --rich 116 | --proxy PROXY Proxy URL (e.g. socks5://127.0.0.1:1080) 117 | --wss-link WSS_LINK WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub) 118 | --style {creative,balanced,precise} 119 | --prompt PROMPT prompt to start with 120 | --cookie-file COOKIE_FILE 121 | path to cookie file 122 | --history-file HISTORY_FILE 123 | path to history file 124 | --locale LOCALE your locale (e.g. en-US, zh-CN, en-IE, en-GB) 125 | ``` 126 | 127 | (China/US/UK/Norway has enhanced support for locale) 128 | 129 | ## Run in Python 130 | 131 | ### 1. The `Chatbot` class and `asyncio` for more granular control 132 | 133 | Use Async for the best experience, for example: 134 | 135 | ```python 136 | import asyncio, json 137 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 138 | 139 | async def main(): 140 | bot = await Chatbot.create() # Passing cookies is "optional", as explained above 141 | response = await bot.ask(prompt="Hello world", conversation_style=ConversationStyle.creative, simplify_response=True) 142 | print(json.dumps(response, indent=2)) # Returns 143 | """ 144 | { 145 | "text": str, 146 | "author": str, 147 | "sources": list[dict], 148 | "sources_text": str, 149 | "suggestions": list[str], 150 | "messages_left": int 151 | } 152 | """ 153 | await bot.close() 154 | 155 | if __name__ == "__main__": 156 | asyncio.run(main()) 157 | ``` 158 | 159 | ### 2) The `Query` and `Cookie` helper classes 160 | 161 | Create a simple Bing Chat AI query (using the 'precise' conversation style by default) and see just the main text output rather than the whole API response: 162 | 163 | Remeber to store your cookies in a specific format: `bing_cookies_*.json`. 164 | 165 | ```python 166 | from EdgeGPT.EdgeUtils import Query, Cookie 167 | 168 | q = Query("What are you? Give your answer as Python code") 169 | print(q) 170 | ``` 171 | 172 | The default directory for storing Cookie files is `HOME/bing_cookies` but you can change it with: 173 | 174 | ```python 175 | Cookie.dir_path = Path(r"...") 176 | ``` 177 | 178 | Or change the conversation style or cookie file to be used: 179 | 180 | ```python 181 | q = Query( 182 | "What are you? Give your answer as Python code", 183 | style="creative", # or: 'balanced', 'precise' 184 | cookie_file="./bing_cookies_alternative.json" 185 | ) 186 | 187 | # Use `help(Query)` to see other supported parameters. 188 | ``` 189 | 190 | Quickly extract the text output, code snippets, list of sources/references, or suggested follow-on questions from a response using the following attributes: 191 | 192 | ```python 193 | q.output # Also: print(q) 194 | q.sources 195 | q.sources_dict 196 | q.suggestions 197 | q.code 198 | q.code_blocks 199 | q.code_block_formatsgiven) 200 | ``` 201 | 202 | Get the orginal prompt and the conversation style you specified: 203 | 204 | ```python 205 | q.prompt 206 | q.ignore_cookies 207 | q.style 208 | q.simplify_response 209 | q.locale 210 | repr(q) 211 | ``` 212 | 213 | Access previous Queries made since importing `Query`: 214 | 215 | ```python 216 | Query.index # A list of Query objects; updated dynamically 217 | Query.image_dir_path 218 | 219 | ``` 220 | 221 | And finally, the `Cookie` class supports multiple cookie files, so if you create additional cookie files with the naming convention `bing_cookies_*.json`, your queries will automatically try using the next file (alphabetically) if you've exceeded your daily quota of requests (currently set at 200). 222 | 223 | Here are the main attributes which you can access: 224 | 225 | ```python 226 | Cookie.current_file_index 227 | Cookie.current_file_path 228 | Cookie.current_data 229 | Cookie.dir_path 230 | Cookie.search_pattern 231 | Cookie.files 232 | Cookie.image_token 233 | Cookie.import_next 234 | Cookie.rotate_cookies 235 | Cookie.ignore_files 236 | Cookie.supplied_files 237 | Cookie.request_count 238 | ``` 239 | 240 | --- 241 | 242 | ## Run with Docker 243 | 244 | This assumes you have a file cookies.json in your current working directory 245 | 246 | ```bash 247 | 248 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt 249 | ``` 250 | 251 | You can add any extra flags as following 252 | 253 | ```bash 254 | 255 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative 256 | ``` 257 | 258 |
259 | 260 | 261 | 262 |
263 | 264 | 265 | 266 | # How to use Image generator 267 | 268 | 269 | 270 | ## Run from Command Line 271 | 272 | ```bash 273 | $ python3 -m ImageGen.ImageGen -h 274 | usage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio] 275 | 276 | optional arguments: 277 | -h, --help show this help message and exit 278 | -U U Auth cookie from browser 279 | --cookie-file COOKIE_FILE 280 | File containing auth cookie 281 | --prompt PROMPT Prompt to generate images for 282 | --output-dir OUTPUT_DIR 283 | Output directory 284 | --quiet Disable pipeline messages 285 | --asyncio Run ImageGen using asyncio 286 | ``` 287 | 288 | ## Run in Python 289 | 290 | ### 1) The `ImageQuery` helper class 291 | 292 | Generate images based on a simple prompt and download to the current working directory: 293 | 294 | ```python 295 | from EdgeGPT.EdgeUtils import ImageQuery 296 | 297 | q=ImageQuery("Meerkats at a garden party in Devon") 298 | ``` 299 | 300 | Change the download directory for all future images in this session: 301 | 302 | ``` 303 | Query.image_dirpath = Path("./to_another_folder") 304 | ``` 305 | 306 | ### 2) The `ImageGen` class and `asyncio` for more granular control 307 | 308 | ```python 309 | from EdgeGPT.ImageGen import ImageGen 310 | import argparse 311 | import json 312 | 313 | async def async_image_gen(args) -> None: 314 | async with ImageGenAsync(args.U, args.quiet) as image_generator: 315 | images = await image_generator.get_images(args.prompt) 316 | await image_generator.save_images(images, output_dir=args.output_dir) 317 | 318 | if __name__ == "__main__": 319 | parser = argparse.ArgumentParser() 320 | parser.add_argument("-U", help="Auth cookie from browser", type=str) 321 | parser.add_argument("--cookie-file", help="File containing auth cookie", type=str) 322 | parser.add_argument( 323 | "--prompt", 324 | help="Prompt to generate images for", 325 | type=str, 326 | required=True, 327 | ) 328 | parser.add_argument( 329 | "--output-dir", 330 | help="Output directory", 331 | type=str, 332 | default="./output", 333 | ) 334 | parser.add_argument( 335 | "--quiet", help="Disable pipeline messages", action="store_true" 336 | ) 337 | parser.add_argument( 338 | "--asyncio", help="Run ImageGen using asyncio", action="store_true" 339 | ) 340 | args = parser.parse_args() 341 | # Load auth cookie 342 | with open(args.cookie_file, encoding="utf-8") as file: 343 | cookie_json = json.load(file) 344 | for cookie in cookie_json: 345 | if cookie.get("name") == "_U": 346 | args.U = cookie.get("value") 347 | break 348 | 349 | if args.U is None: 350 | raise Exception("Could not find auth cookie") 351 | 352 | if not args.asyncio: 353 | # Create image generator 354 | image_generator = ImageGen(args.U, args.quiet) 355 | image_generator.save_images( 356 | image_generator.get_images(args.prompt), 357 | output_dir=args.output_dir, 358 | ) 359 | else: 360 | asyncio.run(async_image_gen(args)) 361 | 362 | ``` 363 | 364 |
365 | 366 |
367 | 368 | 369 | 370 | # Star History 371 | 372 | 373 | 374 | [![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date) 375 | 376 |
377 | 378 |
379 | 380 | 381 | 382 | # Contributors 383 | 384 | 385 | 386 | This project exists thanks to all the people who contribute. 387 | 388 | 389 | 390 | 391 | 392 |
393 | -------------------------------------------------------------------------------- /docs/README_es.md: -------------------------------------------------------------------------------- 1 |
2 | EdgeGPT 3 | 4 | # Edge GPT 5 | 6 | _Ingeniería inversa al nuevo chat integrado en Bing_ 7 | 8 | English - 9 | 简体中文 - 10 | 繁體中文 - 11 | Español - 12 | 日本語 13 | 14 |
15 | 16 |

17 | 18 | PyPI version 19 | 20 | Python version 21 | Total downloads 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | # Configuración 30 | 31 | 32 | 33 | ## Instalación 34 | 35 | ```bash 36 | python3 -m pip install EdgeGPT --upgrade 37 | ``` 38 | 39 | ## Requisitos 40 | 41 | 42 | - python 3.8+ 43 | - Una cuenta de Microsoft con acceso a (Opcional, dependiendo de tu país) 44 | - Estar localizado en un país con acceso al nuevo Bing (VPN necesaria para usarios de China) 45 | - [Selenium](https://pypi.org/project/selenium/) (para la recolección automática de cookies) 46 | 47 | ## Autenticación 48 | 49 | !!! ES PROBABLE QUE YA NO SEA NECESARIO !!! 50 | 51 | **En algunas regiones**, Microsoft ha **abierto** la función de chat para todos, 52 | por lo que es posible que **puedas saltarte este paso**. 53 | Puedes comprobarlo con un navegador (con el user-agent modificado para parecer Edge), 54 | **intentando abrir un chat sin haber iniciado sesión**. 55 | 56 | Se ha encontrado que es posible que sea **dependiente de tu dirección IP**. 57 | Por ejemplo, si intentas acceder a la función de chat desde una dirección IP que se conoce 58 | que **pertenece al rango de un centro de datos** (vServers, servidores raíz, VPN, proxies conocidos, ...), 59 | **es posible que tengas que iniciar sesión** y sin embargo puedas acceder sin problemas a las funciones 60 | desde tu casa. 61 | 62 | Si recibes el siguiente error, puedes probar **usando cookies** y viendo si funciona: 63 | 64 | `Exception: Authentication failed. You have not been accepted into the beta.` 65 | 66 | ### Recolección de cookies 67 | 68 | 1. Necesitas un navegador que _parezca_ Microsoft Edge. 69 | 70 | * a) (Fácil) Instala la última versión de Microsoft Edge 71 | * b) (Avanzado) De forma alternativa, puedes usar cualquier navegador y 72 | cambiar el user-agent para que parezca que estás usando Edge 73 | (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`). 74 | 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) 75 | o [Firefox](https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/). 76 | 77 | 2. Abrir [bing.com/chat](https://bing.com/chat) 78 | 3. Si ves la nueva función de chat, es que puedes continuar... 79 | 4. Instala la extensión cookie editor para [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) o 80 | [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 81 | 5. Ve a [bing.com](https://bing.com) 82 | 6. Abre la extensión 83 | 7. Pulsa en "Export" abajo a la derecha, luego "Export as JSON" (Esto guarda la cookie en el portapapeles) 84 | 8. Pega el contenido en un fichero `bing_cookies_*.json`. 85 | * NOTA: Los **ficheros de cookies DEBEN seguir el siguiente formato de nombre `bing_cookies_*.json`**, 86 | de manera que puedan ser reconocidos por los mecanismos internos de procesado de cookies. 87 | 88 | 89 | 90 | ### Uso de la cookie en la librería: 91 | ```python 92 | cookies = json.loads(open("./path/to/cookies.json", encoding="utf-8").read()) # might omit cookies option 93 | bot = await Chatbot.create(cookies=cookies) 94 | ``` 95 | 96 |
97 | 98 |
99 | 100 | 101 | 102 | # Cómo usar Chatbot 103 | 104 | 105 | 106 | ## Ejecución desde la línea de comandos 107 | 108 | ``` 109 | $ python3 -m EdgeGPT.EdgeGPT -h 110 | 111 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 112 | Repo: github.com/acheong08/EdgeGPT 113 | By: Antonio Cheong 114 | 115 | !help for help 116 | 117 | Type !exit to exit 118 | 119 | usage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] 120 | [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE] 121 | [--history-file HISTORY_FILE] [--locale LOCALE] 122 | 123 | options: 124 | -h, --help show this help message and exit 125 | --enter-once 126 | --search-result 127 | --no-stream 128 | --rich 129 | --proxy PROXY Proxy URL (e.g. socks5://127.0.0.1:1080) 130 | --wss-link WSS_LINK WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub) 131 | --style {creative,balanced,precise} 132 | --prompt PROMPT prompt to start with 133 | --cookie-file COOKIE_FILE 134 | path to cookie file 135 | --history-file HISTORY_FILE 136 | path to history file 137 | --locale LOCALE your locale (e.g. en-US, zh-CN, en-IE, en-GB) 138 | ``` 139 | (China/US/UK/Norway disponen de mejor soporte para la localización) 140 | 141 | ## Ejecución en Python 142 | 143 | ### 1. La clase `Chatbot` y la librería `asyncio` para un control más exhaustivo 144 | 145 | Usa Async para una mejor experiencia, por ejemplo: 146 | 147 | ```python 148 | import asyncio, json 149 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 150 | 151 | async def main(): 152 | bot = await Chatbot.create() # Passing cookies is "optional", as explained above 153 | response = await bot.ask(prompt="Hello world", conversation_style=ConversationStyle.creative, simplify_response=True) 154 | print(json.dumps(response, indent=2)) # Returns 155 | """ 156 | { 157 | "text": str 158 | "author": str 159 | "sources": list[dict] 160 | "sources_text": str 161 | "suggestions": list[str] 162 | "messages_left": int 163 | } 164 | """ 165 | await bot.close() 166 | 167 | if __name__ == "__main__": 168 | asyncio.run(main()) 169 | ``` 170 | 171 | ### 2) Las clases auxiliares `Query` y `Cookie` 172 | 173 | Realiza una petición a la IA de Bing Chat (usa el estilo 'precise' de conversación por defecto) 174 | y devuelve la respuesta final sin ver todo el resultado de la API: 175 | 176 | Recuerda almacenar las cookies en el formato: `bing_cookies_*.json`. 177 | 178 | ```python 179 | from EdgeGPT.EdgeUtils import Query, Cookie 180 | 181 | q = Query("What are you? Give your answer as Python code") 182 | print(q) 183 | ``` 184 | 185 | También puedes cambiar el estilo de conversación o el fichero de cookie a usar: 186 | 187 | ```python 188 | q = Query( 189 | "What are you? Give your answer as Python code", 190 | style="creative", # or: 'balanced', 'precise' 191 | cookie_file="./bing_cookies_alternative.json" 192 | ) 193 | ``` 194 | 195 | Obtén rápidamente la respuesta, fragmentos de código, lista de fuentes/referencias, 196 | o las preguntas sugeridas usando los siguientes atributos: 197 | 198 | ```python 199 | q.output 200 | q.code 201 | q.suggestions 202 | q.sources # for the full json output 203 | q.sources_dict # for a dictionary of titles and urls 204 | ``` 205 | 206 | Obtén la pregunta inicial o el estilo de conversación usado: 207 | 208 | ```python 209 | q.prompt 210 | q.style 211 | repr(q) 212 | ``` 213 | 214 | Accede a la lista de peticiones realizadas: 215 | 216 | ```python 217 | Query.index # A list of Query objects; updated dynamically 218 | Query.request_count # A tally of requests made using each cookie file 219 | ``` 220 | 221 | Finalmente, la clase `Cookie` admite múltiples ficheros de cookie, de manera que 222 | si has creado ficheros adicionales de cookies usando el formato de nombrado 223 | `bing_cookies_*.json`, las peticiones intentarán usar automáticamente el siguiente 224 | fichero (alfabéticamente) si has sobrepasado el límite diario de peticiones (actualmente limitado a 200). 225 | 226 | Principales atributos que tienes a disposición: 227 | 228 | ```python 229 | Cookie.current_file_index 230 | Cookie.dirpath 231 | Cookie.search_pattern # default is `bing_cookies_*.json` 232 | Cookie.files() # list as files that match .search_pattern 233 | Cookie.current_filepath 234 | Cookie.current_data 235 | Cookie.import_next() 236 | Cookie.image_token 237 | Cookie.ignore_files 238 | ``` 239 | 240 | --- 241 | 242 | ## Ejecución en Docker 243 | 244 | Este ejemplo asume que dispones de un fichero `cookies.json` en tu directorio actual 245 | 246 | ```bash 247 | 248 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt 249 | ``` 250 | 251 | Puedes añadir argumentos adicionales de la siguiente manera 252 | 253 | ```bash 254 | 255 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative 256 | ``` 257 | 258 |
259 | 260 | 261 | 262 |
263 | 264 | 265 | 266 | # Cómo usar Image generator 267 | 268 | 269 | 270 | ## Ejecución desde la línea de comandos 271 | 272 | ```bash 273 | $ python3 -m ImageGen.ImageGen -h 274 | usage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio] 275 | 276 | optional arguments: 277 | -h, --help show this help message and exit 278 | -U U Auth cookie from browser 279 | --cookie-file COOKIE_FILE 280 | File containing auth cookie 281 | --prompt PROMPT Prompt to generate images for 282 | --output-dir OUTPUT_DIR 283 | Output directory 284 | --quiet Disable pipeline messages 285 | --asyncio Run ImageGen using asyncio 286 | ``` 287 | 288 | ## Ejecución en Python 289 | 290 | ### 1) La clase auxiliar `ImageQuery` 291 | 292 | Genera imágenes de acuerdo a la petición y las descarga en el directorio actual 293 | 294 | ```python 295 | from EdgeGPT.EdgeUtils import ImageQuery 296 | 297 | q=ImageQuery("Meerkats at a garden party in Devon") 298 | ``` 299 | 300 | Cambia el directorio de descarga para las demás imágenes que se descarguen durante el resto de sesión 301 | 302 | ``` 303 | Query.image_dirpath = Path("./to_another_folder") 304 | ``` 305 | 306 | ### 2) Usa las clases `ImageGen` y `asyncio` para un control más minucioso 307 | 308 | ```python 309 | from EdgeGPT.ImageGen import ImageGen 310 | import argparse 311 | import json 312 | 313 | async def async_image_gen(args) -> None: 314 | async with ImageGenAsync(args.U, args.quiet) as image_generator: 315 | images = await image_generator.get_images(args.prompt) 316 | await image_generator.save_images(images, output_dir=args.output_dir) 317 | 318 | if __name__ == "__main__": 319 | parser = argparse.ArgumentParser() 320 | parser.add_argument("-U", help="Auth cookie from browser", type=str) 321 | parser.add_argument("--cookie-file", help="File containing auth cookie", type=str) 322 | parser.add_argument( 323 | "--prompt", 324 | help="Prompt to generate images for", 325 | type=str, 326 | required=True, 327 | ) 328 | parser.add_argument( 329 | "--output-dir", 330 | help="Output directory", 331 | type=str, 332 | default="./output", 333 | ) 334 | parser.add_argument( 335 | "--quiet", help="Disable pipeline messages", action="store_true" 336 | ) 337 | parser.add_argument( 338 | "--asyncio", help="Run ImageGen using asyncio", action="store_true" 339 | ) 340 | args = parser.parse_args() 341 | # Load auth cookie 342 | with open(args.cookie_file, encoding="utf-8") as file: 343 | cookie_json = json.load(file) 344 | for cookie in cookie_json: 345 | if cookie.get("name") == "_U": 346 | args.U = cookie.get("value") 347 | break 348 | 349 | if args.U is None: 350 | raise Exception("Could not find auth cookie") 351 | 352 | if not args.asyncio: 353 | # Create image generator 354 | image_generator = ImageGen(args.U, args.quiet) 355 | image_generator.save_images( 356 | image_generator.get_images(args.prompt), 357 | output_dir=args.output_dir, 358 | ) 359 | else: 360 | asyncio.run(async_image_gen(args)) 361 | 362 | ``` 363 | 364 |
365 | 366 |
367 | 368 | 369 | 370 | # Historial de estrellas 371 | 372 | 373 | 374 | [![Gráfica historial de estrellas](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date) 375 | 376 |
377 | 378 |
379 | 380 | 381 | 382 | # Contribuidores 383 | 384 | 385 | 386 | Este proyecto existe gracias a todas las personas que apoyan y contribuyen. 387 | 388 | 389 | 390 | 391 | 392 |
393 | -------------------------------------------------------------------------------- /docs/README_ja.md: -------------------------------------------------------------------------------- 1 |
2 | EdgeGPT 3 | 4 | # Edge GPT 5 | 6 | _Bing の新バージョンのチャット機能のリバースエンジニアリング_ 7 | 8 | English - 9 | 简体中文 - 10 | 繁體中文 - 11 | Español - 12 | 日本語 13 | 14 |
15 | 16 |

17 | 18 | PyPI version 19 | 20 | Python version 21 | 22 | Total downloads 23 | 24 |

25 | 26 | --- 27 | 28 | ## 設定 29 | 30 | ### パッケージをインストール 31 | 32 | ```bash 33 | python3 -m pip install EdgeGPT --upgrade 34 | ``` 35 | 36 | ### 要件 37 | 38 | - python 3.8+ 39 | - に早期アクセスできる Microsoft アカウント(必須) 40 | - New Bing のサポート国で必要(中国本土のVPNは必須) 41 | 42 |
43 | 44 | 45 | ### アクセスの確認 (必須) 46 | 47 | 48 | 49 | - Microsoft Edge の最新バージョンをインストール 50 | - また、任意のブラウザを使用し、ユーザーエージェントを 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"のような拡張機能を使えば、簡単に行えます。 51 | - [bing.com/chat](https://bing.com/chat) を開く 52 | - チャット機能が表示されたら、準備完了 53 | 54 |
55 | 56 |
57 | 58 | 59 | ### 認証の取得 (必須) 60 | 61 | 62 | 63 | - [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) または [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) の Cookie エディター拡張機能をインストール 64 | - `bing.com` へ移動 65 | - 拡張機能を開く 66 | - 右下の"エクスポート"から"JSONとしてエクスポート"をクリック(これで Cookie がクリップボードに保存されます) 67 | - クッキーをファイル `cookies.json` に貼り付け 68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | ## チャットボット 76 | 77 | 78 | 79 | ## 使用方法 80 | 81 | ### クイックスタート 82 | 83 | ``` 84 | $ python3 -m EdgeGPT.EdgeGPT -h 85 | 86 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 87 | Repo: github.com/acheong08/EdgeGPT 88 | By: Antonio Cheong 89 | 90 | !help for help 91 | 92 | Type !exit to exit 93 | Enter twice to send message or set --enter-once to send one line message 94 | 95 | usage: EdgeGPT.py [-h] [--enter-once] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] [--style {creative,balanced,precise}] 96 | [--cookie-file COOKIE_FILE] 97 | 98 | options: 99 | -h, --help show this help message and exit 100 | --enter-once 101 | --no-stream 102 | --rich 103 | --proxy PROXY Proxy URL (e.g. socks5://127.0.0.1:1080) 104 | --wss-link WSS_LINK WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub) 105 | --style {creative,balanced,precise} 106 | --cookie-file COOKIE_FILE 107 | needed if environment variable COOKIE_FILE is not set 108 | ``` 109 | 110 | --- 111 | 112 | ## Docker での実行 113 | 114 | これは、現在の作業ディレクトリに cookies.json ファイルがあることを前提としています 115 | 116 | ``` bash 117 | 118 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt 119 | ``` 120 | 121 | 次のように追加のフラグを追加できます 122 | 123 | ``` bash 124 | 125 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative 126 | ``` 127 | 128 | ### 開発者デモ 129 | 130 | Cookie を渡す 3 つの方法: 131 | 132 | - 環境変数: `export COOKIE_FILE=/path/to/cookies.json` 。 133 | - 引数 `cookie_path` には、次のように `cookies.json` へのパスを指定する: 134 | 135 | ```python 136 | bot = Chatbot(cookie_path='./cookies.json') 137 | ``` 138 | 139 | - 次のように、引数 `cookies` で直接クッキーを渡します: 140 | 141 | ```python 142 | with open('./cookies.json', 'r') as f: 143 | cookies = json.load(f) 144 | bot = Chatbot(cookies=cookies) 145 | ``` 146 | 147 | 最高のエクスペリエンスを得るには Async を使用してください 148 | 149 | より高度な使用例の参照コード: 150 | 151 | ```python 152 | import asyncio 153 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 154 | 155 | async def main(): 156 | bot = await Chatbot.create() 157 | print(await bot.ask(prompt="Hello world", conversation_style=ConversationStyle.creative, wss_link="wss://sydney.bing.com/sydney/ChatHub")) 158 | await bot.close() 159 | 160 | 161 | if __name__ == "__main__": 162 | asyncio.run(main()) 163 | 164 | ``` 165 | 166 |
167 | 168 |
169 | 170 | 171 | 172 | ## 画像ジェネレーター 173 | 174 | 175 | 176 | ```bash 177 | $ python3 -m ImageGen.ImageGen -h 178 | usage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio] 179 | 180 | optional arguments: 181 | -h, --help show this help message and exit 182 | -U U Auth cookie from browser 183 | --cookie-file COOKIE_FILE 184 | File containing auth cookie 185 | --prompt PROMPT Prompt to generate images for 186 | --output-dir OUTPUT_DIR 187 | Output directory 188 | --quiet Disable pipeline messages 189 | --asyncio Run ImageGen using asyncio 190 | ``` 191 | 192 | ### 開発者デモ 193 | 194 | ```python 195 | from EdgeGPT.ImageGen import ImageGen 196 | import argparse 197 | import json 198 | 199 | async def async_image_gen(args) -> None: 200 | async with ImageGenAsync(args.U, args.quiet) as image_generator: 201 | images = await image_generator.get_images(args.prompt) 202 | await image_generator.save_images(images, output_dir=args.output_dir) 203 | 204 | if __name__ == "__main__": 205 | parser = argparse.ArgumentParser() 206 | parser.add_argument("-U", help="Auth cookie from browser", type=str) 207 | parser.add_argument("--cookie-file", help="File containing auth cookie", type=str) 208 | parser.add_argument( 209 | "--prompt", 210 | help="Prompt to generate images for", 211 | type=str, 212 | required=True, 213 | ) 214 | parser.add_argument( 215 | "--output-dir", 216 | help="Output directory", 217 | type=str, 218 | default="./output", 219 | ) 220 | parser.add_argument( 221 | "--quiet", help="Disable pipeline messages", action="store_true" 222 | ) 223 | parser.add_argument( 224 | "--asyncio", help="Run ImageGen using asyncio", action="store_true" 225 | ) 226 | args = parser.parse_args() 227 | # 認証クッキーを読み込む 228 | with open(args.cookie_file, encoding="utf-8") as file: 229 | cookie_json = json.load(file) 230 | for cookie in cookie_json: 231 | if cookie.get("name") == "_U": 232 | args.U = cookie.get("value") 233 | break 234 | 235 | if args.U is None: 236 | raise Exception("Could not find auth cookie") 237 | 238 | if not args.asyncio: 239 | # 画像ジェネレーターの作成 240 | image_generator = ImageGen(args.U, args.quiet) 241 | image_generator.save_images( 242 | image_generator.get_images(args.prompt), 243 | output_dir=args.output_dir, 244 | ) 245 | else: 246 | asyncio.run(async_image_gen(args)) 247 | 248 | ``` 249 | 250 |
251 | 252 | ## Star ヒストリー 253 | 254 | [![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date) 255 | 256 | ## コントリビューター 257 | 258 | このプロジェクトが存在するのはコントリビュートしてくださるすべての方々のおかげです。 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /docs/README_zh-cn.md: -------------------------------------------------------------------------------- 1 |
2 | EdgeGPT 3 | 4 | # Edge GPT 5 | 6 | _新必应聊天功能的逆向工程_ 7 | 8 | English - 9 | 简体中文 - 10 | 繁體中文 - 11 | Español - 12 | 日本語 13 | 14 |
15 | 16 |

17 | 18 | PyPI version 19 | 20 | Python version 21 | Total downloads 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | # 设置 30 | 31 | 32 | 33 | ## 安装模块 34 | 35 | ```bash 36 | python3 -m pip install EdgeGPT --upgrade 37 | ``` 38 | 39 | ## 要求 40 | 41 | - python 3.8+ 42 | - 一个可以访问必应聊天的微软账户 (可选,视所在地区而定) 43 | - 需要在 New Bing 支持的国家或地区(中国大陆需使用VPN) 44 | - [Selenium](https://pypi.org/project/selenium/) (对于需要自动配置cookie的情况) 45 | 46 | ## 身份验证 47 | 48 | 基本上不需要了。 49 | 50 | **在部分地区**,微软已将聊天功能**开放**给所有人,或许可以**省略这一步**了。可以使用浏览器来确认(将 UA 设置为能表示为 Edge 的),**试一下能不能不登录就可以开始聊天**。 51 | 52 | 可能也得**看当前所在 IP 地址**。例如,如果试图从一个已知**属于数据中心范围**的 IP 来访问聊天功能(虚拟服务器、根服务器、虚拟专网、公共代理等),**可能就需要登录**;但是要是用家里的 IP 地址访问聊天功能,就没有问题。 53 | 54 | 如果收到这样的错误,可以试试**提供一个 cookie** 看看能不能解决: 55 | 56 | `Exception: Authentication failed. You have not been accepted into the beta.` 57 | 58 | ### 收集 cookie 59 | 60 | 1. 获取一个看着像 Microsoft Edge 的浏览器。 61 | 62 | - a) (简单) 安装最新版本的 Microsoft Edge 63 | - 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/) 这样的扩展轻松完成此操作。 64 | 65 | 2. 打开 [bing.com/chat](https://bing.com/chat) 66 | 3. 如果您看到聊天功能,就接着下面的步骤... 67 | 4. 安装 [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) 或 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 的 cookie editor 扩展 68 | 5. 移步到 [bing.com](https://bing.com) 69 | 6. 打开扩展程序 70 | 7. 点击右下角的"导出" ,然后点击"导出为 JSON" (将会把内容保存到你的剪贴板上) 71 | 8. 把你剪贴板上的内容粘贴到 `bing_cookies_*.json` 文件中 72 | - 注意:**Cookie 文件名必须遵守正则表达式 `bing_cookies_*.json`**,这样才能让本模块的 cookie 处理程序识别到。 73 | 74 | ### 在代码中使用 cookie: 75 | 76 | ```python 77 | cookies = json.loads(open("./path/to/cookies.json", encoding="utf-8").read()) # 可能会忽略 cookie 选项 78 | bot = await Chatbot.create(cookies=cookies) 79 | ``` 80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 | # 如何使用聊天机器人 88 | 89 | 90 | 91 | ## 从命令行运行 92 | 93 | ``` 94 | $ python3 -m EdgeGPT.EdgeGPT -h 95 | 96 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 97 | Repo: github.com/acheong08/EdgeGPT 98 | By: Antonio Cheong 99 | 100 | !help for help 101 | 102 | Type !exit to exit 103 | 104 | usage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] 105 | [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE] 106 | [--history-file HISTORY_FILE] [--locale LOCALE] 107 | 108 | options: 109 | -h, --help show this help message and exit 110 | --enter-once 111 | --search-result 112 | --no-stream 113 | --rich 114 | --proxy PROXY Proxy URL (e.g. socks5://127.0.0.1:1080) 115 | --wss-link WSS_LINK WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub) 116 | --style {creative,balanced,precise} 117 | --prompt PROMPT prompt to start with 118 | --cookie-file COOKIE_FILE 119 | path to cookie file 120 | --history-file HISTORY_FILE 121 | path to history file 122 | --locale LOCALE your locale (e.g. en-US, zh-CN, en-IE, en-GB) 123 | ``` 124 | (中/美/英/挪具有更好的本地化支持) 125 | 126 | ## 在 Python 运行 127 | 128 | ### 1. 使用 `Chatbot` 类和 `asyncio` 类以进行更精细的控制 129 | 130 | 使用 async 获得最佳体验,例如: 131 | 132 | ```python 133 | import asyncio, json 134 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 135 | 136 | async def main(): 137 | bot = await Chatbot.create() # 导入 cookie 是“可选”的,如前所述 138 | response = await bot.ask(prompt="Hello world", conversation_style=ConversationStyle.creative, simplify_response=True) 139 | print(json.dumps(response, indent=2)) # 返回下面这些 140 | """ 141 | { 142 | "text": str, 143 | "author": str, 144 | "sources": list[dict], 145 | "sources_text": str, 146 | "suggestions": list[str], 147 | "messages_left": int 148 | } 149 | """ 150 | await bot.close() 151 | 152 | if __name__ == "__main__": 153 | asyncio.run(main()) 154 | ``` 155 | 156 | 157 | ### 2) `Query` 和 `Cookie` 助手类 158 | 159 | 创建一个简易的必应聊天 AI 查询(默认使用精确模式),这样可以只查看主要的文字输出,而不会打出整个 API 响应内容: 160 | 161 | 注意按照特定格式保存 cookie:`bing_cookies_*.json`. 162 | 163 | ```python 164 | from EdgeGPT.EdgeUtils import Query, Cookie 165 | 166 | q = Query("你是谁?用python代码给出回答") 167 | print(q) 168 | ``` 169 | 170 | 保存 cookie 文件的默认目录是 `HOME/bing_cookies`,不过可以这样修改: 171 | 172 | ```python 173 | Cookie.dir_path = Path(r"...") 174 | ``` 175 | 176 | 也可以修改对话模式,或者指定要使用的 cookie 文件: 177 | 178 | ```python 179 | q = Query( 180 | "你是谁?用python代码给出回答", 181 | style="creative", # 或者平衡模式 'balanced' 或者精确模式 'precise' 182 | cookie_file="./bing_cookies_alternative.json" 183 | ) 184 | 185 | # 使用 `help(Query)` 查看其他支持的参数。 186 | ``` 187 | 188 | 使用以下属性快速提取文本输出、代码片段、来源/参考列表或建议的后续问题: 189 | 190 | ```python 191 | q.output # 或者: print(q) 192 | q.sources 193 | q.sources_dict 194 | q.suggestions 195 | q.code 196 | q.code_blocks 197 | q.code_block_formatsgiven) 198 | ``` 199 | 200 | 获得原始 prompt 和指定的对话模式: 201 | 202 | ```python 203 | q.prompt 204 | q.ignore_cookies 205 | q.style 206 | q.simplify_response 207 | q.locale 208 | repr(q) 209 | ``` 210 | 211 | 通过 import `Query` 获取之前的查询: 212 | 213 | ```python 214 | Query.index # 一个查询对象的列表;是动态更新的 215 | Query.image_dir_path 216 | 217 | ``` 218 | 219 | 最后,`Cookie` 类支持多个 Cookie 文件,因此,如果您使用命名约定 `bing_cookies_*.json` 创建其他 Cookie 文件,那么如果超过了每日请求配额(目前设置为 200),查询将自动尝试使用下一个文件(按字母顺序)。 220 | 221 | 这些是可以访问的主要属性: 222 | 223 | ```python 224 | Cookie.current_file_index 225 | Cookie.current_file_path 226 | Cookie.current_data 227 | Cookie.dir_path 228 | Cookie.search_pattern 229 | Cookie.files 230 | Cookie.image_token 231 | Cookie.import_next 232 | Cookie.rotate_cookies 233 | Cookie.ignore_files 234 | Cookie.supplied_files 235 | Cookie.request_count 236 | ``` 237 | 238 | --- 239 | 240 | ## 使用 Docker 运行 241 | 242 | 假设当前工作目录有一个文件 `cookies.json` 243 | 244 | ```bash 245 | 246 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt 247 | ``` 248 | 249 | 可以像这样添加任意参数 250 | 251 | ```bash 252 | 253 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative 254 | ``` 255 | 256 |
257 | 258 | 259 | 260 |
261 | 262 | 263 | 264 | # 如何使用图片生成器 265 | 266 | 267 | 268 | ## 从命令行运行 269 | 270 | ```bash 271 | $ python3 -m ImageGen.ImageGen -h 272 | usage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio] 273 | 274 | optional arguments: 275 | -h, --help show this help message and exit 276 | -U U Auth cookie from browser 277 | --cookie-file COOKIE_FILE 278 | File containing auth cookie 279 | --prompt PROMPT Prompt to generate images for 280 | --output-dir OUTPUT_DIR 281 | Output directory 282 | --quiet Disable pipeline messages 283 | --asyncio Run ImageGen using asyncio 284 | ``` 285 | 286 | ## 在 Python 运行 287 | 288 | ### 1) `ImageQuery` 助手类 289 | 290 | 使用一个简易提示生成图像,并下载到当前工作目录: 291 | 292 | ```python 293 | from EdgeGPT.EdgeUtils import ImageQuery 294 | 295 | q=ImageQuery("Meerkats at a garden party in Devon") 296 | ``` 297 | 298 | 在此会话中修改所有后续图像的下载目录: 299 | 300 | ``` 301 | Query.image_dirpath = Path("./to_another_folder") 302 | ``` 303 | 304 | ### 2) 使用 `ImageGen` 类和 `asyncio` 类以进行更精细的控制 305 | 306 | ```python 307 | from EdgeGPT.ImageGen import ImageGen 308 | import argparse 309 | import json 310 | 311 | async def async_image_gen(args) -> None: 312 | async with ImageGenAsync(args.U, args.quiet) as image_generator: 313 | images = await image_generator.get_images(args.prompt) 314 | await image_generator.save_images(images, output_dir=args.output_dir) 315 | 316 | if __name__ == "__main__": 317 | parser = argparse.ArgumentParser() 318 | parser.add_argument("-U", help="从浏览器获取的身份认证 cookie", type=str) 319 | parser.add_argument("--cookie-file", help="包含认证 cookie 的文件", type=str) 320 | parser.add_argument( 321 | "--prompt", 322 | help="用于生成图片的 prompt", 323 | type=str, 324 | required=True, 325 | ) 326 | parser.add_argument( 327 | "--output-dir", 328 | help="输出目录", 329 | type=str, 330 | default="./output", 331 | ) 332 | parser.add_argument( 333 | "--quiet", help="禁用管道消息", action="store_true" 334 | ) 335 | parser.add_argument( 336 | "--asyncio", help="使用 asyncio 运行 ImageGen", action="store_true" 337 | ) 338 | args = parser.parse_args() 339 | # 加载认证 cookie 340 | with open(args.cookie_file, encoding="utf-8") as file: 341 | cookie_json = json.load(file) 342 | for cookie in cookie_json: 343 | if cookie.get("name") == "_U": 344 | args.U = cookie.get("value") 345 | break 346 | 347 | if args.U is None: 348 | raise Exception("未能找到认证 Cookie") 349 | 350 | if not args.asyncio: 351 | # 创建图像生成器 352 | image_generator = ImageGen(args.U, args.quiet) 353 | image_generator.save_images( 354 | image_generator.get_images(args.prompt), 355 | output_dir=args.output_dir, 356 | ) 357 | else: 358 | asyncio.run(async_image_gen(args)) 359 | 360 | ``` 361 | 362 |
363 | 364 |
365 | 366 | 367 | 368 | # Star 历史 369 | 370 | 371 | 372 | [![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date) 373 | 374 |
375 | 376 |
377 | 378 | 379 | 380 | # 贡献者 381 | 382 | 383 | 384 | 这个项目的存在要归功于所有做出贡献的人。 385 | 386 | 387 | 388 | 389 | 390 |
391 | -------------------------------------------------------------------------------- /docs/README_zh-tw.md: -------------------------------------------------------------------------------- 1 |
2 | EdgeGPT 3 | 4 | # Edge GPT 5 | 6 | _新必應聊天功能的逆向工程_ 7 | 8 | English - 9 | 简体中文 - 10 | 繁體中文 - 11 | Español - 12 | 日本語 13 | 14 |
15 | 16 |

17 | 18 | PyPI version 19 | 20 | Python version 21 | Total downloads 22 | 23 |

24 | 25 |
26 | 27 | 28 | 29 | # 設置 30 | 31 | 32 | 33 | ## 安裝模組 34 | 35 | ```bash 36 | python3 -m pip install EdgeGPT --upgrade 37 | ``` 38 | 39 | ## 要求 40 | 41 | - python 3.8+ 42 | - 一個可以訪問必應聊天的微軟帳戶 (可選,取決於所在地區) 43 | - 需要在 New Bing 支持的國家或地區(中國大陸需使用VPN) 44 | - [Selenium](https://pypi.org/project/selenium/) (對於需要自動配置cookie的情況) 45 | 46 | ## 認證 47 | 48 | 基本上不需要了。 49 | 50 | **在某些地區**,微軟已將聊天功能**開放**給所有人,或許可以**省略這一步**了。您可以使用瀏覽器進行檢查(將 UA 設置為能表示為 Edge 的),**嘗試能否在不登錄的情況下開始聊天**。 51 | 52 | 可能也得**看當前所在 IP 位址**。例如,如果試圖從一個已知**屬於數據中心範圍**的 IP 來訪問聊天功能(虛擬伺服器、根伺服器、虛擬專網、公共代理等),**可能就需要登錄**;但是要是用家裡的 IP 位址訪問聊天功能,就沒有問題。 53 | 54 | 如果收到這樣的錯誤,可以試試**提供一個 cookie** 看看能不能解決: 55 | 56 | `Exception: Authentication failed. You have not been accepted into the beta.` 57 | 58 | ### 收集 cookie 59 | 60 | 1. 獲取一個看著像 Microsoft Edge 的瀏覽器。 61 | 62 | * a) (簡單) 安裝最新版本的 Microsoft Edge 63 | * 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/) 這樣的擴展輕鬆完成此操作. 64 | 65 | 2. 打開 [bing.com/chat](https://bing.com/chat) 66 | 3. 如果您看到聊天功能,就接著下面的步驟... 67 | 4. 安裝 [Chrome](https://chrome.google.com/webstore/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) 或 [Firefox](https://addons.mozilla.org/en-US/firefox/addon/cookie-editor/) 的 cookie editor 擴展 68 | 5. 轉到 [bing.com](https://bing.com) 69 | 6. 打開擴展程式 70 | 7. 單擊右下角的「匯出」,然後按「匯出為 JSON」(這會將您的 cookie 保存到剪貼簿) 71 | 8. 將您剪貼簿上的 cookie 粘貼到檔 `bing_cookies_*.json` 中 72 | * 注意:**cookie 檔名必須遵循正則表示式 `bing_cookies_*.json`**,這樣才能讓本模組的 cookie 處理程式識別到。 73 | 74 | ### 在代碼中使用 cookie: 75 | 76 | ```python 77 | cookies = json.loads(open("./path/to/cookies.json", encoding="utf-8").read()) # 可能会忽略 cookie 选项 78 | bot = await Chatbot.create(cookies=cookies) 79 | ``` 80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 | # 如何使用聊天機器人 88 | 89 | 90 | 91 | ## 從命令列運行 92 | 93 | ``` 94 | $ python3 -m EdgeGPT.EdgeGPT -h 95 | 96 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 97 | Repo: github.com/acheong08/EdgeGPT 98 | By: Antonio Cheong 99 | 100 | !help for help 101 | 102 | Type !exit to exit 103 | 104 | usage: EdgeGPT.py [-h] [--enter-once] [--search-result] [--no-stream] [--rich] [--proxy PROXY] [--wss-link WSS_LINK] 105 | [--style {creative,balanced,precise}] [--prompt PROMPT] [--cookie-file COOKIE_FILE] 106 | [--history-file HISTORY_FILE] [--locale LOCALE] 107 | 108 | options: 109 | -h, --help show this help message and exit 110 | --enter-once 111 | --search-result 112 | --no-stream 113 | --rich 114 | --proxy PROXY Proxy URL (e.g. socks5://127.0.0.1:1080) 115 | --wss-link WSS_LINK WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub) 116 | --style {creative,balanced,precise} 117 | --prompt PROMPT prompt to start with 118 | --cookie-file COOKIE_FILE 119 | path to cookie file 120 | --history-file HISTORY_FILE 121 | path to history file 122 | --locale LOCALE your locale (e.g. en-US, zh-CN, en-IE, en-GB) 123 | ``` 124 | (中/美/英/挪具有更好的本地化支援) 125 | 126 | ## 在 Python 運行 127 | 128 | ### 1. 使用 `Chatbot` 類和 `asyncio` 類以進行更精細的控制 129 | 130 | 使用 async 獲得最佳體驗,例如: 131 | 132 | ```python 133 | import asyncio, json 134 | from EdgeGPT.EdgeGPT import Chatbot, ConversationStyle 135 | 136 | async def main(): 137 | bot = await Chatbot.create() # 導入 cookie 是“可選”的,如前所述 138 | response = await bot.ask(prompt="Hello world", conversation_style=ConversationStyle.creative, simplify_response=True) 139 | print(json.dumps(response, indent=2)) # 返回如下 140 | """ 141 | { 142 | "text": str, 143 | "author": str, 144 | "sources": list[dict], 145 | "sources_text": str, 146 | "suggestions": list[str], 147 | "messages_left": int 148 | } 149 | """ 150 | await bot.close() 151 | 152 | if __name__ == "__main__": 153 | asyncio.run(main()) 154 | ``` 155 | 156 | 157 | ### 2) `Query` 和 `Cookie` 助手類 158 | 159 | 創建一個簡單的必應聊天 AI 查詢(預設情況下使用“精確”對話樣式),這樣可以僅查看主要文本輸出,而不是整個 API 回應: 160 | 161 | 注意按照特定格式儲存 cookie: ```bing_cookies_*.json```。 162 | 163 | ```python 164 | from EdgeGPT.EdgeUtils import Query, Cookie 165 | 166 | q = Query("你是誰?用python代碼給出回答") 167 | print(q) 168 | ``` 169 | 170 | 儲存 Cookie 檔的預設目錄是 `HOME/bing_cookies`,但您可以通過以下方式更改它: 171 | 172 | ```python 173 | Cookie.dir_path = Path(r"...") 174 | ``` 175 | 176 | 或者更改要使用的對話風格或 Cookie 檔: 177 | 178 | ```python 179 | q = Query( 180 | "你是誰?用python代碼給出回答", 181 | style="creative", # 或者平衡模式 'balanced' 或者精確模式 'precise' 182 | cookies="./bing_cookies_alternative.json" 183 | ) 184 | 185 | # 使用 `help(Query)` 查看其他支持的參數。 186 | ``` 187 | 188 | 使用以下屬性快速提取文字輸出、代碼片段、來源/參考清單或建議的後續問題: 189 | 190 | ```python 191 | q.output # 或者: print(q) 192 | q.sources 193 | q.sources_dict 194 | q.suggestions 195 | q.code 196 | q.code_blocks 197 | q.code_block_formatsgiven) 198 | ``` 199 | 200 | 抓取原始 prompt 與您指定的對話風格: 201 | 202 | ```python 203 | q.prompt 204 | q.ignore_cookies 205 | q.style 206 | q.simplify_response 207 | q.locale 208 | repr(q) 209 | ``` 210 | 211 | 通過 import `Query` 獲取進行的先前查詢: 212 | 213 | ```python 214 | Query.index # 一个查詢物件的串列;是動態更新的 215 | Query.image_dir_path 216 | 217 | ``` 218 | 219 | 最後,`Cookie` 類支援多個 cookie 檔,因此,如果您使用命名約定 `bing_cookies_*.json` 創建其他 cookie 檔,則如果您的請求數已超出每日配額(當前設置為 200),您的查詢將自動嘗試使用下一個檔(按字母順序)。 220 | 221 | 以下是您可以獲得的主要屬性: 222 | 223 | ```python 224 | Cookie.current_file_index 225 | Cookie.current_file_path 226 | Cookie.current_data 227 | Cookie.dir_path 228 | Cookie.search_pattern 229 | Cookie.files 230 | Cookie.image_token 231 | Cookie.import_next 232 | Cookie.rotate_cookies 233 | Cookie.ignore_files 234 | Cookie.supplied_files 235 | Cookie.request_count 236 | ``` 237 | 238 | --- 239 | 240 | ## 使用 Docker 運行 241 | 242 | 假設在當前工作目錄中有一個檔 `cookies.json` 243 | 244 | ```bash 245 | 246 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt 247 | ``` 248 | 249 | 可以像這樣添加任意參數 250 | 251 | ```bash 252 | 253 | docker run --rm -it -v $(pwd)/cookies.json:/cookies.json:ro -e COOKIE_FILE='/cookies.json' ghcr.io/acheong08/edgegpt --rich --style creative 254 | ``` 255 | 256 |
257 | 258 | 259 | 260 |
261 | 262 | 263 | 264 | # 如何使用圖像生成器 265 | 266 | 267 | 268 | ## 從命令列運行 269 | 270 | ```bash 271 | $ python3 -m ImageGen.ImageGen -h 272 | usage: ImageGen.py [-h] [-U U] [--cookie-file COOKIE_FILE] --prompt PROMPT [--output-dir OUTPUT_DIR] [--quiet] [--asyncio] 273 | 274 | optional arguments: 275 | -h, --help show this help message and exit 276 | -U U Auth cookie from browser 277 | --cookie-file COOKIE_FILE 278 | File containing auth cookie 279 | --prompt PROMPT Prompt to generate images for 280 | --output-dir OUTPUT_DIR 281 | Output directory 282 | --quiet Disable pipeline messages 283 | --asyncio Run ImageGen using asyncio 284 | ``` 285 | 286 | ## 在 Python 運行 287 | 288 | ### 1) `ImageQuery` 助手類 289 | 290 | 根據一個簡單的提示產生圖像並下載到目前工作目錄: 291 | 292 | ```python 293 | from EdgeGPT.EdgeUtils import ImageQuery 294 | 295 | q=ImageQuery("Meerkats at a garden party in Devon") 296 | ``` 297 | 298 | 在此工作階段中修改所有後續圖像的下載目錄: 299 | 300 | ``` 301 | Query.image_dirpath = Path("./to_another_folder") 302 | ``` 303 | 304 | ### 2) 使用 `ImageGen` 類和 `asyncio` 類以進行更精細的控制 305 | 306 | ```python 307 | from EdgeGPT.ImageGen import ImageGen 308 | import argparse 309 | import json 310 | 311 | async def async_image_gen(args) -> None: 312 | async with ImageGenAsync(args.U, args.quiet) as image_generator: 313 | images = await image_generator.get_images(args.prompt) 314 | await image_generator.save_images(images, output_dir=args.output_dir) 315 | 316 | if __name__ == "__main__": 317 | parser = argparse.ArgumentParser() 318 | parser.add_argument("-U", help="來自瀏覽器的身份驗證 cookie", type=str) 319 | parser.add_argument("--cookie-file", help="包含身份驗證 cookie 的檔", type=str) 320 | parser.add_argument( 321 | "--prompt", 322 | help="用于產生圖像的 prompt", 323 | type=str, 324 | required=True, 325 | ) 326 | parser.add_argument( 327 | "--output-dir", 328 | help="輸出目錄", 329 | type=str, 330 | default="./output", 331 | ) 332 | parser.add_argument( 333 | "--quiet", help="禁用管道消息", action="store_true" 334 | ) 335 | parser.add_argument( 336 | "--asyncio", help="使用 asyncio 運行 ImageGen", action="store_true" 337 | ) 338 | args = parser.parse_args() 339 | # 載入身份驗證 cookie 340 | with open(args.cookie_file, encoding="utf-8") as file: 341 | cookie_json = json.load(file) 342 | for cookie in cookie_json: 343 | if cookie.get("name") == "_U": 344 | args.U = cookie.get("value") 345 | break 346 | 347 | if args.U is None: 348 | raise Exception("找不到身份驗證 Cookie") 349 | 350 | if not args.asyncio: 351 | # 創建圖片生成器 352 | image_generator = ImageGen(args.U, args.quiet) 353 | image_generator.save_images( 354 | image_generator.get_images(args.prompt), 355 | output_dir=args.output_dir, 356 | ) 357 | else: 358 | asyncio.run(async_image_gen(args)) 359 | 360 | ``` 361 | 362 |
363 | 364 |
365 | 366 | 367 | 368 | # Star 歷史 369 | 370 | 371 | 372 | [![Star History Chart](https://api.star-history.com/svg?repos=acheong08/EdgeGPT&type=Date)](https://star-history.com/#acheong08/EdgeGPT&Date) 373 | 374 |
375 | 376 |
377 | 378 | 379 | 380 | # 貢獻者 381 | 382 | 383 | 384 | 這個專案的存在要歸功於所有做出貢獻的人。 385 | 386 | 387 | 388 | 389 | 390 |
391 | -------------------------------------------------------------------------------- /docs/wiki/EdgeGPT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # EdgeGPT.EdgeGPT 4 | 5 | Main.py 6 | 7 | 8 | 9 | ## Chatbot Objects 10 | 11 | ```python 12 | class Chatbot() 13 | ``` 14 | 15 | Combines everything to make it seamless 16 | 17 | 18 | 19 | #### save\_conversation 20 | 21 | ```python 22 | async def save_conversation(filename: str) -> None 23 | ``` 24 | 25 | Save the conversation to a file 26 | 27 | 28 | 29 | #### load\_conversation 30 | 31 | ```python 32 | async def load_conversation(filename: str) -> None 33 | ``` 34 | 35 | Load the conversation from a file 36 | 37 | 38 | 39 | #### get\_conversation 40 | 41 | ```python 42 | async def get_conversation() -> dict 43 | ``` 44 | 45 | Gets the conversation history from conversation_id (requires load_conversation) 46 | 47 | 48 | 49 | #### get\_activity 50 | 51 | ```python 52 | async def get_activity() -> dict 53 | ``` 54 | 55 | Gets the recent activity (requires cookies) 56 | 57 | 58 | 59 | #### ask 60 | 61 | ```python 62 | async def ask(prompt: str, 63 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 64 | conversation_style: CONVERSATION_STYLE_TYPE = None, 65 | webpage_context: str | None = None, 66 | search_result: bool = False, 67 | locale: str = guess_locale(), 68 | simplify_response: bool = False) -> dict 69 | ``` 70 | 71 | Ask a question to the bot 72 | Response: 73 | { 74 | item (dict): 75 | messages (list[dict]): 76 | adaptiveCards (list[dict]): 77 | body (list[dict]): 78 | text (str): Response 79 | } 80 | To get the response, you can do: 81 | response["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"] 82 | 83 | 84 | 85 | #### ask\_stream 86 | 87 | ```python 88 | async def ask_stream( 89 | prompt: str, 90 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 91 | conversation_style: CONVERSATION_STYLE_TYPE = None, 92 | raw: bool = False, 93 | webpage_context: str | None = None, 94 | search_result: bool = False, 95 | locale: str = guess_locale() 96 | ) -> Generator[bool, dict | str, None] 97 | ``` 98 | 99 | Ask a question to the bot 100 | 101 | 102 | 103 | #### close 104 | 105 | ```python 106 | async def close() -> None 107 | ``` 108 | 109 | Close the connection 110 | 111 | 112 | 113 | #### delete\_conversation 114 | 115 | ```python 116 | async def delete_conversation(conversation_id: str = None, 117 | conversation_signature: str = None, 118 | client_id: str = None) -> None 119 | ``` 120 | 121 | Delete the chat in the server and close the connection 122 | 123 | 124 | 125 | #### reset 126 | 127 | ```python 128 | async def reset(delete=False) -> None 129 | ``` 130 | 131 | Reset the conversation 132 | -------------------------------------------------------------------------------- /docs/wiki/Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the EdgeGPT wiki! 2 | 3 | ## Languages 4 | 5 | - English 6 | - [简体中文](https://github.com/CoolPlayLin/ChatGPT-Wiki/tree/main/docs/EdgeGPT) 7 | -------------------------------------------------------------------------------- /docs/wiki/ImageGen.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ImageGen 4 | 5 | 6 | 7 | ## ImageGen Objects 8 | 9 | ```python 10 | class ImageGen() 11 | ``` 12 | 13 | Image generation by Microsoft Bing 14 | 15 | **Arguments**: 16 | 17 | - `auth_cookie` - str 18 | 19 | 20 | 21 | #### get\_images 22 | 23 | ```python 24 | def get_images(prompt: str) -> list 25 | ``` 26 | 27 | Fetches image links from Bing 28 | 29 | **Arguments**: 30 | 31 | - `prompt` - str 32 | 33 | 34 | 35 | #### save\_images 36 | 37 | ```python 38 | def save_images(links: list, output_dir: str) -> None 39 | ``` 40 | 41 | Saves images to output directory 42 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | export BING_U="" 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | BingImageCreator 3 | certifi 4 | httpx 5 | prompt_toolkit 6 | requests 7 | rich 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file=README.md 3 | license_files=LICENSE 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from setuptools import find_packages 4 | from setuptools import setup 5 | 6 | DOCS_PATH = Path(__file__).parents[0] / "docs/README.md" 7 | PATH = Path("README.md") 8 | if not PATH.exists(): 9 | with Path.open(DOCS_PATH, encoding="utf-8") as f1: 10 | with Path.open(PATH, "w+", encoding="utf-8") as f2: 11 | f2.write(f1.read()) 12 | 13 | setup( 14 | name="EdgeGPT", 15 | version="0.13.2", 16 | license="The Unlicense", 17 | author="Antonio Cheong", 18 | author_email="acheong@student.dalat.org", 19 | description="Reverse engineered Edge Chat API", 20 | packages=find_packages("src"), 21 | package_dir={"": "src"}, 22 | url="https://github.com/acheong08/EdgeGPT", 23 | project_urls={"Bug Report": "https://github.com/acheong08/EdgeGPT/issues/new"}, 24 | entry_points={ 25 | "console_scripts": [ 26 | "edge-gpt = EdgeGPT.main:main", 27 | "edge-gpt-image = EdgeGPT.ImageGen:main", 28 | ], 29 | }, 30 | install_requires=[ 31 | "httpx[socks]>=0.24.0", 32 | "aiohttp", 33 | "websockets", 34 | "rich", 35 | "certifi", 36 | "prompt_toolkit", 37 | "requests", 38 | "BingImageCreator>=0.4.4", 39 | ], 40 | long_description=Path.open(PATH, encoding="utf-8").read(), 41 | long_description_content_type="text/markdown", 42 | py_modules=["EdgeGPT", "EdgeUtils", "ImageGen"], 43 | classifiers=[ 44 | "License :: OSI Approved :: The Unlicense (Unlicense)", 45 | "Intended Audience :: Developers", 46 | "Topic :: Software Development :: Libraries :: Python Modules", 47 | "Programming Language :: Python :: 3.8", 48 | "Programming Language :: Python :: 3.9", 49 | "Programming Language :: Python :: 3.10", 50 | "Programming Language :: Python :: 3.11", 51 | ], 52 | ) 53 | -------------------------------------------------------------------------------- /src/EdgeGPT/EdgeGPT.py: -------------------------------------------------------------------------------- 1 | """ 2 | Main.py 3 | """ 4 | from __future__ import annotations 5 | 6 | import json 7 | from pathlib import Path 8 | from typing import Generator 9 | 10 | from .chathub import * 11 | from .conversation import * 12 | from .conversation_style import * 13 | from .request import * 14 | from .utilities import * 15 | 16 | 17 | class Chatbot: 18 | """ 19 | Combines everything to make it seamless 20 | """ 21 | 22 | def __init__( 23 | self, 24 | proxy: str | None = None, 25 | cookies: list[dict] | None = None, 26 | ) -> None: 27 | self.proxy: str | None = proxy 28 | self.chat_hub: ChatHub = ChatHub( 29 | Conversation(self.proxy, cookies=cookies), 30 | proxy=self.proxy, 31 | cookies=cookies, 32 | ) 33 | 34 | @staticmethod 35 | async def create( 36 | proxy: str | None = None, 37 | cookies: list[dict] | None = None, 38 | ) -> Chatbot: 39 | self = Chatbot.__new__(Chatbot) 40 | self.proxy = proxy 41 | self.chat_hub = ChatHub( 42 | await Conversation.create(self.proxy, cookies=cookies), 43 | proxy=self.proxy, 44 | cookies=cookies, 45 | ) 46 | return self 47 | 48 | async def save_conversation(self, filename: str) -> None: 49 | """ 50 | Save the conversation to a file 51 | """ 52 | with open(filename, "w") as f: 53 | conversation_id = self.chat_hub.request.conversation_id 54 | conversation_signature = self.chat_hub.request.conversation_signature 55 | client_id = self.chat_hub.request.client_id 56 | invocation_id = self.chat_hub.request.invocation_id 57 | f.write( 58 | json.dumps( 59 | { 60 | "conversation_id": conversation_id, 61 | "conversation_signature": conversation_signature, 62 | "client_id": client_id, 63 | "invocation_id": invocation_id, 64 | }, 65 | ), 66 | ) 67 | 68 | async def load_conversation(self, filename: str) -> None: 69 | """ 70 | Load the conversation from a file 71 | """ 72 | with open(filename) as f: 73 | conversation = json.load(f) 74 | self.chat_hub.request = ChatHubRequest( 75 | conversation_signature=conversation["conversation_signature"], 76 | client_id=conversation["client_id"], 77 | conversation_id=conversation["conversation_id"], 78 | invocation_id=conversation["invocation_id"], 79 | ) 80 | 81 | async def get_conversation(self) -> dict: 82 | """ 83 | Gets the conversation history from conversation_id (requires load_conversation) 84 | """ 85 | return await self.chat_hub.get_conversation() 86 | 87 | async def get_activity(self) -> dict: 88 | """ 89 | Gets the recent activity (requires cookies) 90 | """ 91 | return await self.chat_hub.get_activity() 92 | 93 | async def ask( 94 | self, 95 | prompt: str, 96 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 97 | conversation_style: CONVERSATION_STYLE_TYPE = None, 98 | webpage_context: str | None = None, 99 | search_result: bool = False, 100 | locale: str = guess_locale(), 101 | simplify_response: bool = False, 102 | ) -> dict: 103 | """ 104 | Ask a question to the bot 105 | Response: 106 | { 107 | item (dict): 108 | messages (list[dict]): 109 | adaptiveCards (list[dict]): 110 | body (list[dict]): 111 | text (str): Response 112 | } 113 | To get the response, you can do: 114 | response["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"] 115 | """ 116 | async for final, response in self.chat_hub.ask_stream( 117 | prompt=prompt, 118 | conversation_style=conversation_style, 119 | wss_link=wss_link, 120 | webpage_context=webpage_context, 121 | search_result=search_result, 122 | locale=locale, 123 | ): 124 | if final: 125 | if not simplify_response: 126 | return response 127 | messages_left = response["item"]["throttling"][ 128 | "maxNumUserMessagesInConversation" 129 | ] - response["item"]["throttling"].get( 130 | "numUserMessagesInConversation", 131 | 0, 132 | ) 133 | if messages_left == 0: 134 | raise Exception("Max messages reached") 135 | message = "" 136 | for msg in reversed(response["item"]["messages"]): 137 | if msg.get("adaptiveCards") and msg["adaptiveCards"][0]["body"][ 138 | 0 139 | ].get("text"): 140 | message = msg 141 | break 142 | if not message: 143 | raise Exception("No message found") 144 | suggestions = [ 145 | suggestion["text"] 146 | for suggestion in message.get("suggestedResponses", []) 147 | ] 148 | adaptive_cards = message.get("adaptiveCards", []) 149 | adaptive_text = ( 150 | adaptive_cards[0]["body"][0].get("text") if adaptive_cards else None 151 | ) 152 | sources = ( 153 | adaptive_cards[0]["body"][0].get("text") if adaptive_cards else None 154 | ) 155 | sources_text = ( 156 | adaptive_cards[0]["body"][-1].get("text") 157 | if adaptive_cards 158 | else None 159 | ) 160 | return { 161 | "text": message["text"], 162 | "author": message["author"], 163 | "sources": sources, 164 | "sources_text": sources_text, 165 | "suggestions": suggestions, 166 | "messages_left": messages_left, 167 | "max_messages": response["item"]["throttling"][ 168 | "maxNumUserMessagesInConversation" 169 | ], 170 | "adaptive_text": adaptive_text, 171 | } 172 | return {} 173 | 174 | async def ask_stream( 175 | self, 176 | prompt: str, 177 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 178 | conversation_style: CONVERSATION_STYLE_TYPE = None, 179 | raw: bool = False, 180 | webpage_context: str | None = None, 181 | search_result: bool = False, 182 | locale: str = guess_locale(), 183 | ) -> Generator[bool, dict | str, None]: 184 | """ 185 | Ask a question to the bot 186 | """ 187 | async for response in self.chat_hub.ask_stream( 188 | prompt=prompt, 189 | conversation_style=conversation_style, 190 | wss_link=wss_link, 191 | raw=raw, 192 | webpage_context=webpage_context, 193 | search_result=search_result, 194 | locale=locale, 195 | ): 196 | yield response 197 | 198 | async def close(self) -> None: 199 | """ 200 | Close the connection 201 | """ 202 | await self.chat_hub.close() 203 | 204 | async def delete_conversation( 205 | self, 206 | conversation_id: str = None, 207 | conversation_signature: str = None, 208 | client_id: str = None, 209 | ) -> None: 210 | """ 211 | Delete the chat in the server 212 | """ 213 | await self.chat_hub.delete_conversation( 214 | conversation_id=conversation_id, 215 | conversation_signature=conversation_signature, 216 | client_id=client_id, 217 | ) 218 | 219 | async def reset(self, delete=False) -> None: 220 | """ 221 | Reset the conversation 222 | """ 223 | if delete: 224 | await self.remove_and_close() 225 | else: 226 | await self.close() 227 | self.chat_hub = ChatHub( 228 | await Conversation.create(self.proxy, cookies=self.chat_hub.cookies), 229 | proxy=self.proxy, 230 | cookies=self.chat_hub.cookies, 231 | ) 232 | 233 | 234 | if __name__ == "__main__": 235 | from .main import main 236 | 237 | main() 238 | -------------------------------------------------------------------------------- /src/EdgeGPT/EdgeUtils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import contextlib 3 | import json 4 | import re 5 | from pathlib import Path 6 | 7 | from log2d import Log 8 | 9 | from .EdgeGPT import Chatbot 10 | from .EdgeGPT import ConversationStyle 11 | from .ImageGen import ImageGen 12 | 13 | Log("BingChat") 14 | log = Log.BingChat.debug # shortcut to create a log entry 15 | 16 | 17 | class Cookie: 18 | """ 19 | Convenience class for Bing Cookie files, data, and configuration. This Class 20 | is updated dynamically by the Query class to allow cycling through >1 21 | cookie/credentials file e.g. when daily request limits (current 200 per 22 | account per day) are exceeded. 23 | """ 24 | 25 | current_file_index = 0 26 | dir_path = Path.home().resolve() / "bing_cookies" 27 | current_file_path = dir_path # Avoid Path errors when no cookie file used 28 | search_pattern = "bing_cookies_*.json" 29 | ignore_files = set() 30 | request_count = {} 31 | supplied_files = set() 32 | rotate_cookies = True 33 | 34 | @classmethod 35 | def files(cls: type) -> list[Path]: 36 | """ 37 | Return a sorted list of all cookie files matching .search_pattern in 38 | cls.dir_path, plus any supplied files, minus any ignored files. 39 | """ 40 | all_files = set(Path(cls.dir_path).glob(cls.search_pattern)) 41 | if hasattr(cls, "supplied_files"): 42 | supplied_files = {x for x in cls.supplied_files if x.is_file()} 43 | all_files.update(supplied_files) 44 | return sorted(all_files - cls.ignore_files) 45 | 46 | @classmethod 47 | def import_data(cls: type) -> None: 48 | """ 49 | Read the active cookie file and populate the following attributes: 50 | 51 | .current_file_path 52 | .current_data 53 | .image_token 54 | """ 55 | if not cls.files(): 56 | log("No files in Cookie.dir_path") 57 | return 58 | try: 59 | cls.current_file_path = cls.files()[cls.current_file_index] 60 | except IndexError: 61 | log(f"Invalid file index [{cls.current_file_index}]") 62 | log("Files in Cookie.dir_path:") 63 | for file in cls.files(): 64 | log(f"{file}") 65 | return 66 | log(f"Importing cookies from: {cls.current_file_path.name}") 67 | with open(cls.current_file_path, encoding="utf-8") as file: 68 | cls.current_data = json.load(file) 69 | cls.image_token = [ 70 | x for x in cls.current_data if x.get("name").startswith("_U") 71 | ] 72 | cls.image_token = cls.image_token[0].get("value") 73 | 74 | @classmethod 75 | def import_next(cls: type, discard: bool = False) -> None: 76 | """ 77 | Cycle through to the next cookies file then import it. 78 | 79 | discard (bool): True -Mark the previous file to be ignored for the remainder of the current session. Otherwise cycle through all available 80 | cookie files (sharing the workload and 'resting' when not in use). 81 | """ 82 | if not hasattr(cls, "current_file_path"): 83 | cls.import_data() 84 | return 85 | with contextlib.suppress(AttributeError): 86 | # Will fail on first instantiation because no current_file_path 87 | if discard: 88 | cls.ignore_files.add(cls.current_file_path) 89 | else: 90 | Cookie.current_file_index += 1 91 | if Cookie.current_file_index >= len(cls.files()): 92 | Cookie.current_file_index = 0 93 | Cookie.import_data() 94 | 95 | 96 | class Query: 97 | """ 98 | A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input, 99 | config, and output all together. Relies on Cookie class for authentication 100 | unless ignore_cookies=True 101 | """ 102 | 103 | index = [] 104 | image_dir_path = Path.cwd().resolve() / "bing_images" 105 | 106 | def __init__( 107 | self, 108 | prompt: str, 109 | style: ConversationStyle = "precise", 110 | content_type: str = "text", 111 | cookie_files: set[Path] = None, 112 | ignore_cookies: bool = False, 113 | echo: bool = True, 114 | echo_prompt: bool = False, 115 | locale: str = "en-GB", 116 | simplify_response: bool = True, 117 | ) -> None: 118 | """ 119 | Arguments: 120 | 121 | prompt: Text to enter into Bing Chat 122 | style: creative, balanced, or precise 123 | content_type: "text" for Bing Chat; "image" for Dall-e 124 | ignore_cookies (bool): Ignore cookie data altogether 125 | echo: Print something to confirm request made 126 | echo_prompt: Print confirmation of the evaluated prompt 127 | simplify_response: True -> single simplified prompt/response exchange 128 | cookie_files: iterable of Paths or strings of cookie files (json) 129 | 130 | Files in Cookie.dir_path will also be used if they exist. This defaults 131 | to the current working directory, so set Cookie.dir_path before 132 | creating a Query if your cookie files are elsewhere. 133 | """ 134 | self.__class__.index += [self] 135 | self.prompt = prompt 136 | self.locale = locale 137 | self.simplify_response = simplify_response 138 | self.ignore_cookies = ignore_cookies 139 | if not ignore_cookies: 140 | if cookie_files: 141 | # Convert singular argument to an iterable: 142 | if isinstance(cookie_files, (str, Path)): 143 | cookie_files = {cookie_files} 144 | # Check all elements exist and are Paths: 145 | cookie_files = { 146 | Path(x).resolve() 147 | for x in cookie_files 148 | if isinstance(x, (str, Path)) and x 149 | } 150 | Cookie.supplied_files = cookie_files 151 | files = Cookie.files() # includes .supplied_files 152 | if Cookie.rotate_cookies: 153 | Cookie.import_next() 154 | else: 155 | Cookie.import_data() 156 | if content_type == "text": 157 | self.style = style 158 | self.log_and_send_query(echo, echo_prompt) 159 | if content_type == "image": 160 | self.create_image() 161 | 162 | def log_and_send_query(self, echo: bool, echo_prompt: bool) -> None: 163 | self.response = asyncio.run(self.send_to_bing(echo, echo_prompt)) 164 | if not hasattr(Cookie, "current_data"): 165 | name = "" 166 | else: 167 | name = Cookie.current_file_path.name 168 | if not Cookie.request_count.get(name): 169 | Cookie.request_count[name] = 1 170 | else: 171 | Cookie.request_count[name] += 1 172 | 173 | def create_image(self) -> None: 174 | image_generator = ImageGen(Cookie.image_token) 175 | image_generator.save_images( 176 | image_generator.get_images(self.prompt), 177 | output_dir=self.__class__.image_dir_path, 178 | ) 179 | 180 | async def send_to_bing(self, echo: bool = True, echo_prompt: bool = False) -> str: 181 | """Creat, submit, then close a Chatbot instance. Return the response""" 182 | retries = len(Cookie.files()) or 1 183 | while retries: 184 | if not hasattr(Cookie, "current_data"): 185 | bot = await Chatbot.create() 186 | else: 187 | bot = await Chatbot.create(cookies=Cookie.current_data) 188 | if echo_prompt: 189 | log(f"{self.prompt=}") 190 | if echo: 191 | log("Waiting for response...") 192 | if self.style.lower() not in "creative balanced precise".split(): 193 | self.style = "precise" 194 | try: 195 | return await bot.ask( 196 | prompt=self.prompt, 197 | conversation_style=getattr(ConversationStyle, self.style), 198 | simplify_response=self.simplify_response, 199 | locale=self.locale, 200 | ) 201 | except Exception as ex: 202 | log( 203 | f"Exception: [{Cookie.current_file_path.name} may have exceeded the daily limit]\n{ex}", 204 | ) 205 | Cookie.import_next(discard=True) 206 | retries -= 1 207 | finally: 208 | await bot.close() 209 | return None 210 | 211 | @property 212 | def output(self) -> str: 213 | """The response from a completed Chatbot request""" 214 | if not self.simplify_response: 215 | return [ 216 | x.get("text") or x.get("hiddenText") 217 | for x in self.response["item"]["messages"] 218 | if x["author"] == "bot" 219 | ] 220 | try: 221 | return self.response["text"] 222 | except TypeError as te: 223 | raise TypeError( 224 | f"{te}\n(No response received - probably rate throttled...)", 225 | ) from te 226 | 227 | @property 228 | def sources(self) -> list[list[dict]]: 229 | """The source names and details parsed from a completed Chatbot request""" 230 | if self.simplify_response: 231 | return self.response["sources_text"] 232 | return [ 233 | x.get("sourceAttributions") or [] 234 | for x in self.response["item"]["messages"] 235 | if x["author"] == "bot" 236 | ] 237 | 238 | @property 239 | def sources_dict(self) -> dict[int, str]: 240 | """The source names and details as a dictionary""" 241 | if self.simplify_response: 242 | text = self.response["sources_text"] 243 | sources = enumerate(re.findall(r"\((http.*?)\)", text)) 244 | return {index + 1: value for index, value in sources} 245 | all_sources = [] 246 | name = "providerDisplayName" 247 | url = "seeMoreUrl" 248 | for sources in self.sources: 249 | if not sources: 250 | continue 251 | data = { 252 | index + 1: source[url] 253 | for index, source in enumerate(sources) 254 | if name in source and url in source 255 | } 256 | all_sources += [data] 257 | return all_sources 258 | 259 | @property 260 | def code_block_formats(self) -> list[str]: 261 | """ 262 | Extract a list of programming languages/formats used in code blocks 263 | """ 264 | regex = r"``` *(\b\w+\b\+*) *" 265 | if self.simplify_response: 266 | return re.findall(regex, self.output) 267 | return re.findall(regex, "\n".join(self.output)) 268 | 269 | @property 270 | def code_blocks(self) -> list[str]: 271 | """ 272 | Return a list of code blocks (```) or snippets (`) as strings. 273 | 274 | If the response contains a mix of snippets and code blocks, return the 275 | code blocks only. 276 | 277 | This method is not suitable if the main text response includes either of 278 | the delimiters but not as part of an actual snippet or code block. 279 | 280 | For example: 281 | 'In Markdown, the back-tick (`) is used to denote a code snippet' 282 | 283 | """ 284 | 285 | final_blocks = [] 286 | if isinstance(self.output, str): # I.e. simplify_response is True 287 | separator = "```" if "```" in self.output else "`" 288 | code_blocks = self.output.split(separator)[1:-1:2] 289 | if separator == "`": 290 | return code_blocks 291 | else: 292 | code_blocks = [] 293 | for response in self.output: 294 | separator = "```" if "```" in response else "`" 295 | code_blocks.extend(response.split(separator)[1:-1:2]) 296 | code_blocks = [x for x in code_blocks if x] 297 | # Remove language name if present: 298 | for block in code_blocks: 299 | lines = block.splitlines() 300 | code = lines[1:] if re.match(" *\\w+ *", lines[0]) else lines 301 | final_blocks += ["\n".join(code).removeprefix(separator)] 302 | return [x for x in final_blocks if x] 303 | 304 | @property 305 | def code(self) -> str: 306 | """ 307 | Extract and join any snippets of code or formatted data in the response 308 | """ 309 | return "\n\n".join(self.code_blocks) 310 | 311 | @property 312 | def suggestions(self) -> list[str]: 313 | """Follow-on questions suggested by the Chatbot""" 314 | if self.simplify_response: 315 | return self.response["suggestions"] 316 | try: 317 | return [ 318 | x["text"] 319 | for x in self.response["item"]["messages"][1]["suggestedResponses"] 320 | ] 321 | except KeyError: 322 | return None 323 | 324 | def __repr__(self) -> str: 325 | return f"" 326 | 327 | def __str__(self) -> str: 328 | return self.output if self.simplify_response else "\n\n".join(self.output) 329 | 330 | 331 | class ImageQuery(Query): 332 | def __init__(self, prompt: str, **kwargs) -> None: 333 | kwargs["content_type"] = "image" 334 | super().__init__(prompt, **kwargs) 335 | 336 | def __repr__(self) -> str: 337 | return f"" 338 | 339 | 340 | def test_cookie_rotation() -> None: 341 | for i in range(1, 50): 342 | q = Query( 343 | f"What is {i} in Roman numerals? Give the answer in JSON", 344 | style="precise", 345 | ) 346 | log(f"{i}: {Cookie.current_file_path.name}") 347 | log(q.code) 348 | log(f"Cookie count: {Cookie.request_count.get(Cookie.current_file_path.name)}") 349 | 350 | 351 | def test_features() -> Query: 352 | try: 353 | q = Query( 354 | f"What is {i} in Roman numerals? Give the answer in JSON", 355 | style="precise", 356 | ) 357 | log(f"{i}: {Cookie.current_file_path.name}") 358 | print(f"{Cookie.current_file_index=}") 359 | print(f"{Cookie.current_file_path=}") 360 | print(f"{Cookie.current_data=}") 361 | print(f"{Cookie.dir_path=}") 362 | print(f"{Cookie.search_pattern=}") 363 | print(f"{Cookie.files()=}") 364 | print(f"{Cookie.image_token=}") 365 | print(f"{Cookie.import_next(discard=True)=}") 366 | print(f"{Cookie.rotate_cookies=}") 367 | print(f"{Cookie.files()=}") 368 | print(f"{Cookie.ignore_files=}") 369 | print(f"{Cookie.supplied_files=}") 370 | print( 371 | f"{Cookie.request_count=}" 372 | ) # Keeps a tally of requests made in using each cookie file during this session 373 | print(f"{q=}") 374 | print(f"{q.prompt=}") 375 | print(f"{q.ignore_cookies=}") 376 | print(f"{q.style=}") 377 | print(f"{q.simplify_response=}") 378 | print(f"{q.locale=}") 379 | print(f"{q.output=}") 380 | print(q) 381 | print(f"{q.sources=}") 382 | print(f"{q.sources_dict=}") 383 | print(f"{q.suggestions=}") 384 | print(f"{q.code=}") # All code as a single string 385 | print(f"{q.code_blocks=}") # Individual code blocks 386 | print( 387 | f"{q.code_block_formats=}" 388 | ) # The language/format of each code block (if given) 389 | print(f"{Query.index=}") # Keeps an index of Query objects created 390 | print(f"{Query.image_dir_path=}") 391 | except Exception as E: 392 | raise Exception(E) from E 393 | finally: 394 | return q 395 | -------------------------------------------------------------------------------- /src/EdgeGPT/ImageGen.py: -------------------------------------------------------------------------------- 1 | # Open pull requests and issues at https://github.com/acheong08/BingImageCreator 2 | import BingImageCreator 3 | 4 | ImageGen = BingImageCreator.ImageGen 5 | 6 | ImageGenAsync = BingImageCreator.ImageGenAsync 7 | 8 | main = BingImageCreator.main 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /src/EdgeGPT/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acheong08/EdgeGPT/9aed44b641f8fbd23bfdd46f0276efc7f516c1cd/src/EdgeGPT/__init__.py -------------------------------------------------------------------------------- /src/EdgeGPT/chathub.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | import ssl 5 | import sys 6 | from time import time 7 | from typing import Generator 8 | from typing import List 9 | from typing import Union 10 | 11 | import aiohttp 12 | import certifi 13 | import httpx 14 | from BingImageCreator import ImageGenAsync 15 | 16 | from .constants import DELIMITER 17 | from .constants import HEADERS 18 | from .constants import HEADERS_INIT_CONVER 19 | from .conversation import Conversation 20 | from .conversation_style import CONVERSATION_STYLE_TYPE 21 | from .request import ChatHubRequest 22 | from .utilities import append_identifier 23 | from .utilities import get_ran_hex 24 | from .utilities import guess_locale 25 | 26 | ssl_context = ssl.create_default_context() 27 | ssl_context.load_verify_locations(certifi.where()) 28 | 29 | 30 | class ChatHub: 31 | def __init__( 32 | self, 33 | conversation: Conversation, 34 | proxy: str = None, 35 | cookies: Union[List[dict], None] = None, 36 | ) -> None: 37 | self.aio_session = None 38 | self.request: ChatHubRequest 39 | self.loop: bool 40 | self.task: asyncio.Task 41 | self.request = ChatHubRequest( 42 | conversation_signature=conversation.struct["conversationSignature"], 43 | client_id=conversation.struct["clientId"], 44 | conversation_id=conversation.struct["conversationId"], 45 | ) 46 | self.cookies = cookies 47 | self.proxy: str = proxy 48 | proxy = ( 49 | proxy 50 | or os.environ.get("all_proxy") 51 | or os.environ.get("ALL_PROXY") 52 | or os.environ.get("https_proxy") 53 | or os.environ.get("HTTPS_PROXY") 54 | or None 55 | ) 56 | if proxy is not None and proxy.startswith("socks5h://"): 57 | proxy = "socks5://" + proxy[len("socks5h://") :] 58 | self.session = httpx.AsyncClient( 59 | proxies=proxy, 60 | timeout=900, 61 | headers=HEADERS_INIT_CONVER, 62 | ) 63 | 64 | async def get_conversation( 65 | self, 66 | conversation_id: str = None, 67 | conversation_signature: str = None, 68 | client_id: str = None, 69 | ) -> dict: 70 | conversation_id = conversation_id or self.request.conversation_id 71 | conversation_signature = ( 72 | conversation_signature or self.request.conversation_signature 73 | ) 74 | client_id = client_id or self.request.client_id 75 | url = f"https://sydney.bing.com/sydney/GetConversation?conversationId={conversation_id}&source=cib&participantId={client_id}&conversationSignature={conversation_signature}&traceId={get_ran_hex()}" 76 | response = await self.session.get(url) 77 | return response.json() 78 | 79 | async def get_activity(self) -> dict: 80 | url = "https://www.bing.com/turing/conversation/chats" 81 | headers = HEADERS_INIT_CONVER.copy() 82 | if self.cookies is not None: 83 | for cookie in self.cookies: 84 | if cookie["name"] == "_U": 85 | headers["Cookie"] = f"SUID=A; _U={cookie['value']};" 86 | break 87 | response = await self.session.get(url, headers=headers) 88 | return response.json() 89 | 90 | async def ask_stream( 91 | self, 92 | prompt: str, 93 | wss_link: str = None, 94 | conversation_style: CONVERSATION_STYLE_TYPE = None, 95 | raw: bool = False, 96 | webpage_context: Union[str, None] = None, 97 | search_result: bool = False, 98 | locale: str = guess_locale(), 99 | ) -> Generator[bool, Union[dict, str], None]: 100 | """ """ 101 | cookies = {} 102 | if self.cookies is not None: 103 | for cookie in self.cookies: 104 | cookies[cookie["name"]] = cookie["value"] 105 | self.aio_session = aiohttp.ClientSession(cookies=cookies) 106 | # Check if websocket is closed 107 | wss = await self.aio_session.ws_connect( 108 | wss_link or "wss://sydney.bing.com/sydney/ChatHub", 109 | ssl=ssl_context, 110 | headers=HEADERS, 111 | proxy=self.proxy, 112 | ) 113 | await self._initial_handshake(wss) 114 | # Construct a ChatHub request 115 | self.request.update( 116 | prompt=prompt, 117 | conversation_style=conversation_style, 118 | webpage_context=webpage_context, 119 | search_result=search_result, 120 | locale=locale, 121 | ) 122 | # Send request 123 | await wss.send_str(append_identifier(self.request.struct)) 124 | draw = False 125 | resp_txt = "" 126 | result_text = "" 127 | resp_txt_no_link = "" 128 | retry_count = 5 129 | while not wss.closed: 130 | msg = await wss.receive_str() 131 | if not msg: 132 | retry_count -= 1 133 | if retry_count == 0: 134 | raise Exception("No response from server") 135 | continue 136 | if isinstance(msg, str): 137 | objects = msg.split(DELIMITER) 138 | else: 139 | continue 140 | for obj in objects: 141 | if int(time()) % 6 == 0: 142 | await wss.send_str(append_identifier({"type": 6})) 143 | if obj is None or not obj: 144 | continue 145 | response = json.loads(obj) 146 | # print(response) 147 | if response.get("type") == 1 and response["arguments"][0].get( 148 | "messages", 149 | ): 150 | if not draw: 151 | if ( 152 | response["arguments"][0]["messages"][0].get( 153 | "messageType", 154 | ) 155 | == "GenerateContentQuery" 156 | ): 157 | try: 158 | async with ImageGenAsync( 159 | all_cookies=self.cookies, 160 | ) as image_generator: 161 | images = await image_generator.get_images( 162 | response["arguments"][0]["messages"][0]["text"], 163 | ) 164 | for i, image in enumerate(images): 165 | resp_txt = f"{resp_txt}\n![image{i}]({image})" 166 | draw = True 167 | except Exception as e: 168 | print(e) 169 | continue 170 | if ( 171 | ( 172 | response["arguments"][0]["messages"][0]["contentOrigin"] 173 | != "Apology" 174 | ) 175 | and not draw 176 | and not raw 177 | ): 178 | resp_txt = result_text + response["arguments"][0][ 179 | "messages" 180 | ][0]["adaptiveCards"][0]["body"][0].get("text", "") 181 | resp_txt_no_link = result_text + response["arguments"][0][ 182 | "messages" 183 | ][0].get("text", "") 184 | if response["arguments"][0]["messages"][0].get( 185 | "messageType", 186 | ): 187 | resp_txt = ( 188 | resp_txt 189 | + response["arguments"][0]["messages"][0][ 190 | "adaptiveCards" 191 | ][0]["body"][0]["inlines"][0].get("text") 192 | + "\n" 193 | ) 194 | result_text = ( 195 | result_text 196 | + response["arguments"][0]["messages"][0][ 197 | "adaptiveCards" 198 | ][0]["body"][0]["inlines"][0].get("text") 199 | + "\n" 200 | ) 201 | if not raw: 202 | yield False, resp_txt 203 | 204 | elif response.get("type") == 2: 205 | if response["item"]["result"].get("error"): 206 | await self.close() 207 | raise Exception( 208 | f"{response['item']['result']['value']}: {response['item']['result']['message']}", 209 | ) 210 | if draw: 211 | cache = response["item"]["messages"][1]["adaptiveCards"][0][ 212 | "body" 213 | ][0]["text"] 214 | response["item"]["messages"][1]["adaptiveCards"][0]["body"][0][ 215 | "text" 216 | ] = (cache + resp_txt) 217 | if ( 218 | response["item"]["messages"][-1]["contentOrigin"] == "Apology" 219 | and resp_txt 220 | ): 221 | response["item"]["messages"][-1]["text"] = resp_txt_no_link 222 | response["item"]["messages"][-1]["adaptiveCards"][0]["body"][0][ 223 | "text" 224 | ] = resp_txt 225 | print( 226 | "Preserved the message from being deleted", 227 | file=sys.stderr, 228 | ) 229 | await wss.close() 230 | if not self.aio_session.closed: 231 | await self.aio_session.close() 232 | yield True, response 233 | return 234 | if response.get("type") != 2: 235 | if response.get("type") == 6: 236 | await wss.send_str(append_identifier({"type": 6})) 237 | elif response.get("type") == 7: 238 | await wss.send_str(append_identifier({"type": 7})) 239 | elif raw: 240 | yield False, response 241 | 242 | async def _initial_handshake(self, wss) -> None: 243 | await wss.send_str(append_identifier({"protocol": "json", "version": 1})) 244 | await wss.receive_str() 245 | await wss.send_str(append_identifier({"type": 6})) 246 | 247 | async def delete_conversation( 248 | self, 249 | conversation_id: str = None, 250 | conversation_signature: str = None, 251 | client_id: str = None, 252 | ) -> None: 253 | conversation_id = conversation_id or self.request.conversation_id 254 | conversation_signature = ( 255 | conversation_signature or self.request.conversation_signature 256 | ) 257 | client_id = client_id or self.request.client_id 258 | url = "https://sydney.bing.com/sydney/DeleteSingleConversation" 259 | await self.session.post( 260 | url, 261 | json={ 262 | "conversationId": conversation_id, 263 | "conversationSignature": conversation_signature, 264 | "participant": {"id": client_id}, 265 | "source": "cib", 266 | "optionsSets": ["autosave"], 267 | }, 268 | ) 269 | 270 | async def close(self) -> None: 271 | await self.session.aclose() 272 | -------------------------------------------------------------------------------- /src/EdgeGPT/constants.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import uuid 3 | 4 | take_ip_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 5 | take_ip_socket.connect(("8.8.8.8", 80)) 6 | FORWARDED_IP: str = take_ip_socket.getsockname()[0] 7 | take_ip_socket.close() 8 | 9 | DELIMITER = "\x1e" 10 | 11 | HEADERS = { 12 | "accept": "application/json", 13 | "accept-language": "en-US;q=0.9", 14 | "accept-encoding": "gzip, deflate, br, zsdch", 15 | "content-type": "application/json", 16 | "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"', 17 | "sec-ch-ua-arch": '"x86"', 18 | "sec-ch-ua-bitness": '"64"', 19 | "sec-ch-ua-full-version": '"115.0.1901.188"', 20 | "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"', 21 | "sec-ch-ua-mobile": "?0", 22 | "sec-ch-ua-model": "", 23 | "sec-ch-ua-platform": '"Windows"', 24 | "sec-ch-ua-platform-version": '"15.0.0"', 25 | "sec-fetch-dest": "empty", 26 | "sec-fetch-mode": "cors", 27 | "sec-fetch-site": "same-origin", 28 | "sec-ms-gec-version": "1-115.0.1901.188", 29 | "x-ms-client-request-id": str(uuid.uuid4()), 30 | "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/Windows", 31 | "Referer": "https://www.bing.com/search?", 32 | "Referrer-Policy": "origin-when-cross-origin", 33 | "x-forwarded-for": FORWARDED_IP, 34 | } 35 | 36 | HEADERS_INIT_CONVER = { 37 | "authority": "www.bing.com", 38 | "accept": "application/json", 39 | "accept-language": "en-US;q=0.9", 40 | "cache-control": "max-age=0", 41 | "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"', 42 | "sec-ch-ua-arch": '"x86"', 43 | "sec-ch-ua-bitness": '"64"', 44 | "sec-ch-ua-full-version": '"115.0.1901.188"', 45 | "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"', 46 | "sec-ch-ua-mobile": "?0", 47 | "sec-ch-ua-model": '""', 48 | "sec-ch-ua-platform": '"Windows"', 49 | "sec-ch-ua-platform-version": '"15.0.0"', 50 | "upgrade-insecure-requests": "1", 51 | "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", 52 | "x-edge-shopping-flag": "1", 53 | "x-forwarded-for": FORWARDED_IP, 54 | } 55 | -------------------------------------------------------------------------------- /src/EdgeGPT/conversation.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import List 4 | from typing import Union 5 | 6 | import httpx 7 | 8 | from .constants import HEADERS_INIT_CONVER 9 | from .exceptions import NotAllowedToAccess 10 | 11 | 12 | class Conversation: 13 | def __init__( 14 | self, 15 | proxy: Union[str, None] = None, 16 | async_mode: bool = False, 17 | cookies: Union[List[dict], None] = None, 18 | ) -> None: 19 | if async_mode: 20 | return 21 | self.struct: dict = { 22 | "conversationId": None, 23 | "clientId": None, 24 | "conversationSignature": None, 25 | "result": {"value": "Success", "message": None}, 26 | } 27 | self.proxy = proxy 28 | proxy = ( 29 | proxy 30 | or os.environ.get("all_proxy") 31 | or os.environ.get("ALL_PROXY") 32 | or os.environ.get("https_proxy") 33 | or os.environ.get("HTTPS_PROXY") 34 | or None 35 | ) 36 | if proxy is not None and proxy.startswith("socks5h://"): 37 | proxy = "socks5://" + proxy[len("socks5h://") :] 38 | self.session = httpx.Client( 39 | proxies=proxy, 40 | timeout=900, 41 | headers=HEADERS_INIT_CONVER, 42 | ) 43 | if cookies: 44 | for cookie in cookies: 45 | self.session.cookies.set(cookie["name"], cookie["value"]) 46 | # Send GET request 47 | response = self.session.get( 48 | url=os.environ.get("BING_PROXY_URL") 49 | or "https://edgeservices.bing.com/edgesvc/turing/conversation/create", 50 | ) 51 | if response.status_code != 200: 52 | print(f"Status code: {response.status_code}") 53 | print(response.text) 54 | print(response.url) 55 | raise Exception("Authentication failed") 56 | try: 57 | self.struct = response.json() 58 | except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc: 59 | raise Exception( 60 | "Authentication failed. You have not been accepted into the beta.", 61 | ) from exc 62 | if self.struct["result"]["value"] == "UnauthorizedRequest": 63 | raise NotAllowedToAccess(self.struct["result"]["message"]) 64 | 65 | @staticmethod 66 | async def create( 67 | proxy: Union[str, None] = None, 68 | cookies: Union[List[dict], None] = None, 69 | ) -> "Conversation": 70 | self = Conversation(async_mode=True) 71 | self.struct = { 72 | "conversationId": None, 73 | "clientId": None, 74 | "conversationSignature": None, 75 | "result": {"value": "Success", "message": None}, 76 | } 77 | self.proxy = proxy 78 | proxy = ( 79 | proxy 80 | or os.environ.get("all_proxy") 81 | or os.environ.get("ALL_PROXY") 82 | or os.environ.get("https_proxy") 83 | or os.environ.get("HTTPS_PROXY") 84 | or None 85 | ) 86 | if proxy is not None and proxy.startswith("socks5h://"): 87 | proxy = "socks5://" + proxy[len("socks5h://") :] 88 | transport = httpx.AsyncHTTPTransport(retries=900) 89 | # Convert cookie format to httpx format 90 | formatted_cookies = None 91 | if cookies: 92 | formatted_cookies = httpx.Cookies() 93 | for cookie in cookies: 94 | formatted_cookies.set(cookie["name"], cookie["value"]) 95 | async with httpx.AsyncClient( 96 | proxies=proxy, 97 | timeout=30, 98 | headers=HEADERS_INIT_CONVER, 99 | transport=transport, 100 | cookies=formatted_cookies, 101 | ) as client: 102 | # Send GET request 103 | response = await client.get( 104 | url=os.environ.get("BING_PROXY_URL") 105 | or "https://www.bing.com/turing/conversation/create", 106 | follow_redirects=True, 107 | ) 108 | if response.status_code != 200: 109 | print(f"Status code: {response.status_code}") 110 | print(response.text) 111 | print(response.url) 112 | raise Exception("Authentication failed") 113 | try: 114 | self.struct = response.json() 115 | except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc: 116 | print(response.text) 117 | raise Exception( 118 | "Authentication failed. You have not been accepted into the beta.", 119 | ) from exc 120 | if self.struct["result"]["value"] == "UnauthorizedRequest": 121 | raise NotAllowedToAccess(self.struct["result"]["message"]) 122 | return self 123 | -------------------------------------------------------------------------------- /src/EdgeGPT/conversation_style.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | try: 4 | from typing import Literal, Union 5 | except ImportError: 6 | from typing_extensions import Literal 7 | from typing import Optional 8 | 9 | 10 | class ConversationStyle(Enum): 11 | creative = [ 12 | "nlu_direct_response_filter", 13 | "deepleo", 14 | "disable_emoji_spoken_text", 15 | "responsible_ai_policy_235", 16 | "enablemm", 17 | "h3imaginative", 18 | "objopinion", 19 | "dsblhlthcrd", 20 | "dv3sugg", 21 | "autosave", 22 | "clgalileo", 23 | "gencontentv3", 24 | ] 25 | balanced = [ 26 | "nlu_direct_response_filter", 27 | "deepleo", 28 | "disable_emoji_spoken_text", 29 | "responsible_ai_policy_235", 30 | "enablemm", 31 | "galileo", 32 | "saharagenconv5", 33 | "objopinion", 34 | "dsblhlthcrd", 35 | "dv3sugg", 36 | "autosave", 37 | ] 38 | precise = [ 39 | "nlu_direct_response_filter", 40 | "deepleo", 41 | "disable_emoji_spoken_text", 42 | "responsible_ai_policy_235", 43 | "enablemm", 44 | "h3precise", 45 | "objopinion", 46 | "dsblhlthcrd", 47 | "dv3sugg", 48 | "autosave", 49 | "clgalileo", 50 | "gencontentv3", 51 | ] 52 | 53 | 54 | CONVERSATION_STYLE_TYPE = Optional[ 55 | Union[ConversationStyle, Literal["creative", "balanced", "precise"]] 56 | ] 57 | -------------------------------------------------------------------------------- /src/EdgeGPT/exceptions.py: -------------------------------------------------------------------------------- 1 | class NotAllowedToAccess(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /src/EdgeGPT/locale.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | try: 4 | from typing import Literal, Union 5 | except ImportError: 6 | from typing_extensions import Literal 7 | from typing import Optional 8 | 9 | 10 | class LocationHint(Enum): 11 | USA = { 12 | "locale": "en-US", 13 | "LocationHint": [ 14 | { 15 | "country": "United States", 16 | "state": "California", 17 | "city": "Los Angeles", 18 | "timezoneoffset": 8, 19 | "countryConfidence": 8, 20 | "Center": { 21 | "Latitude": 34.0536909, 22 | "Longitude": -118.242766, 23 | }, 24 | "RegionType": 2, 25 | "SourceType": 1, 26 | }, 27 | ], 28 | } 29 | CHINA = { 30 | "locale": "zh-CN", 31 | "LocationHint": [ 32 | { 33 | "country": "China", 34 | "state": "", 35 | "city": "Beijing", 36 | "timezoneoffset": 8, 37 | "countryConfidence": 8, 38 | "Center": { 39 | "Latitude": 39.9042, 40 | "Longitude": 116.4074, 41 | }, 42 | "RegionType": 2, 43 | "SourceType": 1, 44 | }, 45 | ], 46 | } 47 | EU = { 48 | "locale": "en-IE", 49 | "LocationHint": [ 50 | { 51 | "country": "Norway", 52 | "state": "", 53 | "city": "Oslo", 54 | "timezoneoffset": 1, 55 | "countryConfidence": 8, 56 | "Center": { 57 | "Latitude": 59.9139, 58 | "Longitude": 10.7522, 59 | }, 60 | "RegionType": 2, 61 | "SourceType": 1, 62 | }, 63 | ], 64 | } 65 | UK = { 66 | "locale": "en-GB", 67 | "LocationHint": [ 68 | { 69 | "country": "United Kingdom", 70 | "state": "", 71 | "city": "London", 72 | "timezoneoffset": 0, 73 | "countryConfidence": 8, 74 | "Center": { 75 | "Latitude": 51.5074, 76 | "Longitude": -0.1278, 77 | }, 78 | "RegionType": 2, 79 | "SourceType": 1, 80 | }, 81 | ], 82 | } 83 | 84 | 85 | LOCATION_HINT_TYPES = Optional[Union[LocationHint, Literal["USA", "CHINA", "EU", "UK"]]] 86 | -------------------------------------------------------------------------------- /src/EdgeGPT/main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import json 4 | import re 5 | import sys 6 | from pathlib import Path 7 | 8 | from EdgeGPT.EdgeGPT import Chatbot 9 | from prompt_toolkit import PromptSession 10 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory 11 | from prompt_toolkit.completion import WordCompleter 12 | from prompt_toolkit.history import InMemoryHistory 13 | from prompt_toolkit.key_binding import KeyBindings 14 | from rich.live import Live 15 | from rich.markdown import Markdown 16 | 17 | 18 | def create_session() -> PromptSession: 19 | kb = KeyBindings() 20 | 21 | @kb.add("enter") 22 | def _(event) -> None: 23 | buffer_text = event.current_buffer.text 24 | if buffer_text.startswith("!"): 25 | event.current_buffer.validate_and_handle() 26 | else: 27 | event.current_buffer.insert_text("\n") 28 | 29 | @kb.add("escape") 30 | def _(event) -> None: 31 | if event.current_buffer.complete_state: 32 | # event.current_buffer.cancel_completion() 33 | event.current_buffer.text = "" 34 | 35 | return PromptSession(key_bindings=kb, history=InMemoryHistory()) 36 | 37 | 38 | def create_completer(commands: list, pattern_str: str = "$") -> WordCompleter: 39 | return WordCompleter(words=commands, pattern=re.compile(pattern_str)) 40 | 41 | 42 | def _create_history_logger(f) -> callable: 43 | def logger(*args, **kwargs) -> None: 44 | tmp = sys.stdout 45 | sys.stdout = f 46 | print(*args, **kwargs, flush=True) 47 | sys.stdout = tmp 48 | 49 | return logger 50 | 51 | 52 | async def get_input_async( 53 | session: PromptSession = None, 54 | completer: WordCompleter = None, 55 | ) -> str: 56 | """ 57 | Multiline input function. 58 | """ 59 | return await session.prompt_async( 60 | completer=completer, 61 | multiline=True, 62 | auto_suggest=AutoSuggestFromHistory(), 63 | ) 64 | 65 | 66 | async def async_main(args: argparse.Namespace) -> None: 67 | """ 68 | Main function 69 | """ 70 | print("Initializing...") 71 | print("Enter `alt+enter` or `escape+enter` to send a message") 72 | # Read and parse cookies 73 | cookies = None 74 | if args.cookie_file: 75 | file_path = Path(args.cookie_file) 76 | if file_path.exists(): 77 | with file_path.open("r", encoding="utf-8") as f: 78 | cookies = json.load(f) 79 | bot = await Chatbot.create(proxy=args.proxy, cookies=cookies) 80 | session = create_session() 81 | completer = create_completer(["!help", "!exit", "!reset"]) 82 | initial_prompt = args.prompt 83 | 84 | # Log chat history 85 | def p_hist(*args, **kwargs) -> None: 86 | pass 87 | 88 | if args.history_file: 89 | history_file_path = Path(args.history_file) 90 | f = history_file_path.open("a+", encoding="utf-8") 91 | p_hist = _create_history_logger(f) 92 | 93 | while True: 94 | print("\nYou:") 95 | p_hist("\nYou:") 96 | if initial_prompt: 97 | question = initial_prompt 98 | print(question) 99 | initial_prompt = None 100 | else: 101 | question = ( 102 | input() 103 | if args.enter_once 104 | else await get_input_async(session=session, completer=completer) 105 | ) 106 | print() 107 | p_hist(question + "\n") 108 | if question == "!exit": 109 | await bot.close() 110 | break 111 | if question == "!help": 112 | print( 113 | """ 114 | !help - Show this help message 115 | !exit - Exit the program 116 | !reset - Reset the conversation 117 | """, 118 | ) 119 | continue 120 | if question == "!reset": 121 | await bot.reset() 122 | continue 123 | print("Bot:") 124 | p_hist("Bot:") 125 | if args.no_stream: 126 | response = ( 127 | await bot.ask( 128 | prompt=question, 129 | conversation_style=args.style, 130 | wss_link=args.wss_link, 131 | search_result=args.search_result, 132 | locale=args.locale, 133 | ) 134 | )["item"]["messages"][-1]["adaptiveCards"][0]["body"][0]["text"] 135 | print(response) 136 | p_hist(response) 137 | else: 138 | wrote = 0 139 | if args.rich: 140 | md = Markdown("") 141 | with Live(md, auto_refresh=False) as live: 142 | async for final, response in bot.ask_stream( 143 | prompt=question, 144 | conversation_style=args.style, 145 | wss_link=args.wss_link, 146 | search_result=args.search_result, 147 | locale=args.locale, 148 | ): 149 | if not final: 150 | if not wrote: 151 | p_hist(response, end="") 152 | else: 153 | p_hist(response[wrote:], end="") 154 | if wrote > len(response): 155 | print(md) 156 | print(Markdown("***Bing revoked the response.***")) 157 | wrote = len(response) 158 | md = Markdown(response) 159 | live.update(md, refresh=True) 160 | else: 161 | async for final, response in bot.ask_stream( 162 | prompt=question, 163 | conversation_style=args.style, 164 | wss_link=args.wss_link, 165 | search_result=args.search_result, 166 | locale=args.locale, 167 | ): 168 | if not final: 169 | if not wrote: 170 | print(response, end="", flush=True) 171 | p_hist(response, end="") 172 | else: 173 | print(response[wrote:], end="", flush=True) 174 | p_hist(response[wrote:], end="") 175 | wrote = len(response) 176 | print() 177 | p_hist() 178 | if args.history_file: 179 | f.close() 180 | await bot.close() 181 | 182 | 183 | def main() -> None: 184 | print( 185 | """ 186 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 187 | Repo: github.com/acheong08/EdgeGPT 188 | By: Antonio Cheong 189 | 190 | !help for help 191 | 192 | Type !exit to exit 193 | """, 194 | ) 195 | parser = argparse.ArgumentParser() 196 | parser.add_argument("--enter-once", action="store_true") 197 | parser.add_argument("--search-result", action="store_true") 198 | parser.add_argument("--no-stream", action="store_true") 199 | parser.add_argument("--rich", action="store_true") 200 | parser.add_argument( 201 | "--proxy", 202 | help="Proxy URL (e.g. socks5://127.0.0.1:1080)", 203 | type=str, 204 | ) 205 | parser.add_argument( 206 | "--wss-link", 207 | help="WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)", 208 | type=str, 209 | default="wss://sydney.bing.com/sydney/ChatHub", 210 | ) 211 | parser.add_argument( 212 | "--style", 213 | choices=["creative", "balanced", "precise"], 214 | default="balanced", 215 | ) 216 | parser.add_argument( 217 | "--prompt", 218 | type=str, 219 | default="", 220 | required=False, 221 | help="prompt to start with", 222 | ) 223 | parser.add_argument( 224 | "--cookie-file", 225 | type=str, 226 | default="", 227 | required=False, 228 | help="path to cookie file", 229 | ) 230 | parser.add_argument( 231 | "--history-file", 232 | type=str, 233 | default="", 234 | required=False, 235 | help="path to history file", 236 | ) 237 | parser.add_argument( 238 | "--locale", 239 | type=str, 240 | default="en-US", 241 | required=False, 242 | help="your locale", 243 | ) 244 | args = parser.parse_args() 245 | asyncio.run(async_main(args)) 246 | -------------------------------------------------------------------------------- /src/EdgeGPT/request.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | from datetime import datetime 3 | from typing import Union 4 | 5 | from .conversation_style import CONVERSATION_STYLE_TYPE 6 | from .conversation_style import ConversationStyle 7 | from .utilities import get_location_hint_from_locale 8 | from .utilities import get_ran_hex 9 | from .utilities import guess_locale 10 | 11 | 12 | class ChatHubRequest: 13 | def __init__( 14 | self, 15 | conversation_signature: str, 16 | client_id: str, 17 | conversation_id: str, 18 | invocation_id: int = 3, 19 | ) -> None: 20 | self.struct: dict = {} 21 | 22 | self.client_id: str = client_id 23 | self.conversation_id: str = conversation_id 24 | self.conversation_signature: str = conversation_signature 25 | self.invocation_id: int = invocation_id 26 | 27 | def update( 28 | self, 29 | prompt: str, 30 | conversation_style: CONVERSATION_STYLE_TYPE, 31 | webpage_context: Union[str, None] = None, 32 | search_result: bool = False, 33 | locale: str = guess_locale(), 34 | ) -> None: 35 | options = [ 36 | "deepleo", 37 | "enable_debug_commands", 38 | "disable_emoji_spoken_text", 39 | "enablemm", 40 | ] 41 | if conversation_style: 42 | if not isinstance(conversation_style, ConversationStyle): 43 | conversation_style = getattr(ConversationStyle, conversation_style) 44 | options = conversation_style.value 45 | message_id = str(uuid.uuid4()) 46 | # Get the current local time 47 | now_local = datetime.now() 48 | 49 | # Get the current UTC time 50 | now_utc = datetime.utcnow() 51 | 52 | # Calculate the time difference between local and UTC time 53 | timezone_offset = now_local - now_utc 54 | 55 | # Get the offset in hours and minutes 56 | offset_hours = int(timezone_offset.total_seconds() // 3600) 57 | offset_minutes = int((timezone_offset.total_seconds() % 3600) // 60) 58 | 59 | # Format the offset as a string 60 | offset_string = f"{offset_hours:+03d}:{offset_minutes:02d}" 61 | 62 | # Get current time 63 | timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + offset_string 64 | self.struct = { 65 | "arguments": [ 66 | { 67 | "source": "cib", 68 | "optionsSets": options, 69 | "allowedMessageTypes": [ 70 | "ActionRequest", 71 | "Chat", 72 | "Context", 73 | "InternalSearchQuery", 74 | "InternalSearchResult", 75 | "Disengaged", 76 | "InternalLoaderMessage", 77 | "Progress", 78 | "RenderCardRequest", 79 | "AdsQuery", 80 | "SemanticSerp", 81 | "GenerateContentQuery", 82 | "SearchQuery", 83 | ], 84 | "sliceIds": [ 85 | "winmuid1tf", 86 | "styleoff", 87 | "ccadesk", 88 | "smsrpsuppv4cf", 89 | "ssrrcache", 90 | "contansperf", 91 | "crchatrev", 92 | "winstmsg2tf", 93 | "creatgoglt", 94 | "creatorv2t", 95 | "sydconfigoptt", 96 | "adssqovroff", 97 | "530pstho", 98 | "517opinion", 99 | "418dhlth", 100 | "512sprtic1s0", 101 | "emsgpr", 102 | "525ptrcps0", 103 | "529rweas0", 104 | "515oscfing2s0", 105 | "524vidansgs0", 106 | ], 107 | "verbosity": "verbose", 108 | "traceId": get_ran_hex(32), 109 | "isStartOfSession": self.invocation_id == 3, 110 | "message": { 111 | "locale": locale, 112 | "market": locale, 113 | "region": locale[-2:], # en-US -> US 114 | "locationHints": get_location_hint_from_locale(locale), 115 | "timestamp": timestamp, 116 | "author": "user", 117 | "inputMethod": "Keyboard", 118 | "text": prompt, 119 | "messageType": "Chat", 120 | "messageId": message_id, 121 | "requestId": message_id, 122 | }, 123 | "tone": conversation_style.name.capitalize(), # Make first letter uppercase 124 | "requestId": message_id, 125 | "conversationSignature": self.conversation_signature, 126 | "participant": { 127 | "id": self.client_id, 128 | }, 129 | "conversationId": self.conversation_id, 130 | }, 131 | ], 132 | "invocationId": str(self.invocation_id), 133 | "target": "chat", 134 | "type": 4, 135 | } 136 | if search_result: 137 | have_search_result = [ 138 | "InternalSearchQuery", 139 | "InternalSearchResult", 140 | "InternalLoaderMessage", 141 | "RenderCardRequest", 142 | ] 143 | self.struct["arguments"][0]["allowedMessageTypes"] += have_search_result 144 | if webpage_context: 145 | self.struct["arguments"][0]["previousMessages"] = [ 146 | { 147 | "author": "user", 148 | "description": webpage_context, 149 | "contextType": "WebPage", 150 | "messageType": "Context", 151 | "messageId": "discover-web--page-ping-mriduna-----", 152 | }, 153 | ] 154 | self.invocation_id += 1 155 | 156 | # print(timestamp) 157 | -------------------------------------------------------------------------------- /src/EdgeGPT/utilities.py: -------------------------------------------------------------------------------- 1 | import json 2 | import locale 3 | import random 4 | import sys 5 | from typing import Union 6 | 7 | from .constants import DELIMITER 8 | from .locale import LocationHint 9 | 10 | 11 | def append_identifier(msg: dict) -> str: 12 | # Convert dict to json string 13 | return json.dumps(msg, ensure_ascii=False) + DELIMITER 14 | 15 | 16 | def get_ran_hex(length: int = 32) -> str: 17 | return "".join(random.choice("0123456789abcdef") for _ in range(length)) 18 | 19 | 20 | def get_location_hint_from_locale(locale: str) -> Union[dict, None]: 21 | locale = locale.lower() 22 | if locale == "en-gb": 23 | hint = LocationHint.UK.value 24 | elif locale == "en-ie": 25 | hint = LocationHint.EU.value 26 | elif locale == "zh-cn": 27 | hint = LocationHint.CHINA.value 28 | else: 29 | hint = LocationHint.USA.value 30 | return hint.get("LocationHint") 31 | 32 | 33 | def guess_locale() -> str: 34 | if sys.platform.startswith("win"): 35 | return "en-us" 36 | loc, _ = locale.getlocale() 37 | return loc.replace("_", "-") if loc else "en-us" 38 | -------------------------------------------------------------------------------- /test_base.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from EdgeGPT.EdgeGPT import Chatbot 5 | from EdgeGPT.EdgeGPT import ConversationStyle 6 | 7 | pytest_plugins = ("pytest_asyncio",) 8 | 9 | from os import getenv 10 | 11 | 12 | @pytest.mark.asyncio() 13 | async def test_ask() -> None: 14 | bot = await Chatbot.create(cookies=getenv("EDGE_COOKIES")) 15 | response = await bot.ask( 16 | prompt="find me some information about the new ai released by meta.", 17 | conversation_style=ConversationStyle.balanced, 18 | simplify_response=True, 19 | ) 20 | await bot.close() 21 | print(json.dumps(response, indent=2)) 22 | assert response 23 | --------------------------------------------------------------------------------