sonuprasad23 commited on
Commit
7a82de2
·
1 Parent(s): c5b1794
Files changed (2) hide show
  1. server.py +45 -58
  2. worker.py +17 -26
server.py CHANGED
@@ -23,51 +23,14 @@ load_dotenv()
23
 
24
  app = Flask(__name__)
25
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
26
- CORS(app, resources={r"/*": {"origins": "*"}})
27
- socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')
 
28
 
29
  bot_instance = None
30
  session_data = {}
31
  automation_thread = None
32
 
33
- class GoogleDriveService:
34
- def __init__(self):
35
- self.creds = None
36
- self.service = None
37
- self.folder_id = os.getenv('GOOGLE_DRIVE_FOLDER_ID')
38
-
39
- try:
40
- from google.oauth2 import service_account
41
- from googleapiclient.discovery import build
42
-
43
- # --- Definitive Secure Credential Loading ---
44
- base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
45
- if not base64_creds:
46
- raise ValueError("GDRIVE_SA_KEY_BASE64 secret not found.")
47
- if not self.folder_id:
48
- raise ValueError("GOOGLE_DRIVE_FOLDER_ID secret not found.")
49
-
50
- creds_json = base64.b64decode(base64_creds).decode('utf-8')
51
- creds_dict = json.loads(creds_json)
52
-
53
- self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
54
- self.service = build('drive', 'v3', credentials=self.creds)
55
- print("[G-Drive] Service initialized securely from secrets.")
56
- except Exception as e:
57
- print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
58
-
59
- def upload_file(self, filename, file_content):
60
- if not self.service: return False
61
- try:
62
- from googleapiclient.http import MediaIoBaseUpload
63
- file_metadata = {'name': filename, 'parents': [self.folder_id]}
64
- media = MediaIoBaseUpload(io.BytesIO(file_content.encode('utf-8')), mimetype='text/csv', resumable=True)
65
- self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
66
- print(f"[G-Drive] File '{filename}' uploaded successfully.")
67
- return True
68
- except Exception as e:
69
- print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
70
-
71
  class EmailService:
72
  def __init__(self):
73
  self.sender_email = os.getenv('EMAIL_SENDER')
@@ -89,6 +52,31 @@ class EmailService:
89
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
90
  except Exception as e: print(f"Failed to send email: {e}"); return False
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  email_service = EmailService()
93
  drive_service = GoogleDriveService()
94
 
