Kenqt commited on
Commit
1053eaa
Β·
verified Β·
1 Parent(s): bee1838

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +61 -0
  2. README.md +4 -4
  3. app.py +464 -0
  4. requirements.txt +9 -0
Dockerfile ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ ENV DEBIAN_FRONTEND=noninteractive
4
+ ENV PYTHONUNBUFFERED=1
5
+ ENV GRADIO_SERVER_NAME=0.0.0.0
6
+ ENV GRADIO_SERVER_PORT=7860
7
+
8
+ RUN apt-get update && apt-get install -y \
9
+ wget \
10
+ gnupg \
11
+ unzip \
12
+ curl \
13
+ ca-certificates \
14
+ fonts-liberation \
15
+ libasound2 \
16
+ libatk-bridge2.0-0 \
17
+ libatk1.0-0 \
18
+ libatspi2.0-0 \
19
+ libcups2 \
20
+ libdbus-1-3 \
21
+ libdrm2 \
22
+ libgbm1 \
23
+ libgtk-3-0 \
24
+ libnspr4 \
25
+ libnss3 \
26
+ libwayland-client0 \
27
+ libxcomposite1 \
28
+ libxdamage1 \
29
+ libxfixes3 \
30
+ libxkbcommon0 \
31
+ libxrandr2 \
32
+ xdg-utils \
33
+ libu2f-udev \
34
+ libvulkan1 \
35
+ && rm -rf /var/lib/apt/lists/*
36
+
37
+ RUN wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && \
38
+ apt-get update && \
39
+ apt-get install -y ./google-chrome-stable_current_amd64.deb && \
40
+ rm google-chrome-stable_current_amd64.deb && \
41
+ rm -rf /var/lib/apt/lists/*
42
+
43
+ WORKDIR /app
44
+
45
+ RUN useradd -m -u 1000 user
46
+ USER user
47
+ ENV HOME=/home/user \
48
+ PATH=/home/user/.local/bin:$PATH
49
+
50
+ WORKDIR $HOME/app
51
+
52
+ COPY --chown=user requirements.txt .
53
+
54
+ RUN pip install --no-cache-dir --upgrade pip && \
55
+ pip install --no-cache-dir -r requirements.txt
56
+
57
+ COPY --chown=user app.py .
58
+
59
+ EXPOSE 7860
60
+
61
+ CMD ["python", "app.py"]
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: H
3
- emoji: πŸ”₯
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: docker
7
  pinned: false
8
  ---
 
1
  ---
2
+ title: HCaptcha
3
+ emoji: πŸš€
4
+ colorFrom: yellow
5
+ colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  ---
app.py ADDED
@@ -0,0 +1,464 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from flask import Flask, request, jsonify
4
+ import time
5
+ import random
6
+ import json
7
+ import re
8
+ from io import BytesIO
9
+ from typing import Dict, List
10
+ import tempfile
11
+ import os
12
+ import subprocess
13
+ import zipfile
14
+
15
+ from selenium import webdriver
16
+ from selenium.webdriver.common.by import By
17
+ from selenium.webdriver.support.ui import WebDriverWait
18
+ from selenium.webdriver.support import expected_conditions as EC
19
+ from selenium.webdriver.chrome.options import Options
20
+ from selenium.webdriver.chrome.service import Service
21
+ from selenium.webdriver.common.action_chains import ActionChains
22
+
23
+ from PIL import Image
24
+ import numpy as np
25
+ import cv2
26
+ from transformers import CLIPProcessor, CLIPModel
27
+ import torch
28
+ import requests
29
+
30
+ app = Flask(__name__)
31
+
32
+ print("πŸ”„ Loading CLIP model...")
33
+ clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
34
+ clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
35
+ print("βœ… Model ready")
36
+
37
+ TMPFILES_HOST = "https://tmpfiles.org/api/v1/upload"
38
+
39
+ def upload_to_tmpfiles(image_path: str) -> str:
40
+ try:
41
+ with open(image_path, 'rb') as f:
42
+ files = {'file': f}
43
+ response = requests.post(TMPFILES_HOST, files=files, timeout=10)
44
+
45
+ if response.status_code == 200:
46
+ data = response.json()
47
+ url = data.get('data', {}).get('url', '')
48
+ return url.replace('tmpfiles.org/', 'tmpfiles.org/dl/')
49
+ return None
50
+ except Exception as e:
51
+ print(f"Upload error: {e}")
52
+ return None
53
+
54
+ def get_chrome_version():
55
+ try:
56
+ result = subprocess.run(['google-chrome', '--version'], capture_output=True, text=True)
57
+ version = result.stdout.strip().split()[-1]
58
+ major_version = version.split('.')[0]
59
+ return version, major_version
60
+ except:
61
+ return "unknown", "unknown"
62
+
63
+ def download_chromedriver():
64
+ full_version, major_version = get_chrome_version()
65
+ print(f"πŸ” Chrome version: {full_version}")
66
+
67
+ driver_dir = os.path.expanduser("~/.chromedriver")
68
+ driver_path = os.path.join(driver_dir, "chromedriver")
69
+
70
+ if os.path.exists(driver_path):
71
+ print(f"βœ… ChromeDriver exists: {driver_path}")
72
+ return driver_path
73
+
74
+ os.makedirs(driver_dir, exist_ok=True)
75
+
76
+ try:
77
+ url = f"https://googlechromelabs.github.io/chrome-for-testing/LATEST_RELEASE_{major_version}"
78
+ response = requests.get(url, timeout=10)
79
+ driver_version = response.text.strip()
80
+ print(f"πŸ” ChromeDriver version: {driver_version}")
81
+
82
+ download_url = f"https://storage.googleapis.com/chrome-for-testing-public/{driver_version}/linux64/chromedriver-linux64.zip"
83
+ print(f"⬇️ Downloading...")
84
+
85
+ zip_path = os.path.join(driver_dir, "chromedriver.zip")
86
+ response = requests.get(download_url, timeout=60)
87
+
88
+ with open(zip_path, 'wb') as f:
89
+ f.write(response.content)
90
+
91
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
92
+ zip_ref.extractall(driver_dir)
93
+
94
+ extracted_driver = os.path.join(driver_dir, "chromedriver-linux64", "chromedriver")
95
+
96
+ if os.path.exists(extracted_driver):
97
+ os.rename(extracted_driver, driver_path)
98
+ os.chmod(driver_path, 0o755)
99
+
100
+ os.remove(zip_path)
101
+
102
+ import shutil
103
+ extracted_folder = os.path.join(driver_dir, "chromedriver-linux64")
104
+ if os.path.exists(extracted_folder):
105
+ shutil.rmtree(extracted_folder)
106
+
107
+ print(f"βœ… ChromeDriver installed: {driver_path}")
108
+ return driver_path
109
+
110
+ except Exception as e:
111
+ print(f"❌ Download failed: {e}")
112
+ raise e
113
+
114
+ def human_move_to_element(driver, element):
115
+ action = ActionChains(driver)
116
+ current_x = random.randint(100, 500)
117
+ current_y = random.randint(100, 500)
118
+ target_x = element.location['x'] + element.size['width'] / 2
119
+ target_y = element.location['y'] + element.size['height'] / 2
120
+ steps = random.randint(15, 30)
121
+
122
+ for i in range(steps):
123
+ progress = i / steps
124
+ noise_x = random.uniform(-3, 3)
125
+ noise_y = random.uniform(-3, 3)
126
+ intermediate_x = current_x + (target_x - current_x) * progress + noise_x
127
+ intermediate_y = current_y + (target_y - current_y) * progress + noise_y
128
+ action.move_by_offset(intermediate_x - current_x, intermediate_y - current_y)
129
+ current_x = intermediate_x
130
+ current_y = intermediate_y
131
+ time.sleep(random.uniform(0.001, 0.005))
132
+
133
+ action.perform()
134
+ time.sleep(random.uniform(0.1, 0.3))
135
+
136
+ def human_click(driver, element):
137
+ human_move_to_element(driver, element)
138
+ time.sleep(random.uniform(0.05, 0.15))
139
+ element.click()
140
+ time.sleep(random.uniform(0.1, 0.2))
141
+
142
+ def create_driver():
143
+ print("πŸ”„ Initializing ChromeDriver...")
144
+
145
+ driver_path = download_chromedriver()
146
+
147
+ options = Options()
148
+ options.add_argument('--headless=new')
149
+ options.add_argument('--no-sandbox')
150
+ options.add_argument('--disable-dev-shm-usage')
151
+ options.add_argument('--disable-gpu')
152
+ options.add_argument('--window-size=1920,1080')
153
+ options.add_argument('--disable-blink-features=AutomationControlled')
154
+ options.add_argument('--disable-web-security')
155
+ options.add_argument('--disable-features=IsolateOrigins,site-per-process')
156
+ options.add_argument('--allow-running-insecure-content')
157
+ options.add_argument('--disable-setuid-sandbox')
158
+ options.add_argument('--disable-software-rasterizer')
159
+ options.add_argument('--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36')
160
+ options.add_experimental_option("excludeSwitches", ["enable-automation"])
161
+ options.add_experimental_option('useAutomationExtension', False)
162
+ options.set_capability('goog:loggingPrefs', {'browser': 'ALL'})
163
+
164
+ service = Service(driver_path)
165
+ service.log_path = '/dev/null'
166
+
167
+ driver = webdriver.Chrome(service=service, options=options)
168
+ driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
169
+
170
+ driver.set_window_size(1920, 1080)
171
+
172
+ print("βœ… Driver ready")
173
+ return driver
174
+
175
+ def solve_image_with_ai(image: Image.Image, target: str) -> float:
176
+ inputs = clip_processor(
177
+ text=[f"a photo of {target}", "other objects"],
178
+ images=image,
179
+ return_tensors="pt",
180
+ padding=True
181
+ )
182
+ outputs = clip_model(**inputs)
183
+ probs = outputs.logits_per_image.softmax(dim=1)
184
+ return probs[0][0].item()
185
+
186
+ def detect_puzzle_points(image: Image.Image) -> List[Dict]:
187
+ img_array = np.array(image)
188
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
189
+ circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, dp=1, minDist=30, param1=50, param2=30, minRadius=8, maxRadius=30)
190
+ points = []
191
+
192
+ if circles is not None:
193
+ circles = np.uint16(np.around(circles))
194
+ for i, (x, y, r) in enumerate(circles[0, :]):
195
+ points.append({'number': i + 1, 'x': int(x), 'y': int(y), 'radius': int(r)})
196
+ points = sorted(points, key=lambda p: (p['y'], p['x']))
197
+
198
+ return points
199
+
200
+ def solve_puzzle_captcha(driver, canvas_element) -> bool:
201
+ try:
202
+ png = canvas_element.screenshot_as_png()
203
+ img = Image.open(BytesIO(png))
204
+ points = detect_puzzle_points(img)
205
+
206
+ if not points:
207
+ return False
208
+
209
+ print(f"βœ… Detected {len(points)} puzzle points")
210
+ action = ActionChains(driver)
211
+ start_point = points[0]
212
+ offset_x = start_point['x'] - canvas_element.size['width'] / 2
213
+ offset_y = start_point['y'] - canvas_element.size['height'] / 2
214
+ action.move_to_element_with_offset(canvas_element, offset_x, offset_y)
215
+ action.click_and_hold()
216
+
217
+ for point in points[1:]:
218
+ offset_x = point['x'] - canvas_element.size['width'] / 2
219
+ offset_y = point['y'] - canvas_element.size['height'] / 2
220
+ action.move_to_element_with_offset(canvas_element, offset_x, offset_y)
221
+ time.sleep(random.uniform(0.1, 0.3))
222
+
223
+ action.release()
224
+ action.perform()
225
+ time.sleep(1)
226
+ return True
227
+ except Exception as e:
228
+ print(f"❌ Puzzle error: {e}")
229
+ return False
230
+
231
+ def extract_challenge_info(driver):
232
+ try:
233
+ WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, "prompt-text")))
234
+ prompt_text = driver.find_element(By.CLASS_NAME, "prompt-text").text
235
+ match = re.search(r'Select all (\w+)', prompt_text, re.IGNORECASE)
236
+ target = match.group(1).rstrip('s').lower() if match else "unknown"
237
+
238
+ images = []
239
+ img_elements = driver.find_elements(By.CSS_SELECTOR, ".task-image")
240
+
241
+ for img_elem in img_elements:
242
+ png = img_elem.screenshot_as_png()
243
+ img = Image.open(BytesIO(png))
244
+ images.append(img)
245
+
246
+ return {'target': target, 'images': images, 'count': len(images), 'elements': img_elements}
247
+ except Exception as e:
248
+ print(f"❌ Extract error: {e}")
249
+ return None
250
+
251
+ def solve_grid_captcha(driver, challenge_info) -> bool:
252
+ try:
253
+ selected_indices = []
254
+
255
+ for idx, img in enumerate(challenge_info['images']):
256
+ confidence = solve_image_with_ai(img, challenge_info['target'])
257
+ if confidence > 0.55:
258
+ selected_indices.append(idx)
259
+ print(f"βœ… Image {idx}: {confidence:.2%}")
260
+ else:
261
+ print(f"⏭️ Image {idx}: {confidence:.2%}")
262
+
263
+ if not selected_indices:
264
+ return False
265
+
266
+ for idx in selected_indices:
267
+ if idx < len(challenge_info['elements']):
268
+ human_click(driver, challenge_info['elements'][idx])
269
+
270
+ submit_btn = driver.find_element(By.CSS_SELECTOR, ".button-submit")
271
+ human_click(driver, submit_btn)
272
+ time.sleep(2)
273
+ return True
274
+ except Exception as e:
275
+ print(f"❌ Grid error: {e}")
276
+ return False
277
+
278
+ def detect_challenge_type(driver) -> str:
279
+ try:
280
+ if driver.find_elements(By.CSS_SELECTOR, "canvas"):
281
+ return "puzzle"
282
+ elif driver.find_elements(By.CSS_SELECTOR, ".task-image"):
283
+ return "image_grid"
284
+ else:
285
+ return "unknown"
286
+ except:
287
+ return "unknown"
288
+
289
+ def screenshot_and_upload(element) -> str:
290
+ try:
291
+ png = element.screenshot_as_png()
292
+
293
+ temp_dir = tempfile.gettempdir()
294
+ temp_path = os.path.join(temp_dir, f"hcaptcha_{int(time.time())}_{random.randint(1000,9999)}.png")
295
+
296
+ with open(temp_path, 'wb') as f:
297
+ f.write(png)
298
+
299
+ url = upload_to_tmpfiles(temp_path)
300
+
301
+ try:
302
+ os.remove(temp_path)
303
+ except:
304
+ pass
305
+
306
+ return url
307
+ except Exception as e:
308
+ print(f"❌ Screenshot error: {e}")
309
+ return None
310
+
311
+ def solve_hcaptcha(sitekey: str, url: str) -> Dict:
312
+ driver = None
313
+ screenshot_urls = []
314
+
315
+ try:
316
+ driver = create_driver()
317
+ driver.get(url)
318
+ print(f"βœ… Opened: {url}")
319
+ time.sleep(random.uniform(2, 4))
320
+
321
+ WebDriverWait(driver, 20).until(EC.presence_of_element_located((By.CSS_SELECTOR, "iframe[src*='hcaptcha']")))
322
+ iframes = driver.find_elements(By.CSS_SELECTOR, "iframe[src*='hcaptcha']")
323
+
324
+ checkbox_iframe = None
325
+ for iframe in iframes:
326
+ if 'checkbox' in iframe.get_attribute('src'):
327
+ checkbox_iframe = iframe
328
+ break
329
+
330
+ if not checkbox_iframe:
331
+ return {'success': False, 'error': 'Checkbox not found'}
332
+
333
+ screenshot_url = screenshot_and_upload(checkbox_iframe)
334
+ if screenshot_url:
335
+ screenshot_urls.append({'type': 'checkbox', 'url': screenshot_url})
336
+
337
+ driver.switch_to.frame(checkbox_iframe)
338
+ checkbox = WebDriverWait(driver, 10).until(EC.element_to_be_clickable((By.ID, "checkbox")))
339
+ human_click(driver, checkbox)
340
+ print("βœ… Checkbox clicked")
341
+ driver.switch_to.default_content()
342
+ time.sleep(random.uniform(2, 4))
343
+
344
+ iframes = driver.find_elements(By.CSS_SELECTOR, "iframe[src*='hcaptcha']")
345
+ challenge_iframe = None
346
+ for iframe in iframes:
347
+ if 'challenge' in iframe.get_attribute('src'):
348
+ challenge_iframe = iframe
349
+ break
350
+
351
+ if not challenge_iframe:
352
+ token = extract_token(driver)
353
+ if token:
354
+ return {'success': True, 'token': token, 'method': 'checkbox_only', 'screenshots': screenshot_urls}
355
+ else:
356
+ return {'success': False, 'error': 'No token', 'screenshots': screenshot_urls}
357
+
358
+ screenshot_url = screenshot_and_upload(challenge_iframe)
359
+ if screenshot_url:
360
+ screenshot_urls.append({'type': 'challenge', 'url': screenshot_url})
361
+
362
+ driver.switch_to.frame(challenge_iframe)
363
+ challenge_type = detect_challenge_type(driver)
364
+ print(f"🎯 Challenge: {challenge_type}")
365
+
366
+ if challenge_type == "puzzle":
367
+ canvas = driver.find_element(By.CSS_SELECTOR, "canvas")
368
+ success = solve_puzzle_captcha(driver, canvas)
369
+ if not success:
370
+ driver.switch_to.default_content()
371
+ return {'success': False, 'error': 'Puzzle failed', 'screenshots': screenshot_urls}
372
+
373
+ elif challenge_type == "image_grid":
374
+ challenge_info = extract_challenge_info(driver)
375
+ if not challenge_info:
376
+ driver.switch_to.default_content()
377
+ return {'success': False, 'error': 'Extract failed', 'screenshots': screenshot_urls}
378
+
379
+ print(f"🎯 Target: {challenge_info['target']}")
380
+ success = solve_grid_captcha(driver, challenge_info)
381
+ if not success:
382
+ driver.switch_to.default_content()
383
+ return {'success': False, 'error': 'Grid failed', 'screenshots': screenshot_urls}
384
+ else:
385
+ driver.switch_to.default_content()
386
+ return {'success': False, 'error': f'Unknown: {challenge_type}', 'screenshots': screenshot_urls}
387
+
388
+ driver.switch_to.default_content()
389
+ time.sleep(3)
390
+ token = extract_token(driver)
391
+
392
+ if token:
393
+ return {'success': True, 'token': token, 'challenge_type': challenge_type, 'screenshots': screenshot_urls}
394
+ else:
395
+ return {'success': False, 'error': 'No token', 'screenshots': screenshot_urls}
396
+
397
+ except Exception as e:
398
+ import traceback
399
+ error_detail = traceback.format_exc()
400
+ print(f"❌ Error: {error_detail}")
401
+ return {'success': False, 'error': str(e), 'error_detail': error_detail, 'screenshots': screenshot_urls}
402
+ finally:
403
+ if driver:
404
+ try:
405
+ driver.quit()
406
+ except:
407
+ pass
408
+
409
+ def extract_token(driver):
410
+ try:
411
+ time.sleep(2)
412
+ token_element = driver.find_element(By.NAME, "h-captcha-response")
413
+ token = token_element.get_attribute("value")
414
+ if token and len(token) > 10:
415
+ return token
416
+
417
+ token_element = driver.find_element(By.NAME, "g-recaptcha-response")
418
+ token = token_element.get_attribute("value")
419
+ if token and len(token) > 10:
420
+ return token
421
+
422
+ return None
423
+ except:
424
+ return None
425
+
426
+ @app.route('/solve', methods=['GET'])
427
+ def solve():
428
+ sitekey = request.args.get('sitekey')
429
+ url = request.args.get('url')
430
+
431
+ if not sitekey or not url:
432
+ return jsonify({'success': False, 'error': 'Missing params'}), 400
433
+
434
+ print(f"\n{'='*60}")
435
+ print(f"πŸš€ Solving: {url}")
436
+ print(f"{'='*60}\n")
437
+
438
+ result = solve_hcaptcha(sitekey, url)
439
+ return jsonify(result)
440
+
441
+ @app.route('/health', methods=['GET'])
442
+ def health():
443
+ full_version, major_version = get_chrome_version()
444
+ return jsonify({
445
+ 'status': 'online',
446
+ 'model': 'CLIP',
447
+ 'chrome': full_version
448
+ })
449
+
450
+ @app.route('/', methods=['GET'])
451
+ def root():
452
+ return jsonify({
453
+ 'service': 'hCaptcha Solver',
454
+ 'version': '2.3',
455
+ 'endpoints': {
456
+ '/solve': 'GET ?sitekey=X&url=Y',
457
+ '/health': 'GET'
458
+ }
459
+ })
460
+
461
+ if __name__ == '__main__':
462
+ print("\nπŸ€– hCaptcha Solver API v2.3")
463
+ print("🌐 http://0.0.0.0:7860\n")
464
+ app.run(host='0.0.0.0', port=7860, debug=False)
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ flask==3.0.0
2
+ selenium==4.16.0
3
+ Pillow==10.1.0
4
+ opencv-python-headless==4.8.1.78
5
+ numpy==1.26.2
6
+ requests==2.31.0
7
+ transformers==4.36.2
8
+ torch==2.1.2
9
+ torchvision==0.16.2