summaryrefslogtreecommitdiffstats
path: root/g4f/Provider
diff options
context:
space:
mode:
authorH Lohaus <hlohaus@users.noreply.github.com>2024-03-16 18:22:26 +0100
committerGitHub <noreply@github.com>2024-03-16 18:22:26 +0100
commitfb2061da48525edab9cd993205bb5e30c386aa1a (patch)
tree1e740bd6955dfd27b9a4d773df07234ed9e5c75e /g4f/Provider
parentMerge pull request #1694 from ComRSMaster/main (diff)
parentAdd conversation support for Bing (diff)
downloadgpt4free-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.py171
-rw-r--r--g4f/Provider/You.py48
-rw-r--r--g4f/Provider/bing/conversation.py7
-rw-r--r--g4f/Provider/needs_auth/OpenaiChat.py164
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()