Opera8 commited on
Commit
632f40e
·
verified ·
1 Parent(s): 01098f1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +32 -329
app.py CHANGED
@@ -1,351 +1,54 @@
1
- import os
2
- import time
3
- import asyncio
4
- import uvicorn
5
- from fastapi import FastAPI, Form, File, UploadFile, HTTPException
6
- from fastapi.responses import HTMLResponse, JSONResponse
7
- from playwright.async_api import async_playwright
8
- from contextlib import asynccontextmanager
9
-
10
- UPLOAD_DIR = "temp_uploads"
11
- os.makedirs(UPLOAD_DIR, exist_ok=True)
12
-
13
- # تعریف متغیرهای سراسری برای زنده نگه داشتن مرورگر در حافظه سرور
14
- global_browser = None
15
- pw_instance = None
16
-
17
- @asynccontextmanager
18
- async def lifespan(app: FastAPI):
19
- # گام اول: لود کردن مرورگر کروم فقط برای یک بار در زمان استارت آپ سرور
20
- global global_browser, pw_instance
21
- print("⚡ در حال راه‌اندازی مرورگر سراسری کروم...")
22
- pw_instance = await async_playwright().start()
23
- global_browser = await pw_instance.chromium.launch(
24
- headless=True,
25
- args=[
26
- "--no-sandbox",
27
- "--disable-setuid-sandbox",
28
- "--disable-dev-shm-usage",
29
- "--disable-blink-features=AutomationControlled"
30
- ]
31
- )
32
- print("🚀 مرورگر سراسری با موفقیت لود شد و آماده خدمت‌رسانی است.")
33
- yield
34
- # گام پایانی: بستن شکیل مرورگر در زمان خاموش شدن سرور هاگینگ فیس
35
- print("🛑 در حال بستن مرورگر سراسری...")
36
- if global_browser:
37
- await global_browser.close()
38
- if pw_instance:
39
- await pw_instance.stop()
40
-
41
- # تزریق لایف‌اسپن به هسته FastAPI
42
- app = FastAPI(lifespan=lifespan)
43
-
44
- HTML_CHAT_INTERFACE = """
45
- <!DOCTYPE html>
46
- <html lang="fa" dir="rtl">
47
- <head>
48
- <meta charset="UTF-8">
49
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
50
- <title>Google AI Free Chatbot + Vision</title>
51
- <style>
52
- :root {
53
- --bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
54
- --glass-bg: rgba(30, 41, 59, 0.7);
55
- --glass-border: rgba(255, 255, 255, 0.08);
56
- --text-main: #f8fafc;
57
- --accent-color: #38bdf8;
58
- --user-bubble: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);
59
- --ai-bubble: rgba(255, 255, 255, 0.06);
60
- }
61
-
62
- body {
63
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Tahoma, sans-serif;
64
- margin: 0; padding: 0; background: var(--bg-gradient); color: var(--text-main);
65
- height: 100vh; display: flex; align-items: center; justify-content: center; overflow: hidden;
66
- }
67
-
68
- .chat-container {
69
- width: 100%; max-width: 850px; height: 85vh; background: var(--glass-bg);
70
- backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
71
- border: 1px solid var(--glass-border); border-radius: 24px;
72
- display: flex; flex-direction: column; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); margin: 20px;
73
- }
74
-
75
- .chat-header { padding: 20px 24px; border-bottom: 1px solid var(--glass-border); display: flex; align-items: center; gap: 12px; }
76
- .chat-header .status-dot { width: 10px; height: 10px; background: #4ade80; border-radius: 50%; box-shadow: 0 0 10px #4ade80; }
77
- .chat-header h2 { margin: 0; font-size: 18px; font-weight: 600; color: var(--accent-color); }
78
- .messages-box { flex: 1; padding: 24px; overflow-y: auto; display: flex; flex-direction: column; gap: 16px; }
79
- .messages-box::-webkit-scrollbar { width: 6px; }
80
- .messages-box::-webkit-scrollbar-track { background: transparent; }
81
- .messages-box::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.1); border-radius: 10px; }
82
-
83
- .message { max-width: 75%; padding: 14px 18px; border-radius: 18px; font-size: 15px; line-height: 1.6; word-wrap: break-word; animation: fadeIn 0.3s ease forwards; }
84
- .message.user { background: var(--user-bubble); align-self: flex-start; border-bottom-right-radius: 4px; box-shadow: 0 4px 12px rgba(2, 132, 199, 0.2); }
85
- .message.ai { background: var(--ai-bubble); border: 1px solid var(--glass-border); align-self: flex-end; border-bottom-left-radius: 4px; white-space: pre-wrap; }
86
-
87
- .input-wrapper { border-top: 1px solid var(--glass-border); background: rgba(15, 23, 42, 0.4); border-bottom-left-radius: 24px; border-bottom-right-radius: 24px; padding: 15px 24px; display: flex; flex-direction: column; gap: 10px; }
88
- .preview-container { display: none; align-items: center; gap: 10px; background: rgba(255, 255, 255, 0.05); padding: 8px 12px; border-radius: 10px; width: fit-content; border: 1px solid var(--glass-border); }
89
- .preview-container img { width: 40px; height: 40px; object-fit: cover; border-radius: 6px; }
90
- .preview-container .remove-btn { color: #ef4444; cursor: pointer; font-weight: bold; font-size: 14px; }
91
-
92
- .input-area { display: flex; gap: 12px; align-items: center; }
93
- .input-area input[type="text"] { flex: 1; background: rgba(255, 255, 255, 0.05); border: 1px solid var(--glass-border); padding: 14px 20px; border-radius: 14px; color: white; font-size: 15px; outline: none; transition: all 0.2s; }
94
- .input-area input[type="text"]:focus { border-color: var(--accent-color); background: rgba(255, 255, 255, 0.08); box-shadow: 0 0 12px rgba(56, 189, 248, 0.15); }
95
-
96
- .upload-label { background: rgba(255, 255, 255, 0.05); border: 1px solid var(--glass-border); padding: 12px; border-radius: 14px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; }
97
- .upload-label:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--accent-color); }
98
- .upload-label svg { width: 22px; height: 22px; fill: var(--text-main); }
99
-
100
- .input-area button { background: var(--accent-color); color: #0f172a; border: none; padding: 14px 24px; border-radius: 14px; font-size: 15px; font-weight: bold; cursor: pointer; transition: all 0.2s; }
101
- .input-area button:hover { background: #7dd3fc; transform: translateY(-1px); }
102
- .input-area button:disabled { background: #64748b; color: #1e293b; cursor: not-allowed; transform: none; }
103
-
104
- .typing-indicator { display: flex; gap: 5px; padding: 12px 18px; background: var(--ai-bubble); border-radius: 12px; align-self: flex-end; border: 1px solid var(--glass-border); }
105
- .typing-indicator span { width: 8px; height: 8px; background: var(--accent-color); border-radius: 50%; animation: bounce 1.4s infinite ease-in-out both; }
106
- .typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
107
- .typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
108
-
109
- @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
110
- @keyframes bounce { 0%, 80%, 100% { transform: scale(0); } 40% { transform: scale(1.0); } }
111
- </style>
112
- </head>
113
- <body>
114
-
115
- <div class="chat-container">
116
- <div class="chat-header">
117
- <div class="status-dot"></div>
118
- <h2>دستیار مالتی‌مدال گوگل (متن + تصویر کاملاً رایگان)</h2>
119
- </div>
120
- <div class="messages-box" id="messagesBox">
121
- <div class="message ai">سلام! من متصل به هوشواره گوگل هستم. می‌توانید علاوه بر متن، تصویر هم برایم بفرستید تا بررسی کنم.</div>
122
- </div>
123
 
124
- <div class="input-wrapper">
125
- <div class="preview-container" id="previewContainer">
126
- <img src="" id="imagePreview">
127
- <span class="remove-btn" onclick="removeSelectedImage()">✕ حذف</span>
128
- </div>
129
-
130
- <div class="input-area">
131
- <input type="file" id="fileInput" accept="image/*" style="display: none;" onchange="previewImage(event)">
132
- <label for="fileInput" class="upload-label" title="آپلود تصویر">
133
- <svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.66 1.34 3 3 3s3-1.34 3-3V5c0-2.48-2.02-4.5-4.5-4.5S7 2.52 7 5v12.5c0 3.59 2.91 6.5 6.5 6.5s6.5-2.91 6.5-6V6h-1.5z"/></svg>
134
- </label>
135
-
136
- <input type="text" id="userInput" placeholder="پیام یا توضیح مربوط به تصویر را بنویسید..." onkeypress="handleKeyPress(event)">
137
- <button id="sendBtn" onclick="sendMessage()">ارسال</button>
138
- </div>
139
- </div>
140
- </div>
141
-
142
- <script>
143
- const messagesBox = document.getElementById('messagesBox');
144
- const userInput = document.getElementById('userInput');
145
- const fileInput = document.getElementById('fileInput');
146
- const sendBtn = document.getElementById('sendBtn');
147
- const previewContainer = document.getElementById('previewContainer');
148
- const imagePreview = document.getElementById('imagePreview');
149
-
150
- function handleKeyPress(e) { if (e.key === 'Enter') sendMessage(); }
151
-
152
- function previewImage(event) {
153
- const file = event.target.files[0];
154
- if (file) {
155
- const reader = new FileReader();
156
- reader.onload = function(e) { imagePreview.src = e.target.result; previewContainer.style.display = 'flex'; }
157
- reader.readAsDataURL(file);
158
- }
159
- }
160
-
161
- function removeSelectedImage() { fileInput.value = ''; previewContainer.style.display = 'none'; imagePreview.src = ''; }
162
-
163
- async function sendMessage() {
164
- const text = userInput.value.trim();
165
- const file = fileInput.files[0];
166
- if (!text && !file) return;
167
-
168
- if (file) appendImageMessage(imagePreview.src, 'user');
169
- if (text) appendMessage(text, 'user');
170
-
171
- userInput.value = '';
172
- removeSelectedImage();
173
- setLoading(true);
174
-
175
- const formData = new FormData();
176
- formData.append('message', text || "این تصویر را تحلیل کن");
177
- if (file) formData.append('image', file);
178
-
179
- try {
180
- const response = await fetch('/api/chat', { method: 'POST', body: formData });
181
- const data = await response.json();
182
- if (response.ok) {
183
- appendMessage(data.response, 'ai');
184
- } else {
185
- appendMessage("خطا: " + (data.detail || "مشکلی در دریافت پاسخ رخ داد."), 'ai');
186
- }
187
- } catch (error) {
188
- appendMessage("خطای شبکه: دریافت پاسخ از سرور انجام نشد.", 'ai');
189
- } finally {
190
- setLoading(false);
191
- }
192
- }
193
-
194
- function appendMessage(text, sender) {
195
- const msgDiv = document.createElement('div'); msgDiv.classList.add('message', sender);
196
- msgDiv.innerText = text; messagesBox.appendChild(msgDiv); messagesBox.scrollTop = messagesBox.scrollHeight;
197
- }
198
-
199
- function appendImageMessage(src, sender) {
200
- const msgDiv = document.createElement('div'); msgDiv.classList.add('message', sender); msgDiv.style.padding = '8px';
201
- const img = document.createElement('img'); img.src = src; img.style.maxWidth = '200px'; img.style.maxHeight = '200px'; img.style.borderRadius = '12px'; img.style.display = 'block';
202
- msgDiv.appendChild(img); messagesBox.appendChild(msgDiv); messagesBox.scrollTop = messagesBox.scrollHeight;
203
- }
204
-
205
- function setLoading(isLoading) {
206
- if (isLoading) {
207
- userInput.disabled = true; sendBtn.disabled = true;
208
- const indicator = document.createElement('div'); indicator.classList.add('typing-indicator'); indicator.id = 'typingIndicator';
209
- indicator.innerHTML = '<span></span><span></span><span></span>'; messagesBox.appendChild(indicator); messagesBox.scrollTop = messagesBox.scrollHeight;
210
- } else {
211
- userInput.disabled = false; sendBtn.disabled = false;
212
- const indicator = document.getElementById('typingIndicator'); if (indicator) indicator.remove(); userInput.focus();
213
- }
214
- }
215
- </script>
216
- </body>
217
- </html>
218
- """
219
-
220
- @app.get("/", response_class=HTMLResponse)
221
- async def chat_interface():
222
- return HTML_CHAT_INTERFACE
223
-
224
- @app.post("/api/chat")
225
- async def chat_endpoint(message: str = Form(...), image: UploadFile = File(None)):
226
  global global_browser
