CORVO-AI commited on
Commit
ddac050
·
verified ·
1 Parent(s): 94af01f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +167 -449
app.py CHANGED
@@ -1,471 +1,189 @@
1
- from flask import Flask, request, jsonify
2
- import requests
3
- import random
4
- import string
5
  import time
6
- import json
 
 
7
 
8
- app = Flask(__name__)
 
 
 
 
 
9
 
10
- # Global variables to store workspace and bot IDs
11
- GLOBAL_WORKSPACE_ID = None
12
- GLOBAL_BOT_ID = None
13
-
14
-
15
- # Authorization value used in requests (should be updated with a valid Authorization),
16
- TOKEN = "Bearer bp_pat_vTuxol25N0ymBpYaWqtWpFfGPKt260IfT784"
17
- # -------------------------------------------------------------------
18
- # Helper functions for random bot/workspace names
19
- # -------------------------------------------------------------------
20
- def generate_random_name(length=5):
21
- """Generate a random name for workspace or bot"""
22
- return ''.join(random.choices(string.ascii_letters, k=length))
23
-
24
- # -------------------------------------------------------------------
25
- # Functions to create/delete workspaces and bots
26
- # -------------------------------------------------------------------
27
- def create_workspace():
28
- """Create a new workspace and return its ID"""
29
- ws_url = "https://api.botpress.cloud/v1/admin/workspaces"
30
- headers = {
31
- "User-Agent": "Mozilla/5.0",
32
- "Authorization": TOKEN
33
- }
34
- payload = {"name": generate_random_name()}
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  try:
37
- response = requests.post(ws_url, headers=headers, json=payload)
38
- if response.status_code == 200:
39
- response_json = response.json()
40
- workspace_id = response_json.get('id')
41
- print(f"Successfully created workspace: {workspace_id}")
42
- return workspace_id
43
- else:
44
- print(f"Workspace creation failed with: {response.status_code}, {response.text}")
45
- return None
46
  except Exception as e:
47
- print(f"Error creating workspace: {str(e)}")
48
- return None
49
-
50
-
51
- def create_bot(workspace_id):
52
- """Create a new bot in the specified workspace and return its ID"""
53
- if not workspace_id:
54
- print("Cannot create bot: No workspace ID provided")
55
- return None
56
-
57
- bot_url = "https://api.botpress.cloud/v1/admin/bots"
58
- headers = {
59
- "User-Agent": "Mozilla/5.0",
60
- "x-workspace-id": workspace_id,
61
- "Authorization": TOKEN,
62
- "Content-Type": "application/json"
63
- }
64
- payload = {"name": generate_random_name()}
65
-
66
- try:
67
- response = requests.post(bot_url, headers=headers, json=payload)
68
- if response.status_code == 200:
69
- response_json = response.json()
70
- bot_id = response_json.get("bot", {}).get("id")
71
- if not bot_id:
72
- print("Bot ID not found in the response.")
73
- return None
74
-
75
- print(f"Successfully created bot: {bot_id} in workspace: {workspace_id}")
76
-
77
- # Install integration for the new bot
78
- integration_success = install_bot_integration(bot_id, workspace_id)
79
- if integration_success:
80
- print(f"Successfully installed integration for bot {bot_id}")
81
- return bot_id
82
  else:
83
- print(f"Failed to install integration for bot {bot_id}")
84
- return bot_id # Still return the bot ID even if integration fails
85
- else:
86
- print(f"Bot creation failed with: {response.status_code}, {response.text}")
87
- return None
88
- except Exception as e:
89
- print(f"Error creating bot: {str(e)}")
90
- return None
91
-
92
-
93
- def install_bot_integration(bot_id, workspace_id):
94
- """Install required integration for the bot to function properly"""
95
- if not bot_id or not workspace_id:
96
- print("Cannot install integration: Missing bot ID or workspace ID")
97
- return False
98
-
99
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
100
- headers = {
101
- "User-Agent": "Mozilla/5.0",
102
- "Authorization": TOKEN,
103
- "Content-Type": "application/json",
104
- "x-bot-id": bot_id,
105
- "x-workspace-id": workspace_id
106
- }
107
- # Integration payload
108
- payload = {
109
- "integrations": {
110
- "intver_01KKY7XBSZXB1P9GDV5F1KQ4NB": {
111
- "enabled": True
112
  }
113
- }
114
- }
115
 
