文件预览

myquran_sholat.py

查看 Jadwal Sholat 技能包中的文件内容。

文件内容

scripts/myquran_sholat.py

#!/usr/bin/env python3
"""Helper untuk API jadwal sholat Indonesia (api.myquran.com).

Tidak butuh dependency eksternal (stdlib only).

Commands:
  cari <keyword>
  hari-ini <keyword>
  tanggal <keyword> <YYYY-MM-DD>
  bulan <keyword> <YYYY-MM>
  id-hari-ini <id>
  id-tanggal <id> <YYYY-MM-DD>
  id-bulan <id> <YYYY-MM>

Output default: teks ringkas.
"""

from __future__ import annotations

import argparse
import json
import sys
import urllib.parse
import urllib.request
from typing import Any, Dict, List, Optional, Tuple

API_BASE = "https://api.myquran.com/v3"
DEFAULT_TZ = "Asia/Jakarta"


def _get_json(url: str, timeout: int = 20) -> Dict[str, Any]:
    req = urllib.request.Request(url, headers={"User-Agent": "openclaw-jadwal-sholat/1.0"})
    with urllib.request.urlopen(req, timeout=timeout) as resp:
        raw = resp.read().decode("utf-8", errors="replace")
    return json.loads(raw)


def search_locations(keyword: str) -> List[Dict[str, Any]]:
    kw = urllib.parse.quote(keyword.strip())
    url = f"{API_BASE}/sholat/kabkota/cari/{kw}"
    j = _get_json(url)
    if not j.get("status"):
        return []
    data = j.get("data")
    if isinstance(data, list):
        return data
    return []