227
- if not global_browser:
228
- raise HTTPException(status_code=500, detail="وب سرور کروم هنوز آماده نشده است.")
229
-
230
- saved_image_path = None
231
- if image:
232
- try:
233
- saved_image_path = os.path.join(UPLOAD_DIR, f"{int(time.time())}_{image.filename}")
234
- with open(saved_image_path, "wb") as buffer:
235
- buffer.write(await image.read())
236
- except Exception as e:
237
- raise HTTPException(status_code=500, detail=f"خطا در ذخیره‌سازی تصویر: {str(e)}")
238
-
239
- url = "https://www.google.com/search?q=سلام&udm=50"
240
- full_text = ""
241
 
242
- # ساخت یک Context و Page جدید در کسر از ثانیه روی مرورگر زنده سراسری
243
  context = await global_browser.new_context(
244
  user_agent="Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
245
  viewport={"width": 412, "height": 915},
246
  locale="fa-IR", timezone_id="Asia/Tehran"
247
  )
248
-
249
  page = await context.new_page()
250
 
251
  try:
252
- # لود اولیه با سرعت بالا
253
- await page.goto(url, wait_until="commit", timeout=30000)
254
  await page.wait_for_timeout(500)
255
 
256
- # فرآیند مستقل و خطا ناپذیر آپلود تصویر مستقیم به لایه Input گوگل
257
- if saved_image_path and os.path.exists(saved_image_path):
258
- try:
259
- file_input = await page.query_selector("input[type='file']")
260
- if file_input:
261
- await file_input.set_input_files(saved_image_path)
262
- await page.wait_for_timeout(1000) # زمان کوتاه ۱ ثانیه‌ای برای کش شدن فایل در مرورگر
263
- except Exception as e:
264
- print(f"Bypassed image file injection: {e}")
265
-
266
- # پیدا کردن فیلد متنی و تایپ کوئری جدید
267
- input_box = None
268
- candidates = await page.query_selector_all("textarea, input, [contenteditable='true']")
269
- for el in candidates:
270
- try:
271
- if await el.is_visible():
272
- placeholder = await el.get_attribute("placeholder") or ""
273
- if any(w in placeholder for w in ["بپرسید", "ask", "پیام", "چطور", "هرچه"]):
274
- input_box = el
275
- break
276
- except:
277
- continue
278
-
279
- if not input_box:
280
- input_box = await page.query_selector("textarea") or await page.query_selector("input[type='text']")
281
-
282
  if input_box:
