import reverse_geocoder as rg import os, time, math, requests, threading from http.server import HTTPServer, BaseHTTPRequestHandler from datetime import datetime from zoneinfo import ZoneInfo # --- CONFIG --- KL_TZ = ZoneInfo('Asia/Kuala_Lumpur') WEBHOOK_URL = os.getenv("DISCORD_WEBHOOK") MY_DISCORD_ID = os.getenv("MY_DISCORD_ID") GAMBAR_BOT = "https://i.ibb.co/KpyqyjPg/20251224-130538.jpg" NAMA_BOT = "Kapten ISS Pikeuli (Terkini)" # Koordinat Selayang Kau MY_LAT, MY_LON = 3.2350656, 101.6604678 ALERT_RADIUS = 350 # KM # --- DUMMY SERVER (UNTUK KEEP-ALIVE) --- class KeepAliveServer(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header("Content-type", "text/html; charset=utf-8") self.end_headers() waktu = datetime.now(KL_TZ).strftime('%d %b %Y %H:%M:%S') html = f"""
Waktu Tempatan (KL): {waktu}
Status: Menjejak data langsung ISS & Kru Ekspedisi Terkini
""" self.wfile.write(html.encode('utf-8')) def log_message(self, format, *args): return def run_server(): try: server = HTTPServer(('0.0.0.0', 7860), KeepAliveServer) server.serve_forever() except Exception as e: print(f"Ralat Server: {e}") # --- FUNGSI LOKASI & KRU (LIVE DATA) --- def get_location_name(lat, lon): try: results = rg.search((lat, lon)) bandar = results[0].get('name', '') negeri = results[0].get('admin1', '') negara = results[0].get('cc', '') return f"{bandar}, {negeri} ({negara})" except: return "Atas Lautan / Kawasan Terpencil" def get_current_crew(): """Tarik data live siapa yang ada di ISS sekarang (Ekspedisi Semasa)""" try: r = requests.get("http://api.open-notify.org/astros.json", timeout=10) if r.status_code == 200: data = r.json() # Tapis untuk kru ISS sahaja iss_crew = [p["name"] for p in data.get("people",[]) if p.get("craft") == "ISS"] return iss_crew except: pass return[] # --- FUNGSI PENGIRAAN --- def get_distance(lat1, lon1, lat2, lon2): R = 6371 dlat, dlon = math.radians(lat2-lat1), math.radians(lon2-lon1) a = math.sin(dlat/2)**2 + math.cos(math.radians(lat1))*math.cos(math.radians(lat2))*math.sin(dlon/2)**2 return R * (2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))) def get_bearing(lat1, lon1, lat2, lon2): lat1, lon1, lat2, lon2 = map(math.radians,[lat1, lon1, lat2, lon2]) dLon = lon2 - lon1 y = math.sin(dLon) * math.cos(lat2) x = math.cos(lat1) * math.sin(lat2) - math.sin(lat1) * math.cos(lat2) * math.cos(dLon) bearing = math.degrees(math.atan2(y, x)) return (bearing + 360) % 360 def get_compass(bearing): directions =["Utara", "Timur Laut", "Timur", "Tenggara", "Selatan", "Barat Daya", "Barat", "Barat Laut"] idx = int((bearing + 22.5) / 45) % 8 return directions[idx] def send_discord(content, embed=None): if not WEBHOOK_URL: return url = WEBHOOK_URL.replace("discord.com", "discordapp.com") payload = {"content": content, "embeds": [embed] if embed else[], "username": NAMA_BOT, "avatar_url": GAMBAR_BOT} try: requests.post(url, json=payload, timeout=15) except: pass # --- LOOP UTAMA --- def tracking_loop(): print("🛰️ Menjejak ISS menggunakan data API Terkini...", flush=True) last_hour = -1 is_overhead = False while True: try: # Tetap gunakan wheretheiss untuk ketepatan Altitud/Kelajuan r = requests.get("https://api.wheretheiss.at/v1/satellites/25544", timeout=10) if r.status_code == 200: data = r.json() lat, lon = float(data['latitude']), float(data['longitude']) dist = get_distance(MY_LAT, MY_LON, lat, lon) bearing = get_bearing(MY_LAT, MY_LON, lat, lon) velocity = data['velocity'] altitude = data['altitude'] now = datetime.now(KL_TZ) # 1. Update Hourly (Laporan Jam + Update Kru ISS) if now.hour != last_hour: nama_lokasi = get_location_name(lat, lon) kru_sebenar = get_current_crew() jumlah_kru = len(kru_sebenar) if kru_sebenar else "Tidak diketahui" nama_kru = ", ".join(kru_sebenar) if kru_sebenar else "Gagal menyemak nama kru" embed = { "title": f"📊 Laporan Jam ISS ({now.strftime('%H:%M')})", "description": f"ISS sekarang berada di atas:\n**🌍 {nama_lokasi}**", "color": 3447003, "fields":[ {"name": "📏 Jarak ke Selayang", "value": f"{dist:.2f} km", "inline": True}, {"name": "🧭 Arah", "value": f"{get_compass(bearing)}", "inline": True}, {"name": "👨🚀 Senarai Angkasawan di ISS", "value": f"**{jumlah_kru} Orang:**\n_{nama_kru}_", "inline": False} ], "footer": {"text": f"Kemas kini: {now.strftime('%d %b %Y')}"} } send_discord(None, embed) last_hour = now.hour # 2. Alert Ping (Radius < 350km) if dist <= ALERT_RADIUS: if not is_overhead: nama_lokasi = get_location_name(lat, lon) ping = f"<@{MY_DISCORD_ID}>" if MY_DISCORD_ID else "Bro" embed = { "title": "🚀 ISS MASUK RADAR SELAYANG!", "description": f"Lokasi Sekarang: **🌍 {nama_lokasi}**", "color": 15158332, "fields":[ {"name": "🧭 Arah Mata Angin", "value": f"{get_compass(bearing)} ({bearing:.0f}°)", "inline": True}, {"name": "📏 Jarak", "value": f"{dist:.2f} km", "inline": True}, {"name": "💨 Kelajuan", "value": f"{velocity:.0f} km/j", "inline": True}, {"name": "🏔️ Altitud", "value": f"{altitude:.0f} km", "inline": True} ], "footer": {"text": f"Masa Pintasan: {now.strftime('%H:%M:%S')}"} } send_discord(f"🚨 {ping} ISS TENGAH LALU!", embed) is_overhead = True else: is_overhead = False except Exception as e: pass time.sleep(60) if __name__ == "__main__": threading.Thread(target=run_server, daemon=True).start() tracking_loop()