├── .dockerignore ├── .gitignore ├── Dockerfile ├── 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 ├── LICENSE ├── README.md ├── claude.json ├── claude.py ├── docker-compose.yml ├── main.py ├── public ├── background.png ├── dialog.css ├── favicon.ico ├── index.html └── style.css └── requirements.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | **/__pycache__ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /cookies.json 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | # C extensions 8 | *.so 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 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 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # poetry 99 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 100 | # This is especially recommended for binary packages to ensure reproducibility, and is more 101 | # commonly ignored for libraries. 102 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 103 | #poetry.lock 104 | 105 | # pdm 106 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 107 | #pdm.lock 108 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 109 | # in version control. 110 | # https://pdm.fming.dev/#use-with-ide 111 | .pdm.toml 112 | 113 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 114 | __pypackages__/ 115 | 116 | # Celery stuff 117 | celerybeat-schedule 118 | celerybeat.pid 119 | 120 | # SageMath parsed files 121 | *.sage.py 122 | 123 | # Environments 124 | .env 125 | .venv 126 | env/ 127 | venv/ 128 | ENV/ 129 | env.bak/ 130 | venv.bak/ 131 | 132 | # Spyder project settings 133 | .spyderproject 134 | .spyproject 135 | 136 | # Rope project settings 137 | .ropeproject 138 | 139 | # mkdocs documentation 140 | /site 141 | 142 | # mypy 143 | .mypy_cache/ 144 | .dmypy.json 145 | dmypy.json 146 | 147 | # Pyre type checker 148 | .pyre/ 149 | 150 | # pytype static type analyzer 151 | .pytype/ 152 | 153 | # Cython debug symbols 154 | cython_debug/ 155 | 156 | # PyCharm 157 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 158 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 159 | # and can be added to the global gitignore or merged into this file. For a more nuclear 160 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 161 | #.idea/ 162 | 163 | #vscode config 164 | .vscode -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | WORKDIR /app 4 | 5 | ADD requirements.txt requirements.txt 6 | RUN pip install -r requirements.txt --upgrade 7 | 8 | ADD . . 9 | # EXPOSE 65432 10 | 11 | CMD ["python", "-m","main","-H","0.0.0.0:65432"] 12 | -------------------------------------------------------------------------------- /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 | imgid: Union[dict, None] = None, 39 | ) -> Chatbot: 40 | self = Chatbot.__new__(Chatbot) 41 | self.proxy = proxy 42 | self.chat_hub = ChatHub( 43 | await Conversation.create(self.proxy, cookies=cookies, imgid=imgid), 44 | proxy=self.proxy, 45 | cookies=cookies, 46 | ) 47 | return self 48 | 49 | async def save_conversation(self, filename: str) -> None: 50 | """ 51 | Save the conversation to a file 52 | """ 53 | with open(filename, "w") as f: 54 | conversation_id = self.chat_hub.request.conversation_id 55 | conversation_signature = self.chat_hub.request.conversation_signature 56 | client_id = self.chat_hub.request.client_id 57 | invocation_id = self.chat_hub.request.invocation_id 58 | f.write( 59 | json.dumps( 60 | { 61 | "conversation_id": conversation_id, 62 | "conversation_signature": conversation_signature, 63 | "client_id": client_id, 64 | "invocation_id": invocation_id, 65 | }, 66 | ), 67 | ) 68 | 69 | async def load_conversation(self, filename: str) -> None: 70 | """ 71 | Load the conversation from a file 72 | """ 73 | with open(filename) as f: 74 | conversation = json.load(f) 75 | self.chat_hub.request = ChatHubRequest( 76 | conversation_signature=conversation["conversation_signature"], 77 | client_id=conversation["client_id"], 78 | conversation_id=conversation["conversation_id"], 79 | invocation_id=conversation["invocation_id"], 80 | ) 81 | 82 | async def get_conversation(self) -> dict: 83 | """ 84 | Gets the conversation history from conversation_id (requires load_conversation) 85 | """ 86 | return await self.chat_hub.get_conversation() 87 | 88 | async def get_activity(self) -> dict: 89 | """ 90 | Gets the recent activity (requires cookies) 91 | """ 92 | return await self.chat_hub.get_activity() 93 | 94 | async def ask( 95 | self, 96 | prompt: str, 97 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 98 | conversation_style: CONVERSATION_STYLE_TYPE = None, 99 | webpage_context: str | None = None, 100 | search_result: bool = False, 101 | locale: str = guess_locale(), 102 | simplify_response: bool = False, 103 | ) -> dict: 104 | """ 105 | Ask a question to the bot 106 | Response: 107 | { 108 | item (dict): 109 | messages (list[dict]): 110 | adaptiveCards (list[dict]): 111 | body (list[dict]): 112 | text (str): Response 113 | } 114 | To get the response, you can do: 115 | response["item"]["messages"][1]["adaptiveCards"][0]["body"][0]["text"] 116 | """ 117 | async for final, response in self.chat_hub.ask_stream( 118 | prompt=prompt, 119 | conversation_style=conversation_style, 120 | wss_link=wss_link, 121 | webpage_context=webpage_context, 122 | search_result=search_result, 123 | locale=locale, 124 | ): 125 | if final: 126 | if not simplify_response: 127 | return response 128 | messages_left = response["item"]["throttling"][ 129 | "maxNumUserMessagesInConversation" 130 | ] - response["item"]["throttling"].get( 131 | "numUserMessagesInConversation", 132 | 0, 133 | ) 134 | if messages_left == 0: 135 | raise Exception("Max messages reached") 136 | message = "" 137 | for msg in reversed(response["item"]["messages"]): 138 | if msg.get("adaptiveCards") and msg["adaptiveCards"][0]["body"][ 139 | 0 140 | ].get("text"): 141 | message = msg 142 | break 143 | if not message: 144 | raise Exception("No message found") 145 | suggestions = [ 146 | suggestion["text"] 147 | for suggestion in message.get("suggestedResponses", []) 148 | ] 149 | adaptive_cards = message.get("adaptiveCards", []) 150 | adaptive_text = ( 151 | adaptive_cards[0]["body"][0].get("text") if adaptive_cards else None 152 | ) 153 | sources = ( 154 | adaptive_cards[0]["body"][0].get("text") if adaptive_cards else None 155 | ) 156 | sources_text = ( 157 | adaptive_cards[0]["body"][-1].get("text") 158 | if adaptive_cards 159 | else None 160 | ) 161 | return { 162 | "text": message["text"], 163 | "author": message["author"], 164 | "sources": sources, 165 | "sources_text": sources_text, 166 | "suggestions": suggestions, 167 | "messages_left": messages_left, 168 | "max_messages": response["item"]["throttling"][ 169 | "maxNumUserMessagesInConversation" 170 | ], 171 | "adaptive_text": adaptive_text, 172 | } 173 | return {} 174 | 175 | async def ask_stream( 176 | self, 177 | prompt: str, 178 | wss_link: str = "wss://sydney.bing.com/sydney/ChatHub", 179 | conversation_style: CONVERSATION_STYLE_TYPE = None, 180 | raw: bool = False, 181 | webpage_context: str | None = None, 182 | search_result: bool = False, 183 | locale: str = guess_locale(), 184 | ) -> Generator[bool, dict | str, None]: 185 | """ 186 | Ask a question to the bot 187 | """ 188 | async for response in self.chat_hub.ask_stream( 189 | prompt=prompt, 190 | conversation_style=conversation_style, 191 | wss_link=wss_link, 192 | raw=raw, 193 | webpage_context=webpage_context, 194 | search_result=search_result, 195 | locale=locale, 196 | ): 197 | yield response 198 | 199 | async def close(self) -> None: 200 | """ 201 | Close the connection 202 | """ 203 | await self.chat_hub.close() 204 | 205 | async def delete_conversation( 206 | self, 207 | conversation_id: str = None, 208 | conversation_signature: str = None, 209 | client_id: str = None, 210 | ) -> None: 211 | """ 212 | Delete the chat in the server 213 | """ 214 | await self.chat_hub.delete_conversation( 215 | conversation_id=conversation_id, 216 | conversation_signature=conversation_signature, 217 | client_id=client_id, 218 | ) 219 | 220 | async def reset(self, delete=False) -> None: 221 | """ 222 | Reset the conversation 223 | """ 224 | if delete: 225 | await self.remove_and_close() 226 | else: 227 | await self.close() 228 | self.chat_hub = ChatHub( 229 | await Conversation.create(self.proxy, cookies=self.chat_hub.cookies), 230 | proxy=self.proxy, 231 | cookies=self.chat_hub.cookies, 232 | ) 233 | 234 | 235 | if __name__ == "__main__": 236 | from .main import main 237 | 238 | main() 239 | -------------------------------------------------------------------------------- /EdgeGPT/EdgeUtils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import platform 4 | import time 5 | from pathlib import Path 6 | from typing import Dict 7 | from typing import List 8 | from typing import Set 9 | from typing import Union 10 | 11 | from EdgeGPT.EdgeGPT import Chatbot 12 | from EdgeGPT.EdgeGPT import ConversationStyle 13 | from EdgeGPT.ImageGen import ImageGen 14 | 15 | 16 | class Cookie: 17 | """ 18 | Convenience class for Bing Cookie files, data, and configuration. This Class 19 | is updated dynamically by the Query class to allow cycling through >1 20 | cookie/credentials file e.g. when daily request limits (current 200 per 21 | account per day) are exceeded. 22 | """ 23 | 24 | current_file_index = 0 25 | dirpath = Path("./").resolve() 26 | search_pattern = "bing_cookies_*.json" 27 | ignore_files = set() 28 | current_filepath: Union[dict, None] = None 29 | 30 | @classmethod 31 | def fetch_default(cls, path: Union[Path, None] = None) -> None: 32 | from selenium import webdriver 33 | from selenium.webdriver.common.by import By 34 | 35 | driver = webdriver.Edge() 36 | driver.get("https://bing.com/chat") 37 | time.sleep(5) 38 | xpath = '//button[@id="bnp_btn_accept"]' 39 | driver.find_element(By.XPATH, xpath).click() 40 | time.sleep(2) 41 | xpath = '//a[@id="codexPrimaryButton"]' 42 | driver.find_element(By.XPATH, xpath).click() 43 | if path is None: 44 | path = Path("./bing_cookies__default.json") 45 | # Double underscore ensures this file is first when sorted 46 | cookies = driver.get_cookies() 47 | Path(path).write_text(json.dumps(cookies, indent=4), encoding="utf-8") 48 | # Path again in case supplied path is: str 49 | print(f"Cookies saved to: {path}") 50 | driver.quit() 51 | 52 | @classmethod 53 | def files(cls) -> List[Path]: 54 | """Return a sorted list of all cookie files matching .search_pattern""" 55 | all_files = set(cls.dirpath.glob(cls.search_pattern)) 56 | return sorted(all_files - cls.ignore_files) 57 | 58 | @classmethod 59 | def import_data(cls) -> None: 60 | """ 61 | Read the active cookie file and populate the following attributes: 62 | 63 | .current_filepath 64 | .current_data 65 | .image_token 66 | """ 67 | try: 68 | cls.current_filepath = cls.files()[cls.current_file_index] 69 | except IndexError as exc: 70 | print( 71 | "> Please set Cookie.current_filepath to a valid cookie file, then run Cookie.import_data()", 72 | ) 73 | raise "No valid cookie file found." from exc 74 | print(f"> Importing cookies from: {cls.current_filepath.name}") 75 | with Path.open(cls.current_filepath, encoding="utf-8") as file: 76 | cls.current_data = json.load(file) 77 | cls.image_token = [x for x in cls.current_data if x.get("name") == "_U"] 78 | cls.image_token = cls.image_token[0].get("value") 79 | 80 | @classmethod 81 | def import_next(cls) -> None: 82 | """ 83 | Cycle through to the next cookies file. Import it. Mark the previous 84 | file to be ignored for the remainder of the current session. 85 | """ 86 | cls.ignore_files.add(cls.current_filepath) 87 | if Cookie.current_file_index >= len(cls.files()): 88 | Cookie.current_file_index = 0 89 | Cookie.import_data() 90 | 91 | 92 | class Query: 93 | """ 94 | A convenience class that wraps around EdgeGPT.Chatbot to encapsulate input, 95 | config, and output all together. Relies on Cookie class for authentication 96 | """ 97 | 98 | def __init__( 99 | self, 100 | prompt: str, 101 | style: str = "precise", 102 | content_type: str = "text", 103 | cookie_file: int = 0, 104 | echo: bool = True, 105 | echo_prompt: bool = False, 106 | proxy: Union[str, None] = None, 107 | ) -> None: 108 | """ 109 | Arguments: 110 | 111 | prompt: Text to enter into Bing Chat 112 | style: creative, balanced, or precise 113 | content_type: "text" for Bing Chat; "image" for Dall-e 114 | cookie_file: Path, filepath string, or index (int) to list of cookie paths 115 | echo: Print something to confirm request made 116 | echo_prompt: Print confirmation of the evaluated prompt 117 | """ 118 | self.proxy = proxy 119 | self.index = [] 120 | self.request_count = {} 121 | self.image_dirpath = Path("./").resolve() 122 | Cookie.import_data() 123 | self.index += [self] 124 | self.prompt = prompt 125 | files = Cookie.files() 126 | if isinstance(cookie_file, int): 127 | index = cookie_file if cookie_file < len(files) else 0 128 | else: 129 | if not isinstance(cookie_file, (str, Path)): 130 | message = "'cookie_file' must be an int, str, or Path object" 131 | raise TypeError(message) 132 | cookie_file = Path(cookie_file) 133 | if cookie_file in files: # Supplied filepath IS in Cookie.dirpath 134 | index = files.index(cookie_file) 135 | else: # Supplied filepath is NOT in Cookie.dirpath 136 | if cookie_file.is_file(): 137 | Cookie.dirpath = cookie_file.parent.resolve() 138 | if cookie_file.is_dir(): 139 | Cookie.dirpath = cookie_file.resolve() 140 | index = 0 141 | Cookie.current_file_index = index 142 | if content_type == "text": 143 | self.style = style 144 | self.log_and_send_query(echo, echo_prompt) 145 | if content_type == "image": 146 | self.create_image() 147 | 148 | def log_and_send_query(self, echo: bool, echo_prompt: bool) -> None: 149 | if platform.system() == "Windows": 150 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 151 | self.response = asyncio.run(self.send_to_bing(echo, echo_prompt)) 152 | name = str(Cookie.current_filepath.name) 153 | if not self.request_count.get(name): 154 | self.request_count[name] = 1 155 | else: 156 | self.request_count[name] += 1 157 | 158 | def create_image(self) -> None: 159 | image_generator = ImageGen(Cookie.image_token) 160 | image_generator.save_images( 161 | image_generator.get_images(self.prompt), 162 | output_dir=self.image_dirpath, 163 | ) 164 | 165 | async def send_to_bing(self, echo: bool = True, echo_prompt: bool = False) -> str: 166 | """Creat, submit, then close a Chatbot instance. Return the response""" 167 | retries = len(Cookie.files()) 168 | while retries: 169 | try: 170 | # Read the cookies file 171 | bot = await Chatbot.create( 172 | proxy=self.proxy, 173 | cookies=Cookie.current_data, 174 | ) 175 | if echo_prompt: 176 | print(f"> {self.prompt}=") 177 | if echo: 178 | print("> Waiting for response...") 179 | if self.style.lower() not in "creative balanced precise".split(): 180 | self.style = "precise" 181 | return await bot.ask( 182 | prompt=self.prompt, 183 | conversation_style=getattr(ConversationStyle, self.style), 184 | # wss_link="wss://sydney.bing.com/sydney/ChatHub" 185 | # What other values can this parameter take? It seems to be optional 186 | ) 187 | except KeyError: 188 | print( 189 | f"> KeyError [{Cookie.current_filepath.name} may have exceeded the daily limit]", 190 | ) 191 | Cookie.import_next() 192 | retries -= 1 193 | finally: 194 | await bot.close() 195 | return None 196 | 197 | @property 198 | def output(self) -> str: 199 | """The response from a completed Chatbot request""" 200 | return self.response["item"]["messages"][-1]["text"] 201 | 202 | @property 203 | def sources(self) -> str: 204 | """The source names and details parsed from a completed Chatbot request""" 205 | return self.response["item"]["messages"][-1]["sourceAttributions"] 206 | 207 | @property 208 | def sources_dict(self) -> Dict[str, str]: 209 | """The source names and details as a dictionary""" 210 | sources_dict = {} 211 | name = "providerDisplayName" 212 | url = "seeMoreUrl" 213 | for source in self.sources: 214 | if name in source and url in source: 215 | sources_dict[source[name]] = source[url] 216 | else: 217 | continue 218 | return sources_dict 219 | 220 | @property 221 | def code(self) -> str: 222 | """Extract and join any snippets of Python code in the response""" 223 | code_blocks = self.output.split("```")[1:-1:2] 224 | code_blocks = ["\n".join(x.splitlines()[1:]) for x in code_blocks] 225 | return "\n\n".join(code_blocks) 226 | 227 | @property 228 | def languages(self) -> Set[str]: 229 | """Extract all programming languages given in code blocks""" 230 | code_blocks = self.output.split("```")[1:-1:2] 231 | return {x.splitlines()[0] for x in code_blocks} 232 | 233 | @property 234 | def suggestions(self) -> List[str]: 235 | """Follow-on questions suggested by the Chatbot""" 236 | return [ 237 | x["text"] 238 | for x in self.response["item"]["messages"][-1]["suggestedResponses"] 239 | ] 240 | 241 | def __repr__(self) -> str: 242 | return f"" 243 | 244 | def __str__(self) -> str: 245 | return self.output 246 | 247 | 248 | class ImageQuery(Query): 249 | def __init__(self, prompt: str, **kwargs) -> None: 250 | kwargs["content_type"] = "image" 251 | super().__init__(prompt, **kwargs) 252 | 253 | def __repr__(self) -> str: 254 | return f"" 255 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /EdgeGPT/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoraRoseous/ChatSydney-react/888ff3ef3ca38fb5d04339a9a82dd767e8ee3888/EdgeGPT/__init__.py -------------------------------------------------------------------------------- /EdgeGPT/chathub.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | import random 5 | import ssl 6 | import sys 7 | import aiohttp 8 | import urllib.parse 9 | from time import time 10 | from typing import Generator 11 | from typing import List 12 | from typing import Union 13 | 14 | import certifi 15 | import httpx 16 | from BingImageCreator import ImageGenAsync 17 | 18 | from .constants import DELIMITER 19 | from .constants import HEADERS 20 | from .constants import HEADERS_INIT_CONVER 21 | from .conversation import Conversation 22 | from .conversation_style import CONVERSATION_STYLE_TYPE 23 | from .request import ChatHubRequest 24 | from .utilities import append_identifier 25 | from .utilities import get_ran_hex 26 | from .utilities import guess_locale 27 | 28 | ssl_context = ssl.create_default_context() 29 | ssl_context.load_verify_locations(certifi.where()) 30 | 31 | 32 | class ChatHub: 33 | def __init__( 34 | self, 35 | conversation: Conversation, 36 | proxy: str = None, 37 | cookies: Union[List[dict], None] = None, 38 | ) -> None: 39 | self.request: ChatHubRequest 40 | self.loop: bool 41 | self.task: asyncio.Task 42 | self.sec_access_token: str | None = conversation.sec_access_token 43 | self.request = ChatHubRequest( 44 | conversation_signature=conversation.struct.get("conversationSignature"), 45 | client_id=conversation.struct["clientId"], 46 | conversation_id=conversation.struct["conversationId"], 47 | imgid=conversation.imgid, 48 | ) 49 | self.cookies = cookies 50 | self.proxy: str = proxy 51 | proxy = ( 52 | proxy 53 | or os.environ.get("all_proxy") 54 | or os.environ.get("ALL_PROXY") 55 | or os.environ.get("https_proxy") 56 | or os.environ.get("HTTPS_PROXY") 57 | or None 58 | ) 59 | if proxy is not None and proxy.startswith("socks5h://"): 60 | proxy = "socks5://" + proxy[len("socks5h://") :] 61 | self.session = httpx.AsyncClient( 62 | proxies=proxy, 63 | timeout=900, 64 | headers=HEADERS_INIT_CONVER, 65 | ) 66 | cookies = {} 67 | if self.cookies is not None: 68 | for cookie in self.cookies: 69 | cookies[cookie["name"]] = cookie["value"] 70 | self.aio_session = aiohttp.ClientSession(cookies=cookies) 71 | 72 | async def get_conversation( 73 | self, 74 | conversation_id: str = None, 75 | conversation_signature: str = None, 76 | client_id: str = None, 77 | ) -> dict: 78 | conversation_id = conversation_id or self.request.conversation_id 79 | conversation_signature = ( 80 | conversation_signature or self.request.conversation_signature 81 | ) 82 | client_id = client_id or self.request.client_id 83 | url = f"https://sydney.bing.com/sydney/GetConversation?conversationId={conversation_id}&source=cib&participantId={client_id}&conversationSignature={conversation_signature}&traceId={get_ran_hex()}" 84 | response = await self.session.get(url) 85 | return response.json() 86 | 87 | async def get_activity(self) -> dict: 88 | url = "https://www.bing.com/turing/conversation/chats" 89 | headers = HEADERS_INIT_CONVER.copy() 90 | if self.cookies is not None: 91 | for cookie in self.cookies: 92 | if cookie["name"] == "_U": 93 | headers["Cookie"] = f"SUID=A; _U={cookie['value']};" 94 | break 95 | response = await self.session.get(url, headers=headers) 96 | return response.json() 97 | 98 | async def ask_stream( 99 | self, 100 | prompt: str, 101 | wss_link: str = None, 102 | conversation_style: CONVERSATION_STYLE_TYPE = None, 103 | raw: bool = False, 104 | webpage_context: Union[str, None] = None, 105 | search_result: bool = False, 106 | locale: str = guess_locale(), 107 | ) -> Generator[bool, Union[dict, str], None]: 108 | """ """ 109 | 110 | if self.sec_access_token: 111 | wss_link = ( 112 | "wss://sydney.bing.com/sydney/ChatHub?sec_access_token=" 113 | + urllib.parse.quote_plus(self.sec_access_token) 114 | ) 115 | 116 | # Check if websocket is closed 117 | async with self.aio_session.ws_connect( 118 | wss_link or "wss://sydney.bing.com/sydney/ChatHub", 119 | ssl=ssl_context, 120 | headers={ 121 | **HEADERS, 122 | "x-forwarded-for": f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}", 123 | }, 124 | proxy=self.proxy, 125 | ) as wss: 126 | await self._initial_handshake(wss) 127 | # Construct a ChatHub request 128 | self.request.update( 129 | prompt=prompt, 130 | conversation_style=conversation_style, 131 | webpage_context=webpage_context, 132 | search_result=search_result, 133 | locale=locale, 134 | ) 135 | # Send request 136 | await wss.send_str(append_identifier(self.request.struct)) 137 | # print(json.dumps(self.request.struct), "\n\n") 138 | draw = False 139 | resp_txt = "" 140 | result_text = "" 141 | resp_txt_no_link = "" 142 | retry_count = 5 143 | while not wss.closed: 144 | msg = await wss.receive_str() 145 | if not msg: 146 | retry_count -= 1 147 | if retry_count == 0: 148 | raise Exception("No response from server") 149 | continue 150 | if isinstance(msg, str): 151 | objects = msg.split(DELIMITER) 152 | else: 153 | continue 154 | for obj in objects: 155 | if int(time()) % 6 == 0: 156 | await wss.send_str(append_identifier({"type": 6})) 157 | if obj is None or not obj: 158 | continue 159 | response = json.loads(obj) 160 | # print(json.dumps(response, indent=4, separators=(', ', ': ')), "\n\n") 161 | if response.get("type") == 1 and response["arguments"][0].get( 162 | "messages", 163 | ): 164 | if not draw: 165 | if ( 166 | response["arguments"][0]["messages"][0].get( 167 | "messageType", 168 | ) 169 | == "GenerateContentQuery" 170 | ): 171 | async with ImageGenAsync( 172 | all_cookies=self.cookies, 173 | ) as image_generator: 174 | images = await image_generator.get_images( 175 | response["arguments"][0]["messages"][0]["text"], 176 | ) 177 | for i, image in enumerate(images): 178 | resp_txt = f"{resp_txt}\n![image{i}]({image})" 179 | draw = True 180 | if ( 181 | ( 182 | response["arguments"][0]["messages"][0][ 183 | "contentOrigin" 184 | ] 185 | != "Apology" 186 | ) 187 | and not draw 188 | and not raw 189 | ): 190 | resp_txt = result_text + response["arguments"][0][ 191 | "messages" 192 | ][0]["adaptiveCards"][0]["body"][0].get("text", "") 193 | resp_txt_no_link = result_text + response["arguments"][ 194 | 0 195 | ]["messages"][0].get("text", "") 196 | if response["arguments"][0]["messages"][0].get( 197 | "messageType", 198 | ): 199 | resp_txt = ( 200 | resp_txt 201 | + response["arguments"][0]["messages"][0][ 202 | "adaptiveCards" 203 | ][0]["body"][0]["inlines"][0].get("text") 204 | + "\n" 205 | ) 206 | result_text = ( 207 | result_text 208 | + response["arguments"][0]["messages"][0][ 209 | "adaptiveCards" 210 | ][0]["body"][0]["inlines"][0].get("text") 211 | + "\n" 212 | ) 213 | if not raw: 214 | yield False, resp_txt 215 | 216 | elif response.get("type") == 2: 217 | if response["item"]["result"].get("error"): 218 | await self.close() 219 | raise Exception( 220 | f"{response['item']['result']['value']}: {response['item']['result']['message']}", 221 | ) 222 | if draw: 223 | for message in response["item"]["messages"]: 224 | if "adaptiveCards" in message: 225 | cache = message["adaptiveCards"][0]["body"][0][ 226 | "text" 227 | ] 228 | message["adaptiveCards"][0]["body"][0]["text"] = ( 229 | cache + resp_txt 230 | ) 231 | if ( 232 | response["item"]["messages"][-1]["contentOrigin"] 233 | == "Apology" 234 | and resp_txt 235 | ): 236 | response["item"]["messages"][-1]["text"] = resp_txt_no_link 237 | response["item"]["messages"][-1]["adaptiveCards"][0][ 238 | "body" 239 | ][0]["text"] = resp_txt 240 | print( 241 | "Preserved the message from being deleted", 242 | file=sys.stderr, 243 | ) 244 | await wss.close() 245 | yield True, response 246 | return 247 | if response.get("type") != 2: 248 | if response.get("type") == 6: 249 | await wss.send_str(append_identifier({"type": 6})) 250 | elif response.get("type") == 7: 251 | await wss.send_str(append_identifier({"type": 7})) 252 | elif raw: 253 | yield False, response 254 | 255 | async def _initial_handshake(self, wss) -> None: 256 | await wss.send_str(append_identifier({"protocol": "json", "version": 1})) 257 | await wss.receive_str() 258 | await wss.send_str(append_identifier({"type": 6})) 259 | 260 | async def delete_conversation( 261 | self, 262 | conversation_id: str = None, 263 | conversation_signature: str = None, 264 | client_id: str = None, 265 | ) -> None: 266 | conversation_id = conversation_id or self.request.conversation_id 267 | conversation_signature = ( 268 | conversation_signature or self.request.conversation_signature 269 | ) 270 | client_id = client_id or self.request.client_id 271 | url = "https://sydney.bing.com/sydney/DeleteSingleConversation" 272 | await self.session.post( 273 | url, 274 | json={ 275 | "conversationId": conversation_id, 276 | "conversationSignature": conversation_signature, 277 | "participant": {"id": client_id}, 278 | "source": "cib", 279 | "optionsSets": ["autosave"], 280 | }, 281 | ) 282 | 283 | async def close(self) -> None: 284 | await self.session.aclose() 285 | await self.aio_session.close() 286 | -------------------------------------------------------------------------------- /EdgeGPT/constants.py: -------------------------------------------------------------------------------- 1 | import random 2 | import uuid 3 | 4 | DELIMITER = "\x1e" 5 | 6 | 7 | # Generate random IP between range 13.104.0.0/14 8 | FORWARDED_IP = f"1.0.0.{random.randint(0, 255)}" 9 | 10 | HEADERS = { 11 | "accept": "application/json", 12 | "accept-language": "en-US,en;q=0.9", 13 | "content-type": "application/json", 14 | "sec-ch-ua": '"Not_A Brand";v="99", Microsoft Edge";v="110", "Chromium";v="110"', 15 | "sec-ch-ua-arch": '"x86"', 16 | "sec-ch-ua-bitness": '"64"', 17 | "sec-ch-ua-full-version": '"109.0.1518.78"', 18 | "sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"', 19 | "sec-ch-ua-mobile": "?0", 20 | "sec-ch-ua-model": "", 21 | "sec-ch-ua-platform": '"Windows"', 22 | "sec-ch-ua-platform-version": '"15.0.0"', 23 | "sec-fetch-dest": "empty", 24 | "sec-fetch-mode": "cors", 25 | "sec-fetch-site": "same-origin", 26 | "x-ms-client-request-id": str(uuid.uuid4()), 27 | "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32", 28 | "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx", 29 | "Referrer-Policy": "origin-when-cross-origin", 30 | "x-forwarded-for": FORWARDED_IP, 31 | } 32 | 33 | HEADERS_INIT_CONVER = { 34 | "authority": "www.bing.com", 35 | "accept": "application/json", 36 | "accept-language": "en-US,en;q=0.9", 37 | "cache-control": "max-age=0", 38 | "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"', 39 | "sec-ch-ua-arch": '"x86"', 40 | "sec-ch-ua-bitness": '"64"', 41 | "sec-ch-ua-full-version": '"110.0.1587.69"', 42 | "sec-ch-ua-full-version-list": '"Chromium";v="110.0.5481.192", "Not A(Brand";v="24.0.0.0", "Microsoft Edge";v="110.0.1587.69"', 43 | "sec-ch-ua-mobile": "?0", 44 | "sec-ch-ua-model": '""', 45 | "sec-ch-ua-platform": '"Windows"', 46 | "sec-ch-ua-platform-version": '"15.0.0"', 47 | "upgrade-insecure-requests": "1", 48 | "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.46", 49 | "x-edge-shopping-flag": "1", 50 | "x-forwarded-for": FORWARDED_IP, 51 | } -------------------------------------------------------------------------------- /EdgeGPT/conversation.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import random 4 | from typing import List 5 | from typing import Union 6 | 7 | import httpx 8 | 9 | from .constants import HEADERS_INIT_CONVER 10 | from .exceptions import NotAllowedToAccess 11 | 12 | 13 | class Conversation: 14 | def __init__( 15 | self, 16 | proxy: Union[str, None] = None, 17 | async_mode: bool = False, 18 | cookies: Union[List[dict], None] = None, 19 | ) -> None: 20 | self.sec_access_token: str | None = None 21 | if async_mode: 22 | return 23 | self.imgid: Union[dict, None] = None 24 | self.struct: dict = { 25 | "conversationId": None, 26 | "clientId": None, 27 | "conversationSignature": None, 28 | "result": {"value": "Success", "message": None}, 29 | } 30 | self.proxy = proxy 31 | proxy = ( 32 | proxy 33 | or os.environ.get("all_proxy") 34 | or os.environ.get("ALL_PROXY") 35 | or os.environ.get("https_proxy") 36 | or os.environ.get("HTTPS_PROXY") 37 | or None 38 | ) 39 | if proxy is not None and proxy.startswith("socks5h://"): 40 | proxy = "socks5://" + proxy[len("socks5h://") :] 41 | self.session = httpx.Client( 42 | proxies=proxy, 43 | timeout=900, 44 | headers=HEADERS_INIT_CONVER, 45 | ) 46 | if cookies: 47 | for cookie in cookies: 48 | self.session.cookies.set(cookie["name"], cookie["value"]) 49 | # Send GET request 50 | response = self.session.get( 51 | url=os.environ.get("BING_PROXY_URL") 52 | or "https://edgeservices.bing.com/edgesvc/turing/conversation/create", 53 | ) 54 | if response.status_code != 200: 55 | print(f"Status code: {response.status_code}") 56 | print(response.text) 57 | print(response.url) 58 | raise Exception("Authentication failed") 59 | try: 60 | self.struct = response.json() 61 | except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc: 62 | raise Exception( 63 | "Authentication failed. You have not been accepted into the beta.", 64 | ) from exc 65 | if self.struct["result"]["value"] == "UnauthorizedRequest": 66 | raise NotAllowedToAccess(self.struct["result"]["message"]) 67 | 68 | @staticmethod 69 | async def create( 70 | proxy: Union[str, None] = None, 71 | cookies: Union[List[dict], None] = None, 72 | imgid: Union[dict, None] = None, 73 | ) -> "Conversation": 74 | self = Conversation(async_mode=True) 75 | self.imgid = imgid 76 | self.struct = { 77 | "conversationId": None, 78 | "clientId": None, 79 | "conversationSignature": None, 80 | "result": {"value": "Success", "message": None}, 81 | } 82 | self.proxy = proxy 83 | proxy = ( 84 | proxy 85 | or os.environ.get("all_proxy") 86 | or os.environ.get("ALL_PROXY") 87 | or os.environ.get("https_proxy") 88 | or os.environ.get("HTTPS_PROXY") 89 | or None 90 | ) 91 | if proxy is not None and proxy.startswith("socks5h://"): 92 | proxy = "socks5://" + proxy[len("socks5h://") :] 93 | transport = httpx.AsyncHTTPTransport(retries=900) 94 | # Convert cookie format to httpx format 95 | formatted_cookies = None 96 | if cookies: 97 | formatted_cookies = httpx.Cookies() 98 | for cookie in cookies: 99 | formatted_cookies.set(cookie["name"], cookie["value"]) 100 | async with httpx.AsyncClient( 101 | proxies=proxy, 102 | timeout=30, 103 | headers={ 104 | **HEADERS_INIT_CONVER, 105 | "x-forwarded-for": f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}", 106 | }, 107 | transport=transport, 108 | cookies=formatted_cookies, 109 | ) as client: 110 | # Send GET request 111 | response = await client.get( 112 | url=os.environ.get("BING_PROXY_URL") 113 | or "https://www.bing.com/turing/conversation/create", 114 | follow_redirects=True, 115 | ) 116 | if response.status_code != 200: 117 | print(f"Status code: {response.status_code}") 118 | print(response.text) 119 | print(response.url) 120 | raise Exception("Authentication failed\n创建对话时认证失败,尝试更换cookie或者更换代理后再试。") 121 | try: 122 | self.struct = response.json() 123 | # print(response.text) 124 | except (json.decoder.JSONDecodeError, NotAllowedToAccess) as exc: 125 | print(response.text) 126 | raise Exception( 127 | "Authentication failed. You have not been accepted into the beta.", 128 | ) from exc 129 | if self.struct["result"]["value"] == "UnauthorizedRequest": 130 | raise NotAllowedToAccess(self.struct["result"]["message"]) 131 | if 'X-Sydney-Encryptedconversationsignature' in response.headers: 132 | self.sec_access_token = response.headers['X-Sydney-Encryptedconversationsignature'] 133 | return self 134 | -------------------------------------------------------------------------------- /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 | "iycapbing", 18 | "iyxapbing", 19 | "uquopt", 20 | "authsndfdbk", 21 | "refpromptv1", 22 | "enuaug", 23 | "dagslnv1nr", 24 | "dv3sugg", 25 | "iyoloxap", 26 | "iyoloneutral", 27 | "h3imaginative", 28 | "clgalileo", 29 | "gencontentv3", 30 | "travelansgnd", 31 | "fluxv14l", 32 | "gndelec", 33 | "gndlogcf", 34 | "fluxprod", 35 | "nojbfedge", 36 | # "autosave", 37 | ] 38 | balanced = [ 39 | "nlu_direct_response_filter", 40 | "deepleo", 41 | "disable_emoji_spoken_text", 42 | "responsible_ai_policy_235", 43 | "enablemm", 44 | "galileo", 45 | "saharagenconv5", 46 | "objopinion", 47 | "dsblhlthcrd", 48 | "dv3sugg", 49 | "autosave", 50 | ] 51 | precise = [ 52 | "nlu_direct_response_filter", 53 | "deepleo", 54 | "disable_emoji_spoken_text", 55 | "responsible_ai_policy_235", 56 | "enablemm", 57 | "h3precise", 58 | "objopinion", 59 | "dsblhlthcrd", 60 | "dv3sugg", 61 | "autosave", 62 | "clgalileo", 63 | "gencontentv3", 64 | ] 65 | 66 | 67 | CONVERSATION_STYLE_TYPE = Optional[ 68 | Union[ConversationStyle, Literal["creative", "balanced", "precise"]] 69 | ] 70 | -------------------------------------------------------------------------------- /EdgeGPT/exceptions.py: -------------------------------------------------------------------------------- 1 | class NotAllowedToAccess(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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): 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 | break 110 | if question == "!help": 111 | print( 112 | """ 113 | !help - Show this help message 114 | !exit - Exit the program 115 | !reset - Reset the conversation 116 | """, 117 | ) 118 | continue 119 | if question == "!reset": 120 | await bot.reset() 121 | continue 122 | print("Bot:") 123 | p_hist("Bot:") 124 | if args.no_stream: 125 | response = ( 126 | await bot.ask( 127 | prompt=question, 128 | conversation_style=args.style, 129 | wss_link=args.wss_link, 130 | search_result=args.search_result, 131 | locale=args.locale, 132 | ) 133 | )["item"]["messages"][-1]["adaptiveCards"][0]["body"][0]["text"] 134 | print(response) 135 | p_hist(response) 136 | else: 137 | wrote = 0 138 | if args.rich: 139 | md = Markdown("") 140 | with Live(md, auto_refresh=False) as live: 141 | async for final, response in bot.ask_stream( 142 | prompt=question, 143 | conversation_style=args.style, 144 | wss_link=args.wss_link, 145 | search_result=args.search_result, 146 | locale=args.locale, 147 | ): 148 | if not final: 149 | if not wrote: 150 | p_hist(response, end="") 151 | else: 152 | p_hist(response[wrote:], end="") 153 | if wrote > len(response): 154 | print(md) 155 | print(Markdown("***Bing revoked the response.***")) 156 | wrote = len(response) 157 | md = Markdown(response) 158 | live.update(md, refresh=True) 159 | else: 160 | async for final, response in bot.ask_stream( 161 | prompt=question, 162 | conversation_style=args.style, 163 | wss_link=args.wss_link, 164 | search_result=args.search_result, 165 | locale=args.locale, 166 | ): 167 | if not final: 168 | if not wrote: 169 | print(response, end="", flush=True) 170 | p_hist(response, end="") 171 | else: 172 | print(response[wrote:], end="", flush=True) 173 | p_hist(response[wrote:], end="") 174 | wrote = len(response) 175 | print() 176 | p_hist() 177 | if args.history_file: 178 | f.close() 179 | await bot.close() 180 | 181 | 182 | def main() -> None: 183 | print( 184 | """ 185 | EdgeGPT - A demo of reverse engineering the Bing GPT chatbot 186 | Repo: github.com/acheong08/EdgeGPT 187 | By: Antonio Cheong 188 | 189 | !help for help 190 | 191 | Type !exit to exit 192 | """, 193 | ) 194 | parser = argparse.ArgumentParser() 195 | parser.add_argument("--enter-once", action="store_true") 196 | parser.add_argument("--search-result", action="store_true") 197 | parser.add_argument("--no-stream", action="store_true") 198 | parser.add_argument("--rich", action="store_true") 199 | parser.add_argument( 200 | "--proxy", 201 | help="Proxy URL (e.g. socks5://127.0.0.1:1080)", 202 | type=str, 203 | ) 204 | parser.add_argument( 205 | "--wss-link", 206 | help="WSS URL(e.g. wss://sydney.bing.com/sydney/ChatHub)", 207 | type=str, 208 | default="wss://sydney.bing.com/sydney/ChatHub", 209 | ) 210 | parser.add_argument( 211 | "--style", 212 | choices=["creative", "balanced", "precise"], 213 | default="balanced", 214 | ) 215 | parser.add_argument( 216 | "--prompt", 217 | type=str, 218 | default="", 219 | required=False, 220 | help="prompt to start with", 221 | ) 222 | parser.add_argument( 223 | "--cookie-file", 224 | type=str, 225 | default="", 226 | required=False, 227 | help="path to cookie file", 228 | ) 229 | parser.add_argument( 230 | "--history-file", 231 | type=str, 232 | default="", 233 | required=False, 234 | help="path to history file", 235 | ) 236 | parser.add_argument( 237 | "--locale", 238 | type=str, 239 | default="en-US", 240 | required=False, 241 | help="your locale", 242 | ) 243 | args = parser.parse_args() 244 | asyncio.run(async_main(args)) 245 | -------------------------------------------------------------------------------- /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 | imgid: Union[dict, None] = None, 20 | ) -> None: 21 | self.struct: dict = {} 22 | 23 | self.imgid: Union[dict, None] = imgid 24 | self.client_id: str = client_id 25 | self.conversation_id: str = conversation_id 26 | self.conversation_signature: str = conversation_signature 27 | self.invocation_id: int = invocation_id 28 | 29 | def update( 30 | self, 31 | prompt: str, 32 | conversation_style: CONVERSATION_STYLE_TYPE, 33 | webpage_context: Union[str, None] = None, 34 | search_result: bool = False, 35 | locale: str = guess_locale(), 36 | ) -> None: 37 | options = [ 38 | "deepleo", 39 | "enable_debug_commands", 40 | "disable_emoji_spoken_text", 41 | "enablemm", 42 | ] 43 | if conversation_style: 44 | if not isinstance(conversation_style, ConversationStyle): 45 | conversation_style = getattr(ConversationStyle, conversation_style) 46 | options = conversation_style.value 47 | message_id = str(uuid.uuid4()) 48 | # Get the current local time 49 | now_local = datetime.now() 50 | 51 | # Get the current UTC time 52 | now_utc = datetime.utcnow() 53 | 54 | # Calculate the time difference between local and UTC time 55 | timezone_offset = now_local - now_utc 56 | 57 | # Get the offset in hours and minutes 58 | offset_hours = int(timezone_offset.total_seconds() // 3600) 59 | offset_minutes = int((timezone_offset.total_seconds() % 3600) // 60) 60 | 61 | # Format the offset as a string 62 | offset_string = f"{offset_hours:+03d}:{offset_minutes:02d}" 63 | 64 | # Get current time 65 | timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") + offset_string 66 | self.struct = { 67 | "arguments": [ 68 | { 69 | "source": "cib", 70 | "optionsSets": options, 71 | "allowedMessageTypes": [ 72 | "ActionRequest", 73 | "Chat", 74 | "Context", 75 | "InternalSearchQuery", 76 | "InternalSearchResult", 77 | "Disengaged", 78 | "InternalLoaderMessage", 79 | "Progress", 80 | "RenderCardRequest", 81 | "AdsQuery", 82 | "SemanticSerp", 83 | "GenerateContentQuery", 84 | "SearchQuery", 85 | ], 86 | "sliceIds": [ 87 | "winmuid1tf", 88 | "styleoff", 89 | "ccadesk", 90 | "smsrpsuppv4cf", 91 | "ssrrcache", 92 | "contansperf", 93 | "crchatrev", 94 | "winstmsg2tf", 95 | "creatgoglt", 96 | "creatorv2t", 97 | "sydconfigoptt", 98 | "adssqovroff", 99 | "530pstho", 100 | "517opinion", 101 | "418dhlth", 102 | "512sprtic1s0", 103 | "emsgpr", 104 | "525ptrcps0", 105 | "529rweas0", 106 | "515oscfing2s0", 107 | "524vidansgs0", 108 | ], 109 | "verbosity": "verbose", 110 | "traceId": get_ran_hex(32), 111 | "isStartOfSession": self.invocation_id == 3, 112 | "message": { 113 | "locale": locale, 114 | "market": locale, 115 | "region": locale[-2:], # en-US -> US 116 | "locationHints": get_location_hint_from_locale(locale), 117 | "timestamp": timestamp, 118 | "author": "user", 119 | "inputMethod": "Keyboard", 120 | "text": prompt, 121 | "messageType": "Chat", 122 | "messageId": message_id, 123 | "requestId": message_id, 124 | }, 125 | "tone": conversation_style.name.capitalize(), # Make first letter uppercase 126 | "requestId": message_id, 127 | "conversationSignature": self.conversation_signature \ 128 | if self.conversation_signature else None, 129 | "participant": { 130 | "id": self.client_id, 131 | }, 132 | "conversationId": self.conversation_id, 133 | }, 134 | ], 135 | "invocationId": str(self.invocation_id), 136 | "target": "chat", 137 | "type": 4, 138 | } 139 | if search_result: 140 | have_search_result = [ 141 | "InternalSearchQuery", 142 | "InternalSearchResult", 143 | "InternalLoaderMessage", 144 | "RenderCardRequest", 145 | ] 146 | self.struct["arguments"][0]["allowedMessageTypes"] += have_search_result 147 | if webpage_context: 148 | self.struct["arguments"][0]["previousMessages"] = [ 149 | { 150 | "author": "user", 151 | "description": webpage_context, 152 | "contextType": "WebPage", 153 | "messageType": "Context", 154 | "messageId": "discover-web--page-ping-mriduna-----", 155 | }, 156 | ] 157 | if self.imgid: 158 | self.struct["arguments"][0]["message"] = { 159 | **self.struct["arguments"][0]["message"], 160 | "imageUrl": f'https://www.bing.com/images/blob?bcid={self.imgid["processedBlobId"] or self.imgid["blobId"]}', 161 | "originalImageUrl": f'https://www.bing.com/images/blob?bcid={self.imgid["blobId"]}', 162 | } 163 | self.invocation_id += 1 164 | 165 | # print(timestamp) 166 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 欢迎到slack来tree new bing。[https://join.slack.com/t/chatsydneycomminicate/shared_invite/zt-1x7tjzgdz-v71xazsoE206kJwFuyf1rA](https://join.slack.com/t/chatsydneycomminicate/shared_invite/zt-23d78m6xe-1UfuM_831_2uGoRgn16ESA) 2 | 3 | 4 | # ChatSydney 5 | 中国用户请设置能翻墙的系统代理 Chinese users, please set up a system proxy that can bypass the GFW. 6 | 使用的时候最好添加cookies.json文件。 7 | 8 | 9 | ## Installation 10 | 11 | First, you need to have Python 3.11 or higher installed. Then, you can install the required dependencies using pip: 12 | 13 | ```bash 14 | pip install -r requirements.txt --upgrade 15 | ``` 16 | 17 | ## How to get cookies.json 18 | same as EdgeGPT https://github.com/acheong08/EdgeGPT#getting-authentication-required 19 | 20 | ## Usage 21 | 22 | After saving `cookies.json` in current directory, you can run this project using the Python command line: 23 | 24 | ```bash 25 | python main.py 26 | ``` 27 | 28 | Then, you can open `http://localhost:65432` in your browser to start chatting. 29 | 30 | ## Command Line Arguments 31 | 32 | - `--host` or `-H`: The hostname and port for the server, default is `localhost:65432`. 33 | - `--proxy` or `-p`: Proxy address, like `http://localhost:7890`, default is to use urllib to get proxy 34 | 35 | ## WebSocket API 36 | 37 | The WebSocket API accepts a JSON object containing the following fields: 38 | 39 | - `message`: The user's message. 40 | - `context`: The context of the conversation, can be any string. 41 | 42 | The WebSocket API returns a JSON object containing the following fields: 43 | 44 | - `type`: The type of the message, can be the type from Bing response or `error`. 45 | - `message`: The response from EdgeGPT. 46 | - `error`: If an error occurs, this field will contain the error message. 47 | 48 | ## Server 49 | 50 | If you want to deploy to the server, and use https protocol to access, then you need to change `websocket = new WebSocket(ws://${window.location.host}/ws/)` to `websocket = new WebSocket(wss://${window.location.host}/ws/)` in public/index.html 51 | -------------------------------------------------------------------------------- /claude.json: -------------------------------------------------------------------------------- 1 | { 2 | "slackUserToken": "", 3 | "slackChannelId": "", 4 | "claudeMemberId": "" 5 | } -------------------------------------------------------------------------------- /claude.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | import os 4 | 5 | from slack_sdk.web.async_client import AsyncWebClient 6 | 7 | if os.path.exists("claude.json"): 8 | with open("claude.json") as f: 9 | try: 10 | claude_config = json.load(f) 11 | except json.JSONDecodeError: 12 | claude_config = {} 13 | else: 14 | claude_config = {} 15 | 16 | 17 | class Chatbot: 18 | def __init__( 19 | self, 20 | slack_user_token=claude_config.get("slackUserToken"), 21 | slack_channel_id=claude_config.get("slackChannelId"), 22 | claude_member_id=claude_config.get("claudeMemberId"), 23 | proxy=None, 24 | ): 25 | self.client = AsyncWebClient(token=slack_user_token, proxy=proxy) 26 | self.slack_channel_id = slack_channel_id 27 | self.claude_member_id = claude_member_id 28 | 29 | async def ask_stream(self, message): 30 | if len(message) < 3000: # Slack truncates message at ~3000 characters 31 | response = await self.client.chat_postMessage(channel=self.slack_channel_id, text=message) 32 | thread_ts = response["ts"] 33 | else: 34 | response = await self.client.chat_postMessage(channel=self.slack_channel_id, text=message[:3000]) 35 | thread_ts = response["ts"] 36 | await self.client.chat_postMessage( 37 | channel=self.slack_channel_id, 38 | text=message[3000:], 39 | thread_ts=thread_ts, 40 | ) 41 | 42 | await self.client.chat_postMessage( 43 | channel=self.slack_channel_id, 44 | text=f'<@{self.claude_member_id}> [assistant](#message)', 45 | thread_ts=thread_ts, 46 | ) 47 | 48 | while True: 49 | await asyncio.sleep(1) 50 | replies_response = await self.client.conversations_replies(channel=self.slack_channel_id, ts=thread_ts) 51 | all_replies = replies_response["messages"] 52 | for reply in all_replies: 53 | if reply["user"] == self.claude_member_id: 54 | break 55 | else: 56 | continue 57 | 58 | if reply["text"].endswith("_Typing…_"): 59 | yield reply["text"][:-11] 60 | else: 61 | yield reply["text"] 62 | break 63 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.0" 2 | 3 | services: 4 | app: 5 | build: ./ 6 | image: redquilt/chatsydney 7 | container_name: chatsydney 8 | restart: unless-stopped 9 | ports: 10 | - "65432:65432" -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import json 4 | import os 5 | import random 6 | import traceback 7 | import emoji 8 | import httpx 9 | import urllib.request 10 | import claude 11 | 12 | from EdgeGPT.EdgeGPT import Chatbot 13 | from aiohttp import web 14 | from aiohttp.client_exceptions import ClientConnectorError 15 | 16 | public_dir = '/public' 17 | 18 | async def sydney_process_message(user_message, context, _U, KievRPSSecAuth, SRCHHPGUSR, _RwBf, locale, imgid): 19 | chatbot = None 20 | # Set the maximum number of retries 21 | max_retries = 5 22 | for i in range(max_retries + 1): 23 | try: 24 | cookies = loaded_cookies 25 | if _U: 26 | cookies = list(filter(lambda d: d.get('name') != '_U', cookies)) + [{"name": "_U", "value": _U}] 27 | if KievRPSSecAuth: 28 | cookies = list(filter(lambda d: d.get('name') != 'KievRPSSecAuth', cookies)) + [{"name": "KievRPSSecAuth", "value": KievRPSSecAuth}] 29 | if SRCHHPGUSR: 30 | cookies = list(filter(lambda d: d.get('name') != 'SRCHHPGUSR', cookies)) + [{"name": "SRCHHPGUSR", "value": SRCHHPGUSR}] 31 | if _RwBf: 32 | cookies = list(filter(lambda d: d.get('name') != '_RwBf', cookies)) + [{"name": "_RwBf", "value": _RwBf}] 33 | chatbot = await Chatbot.create(cookies=cookies, proxy=args.proxy, imgid=imgid) 34 | async for _, response in chatbot.ask_stream(prompt=user_message, conversation_style="creative", raw=True, 35 | webpage_context=context, search_result=True, locale=locale): 36 | yield response 37 | break 38 | except Exception as e: 39 | if ( 40 | isinstance(e, TimeoutError) 41 | or isinstance(e, httpx.ConnectError) 42 | or isinstance(e, ClientConnectorError) 43 | or "Sorry, you need to login first to access this service." in str(e) 44 | or "ServiceClient failure for DeepLeo" in str(e) 45 | ) and i < max_retries: 46 | print("Retrying...", i + 1, "attempts.") 47 | # wait two second 48 | await asyncio.sleep(2) 49 | else: 50 | if i == max_retries: 51 | print("Failed after", max_retries, "attempts.") 52 | traceback.print_exc() 53 | yield {"type": "error", "error": str(e)} 54 | break 55 | finally: 56 | if chatbot: 57 | await chatbot.close() 58 | 59 | 60 | async def claude_process_message(context): 61 | try: 62 | async for reply in claude_chatbot.ask_stream(context): 63 | yield {"type": "reply", "text": emoji.emojize(reply, language='alias').strip()} 64 | yield {"type": "finished"} 65 | except: 66 | yield {"type": "error", "error": traceback.format_exc()} 67 | 68 | 69 | async def http_handler(request): 70 | file_path = request.path 71 | if file_path == "/": 72 | file_path = "/index.html" 73 | full_path = os.path.realpath('.' + public_dir + file_path) 74 | if not full_path.startswith(os.path.realpath('.' + public_dir)): 75 | raise web.HTTPForbidden() 76 | response = web.FileResponse(full_path) 77 | response.headers['Cache-Control'] = 'no-store' 78 | return response 79 | 80 | # upload image 81 | async def upload_image_handler(request): 82 | request_body = await request.json() 83 | if request_body['image']: 84 | upload_image = request_body['image'] 85 | HEADERS_IMG = { 86 | "accept": "*/*", 87 | "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", 88 | "sec-ch-ua": "\"Not.A/Brand\";v=\"8\", \"Chromium\";v=\"114\", \"Microsoft Edge\";v=\"114\"", 89 | "sec-ch-ua-arch": "\"x86\"", 90 | "sec-ch-ua-bitness": "\"64\"", 91 | "sec-ch-ua-full-version": "\"114.0.1823.67\"", 92 | "sec-ch-ua-full-version-list": "\"Not.A/Brand\";v=\"8.0.0.0\", \"Chromium\";v=\"114.0.5735.201\", \"Microsoft Edge\";v=\"114.0.1823.67\"", 93 | "sec-ch-ua-mobile": "?0", 94 | "sec-ch-ua-model": "\"\"", 95 | "sec-ch-ua-platform": "\"Windows\"", 96 | "sec-ch-ua-platform-version": "\"14.0.0\"", 97 | "sec-fetch-dest": "empty", 98 | "sec-fetch-mode": "cors", 99 | "sec-fetch-site": "same-origin", 100 | "sec-ms-gec-version": "1-114.0.1823.67", 101 | "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx&wlsso=0", 102 | "Referrer-Policy": "origin-when-cross-origin" 103 | } 104 | files={ 105 | 'knowledgeRequest':(None, '{"imageInfo":{},"knowledgeRequest":{"invokedSkills":["ImageById"],"subscriptionId":"Bing.Chat.Multimodal","invokedSkillsRequestData":{"enableFaceBlur":true},"convoData":{"convoid":"","convotone":"Creative"}}}'), 106 | 'imageBase64':(None, upload_image) 107 | } 108 | async with httpx.AsyncClient( 109 | proxies=args.proxy or None, 110 | timeout=30, 111 | headers={ 112 | **HEADERS_IMG, 113 | "x-forwarded-for": f"13.{random.randint(104, 107)}.{random.randint(0, 255)}.{random.randint(0, 255)}", 114 | }, 115 | ) as client: 116 | # Send POST request 117 | img_response = await client.post( 118 | url="https://www.bing.com/images/kblob", 119 | files=files 120 | ) 121 | if img_response.status_code != 200: 122 | img_response.request.read() 123 | print(f"Status code: {img_response.status_code}") 124 | print(img_response.request.stream._stream.decode("utf-8")) 125 | raise Exception("Image upload failed") 126 | return web.json_response(img_response.text) 127 | else: 128 | raise Exception("No image.") 129 | 130 | async def websocket_handler(request): 131 | ws = web.WebSocketResponse() 132 | await ws.prepare(request) 133 | 134 | async def monitor(): 135 | while True: 136 | if ws.closed: 137 | task.cancel() 138 | break 139 | await asyncio.sleep(0.1) 140 | 141 | async def main_process(): 142 | async for msg in ws: 143 | if msg.type == web.WSMsgType.TEXT: 144 | request = json.loads(msg.data) 145 | user_message = request['message'] 146 | context = request['context'] 147 | locale = request['locale'] 148 | _U = request.get('_U') 149 | KievRPSSecAuth = request.get('KievRPSSecAuth') 150 | SRCHHPGUSR = request.get('SRCHHPGUSR') 151 | _RwBf = request.get('_RwBf') 152 | if request.get('imgid'): 153 | imgid = json.loads(request.get('imgid')) 154 | else: 155 | imgid = None 156 | bot_type = request.get("botType", "Sydney") 157 | if bot_type == "Sydney": 158 | async for response in sydney_process_message(user_message, context, _U, KievRPSSecAuth, SRCHHPGUSR, _RwBf, locale=locale, imgid=imgid): 159 | await ws.send_json(response) 160 | elif bot_type == "Claude": 161 | async for response in claude_process_message(context): 162 | await ws.send_json(response) 163 | else: 164 | print(f"Unknown bot type: {bot_type}") 165 | 166 | task = asyncio.ensure_future(main_process()) 167 | monitor_task = asyncio.ensure_future(monitor()) 168 | done, pending = await asyncio.wait([task, monitor_task], return_when=asyncio.FIRST_COMPLETED) 169 | 170 | for task in pending: 171 | task.cancel() 172 | 173 | return ws 174 | 175 | 176 | async def main(host, port): 177 | app = web.Application() 178 | app.router.add_get('/ws/', websocket_handler) 179 | app.router.add_post('/upload_image/', upload_image_handler) 180 | app.router.add_get('/{tail:.*}', http_handler) 181 | 182 | runner = web.AppRunner(app) 183 | await runner.setup() 184 | site = web.TCPSite(runner, host, port) 185 | await site.start() 186 | print(f"Go to http://{host}:{port} to start chatting!") 187 | 188 | 189 | if __name__ == '__main__': 190 | parser = argparse.ArgumentParser() 191 | parser.add_argument("--host", "-H", help="host:port for the server", default="localhost:65432") 192 | proxies = urllib.request.getproxies() 193 | parser.add_argument("--proxy", "-p", help='proxy address like "http://localhost:7890"', default=proxies.get("http")) 194 | args = parser.parse_args() 195 | print(f"Proxy used: {args.proxy}") 196 | 197 | host, port = args.host.split(":") 198 | port = int(port) 199 | 200 | if os.path.isfile("cookies.json"): 201 | with open("cookies.json", 'r') as f: 202 | loaded_cookies = json.load(f) 203 | print("Loaded cookies.json") 204 | else: 205 | loaded_cookies = [] 206 | print("cookies.json not found") 207 | 208 | claude_chatbot = claude.Chatbot(proxy=args.proxy) 209 | 210 | loop = asyncio.get_event_loop() 211 | try: 212 | loop.run_until_complete(main(host, port)) 213 | loop.run_forever() 214 | except KeyboardInterrupt: 215 | pass 216 | finally: 217 | loop.close() 218 | -------------------------------------------------------------------------------- /public/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoraRoseous/ChatSydney-react/888ff3ef3ca38fb5d04339a9a82dd767e8ee3888/public/background.png -------------------------------------------------------------------------------- /public/dialog.css: -------------------------------------------------------------------------------- 1 | .modal { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | position: fixed; 6 | z-index: 1; 7 | left: 0; 8 | top: 0; 9 | width: 100%; 10 | height: 100%; 11 | overflow: auto; 12 | background-color: rgba(0, 0, 0, 0.4); 13 | } 14 | 15 | .modal-content { 16 | background-color: #fefefe; 17 | margin: auto; 18 | padding: 20px; 19 | border: 1px solid #888; 20 | width: 80%; 21 | } 22 | 23 | .close { 24 | color: #aaaaaa; 25 | float: right; 26 | font-size: 28px; 27 | font-weight: bold; 28 | } 29 | 30 | .close:hover, 31 | .close:focus { 32 | color: #000; 33 | text-decoration: none; 34 | cursor: pointer; 35 | } 36 | 37 | .input-field { 38 | width: 100%; 39 | padding: 12px 20px; 40 | margin: 8px 0; 41 | box-sizing: border-box; 42 | border: 2px solid #ccc; 43 | border-radius: 4px; 44 | } 45 | 46 | .large-textarea { 47 | width: 100%; 48 | height: 150px; 49 | padding: 12px 20px; 50 | box-sizing: border-box; 51 | border: 2px solid #ccc; 52 | border-radius: 4px; 53 | resize: vertical; 54 | font-family: "Microsoft YaHei", sans-serif; 55 | } 56 | 57 | .save-button { 58 | background-color: #4CAF50; 59 | color: white; 60 | padding: 15px 32px; 61 | text-align: center; 62 | text-decoration: none; 63 | display: inline-block; 64 | font-size: 16px; 65 | margin: 4px 2px; 66 | cursor: pointer; 67 | border: none; 68 | border-radius: 4px; 69 | } 70 | 71 | .error { 72 | color: red; 73 | } 74 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoraRoseous/ChatSydney-react/888ff3ef3ca38fb5d04339a9a82dd767e8ee3888/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ChatSydney 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 16 | 925 | 926 | 927 | -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "Microsoft YaHei", sans-serif; 3 | margin: 0; 4 | padding: 0; 5 | background-image: url("background.png"); 6 | background-size: cover; 7 | } 8 | 9 | .container { 10 | display: flex; 11 | flex-direction: column; 12 | margin: auto; 13 | max-width: 1184px; 14 | padding: 20px; 15 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); 16 | border-radius: 10px; 17 | } 18 | 19 | .heading { 20 | color: #444; 21 | font-size: 1.5em; 22 | margin-bottom: 2px; 23 | } 24 | 25 | .button-container { 26 | display: flex; 27 | justify-content: flex-end; 28 | flex-wrap: wrap; 29 | } 30 | 31 | .button { 32 | margin-left: 10px; 33 | padding: 5px 10px; 34 | border: none; 35 | border-radius: 5px; 36 | background-color: #007BFF; 37 | color: white; 38 | cursor: pointer; 39 | transition: background-color 0.3s; 40 | } 41 | 42 | .button.green { 43 | background-color: rgb(89, 217, 89); 44 | } 45 | 46 | .button:hover { 47 | background-color: #0056b3; 48 | } 49 | 50 | .button[disabled] { 51 | background-color: gray; 52 | } 53 | 54 | .messages { 55 | display: flex; 56 | flex-direction: column; 57 | padding: 10px; 58 | margin-bottom: 20px; 59 | border-radius: 5px; 60 | } 61 | 62 | .textarea { 63 | width: 100%; 64 | margin-bottom: 10px; 65 | border: 1px solid #ccc; 66 | border-radius: 5px; 67 | padding: 10px; 68 | box-sizing: border-box; 69 | font-family: "Microsoft YaHei", sans-serif; 70 | } 71 | 72 | .selector { 73 | margin-bottom: 10px; 74 | } 75 | 76 | .message { 77 | margin-bottom: 10px; 78 | padding: 10px; 79 | border-radius: 12px; 80 | box-shadow: 0 0.3px 0.9px rgba(0, 0, 0, 0.12), 0 1.6px 3.6px rgba(0, 0, 0, 0.16); 81 | font-size: 16px; 82 | width: fit-content; 83 | max-width: 768px; 84 | position: relative; 85 | } 86 | 87 | .user-message { 88 | color: white; 89 | background-image: linear-gradient(90deg, #904887 10.79%, #8B257E 87.08%); 90 | align-self: flex-end; 91 | } 92 | 93 | .assistant-message { 94 | background-color: rgba(255, 255, 255, 0.6); 95 | } 96 | 97 | .other-message { 98 | background-color: rgba(255, 255, 255, 0.3); 99 | align-self: flex-end; 100 | } 101 | 102 | .message * { 103 | margin-block: 0; 104 | } 105 | 106 | .add-button, .delete-button, .edit-button { 107 | box-shadow: 0 0.3px 0.9px rgba(0, 0, 0, 0.12), 0 1.6px 3.6px rgba(0, 0, 0, 0.16); 108 | position: absolute; 109 | top: -36px; 110 | background-color: white; 111 | color: white; 112 | border: none; 113 | border-radius: 8px; 114 | width: 36px; 115 | height: 36px; 116 | text-align: center; 117 | line-height: 36px; 118 | cursor: pointer; 119 | } 120 | 121 | .delete-button { 122 | right: 0; 123 | } 124 | 125 | .edit-button { 126 | right: 36px; 127 | } 128 | 129 | .add-button { 130 | right: 72px; 131 | } 132 | 133 | .add-button:hover, .delete-button:hover, .edit-button:hover { 134 | background-color: rgb(255, 255, 255, 0.06); 135 | } 136 | 137 | img[alt^="image"] { 138 | width: 206px; 139 | height: 206px; 140 | border: 6px solid transparent; 141 | border-radius: 15px; 142 | transition: transform 0.3s; 143 | } 144 | 145 | img[alt^="image"]:hover { 146 | transform: scale(1.1); 147 | } 148 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp 2 | EdgeGPT 3 | BingImageCreator 4 | certifi 5 | httpx 6 | prompt_toolkit 7 | requests 8 | rich 9 | aiofiles 10 | slack_sdk 11 | emoji --------------------------------------------------------------------------------