sonuprasad23 commited on
Commit
565afe0
·
1 Parent(s): 187e9a6
Files changed (4) hide show
  1. Dockerfile +20 -6
  2. requirements.txt +3 -1
  3. server.py +39 -37
  4. worker.py +103 -39
Dockerfile CHANGED
@@ -1,3 +1,4 @@
 
1
  FROM python:3.11-slim
2
 
3
  WORKDIR /app
@@ -5,18 +6,20 @@ WORKDIR /app
5
  ENV DEBIAN_FRONTEND=noninteractive
6
  ENV TZ=Etc/UTC
7
 
8
- # Install Chrome and dependencies
9
  RUN apt-get update && apt-get install -y --no-install-recommends \
10
  chromium \
11
  chromium-driver \
12
  ca-certificates \
13
  fonts-liberation \
14
- libnss3 \
15
- libxss1 \
16
  libasound2 \
17
- libxtst6 \
18
  libatk-bridge2.0-0 \
 
19
  libgtk-3-0 \
 
 
 
 
20
  libgbm1 \
21
  libxrandr2 \
22
  libxcomposite1 \
@@ -24,15 +27,26 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
24
  procps \
25
  && rm -rf /var/lib/apt/lists/*
26
 
27
- # Install Python packages
28
  COPY requirements.txt .
 
 
29
  RUN pip install --no-cache-dir -r requirements.txt
30
 
 
31
  USER 1000
32
 
 
 
 
 
 
 
33
  COPY --chown=1000:1000 . .
 
 
34
  RUN mkdir -p config
35
 
36
  EXPOSE 7860
37
 
38
- CMD ["python", "server.py"]
 
1
+ # Dockerfile
2
  FROM python:3.11-slim
3
 
4
  WORKDIR /app
 
6
  ENV DEBIAN_FRONTEND=noninteractive
7
  ENV TZ=Etc/UTC
8
 
9
+ # Install Chrome and dependencies for HuggingFace Spaces
10
  RUN apt-get update && apt-get install -y --no-install-recommends \
11
  chromium \
12
  chromium-driver \
13
  ca-certificates \
14
  fonts-liberation \
 
 
15
  libasound2 \
 
16
  libatk-bridge2.0-0 \
17
+ libdrm2 \
18
  libgtk-3-0 \
19
+ libnspr4 \
20
+ libnss3 \
21
+ libxss1 \
22
+ libxtst6 \
23
  libgbm1 \
24
  libxrandr2 \
25
  libxcomposite1 \
 
27
  procps \
28
  && rm -rf /var/lib/apt/lists/*
29
 
30
+ # Copy requirements first
31
  COPY requirements.txt .
32
+
33
+ # Install Python packages as root BEFORE switching users
34
  RUN pip install --no-cache-dir -r requirements.txt
35
 
36
+ # Now switch to non-root user for HuggingFace Spaces
37
  USER 1000
38
 
39
+ # Set environment for user 1000
40
+ ENV HOME=/tmp/hf-user
41
+ ENV PYTHONUSERBASE=/tmp/hf-user/.local
42
+ ENV PATH="/tmp/hf-user/.local/bin:$PATH"
43
+
44
+ # Copy application files
45
  COPY --chown=1000:1000 . .
46
+
47
+ # Create config directory
48
  RUN mkdir -p config
49
 
50
  EXPOSE 7860
51
 
52
+ CMD ["python", "server.py"]
requirements.txt CHANGED
@@ -5,4 +5,6 @@ Flask-Cors
5
  eventlet
6
  python-dotenv
7
  selenium
8
- psutil
 
 
 
5
  eventlet
6
  python-dotenv
7
  selenium
8
+ google-api-python-client
9
+ google-auth-httplib2
10
+ google-auth-oauthlib
server.py CHANGED
@@ -1,9 +1,13 @@
 
1
  import eventlet
2
  eventlet.monkey_patch()
3
 
4
  import pandas as pd
5
  import io
 
6
  import os
 
 
7
  from datetime import datetime
8
  from flask import Flask, Response
9
  from flask_socketio import SocketIO, emit
@@ -20,9 +24,11 @@ load_dotenv()
20
 
21
  app = Flask(__name__)
22
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
23
- FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'https://quantbot.netlify.app')
24
- CORS(app, resources={r"/*": {"origins": [FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500", "null"]}})
25
- socketio = SocketIO(app, cors_allowed_origins=[FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500", "null"], async_mode='eventlet')
 
 
26
 
27
  bot_instance = None
28
  session_data = {}
@@ -32,7 +38,9 @@ class EmailService:
32
  self.sender_email = os.getenv('EMAIL_SENDER')
33
  self.password = os.getenv('EMAIL_PASSWORD')
34
  self.smtp_server = "smtp.gmail.com"; self.smtp_port = 587
35
- if not self.sender_email or not self.password: print("[Email] WARNING: Email credentials not found in secrets.")
 
 
36
  def send_report(self, recipients, subject, body, attachments=None):
37
  if not self.sender_email or not self.password: return False
38
  try:
@@ -46,7 +54,8 @@ class EmailService:
46
  with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
47
  server.starttls(); server.login(self.sender_email, self.password); server.send_message(msg)
48
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
49
- except Exception as e: print(f"Failed to send email: {e}"); return False
 
50
 
51
  class GoogleDriveService:
52
  def __init__(self):
@@ -56,12 +65,15 @@ class GoogleDriveService:
56
  from google.oauth2 import service_account
57
  from googleapiclient.discovery import build
58
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
59
- if not base64_creds or not self.folder_id: raise ValueError("Google Drive secrets not found.")
 
60
  creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
61
  self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
62
  self.service = build('drive', 'v3', credentials=self.creds)
63
- print("[G-Drive] Service initialized securely from secrets.")
64
- except Exception as e: print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
 
 
65
  def upload_file(self, filename, file_content):
66
  if not self.service: return False
67
  try:
@@ -71,15 +83,18 @@ class GoogleDriveService:
71
  self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
72
  print(f"[G-Drive] File '{filename}' uploaded successfully.")
73
  return True
74
- except Exception as e: print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
 
75
 
76
  email_service = EmailService()
77
  drive_service = GoogleDriveService()
78
 
79
  def get_email_list():
80
  try:
81
- with open('config/emails.conf', 'r') as f: return [line.strip() for line in f if line.strip()]
82
- except FileNotFoundError: return []
 
 
83
 
84
  def run_automation_process(session_id):
85
  global bot_instance
@@ -97,11 +112,9 @@ def run_automation_process(session_id):
97
  socketio.emit('error', {'message': f'A fatal error occurred: {e}'})
98
  finally:
99
  socketio.emit('micro_status_update', {'message': 'Generating final reports...'})
100
- # We now call the report generation in the main thread via an event
101
- socketio.emit('request_finalization', {
102
- 'session_id': session_id, 'results': results,
103
- 'is_crash': is_crash, 'is_terminated': is_terminated
104
- })
105
 
106
  def generate_and_send_reports(session_id, results, is_crash_report=False, is_terminated=False):
107
  if not results:
@@ -118,7 +131,7 @@ def generate_and_send_reports(session_id, results, is_crash_report=False, is_ter
118
  attachments = {full_report_name: full_report_content, bad_report_name: bad_df.to_csv(index=False)}
119
  status_text = "terminated by user" if is_terminated else "crashed due to an error" if is_crash_report else "completed successfully"
120
  subject = f"Automation Report [{status_text.upper()}]: {custom_name}"
121
- body = f"""<html><body><h2>Hillside's Quantum Automation Report</h2><p><b>The process was {status_text}.</b></p><p><b>Total Patients in File:</b> {len(full_df)}</p><p><b>Processed in this run:</b> {len(results)}</p><p><b>Successful ('Done'):</b> {len([r for r in results if r['Status'] == 'Done'])}</p><p><b>Bad State ('Bad'):</b> {len([r for r in results if r['Status'] == 'Bad'])}</p><p>The full report is in Google Drive and also attached here, along with a list of 'Bad' status patients.</p></body></html>"""
122
  email_service.send_report(data.get('emails'), subject, body, attachments)
123
  socketio.emit('process_complete', {'message': f'Process {status_text}. Report sent.'})
124
 
@@ -129,7 +142,7 @@ def status_page():
129
 
130
  @socketio.on('connect')
131
  def handle_connect():
132
- print(f'Frontend connected.')
133
  emit('email_list', {'emails': get_email_list()})
134
 
135
  @socketio.on('initialize_session')
@@ -138,9 +151,12 @@ def handle_init(data):
138
  session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
139
  if bot_instance: bot_instance.shutdown()
140
  bot_instance = QuantumBot(socketio, app)
 
141
  is_success, error_message = bot_instance.initialize_driver()
142
- if is_success: emit('bot_initialized')
143
- else: emit('error', {'message': f'Failed to initialize automation bot: {error_message}'})
 
 
144
 
145
  @socketio.on('start_login')
146
  def handle_login(credentials):
@@ -156,6 +172,7 @@ def handle_otp(data):
156
  if is_success:
157
  emit('login_successful')
158
  session_id = 'user_session'
 
159
  socketio.start_background_task(run_automation_process, session_id)
160
  else: emit('error', {'message': f'OTP failed: {error_message}'})
161
 
@@ -163,26 +180,11 @@ def handle_otp(data):
163
  def handle_terminate():
164
  if bot_instance: print("Termination signal received."); bot_instance.stop()
165
 
166
- @socketio.on('request_finalization')
167
- def handle_finalize(data):
168
- print("Finalization requested by worker thread.")
169
- global bot_instance
170
- session_id = data.get('session_id')
171
- results = data.get('results', [])
172
- is_crash = data.get('is_crash', False)
173
- is_terminated = data.get('is_terminated', False)
174
-
175
- generate_and_send_reports(session_id, results, is_crash_report=is_crash, is_terminated=is_terminated)
176
- if bot_instance:
177
- bot_instance.shutdown()
178
- bot_instance = None
179
- if session_id in session_data:
180
- del session_data[session_id]
181
-
182
  if __name__ == '__main__':
183
  print("====================================================================")
184
  print(" 🤗 Hillside Automation Backend - HuggingFace Spaces")
185
  print(f" Frontend URL: {FRONTEND_ORIGIN}")
186
  print(f" Port: {os.getenv('PORT', 7860)}")
187
  print("====================================================================")
188
- socketio.run(app, host='0.0.0.0', port=int(os.getenv('PORT', 7860)))
 
 
1
+ # server.py
2
  import eventlet
3
  eventlet.monkey_patch()
4
 
5
  import pandas as pd
6
  import io
7
+ import threading
8
  import os
9
+ import base64
10
+ import json
11
  from datetime import datetime
12
  from flask import Flask, Response
13
  from flask_socketio import SocketIO, emit
 
24
 
25
  app = Flask(__name__)
26
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
27
+
28
+ # Configure CORS for both local development and your Netlify frontend
29
+ FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'https://quantbot.netlify.app')
30
+ CORS(app, resources={r"/*": {"origins": [FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500"]}})
31
+ socketio = SocketIO(app, cors_allowed_origins=[FRONTEND_ORIGIN, "http://localhost:3000", "http://127.0.0.1:5500"], async_mode='eventlet')
32
 
33
  bot_instance = None
34
  session_data = {}
 
38
  self.sender_email = os.getenv('EMAIL_SENDER')
39
  self.password = os.getenv('EMAIL_PASSWORD')
40
  self.smtp_server = "smtp.gmail.com"; self.smtp_port = 587
41
+ if not self.sender_email or not self.password:
42
+ print("[Email] WARNING: Email credentials not found in environment variables.")
43
+
44
  def send_report(self, recipients, subject, body, attachments=None):
45
  if not self.sender_email or not self.password: return False
46
  try:
 
54
  with smtplib.SMTP(self.smtp_server, self.smtp_port) as server:
55
  server.starttls(); server.login(self.sender_email, self.password); server.send_message(msg)
56
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
57
+ except Exception as e:
58
+ print(f"Failed to send email: {e}"); return False
59
 
60
  class GoogleDriveService:
61
  def __init__(self):
 
65
  from google.oauth2 import service_account
66
  from googleapiclient.discovery import build
67
  base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
68
+ if not base64_creds or not self.folder_id:
69
+ raise ValueError("Google Drive secrets not found.")
70
  creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
71
  self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
72
  self.service = build('drive', 'v3', credentials=self.creds)
73
+ print("[G-Drive] Service initialized securely from environment variables.")
74
+ except Exception as e:
75
+ print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
76
+
77
  def upload_file(self, filename, file_content):
78
  if not self.service: return False
79
  try:
 
83
  self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
84
  print(f"[G-Drive] File '{filename}' uploaded successfully.")
85
  return True
86
+ except Exception as e:
87
+ print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
88
 
89
  email_service = EmailService()
90
  drive_service = GoogleDriveService()
91
 
92
  def get_email_list():
93
  try:
94
+ with open('config/emails.conf', 'r') as f:
95
+ return [line.strip() for line in f if line.strip()]
96
+ except FileNotFoundError:
97
+ return []
98
 
99
  def run_automation_process(session_id):
100
  global bot_instance
 
112
  socketio.emit('error', {'message': f'A fatal error occurred: {e}'})
113
  finally:
114
  socketio.emit('micro_status_update', {'message': 'Generating final reports...'})
115
+ generate_and_send_reports(session_id, results, is_crash_report=is_crash, is_terminated=is_terminated)
116
+ if bot_instance: bot_instance.shutdown(); bot_instance = None
117
+ if session_id in session_data: del session_data[session_id]
 
 
118
 
119
  def generate_and_send_reports(session_id, results, is_crash_report=False, is_terminated=False):
120
  if not results:
 
131
  attachments = {full_report_name: full_report_content, bad_report_name: bad_df.to_csv(index=False)}
132
  status_text = "terminated by user" if is_terminated else "crashed due to an error" if is_crash_report else "completed successfully"
133
  subject = f"Automation Report [{status_text.upper()}]: {custom_name}"
134
+ body = f"""<html><body><h2>Hillside's Quantum Automation Report</h2><p><b>The process was {status_text}.</b></p><p><b>Total Patients in File:</b> {len(full_df)}</p><p><b>Processed in this run:</b> {len(results)}</p><p><b>Successful ('Done'):</b> {len([r for r in results if r['Status'] == 'Done'])}</p><p><b>Bad State ('Bad'):</b> {len([r for r in results if r['Status'] == 'Bad'])}</p><p>The full report and a list of 'Bad' status patients from this run are attached.</p></body></html>"""
135
  email_service.send_report(data.get('emails'), subject, body, attachments)
136
  socketio.emit('process_complete', {'message': f'Process {status_text}. Report sent.'})
137
 
 
142
 
143
  @socketio.on('connect')
144
  def handle_connect():
145
+ print(f'Frontend connected from: {FRONTEND_ORIGIN}')
146
  emit('email_list', {'emails': get_email_list()})
147
 
148
  @socketio.on('initialize_session')
 
151
  session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
152
  if bot_instance: bot_instance.shutdown()
153
  bot_instance = QuantumBot(socketio, app)
154
+
155
  is_success, error_message = bot_instance.initialize_driver()
156
+ if is_success:
157
+ emit('bot_initialized')
158
+ else:
159
+ emit('error', {'message': f'Failed to initialize automation bot: {error_message}'})
160
 
161
  @socketio.on('start_login')
162
  def handle_login(credentials):
 
172
  if is_success:
173
  emit('login_successful')
174
  session_id = 'user_session'
175
+ # Fixed syntax for background task
176
  socketio.start_background_task(run_automation_process, session_id)
177
  else: emit('error', {'message': f'OTP failed: {error_message}'})
178
 
 
180
  def handle_terminate():
181
  if bot_instance: print("Termination signal received."); bot_instance.stop()
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
  if __name__ == '__main__':
184
  print("====================================================================")
185
  print(" 🤗 Hillside Automation Backend - HuggingFace Spaces")
186
  print(f" Frontend URL: {FRONTEND_ORIGIN}")
187
  print(f" Port: {os.getenv('PORT', 7860)}")
188
  print("====================================================================")
189
+ socketio.run(app, host='0.0.0.0', port=int(os.getenv('PORT', 7860)))
190
+
worker.py CHANGED
@@ -1,14 +1,15 @@
1
- import pandas as pd
2
  import time
3
- import io
4
  import threading
 
 
5
  from selenium import webdriver
6
  from selenium.webdriver.chrome.service import Service as ChromeService
7
  from selenium.webdriver.chrome.options import Options as ChromeOptions
8
  from selenium.webdriver.common.by import By
9
  from selenium.webdriver.support.ui import WebDriverWait
10
  from selenium.webdriver.support import expected_conditions as EC
11
- from selenium.common.exceptions import TimeoutException, NoSuchElementException, ElementNotInteractableException
 
12
 
13
  class QuantumBot:
14
  def __init__(self, socketio, app):
@@ -18,26 +19,52 @@ class QuantumBot:
18
  self.DEFAULT_TIMEOUT = 30
19
  self.termination_event = threading.Event()
20
 
 
 
 
 
 
 
 
 
 
21
  def initialize_driver(self):
22
  try:
23
  self.micro_status("Initializing headless browser...")
 
 
 
 
24
  options = ChromeOptions()
25
  options.binary_location = "/usr/bin/chromium"
26
- user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
27
- options.add_argument(f"user-agent={user_agent}")
 
 
 
28
  options.add_argument("--headless=new")
29
- options.add_argument("--window-size=1920,1080")
30
  options.add_argument("--no-sandbox")
31
  options.add_argument("--disable-dev-shm-usage")
32
  options.add_argument("--disable-gpu")
 
 
 
 
 
 
 
 
33
 
34
  service = ChromeService(executable_path="/usr/bin/chromedriver")
35
  self.driver = webdriver.Chrome(service=service, options=options)
36
 
 
37
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
38
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
39
  })
 
40
  return True, None
 
41
  except Exception as e:
42
  error_message = f"Message: {str(e)}"
43
  print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
@@ -58,11 +85,19 @@ class QuantumBot:
58
  self.driver.get("https://gateway.quantumepay.com/")
59
  time.sleep(2)
60
  self.micro_status("Entering credentials...")
61
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Username"))).send_keys(username)
62
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "Password"))).send_keys(password)
63
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
 
 
 
 
 
 
64
  self.micro_status("Waiting for OTP screen...")
65
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
 
 
66
  return True, None
67
  except Exception as e:
68
  error_message = f"Error during login: {str(e)}"
@@ -75,9 +110,13 @@ class QuantumBot:
75
  otp_digits = list(otp)
76
  for i in range(6):
77
  self.driver.find_element(By.ID, f"code{i+1}").send_keys(otp_digits[i])
78
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
 
 
79
  self.micro_status("Verifying login success...")
80
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
 
 
81
  return True, None
82
  except Exception as e:
83
  error_message = f"Error during OTP submission: {str(e)}"
@@ -90,66 +129,91 @@ class QuantumBot:
90
  if self.termination_event.is_set():
91
  print("[Bot] Termination detected. Stopping process.")
92
  break
93
-
 
 
 
 
94
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
95
  status = self._process_single_patient(patient_name)
96
  results.append({'Name': patient_name, 'Status': status})
97
-
98
  with self.app.app_context():
99
- bad_count = len([r for r in results if r['Status'] == 'Bad'])
100
  self.socketio.emit('log_update', {'name': patient_name, 'status': status})
101
- self.socketio.emit('stats_update', {
102
- 'processed': len(results),
103
- 'remaining': len(patient_list) - len(results),
104
- 'bad': bad_count
105
- })
106
  return results
107
 
108
  def _process_single_patient(self, patient_name):
109
  try:
110
  self.micro_status(f"Navigating to Void page for '{patient_name}'")
111
  self.driver.get("https://gateway.quantumepay.com/credit-card/void")
112
-
113
  search_successful = False
114
  for attempt in range(15):
115
  try:
116
  self.micro_status(f"Searching for patient (Attempt {attempt + 1})...")
117
- WebDriverWait(self.driver, 2).until(EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'table-wrapper')]")))
118
- search_box = WebDriverWait(self.driver, 2).until(EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='Search']")))
 
 
 
 
119
  search_box.click(); time.sleep(0.5)
120
  search_box.clear(); time.sleep(0.5)
121
  search_box.send_keys(patient_name)
122
  search_successful = True
123
  break
124
- except Exception: time.sleep(1)
125
- if not search_successful: raise Exception("Failed to search for patient.")
126
-
 
 
 
127
  time.sleep(3)
128
  self.micro_status("Opening transaction details...")
129
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
130
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))).click()
131
-
 
 
 
 
132
  self.micro_status("Adding to Vault...")
133
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))).click()
134
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))).click()
135
-
 
 
 
 
136
  try:
137
  self.micro_status("Verifying success and saving...")
138
- company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
 
 
139
  company_input.clear()
140
  company_input.send_keys(patient_name)
141
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))).click()
142
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))).click()
 
 
 
 
143
  time.sleep(5)
144
  return 'Done'
145
  except TimeoutException:
146
  self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
147
- WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))).click()
 
 
148
  return 'Bad'
149
  except Exception as e:
150
  print(f"An error occurred while processing {patient_name}: {e}")
151
  return 'Error'
152
-
153
  def shutdown(self):
154
- if self.driver:
155
- self.driver.quit()
 
 
 
 
 
 
 
1
  import time
 
2
  import threading
3
+ import subprocess
4
+
5
  from selenium import webdriver
6
  from selenium.webdriver.chrome.service import Service as ChromeService
7
  from selenium.webdriver.chrome.options import Options as ChromeOptions
8
  from selenium.webdriver.common.by import By
9
  from selenium.webdriver.support.ui import WebDriverWait
10
  from selenium.webdriver.support import expected_conditions as EC
11
+ from selenium.common.exceptions import TimeoutException
12
+
13
 
14
  class QuantumBot:
15
  def __init__(self, socketio, app):
 
19
  self.DEFAULT_TIMEOUT = 30
20
  self.termination_event = threading.Event()
21
 
22
+ def _kill_chrome_processes(self):
23
+ """Kill any existing Chrome processes to prevent conflicts"""
24
+ try:
25
+ subprocess.run(['pkill', '-f', 'chrome'], capture_output=True, text=True, timeout=5)
26
+ subprocess.run(['pkill', '-f', 'chromium'], capture_output=True, text=True, timeout=5)
27
+ time.sleep(1)
28
+ except:
29
+ pass
30
+
31
  def initialize_driver(self):
32
  try:
33
  self.micro_status("Initializing headless browser...")
34
+
35
+ # Kill any existing Chrome processes first
36
+ self._kill_chrome_processes()
37
+
38
  options = ChromeOptions()
39
  options.binary_location = "/usr/bin/chromium"
40
+
41
+ # DON'T set --user-data-dir - let Chrome handle it automatically
42
+ # This prevents the "already in use" error
43
+
44
+ # Minimal required flags for HuggingFace Spaces
45
  options.add_argument("--headless=new")
 
46
  options.add_argument("--no-sandbox")
47
  options.add_argument("--disable-dev-shm-usage")
48
  options.add_argument("--disable-gpu")
49
+ options.add_argument("--remote-debugging-port=0")
50
+ options.add_argument("--window-size=1920,1080")
51
+ options.add_argument("--no-first-run")
52
+ options.add_argument("--disable-extensions")
53
+
54
+ # User agent for better compatibility
55
+ user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
56
+ options.add_argument(f"--user-agent={user_agent}")
57
 
58
  service = ChromeService(executable_path="/usr/bin/chromedriver")
59
  self.driver = webdriver.Chrome(service=service, options=options)
60
 
61
+ # Anti-detection
62
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
63
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
64
  })
65
+
66
  return True, None
67
+
68
  except Exception as e:
69
  error_message = f"Message: {str(e)}"
70
  print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
 
85
  self.driver.get("https://gateway.quantumepay.com/")
86
  time.sleep(2)
87
  self.micro_status("Entering credentials...")
88
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
89
+ EC.presence_of_element_located((By.ID, "Username"))
90
+ ).send_keys(username)
91
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
92
+ EC.presence_of_element_located((By.ID, "Password"))
93
+ ).send_keys(password)
94
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
95
+ EC.element_to_be_clickable((By.ID, "login"))
96
+ ).click()
97
  self.micro_status("Waiting for OTP screen...")
98
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
99
+ EC.presence_of_element_located((By.ID, "code1"))
100
+ )
101
  return True, None
102
  except Exception as e:
103
  error_message = f"Error during login: {str(e)}"
 
110
  otp_digits = list(otp)
111
  for i in range(6):
112
  self.driver.find_element(By.ID, f"code{i+1}").send_keys(otp_digits[i])
113
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
114
+ EC.element_to_be_clickable((By.ID, "login"))
115
+ ).click()
116
  self.micro_status("Verifying login success...")
117
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
118
+ EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']"))
119
+ )
120
  return True, None
121
  except Exception as e:
122
  error_message = f"Error during OTP submission: {str(e)}"
 
129
  if self.termination_event.is_set():
130
  print("[Bot] Termination detected. Stopping process.")
131
  break
132
+ with self.app.app_context():
133
+ self.socketio.emit('stats_update', {
134
+ 'processed': len(results),
135
+ 'remaining': len(patient_list) - len(results)
136
+ })
137
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
138
  status = self._process_single_patient(patient_name)
139
  results.append({'Name': patient_name, 'Status': status})
 
140
  with self.app.app_context():
 
141
  self.socketio.emit('log_update', {'name': patient_name, 'status': status})
 
 
 
 
 
142
  return results
143
 
144
  def _process_single_patient(self, patient_name):
145
  try:
146
  self.micro_status(f"Navigating to Void page for '{patient_name}'")
147
  self.driver.get("https://gateway.quantumepay.com/credit-card/void")
148
+
149
  search_successful = False
150
  for attempt in range(15):
151
  try:
152
  self.micro_status(f"Searching for patient (Attempt {attempt + 1})...")
153
+ WebDriverWait(self.driver, 2).until(
154
+ EC.presence_of_element_located((By.XPATH, "//div[contains(@class, 'table-wrapper')]"))
155
+ )
156
+ search_box = WebDriverWait(self.driver, 2).until(
157
+ EC.element_to_be_clickable((By.XPATH, "//input[@placeholder='Search']"))
158
+ )
159
  search_box.click(); time.sleep(0.5)
160
  search_box.clear(); time.sleep(0.5)
161
  search_box.send_keys(patient_name)
162
  search_successful = True
163
  break
164
+ except Exception:
165
+ time.sleep(1)
166
+
167
+ if not search_successful:
168
+ raise Exception("Failed to search for patient.")
169
+
170
  time.sleep(3)
171
  self.micro_status("Opening transaction details...")
172
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
173
+ EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))
174
+ ).click()
175
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
176
+ EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))
177
+ ).click()
178
+
179
  self.micro_status("Adding to Vault...")
180
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
181
+ EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))
182
+ ).click()
183
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
184
+ EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))
185
+ ).click()
186
+
187
  try:
188
  self.micro_status("Verifying success and saving...")
189
+ company_input = WebDriverWait(self.driver, 10).until(
190
+ EC.element_to_be_clickable((By.NAME, "company_name"))
191
+ )
192
  company_input.clear()
193
  company_input.send_keys(patient_name)
194
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
195
+ EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Save Changes']"))
196
+ ).click()
197
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
198
+ EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Confirm']]"))
199
+ ).click()
200
  time.sleep(5)
201
  return 'Done'
202
  except TimeoutException:
203
  self.micro_status(f"'{patient_name}' is in a bad state, cancelling.")
204
+ WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(
205
+ EC.element_to_be_clickable((By.XPATH, "//button[.//span[normalize-space()='Cancel']]"))
206
+ ).click()
207
  return 'Bad'
208
  except Exception as e:
209
  print(f"An error occurred while processing {patient_name}: {e}")
210
  return 'Error'
211
+
212
  def shutdown(self):
213
+ try:
214
+ if self.driver:
215
+ self.driver.quit()
216
+ self._kill_chrome_processes()
217
+ print("[Bot] Chrome session closed and cleaned up.")
218
+ except Exception as e:
219
+ print(f"[Bot] Error during shutdown: {e}")