283
  await input_box.focus()
284
- await input_box.fill(message)
285
- await page.wait_for_timeout(200)
286
  await input_box.press("Enter")
287
 
288
- # کلیک ضربتی روی دکمه ارسال در صورت عدم فعال شدن اینتر
289
- try:
290
- send_buttons = await page.query_selector_all("button, [role='button']")
291
- for btn in send_buttons:
292
- if await btn.is_visible():
293
- aria = (await btn.get_attribute("aria-label") or "").lower()
294
- if any(w in aria for w in ["send", "ارسال", "arrow", "up"]):
295
- await btn.click(timeout=1000)
296
- break
297
- except:
298
- pass
299
-
300
- # 🪐 مانیتورینگ هوشمند (Smart Pooling Loop): چک کردن هر ۴۰۰ میلی‌ثانیه برای دریافت در لحظه پاسخ
301
- for _ in range(30): # حداکثر ۱۲ ثانیه نگه می‌دارد اما معمولاً در ۲ الی ۳ ثانیه اول پاسخ کامل می‌شود
302
  await page.wait_for_timeout(400)
303
  full_text = await page.evaluate("() => document.body.innerText")
304
- if "هوشواره ممکن است اشتباه" in full_text or "بیشتر بدانید" in full_text or "پاسخ‌های مربوط به افراد" in full_text:
305
  break
