Spaces:
Sleeping
Sleeping
Update main.py
Browse files
main.py
CHANGED
|
@@ -6,18 +6,6 @@ from datetime import datetime
|
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
import threading
|
| 8 |
from http.server import BaseHTTPRequestHandler, HTTPServer
|
| 9 |
-
import socket
|
| 10 |
-
|
| 11 |
-
# --- Hugging Face DNS Workaround ---
|
| 12 |
-
# Hugging Face Spaces often fail to resolve 'api.telegram.org' via DNS.
|
| 13 |
-
# We monkey-patch socket.getaddrinfo to force it to use Telegram's known IP.
|
| 14 |
-
_old_getaddrinfo = socket.getaddrinfo
|
| 15 |
-
def _new_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
|
| 16 |
-
if host.lower() == 'api.telegram.org':
|
| 17 |
-
return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, '', ('149.154.167.220', port))]
|
| 18 |
-
return _old_getaddrinfo(host, port, family, type, proto, flags)
|
| 19 |
-
socket.getaddrinfo = _new_getaddrinfo
|
| 20 |
-
# -----------------------------------
|
| 21 |
|
| 22 |
# Load optional .env if present in same directory
|
| 23 |
load_dotenv()
|
|
@@ -28,9 +16,13 @@ load_dotenv()
|
|
| 28 |
# Environment values (Make sure to populate your .env file)
|
| 29 |
# We will reload these inside the loop so you can change them on the fly.
|
| 30 |
|
| 31 |
-
#
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
|
| 35 |
# App check interval in seconds (default 60 secs = 1 min)
|
| 36 |
CHECK_INTERVAL_SECONDS = 60
|
|
@@ -50,9 +42,15 @@ EXPIRED_BIP = None
|
|
| 50 |
|
| 51 |
|
| 52 |
# ==============================================================================
|
| 53 |
-
#
|
| 54 |
# ==============================================================================
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
def update_env_file(new_xsrf, new_bip):
|
| 58 |
env_path = ".env"
|
|
@@ -82,31 +80,40 @@ def update_env_file(new_xsrf, new_bip):
|
|
| 82 |
if new_bip is not None and not bip_updated:
|
| 83 |
f.write(f'BIP_SESSION="{new_bip}"\n')
|
| 84 |
|
| 85 |
-
def
|
| 86 |
-
global
|
| 87 |
-
if not
|
| 88 |
|
| 89 |
-
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/getUpdates"
|
| 90 |
-
params = {"timeout": 5}
|
| 91 |
-
if TELEGRAM_OFFSET:
|
| 92 |
-
params["offset"] = TELEGRAM_OFFSET
|
| 93 |
-
|
| 94 |
try:
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
new_xsrf = None
|
| 108 |
new_bip = None
|
| 109 |
-
for line in text.
|
| 110 |
line = line.strip()
|
| 111 |
if line.startswith("XSRF_TOKEN=") or line.startswith("XSRF-TOKEN="):
|
| 112 |
parts = line.split("=", 1)
|
|
@@ -116,45 +123,51 @@ def check_telegram_messages():
|
|
| 116 |
parts = line.split("=", 1)
|
| 117 |
if len(parts) > 1:
|
| 118 |
new_bip = parts[1].strip(' "\'')
|
| 119 |
-
|
| 120 |
if new_xsrf or new_bip:
|
| 121 |
update_env_file(new_xsrf, new_bip)
|
| 122 |
-
|
| 123 |
SESSION_EXPIRED = False
|
| 124 |
EXPIRED_XSRF = None
|
| 125 |
EXPIRED_BIP = None
|
| 126 |
-
|
| 127 |
-
print(f"[{datetime.now().strftime('%I:%M:%S %p')}] 🔄 Cookies received via
|
| 128 |
-
return
|
| 129 |
except Exception as e:
|
| 130 |
-
print(f"[DEBUG]
|
| 131 |
return False
|
| 132 |
-
|
| 133 |
-
def
|
| 134 |
-
print(f"\n[DEBUG] -->
|
| 135 |
-
if not
|
| 136 |
-
print("\n[!]
|
| 137 |
-
print(
|
|
|
|
| 138 |
print("-" * 50)
|
| 139 |
return False
|
| 140 |
|
| 141 |
-
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
|
| 142 |
-
payload = {
|
| 143 |
-
"chat_id": TELEGRAM_CHAT_ID,
|
| 144 |
-
"text": text,
|
| 145 |
-
"parse_mode": "HTML",
|
| 146 |
-
"disable_web_page_preview": True
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
try:
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
return True
|
| 155 |
except Exception as e:
|
| 156 |
-
print(f"[DEBUG] Network/
|
| 157 |
-
print(f"[!] Failed to send
|
| 158 |
return False
|
| 159 |
|
| 160 |
def send_event_alerts(events):
|
|
@@ -176,17 +189,17 @@ def send_event_alerts(events):
|
|
| 176 |
|
| 177 |
msg += (
|
| 178 |
f"<b>{ev.get('event_code', '-')} - "
|
| 179 |
-
f"{ev.get('event_name', 'Unknown')}</b>
|
| 180 |
-
f"{ev.get('event_category', '-')}
|
| 181 |
-
f"{start} to {end}
|
| 182 |
-
f"{ev.get('location', '-')}
|
| 183 |
-
f"{ev.get('status', '-')}
|
| 184 |
f"Seats: Max {ev.get('maximum_count', '-')} | "
|
| 185 |
-
f"Applied {ev.get('applied_count', '-')}
|
| 186 |
-
f"<a href='{ev.get('web_url', '#')}'>View Event Here</a>
|
| 187 |
-
f"
|
| 188 |
)
|
| 189 |
-
|
| 190 |
|
| 191 |
|
| 192 |
# ==============================================================================
|
|
@@ -292,12 +305,14 @@ def process_tick():
|
|
| 292 |
|
| 293 |
if err:
|
| 294 |
print(f"{time_str.lower()} - ❌ Error scraping events: {err}")
|
| 295 |
-
|
| 296 |
-
"⚠️
|
| 297 |
-
|
| 298 |
-
f"<
|
| 299 |
-
"
|
| 300 |
-
"<
|
|
|
|
|
|
|
| 301 |
)
|
| 302 |
SESSION_EXPIRED = True
|
| 303 |
EXPIRED_XSRF = xsrf
|
|
@@ -376,10 +391,10 @@ def get_latest_event():
|
|
| 376 |
print(f"Link: {ev.get('web_url')}")
|
| 377 |
print("-" * 60)
|
| 378 |
|
| 379 |
-
def
|
| 380 |
-
"""Sends a dummy test message to the configured
|
| 381 |
-
print("Sending test exact alert to
|
| 382 |
-
success =
|
| 383 |
if success:
|
| 384 |
print("✅ Test message sent successfully!")
|
| 385 |
else:
|
|
@@ -399,7 +414,7 @@ def start_loop():
|
|
| 399 |
target_time = time.time() + CHECK_INTERVAL_SECONDS
|
| 400 |
|
| 401 |
while time.time() < target_time:
|
| 402 |
-
cookies_updated =
|
| 403 |
if cookies_updated:
|
| 404 |
# User sent new cookies, immediately break to run process_tick right now!
|
| 405 |
break
|
|
@@ -436,7 +451,7 @@ if __name__ == "__main__":
|
|
| 436 |
parser = argparse.ArgumentParser(description="BIP Cloud Notifier CLI")
|
| 437 |
parser.add_argument("--list-all", action="store_true", help="List all recent events and exit")
|
| 438 |
parser.add_argument("--latest", action="store_true", help="Print details of the latest event and exit")
|
| 439 |
-
parser.add_argument("--test-alert", action="store_true", help="Send a test message to
|
| 440 |
parser.add_argument("--run", action="store_true", help="Start the continuous 1-minute monitoring loop")
|
| 441 |
print("Parsed arguments:", parser.parse_args())
|
| 442 |
args = parser.parse_args()
|
|
@@ -446,7 +461,7 @@ if __name__ == "__main__":
|
|
| 446 |
elif args.latest:
|
| 447 |
get_latest_event()
|
| 448 |
elif args.test_alert:
|
| 449 |
-
|
| 450 |
elif args.run:
|
| 451 |
threading.Thread(target=run_dummy_server, daemon=True).start()
|
| 452 |
start_loop()
|
|
|
|
| 6 |
from dotenv import load_dotenv
|
| 7 |
import threading
|
| 8 |
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# Load optional .env if present in same directory
|
| 11 |
load_dotenv()
|
|
|
|
| 16 |
# Environment values (Make sure to populate your .env file)
|
| 17 |
# We will reload these inside the loop so you can change them on the fly.
|
| 18 |
|
| 19 |
+
# Email settings (Loaded from .env)
|
| 20 |
+
# The email you are sending FROM and TO (can be the same)
|
| 21 |
+
EMAIL_ADDRESS = os.getenv("EMAIL_ADDRESS", "")
|
| 22 |
+
# Gmail App Password (NOT your regular password)
|
| 23 |
+
EMAIL_PASSWORD = os.getenv("EMAIL_PASSWORD", "")
|
| 24 |
+
# Notification recipient
|
| 25 |
+
EMAIL_RECIPIENT = os.getenv("EMAIL_RECIPIENT", EMAIL_ADDRESS)
|
| 26 |
|
| 27 |
# App check interval in seconds (default 60 secs = 1 min)
|
| 28 |
CHECK_INTERVAL_SECONDS = 60
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
# ==============================================================================
|
| 45 |
+
# EMAIL NOTIFIER
|
| 46 |
# ==============================================================================
|
| 47 |
+
import smtplib
|
| 48 |
+
from email.mime.text import MIMEText
|
| 49 |
+
from email.mime.multipart import MIMEMultipart
|
| 50 |
+
import imaplib
|
| 51 |
+
import email
|
| 52 |
+
|
| 53 |
+
LAST_SEEN_EMAIL_UID = None
|
| 54 |
|
| 55 |
def update_env_file(new_xsrf, new_bip):
|
| 56 |
env_path = ".env"
|
|
|
|
| 80 |
if new_bip is not None and not bip_updated:
|
| 81 |
f.write(f'BIP_SESSION="{new_bip}"\n')
|
| 82 |
|
| 83 |
+
def check_email_for_cookies():
|
| 84 |
+
global LAST_SEEN_EMAIL_UID, SESSION_EXPIRED, EXPIRED_XSRF, EXPIRED_BIP
|
| 85 |
+
if not EMAIL_ADDRESS or not EMAIL_PASSWORD: return False
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
try:
|
| 88 |
+
# Connect to Gmail IMAP
|
| 89 |
+
mail = imaplib.IMAP4_SSL('imap.gmail.com')
|
| 90 |
+
mail.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
|
| 91 |
+
mail.select('inbox')
|
| 92 |
+
|
| 93 |
+
# Search for unread emails with our specific subject
|
| 94 |
+
status, messages = mail.search(None, '(UNSEEN SUBJECT "BIP_COOKIES")')
|
| 95 |
+
if status != 'OK': return False
|
| 96 |
+
|
| 97 |
+
cookie_updated = False
|
| 98 |
+
message_ids = messages[0].split()
|
| 99 |
+
|
| 100 |
+
for msg_id in message_ids:
|
| 101 |
+
status, msg_data = mail.fetch(msg_id, '(RFC822)')
|
| 102 |
+
for response_part in msg_data:
|
| 103 |
+
if isinstance(response_part, tuple):
|
| 104 |
+
msg = email.message_from_bytes(response_part[1])
|
| 105 |
+
if msg.is_multipart():
|
| 106 |
+
for part in msg.walk():
|
| 107 |
+
if part.get_content_type() == "text/plain":
|
| 108 |
+
text = part.get_payload(decode=True).decode()
|
| 109 |
+
break
|
| 110 |
+
else:
|
| 111 |
+
text = msg.get_payload(decode=True).decode()
|
| 112 |
+
|
| 113 |
+
# Process text
|
| 114 |
new_xsrf = None
|
| 115 |
new_bip = None
|
| 116 |
+
for line in text.split("\n"):
|
| 117 |
line = line.strip()
|
| 118 |
if line.startswith("XSRF_TOKEN=") or line.startswith("XSRF-TOKEN="):
|
| 119 |
parts = line.split("=", 1)
|
|
|
|
| 123 |
parts = line.split("=", 1)
|
| 124 |
if len(parts) > 1:
|
| 125 |
new_bip = parts[1].strip(' "\'')
|
| 126 |
+
|
| 127 |
if new_xsrf or new_bip:
|
| 128 |
update_env_file(new_xsrf, new_bip)
|
| 129 |
+
send_email_message("Cookies Received", "✅ Cookies Received!\n\nI have updated the .env file and will now resume checking the BIP portal.")
|
| 130 |
SESSION_EXPIRED = False
|
| 131 |
EXPIRED_XSRF = None
|
| 132 |
EXPIRED_BIP = None
|
| 133 |
+
cookie_updated = True
|
| 134 |
+
print(f"[{datetime.now().strftime('%I:%M:%S %p')}] 🔄 Cookies received via Email! Resuming checks...")
|
| 135 |
+
return cookie_updated
|
| 136 |
except Exception as e:
|
| 137 |
+
print(f"[DEBUG] IMAP EXCEPTION: {e}")
|
| 138 |
return False
|
| 139 |
+
|
| 140 |
+
def send_email_message(subject: str, body: str, is_html=False):
|
| 141 |
+
print(f"\n[DEBUG] --> send_email_message called")
|
| 142 |
+
if not EMAIL_ADDRESS or not EMAIL_PASSWORD or not EMAIL_RECIPIENT:
|
| 143 |
+
print("\n[!] Email credentials not configured. The following alert would have been sent:\n")
|
| 144 |
+
print(f"Subject: {subject}")
|
| 145 |
+
print(body)
|
| 146 |
print("-" * 50)
|
| 147 |
return False
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
try:
|
| 150 |
+
msg = MIMEMultipart()
|
| 151 |
+
msg['From'] = EMAIL_ADDRESS
|
| 152 |
+
msg['To'] = EMAIL_RECIPIENT
|
| 153 |
+
msg['Subject'] = subject
|
| 154 |
+
|
| 155 |
+
if is_html:
|
| 156 |
+
msg.attach(MIMEText(body, 'html'))
|
| 157 |
+
else:
|
| 158 |
+
msg.attach(MIMEText(body, 'plain'))
|
| 159 |
+
|
| 160 |
+
server = smtplib.SMTP('smtp.gmail.com', 587)
|
| 161 |
+
server.starttls()
|
| 162 |
+
server.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
|
| 163 |
+
text = msg.as_string()
|
| 164 |
+
server.sendmail(EMAIL_ADDRESS, EMAIL_RECIPIENT, text)
|
| 165 |
+
server.quit()
|
| 166 |
+
print(f"[DEBUG] Email sent successfully.")
|
| 167 |
return True
|
| 168 |
except Exception as e:
|
| 169 |
+
print(f"[DEBUG] Network/SMTP EXCEPTION: {e}")
|
| 170 |
+
print(f"[!] Failed to send email: {e}")
|
| 171 |
return False
|
| 172 |
|
| 173 |
def send_event_alerts(events):
|
|
|
|
| 189 |
|
| 190 |
msg += (
|
| 191 |
f"<b>{ev.get('event_code', '-')} - "
|
| 192 |
+
f"{ev.get('event_name', 'Unknown')}</b><br><br>"
|
| 193 |
+
f"{ev.get('event_category', '-')}<br><br>"
|
| 194 |
+
f"{start} to {end}<br><br>"
|
| 195 |
+
f"{ev.get('location', '-')}<br><br>"
|
| 196 |
+
f"{ev.get('status', '-')}<br><br>"
|
| 197 |
f"Seats: Max {ev.get('maximum_count', '-')} | "
|
| 198 |
+
f"Applied {ev.get('applied_count', '-')}<br>"
|
| 199 |
+
f"<a href='{ev.get('web_url', '#')}'>View Event Here</a><br>"
|
| 200 |
+
f"<hr>"
|
| 201 |
)
|
| 202 |
+
send_email_message("📢 New BIP Event(s) Found!", msg, is_html=True)
|
| 203 |
|
| 204 |
|
| 205 |
# ==============================================================================
|
|
|
|
| 305 |
|
| 306 |
if err:
|
| 307 |
print(f"{time_str.lower()} - ❌ Error scraping events: {err}")
|
| 308 |
+
send_email_message(
|
| 309 |
+
"⚠️ BIP Scraper Error",
|
| 310 |
+
"⚠️ <b>Scraper Error!</b><br><br>"
|
| 311 |
+
f"The notifier encountered an error and has paused checking. Error:<br>"
|
| 312 |
+
f"<code>{err}</code><br><br>"
|
| 313 |
+
"Please send an email to this address with the subject <b>BIP_COOKIES</b> and your new cookies in the body like this:<br><br>"
|
| 314 |
+
"<code>XSRF_TOKEN=your_new_token_here<br>BIP_SESSION=your_new_session_here</code>",
|
| 315 |
+
is_html=True
|
| 316 |
)
|
| 317 |
SESSION_EXPIRED = True
|
| 318 |
EXPIRED_XSRF = xsrf
|
|
|
|
| 391 |
print(f"Link: {ev.get('web_url')}")
|
| 392 |
print("-" * 60)
|
| 393 |
|
| 394 |
+
def test_email_alert():
|
| 395 |
+
"""Sends a dummy test message to the configured Email."""
|
| 396 |
+
print("Sending test exact alert to Email...")
|
| 397 |
+
success = send_email_message("🤖 Test Alert", "🤖 <b>Test Alert from BIP CLI Notifier</b><br><br>Your Email integration is working perfectly!", is_html=True)
|
| 398 |
if success:
|
| 399 |
print("✅ Test message sent successfully!")
|
| 400 |
else:
|
|
|
|
| 414 |
target_time = time.time() + CHECK_INTERVAL_SECONDS
|
| 415 |
|
| 416 |
while time.time() < target_time:
|
| 417 |
+
cookies_updated = check_email_for_cookies()
|
| 418 |
if cookies_updated:
|
| 419 |
# User sent new cookies, immediately break to run process_tick right now!
|
| 420 |
break
|
|
|
|
| 451 |
parser = argparse.ArgumentParser(description="BIP Cloud Notifier CLI")
|
| 452 |
parser.add_argument("--list-all", action="store_true", help="List all recent events and exit")
|
| 453 |
parser.add_argument("--latest", action="store_true", help="Print details of the latest event and exit")
|
| 454 |
+
parser.add_argument("--test-alert", action="store_true", help="Send a test message to Email and exit")
|
| 455 |
parser.add_argument("--run", action="store_true", help="Start the continuous 1-minute monitoring loop")
|
| 456 |
print("Parsed arguments:", parser.parse_args())
|
| 457 |
args = parser.parse_args()
|
|
|
|
| 461 |
elif args.latest:
|
| 462 |
get_latest_event()
|
| 463 |
elif args.test_alert:
|
| 464 |
+
test_email_alert()
|
| 465 |
elif args.run:
|
| 466 |
threading.Thread(target=run_dummy_server, daemon=True).start()
|
| 467 |
start_loop()
|