返回提交历史
Modified
g4f/gui/server/backend_api.py
+4
-3
Modified
g4f/gui/server/providers.html
+22
-1
Modified
g4f/gui/server/website.py
+1
-1
Modified
g4f/requests/cdp.py
+73
-28
XFEstudio/gpt4free
Update providers.html
51b6d038
代码差异
4 个文件
+100
-33
@@ -157,14 +157,13 @@ class Backend_Api(Api):
157
157
logger.error(f"Secret validation failed: {e}")
158
158
return False
159
159
160
@app.route("/backend-api/v2/public-key", methods=["GET", "POST"])
160
@app.route("/backend-api/v2/public-key", methods=["GET"])
161
161
def get_public_key():
162
162
if not has_crypto:
163
163
return (
164
164
jsonify(
165
165
{"error": {"message": "Crypto support is not available"}}
166
166
),
167
501,
168
167
)
169
168
# try:
170
169
# diff = time.time() - int(base64.b64decode(request.cookies.get("fingerprint")).decode())
@@ -173,13 +172,15 @@ class Backend_Api(Api):
173
172
# if diff > 60 * 60 * 2:
174
173
# return jsonify({"error": {"message": "Please refresh the page"}}), 403
175
174
# Send the public key to the client for encryption
176
return jsonify(
175
response = jsonify(
177
176
{
178
177
"public_key": public_key_pem.decode(),
179
178
"data": encrypt_data(sub_public_key, str(int(time.time()))),
180
179
"user": request.headers.get("x-user", "error"),
181
180
}
182
181
)
182
response.headers["cache-control"] = "no-cache"
183
return response
183
184
184
185
@app.route("/pa/providers", methods=["GET"])
185
186
async def pa_providers():
@@ -3,7 +3,28 @@
3
3
<head>
4
4
<meta charset="UTF-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
<title>Providers</title>
6
<title>AI Providers - GPT4Free | Browse 100+ Free AI Models</title>
7
<meta name="description" content="Browse all AI providers supported by GPT4Free. Find free AI chat, image generation, and text models from OpenAI, Anthropic, Google, Meta, and more.">
8
<meta name="keywords" content="AI providers, GPT4Free, free AI, OpenAI, Anthropic, Google AI, Meta AI, text generation, image generation, AI chat, free AI models">
9
<meta name="author" content="GPT4Free Team">
10
11
<!-- Open Graph -->
12
<meta property="og:type" content="website">
13
<meta property="og:title" content="AI Providers - GPT4Free">
14
<meta property="og:description" content="Browse all AI providers supported by GPT4Free. Free access to 100+ AI models.">
15
<meta property="og:url" content="https://g4f.space/providers">
16
<meta property="og:site_name" content="GPT4Free">
17
<meta property="og:image" content="https://g4f.space/screenshot/providers">
18
19
<!-- Twitter Card -->
20
<meta name="twitter:card" content="summary_large_image">
21
<meta name="twitter:title" content="AI Providers - GPT4Free">
22
<meta name="twitter:description" content="Browse all AI providers supported by GPT4Free. Free access to 100+ AI models.">
23
<meta name="twitter:image" content="https://g4f.space/screenshot/providers">
24
25
<!-- Canonical -->
26
<link rel="canonical" href="https://g4f.space/providers">
27
7
28
<style>
8
29
:root {
9
30
--primary: #6e48aa;
@@ -320,7 +320,7 @@ class Website:
320
320
"""
321
321
322
322
# Screenshot / logo section
323
logo_url = f"/screenshot?url={p.get('url', (p.get('base_url', p.get('baseUrl', ''))).replace('https://', '').replace('http://', '').replace('playground.ai.', '').replace('api.', '').replace('router.', '').split('/')[0])}"
323
logo_url = f"/screenshot?url={p.get('url', (p.get('base_url', p.get('baseUrl', ''))).replace('https://', '').replace('http://', '').replace('playground.ai.', '').replace('console.', '').replace('api.', '').replace('router.', '').split('/')[0])}"
324
324
screenshot_html = f"""
325
325
<div class="screenshot-section">
326
326
<img src="{logo_url}" alt="{escape(p['name'])} logo" class="provider-logo"
@@ -287,6 +287,8 @@ def get_shared_browser(host: str, preferred_port: int, headless: bool = True) ->
287
287
"--disable-features=PrivacySandboxSettings4",
288
288
"--disable-blink-features=AutomationControlled",
289
289
"--remote-allow-origins=*",
290
"--disable-web-security",
291
"--disable-features=IsolateOrigins,site-per-process",
290
292
]
291
293
if headless:
292
294
cmd.append("--headless=new")
@@ -670,34 +672,70 @@ class CDPSession:
670
672
return False
671
673
672
674
async def click_accept_button(self) -> bool:
673
"""Find and click an 'Accept' or 'Einwilligen' button."""
675
"""Find and click an 'Accept' or 'Einwilligen' button, including inside iframes."""
674
676
js_code = """
675
(() => {
676
const buttons = document.querySelectorAll('button, input[type="submit"]');
677
(() => {
678
const targetTexts = ['Accept', 'Accept all', 'Einwilligen', 'Alle akzeptieren', 'Zustimmen und weiter'];
679
680
function searchDocument(doc, offsetX = 0, offsetY = 0) {
681
try {
682
if (!doc) return null;
683
684
// 1. Search buttons in the current document
685
const buttons = doc.querySelectorAll('button, input[type="submit"], [role="button"]');
677
686
for (let button of buttons) {
678
const text = button.textContent.trim();
679
if (text === 'Accept' || text == 'Accept all' || text === 'Einwilligen' || text === 'Alle akzeptieren') {
687
const text = (button.innerText || button.value || button.textContent || '').trim();
688
if (targetTexts.includes(text)) {
689
690
// NEU: Scrollt das Element/den Container in den sichtbaren Bereich
691
button.scrollIntoView({ block: 'center', inline: 'center' });
692
693
// Wichtig: Nach dem Scrollen müssen die Koordinaten neu berechnet werden!
680
694
const rect = button.getBoundingClientRect();
681
return {
682
x: rect.left + window.scrollX,
683
y: rect.top + window.scrollY,
684
width: rect.width,
685
height: rect.height
686
};
695
696
if (rect.width > 0 && rect.height > 0) {
697
return [
698
offsetX + rect.left + rect.width / 2,
699
offsetY + rect.top + rect.height / 2
700
];
701
}
687
702
}
688
703
}
689
return null;
690
})()
691
"""
704
705
// 2. Search inside nested iframes
706
const iframes = doc.querySelectorAll('iframe');
707
for (let iframe of iframes) {
708
try {
709
const iframeDoc = iframe.contentDocument || iframe.contentWindow?.document;
710
if (iframeDoc) {
711
const iframeRect = iframe.getBoundingClientRect();
712
const res = searchDocument(
713
iframeDoc,
714
offsetX + iframeRect.left,
715
offsetY + iframeRect.top
716
);
717
if (res) return res;
718
}
719
} catch (e) {
720
// Cross-origin iframe security restriction
721
}
722
}
723
} catch (e) {}
724
return null;
725
}
726
727
// window.scrollX/Y wird am Ende aufgeschlagen, falls du absolute Page-Koordinaten brauchst
728
return searchDocument(document, window.scrollX, window.scrollY);
729
})()
730
"""
692
731
try:
693
732
rect = await self.evaluate_js(js_code)
694
if rect and isinstance(rect, dict) and rect.get("width", 0) > 0:
695
center_x = int(rect["x"] + rect["width"] / 2)
696
center_y = int(rect["y"] + rect["height"] / 2)
697
await self.click(center_x, center_y)
733
debug.log(f"Accept button rect: {rect}")
734
if rect and isinstance(rect, list) and len(rect) == 2:
735
await self.click(int(rect[0]), int(rect[1]))
698
736
return True
699
737
except Exception as e:
700
logger.debug(f"Failed to click accept button: {e}")
738
debug.log(f"Failed to click accept button: {e}")
701
739
return False
702
740
703
741
async def bypass_turnstile(self):
@@ -746,7 +784,7 @@ class CDPSession:
746
784
"""Navigate to a URL and capture a screenshot, caching the result."""
747
785
datekey = datetime.date.today().isoformat()
748
786
screenshot_dir = get_screenshot_dir(datekey)
749
filename = f"{secure_filename(url.replace('https://', '').replace('http://', ''))}.png"
787
filename = f"{secure_filename(url.replace('https://', '').replace('http://', ''))}.jpg"
750
788
filepath = os.path.join(screenshot_dir, filename)
751
789
debug.log(f"Screenshot path: {filepath}")
752
790
@@ -759,22 +797,28 @@ class CDPSession:
759
797
await self.wait_for_network_idle(idle_time=5, timeout=15.0)
760
798
if await self.evaluate_js('!document.doctype'):
761
799
raise RuntimeError(f"Failed to load page {url} for screenshot, doctype={await self.evaluate_js('String(document.doctype)')}")
762
await asyncio.sleep(3)
763
800
# Try to click any "Accept" or "Einwilligen" cookie consent buttons
764
await self.click_accept_button()
765
await asyncio.sleep(0.5)
801
for _ in range(5):
802
debug.log("Attempting to click accept button...")
803
await asyncio.sleep(1)
804
if await self.click_accept_button():
805
debug.log("Clicked accept button.")
806
await asyncio.sleep(1)
807
break
808
await self.wait_for_network_idle(idle_time=5, timeout=15.0)
766
809
result = await self.call("Page.captureScreenshot")
767
810
image_bytes = base64.b64decode(result["data"])
768
811
769
# Resize to 1200x675
812
# Resize to 1200x630 and save as JPEG to reduce file size
770
813
if has_pillow:
771
814
from io import BytesIO
772
815
image = Image.open(BytesIO(image_bytes))
773
816
image = image.resize((1200, 630), Image.Resampling.LANCZOS)
774
817
width, height = image.size
775
818
image = image.crop((0, 0, max(0, width - 14), height))
819
image = image.convert("RGB") # JPEG does not support alpha channel
776
820
output = BytesIO()
777
image.save(output, format="PNG")
821
image.save(output, format="JPEG", quality=85, optimize=True)
778
822
image_bytes = output.getvalue()
779
823
780
824
Path(filepath).write_bytes(image_bytes)
@@ -1083,7 +1127,7 @@ class SyncCDPSession:
1083
1127
"""Navigate to a URL and capture a screenshot, caching the result."""
1084
1128
datekey = datetime.date.today().isoformat()
1085
1129
screenshots_dir = get_screenshot_dir(datekey)
1086
filename = secure_filename(url)
1130
filename = f"{secure_filename(url)}.jpg"
1087
1131
filepath = os.path.join(screenshots_dir, filename)
1088
1132
1089
1133
if os.path.exists(filepath):
@@ -1098,15 +1142,16 @@ class SyncCDPSession:
1098
1142
result = self.call("Page.captureScreenshot")
1099
1143
image_bytes = base64.b64decode(result["data"])
1100
1144
1101
# Resize to 1200x675
1145
# Resize to 1200x675 and save as JPEG to reduce file size
1102
1146
if has_pillow:
1103
1147
from io import BytesIO
1104
1148
image = Image.open(BytesIO(image_bytes))
1105
1149
image = image.resize((1200, 675), Image.Resampling.LANCZOS)
1106
1150
width, height = image.size
1107
1151
image = image.crop((0, 0, max(0, width - 10), height))
1152
image = image.convert("RGB") # JPEG does not support alpha channel
1108
1153
output = BytesIO()
1109
image.save(output, format="PNG")
1154
image.save(output, format="JPEG", quality=85, optimize=True)
1110
1155
image_bytes = output.getvalue()
1111
1156
1112
1157
Path(filepath).write_bytes(image_bytes)