306
-
307
- except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  await context.close()
309
- raise HTTPException(status_code=500, detail=f"خطا در رندر سرور هوش مصنوعی: {str(e)}")
310
- finally:
311
- await context.close() # بستن فوری پیج و کانتکست جهت آزاد سازی رم
312
- if saved_image_path and os.path.exists(saved_image_path):
313
- try: os.remove(saved_image_path)
314
- except: pass
315
-
316
- # 🧼 پورت تصفیه متن (Text Purifying Algorithm)
317
- lines = full_text.split('\n')
318
- cleaned_lines = []
319
-
320
- # واژه‌های ممنوعه لایه‌های کاربری سرچ گوگل که باید کلاً نادیده گرفته شوند
321
- global_junk = ["تصاویر", "ویدیوها", "اخبار", "نقشه‌ها", "خرید کردن", "کتاب‌ها", "مالی", "حالت موضوع‌محور", "ورود", "همه", "ویدئوها", "ارسال", "پاسخ «حالت هوشواره‌ای» آماده است", "All items removed"]
322
-
323
- for line in lines:
324
- line_str = line.strip()
325
- if not line_str:
326
- continue
327
- # حذف خطوط ابزارها
328
- if any(junk == line_str for junk in global_junk):
329
- continue
330
- # به محض رسیدن به کادر هشدار سلب مسئولیت گوگل، فوتور را قطع کن
331
- if "هوشواره ممکن است اشتباه" in line_str or "در پاسخ‌های" in line_str or "بیشتر بدانید" in line_str:
332
- break
333
 
