summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/publish-workflow.yaml18
-rw-r--r--g4f/Provider/needs_auth/OpenaiChat.py128
-rw-r--r--g4f/Provider/openai/har_file.py72
-rw-r--r--g4f/Provider/openai/proofofwork.py10
-rw-r--r--g4f/client/__init__.py5
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: