Refactor Cloudflare and LMArena providers to enhance authentication handling and improve WebSocket communication

This commit is contained in:
hlohaus 2025-10-30 21:34:45 +01:00
parent 63315df501
commit da6c00e2a2
6 changed files with 58 additions and 74 deletions

57
.gitignore vendored
View file

@ -1,48 +1,11 @@
# Default ignored files bin
/shelf/ dist
/workspace.xml __pycache__
# Editor-based HTTP Client requests generated_media
/httpRequests/ site-packages
# Datasource local storage ignored files projects
/dataSources/
/dataSources.local.xml
# Ignore local python virtual environment
venv/
# Ignore streamlit_chat_app.py conversations pickle
conversations.pkl
*.pkl
.idea/
**/__pycache__/
__pycache__/
*.log
*.pyc
*.egg-info/
*.egg
*.egg-info
.DS_Store
*~
*.gguf
.buildozer
har_and_cookies
node_modules node_modules
models g4f.egg-info
projects/windows/g4f models/models.json
generated_images/ pyvenv.cfg
generated_media/ lib64
projects/windows/
*.bak
*.backup
.env
g4f.dev/
# Build artifacts
build/
dist/
*.spec
pyproject.toml.bak
debian/
winget/

View file

@ -568,15 +568,22 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
pass pass
elif has_nodriver or cls.share_url is None: elif has_nodriver or cls.share_url is None:
async def callback(page): async def callback(page):
element = await page.select('[style="display: grid;"]')
if element:
await click_trunstile(page, 'document.querySelector(\'[style="display: grid;"]\')')
await page.find("Ask anything…", 120)
button = await page.find("Accept Cookies") button = await page.find("Accept Cookies")
if button: if button:
await button.click() await button.click()
else: else:
debug.log("No 'Accept Cookies' button found, skipping.") debug.log("No 'Accept Cookies' button found, skipping.")
await asyncio.sleep(1)
textarea = await page.find("Ask anything…")
if textarea:
await textarea.send_keys("Hello")
await asyncio.sleep(1)
button = await page.select('[type="submit"]:has([data-sentry-element="ArrowUp"])')
if button:
await button.click()
element = await page.select('[style="display: grid;"]')
if element:
await click_trunstile(page, 'document.querySelector(\'[style="display: grid;"]\')')
if not await page.evaluate('document.cookie.indexOf("arena-auth-prod-v1") >= 0'): if not await page.evaluate('document.cookie.indexOf("arena-auth-prod-v1") >= 0'):
debug.log("No authentication cookie found, trying to authenticate.") debug.log("No authentication cookie found, trying to authenticate.")
await page.select('#cf-turnstile', 300) await page.select('#cf-turnstile', 300)
@ -628,7 +635,7 @@ class LMArena(AsyncGeneratorProvider, ProviderModelMixin, AuthFileMixin):
elif model in cls.image_models: elif model in cls.image_models:
model_id = cls.image_models[model] model_id = cls.image_models[model]
else: else:
raise ModelNotFoundError(f"Model '{model}' is not supported by LMArena Beta.") raise ModelNotFoundError(f"Model '{model}' is not supported by LMArena provider.")
userMessageId = str(uuid.uuid7()) userMessageId = str(uuid.uuid7())
modelAMessageId = str(uuid.uuid7()) modelAMessageId = str(uuid.uuid7())

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import os
import asyncio import asyncio
from typing import Optional from typing import Optional
from aiohttp import ClientSession, ClientTimeout from aiohttp import ClientSession, ClientTimeout
@ -10,13 +11,13 @@ from aiohttp import ClientSession
try: try:
import nodriver import nodriver
from nodriver.core.connection import ProtocolException from nodriver.core.connection import ProtocolException
has_nodriver = True
except: except:
pass has_nodriver = False
from ...typing import Messages, AsyncResult from ...typing import Messages, AsyncResult
from ...providers.response import VideoResponse, Reasoning, ContinueResponse, ProviderInfo from ...providers.response import VideoResponse, Reasoning, ContinueResponse, ProviderInfo
from ...requests import get_nodriver from ...requests import get_nodriver
from ...errors import MissingRequirementsError
from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin from ..base_provider import AsyncGeneratorProvider, ProviderModelMixin
from ..helper import format_media_prompt from ..helper import format_media_prompt
from ... import debug from ... import debug
@ -53,7 +54,7 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
"sora": "https://sora.chatgpt.com/explore", "sora": "https://sora.chatgpt.com/explore",
#"veo": "https://aistudio.google.com/generate-video" #"veo": "https://aistudio.google.com/generate-video"
} }
api_url = f"{PUBLIC_URL}/backend-api/v2/create?provider=Video&cache=true&prompt=" api_path = f"?provider=Video&cache=true&prompt="
drive_url = "https://www.googleapis.com/drive/v3/" drive_url = "https://www.googleapis.com/drive/v3/"
active_by_default = True active_by_default = True
@ -62,10 +63,11 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
video_models = models video_models = models
needs_auth = True needs_auth = True
working = True working = has_nodriver
browser = None browser = None
stop_browser = None stop_browser = None
share_url: Optional[str] = None
@classmethod @classmethod
async def create_async_generator( async def create_async_generator(
@ -77,6 +79,8 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
aspect_ratio: str = None, aspect_ratio: str = None,
**kwargs **kwargs
) -> AsyncResult: ) -> AsyncResult:
if cls.share_url is None:
cls.share_url = os.getenv("G4F_SHARE_URL")
if not model: if not model:
model = cls.default_model model = cls.default_model
if model not in cls.video_models: if model not in cls.video_models:
@ -94,10 +98,12 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
yield Reasoning(label="Open browser") yield Reasoning(label="Open browser")
browser, stop_browser = await get_nodriver(proxy=proxy) browser, stop_browser = await get_nodriver(proxy=proxy)
except Exception as e: except Exception as e:
if cls.share_url is None:
raise
debug.error(f"Error getting nodriver:", e) debug.error(f"Error getting nodriver:", e)
async with ClientSession() as session: async with ClientSession() as session:
yield Reasoning(label="Generating") yield Reasoning(label="Generating")
async with session.get(cls.api_url + quote(prompt)) as response: async with session.get(f"{cls.share_url}{cls.api_path + quote(prompt)}") as response:
if not response.ok: if not response.ok:
debug.error(f"Failed to generate Video: {response.status}") debug.error(f"Failed to generate Video: {response.status}")
else: else:
@ -108,7 +114,7 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
return return
yield VideoResponse(str(response.url), prompt) yield VideoResponse(str(response.url), prompt)
return return
raise MissingRequirementsError("Video provider requires a browser to be installed.") raise
page = None page = None
try: try:
yield ContinueResponse("Timeout waiting for Video URL") yield ContinueResponse("Timeout waiting for Video URL")
@ -123,7 +129,7 @@ class Video(AsyncGeneratorProvider, ProviderModelMixin):
RequestConfig.headers = {} RequestConfig.headers = {}
for key, value in event.request.headers.items(): for key, value in event.request.headers.items():
RequestConfig.headers[key.lower()] = value RequestConfig.headers[key.lower()] = value
for _, urls in RequestConfig.urls.items(): for urls in RequestConfig.urls.values():
if event.request.url in urls: if event.request.url in urls:
return return
debug.log(f"Adding URL: {event.request.url}") debug.log(f"Adding URL: {event.request.url}")