@@ -138,44 +126,43 @@ def generate_and_send_reports(session_id, results, is_crash_report=False, is_ter
138
 
139
  @app.route('/')
140
  def status_page():
141
- APP_STATUS_HTML = """
142
- <!DOCTYPE html><html lang="en"><head><title>API Status</title><style>
143
- body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}
144
- .status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}
145
- h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}
146
- .active{color:#28a745;background-color:#e9f7ea;}
147
- </style></head><body><div class="status-box"><h1>Hillside's Automation API</h1><div class="indicator active">● Active</div></div></body></html>
148
- """
149
  return Response(APP_STATUS_HTML)
150
 
151
  @socketio.on('connect')
152
  def handle_connect():
153
- print('Frontend connected.'); emit('email_list', {'emails': get_email_list()})
154
 
155
  @socketio.on('initialize_session')
156
  def handle_init(data):
157
- session_id = 'user_session'
158
  session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
159
- global bot_instance
160
  if bot_instance: bot_instance.shutdown()
161
  bot_instance = QuantumBot(socketio, app)
162
- if bot_instance.initialize_driver(): emit('bot_initialized')
163
- else: emit('error', {'message': 'Failed to initialize the automation bot.'})
 
 
 
 
 
164
 
165
  @socketio.on('start_login')
166
  def handle_login(credentials):
167
  if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
168
- if bot_instance.login(credentials['username'], credentials['password']): emit('otp_required')
169
- else: emit('error', {'message': 'Login failed. Please check credentials.'})
 
170
 
171
  @socketio.on('submit_otp')
172
  def handle_otp(data):
173
  if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
174
- if bot_instance.submit_otp(data['otp']):
 
175
  emit('login_successful')
176
  session_id = 'user_session'
177
- socketio.start_background_task(target=run_automation_process, session_id=session_id)
178
- else: emit('error', {'message': 'OTP was incorrect or timed out.'})
179
 
180
  @socketio.on('terminate_process')
181
  def handle_terminate():
 
23
 
24
  app = Flask(__name__)
25
  app.config['SECRET_KEY'] = 'secret-key-for-hillside-automation'
26
+ FRONTEND_ORIGIN = os.getenv('FRONTEND_URL', 'http://127.0.0.1:5500')
27
+ CORS(app, resources={r"/*": {"origins": FRONTEND_ORIGIN}})
28
+ socketio = SocketIO(app, cors_allowed_origins=FRONTEND_ORIGIN, async_mode='eventlet')
29
 
30
  bot_instance = None
31
  session_data = {}
32
  automation_thread = None
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  class EmailService:
35
  def __init__(self):
36
  self.sender_email = os.getenv('EMAIL_SENDER')
 
52
  print(f"Email sent successfully to {', '.join(recipients)}"); return True
53
  except Exception as e: print(f"Failed to send email: {e}"); return False
54
 
55
+ class GoogleDriveService:
56
+ def __init__(self):
57
+ self.creds = None; self.service = None
58
+ self.folder_id = os.getenv('GOOGLE_DRIVE_FOLDER_ID')
59
+ try:
60
+ from google.oauth2 import service_account
61
+ from googleapiclient.discovery import build
62
+ base64_creds = os.getenv('GDRIVE_SA_KEY_BASE64')
63
+ if not base64_creds or not self.folder_id: raise ValueError("Google Drive secrets not found.")
64
+ creds_json = base64.b64decode(base64_creds).decode('utf-8'); creds_dict = json.loads(creds_json)
65
+ self.creds = service_account.Credentials.from_service_account_info(creds_dict, scopes=['https://www.googleapis.com/auth/drive'])
66
+ self.service = build('drive', 'v3', credentials=self.creds)
67
+ print("[G-Drive] Service initialized securely from secrets.")
68
+ except Exception as e: print(f"[G-Drive] CRITICAL ERROR: Could not initialize Google Drive service: {e}")
69
+ def upload_file(self, filename, file_content):
70
+ if not self.service: return False
71
+ try:
72
+ from googleapiclient.http import MediaIoBaseUpload
73
+ file_metadata = {'name': filename, 'parents': [self.folder_id]}
74
+ media = MediaIoBaseUpload(io.BytesIO(file_content.encode('utf-8')), mimetype='text/csv', resumable=True)
75
+ self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
76
+ print(f"[G-Drive] File '{filename}' uploaded successfully.")
77
+ return True
78
+ except Exception as e: print(f"[G-Drive] ERROR: File upload failed: {e}"); return False
79
+
80
  email_service = EmailService()
81
  drive_service = GoogleDriveService()
82
 
 
126
 
127
  @app.route('/')
128
  def status_page():
129
+ APP_STATUS_HTML = """<!DOCTYPE html><html lang="en"><head><title>API Status</title><style>body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#f0f2f5;}.status-box{text-align:center;padding:40px 60px;background:white;border-radius:12px;box-shadow:0 8px 30px rgba(0,0,0,0.1);}h1{font-size:24px;color:#333;margin-bottom:10px;} .indicator{font-size:18px;font-weight:600;padding:8px 16px;border-radius:20px;}.active{color:#28a745;background-color:#e9f7ea;}</style></head><body><div class="status-box"><h1>Hillside's Automation API</h1><div class="indicator active">● Active</div></div></body></html>"""
 
 
 
 
 
 
 
130
  return Response(APP_STATUS_HTML)
131
 
132
  @socketio.on('connect')
133
  def handle_connect():
134
+ print(f'Frontend connected. Allowed origin: {FRONTEND_ORIGIN}'); emit('email_list', {'emails': get_email_list()})
135
 
136
  @socketio.on('initialize_session')
137
  def handle_init(data):
138
+ session_id = 'user_session'; global bot_instance
139
  session_data[session_id] = {'csv_content': data['content'], 'emails': data['emails'], 'filename': data['filename']}
 
140
  if bot_instance: bot_instance.shutdown()
141
  bot_instance = QuantumBot(socketio, app)
142
+
143
+ # --- Definitive Fix: Capture and send detailed error message ---
144
+ is_success, error_message = bot_instance.initialize_driver()
145
+ if is_success:
146
+ emit('bot_initialized')
147
+ else:
148
+ emit('error', {'message': f'Failed to initialize automation bot: {error_message}'})
149
 
150
  @socketio.on('start_login')
151
  def handle_login(credentials):
152
  if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
153
+ is_success, error_message = bot_instance.login(credentials['username'], credentials['password'])
154
+ if is_success: emit('otp_required')
155
+ else: emit('error', {'message': f'Login failed: {error_message}'})
156
 
157
  @socketio.on('submit_otp')
158
  def handle_otp(data):
159
  if not bot_instance: return emit('error', {'message': 'Bot not initialized.'})
160
+ is_success, error_message = bot_instance.submit_otp(data['otp'])
161
+ if is_success:
162
  emit('login_successful')
163
  session_id = 'user_session'
164
+ socketio.start_background_task(target=run_automation_process, args=(session_id,))
165
+ else: emit('error', {'message': f'OTP failed: {error_message}'})
166
 
167
  @socketio.on('terminate_process')
168
  def handle_terminate():
worker.py CHANGED
@@ -6,9 +6,6 @@ import threading
6
  from selenium import webdriver
7
  from selenium.webdriver.chrome.service import Service as ChromeService
8
  from selenium.webdriver.chrome.options import Options as ChromeOptions
9
- from webdriver_manager.chrome import ChromeDriverManager
10
- # --- Definitive Fix: Import ChromeType from its new, correct location ---
11
- from webdriver_manager.core.os_manager import ChromeType
12
  from selenium.webdriver.common.by import By
13
  from selenium.webdriver.support.ui import WebDriverWait
14
  from selenium.webdriver.support import expected_conditions as EC
@@ -31,23 +28,25 @@ class QuantumBot:
31
  options.add_argument("--window-size=1920,1080")
32
  options.add_argument("--no-sandbox")
33
  options.add_argument("--disable-dev-shm-usage")
 
34
  options.add_argument('--disable-blink-features=AutomationControlled')
35
  options.add_experimental_option("excludeSwitches", ["enable-automation"])
36
  options.add_experimental_option('useAutomationExtension', False)
37
 
38
- os.environ['WDM_LOCAL'] = '/home/appuser/.wdm'
39
- service = ChromeService(ChromeDriverManager(chrome_type=ChromeType.CHROMIUM).install())
 
40
 
41
  self.driver = webdriver.Chrome(service=service, options=options)
42
 
43
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
44
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
45
  })
