XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Refactor screenshot handling to use secure filenames and improve URL encoding

3b62a0a3
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

4 个文件 +59 -71
Modified g4f/api/__init__.py +6 -5
@@ -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 )
Modified g4f/gui/server/website.py +34 -38
@@ -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"]))}';
Modified g4f/providers/base_provider.py +3 -23
@@ -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
Modified g4f/requests/cdp.py +16 -5
@@ -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) => {