View file

@ -14,9 +14,7 @@ class QwenCode(OpenaiTemplate):
needs_auth = True needs_auth = True
active_by_default = True active_by_default = True
default_model = "qwen3-coder-plus" default_model = "qwen3-coder-plus"
default_vision_model = "qwen-vl-max-latest" models = [default_model]
models = [default_model, default_vision_model]
vision_models = [default_vision_model]
client = QwenContentGenerator(QwenOAuth2Client()) client = QwenContentGenerator(QwenOAuth2Client())
@classmethod @classmethod
@ -48,9 +46,12 @@ class QwenCode(OpenaiTemplate):
api_base=creds.get("endpoint", api_base), api_base=creds.get("endpoint", api_base),
**kwargs **kwargs
): ):
if isinstance(chunk, str):
if chunk != last_chunk: if chunk != last_chunk:
yield chunk yield chunk
last_chunk = chunk last_chunk = chunk
else:
yield chunk
except TokenManagerError: except TokenManagerError:
await cls.client.shared_manager.getValidCredentials(cls.client.qwen_client, True) await cls.client.shared_manager.getValidCredentials(cls.client.qwen_client, True)
creds = await cls.client.get_valid_token() creds = await cls.client.get_valid_token()
@ -62,8 +63,11 @@ class QwenCode(OpenaiTemplate):
api_base=creds.get("endpoint"), api_base=creds.get("endpoint"),
**kwargs **kwargs
): ):
if isinstance(chunk, str):
if chunk != last_chunk: if chunk != last_chunk:
yield chunk yield chunk
last_chunk = chunk last_chunk = chunk
else:
yield chunk
except: except:
raise raise