46
- return True
47
  except Exception as e:
48
- print(f"CRITICAL ERROR in WebDriver Initialization: {e}")
49
- self.micro_status(f"Error: Could not start browser. {e}")
50
- return False
51
 
52
  def micro_status(self, message):
53
  print(f"[Bot Action] {message}")
@@ -69,10 +68,11 @@ class QuantumBot:
69
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
70
  self.micro_status("Waiting for OTP screen...")
71
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
72
- return True
73
  except Exception as e:
74
- print(f"[Bot] ERROR during login: {e}")
75
- return False
 
76
 
77
  def submit_otp(self, otp):
78
  try:
@@ -83,10 +83,11 @@ class QuantumBot:
83
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
84
  self.micro_status("Verifying login success...")
85
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
86
- return True
87
  except Exception as e:
88
- print(f"[Bot] ERROR during OTP submission: {e}")
89
- return False
 
90
 
91
  def process_patient_list(self, patient_list):
92
  results = []
@@ -94,17 +95,11 @@ class QuantumBot:
94
  if self.termination_event.is_set():
95
  print("[Bot] Termination detected. Stopping process.")
96
  break
97
-
98
  with self.app.app_context():
99
- self.socketio.emit('stats_update', {
100
- 'processed': len(results),
101
- 'remaining': len(patient_list) - len(results)
102
- })
103
-
104
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
105
  status = self._process_single_patient(patient_name)
106
  results.append({'Name': patient_name, 'Status': status})
107
-
108
  with self.app.app_context():
109
  self.socketio.emit('log_update', {'name': patient_name, 'status': status})
110
  return results
@@ -113,7 +108,6 @@ class QuantumBot:
113
  try:
114
  self.micro_status(f"Navigating to Void page for '{patient_name}'")
115
  self.driver.get("https://gateway.quantumepay.com/credit-card/void")
116
-
117
  search_successful = False
118
  for attempt in range(15):
119
  try:
@@ -127,16 +121,13 @@ class QuantumBot:
127
  break
128
  except Exception: time.sleep(1)
129
  if not search_successful: raise Exception("Failed to search for patient.")
130
-
131
  time.sleep(3)
132
  self.micro_status("Opening transaction details...")
133
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
134
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))).click()
135
-
136
  self.micro_status("Adding to Vault...")
