diff options
author | H Lohaus <hlohaus@users.noreply.github.com> | 2024-03-16 18:22:26 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-16 18:22:26 +0100 |
commit | fb2061da48525edab9cd993205bb5e30c386aa1a (patch) | |
tree | 1e740bd6955dfd27b9a4d773df07234ed9e5c75e /g4f/Provider | |
parent | Merge pull request #1694 from ComRSMaster/main (diff) | |
parent | Add conversation support for Bing (diff) | |
download | gpt4free-0.2.5.0.tar gpt4free-0.2.5.0.tar.gz gpt4free-0.2.5.0.tar.bz2 gpt4free-0.2.5.0.tar.lz gpt4free-0.2.5.0.tar.xz gpt4free-0.2.5.0.tar.zst gpt4free-0.2.5.0.zip |
Diffstat (limited to 'g4f/Provider')
-rw-r--r-- | g4f/Provider/Bing.py | 171 | ||||
-rw-r--r-- | g4f/Provider/You.py | 48 | ||||
-rw-r--r-- | g4f/Provider/bing/conversation.py | 7 | ||||
-rw-r--r-- | g4f/Provider/needs_auth/OpenaiChat.py | 164 |
4 files changed, 261 insertions, 129 deletions
diff --git a/g4f/Provider/Bing.py b/g4f/Provider/Bing.py index 786fec49..69c32775 100644 --- a/g4f/Provider/Bing.py +++ b/g4f/Provider/Bing.py @@ -12,7 +12,7 @@ from aiohttp import ClientSession, ClientTimeout, BaseConnector, WSMsgType from ..typing import AsyncResult, Messages, ImageType, Cookies from ..image import ImageRequest from ..errors import ResponseStatusError -from .base_provider import AsyncGeneratorProvider +from .base_provider import AsyncGeneratorProvider, ProviderModelMixin from .helper import get_connector, get_random_hex from .bing.upload_image import upload_image from .bing.conversation import Conversation, create_conversation, delete_conversation @@ -26,8 +26,9 @@ class Tones: creative = "Creative" balanced = "Balanced" precise = "Precise" + copilot = "Balanced" -class Bing(AsyncGeneratorProvider): +class Bing(AsyncGeneratorProvider, ProviderModelMixin): """ Bing provider for generating responses using the Bing API. """ @@ -35,18 +36,22 @@ class Bing(AsyncGeneratorProvider): working = True supports_message_history = True supports_gpt_4 = True + default_model = "balanced" + models = [key for key in Tones.__dict__ if not key.startswith("__")] - @staticmethod + @classmethod def create_async_generator( + cls, model: str, messages: Messages, proxy: str = None, timeout: int = 900, cookies: Cookies = None, connector: BaseConnector = None, - tone: str = Tones.balanced, + tone: str = None, image: ImageType = None, web_search: bool = False, + context: str = None, **kwargs ) -> AsyncResult: """ @@ -62,13 +67,12 @@ class Bing(AsyncGeneratorProvider): :param web_search: Flag to enable or disable web search. :return: An asynchronous result object. """ - if len(messages) < 2: - prompt = messages[0]["content"] - context = None - else: - prompt = messages[-1]["content"] - context = create_context(messages[:-1]) - + prompt = messages[-1]["content"] + if context is None: + context = create_context(messages[:-1]) if len(messages) > 1 else None + if tone is None: + tone = tone if model.startswith("gpt-4") else model + tone = cls.get_model("" if tone is None else tone.lower()) gpt4_turbo = True if model.startswith("gpt-4-turbo") else False return stream_generate( @@ -86,7 +90,9 @@ def create_context(messages: Messages) -> str: :return: A string representing the context created from the messages. """ return "".join( - f"[{message['role']}]" + ("(#message)" if message['role'] != "system" else "(#additional_instructions)") + f"\n{message['content']}" + f"[{message['role']}]" + ("(#message)" + if message['role'] != "system" + else "(#additional_instructions)") + f"\n{message['content']}" for message in messages ) + "\n\n" @@ -122,7 +128,7 @@ class Defaults: "ActionRequest","Chat", "ConfirmationCard", "Context", "InternalSearchQuery", #"InternalSearchResult", - "Disengaged", #"InternalLoaderMessage", + #"Disengaged", "InternalLoaderMessage", "Progress", "RenderCardRequest", "RenderContentRequest", "AdsQuery", "SemanticSerp", "GenerateContentQuery", @@ -131,53 +137,93 @@ class Defaults: ] sliceIds = { - "Balanced": [ + "balanced": [ "supllmnfe","archnewtf", "stpstream", "stpsig", "vnextvoicecf", "scmcbase", "cmcpupsalltf", "sydtransctrl", "thdnsrch", "220dcl1s0", "0215wcrwips0", "0305hrthrots0", "0130gpt4t", "bingfc", "0225unsticky1", "0228scss0", "defquerycf", "defcontrol", "3022tphpv" ], - "Creative": [ + "creative": [ "bgstream", "fltltst2c", "stpstream", "stpsig", "vnextvoicecf", "cmcpupsalltf", "sydtransctrl", "0301techgnd", "220dcl1bt15", "0215wcrwip", "0305hrthrot", "0130gpt4t", "bingfccf", "0225unsticky1", "0228scss0", "3022tpvs0" ], - "Precise": [ + "precise": [ "bgstream", "fltltst2c", "stpstream", "stpsig", "vnextvoicecf", "cmcpupsalltf", "sydtransctrl", "0301techgnd", "220dcl1bt15", "0215wcrwip", "0305hrthrot", "0130gpt4t", "bingfccf", "0225unsticky1", "0228scss0", "defquerycf", "3022tpvs0" ], + "copilot": [] } optionsSets = { - "Balanced": [ - "nlu_direct_response_filter", "deepleo", - "disable_emoji_spoken_text", "responsible_ai_policy_235", - "enablemm", "dv3sugg", "autosave", - "iyxapbing", "iycapbing", - "galileo", "saharagenconv5", "gldcl1p", - "gpt4tmncnp" - ], - "Creative": [ + "balanced": { + "default": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", "autosave", + "iyxapbing", "iycapbing", + "galileo", "saharagenconv5", "gldcl1p", + "gpt4tmncnp" + ], + "nosearch": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", "autosave", + "iyxapbing", "iycapbing", + "galileo", "sunoupsell", "base64filter", "uprv4p1upd", + "hourthrot", "noctprf", "gndlogcf", "nosearchall" + ] + }, + "creative": { + "default": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", + "iyxapbing", "iycapbing", + "h3imaginative", "techinstgnd", "hourthrot", "clgalileo", "gencontentv3", + "gpt4tmncnp" + ], + "nosearch": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", "autosave", + "iyxapbing", "iycapbing", + "h3imaginative", "sunoupsell", "base64filter", "uprv4p1upd", + "hourthrot", "noctprf", "gndlogcf", "nosearchall", + "clgalileo", "nocache", "up4rp14bstcst" + ] + }, + "precise": { + "default": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", + "iyxapbing", "iycapbing", + "h3precise", "techinstgnd", "hourthrot", "techinstgnd", "hourthrot", + "clgalileo", "gencontentv3" + ], + "nosearch": [ + "nlu_direct_response_filter", "deepleo", + "disable_emoji_spoken_text", "responsible_ai_policy_235", + "enablemm", "dv3sugg", "autosave", + "iyxapbing", "iycapbing", + "h3precise", "sunoupsell", "base64filter", "uprv4p1upd", + "hourthrot", "noctprf", "gndlogcf", "nosearchall", + "clgalileo", "nocache", "up4rp14bstcst" + ] + }, + "copilot": [ "nlu_direct_response_filter", "deepleo", "disable_emoji_spoken_text", "responsible_ai_policy_235", "enablemm", "dv3sugg", "iyxapbing", "iycapbing", - "h3imaginative", "techinstgnd", "hourthrot", "clgalileo", "gencontentv3", - "gpt4tmncnp" - ], - "Precise": [ - "nlu_direct_response_filter", "deepleo", - "disable_emoji_spoken_text", "responsible_ai_policy_235", - "enablemm", "dv3sugg", - "iyxapbing", "iycapbing", - "h3precise", "techinstgnd", "hourthrot", "techinstgnd", "hourthrot", - "clgalileo", "gencontentv3" + "h3precise", "clgalileo", "gencontentv3", "prjupy" ], } @@ -232,7 +278,8 @@ def create_message( context: str = None, image_request: ImageRequest = None, web_search: bool = False, - gpt4_turbo: bool = False + gpt4_turbo: bool = False, + new_conversation: bool = True ) -> str: """ Creates a message for the Bing API with specified parameters. @@ -247,7 +294,12 @@ def create_message( :return: A formatted string message for the Bing API. """ - options_sets = [] + options_sets = Defaults.optionsSets[tone] + if not web_search and "nosearch" in options_sets: + options_sets = options_sets["nosearch"] + elif "default" in options_sets: + options_sets = options_sets["default"] + options_sets = options_sets.copy() if gpt4_turbo: options_sets.append("dlgpt4t") @@ -255,16 +307,16 @@ def create_message( struct = { "arguments":[{ "source": "cib", - "optionsSets": [*Defaults.optionsSets[tone], *options_sets], + "optionsSets": options_sets, "allowedMessageTypes": Defaults.allowedMessageTypes, "sliceIds": Defaults.sliceIds[tone], "verbosity": "verbose", - "scenario": "SERP", + "scenario": "CopilotMicrosoftCom", # "SERP", "plugins": [{"id": "c310c353-b9f0-4d76-ab0d-1dd5e979cf68", "category": 1}] if web_search else [], "traceId": get_random_hex(40), "conversationHistoryOptionsSets": ["autosave","savemem","uprofupd","uprofgen"], "gptId": "copilot", - "isStartOfSession": True, + "isStartOfSession": new_conversation, "requestId": request_id, "message":{ **Defaults.location, @@ -277,8 +329,7 @@ def create_message( "requestId": request_id, "messageId": request_id }, - "tone": tone, - "extraExtensionParameters": {"gpt-creator-persona": {"personaId": "copilot"}}, + "tone": getattr(Tones, tone), "spokenTextMode": "None", "conversationId": conversation.conversationId, "participant": {"id": conversation.clientId} @@ -298,7 +349,7 @@ def create_message( struct['arguments'][0]['previousMessages'] = [{ "author": "user", "description": context, - "contextType": "WebPage", + "contextType": "ClientApp", "messageType": "Context", "messageId": "discover-web--page-ping-mriduna-----" }] @@ -317,8 +368,9 @@ async def stream_generate( gpt4_turbo: bool = False, timeout: int = 900, conversation: Conversation = None, + return_conversation: bool = False, raise_apology: bool = False, - max_retries: int = 5, + max_retries: int = None, sleep_retry: int = 15, **kwargs ): @@ -336,13 +388,20 @@ async def stream_generate( :return: An asynchronous generator yielding responses. """ headers = create_headers(cookies) + new_conversation = conversation is None + max_retries = (5 if new_conversation else 0) if max_retries is None else max_retries async with ClientSession( timeout=ClientTimeout(total=timeout), connector=connector ) as session: - while conversation is None: + first = True + while first or conversation is None: + first = False do_read = True try: - conversation = await create_conversation(session, headers) + if conversation is None: + conversation = await create_conversation(session, headers, tone) + if return_conversation: + yield conversation except ResponseStatusError as e: max_retries -= 1 if max_retries < 1: @@ -353,8 +412,10 @@ async def stream_generate( await asyncio.sleep(sleep_retry) continue - image_request = await upload_image(session, image, tone, headers) if image else None + image_request = await upload_image(session, image, getattr(Tones, tone), headers) if image else None async with session.ws_connect( + 'wss://s.copilot.microsoft.com/sydney/ChatHub' + if tone == "copilot" else 'wss://sydney.bing.com/sydney/ChatHub', autoping=False, params={'sec_access_token': conversation.conversationSignature}, @@ -363,7 +424,12 @@ async def stream_generate( await wss.send_str(format_message({'protocol': 'json', 'version': 1})) await wss.send_str(format_message({"type": 6})) await wss.receive(timeout=timeout) - await wss.send_str(create_message(conversation, prompt, tone, context, image_request, web_search, gpt4_turbo)) + await wss.send_str(create_message( + conversation, prompt, tone, + context if new_conversation else None, + image_request, web_search, gpt4_turbo, + new_conversation + )) response_txt = '' returned_text = '' message_id = None @@ -399,14 +465,15 @@ async def stream_generate( image_client = BingCreateImages(cookies, proxy) image_response = await image_client.create_async(prompt) except Exception as e: - response_txt += f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}" - do_read = False + if debug.logging: + print(f"Bing: Failed to create images: {e}") + image_response = f"\nhttps://www.bing.com/images/create?q={parse.quote(prompt)}" if response_txt.startswith(returned_text): new = response_txt[len(returned_text):] - if new != "\n": + if new not in ("", "\n"): yield new returned_text = response_txt - if image_response: + if image_response is not None: yield image_response elif response.get('type') == 2: result = response['item']['result'] diff --git a/g4f/Provider/You.py b/g4f/Provider/You.py index 85b60452..9b040367 100644 --- a/g4f/Provider/You.py +++ b/g4f/Provider/You.py @@ -4,14 +4,18 @@ import re import json import base64 import uuid -from asyncio import get_running_loop -from aiohttp import ClientSession, FormData, BaseConnector, CookieJar +try: + from ..requests.curl_cffi import FormData + has_curl_cffi = True +except ImportError: + has_curl_cffi = False from ..typing import AsyncResult, Messages, ImageType, Cookies from .base_provider import AsyncGeneratorProvider, ProviderModelMixin -from .helper import format_prompt, get_connector +from .helper import format_prompt from ..image import to_bytes, ImageResponse -from ..requests import WebDriver, raise_for_status, get_args_from_browser +from ..requests import StreamSession, raise_for_status +from ..errors import MissingRequirementsError class You(AsyncGeneratorProvider, ProviderModelMixin): url = "https://you.com" @@ -33,8 +37,6 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): model_aliases = { "claude-v2": "claude-2" } - _args: dict = None - _cookie_jar: CookieJar = None _cookies = None _cookies_used = 0 @@ -45,19 +47,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): messages: Messages, image: ImageType = None, image_name: str = None, - connector: BaseConnector = None, - webdriver: WebDriver = None, proxy: str = None, chat_mode: str = "default", **kwargs, ) -> AsyncResult: - if cls._args is None: - cls._args = get_args_from_browser(cls.url, webdriver, proxy) - cls._cookie_jar = CookieJar(loop=get_running_loop()) - else: - if "cookies" in cls._args: - del cls._args["cookies"] - cls._cookie_jar._loop = get_running_loop() + if not has_curl_cffi: + raise MissingRequirementsError('Install "curl_cffi" package') if image is not None: chat_mode = "agent" elif not model or model == cls.default_model: @@ -67,10 +62,9 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): else: chat_mode = "custom" model = cls.get_model(model) - async with ClientSession( - connector=get_connector(connector, proxy), - cookie_jar=cls._cookie_jar, - **cls._args + async with StreamSession( + proxy=proxy, + impersonate="chrome" ) as session: cookies = await cls.get_cookies(session) if chat_mode != "default" else None upload = json.dumps([await cls.upload_file(session, cookies, to_bytes(image), image_name)]) if image else "" @@ -82,8 +76,8 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): # and idx < len(questions) # ] headers = { - "accept": "text/event-stream", - "referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", + "Accept": "text/event-stream", + "Referer": f"{cls.url}/search?fromSearchBar=true&tbm=youchat", } data = { "userFiles": upload, @@ -106,12 +100,12 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): cookies=cookies ) as response: await raise_for_status(response) - async for line in response.content: + async for line in response.iter_lines(): if line.startswith(b'event: '): - event = line[7:-1].decode() + event = line[7:].decode() elif line.startswith(b'data: '): if event in ["youChatUpdate", "youChatToken"]: - data = json.loads(line[6:-1]) + data = json.loads(line[6:]) if event == "youChatToken" and event in data: yield data[event] elif event == "youChatUpdate" and "t" in data: @@ -122,7 +116,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): yield data["t"] @classmethod - async def upload_file(cls, client: ClientSession, cookies: Cookies, file: bytes, filename: str = None) -> dict: + async def upload_file(cls, client: StreamSession, cookies: Cookies, file: bytes, filename: str = None) -> dict: async with client.get( f"{cls.url}/api/get_nonce", cookies=cookies, @@ -146,7 +140,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): return result @classmethod - async def get_cookies(cls, client: ClientSession) -> Cookies: + async def get_cookies(cls, client: StreamSession) -> Cookies: if not cls._cookies or cls._cookies_used >= 5: cls._cookies = await cls.create_cookies(client) cls._cookies_used = 0 @@ -173,7 +167,7 @@ class You(AsyncGeneratorProvider, ProviderModelMixin): return f"Basic {auth}" @classmethod - async def create_cookies(cls, client: ClientSession) -> Cookies: + async def create_cookies(cls, client: StreamSession) -> Cookies: user_uuid = str(uuid.uuid4()) async with client.post( "https://web.stytch.com/sdk/v1/passwords", diff --git a/g4f/Provider/bing/conversation.py b/g4f/Provider/bing/conversation.py index da842808..886efa68 100644 --- a/g4f/Provider/bing/conversation.py +++ b/g4f/Provider/bing/conversation.py @@ -20,7 +20,7 @@ class Conversation: self.clientId = clientId self.conversationSignature = conversationSignature -async def create_conversation(session: ClientSession, headers: dict) -> Conversation: +async def create_conversation(session: ClientSession, headers: dict, tone: str) -> Conversation: """ Create a new conversation asynchronously. @@ -31,7 +31,10 @@ async def create_conversation(session: ClientSession, headers: dict) -> Conversa Returns: Conversation: An instance representing the created conversation. """ - url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1626.1" + if tone == "copilot": + url = "https://copilot.microsoft.com/turing/conversation/create?bundleVersion=1.1634.3-nodesign2" + else: + url = "https://www.bing.com/turing/conversation/create?bundleVersion=1.1626.1" async with session.get(url, headers=headers) as response: await raise_for_status(response, "Failed to create conversation") data = await response.json() diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 3d19e003..6601f500 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -15,6 +15,12 @@ except ImportError: has_arkose_generator = False try: + import webview + has_webview = True +except ImportError: + has_webview = False + +try: from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -25,10 +31,10 @@ from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..helper import get_cookies from ...webdriver import get_browser from ...typing import AsyncResult, Messages, Cookies, ImageType, Union, AsyncIterator -from ...requests import get_args_from_browser +from ...requests import get_args_from_browser, raise_for_status from ...requests.aiohttp import StreamSession from ...image import to_image, to_bytes, ImageResponse, ImageRequest -from ...errors import MissingRequirementsError, MissingAuthError +from ...errors import MissingRequirementsError, MissingAuthError, ProviderNotWorkingError from ... import debug class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): @@ -134,7 +140,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): } # Post the image data to the service and get the image data async with session.post(f"{cls.url}/backend-api/files", json=data, headers=headers) as response: - response.raise_for_status() + cls._update_request_args() + await raise_for_status(response) image_data = { **data, **await response.json(), @@ -152,14 +159,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): "x-ms-blob-type": "BlockBlob" } ) as response: - response.raise_for_status() + await raise_for_status(response) # Post the file ID to the service and get the download URL async with session.post( f"{cls.url}/backend-api/files/{image_data['file_id']}/uploaded", json={}, headers=headers ) as response: - response.raise_for_status() + cls._update_request_args(session) + await raise_for_status(response) image_data["download_url"] = (await response.json())["download_url"] return ImageRequest(image_data) @@ -178,7 +186,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): if not cls.default_model: async with session.get(f"{cls.url}/backend-api/models", headers=headers) as response: cls._update_request_args(session) - response.raise_for_status() + await raise_for_status(response) data = await response.json() if "categories" in data: cls.default_model = data["categories"][-1]["default_model"] @@ -261,7 +269,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): file_id = first_part["asset_pointer"].split("file-service://", 1)[1] try: async with session.get(f"{cls.url}/backend-api/files/{file_id}/download", headers=headers) as response: - response.raise_for_status() + cls._update_request_args(session) + await raise_for_status(response) download_url = (await response.json())["download_url"] return ImageResponse(download_url, prompt) except Exception as e: @@ -288,6 +297,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): json={"is_visible": False}, headers=headers ) as response: + cls._update_request_args(session) ... @classmethod @@ -337,31 +347,32 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): if parent_id is None: parent_id = str(uuid.uuid4()) - # Read api_key from arguments - api_key = kwargs["access_token"] if "access_token" in kwargs else api_key - async with StreamSession( proxies={"https": proxy}, impersonate="chrome", timeout=timeout ) as session: - # Read api_key and cookies from cache / browser config + api_key = kwargs["access_token"] if "access_token" in kwargs else api_key if cls._headers is None or cls._expires is None or time.time() > cls._expires: - if api_key is None: - # Read api_key from cookies + if cls._headers is None: cookies = get_cookies("chat.openai.com", False) if cookies is None else cookies api_key = cookies["access_token"] if "access_token" in cookies else api_key - cls._create_request_args(cookies) + if api_key is None: + try: + await cls.webview_access_token() if has_webview else None + except Exception as e: + if debug.logging: + print(f"Use webview failed: {e}") else: api_key = cls._api_key if api_key is None else api_key - # Read api_key with session cookies - #if api_key is None and cookies: - # api_key = await cls.fetch_access_token(session, cls._headers) - # Load default model - if cls.default_model is None and api_key is not None: + + if api_key is not None: + cls._create_request_args(cookies) + cls._set_api_key(api_key) + + if cls.default_model is None and cls._headers is not None: try: if not model: - cls._set_api_key(api_key) cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers)) else: cls.default_model = cls.get_model(model) @@ -369,8 +380,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): if debug.logging: print("OpenaiChat: Load default_model failed") print(f"{e.__class__.__name__}: {e}") - # Browse api_key and default model - if api_key is None or cls.default_model is None: + if cls.default_model is None: login_url = os.environ.get("G4F_LOGIN_URL") if login_url: yield f"Please login: [ChatGPT]({login_url})\n\n" @@ -379,20 +389,21 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): except MissingRequirementsError: raise MissingAuthError(f'Missing "access_token". Add a "api_key" please') cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers)) - else: - cls._set_api_key(api_key) async with session.post( f"{cls.url}/backend-api/sentinel/chat-requirements", json={"conversation_mode_kind": "primary_assistant"}, headers=cls._headers ) as response: - response.raise_for_status() + cls._update_request_args(session) + await raise_for_status(response) data = await response.json() + blob = data["arkose"]["dx"] need_arkose = data["arkose"]["required"] chat_token = data["token"] if need_arkose and not has_arkose_generator: + raise ProviderNotWorkingError("OpenAI Plus Subscriber are not working") raise MissingRequirementsError('Install "py-arkose-generator" package') try: @@ -407,6 +418,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): while fields.finish_reason is None: conversation_id = conversation_id if fields.conversation_id is None else fields.conversation_id parent_id = parent_id if fields.message_id is None else fields.message_id + websocket_request_id = str(uuid.uuid4()) data = { "action": action, "conversation_mode": {"kind": "primary_assistant"}, @@ -416,25 +428,29 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): "parent_message_id": parent_id, "model": model, "history_and_training_disabled": history_disabled and not auto_continue, + "websocket_request_id": websocket_request_id } if action != "continue": messages = messages if conversation_id is None else [messages[-1]] - data["messages"] = cls.create_messages(messages, image_request) + data["messages"] = cls.create_messages(messages, image_request) + headers = { + "Accept": "text/event-stream", + "OpenAI-Sentinel-Chat-Requirements-Token": chat_token, + **cls._headers + } + if need_arkose: + raise ProviderNotWorkingError("OpenAI Plus Subscriber are not working") + headers["OpenAI-Sentinel-Arkose-Token"] = await cls.get_arkose_token(session, cls._headers, blob) + headers["OpenAI-Sentinel-Chat-Requirements-Token"] = chat_token async with session.post( f"{cls.url}/backend-api/conversation", json=data, - headers={ - "Accept": "text/event-stream", - **({"OpenAI-Sentinel-Arkose-Token": await cls.get_arkose_token(session)} if need_arkose else {}), - "OpenAI-Sentinel-Chat-Requirements-Token": chat_token, - **cls._headers - } + headers=headers ) as response: cls._update_request_args(session) - if not response.ok: - raise RuntimeError(f"Response {response.status}: {await response.text()}") - async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, fields): + await raise_for_status(response) + async for chunk in cls.iter_messages_chunk(response.iter_lines(), session, fields, websocket_request_id): if response_fields: response_fields = False yield fields @@ -447,21 +463,35 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): await cls.delete_conversation(session, cls._headers, fields.conversation_id) @staticmethod - async def iter_messages_ws(ws: ClientWebSocketResponse, conversation_id: str) -> AsyncIterator: + async def iter_messages_ws(ws: ClientWebSocketResponse, conversation_id: str, is_curl: bool) -> AsyncIterator: while True: - message = await ws.receive_json() + if is_curl: + message = json.loads(ws.recv()[0]) + else: + message = await ws.receive_json() if message["conversation_id"] == conversation_id: yield base64.b64decode(message["body"]) @classmethod - async def iter_messages_chunk(cls, messages: AsyncIterator, session: StreamSession, fields: ResponseFields) -> AsyncIterator: + async def iter_messages_chunk( + cls, + messages: AsyncIterator, + session: StreamSession, + fields: ResponseFields + ) -> AsyncIterator: last_message: int = 0 async for message in messages: if message.startswith(b'{"wss_url":'): message = json.loads(message) - async with session.ws_connect(message["wss_url"]) as ws: - async for chunk in cls.iter_messages_chunk(cls.iter_messages_ws(ws, message["conversation_id"]), session, fields): + ws = await session.ws_connect(message["wss_url"]) + try: + async for chunk in cls.iter_messages_chunk( + cls.iter_messages_ws(ws, message["conversation_id"], hasattr(ws, "recv")), + session, fields + ): yield chunk + finally: + await ws.aclose() break async for chunk in cls.iter_messages_line(session, message, fields): if fields.finish_reason is not None: @@ -514,6 +544,43 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): fields.finish_reason = line["message"]["metadata"]["finish_details"]["type"] @classmethod + async def webview_access_token(cls) -> str: + window = webview.create_window("OpenAI Chat", cls.url) + await asyncio.sleep(3) + prompt_input = None + while not prompt_input: + try: + await asyncio.sleep(1) + prompt_input = window.dom.get_element("#prompt-textarea") + except: + ... + window.evaluate_js(""" +this._fetch = this.fetch; +this.fetch = async (url, options) => { + const response = await this._fetch(url, options); + if (url == "https://chat.openai.com/backend-api/conversation") { + this._headers = options.headers; + return response; + } + return response; +}; +""") + window.evaluate_js(""" + document.querySelector('.from-token-main-surface-secondary').click(); + """) + headers = None + while headers is None: + headers = window.evaluate_js("this._headers") + await asyncio.sleep(1) + headers["User-Agent"] = window.evaluate_js("this.navigator.userAgent") + cookies = [list(*cookie.items()) for cookie in window.get_cookies()] + window.destroy() + cls._cookies = dict([(name, cookie.value) for name, cookie in cookies]) + cls._headers = headers + cls._expires = int(time.time()) + 60 * 60 * 4 + cls._update_cookie_header() + + @classmethod def browse_access_token(cls, proxy: str = None, timeout: int = 1200) -> None: """ Browse to obtain an access token. @@ -542,10 +609,10 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): cls._update_cookie_header() cls._set_api_key(access_token) finally: - driver.close() + driver.close() @classmethod - async def get_arkose_token(cls, session: StreamSession) -> str: + async def get_arkose_token(cls, session: StreamSession, headers: dict, blob: str) -> str: """ Obtain an Arkose token for the session. @@ -559,16 +626,15 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): RuntimeError: If unable to retrieve the token. """ config = { - "pkey": "3D86FBBA-9D22-402A-B512-3420086BA6CC", + "pkey": "35536E1E-65B4-4D96-9D97-6ADB7EFF8147", "surl": "https://tcr9i.chat.openai.com", - "headers": { - "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' - }, + "headers": headers, "site": cls.url, + "data": {"blob": blob} } args_for_request = get_values_for_request(config) async with session.post(**args_for_request) as response: - response.raise_for_status() + await raise_for_status(response) decoded_json = await response.json() if "token" in decoded_json: return decoded_json["token"] @@ -591,7 +657,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): @classmethod def _create_request_args(cls, cookies: Union[Cookies, None]): - cls._headers = {} + cls._headers = { + "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36' + } cls._cookies = {} if cookies is None else cookies cls._update_cookie_header() |