View file

@ -239,8 +239,7 @@ class Api:
user_g4f_api_key = await self.get_g4f_api_key(request) user_g4f_api_key = await self.get_g4f_api_key(request)
except HTTPException: except HTTPException:
user_g4f_api_key = await self.security(request) user_g4f_api_key = await self.security(request)
if hasattr(user_g4f_api_key, "credentials"): user_g4f_api_key = getattr(user_g4f_api_key, "credentials", user_g4f_api_key)
user_g4f_api_key = user_g4f_api_key.credentials
if AppConfig.demo and user is None: if AppConfig.demo and user is None:
ip = request.headers.get("X-Forwarded-For", "")[:4].strip(":.") ip = request.headers.get("X-Forwarded-For", "")[:4].strip(":.")
country = request.headers.get("Cf-Ipcountry", "") country = request.headers.get("Cf-Ipcountry", "")
@ -265,14 +264,14 @@ class Api:
debug.log(f"User: '{user}' G4F API key expires in {hours}h {minutes}m {seconds}s") debug.log(f"User: '{user}' G4F API key expires in {hours}h {minutes}m {seconds}s")
if expires < 0: if expires < 0:
return ErrorResponse.from_message("G4F API key expired", HTTP_401_UNAUTHORIZED) return ErrorResponse.from_message("G4F API key expired", HTTP_401_UNAUTHORIZED)
else:
user = "admin"
count = 0 count = 0
for char in string: for char in user:
if char.isupper(): if char.isupper():
count += 1 count += 1
if count > 4: if count > 4:
return ErrorResponse.from_message("Invalid user name", HTTP_401_UNAUTHORIZED) return ErrorResponse.from_message("Invalid user name (screaming)", HTTP_401_UNAUTHORIZED)
else:
user = "admin"
path = request.url.path path = request.url.path
if path.startswith("/v1") or path.startswith("/api/") or (AppConfig.demo and path == '/backend-api/v2/upload_cookies'): if path.startswith("/v1") or path.startswith("/api/") or (AppConfig.demo and path == '/backend-api/v2/upload_cookies'):
if request.method != "OPTIONS" and not path.endswith("/models"): if request.method != "OPTIONS" and not path.endswith("/models"):

View file

@ -107,6 +107,11 @@ def is_data_an_media(data, filename: str = None) -> str:
return content_type return content_type
if isinstance(data, bytes): if isinstance(data, bytes):
return is_accepted_format(data) return is_accepted_format(data)
if isinstance(data, str) and data.startswith("http"):
path = urlparse(data).path
extension = get_extension(path)
if extension is not None:
return EXTENSIONS_MAP[extension]
return is_data_uri_an_image(data) return is_data_uri_an_image(data)
def is_valid_media(data: ImageType = None, filename: str = None) -> str: def is_valid_media(data: ImageType = None, filename: str = None) -> str: