diff options
-rw-r--r-- | .github/workflows/publish-workflow.yaml | 18 | ||||
-rw-r--r-- | g4f/Provider/needs_auth/OpenaiChat.py | 128 | ||||
-rw-r--r-- | g4f/Provider/openai/har_file.py | 72 | ||||
-rw-r--r-- | g4f/Provider/openai/proofofwork.py | 10 | ||||
-rw-r--r-- | g4f/client/__init__.py | 5 |
5 files changed, 126 insertions, 107 deletions
diff --git a/.github/workflows/publish-workflow.yaml b/.github/workflows/publish-workflow.yaml index 1a1edaab..67d9dc53 100644 --- a/.github/workflows/publish-workflow.yaml +++ b/.github/workflows/publish-workflow.yaml @@ -37,26 +37,28 @@ jobs: username: ${{ github.repository_owner }} password: ${{ secrets.GHCR_PAT }} - - name: Build and push image + - name: Build and push small images uses: docker/build-push-action@v5 with: context: . - file: docker/Dockerfile + file: docker/Dockerfile-slim platforms: linux/amd64,linux/arm64 push: true - tags: ${{ steps.metadata.outputs.tags }} + tags: | + hlohaus789/g4f:latest-slim + hlohaus789/g4f:${{ github.ref_name }}-slim labels: ${{ steps.metadata.outputs.labels }} build-args: | G4F_VERSION=${{ github.ref_name }} - - name: Build and push slim image + + - name: Build and push big images uses: docker/build-push-action@v5 with: context: . - file: docker/Dockerfile-slim + file: docker/Dockerfile + platforms: linux/amd64,linux/arm64 push: true - tags: | - hlohaus789/g4f:slim-latest - hlohaus789/g4f:${{ github.ref_name }}-slim + tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} build-args: | G4F_VERSION=${{ github.ref_name }}
\ No newline at end of file diff --git a/g4f/Provider/needs_auth/OpenaiChat.py b/g4f/Provider/needs_auth/OpenaiChat.py index 13e20fce..43444699 100644 --- a/g4f/Provider/needs_auth/OpenaiChat.py +++ b/g4f/Provider/needs_auth/OpenaiChat.py @@ -1,5 +1,6 @@ from __future__ import annotations +import re import asyncio import uuid import json @@ -10,6 +11,7 @@ from copy import copy try: import nodriver + from nodriver.cdp.network import get_response_body has_nodriver = True except ImportError: has_nodriver = False @@ -27,8 +29,10 @@ from ...image import ImageResponse, ImageRequest, to_image, to_bytes, is_accepte from ...errors import MissingAuthError, ResponseError from ...providers.conversation import BaseConversation from ..helper import format_cookies -from ..openai.har_file import getArkoseAndAccessToken, NoValidHarFileError +from ..openai.har_file import get_request_config, NoValidHarFileError +from ..openai.har_file import RequestConfig, arkReq, arkose_url, start_url, conversation_url, backend_url, backend_anon_url from ..openai.proofofwork import generate_proof_token +from ..openai.new import get_requirements_token from ... import debug DEFAULT_HEADERS = { @@ -56,9 +60,9 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): supports_gpt_4 = True supports_message_history = True supports_system_message = True - default_model = None + default_model = "auto" default_vision_model = "gpt-4o" - models = [ "auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"] + models = ["auto", "gpt-4o-mini", "gpt-4o", "gpt-4", "gpt-4-gizmo"] _api_key: str = None _headers: dict = None @@ -352,21 +356,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): ) as session: if cls._expires is not None and cls._expires < time.time(): cls._headers = cls._api_key = None - arkose_token = None - proofToken = None - turnstileToken = None try: - arkose_token, api_key, cookies, headers, proofToken, turnstileToken = await getArkoseAndAccessToken(proxy) - cls._create_request_args(cookies, headers) - cls._set_api_key(api_key) + await get_request_config(proxy) + cls._create_request_args(RequestConfig.cookies, RequestConfig.headers) + cls._set_api_key(RequestConfig.access_token) except NoValidHarFileError as e: - if cls._api_key is None and cls.needs_auth: - raise e - cls._create_request_args() - - if cls.default_model is None: - cls.default_model = cls.get_model(await cls.get_default_model(session, cls._headers)) - + await cls.nodriver_auth(proxy) try: image_request = await cls.upload_image(session, cls._headers, image, image_name) if image else None except Exception as e: @@ -374,9 +369,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): if debug.logging: print("OpenaiChat: Upload image failed") print(f"{e.__class__.__name__}: {e}") - model = cls.get_model(model) - model = "text-davinci-002-render-sha" if model == "gpt-3.5-turbo" else model if conversation is None: conversation = Conversation(conversation_id, str(uuid.uuid4()) if parent_id is None else parent_id) else: @@ -389,7 +382,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): f"{cls.url}/backend-anon/sentinel/chat-requirements" if cls._api_key is None else f"{cls.url}/backend-api/sentinel/chat-requirements", - json={"p": generate_proof_token(True, user_agent=cls._headers["user-agent"], proofToken=proofToken)}, + json={"p": get_requirements_token(RequestConfig.proof_token)}, headers=cls._headers ) as response: cls._update_request_args(session) @@ -399,22 +392,22 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): need_arkose = chat_requirements.get("arkose", {}).get("required", False) chat_token = chat_requirements.get("token") - if need_arkose and arkose_token is None: - arkose_token, api_key, cookies, headers, proofToken, turnstileToken = await getArkoseAndAccessToken(proxy) - cls._create_request_args(cookies, headers) - cls._set_api_key(api_key) - if arkose_token is None: + if need_arkose and RequestConfig.arkose_token is None: + await get_request_config(proxy) + cls._create_request_args(RequestConfig,cookies, RequestConfig.headers) + cls._set_api_key(RequestConfig.access_token) + if RequestConfig.arkose_token is None: raise MissingAuthError("No arkose token found in .har file") if "proofofwork" in chat_requirements: proofofwork = generate_proof_token( **chat_requirements["proofofwork"], user_agent=cls._headers["user-agent"], - proofToken=proofToken + proof_token=RequestConfig.proof_token ) if debug.logging: print( - 'Arkose:', False if not need_arkose else arkose_token[:12]+"...", + 'Arkose:', False if not need_arkose else RequestConfig.arkose_token[:12]+"...", 'Proofofwork:', False if proofofwork is None else proofofwork[:12]+"...", ) ws = None @@ -423,7 +416,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): wss_url = (await response.json()).get("wss_url") if wss_url: ws = await session.ws_connect(wss_url) - websocket_request_id = str(uuid.uuid4()) data = { "action": action, "messages": None, @@ -432,7 +424,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): "paragen_cot_summary_display_override": "allow", "history_and_training_disabled": history_disabled and not auto_continue and not return_conversation, "conversation_mode": {"kind":"primary_assistant"}, - "websocket_request_id": websocket_request_id, + "websocket_request_id": str(uuid.uuid4()), "supported_encodings": ["v1"], "supports_buffering": True } @@ -446,12 +438,12 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): "Openai-Sentinel-Chat-Requirements-Token": chat_token, **cls._headers } - if arkose_token: - headers["Openai-Sentinel-Arkose-Token"] = arkose_token + if RequestConfig.arkose_token: + headers["Openai-Sentinel-Arkose-Token"] = RequestConfig.arkose_token if proofofwork is not None: headers["Openai-Sentinel-Proof-Token"] = proofofwork - if need_turnstile and turnstileToken is not None: - headers['openai-sentinel-turnstile-token'] = turnstileToken + if need_turnstile and RequestConfig.turnstile_token is not None: + headers['openai-sentinel-turnstile-token'] = RequestConfig.turnstile_token async with session.post( f"{cls.url}/backend-anon/conversation" if cls._api_key is None else @@ -535,7 +527,6 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): return if isinstance(line, dict) and "v" in line: v = line.get("v") - r = "" if isinstance(v, str): yield v elif isinstance(v, list): @@ -567,7 +558,7 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): raise RuntimeError(line.get("error")) @classmethod - async def nodriver_access_token(cls, proxy: str = None): + async def nodriver_auth(cls, proxy: str = None): if not has_nodriver: return if has_platformdirs: @@ -580,27 +571,53 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): user_data_dir=user_data_dir, browser_args=None if proxy is None else [f"--proxy-server={proxy}"], ) - page = await browser.get("https://chatgpt.com/") - await page.select("[id^=headlessui-menu-button-]", 240) - api_key = await page.evaluate( - "(async () => {" - "let session = await fetch('/api/auth/session');" - "let data = await session.json();" - "let accessToken = data['accessToken'];" - "let expires = new Date(); expires.setTime(expires.getTime() + 60 * 60 * 4 * 1000);" - "document.cookie = 'access_token=' + accessToken + ';expires=' + expires.toUTCString() + ';path=/';" - "return accessToken;" - "})();", - await_promise=True - ) - cookies = {} - for c in await page.browser.cookies.get_all(): - if c.domain.endswith("chatgpt.com"): - cookies[c.name] = c.value - user_agent = await page.evaluate("window.navigator.userAgent") + page = browser.main_tab + def on_request(event: nodriver.cdp.network.RequestWillBeSent): + if event.request.url == start_url or event.request.url.startswith(conversation_url): + RequestConfig.access_request_id = event.request_id + RequestConfig.headers = event.request.headers + elif event.request.url in (backend_url, backend_anon_url): + if "OpenAI-Sentinel-Proof-Token" in event.request.headers: + RequestConfig.proof_token = json.loads(base64.b64decode( + event.request.headers["OpenAI-Sentinel-Proof-Token"].split("gAAAAAB", 1)[-1].encode() + ).decode()) + if "OpenAI-Sentinel-Turnstile-Token" in event.request.headers: + RequestConfig.turnstile_token = event.request.headers["OpenAI-Sentinel-Turnstile-Token"] + if "Authorization" in event.request.headers: + RequestConfig.access_token = event.request.headers["Authorization"].split()[-1] + elif event.request.url == arkose_url: + RequestConfig.arkose_request = arkReq( + arkURL=event.request.url, + arkBx=None, + arkHeader=event.request.headers, + arkBody=event.request.post_data, + userAgent=event.request.headers.get("user-agent") + ) + await page.send(nodriver.cdp.network.enable()) + page.add_handler(nodriver.cdp.network.RequestWillBeSent, on_request) + page = await browser.get(cls.url) + try: + if RequestConfig.access_request_id is not None: + body = await page.send(get_response_body(RequestConfig.access_request_id)) + if isinstance(body, tuple) and body: + body = body[0] + if body: + match = re.search(r'"accessToken":"(.*?)"', body) + if match: + RequestConfig.access_token = match.group(1) + except KeyError: + pass + for c in await page.send(nodriver.cdp.network.get_cookies([cls.url])): + RequestConfig.cookies[c.name] = c.value + RequestConfig.user_agent = await page.evaluate("window.navigator.userAgent") + await page.select("#prompt-textarea", 240) + while True: + if RequestConfig.proof_token: + break + await asyncio.sleep(1) await page.close() - cls._create_request_args(cookies, user_agent=user_agent) - cls._set_api_key(api_key) + cls._create_request_args(RequestConfig.cookies, RequestConfig.headers, user_agent=RequestConfig.user_agent) + cls._set_api_key(RequestConfig.access_token) @staticmethod def get_default_headers() -> dict: @@ -627,7 +644,8 @@ class OpenaiChat(AsyncGeneratorProvider, ProviderModelMixin): def _set_api_key(cls, api_key: str): cls._api_key = api_key cls._expires = int(time.time()) + 60 * 60 * 4 - cls._headers["authorization"] = f"Bearer {api_key}" + if api_key: + cls._headers["authorization"] = f"Bearer {api_key}" @classmethod def _update_cookie_header(cls): diff --git a/g4f/Provider/openai/har_file.py b/g4f/Provider/openai/har_file.py index b7c36f05..c8d85a65 100644 --- a/g4f/Provider/openai/har_file.py +++ b/g4f/Provider/openai/har_file.py @@ -15,8 +15,27 @@ from ...requests import StreamSession from ...cookies import get_cookies_dir from ... import debug +arkose_url = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147" +backend_url = "https://chatgpt.com/backend-api/conversation" +backend_anon_url = "https://chatgpt.com/backend-anon/conversation" +start_url = "https://chatgpt.com/" +conversation_url = "https://chatgpt.com/c/" + class NoValidHarFileError(Exception): - ... + pass + +class RequestConfig: + user_agent: str = None + cookies: dict = None + headers: dict = None + access_request_id: str = None + access_token: str = None + proof_token: list = None + turnstile_token: str = None + arkose_request: arkReq = None + arkose_token: str = None + headers: dict = {} + cookies: dict = {} class arkReq: def __init__(self, arkURL, arkBx, arkHeader, arkBody, arkCookies, userAgent): @@ -27,23 +46,9 @@ class arkReq: self.arkCookies = arkCookies self.userAgent = userAgent -arkoseURL = "https://tcr9i.chat.openai.com/fc/gt2/public_key/35536E1E-65B4-4D96-9D97-6ADB7EFF8147" -startUrl = "https://chatgpt.com/" -conversationUrl = "https://chatgpt.com/c/" -arkoseRequest: arkReq = None -accessToken: str = None -cookies: dict = None -headers: dict = None -proofToken: list = [] -turnstileToken: str = None - def readHAR(): - global arkoseRequest, accessToken, proofToken, turnstileToken harPath = [] - chatArks = [] - accessToken = None - cookies = {} - for root, dirs, files in os.walk(get_cookies_dir()): + for root, _, files in os.walk(get_cookies_dir()): for file in files: if file.endswith(".har"): harPath.append(os.path.join(root, file)) @@ -60,30 +65,27 @@ def readHAR(): v_headers = get_headers(v) try: if "openai-sentinel-proof-token" in v_headers: - proofToken = json.loads(base64.b64decode( + RequestConfig.proof_token = json.loads(base64.b64decode( v_headers["openai-sentinel-proof-token"].split("gAAAAAB", 1)[-1].encode() ).decode()) if "openai-sentinel-turnstile-token" in v_headers: - turnstileToken = v_headers["openai-sentinel-turnstile-token"] + RequestConfig.turnstile_token = v_headers["openai-sentinel-turnstile-token"] except Exception as e: if debug.logging: print(f"Read proof token: {e}") - if arkoseURL in v['request']['url']: - arkoseRequest = parseHAREntry(v) - elif v['request']['url'] == startUrl or v['request']['url'].startswith(conversationUrl): + if arkose_url == v['request']['url']: + RequestConfig.arkose_request = parseHAREntry(v) + elif v['request']['url'] == start_url or v['request']['url'].startswith(conversation_url): try: match = re.search(r'"accessToken":"(.*?)"', v["response"]["content"]["text"]) if match: - accessToken = match.group(1) + RequestConfig.access_token = match.group(1) except KeyError: continue - cookies = {c['name']: c['value'] for c in v['request']['cookies'] if c['name'] != "oai-did"} - headers = v_headers - if not accessToken: + RequestConfig.cookies = {c['name']: c['value'] for c in v['request']['cookies'] if c['name'] != "oai-did"} + RequestConfig.headers = v_headers + if RequestConfig.access_token is None: raise NoValidHarFileError("No accessToken found in .har files") - if not chatArks: - return cookies, headers - return cookies, headers def get_headers(entry) -> dict: return {h['name'].lower(): h['value'] for h in entry['request']['headers'] if h['name'].lower() not in ['content-length', 'cookie'] and not h['name'].startswith(':')} @@ -147,11 +149,9 @@ def getN() -> str: timestamp = str(int(time.time())) return base64.b64encode(timestamp.encode()).decode() -async def getArkoseAndAccessToken(proxy: str) -> tuple[str, str, dict, dict]: - global arkoseRequest, accessToken, cookies, headers, proofToken, turnstileToken - if arkoseRequest is None or accessToken is None: - cookies, headers = readHAR() - if arkoseRequest is None: - return None, accessToken, cookies, headers, proofToken, turnstileToken - newReq = genArkReq(arkoseRequest) - return await sendRequest(newReq, proxy), accessToken, cookies, headers, proofToken, turnstileToken +async def get_request_config(proxy: str) -> RequestConfig: + if RequestConfig.arkose_request is None or RequestConfig.access_token is None: + readHAR() + if RequestConfig.arkose_request is not None: + RequestConfig.arkose_token = await sendRequest(genArkReq(RequestConfig.arkose_request), proxy) + return RequestConfig
\ No newline at end of file diff --git a/g4f/Provider/openai/proofofwork.py b/g4f/Provider/openai/proofofwork.py index 23e5ab6e..4294c99a 100644 --- a/g4f/Provider/openai/proofofwork.py +++ b/g4f/Provider/openai/proofofwork.py @@ -4,16 +4,16 @@ import json import base64 from datetime import datetime, timezone -def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", user_agent: str = None, proofToken: str = None): +def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", user_agent: str = None, proof_token: str = None): if not required: return - if proofToken is None: + if proof_token is None: screen = random.choice([3008, 4010, 6000]) * random.choice([1, 2, 4]) # Get current UTC time now_utc = datetime.now(timezone.utc) parse_time = now_utc.strftime('%a, %d %b %Y %H:%M:%S GMT') - proofToken = [ + proof_token = [ screen, parse_time, None, 0, user_agent, "https://tcr9i.chat.openai.com/v2/35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js", @@ -26,8 +26,8 @@ def generate_proof_token(required: bool, seed: str = "", difficulty: str = "", u diff_len = len(difficulty) for i in range(100000): - proofToken[3] = i - json_data = json.dumps(proofToken) + proof_token[3] = i + json_data = json.dumps(proof_token) base = base64.b64encode(json_data.encode()).decode() hash_value = hashlib.sha3_512((seed + base).encode()).digest() diff --git a/g4f/client/__init__.py b/g4f/client/__init__.py index 5ffe9288..6f679e4a 100644 --- a/g4f/client/__init__.py +++ b/g4f/client/__init__.py @@ -247,7 +247,7 @@ class Images: """ Synchronous generate method that runs the async_generate method in an event loop. """ - return asyncio.run(self.async_generate(prompt, model, provider, response_format=response_format, proxy=proxy **kwargs)) + return asyncio.run(self.async_generate(prompt, model, provider, response_format=response_format, proxy=proxy, **kwargs)) async def async_generate(self, prompt: str, model: str = None, provider: ProviderType = None, response_format: str = "url", proxy: str = None, **kwargs) -> ImagesResponse: if provider is None: @@ -343,8 +343,7 @@ class Images: def create_variation(self, image: Union[str, bytes], model: str = None, provider: ProviderType = None, response_format: str = "url", **kwargs) -> ImagesResponse: return asyncio.run(self.async_create_variation( - image, model, provider, response_format - **kwargs + image, model, provider, response_format, **kwargs )) async def async_create_variation(self, image: Union[str, bytes], model: str = None, provider: ProviderType = None, response_format: str = "url", proxy: str = None, **kwargs) -> ImagesResponse: |