返回提交历史
Modified
g4f/api/__init__.py
+6
-5
Modified
g4f/gui/server/website.py
+34
-38
Modified
g4f/providers/base_provider.py
+3
-23
Modified
g4f/requests/cdp.py
+16
-5
XFEstudio/gpt4free
Refactor screenshot handling to use secure filenames and improve URL encoding
3b62a0a3
代码差异
4 个文件
+59
-71
@@ -87,7 +87,7 @@ from g4f.providers.response import BaseConversation, JsonConversation
87
87
from g4f.client.helper import filter_none
88
88
from g4f.config import DEFAULT_PORT, DEFAULT_TIMEOUT, DEFAULT_STREAM_TIMEOUT
89
89
from g4f.image import EXTENSIONS_MAP, is_data_an_media, process_image, is_safe_url
90
from g4f.image.copy_images import get_media_dir, copy_media, get_source_url
90
from g4f.image.copy_images import get_media_dir, copy_media, get_source_url, secure_filename
91
91
from g4f.errors import (
92
92
ProviderNotFoundError,
93
93
ModelNotFoundError,
@@ -1409,19 +1409,20 @@ class Api:
1409
1409
e, None, HTTP_500_INTERNAL_SERVER_ERROR
1410
1410
)
1411
1411
1412
@self.app.get("/screenshot/{path}", responses=responses)
1412
@self.app.get("/screenshot/{name:path}", responses=responses)
1413
1413
async def image_from_url(
1414
path: str,
1414
name: str,
1415
1415
):
1416
safe_name = secure_filename(name)
1416
1417
screenshots_dir = os.path.join(get_media_dir(), "screenshots")
1417
1418
for root, _, files in os.walk(screenshots_dir):
1418
1419
for file in files:
1419
if file != path:
1420
if file != safe_name:
1420
1421
continue
1421
1422
if not os.path.isfile(os.path.join(root, file)):
1422
1423
continue
1423
1424
return FileResponse(
1424
os.path.join(root, path),
1425
os.path.join(root, safe_name),
1425
1426
media_type="image/webp",
1426
1427
headers={"Cache-Control": "max-age=604800"},
1427
1428
)
@@ -7,7 +7,7 @@ import requests
7
7
import re
8
8
import time
9
9
from datetime import datetime
10
from urllib.parse import quote, unquote
10
from urllib.parse import quote_plus
11
11
from flask import jsonify, send_from_directory, redirect, request
12
12
13
13
from ...files import secure_filename
@@ -334,45 +334,40 @@ class Website:
334
334
"""
335
335
336
336
# Screenshot / logo section
337
logo_url = f"{(p.get('url', (p.get('base_url', p.get('baseUrl', '')))) or "").replace('https://', '').replace('http://', '').replace('api.', '').replace('www.', '').replace('console.', '').replace('api.', '').replace('router.', '').split('/')[0]}"
338
logo_url = f"api.airforce" if logo_url == "airforce" else logo_url or "g4f.dev"
339
create_url = f"/screenshot?url=https://{logo_url}"
337
screenshot_url = f"{(p.get('url', (p.get('base_url', p.get('baseUrl', '')))) or "").replace('https://', '').replace('http://', '').replace('api.', '').replace('www.', '').replace('console.', '').replace('api.', '').replace('router.', '').split('/')[0]}"
338
screenshot_url = f"api.airforce" if screenshot_url == "airforce" else screenshot_url or "g4f.dev"
339
if p.get("name", "") == "OperaAria" or p.get("name", "") == "CopilotApp":
340
screenshot_url = p["url"].replace("https://", "")
341
create_url = f"/screenshot?url={quote_plus('https://' + screenshot_url)}"
342
logo_url = "https://g4f.space/logo/" + p.get("name", "").replace(
343
'MetaAIAccount', 'Facebook AI').replace(
344
'MetaAI', 'Facebook AI').replace(
345
'Aria', '').replace(
346
'OpenAI', 'ChatGPT').replace(
347
'Video', 'TV').replace(
348
'Phi-4', 'Windows').replace(
349
'(Text Generation)', '').replace(
350
'Glhf', 'AI').replace(
351
'GithubCopilot', 'GitHub Copilot').replace(
352
'PerplexityApi', 'Perplexity API').replace(
353
'Gemini', '').replace(
354
'API', '').replace(
355
'-2.5M', '').replace(
356
'grok', 'xAI').replace(
357
'Qwen_Qwen_3', 'Qwen').replace(
358
'Yupp', 'with yupp').replace(
359
'groq', 'Groq')
340
360
screenshot_html = f"""
341
361
<div class="screenshot-section">
342
<img src="/screenshot/{logo_url}.webp" data-src="{create_url}" alt="{escape(p['name'])} logo" class="provider-logo"
362
<img src="/screenshot/{quote_plus(screenshot_url)}.webp" data-src="{create_url}" alt="{escape(p['name'])} logo" class="provider-logo"
343
363
style="max-width:100%;border-radius:8px;border:1px solid var(--card-border)" />
364
<img src="{logo_url}" alt="{escape(p['name'])} logo" class="provider-logo" style="max-width:100%;border-radius:8px;border:1px solid var(--card-border)" />
344
365
<p class="screenshot-caption">Load screenshot from {escape(p['url'] or 'N/A')}</p>
345
366
</div>
346
367
<script>
347
// Get provider logo (same as docs page)
348
function getImage(key) {{
349
key = key || "";
350
const img = new Image(200, 200);
351
const gen = 'https://g4f.space';
352
img.src = gen + '/logo/' +
353
key.replace(
354
'MetaAIAccount', 'Facebook AI').replace(
355
'MetaAI', 'Facebook AI').replace(
356
'Aria', '').replace(
357
'OpenAI', 'ChatGPT').replace(
358
'Video', 'TV').replace(
359
'Phi-4', 'Windows').replace(
360
'(Text Generation)', '').replace(
361
'Glhf', 'AI').replace(
362
'GithubCopilot', 'GitHub Copilot').replace(
363
'PerplexityApi', 'Perplexity API').replace(
364
'Gemini', '').replace(
365
'API', '').replace(
366
'-2.5M', '').replace(
367
'grok', 'xAI').replace(
368
'Qwen_Qwen_3', 'Qwen').replace(
369
'Yupp', 'with yupp').replace(
370
'groq', 'Groq');
371
return img;
372
}}
373
368
const img = document.querySelector('img[data-src="{create_url}"]');
374
369
const input = document.createElement('input');
375
const previewImg = getImage("{p['name']}")
370
const previewImg = document.querySelector('img[src="{logo_url}"]');
376
371
img.parentElement.appendChild(previewImg);
377
372
let n = 1;
378
373
let previewRemoved = false;
@@ -384,12 +379,13 @@ class Website:
384
379
previewRemoved = true;
385
380
document.querySelector('.screenshot-caption').style.display = 'none';
386
381
}}
387
// n = n + 1;
388
// if (n <= 3) {{
389
// setTimeout(() => {{
390
// img.src = createSrc + `_${{n}}.webp`;
391
// }}, 1000);
392
// }}
382
}};
383
img.onmouseenter = () => {{
384
n = (n % 3) + 1;
385
if (n <= 3) {{
386
const append = n == 1 ? ".webp" : `_${{n}}.webp`;
387
img.src = `/screenshot/{quote_plus(screenshot_url)}${{append}}`;
388
}}
393
389
}};
394
390
img.onerror = () => {{
395
391
input.placeholder = 'Ask {escape(p.get("label", p["name"]))}';
@@ -316,31 +316,11 @@ class AsyncGeneratorProvider(AbstractProvider):
316
316
@classmethod
317
317
async def get_quota(cls, api_key: Optional[str] = None, **kwargs) -> dict:
318
318
"""Get the quota information for the API key."""
319
if cls.quota_url is None:
320
if not getattr(cls, "use_nodriver", False) or not cls.needs_auth:
321
with cls.quota_lock:
322
chunks = []
323
async for chunk in cls.create_async_generator(
324
model=getattr(cls, "default_model", "auto"),
325
messages=[{"role": "user", "content": "Hi"}],
326
stream=False,
327
reasoning_effort=cls.default_reasoning_effort,
328
**kwargs,
329
):
330
if isinstance(chunk, str):
331
chunks.append(chunk)
332
if chunks:
333
return {"choices": [{"message": {"content": "".join(chunks)}}]}
334
raise NotImplementedError(
335
f"{cls.__name__} does not implement get_quota method"
336
)
337
319
if not api_key and cls.needs_auth:
338
320
raise MissingAuthError("API key is required.")
339
headers = {"authorization": f"Bearer {api_key}"} if api_key else {}
340
async with StreamSession() as session:
341
async with session.get(cls.quota_url, headers=headers) as response:
342
await raise_for_status(response)
343
return await response.json()
321
raise NotImplementedError(
322
f"{cls.__name__} does not implement get_quota method"
323
)
344
324
345
325
@staticmethod
346
326
@abstractmethod
@@ -787,7 +787,13 @@ if (searchQuery && textarea) {
787
787
(function() {
788
788
// 1. Target the specific element Kimi uses
789
789
// Inspect the page; if it's the main input, it might be a div with contenteditable
790
const editor = document.querySelector('[contenteditable="true"], [placeholder="Message DeepSeek"], .message-input-textarea');
790
const fields = [
791
'[contenteditable="true"]',
792
'[placeholder="Message DeepSeek"]',
793
'.message-input-textarea',
794
'#chat-input'
795
];
796
const editor = document.querySelector(fields.join(', '));
791
797
if (!editor) return;
792
798
793
799
// 2. Get your query
@@ -820,10 +826,15 @@ if (searchQuery && textarea) {
820
826
821
827
822
828
// 8. Click the send button if it exists
823
document.querySelector('[data-send-label="Send message"],'
824
+ ' [aria-label="Send message"], '
825
+ '[class^="MessageInput__Submit--"], '
826
+ ' .send-button-container, .send-button')?.click();
829
const sendButtonSelectors = [
830
'[data-send-label="Send message"]',
831
'[aria-label="Send message"]',
832
'[class^="MessageInput__Submit--"]',
833
'.send-button-container',
834
'.send-button',
835
'#send-message-button', // z.ai
836
];
837
document.querySelector(sendButtonSelectors.join(', '))?.click();
827
838
828
839
// 8. Click the send button on gemini.google.com
829
840
const trigger = (el, etype) => {