116
- try:
117
- response = requests.put(url, headers=headers, json=payload)
118
- if response.status_code == 200:
119
- print(f"Successfully installed integration for bot {bot_id}")
120
- return True
121
- else:
122
- print(f"Failed to install integration: {response.status_code}, {response.text}")
123
- return False
124
- except Exception as e:
125
- print(f"Error installing integration: {str(e)}")
126
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
 
129
- def try_delete_bot(bot_id, workspace_id):
130
- """Attempt to delete a bot from the specified workspace but continue if it fails"""
131
- if not bot_id or not workspace_id:
132
- print("Cannot delete bot: Missing bot ID or workspace ID")
133
- return False
134
 
135
- url = f"https://api.botpress.cloud/v1/admin/bots/{bot_id}"
136
- headers = {
137
- "User-Agent": "Mozilla/5.0",
138
- "x-workspace-id": workspace_id,
139
- "Authorization": TOKEN
140
- }
141
 
142
- try:
143
- response = requests.delete(url, headers=headers)
144
- if response.status_code in [200, 204]:
145
- print(f"Successfully deleted bot: {bot_id}")
146
- return True
147
- else:
148
- print(f"Failed to delete bot: {response.status_code}, {response.text}")
149
- return False
150
- except Exception as e:
151
- print(f"Error deleting bot: {str(e)}")
152
- return False
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
- def try_delete_workspace(workspace_id):
156
- """Attempt to delete a workspace but continue if it fails"""
157
- if not workspace_id:
158
- print("Cannot delete workspace: No workspace ID provided")
159
- return False
160
 
161
- url = f"https://api.botpress.cloud/v1/admin/workspaces/{workspace_id}"
162
- headers = {
163
- "User-Agent": "Mozilla/5.0",
164
- "Authorization": TOKEN
165
- }
166
 
167
- try:
168
- response = requests.delete(url, headers=headers)
169
- if response.status_code in [200, 204]:
170
- print(f"Successfully deleted workspace: {workspace_id}")
171
- return True
172
- else:
173
- print(f"Failed to delete workspace: {response.status_code}, {response.text}")
174
- return False
175
- except Exception as e:
176
- print(f"Error deleting workspace: {str(e)}")
177
- return False
178
-
179
-
180
- # -------------------------------------------------------------------
181
- # Main function that calls the Botpress API endpoint
182
- # -------------------------------------------------------------------
183
- def chat_with_assistant(user_input, chat_history, bot_id, workspace_id, temperature=0.9, top_p=0.95, max_tokens=None):
184
- """
185
- Sends the user input and chat history to the Botpress API endpoint,
186
- returns the assistant's response and (possibly updated) bot/workspace IDs.
187
- """
188
- # Prepare the headers
189
- headers = {
190
- "User-Agent": "Mozilla/5.0",
191
- "x-bot-id": bot_id,
192
- "Content-Type": "application/json",
193
- "Authorization": TOKEN
194
- }
195
-
196
- # Process chat history into the format expected by the API
197
- messages = []
198
- system_prompt = ""
199
-
200
- for msg in chat_history:
201
- if msg["role"] == "system":
202
- system_prompt = msg["content"]
203
- elif msg["role"] in ["user", "assistant"]:
204
- # Pass multipart messages directly without modifying their structure
205
- if "type" in msg and msg["type"] == "multipart" and "content" in msg:
206
- messages.append(msg) # Keep the original multipart structure
207
- # Handle regular text messages
208
- else:
209
- messages.append({
210
- "role": msg["role"],
211
- "content": msg["content"]
212
- })
213
-
214
- # Add the latest user input if not already in chat history
215
- if user_input and isinstance(user_input, str) and (not messages or messages[-1]["role"] != "user" or messages[-1]["content"] != user_input):
216
- messages.append({
217
- "role": "user",
218
- "content": user_input
219
- })
220
 
