Spaces:
Paused
Paused
| 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""" | |
| <html> | |
| <body style="font-family: sans-serif; text-align: center; margin-top: 50px;"> | |
| <h2>π {NAMA_BOT} SEDANG AKTIF</h2> | |
| <p><b>Waktu Tempatan (KL):</b> {waktu}</p> | |
| <p style="color: green;">Status: Menjejak data langsung ISS & Kru Ekspedisi Terkini</p> | |
| </body> | |
| </html> | |
| """ | |
| 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() |