334
- # ترفند طلایی: اگر خط جاری دقیقاً برابر با پیام ارسالی کاربر بود، تمام زباله‌ه��ی بالای صفحه را تخلیه کن
335
- if line_str == message:
336
- cleaned_lines = []
337
- continue
338
-
339
- cleaned_lines.append(line_str)
340
-
341
- # بازسازی نهایی متن
342
- final_response = "\n".join(cleaned_lines).strip()
343
-
344
- # لایه پشتیبان در صورت عدم تطابق متون
345
- if not final_response:
346
- final_response = "پاسخی دریافت نشد یا ساختار خروجی تغییر کرده است."
347
-
348
- return JSONResponse(content={"response": final_response})
349
-
350
- if __name__ == "__main__":
351
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ # یک پروکسی لینک مستقیم برای استفاده در برنامه‌ها و ربات‌های دیگر
2
+ @app.get("/proxy")
3
+ async def proxy_endpoint(q: str):
4
+ if not q.strip():
5
+ return {"error": "عبارت جستجو نمی‌تواند خالی باشد"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  global global_browser
8
+ url = f"https://www.google.com/search?q={q}&udm=50"
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
 
10
  context = await global_browser.new_context(
11
  user_agent="Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Mobile Safari/537.36",
12
  viewport={"width": 412, "height": 915},
13
  locale="fa-IR", timezone_id="Asia/Tehran"
14
  )
 
15
  page = await context.new_page()
16
 
17
  try:
18
+ await page.goto(url, wait_until="commit", timeout=20000)
 
19
  await page.wait_for_timeout(500)
20
 
21
+ # شبیه‌سازی زدن اینتر
22
+ input_box = await page.query_selector("textarea") or await page.query_selector("input[type='text']")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  if input_box:
24
  await input_box.focus()
 
 
25
  await input_box.press("Enter")
26
 
27
+ # چک کردن داینامیک پایان تایپ گوگل
28
+ full_text = ""
29
+ for _ in range(25):
 
 
 
 
 
 
 
 
 
 
 
30
  await page.wait_for_timeout(400)
31
  full_text = await page.evaluate("() => document.body.innerText")
32
+ if "هوشواره ممکن است اشتباه" in full_text or "بیشتر بدانید" in full_text:
33
  break
34
+
35
+ # تصفیه و استخراج متن خالص
36
+ lines = full_text.split('\n')
37
+ cleaned_lines = []
38
+ for line in lines:
39
+ line_str = line.strip()
40
+ if not line_str or any(j == line_str for j in ["تصاویر", "ویدیوها", "اخبار", "نقشه‌ها", "خرید کردن", "کتاب‌ها", "مالی", "حالت موضوع‌محور", "ورود", "همه", "ارسال"]):
41
+ continue
42
+ if "هوشواره ممکن است اشتباه" in line_str or "بیشتر بدانید" in line_str:
43
+ break
44
+ if line_str == q:
45
+ cleaned_lines = []
46
+ continue
47
+ cleaned_lines.append(line_str)
48
+
49
  await context.close()
50
+ return HTMLResponse(content="\n".join(cleaned_lines).strip(), status_code=200)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ except Exception as e:
53
+ await context.close()
54
+ return HTMLResponse(content=f"Error: {str(e)}", status_code=500)