221
- # Prepare the payload for the API
222
- payload = {
223
- "type": "openai:generateContent",
224
- "input": {
225
- "model": {
226
- "id": "gpt-5.4-2026-03-05"
227
- },
228
- "systemPrompt": system_prompt,
229
- "messages": messages,
230
- "temperature": temperature,
231
- "debug": False,
232
- }
233
- }
234
-
235
- # Add maxTokens to the payload if provided
236
- if max_tokens is not None:
237
- payload["input"]["maxTokens"] = max_tokens
238
-
239
- botpress_url = "https://api.botpress.cloud/v1/chat/actions"
240
- max_retries = 3
241
- timeout = 120 # Increased timeout for long messages
242
-
243
- # For debugging
244
- print("Payload being sent to Botpress:")
245
- print(json.dumps(payload, indent=2))
246
-
247
- # Attempt to send the request
248
- for attempt in range(max_retries):
249
- try:
250
- print(f"Attempt {attempt+1}: Sending request to Botpress API with bot_id={bot_id}, workspace_id={workspace_id}")
251
- response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
252
-
253
- # If successful (200)
254
- if response.status_code == 200:
255
- data = response.json()
256
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
257
- print(f"Successfully received response from Botpress API")
258
- return assistant_content, bot_id, workspace_id
259
-
260
- # Check for authentication or permission errors (401, 403)
261
- elif response.status_code in [401, 403]:
262
- error_message = "Authentication error"
263
- try:
264
- error_data = response.json()
265
- error_message = error_data.get('message', 'Authentication error')
266
- except:
267
- pass
268
-
269
- print(f"Authentication error detected: {error_message}")
270
-
271
- # We need to create new resources immediately
272
- print("Creating new workspace and bot...")
273
- new_workspace_id = create_workspace()
274
- if not new_workspace_id:
275
- print("Failed to create a new workspace")
276
- if attempt < max_retries - 1:
277
- time.sleep(3)
278
- continue
279
- else:
280
- return "Unable to create new resources. Please try again later.", bot_id, workspace_id
281
-
282
- new_bot_id = create_bot(new_workspace_id)
283
- if not new_bot_id:
284
- print("Failed to create a new bot")
285
- if attempt < max_retries - 1:
286
- time.sleep(3)
287
- continue
288
- else:
289
- return "Unable to create new bot. Please try again later.", new_workspace_id, workspace_id
290
-
291
- print(f"Created new workspace: {new_workspace_id} and bot: {new_bot_id}")
292
-
293
- # Try again with new IDs
294
- headers["x-bot-id"] = new_bot_id
295
- try:
296
- print(f"Retrying with new bot_id={new_bot_id}")
297
- retry_response = requests.post(botpress_url, json=payload, headers=headers, timeout=timeout)
298
-
299
- if retry_response.status_code == 200:
300
- data = retry_response.json()
301
- assistant_content = data.get('output', {}).get('choices', [{}])[0].get('content', '')
302
- print(f"Successfully received response with new IDs")
303
-
304
- # Try to clean up old resources in the background, but don't wait for result
305
- if bot_id and workspace_id:
306
- print(f"Attempting to clean up old resources in the background")
307
- try_delete_bot(bot_id, workspace_id)
308
- try_delete_workspace(workspace_id)
309
-
310
- return assistant_content, new_bot_id, new_workspace_id
311
- else:
312
- print(f"Failed with new IDs: {retry_response.status_code}")
313
- if attempt < max_retries - 1:
314
- time.sleep(2)
315
- continue
316
- else:
317
- return f"Unable to get a response even with new credentials.", new_bot_id, new_workspace_id
318
-
319
- except Exception as e:
320
- print(f"Error with new IDs: {str(e)}")
321
- if attempt < max_retries - 1:
322
- time.sleep(2)
323
- continue
324
- else:
325
- return f"Error with new credentials: {str(e)}", new_bot_id, new_workspace_id
326
-
327
- # Handle network errors or timeouts (just retry)
328
- elif response.status_code in [404, 408, 502, 503, 504]:
329
- print(f"Received error {response.status_code}. Retrying...")
330
- time.sleep(3) # Wait before retrying
331
- continue
332
-
333
- # Any other error status code
334
- else:
335
- print(f"Received unexpected error: {response.status_code}, {response.text}")
336
- if attempt < max_retries - 1:
337
- time.sleep(2)
338
- continue
339
- else:
340
- return f"Unable to get a response from the assistant (Error {response.status_code}).", bot_id, workspace_id
341
-
342
- except requests.exceptions.Timeout:
343
- print(f"Request timed out. Retrying...")
344
- if attempt < max_retries - 1:
345
- time.sleep(2)
346
- continue
347
- else:
348
- return "The assistant is taking too long to respond. Please try again with a shorter message.", bot_id, workspace_id
349
 
