From 1da9d27a8d4b69862ef9b84b7b86f8034df5964f Mon Sep 17 00:00:00 2001 From: JeffreyChen Date: Wed, 2 Aug 2023 13:48:40 +0800 Subject: [PATCH] Use local IP not random IP (Fix timeout error & Eventloop error). (#634) * Use local IP not random IP Use local IP not random IP * Add pull_request_target Add pull_request_target * Add edited state to test Add edited state to test not only test on open pull request * Move aiohttp session to stream Move aiohttp session to stream * Avoid Runtime error * Refactor Refactor * Close aio session on stream Close aio session on stream * Not need to catch take IP exception Not need to catch take IP exception --- .github/workflows/tests.yml | 2 + .gitignore | 3 + src/EdgeGPT/chathub.py | 276 ++++++++++++++++++------------------ src/EdgeGPT/constants.py | 35 ++--- 4 files changed, 163 insertions(+), 153 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c5a0f9c1c..92108e1b3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -3,6 +3,8 @@ name: tests on: push: pull_request: + pull_request_target: + types: [ ready_for_review, review_requested, edited] jobs: diff --git a/.gitignore b/.gitignore index ddc776385..638748ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -131,3 +131,6 @@ cookies.json output/* README.md *.json + +# Pycharm +.idea/* diff --git a/src/EdgeGPT/chathub.py b/src/EdgeGPT/chathub.py index 08feebd92..af451c14a 100644 --- a/src/EdgeGPT/chathub.py +++ b/src/EdgeGPT/chathub.py @@ -4,6 +4,7 @@ import ssl import sys import aiohttp + from time import time from typing import Generator from typing import List @@ -29,11 +30,12 @@ class ChatHub: def __init__( - self, - conversation: Conversation, - proxy: str = None, - cookies: Union[List[dict], None] = None, + self, + conversation: Conversation, + proxy: str = None, + cookies: Union[List[dict], None] = None, ) -> None: + self.aio_session = None self.request: ChatHubRequest self.loop: bool self.task: asyncio.Task @@ -45,35 +47,30 @@ def __init__( self.cookies = cookies self.proxy: str = proxy proxy = ( - proxy - or os.environ.get("all_proxy") - or os.environ.get("ALL_PROXY") - or os.environ.get("https_proxy") - or os.environ.get("HTTPS_PROXY") - or None + proxy + or os.environ.get("all_proxy") + or os.environ.get("ALL_PROXY") + or os.environ.get("https_proxy") + or os.environ.get("HTTPS_PROXY") + or None ) if proxy is not None and proxy.startswith("socks5h://"): - proxy = "socks5://" + proxy[len("socks5h://") :] + proxy = "socks5://" + proxy[len("socks5h://"):] self.session = httpx.AsyncClient( proxies=proxy, timeout=900, headers=HEADERS_INIT_CONVER, ) - cookies = {} - if self.cookies is not None: - for cookie in self.cookies: - cookies[cookie["name"]] = cookie["value"] - self.aio_session = aiohttp.ClientSession(cookies=cookies) async def get_conversation( - self, - conversation_id: str = None, - conversation_signature: str = None, - client_id: str = None, + self, + conversation_id: str = None, + conversation_signature: str = None, + client_id: str = None, ) -> dict: conversation_id = conversation_id or self.request.conversation_id conversation_signature = ( - conversation_signature or self.request.conversation_signature + conversation_signature or self.request.conversation_signature ) client_id = client_id or self.request.client_id url = f"https://sydney.bing.com/sydney/GetConversation?conversationId={conversation_id}&source=cib&participantId={client_id}&conversationSignature={conversation_signature}&traceId={get_ran_hex()}" @@ -92,149 +89,155 @@ async def get_activity(self) -> dict: return response.json() async def ask_stream( - self, - prompt: str, - wss_link: str = None, - conversation_style: CONVERSATION_STYLE_TYPE = None, - raw: bool = False, - webpage_context: Union[str, None] = None, - search_result: bool = False, - locale: str = guess_locale(), + self, + prompt: str, + wss_link: str = None, + conversation_style: CONVERSATION_STYLE_TYPE = None, + raw: bool = False, + webpage_context: Union[str, None] = None, + search_result: bool = False, + locale: str = guess_locale(), ) -> Generator[bool, Union[dict, str], None]: """ """ - + cookies = {} + if self.cookies is not None: + for cookie in self.cookies: + cookies[cookie["name"]] = cookie["value"] + self.aio_session = aiohttp.ClientSession(cookies=cookies) # Check if websocket is closed - async with self.aio_session.ws_connect( + wss = await self.aio_session.ws_connect( wss_link or "wss://sydney.bing.com/sydney/ChatHub", ssl=ssl_context, headers=HEADERS, proxy=self.proxy, - ) as wss: - await self._initial_handshake(wss) - # Construct a ChatHub request - self.request.update( - prompt=prompt, - conversation_style=conversation_style, - webpage_context=webpage_context, - search_result=search_result, - locale=locale, - ) - # Send request - await wss.send_str(append_identifier(self.request.struct)) - draw = False - resp_txt = "" - result_text = "" - resp_txt_no_link = "" - retry_count = 5 - while not wss.closed: - msg = await wss.receive_str() - if not msg: - retry_count -= 1 - if retry_count == 0: - raise Exception("No response from server") - continue - if isinstance(msg, str): - objects = msg.split(DELIMITER) - else: + ) + await self._initial_handshake(wss) + # Construct a ChatHub request + self.request.update( + prompt=prompt, + conversation_style=conversation_style, + webpage_context=webpage_context, + search_result=search_result, + locale=locale, + ) + # Send request + await wss.send_str(append_identifier(self.request.struct)) + draw = False + resp_txt = "" + result_text = "" + resp_txt_no_link = "" + retry_count = 5 + while not wss.closed: + msg = await wss.receive_str() + if not msg: + retry_count -= 1 + if retry_count == 0: + raise Exception("No response from server") + continue + if isinstance(msg, str): + objects = msg.split(DELIMITER) + else: + continue + for obj in objects: + if int(time()) % 6 == 0: + await wss.send_str(append_identifier({"type": 6})) + if obj is None or not obj: continue - for obj in objects: - if int(time()) % 6 == 0: - await wss.send_str(append_identifier({"type": 6})) - if obj is None or not obj: - continue - response = json.loads(obj) - # print(response) - if response.get("type") == 1 and response["arguments"][0].get( + response = json.loads(obj) + # print(response) + if response.get("type") == 1 and response["arguments"][0].get( "messages", - ): - if not draw: - if ( + ): + if not draw: + if ( response["arguments"][0]["messages"][0].get( "messageType", ) == "GenerateContentQuery" - ): - async with ImageGenAsync( + ): + async with ImageGenAsync( all_cookies=self.cookies, - ) as image_generator: - images = await image_generator.get_images( - response["arguments"][0]["messages"][0]["text"], - ) - for i, image in enumerate(images): - resp_txt = f"{resp_txt}\n![image{i}]({image})" - draw = True - if ( + ) as image_generator: + images = await image_generator.get_images( + response["arguments"][0]["messages"][0]["text"], + ) + for i, image in enumerate(images): + resp_txt = f"{resp_txt}\n![image{i}]({image})" + draw = True + if ( ( - response["arguments"][0]["messages"][0][ - "contentOrigin" - ] - != "Apology" + response["arguments"][0]["messages"][0][ + "contentOrigin" + ] + != "Apology" ) and not draw and not raw - ): - resp_txt = result_text + response["arguments"][0][ - "messages" - ][0]["adaptiveCards"][0]["body"][0].get("text", "") - resp_txt_no_link = result_text + response["arguments"][ - 0 - ]["messages"][0].get("text", "") - if response["arguments"][0]["messages"][0].get( + ): + resp_txt = result_text + response["arguments"][0][ + "messages" + ][0]["adaptiveCards"][0]["body"][0].get("text", "") + resp_txt_no_link = result_text + response["arguments"][ + 0 + ]["messages"][0].get("text", "") + if response["arguments"][0]["messages"][0].get( "messageType", - ): - resp_txt = ( + ): + resp_txt = ( resp_txt + response["arguments"][0]["messages"][0][ "adaptiveCards" ][0]["body"][0]["inlines"][0].get("text") + "\n" - ) - result_text = ( + ) + result_text = ( result_text + response["arguments"][0]["messages"][0][ "adaptiveCards" ][0]["body"][0]["inlines"][0].get("text") + "\n" - ) - if not raw: - yield False, resp_txt + ) + if not raw: + yield False, resp_txt - elif response.get("type") == 2: - if response["item"]["result"].get("error"): - await self.close() - raise Exception( - f"{response['item']['result']['value']}: {response['item']['result']['message']}", - ) - if draw: - cache = response["item"]["messages"][1]["adaptiveCards"][0][ - "body" - ][0]["text"] - response["item"]["messages"][1]["adaptiveCards"][0]["body"][ - 0 - ]["text"] = (cache + resp_txt) - if ( + elif response.get("type") == 2: + if response["item"]["result"].get("error"): + await self.close() + raise Exception( + f"{response['item']['result']['value']}: {response['item']['result']['message']}", + ) + if draw: + cache = response["item"]["messages"][1]["adaptiveCards"][0][ + "body" + ][0]["text"] + response["item"]["messages"][1]["adaptiveCards"][0]["body"][ + 0 + ]["text"] = (cache + resp_txt) + if ( response["item"]["messages"][-1]["contentOrigin"] == "Apology" and resp_txt - ): - response["item"]["messages"][-1]["text"] = resp_txt_no_link - response["item"]["messages"][-1]["adaptiveCards"][0][ - "body" - ][0]["text"] = resp_txt - print( - "Preserved the message from being deleted", - file=sys.stderr, - ) - await wss.close() - yield True, response - return - if response.get("type") != 2: - if response.get("type") == 6: - await wss.send_str(append_identifier({"type": 6})) - elif response.get("type") == 7: - await wss.send_str(append_identifier({"type": 7})) - elif raw: - yield False, response + ): + response["item"]["messages"][-1]["text"] = resp_txt_no_link + response["item"]["messages"][-1]["adaptiveCards"][0][ + "body" + ][0]["text"] = resp_txt + print( + "Preserved the message from being deleted", + file=sys.stderr, + ) + await wss.close() + if not self.aio_session.closed: + await self.aio_session.close() + yield True, response + return + if response.get("type") != 2: + if response.get("type") == 6: + await wss.send_str(append_identifier({"type": 6})) + elif response.get("type") == 7: + await wss.send_str(append_identifier({"type": 7})) + elif raw: + yield False, response async def _initial_handshake(self, wss) -> None: await wss.send_str(append_identifier({"protocol": "json", "version": 1})) @@ -242,14 +245,14 @@ async def _initial_handshake(self, wss) -> None: await wss.send_str(append_identifier({"type": 6})) async def delete_conversation( - self, - conversation_id: str = None, - conversation_signature: str = None, - client_id: str = None, + self, + conversation_id: str = None, + conversation_signature: str = None, + client_id: str = None, ) -> None: conversation_id = conversation_id or self.request.conversation_id conversation_signature = ( - conversation_signature or self.request.conversation_signature + conversation_signature or self.request.conversation_signature ) client_id = client_id or self.request.client_id url = "https://sydney.bing.com/sydney/DeleteSingleConversation" @@ -266,4 +269,3 @@ async def delete_conversation( async def close(self) -> None: await self.session.aclose() - await self.aio_session.close() diff --git a/src/EdgeGPT/constants.py b/src/EdgeGPT/constants.py index cd1dc305e..58933b216 100644 --- a/src/EdgeGPT/constants.py +++ b/src/EdgeGPT/constants.py @@ -1,21 +1,23 @@ -import random import uuid +import socket -DELIMITER = "\x1e" - +take_ip_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +take_ip_socket.connect(("8.8.8.8", 80)) +FORWARDED_IP: str = take_ip_socket.getsockname()[0] +take_ip_socket.close() -# Generate random IP between range 13.104.0.0/14 -FORWARDED_IP = f"1.0.0.{random.randint(0, 255)}" +DELIMITER = "\x1e" HEADERS = { "accept": "application/json", - "accept-language": "en-US,en;q=0.9", + "accept-language": "en-US;q=0.9", + "accept-encoding": "gzip, deflate, br, zsdch", "content-type": "application/json", - "sec-ch-ua": '"Not_A Brand";v="99", Microsoft Edge";v="110", "Chromium";v="110"', + "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"', "sec-ch-ua-arch": '"x86"', "sec-ch-ua-bitness": '"64"', - "sec-ch-ua-full-version": '"109.0.1518.78"', - "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"', + "sec-ch-ua-full-version": '"115.0.1901.188"', + "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"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-model": "", "sec-ch-ua-platform": '"Windows"', @@ -23,9 +25,10 @@ "sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", + "sec-ms-gec-version": "1-115.0.1901.188", "x-ms-client-request-id": str(uuid.uuid4()), - "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/Win32", - "Referer": "https://www.bing.com/search?q=Bing+AI&showconv=1&FORM=hpcodx", + "x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.3 OS/Windows", + "Referer": "https://www.bing.com/search?", "Referrer-Policy": "origin-when-cross-origin", "x-forwarded-for": FORWARDED_IP, } @@ -33,19 +36,19 @@ HEADERS_INIT_CONVER = { "authority": "www.bing.com", "accept": "application/json", - "accept-language": "en-US,en;q=0.9", + "accept-language": "en-US;q=0.9", "cache-control": "max-age=0", - "sec-ch-ua": '"Chromium";v="110", "Not A(Brand";v="24", "Microsoft Edge";v="110"', + "sec-ch-ua": '"Not/A)Brand";v="99", "Microsoft Edge";v="115", "Chromium";v="115"', "sec-ch-ua-arch": '"x86"', "sec-ch-ua-bitness": '"64"', - "sec-ch-ua-full-version": '"110.0.1587.69"', - "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"', + "sec-ch-ua-full-version": '"115.0.1901.188"', + "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"', "sec-ch-ua-mobile": "?0", "sec-ch-ua-model": '""', "sec-ch-ua-platform": '"Windows"', "sec-ch-ua-platform-version": '"15.0.0"', "upgrade-insecure-requests": "1", - "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", + "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", "x-edge-shopping-flag": "1", "x-forwarded-for": FORWARDED_IP, }