137
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))).click()
138
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))).click()
139
-
140
  try:
141
  self.micro_status("Verifying success and saving...")
142
  company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))
 
6
  from selenium import webdriver
7
  from selenium.webdriver.chrome.service import Service as ChromeService
8
  from selenium.webdriver.chrome.options import Options as ChromeOptions
 
 
 
9
  from selenium.webdriver.common.by import By
10
  from selenium.webdriver.support.ui import WebDriverWait
11
  from selenium.webdriver.support import expected_conditions as EC
 
28
  options.add_argument("--window-size=1920,1080")
29
  options.add_argument("--no-sandbox")
30
  options.add_argument("--disable-dev-shm-usage")
31
+ options.add_argument("--disable-gpu") # Added for stability in Linux environments
32
  options.add_argument('--disable-blink-features=AutomationControlled')
33
  options.add_experimental_option("excludeSwitches", ["enable-automation"])
34
  options.add_experimental_option('useAutomationExtension', False)
35
 
36
+ # --- Definitive Fix: Use the explicit driver path installed by Docker ---
37
+ # This removes the dependency on webdriver-manager for production.
38
+ service = ChromeService(executable_path='/usr/bin/chromedriver')
39
 
40
  self.driver = webdriver.Chrome(service=service, options=options)
41
 
42
  self.driver.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
43
  'source': "Object.defineProperty(navigator, 'webdriver', {get: () => undefined})"
44
  })
45
+ return True, None
46
  except Exception as e:
47
+ error_message = f"Error during WebDriver initialization: {str(e)}"
48
+ print(f"CRITICAL ERROR in WebDriver Initialization: {error_message}")
49
+ return False, error_message
50
 
51
  def micro_status(self, message):
52
  print(f"[Bot Action] {message}")
 
68
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
69
  self.micro_status("Waiting for OTP screen...")
70
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.presence_of_element_located((By.ID, "code1")))
71
+ return True, None
72
  except Exception as e:
73
+ error_message = f"Error during login process: {str(e)}"
74
+ print(f"[Bot] ERROR during login: {error_message}")
75
+ return False, error_message
76
 
77
  def submit_otp(self, otp):
78
  try:
 
83
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.ID, "login"))).click()
84
  self.micro_status("Verifying login success...")
85
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//span[text()='Payments']")))
86
+ return True, None
87
  except Exception as e:
88
+ error_message = f"Error during OTP submission: {str(e)}"
89
+ print(f"[Bot] ERROR during OTP submission: {error_message}")
90
+ return False, error_message
91
 
92
  def process_patient_list(self, patient_list):
93
  results = []
 
95
  if self.termination_event.is_set():
96
  print("[Bot] Termination detected. Stopping process.")
97
  break
 
98
  with self.app.app_context():
99
+ self.socketio.emit('stats_update', {'processed': len(results), 'remaining': len(patient_list) - len(results)})
 
 
 
 
100
  self.micro_status(f"Processing '{patient_name}' ({index + 1}/{len(patient_list)})...")
101
  status = self._process_single_patient(patient_name)
102
  results.append({'Name': patient_name, 'Status': status})
 
103
  with self.app.app_context():
104
  self.socketio.emit('log_update', {'name': patient_name, 'status': status})
105
  return results
 
108
  try:
109
  self.micro_status(f"Navigating to Void page for '{patient_name}'")
110
  self.driver.get("https://gateway.quantumepay.com/credit-card/void")
 
111
  search_successful = False
112
  for attempt in range(15):
113
  try:
 
121
  break
122
  except Exception: time.sleep(1)
123
  if not search_successful: raise Exception("Failed to search for patient.")
 
124
  time.sleep(3)
125
  self.micro_status("Opening transaction details...")
126
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, f"//tr[contains(., \"{patient_name}\")]//button[@data-v-b6b33fa0]"))).click()
127
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.LINK_TEXT, "Transaction Detail"))).click()
 
128
  self.micro_status("Adding to Vault...")
129
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//button/span[normalize-space()='Add to Vault']"))).click()
130
  WebDriverWait(self.driver, self.DEFAULT_TIMEOUT).until(EC.element_to_be_clickable((By.XPATH, "//div[@class='modal-footer']//button/span[normalize-space()='Confirm']"))).click()
 
131
  try:
132
  self.micro_status("Verifying success and saving...")
133
  company_input = WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.NAME, "company_name")))