350
- except Exception as e:
351
- print(f"Error during request: {str(e)}")
352
- if attempt < max_retries - 1:
353
- time.sleep(2)
354
- continue
355
- else:
356
- return f"Unable to get a response from the assistant: {str(e)}", bot_id, workspace_id
357
-
358
- # Should not reach here due to the handling in the loop
359
- return "Unable to get a response from the assistant.", bot_id, workspace_id
360
-
361
-
362
- # -------------------------------------------------------------------
363
- # Flask Endpoint
364
- # -------------------------------------------------------------------
365
- @app.route("/chat", methods=["POST"])
366
- def chat_endpoint():
367
- """
368
- Expects JSON with:
369
- {
370
- "user_input": "string", // Can be null if multipart message is in chat_history
371
- "chat_history": [
372
- {"role": "system", "content": "..."},
373
- {"role": "user", "content": "..."},
374
- // Or for images:
375
- {"role": "user", "type": "multipart", "content": [
376
- {"type": "image", "url": "https://example.com/image.jpg"},
377
- {"type": "text", "text": "What's in this image?"}
378
- ]},
379
- ...
380
- ],
381
- "temperature": 0.9, // Optional, defaults to 0.9
382
- "top_p": 0.95, // Optional, defaults to 0.95
383
- "max_tokens": 1000 // Optional, defaults to null (no limit)
384
- }
385
- Returns JSON with:
386
- {
387
- "assistant_response": "string"
388
- }
389
- """
390
- global GLOBAL_WORKSPACE_ID, GLOBAL_BOT_ID
391
-
392
- # Parse JSON from request
393
- data = request.get_json(force=True)
394
- user_input = data.get("user_input", "")
395
- chat_history = data.get("chat_history", [])
396
-
397
- # Get temperature, top_p, and max_tokens from request, or use defaults
398
- temperature = data.get("temperature", 0.9)
399
- top_p = data.get("top_p", 0.95)
400
- max_tokens = data.get("max_tokens", None)
401
-
402
- # Validate temperature and top_p values
403
- try:
404
- temperature = float(temperature)
405
- if not 0 <= temperature <= 2:
406
- temperature = 0.9
407
- print(f"Invalid temperature value. Using default: {temperature}")
408
- except (ValueError, TypeError):
409
- temperature = 0.9
410
- print(f"Invalid temperature format. Using default: {temperature}")
411
 
412
- try:
413
- top_p = float(top_p)
414
- if not 0 <= top_p <= 1:
415
- top_p = 0.95
416
- print(f"Invalid top_p value. Using default: {top_p}")
417
- except (ValueError, TypeError):
418
- top_p = 0.95
419
- print(f"Invalid top_p format. Using default: {top_p}")
420
-
421
- # Validate max_tokens if provided
422
- if max_tokens is not None:
423
- try:
424
- max_tokens = int(max_tokens)
425
- if max_tokens <= 0:
426
- print("Invalid max_tokens value (must be positive). Not using max_tokens.")
427
- max_tokens = None
428
- except (ValueError, TypeError):
429
- print("Invalid max_tokens format. Not using max_tokens.")
430
- max_tokens = None
431
-
432
- # If we don't yet have a workspace or bot, create them
433
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
434
- print("No existing IDs found. Creating new workspace and bot...")
435
- GLOBAL_WORKSPACE_ID = create_workspace()
436
- if GLOBAL_WORKSPACE_ID:
437
- GLOBAL_BOT_ID = create_bot(GLOBAL_WORKSPACE_ID)
438
-
439
- # If creation failed
440
- if not GLOBAL_WORKSPACE_ID or not GLOBAL_BOT_ID:
441
- return jsonify({"assistant_response": "I'm currently unavailable. Please try again later."}), 500
442
-
443
- # Call our function that interacts with Botpress API
444
- print(f"Sending chat request with existing bot_id={GLOBAL_BOT_ID}, workspace_id={GLOBAL_WORKSPACE_ID}")
445
- print(f"Using temperature={temperature}, top_p={top_p}, max_tokens={max_tokens}")
446
-
447
- assistant_response, updated_bot_id, updated_workspace_id = chat_with_assistant(
448
- user_input,
449
- chat_history,
450
- GLOBAL_BOT_ID,
451
- GLOBAL_WORKSPACE_ID,
452
- temperature,
453
- top_p,
454
- max_tokens
455
- )
456
-
457
- # Update global IDs if they changed
458
- if updated_bot_id != GLOBAL_BOT_ID or updated_workspace_id != GLOBAL_WORKSPACE_ID:
459
- print(f"Updating global IDs: bot_id={updated_bot_id}, workspace_id={updated_workspace_id}")
460
- GLOBAL_BOT_ID = updated_bot_id
461
- GLOBAL_WORKSPACE_ID = updated_workspace_id
462
-
463
- return jsonify({"assistant_response": assistant_response})
464
-
465
-
466
- # -------------------------------------------------------------------
467
- # Run the Flask app
468
- # -------------------------------------------------------------------
469
 