def pick_location(keyword: str, items: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
    if not items:
        return None
    k = keyword.strip().lower()
    # Exact match against lokasi field
    for it in items:
        lokasi = str(it.get("lokasi", "")).strip().lower()
        if lokasi == k:
            return it
    # Contains match
    for it in items:
        lokasi = str(it.get("lokasi", "")).strip().lower()
        if k and k in lokasi:
            return it
    return items[0]


def fetch_jadwal_today(id_: str, tz: str = DEFAULT_TZ) -> Dict[str, Any]:
    q = urllib.parse.urlencode({"tz": tz})
    url = f"{API_BASE}/sholat/jadwal/{urllib.parse.quote(id_)}/today?{q}"
    return _get_json(url)


def fetch_jadwal_period(id_: str, period: str, tz: str = DEFAULT_TZ) -> Dict[str, Any]:
    q = urllib.parse.urlencode({"tz": tz})
    url = f"{API_BASE}/sholat/jadwal/{urllib.parse.quote(id_)}/{urllib.parse.quote(period)}?{q}"
    return _get_json(url)


def _extract_single_day(j: Dict[str, Any]) -> Tuple[str, Dict[str, Any], Dict[str, Any]]:
    """Return (label, meta, day) where day is an entry with keys imsak/subuh/..."""
    data = j.get("data") or {}
    meta = {
        "id": data.get("id"),
        "kabko": data.get("kabko"),
        "prov": data.get("prov"),
    }
    jadwal = (data.get("jadwal") or {})
    if not isinstance(jadwal, dict) or not jadwal:
        return ("", meta, {})
    # pick first key
    key = sorted(jadwal.keys())[0]
    day = jadwal.get(key) or {}
    label = str(day.get("tanggal") or key)
    return (label, meta, day)


def format_day(label: str, meta: Dict[str, Any], day: Dict[str, Any]) -> str:
    kabko = meta.get("kabko") or "(lokasi)"
    prov = meta.get("prov") or ""
    loc = f"{kabko}{', ' + prov if prov else ''}".strip()

    fields = [
        ("Imsak", day.get("imsak")),
        ("Subuh", day.get("subuh")),
        ("Terbit", day.get("terbit")),
        ("Dhuha", day.get("dhuha")),
        ("Dzuhur", day.get("dzuhur")),
        ("Ashar", day.get("ashar")),
        ("Maghrib", day.get("maghrib")),
        ("Isya", day.get("isya")),
    ]
    lines = [f"{loc}", f"Tanggal: {label}"]
    for k, v in fields:
        if v:
            lines.append(f"- {k}: {v}")
    return "\n".join(lines)


def cmd_cari(args: argparse.Namespace) -> int:
    items = search_locations(args.keyword)
    if not items:
        print("Tidak ditemukan lokasi.")
        return 2
    # print as list
    for it in items[:30]:
        print(f"{it.get('id','')}\t{it.get('lokasi','')}")
    if len(items) > 30:
        print(f"... ({len(items)-30} lagi)")
    return 0


def cmd_hari_ini(args: argparse.Namespace) -> int:
    items = search_locations(args.keyword)
    picked = pick_location(args.keyword, items)
    if not picked:
        print("Tidak ditemukan lokasi.")
        return 2
    j = fetch_jadwal_today(str(picked.get("id")), tz=args.tz)
    if not j.get("status"):
        print(j.get("message") or "Gagal mengambil jadwal.")
        return 3
    label, meta, day = _extract_single_day(j)
    print(format_day(label, meta, day))
    return 0


def cmd_tanggal(args: argparse.Namespace) -> int:
    items = search_locations(args.keyword)
    picked = pick_location(args.keyword, items)
    if not picked:
        print("Tidak ditemukan lokasi.")
        return 2
    j = fetch_jadwal_period(str(picked.get("id")), args.date, tz=args.tz)
    if not j.get("status"):
        print(j.get("message") or "Gagal mengambil jadwal.")
        return 3
    label, meta, day = _extract_single_day(j)
    print(format_day(label, meta, day))
    return 0


def cmd_bulan(args: argparse.Namespace) -> int:
    items = search_locations(args.keyword)
    picked = pick_location(args.keyword, items)
    if not picked:
        print("Tidak ditemukan lokasi.")
        return 2
    j = fetch_jadwal_period(str(picked.get("id")), args.month, tz=args.tz)
    if not j.get("status"):
        print(j.get("message") or "Gagal mengambil jadwal.")
        return 3
    data = j.get("data") or {}
    jadwal = data.get("jadwal") or {}
    if not isinstance(jadwal, dict) or not jadwal:
        print("Jadwal kosong.")
        return 4
    # Print first 7 days as preview
    keys = sorted(jadwal.keys())
    meta = {"kabko": data.get("kabko"), "prov": data.get("prov")}
    print(f"{meta.get('kabko','(lokasi)')}, {meta.get('prov','')}``.\nBulan: {args.month} (menampilkan 7 hari pertama)".replace('`', ''))
    for k in keys[:7]:
        day = jadwal.get(k) or {}
        label = str(day.get("tanggal") or k)
        print(f"\n{label}")
        for name in ["imsak","subuh","dzuhur","ashar","maghrib","isya"]:
            val = day.get(name)
            if val:
                print(f"- {name}: {val}")
    return 0


def cmd_id_hari_ini(args: argparse.Namespace) -> int:
    j = fetch_jadwal_today(args.id, tz=args.tz)
    if not j.get("status"):
        print(j.get("message") or "Gagal mengambil jadwal.")
        return 3
    label, meta, day = _extract_single_day(j)
    print(format_day(label, meta, day))
    return 0


def cmd_id_period(args: argparse.Namespace) -> int:
    j = fetch_jadwal_period(args.id, args.period, tz=args.tz)
    if not j.get("status"):
        print(j.get("message") or "Gagal mengambil jadwal.")
        return 3
    label, meta, day = _extract_single_day(j)
    print(format_day(label, meta, day))
    return 0


def main(argv: List[str]) -> int:
    ap = argparse.ArgumentParser(prog="myquran_sholat")
    ap.add_argument("--tz", default=DEFAULT_TZ, help="Timezone (default Asia/Jakarta)")

    sub = ap.add_subparsers(dest="cmd", required=True)

    p = sub.add_parser("cari", help="Cari kab/kota")
    p.add_argument("keyword")
    p.set_defaults(func=cmd_cari)

    p = sub.add_parser("hari-ini", help="Jadwal sholat hari ini berdasarkan keyword lokasi")
    p.add_argument("keyword")
    p.set_defaults(func=cmd_hari_ini)

    p = sub.add_parser("tanggal", help="Jadwal sholat tanggal tertentu (YYYY-MM-DD) berdasarkan keyword lokasi")
    p.add_argument("keyword")
    p.add_argument("date")
    p.set_defaults(func=cmd_tanggal)

    p = sub.add_parser("bulan", help="Jadwal sholat bulan tertentu (YYYY-MM) berdasarkan keyword lokasi (preview 7 hari)")
    p.add_argument("keyword")
    p.add_argument("month")
    p.set_defaults(func=cmd_bulan)

    p = sub.add_parser("id-hari-ini", help="Jadwal sholat hari ini berdasarkan id lokasi")
    p.add_argument("id")
    p.set_defaults(func=cmd_id_hari_ini)

    p = sub.add_parser("id-period", help="Jadwal sholat period (YYYY-MM / YYYY-MM-DD) berdasarkan id lokasi")
    p.add_argument("id")
    p.add_argument("period")
    p.set_defaults(func=cmd_id_period)

    args = ap.parse_args(argv)
    return int(args.func(args))


if __name__ == "__main__":
    raise SystemExit(main(sys.argv[1:]))