Files changed (1) hide show
  1. app.py +50 -57
app.py CHANGED
@@ -1,69 +1,67 @@
 
 
 
 
1
  import asyncio
2
- import uvicorn
3
  from fastapi import FastAPI
4
  from pydantic import BaseModel
5
  from playwright.async_api import async_playwright
6
  from contextlib import asynccontextmanager
7
 
8
  # --- Configuration ---
9
- # Limit simultaneous browser instances to prevent memory crashes
10
- MAX_CONCURRENT_REQUESTS = 3
11
- semaphore = asyncio.Semaphore(MAX_CONCURRENT_REQUESTS)
12
 
13
  class GeminiBot:
14
  def __init__(self):
15
  self.playwright = None
16
- self.browser = None
17
 
18
  async def start(self):
19
  self.playwright = await async_playwright().start()
20
- self.browser = await self.playwright.chromium.launch(
 
 
21
  headless=True,
22
- args=["--no-sandbox", "--disable-blink-features=AutomationControlled"]
23
  )
24
- print("--- Gemini Bot (Concurrent Stateless Mode) Ready ---")
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  async def process_chat(self, message: str):
27
- # The semaphore ensures only N requests run at once
28
- async with semaphore:
29
- context = await self.browser.new_context(
30
- user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
31
- )
32
- page = await context.new_page()
33
 
34
- try:
35
- # Block heavy assets for speed
36
- await page.route("**/*", lambda route: route.abort() if route.request.resource_type in
37
- ["image", "font", "media", "stylesheet"] else route.continue_())
38
-
39
- await page.goto("https://gemini.google.com", wait_until="networkidle")
40
-
41
- # Logic: If not logged in, this will wait until timeout
42
- chat_box = page.locator("div[contenteditable='true']")
43
- await chat_box.wait_for(timeout=30000)
44
- await chat_box.fill(message)
45
- await page.keyboard.press("Enter")
46
-
47
- # Wait for the response container
48
- await page.wait_for_selector("message-content", timeout=45000)
49
-
50
- # Extract the last message
51
- current_text = await page.evaluate("""() => {
52
- const msgs = document.querySelectorAll('message-content');
53
- return msgs.length > 0 ? msgs[msgs.length - 1].innerText : "No response";
54
- }""")
55
-
56
- return {"text": current_text}
57
-
58
- except Exception as e:
59
- return {"text": f"Error: {str(e)}"}
60
- finally:
61
- # Session is wiped here, regardless of success or failure
62
- await context.close()
63
-
64
- async def stop(self):
65
- if self.browser: await self.browser.close()
66
- if self.playwright: await self.playwright.stop()
67
 
68
  bot = GeminiBot()
69
 
@@ -71,17 +69,12 @@ bot = GeminiBot()
71
  async def lifespan(app: FastAPI):
72
  await bot.start()
73
  yield
74
- await bot.stop()
 
75
 
76
  app = FastAPI(lifespan=lifespan)
77
 
78
- class ChatRequest(BaseModel):
79
- message: str
80
-
81
  @app.post("/chat")
82
- async def chat_api(request: ChatRequest):
83
- # This call is non-blocking to other concurrent requests
84
- return await bot.process_chat(request.message)
85
-
86
- if __name__ == "__main__":
87
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
1
+ import os
2
+ import time
3
+ import base64
4
+ import httpx
5
  import asyncio
 
6
  from fastapi import FastAPI
7
  from pydantic import BaseModel
8
  from playwright.async_api import async_playwright
9
  from contextlib import asynccontextmanager
10
 
11
  # --- Configuration ---
12
+ IMGBB_API_KEY = os.getenv("IMGBB_API_KEY", "5765c99f7a977515ba320a689330e508")
13
+ SAVE_FOLDER = "/tmp/downloaded_images"
14
+ os.makedirs(SAVE_FOLDER, exist_ok=True)
15
 
16
  class GeminiBot:
17
  def __init__(self):
18
  self.playwright = None
19
+ self.context = None
20
 
21
  async def start(self):
22
  self.playwright = await async_playwright().start()
23
+ # Persistent context maintains login state in ./user_data
24
+ self.context = await self.playwright.chromium.launch_persistent_context(
25
+ user_data_dir="./user_data",
26
  headless=True,
27
+ args=["--no-sandbox", "--disable-dev-shm-usage"]
28
  )
29
+
30
+ async def upload_to_imgbb(self, image_path):
31
+ if not IMGBB_API_KEY or not image_path or not os.path.exists(image_path):
32
+ return None
33
+ try:
34
+ with open(image_path, "rb") as file:
35
+ img_base64 = base64.b64encode(file.read()).decode('utf-8')
36
+ async with httpx.AsyncClient() as client:
37
+ response = await client.post("https://api.imgbb.com/1/upload", data={"key": IMGBB_API_KEY, "image": img_base64})
38
+ return response.json()['data']['url'] if response.status_code == 200 else None
39
+ except:
40
+ return None
41
 
42
  async def process_chat(self, message: str):
43
+ # Create a new page for parallel execution
44
+ page = await self.context.new_page()
45
+ try:
46
+ await page.goto("https://gemini.google.com")
47
+ # Wait for login state
48
+ await page.wait_for_selector("div[contenteditable='true']", timeout=60000)
49
 
50
+ chat_box = page.locator("div[contenteditable='true']").first
51
+ await chat_box.fill(message)
52
+ await page.keyboard.press("Enter")
53
+
54
+ # Wait for response
55
+ await page.wait_for_selector("message-content", timeout=60000)
56
+ await asyncio.sleep(3) # Wait for final rendering
57
+
58
+ # Extract text
59
+ text = await page.evaluate("document.querySelectorAll('message-content')[document.querySelectorAll('message-content').length-1].innerText")
60
+ return {"text": text, "image_url": None} # Image extraction simplified for parallel safety
61
+ except Exception as e:
62
+ return {"text": f"Error: {str(e)}", "image_url": None}
63
+ finally:
64
+ await page.close()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  bot = GeminiBot()
67
 
 
69
  async def lifespan(app: FastAPI):
70
  await bot.start()
71
  yield
72
+ if bot.context: await bot.context.close()
73
+ if bot.playwright: await bot.playwright.stop()
74
 
75
  app = FastAPI(lifespan=lifespan)
76
 
 
 
 
77
  @app.post("/chat")
78
+ async def chat_api(request: BaseModel):
79
+ msg = request.dict().get("message")
80
+ return {"status": "success", "data": await bot.process_chat(msg)}