470
  if __name__ == "__main__":
471
- app.run(host="0.0.0.0", port=7860, debug=True)
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, jsonify
2
+ import threading
 
 
3
  import time
4
+ import requests
5
+ from datetime import datetime
6
+ import logging
7
 
8
+ # Setup logging
9
+ logging.basicConfig(
10
+ level=logging.INFO,
11
+ format='%(asctime)s - %(levelname)s - %(message)s'
12
+ )
13
+ logger = logging.getLogger(__name__)
14
 
15
+ app = Flask(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ # ============ CONFIG ============
18
+ NUM_SERVERS = 55 # serverclass1 to serverclass55
19
+ PING_INTERVAL = 300 # seconds (5 minutes) - like cron-job.org
20
+ REQUEST_TIMEOUT = 30
21
+
22
+ # Templates for serverclassN URLs
23
+ SERVERCLASS_TEMPLATES = [
24
+ "https://serverclass{n}-claude-4-6-opus.hf.space/health",
25
+ "https://serverclass{n}-gpt-5-4.hf.space/health",
26
+ "https://serverclass{n}-tts.hf.space/health",
27
+ "https://serverclass{n}-transcript.hf.space/health",
28
+ ]
29
+
30
+ # Extra fixed URLs
31
+ EXTRA_URLS = [
32
+ "https://dooratre-backup.hf.space/health",
33
+ "https://dooratre-reload-bot.hf.space/health",
34
+ ]
35
+
36
+
37
+ def build_urls():
38
+ urls = []
39
+ for n in range(1, NUM_SERVERS + 1):
40
+ for tpl in SERVERCLASS_TEMPLATES:
41
+ urls.append(tpl.format(n=n))
42
+ urls.extend(EXTRA_URLS)
43
+ return urls
44
+
45
+
46
+ ALL_URLS = build_urls()
47
+
48
+ # ============ STATE ============
49
+ state = {
50
+ "running": False,
51
+ "thread": None,
52
+ "lock": threading.Lock(),
53
+ "stop_event": threading.Event(),
54
+ "last_cycle_start": None,
55
+ "last_cycle_end": None,
56
+ "cycles_completed": 0,
57
+ "total_pings": 0,
58
+ "success_count": 0,
59
+ "fail_count": 0,
60
+ "last_results": {},
61
+ }
62
+
63
+
64
+ def ping_url(url):
65
  try:
66
+ r = requests.get(url, timeout=REQUEST_TIMEOUT)
67
+ ok = r.status_code == 200
68
+ return ok, r.status_code, None
 
 
 
 
 
 
69
  except Exception as e:
70
+ return False, None, str(e)
71
+
72
+
73
+ def cron_worker(stop_event: threading.Event):
74
+ logger.info(f"Cron worker started. Total URLs: {len(ALL_URLS)}")
75
+ while not stop_event.is_set():
76
+ cycle_start = datetime.utcnow().isoformat()
77
+ state["last_cycle_start"] = cycle_start
78
+ logger.info(f"=== Cycle started at {cycle_start} ===")
79
+
80
+ for url in ALL_URLS:
81
+ if stop_event.is_set():
82
+ logger.info("Stop event received, breaking cycle.")
83
+ break
84
+ ok, status, err = ping_url(url)
85
+ state["total_pings"] += 1
86
+ if ok:
87
+ state["success_count"] += 1
88
+ logger.info(f"[OK {status}] {url}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  else:
90
+ state["fail_count"] += 1
91
+ logger.warning(f"[FAIL {status}] {url} | err={err}")
92
+ state["last_results"][url] = {
93
+ "ok": ok,
94
+ "status": status,
95
+ "error": err,
96
+ "time": datetime.utcnow().isoformat(),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
 
 
98
 
99
+ state["cycles_completed"] += 1
100
+ state["last_cycle_end"] = datetime.utcnow().isoformat()
101
+ logger.info(f"=== Cycle done. Sleeping {PING_INTERVAL}s ===")
102
+
103
+ # Sleep but be responsive to stop_event
104
+ stop_event.wait(PING_INTERVAL)
105
+
106
+ logger.info("Cron worker stopped.")
107
+
108
+
109
+ @app.route("/")
110
+ def index():
111
+ return jsonify({
112
+ "service": "Cron-like pinger",
113
+ "running": state["running"],
114
+ "total_urls": len(ALL_URLS),
115
+ "endpoints": ["/start", "/end", "/status", "/urls"],
116
+ })
117
+
118
+
119
+ @app.route("/start", methods=["GET", "POST"])
120
+ def start():
121
+ with state["lock"]:
122
+ if state["running"]:
123
+ return jsonify({"status": "already_running"}), 200
124
+
125
+ state["stop_event"] = threading.Event()
126
+ t = threading.Thread(target=cron_worker, args=(state["stop_event"],), daemon=True)
127
+ state["thread"] = t
128
+ state["running"] = True
129
+ t.start()
130
+ logger.info("Cron started via /start")
131
+ return jsonify({
132
+ "status": "started",
133
+ "total_urls": len(ALL_URLS),
134
+ "interval_seconds": PING_INTERVAL,
135
+ })
136
 
137
 
138
+ @app.route("/end", methods=["GET", "POST"])
139
+ def end():
140
+ with state["lock"]:
141
+ if not state["running"]:
142
+ return jsonify({"status": "not_running"}), 200
143
 
144
+ state["stop_event"].set()
145
+ state["running"] = False
146
+ logger.info("Cron stop requested via /end")
147
+ return jsonify({"status": "stopping"})
 
 
148
 
 
 
 
 
 
 
 
 
 
 
 
149
 
150
+ @app.route("/status")
151
+ def status():
152
+ return jsonify({
153
+ "running": state["running"],
154
+ "total_urls": len(ALL_URLS),
155
+ "cycles_completed": state["cycles_completed"],
156
+ "total_pings": state["total_pings"],
157
+ "success_count": state["success_count"],
158
+ "fail_count": state["fail_count"],
159
+ "last_cycle_start": state["last_cycle_start"],
160
+ "last_cycle_end": state["last_cycle_end"],
161
+ "interval_seconds": PING_INTERVAL,
162
+ })
163
 
 
 
 
 
 
164
 
165
+ @app.route("/urls")
166
+ def urls():
167
+ return jsonify({"count": len(ALL_URLS), "urls": ALL_URLS})
 
 
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
+ @app.route("/results")
171
+ def results():
172
+ return jsonify(state["last_results"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
+ @app.route("/health")
176
+ def health():
177
+ return jsonify({"status": "ok"})
178
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
  if __name__ == "__main__":
181
+ # Auto-start cron on boot (optional - comment out if you only want manual /start)
182
+ # Uncomment below if you want it to auto-run:
183
+ # state["stop_event"] = threading.Event()
184
+ # t = threading.Thread(target=cron_worker, args=(state["stop_event"],), daemon=True)
185
+ # state["thread"] = t
186
+ # state["running"] = True
187
+ # t.start()
188
+
189
+ app.run(host="0.0.0.0", port=7860, debug=False, threaded=True)