Kgshop commited on
Commit
c79f542
·
verified ·
1 Parent(s): 0727a3e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +212 -1
app.py CHANGED
@@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone
10
  from uuid import uuid4
11
  import random
12
  import string
 
13
 
14
  from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, Response
15
  from PIL import Image
@@ -21,6 +22,17 @@ from werkzeug.utils import secure_filename
21
  from dotenv import load_dotenv
22
  import requests
23
 
 
 
 
 
 
 
 
 
 
 
 
24
  load_dotenv()
25
 
26
  app = Flask(__name__)
@@ -66,6 +78,165 @@ COLOR_SCHEMES = {
66
 
67
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
70
  if not HF_TOKEN_READ and not HF_TOKEN_WRITE:
71
  logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.")
@@ -2501,6 +2672,17 @@ ADMIN_TEMPLATE = '''
2501
  {% endif %}
2502
  </div>
2503
 
 
 
 
 
 
 
 
 
 
 
 
2504
  <div class="section">
2505
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
2506
  <div class="sync-buttons">
@@ -3663,7 +3845,9 @@ def admin(env_id):
3663
  currencies=CURRENCIES,
3664
  color_schemes=COLOR_SCHEMES,
3665
  env_id=env_id,
3666
- chat_status=chat_status
 
 
3667
  )
3668
 
3669
  @app.route('/generate_description_ai', methods=['POST'])
@@ -3769,6 +3953,33 @@ def force_download(env_id):
3769
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
3770
  return redirect(url_for('admin', env_id=env_id))
3771
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3772
  if __name__ == '__main__':
3773
  configure_gemini()
3774
  download_db_from_hf()
 
10
  from uuid import uuid4
11
  import random
12
  import string
13
+ import queue
14
 
15
  from flask import Flask, render_template_string, request, redirect, url_for, flash, jsonify, Response
16
  from PIL import Image
 
22
  from dotenv import load_dotenv
23
  import requests
24
 
25
+ from selenium import webdriver
26
+ from selenium.webdriver.chrome.service import Service as ChromeService
27
+ from selenium.webdriver.chrome.options import Options as ChromeOptions
28
+ from selenium.webdriver.common.by import By
29
+ from selenium.webdriver.support.ui import WebDriverWait
30
+ from selenium.webdriver.support import expected_conditions as EC
31
+ from selenium.webdriver.common.keys import Keys
32
+ from selenium.common.exceptions import TimeoutException, NoSuchElementException
33
+ from webdriver_manager.chrome import ChromeDriverManager
34
+
35
+
36
  load_dotenv()
37
 
38
  app = Flask(__name__)
 
78
 
79
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
80
 
81
+ driver = None
82
+ whatsapp_thread = None
83
+ whatsapp_env_id = None
84
+ whatsapp_queues = {
85
+ 'incoming': queue.Queue(),
86
+ 'outgoing': queue.Queue()
87
+ }
88
+ whatsapp_active = threading.Event()
89
+
90
+ def whatsapp_bot_thread(env_id):
91
+ global driver, whatsapp_env_id
92
+ whatsapp_env_id = env_id
93
+
94
+ try:
95
+ chrome_options = ChromeOptions()
96
+ user_data_dir = os.path.join(os.getcwd(), "whatsapp_profile")
97
+ os.makedirs(user_data_dir, exist_ok=True)
98
+ chrome_options.add_argument(f"--user-data-dir={user_data_dir}")
99
+ chrome_options.add_argument("--headless=new")
100
+ chrome_options.add_argument("--no-sandbox")
101
+ chrome_options.add_argument("--disable-dev-shm-usage")
102
+ chrome_options.add_argument("--disable-gpu")
103
+ chrome_options.add_argument("user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
104
+
105
+ try:
106
+ service = ChromeService(ChromeDriverManager().install())
107
+ driver = webdriver.Chrome(service=service, options=chrome_options)
108
+ except Exception as e:
109
+ logging.error(f"Could not start WebDriver with manager: {e}. Falling back to default.")
110
+ driver = webdriver.Chrome(options=chrome_options)
111
+
112
+ driver.get("https://web.whatsapp.com")
113
+ logging.info("WhatsApp Web page opened. Please scan the QR code if required.")
114
+
115
+ wait = WebDriverWait(driver, 60)
116
+ wait.until(EC.presence_of_element_located((By.XPATH, '//*[@id="side"]/div[1]/div/div[2]/div[2]/div/div[1]/p'))) # Wait for search bar
117
+ logging.info("WhatsApp Web is logged in and ready.")
118
+ whatsapp_active.set()
119
+
120
+ processor = threading.Thread(target=process_whatsapp_queues, daemon=True)
121
+ processor.start()
122
+
123
+ last_processed_messages = {}
124
+
125
+ while whatsapp_active.is_set():
126
+ try:
127
+ unread_chats = driver.find_elements(By.XPATH, '//span[@data-testid="icon-unread-count"]')
128
+ for chat in unread_chats:
129
+ try:
130
+ parent_element = chat.find_element(By.XPATH, './ancestor::div[contains(@class, "_aou8")]')
131
+ parent_element.click()
132
+ time.sleep(1)
133
+
134
+ chat_title_element = driver.find_element(By.XPATH, '//header//span[@dir="auto"]')
135
+ chat_name = chat_title_element.text
136
+
137
+ messages = driver.find_elements(By.XPATH, '//div[contains(@class, "message-in")]')
138
+ if messages:
139
+ last_message = messages[-1]
140
+ message_text_element = last_message.find_element(By.XPATH, './/span[contains(@class, "selectable-text")]/span')
141
+ message_text = message_text_element.text
142
+
143
+ if last_processed_messages.get(chat_name) != message_text:
144
+ logging.info(f"New message from '{chat_name}': {message_text}")
145
+ whatsapp_queues['incoming'].put({'sender': chat_name, 'message': message_text})
146
+ last_processed_messages[chat_name] = message_text
147
+
148
+ except (NoSuchElementException, StaleElementReferenceException):
149
+ continue
150
+ time.sleep(5)
151
+ except Exception as e:
152
+ logging.error(f"Error in WhatsApp listener loop: {e}")
153
+ time.sleep(10)
154
+
155
+ except TimeoutException:
156
+ logging.error("Timed out waiting for WhatsApp Web to load. Please scan the QR code faster or check your connection.")
157
+ except Exception as e:
158
+ logging.error(f"An error occurred in the WhatsApp bot thread: {e}")
159
+ finally:
160
+ if driver:
161
+ driver.quit()
162
+ driver = None
163
+ whatsapp_active.clear()
164
+ logging.info("WhatsApp bot thread has stopped.")
165
+
166
+
167
+ def process_whatsapp_queues():
168
+ while whatsapp_active.is_set():
169
+ try:
170
+ # Process incoming messages
171
+ if not whatsapp_queues['incoming'].empty():
172
+ data = whatsapp_queues['incoming'].get()
173
+ sender = data['sender']
174
+ message = data['message']
175
+
176
+ # Simple chat history management (in-memory for this example)
177
+ # For a real app, this should be persisted in data.json
178
+ if 'whatsapp_chats' not in app.config:
179
+ app.config['whatsapp_chats'] = {}
180
+ if sender not in app.config['whatsapp_chats']:
181
+ app.config['whatsapp_chats'][sender] = []
182
+
183
+ history = app.config['whatsapp_chats'][sender]
184
+
185
+ ai_response = generate_chat_response(message, history, whatsapp_env_id)
186
+
187
+ history.append({'role': 'user', 'text': message})
188
+ history.append({'role': 'ai', 'text': ai_response})
189
+ # Trim history to keep it from growing too large
190
+ app.config['whatsapp_chats'][sender] = history[-20:]
191
+
192
+ whatsapp_queues['outgoing'].put({'recipient': sender, 'message': ai_response})
193
+
194
+ # Process outgoing messages
195
+ if not whatsapp_queues['outgoing'].empty():
196
+ data = whatsapp_queues['outgoing'].get()
197
+ send_whatsapp_message(data['recipient'], data['message'])
198
+
199
+ time.sleep(1)
200
+ except Exception as e:
201
+ logging.error(f"Error processing WhatsApp queues: {e}")
202
+
203
+
204
+ def send_whatsapp_message(recipient, message):
205
+ if not driver or not whatsapp_active.is_set():
206
+ logging.warning("WhatsApp driver not ready, cannot send message.")
207
+ return
208
+
209
+ try:
210
+ search_box = WebDriverWait(driver, 10).until(
211
+ EC.presence_of_element_located((By.XPATH, '//*[@id="side"]/div[1]/div/div[2]/div[2]/div/div[1]/p'))
212
+ )
213
+ search_box.clear()
214
+ search_box.send_keys(recipient)
215
+ time.sleep(2)
216
+
217
+ chat = WebDriverWait(driver, 10).until(
218
+ EC.presence_of_element_located((By.XPATH, f'//span[@title="{recipient}"]'))
219
+ )
220
+ chat.click()
221
+ time.sleep(1)
222
+
223
+ message_box = WebDriverWait(driver, 10).until(
224
+ EC.presence_of_element_located((By.XPATH, '//*[@id="main"]/footer/div[1]/div/span[2]/div/div[2]/div[1]/div/div[1]/p'))
225
+ )
226
+
227
+ for line in message.split('\n'):
228
+ message_box.send_keys(line)
229
+ message_box.send_keys(Keys.SHIFT, Keys.ENTER)
230
+
231
+ message_box.send_keys(Keys.ENTER)
232
+ logging.info(f"Message sent to '{recipient}'.")
233
+
234
+ except TimeoutException:
235
+ logging.error(f"Could not find chat or message box for '{recipient}'.")
236
+ except Exception as e:
237
+ logging.error(f"Failed to send WhatsApp message to '{recipient}': {e}")
238
+
239
+
240
  def download_db_from_hf(specific_file=None, retries=DOWNLOAD_RETRIES, delay=DOWNLOAD_DELAY):
241
  if not HF_TOKEN_READ and not HF_TOKEN_WRITE:
242
  logging.warning("HF_TOKEN_READ/HF_TOKEN_WRITE not set. Download might fail for private repos.")
 
2672
  {% endif %}
2673
  </div>
2674
 
2675
+ <div class="section">
2676
+ <h2><i class="fab fa-whatsapp"></i> Интеграция с WhatsApp</h2>
2677
+ {% if whatsapp_is_active %}
2678
+ <p style="color: green;">WhatsApp бот активен для среды <strong>{{ whatsapp_env_id }}</strong>.</p>
2679
+ <a href="{{ url_for('stop_whatsapp_bot', env_id=env_id) }}" class="button delete-button">Остановить WhatsApp Бота</a>
2680
+ {% else %}
2681
+ <p>Запустите ИИ-консультанта в WhatsApp. После запуска, откроется браузер. Если потребуется, отсканируйте QR-код.</p>
2682
+ <a href="{{ url_for('start_whatsapp_bot', env_id=env_id) }}" class="button add-button" style="background-color: #25D366; color: white;"><i class="fab fa-whatsapp"></i> Запустить WhatsApp Бота</a>
2683
+ {% endif %}
2684
+ </div>
2685
+
2686
  <div class="section">
2687
  <h2><i class="fas fa-sync-alt"></i> Синхронизация с Датацентром</h2>
2688
  <div class="sync-buttons">
 
3845
  currencies=CURRENCIES,
3846
  color_schemes=COLOR_SCHEMES,
3847
  env_id=env_id,
3848
+ chat_status=chat_status,
3849
+ whatsapp_is_active=whatsapp_active.is_set(),
3850
+ whatsapp_env_id=whatsapp_env_id
3851
  )
3852
 
3853
  @app.route('/generate_description_ai', methods=['POST'])
 
3953
  flash(f"Ошибка при скачивании с Hugging Face: {e}", 'error')
3954
  return redirect(url_for('admin', env_id=env_id))
3955
 
3956
+ @app.route('/<env_id>/whatsapp/start')
3957
+ def start_whatsapp_bot(env_id):
3958
+ global whatsapp_thread
3959
+ if whatsapp_thread and whatsapp_thread.is_alive():
3960
+ flash('WhatsApp бот уже запущен.', 'warning')
3961
+ else:
3962
+ whatsapp_thread = threading.Thread(target=whatsapp_bot_thread, args=(env_id,), daemon=True)
3963
+ whatsapp_thread.start()
3964
+ flash('Запуск WhatsApp бота... Пожалуйста, будьте готовы отсканировать QR-код в новом окне браузера.', 'success')
3965
+ return redirect(url_for('admin', env_id=env_id))
3966
+
3967
+ @app.route('/<env_id>/whatsapp/stop')
3968
+ def stop_whatsapp_bot(env_id):
3969
+ global driver, whatsapp_thread
3970
+ if not (whatsapp_thread and whatsapp_thread.is_alive()):
3971
+ flash('WhatsApp бот не был запущен.', 'warning')
3972
+ else:
3973
+ whatsapp_active.clear()
3974
+ if driver:
3975
+ try:
3976
+ driver.quit()
3977
+ except:
3978
+ pass
3979
+ flash('WhatsApp бот остановлен.', 'success')
3980
+ return redirect(url_for('admin', env_id=env_id))
3981
+
3982
+
3983
  if __name__ == '__main__':
3984
  configure_gemini()
3985
  download_db_from_hf()