Update main.py

This commit is contained in:
2026-02-01 15:05:47 +00:00
parent 91df268a57
commit 92a1146025

137
main.py
View File

@@ -1,36 +1,38 @@
import os
import time
import json
import requests
from datetime import datetime
from pathlib import Path
# ===== SMS GATEWAY =====
SMS_USER = "weatherbot"
SMS_PASS = "Cupakabra17"
SMS_USER = os.getenv("SMS_USER", "weatherbot")
SMS_PASS = os.getenv("SMS_PASS", "Cupakabra17")
RECEIVE_URL = "https://sms.internet-master.cz/receive/"
SEND_URL = "https://sms.internet-master.cz/send/"
POLL_INTERVAL = 1 # 🔥 1 second polling (worst-case 5s)
MAX_ERRORS_BEFORE_SLEEP = 3
# Latency target: worst-case <= ~5s
POLL_INTERVAL = 1 # seconds
seen_offsets = set()
error_count = 0
# Persistence
STATE_FILE = Path("weatherbot_state.json")
MAX_SEEN_OFFSETS = 5000 # cap memory/state growth
HEADERS = {
"User-Agent": "weatherbot/1.0 (jakub@jimbuntu)"
}
# =======================
session = requests.Session()
session.headers.update(HEADERS)
def fetch_inbox():
def fetch_inbox(limit=5):
r = session.get(
RECEIVE_URL,
params={
"username": SMS_USER,
"password": SMS_PASS,
"limit": 5
"limit": limit
},
timeout=5
)
@@ -64,7 +66,6 @@ def get_weather(city):
return f"❌ Weather unavailable for {city}"
msg = f"🌦 Weather for {city}:\n"
for day in weather[:3]:
date = day["date"]
avgtemp = day["avgtempC"]
@@ -74,40 +75,120 @@ def get_weather(city):
return msg.strip()
# ===== MAIN LOOP =====
print("📡 SMS Weather Bot started (≤5s reply mode)")
def load_state():
if not STATE_FILE.exists():
return {"seen_offsets": []}
try:
with STATE_FILE.open("r", encoding="utf-8") as f:
data = json.load(f)
if not isinstance(data, dict):
return {"seen_offsets": []}
if "seen_offsets" not in data or not isinstance(data["seen_offsets"], list):
data["seen_offsets"] = []
return data
except Exception:
# If state is corrupt, start clean (but startup priming still prevents backlog replies)
return {"seen_offsets": []}
def save_state(state):
# Keep only the newest N offsets (as ints if possible)
seen = state.get("seen_offsets", [])
if len(seen) > MAX_SEEN_OFFSETS:
seen = seen[-MAX_SEEN_OFFSETS:]
state["seen_offsets"] = seen
tmp = STATE_FILE.with_suffix(".json.tmp")
with tmp.open("w", encoding="utf-8") as f:
json.dump(state, f, ensure_ascii=False)
tmp.replace(STATE_FILE)
def prime_seen_offsets(seen_set):
"""
On startup, mark whatever is currently in the inbox as 'already seen'
so we do NOT reply to backlog after a restart.
"""
try:
data = fetch_inbox(limit=20)
inbox = data.get("inbox", [])
primed = 0
for sms in inbox:
offset = sms.get("offset")
if offset is None:
continue
if offset not in seen_set:
seen_set.add(offset)
primed += 1
if primed:
print(f"🧹 Primed {primed} existing message(s) as already-seen (no backlog replies).")
else:
print("🧹 No existing messages to prime.")
except Exception as e:
print("⚠ Prime failed (continuing):", e)
def add_seen(seen_set, seen_list, offset):
# offset can be int/str depending on API; store exactly as returned
if offset in seen_set:
return False
seen_set.add(offset)
seen_list.append(offset)
# cap list size
if len(seen_list) > MAX_SEEN_OFFSETS:
# drop oldest chunk
drop = len(seen_list) - MAX_SEEN_OFFSETS
for old in seen_list[:drop]:
seen_set.discard(old)
del seen_list[:drop]
return True
# ===== MAIN =====
print("📡 SMS Weather Bot started (≤5s reply + persistent offsets)")
state = load_state()
seen_list = state.get("seen_offsets", [])
seen_set = set(seen_list)
# Prevent replying to old messages sitting in inbox after restart
prime_seen_offsets(seen_set)
# Sync list to set after priming (preserve order by appending primed ones)
# (We don't know which were primed vs existing; easiest is to rewrite list from set as a stable list)
# But to preserve bounded size, we just extend list with items missing:
for off in list(seen_set):
if off not in seen_list:
seen_list.append(off)
state["seen_offsets"] = seen_list
save_state(state)
while True:
try:
data = fetch_inbox()
data = fetch_inbox(limit=5)
inbox = data.get("inbox", [])
error_count = 0 # reset on success
for sms in inbox:
offset = sms["offset"]
if offset in seen_offsets:
offset = sms.get("offset")
if offset is None:
continue
phone = sms["phone"]
text = sms["message"].strip()
# If already processed, skip
if offset in seen_set:
continue
phone = sms.get("phone", "")
text = (sms.get("message") or "").strip()
# Mark seen immediately to avoid duplicate replies if send fails mid-loop
add_seen(seen_set, seen_list, offset)
state["seen_offsets"] = seen_list
save_state(state)
print(f"📨 {phone}: {text}")
reply = get_weather(text)
send_sms(phone, reply)
seen_offsets.add(offset)
print(f"✅ Replied to {phone}")
except Exception as e:
error_count += 1
print("⚠ ERROR:", e)
# If gateway is unstable, slow down briefly
if error_count >= MAX_ERRORS_BEFORE_SLEEP:
print("⏳ Too many errors, sleeping 5s")
time.sleep(5)
error_count = 0
# Keep latency target: no long backoff; next poll in 1s
time.sleep(POLL_INTERVAL)