/** * Wilayah Indonesia + Jadwal Sholat + Cuaca Scraper & API Client * Author / Base: Nimzz * Sources: * - Wilayah : https://wilayah.cahyadsn.com * - Sholat : https://api.aladhan.com * - Cuaca : https://api.open-meteo.com */ import fetch from 'node-fetch' const BASE_WILAYAH = 'https://wilayah.cahyadsn.com/api/' const BASE_SHOLAT = 'https://api.aladhan.com/v1/timings' const BASE_CUACA = 'https://api.open-meteo.com/v1/forecast' const USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36' async function fetchJson(url) { const res = await fetch(url, { headers: { 'User-Agent': USER_AGENT, 'Accept': 'application/json' } }) if (!res.ok) throw new Error(`HTTP ${res.status} untuk ${url}`) return res.json() } const WEATHER_CODES = { 0: 'Cerah (Clear sky)', 1: 'Utamanya Cerah (Mainly clear)', 2: 'Sebagian Berawan (Partly cloudy)', 3: 'Mendung (Overcast)', 45: 'Berkabut (Fog)', 48: 'Kabut Tebal (Depositing rime fog)', 51: 'Gerimis Ringan (Light drizzle)', 53: 'Gerimis Sedang (Moderate drizzle)', 55: 'Gerimis Lebat (Dense drizzle)', 61: 'Hujan Ringan (Slight rain)', 63: 'Hujan Sedang (Moderate rain)', 65: 'Hujan Deras (Heavy rain)', 71: 'Salju Ringan', 73: 'Salju Sedang', 75: 'Salju Lebat', 80: 'Hujan Singkat Ringan', 81: 'Hujan Singkat Sedang', 82: 'Hujan Badai Lokal', 95: 'Badai Petir (Thunderstorm)', 96: 'Badai Petir Berbutir Es Ringan', 99: 'Badai Petir Berbutir Es Lebat' } /** * Mengambil daftar Provinsi di Indonesia */ export async function getProvinsi() { const data = await fetchJson(`${BASE_WILAYAH}?kode=`) return data.data || [] } /** * Mengambil daftar Kabupaten/Kota berdasarkan kode provinsi * @param {string} kodeProvinsi Kode provinsi (contoh: '31', '32', '33') */ export async function getKabupaten(kodeProvinsi) { if (!kodeProvinsi) throw new Error('Kode provinsi harus diisi.') const data = await fetchJson(`${BASE_WILAYAH}?kode=${kodeProvinsi}&level=2`) return data.data || [] } /** * Mengambil daftar Kecamatan berdasarkan kode kabupaten * @param {string} kodeKabupaten Kode kabupaten (contoh: '33.72') */ export async function getKecamatan(kodeKabupaten) { if (!kodeKabupaten) throw new Error('Kode kabupaten harus diisi.') const data = await fetchJson(`${BASE_WILAYAH}?kode=${kodeKabupaten}&level=3`) return data.data || [] } /** * Mengambil daftar Kelurahan/Desa berdasarkan kode kecamatan * @param {string} kodeKecamatan Kode kecamatan (contoh: '33.72.03') */ export async function getKelurahan(kodeKecamatan) { if (!kodeKecamatan) throw new Error('Kode kecamatan harus diisi.') const data = await fetchJson(`${BASE_WILAYAH}?kode=${kodeKecamatan}&level=4`) return data.data || [] } /** * Mengambil detail lengkap koordinat lat, lng, kodepos, dan polygon suatu wilayah * @param {string} kodeWilayah Kode wilayah lengkap (contoh: '33.72.03.1009' atau '31') */ export async function getDetailWilayah(kodeWilayah) { if (!kodeWilayah) throw new Error('Kode wilayah harus diisi.') const data = await fetchJson(`${BASE_WILAYAH}?kode=${kodeWilayah}&detail=1`) return data.data || data } /** * Mengambil jadwal sholat realtime berdasarkan koordinat Latitude & Longitude * @param {number|string} lat Latitude * @param {number|string} lon Longitude */ export async function getJadwalSholat(lat, lon) { if (!lat || !lon) throw new Error('Latitude dan Longitude harus diisi.') const date = new Date() const dd = String(date.getDate()).padStart(2, '0') const mm = String(date.getMonth() + 1).padStart(2, '0') const yyyy = date.getFullYear() const data = await fetchJson(`${BASE_SHOLAT}/${dd}-${mm}-${yyyy}?latitude=${lat}&longitude=${lon}&method=20`) return data.data || data } /** * Mengambil cuaca realtime (suhu, kelembaban, angin, deskripsi cuaca) dari koordinat * @param {number|string} lat Latitude * @param {number|string} lon Longitude */ export async function getCuacaRealtime(lat, lon) { if (!lat || !lon) throw new Error('Latitude dan Longitude harus diisi.') const data = await fetchJson(`${BASE_CUACA}?latitude=${lat}&longitude=${lon}¤t=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code`) const current = data.current || {} const weatherDesc = WEATHER_CODES[current.weather_code] || 'Tidak diketahui' return { ...current, weather_desc: weatherDesc } } /** * Mengambil informasi lengkap (Wilayah + Jadwal Sholat + Cuaca) sekaligus dari kode wilayah * @param {string} kodeWilayah Kode wilayah (contoh: '31.71', '33.72.03.1009') */ export async function getWilayahFullInfo(kodeWilayah) { const detail = await getDetailWilayah(kodeWilayah) const item = Array.isArray(detail) ? detail[0] : detail if (!item) throw new Error(`Wilayah dengan kode ${kodeWilayah} tidak ditemukan.`) let sholat = null let cuaca = null if (item.lat && item.lng) { try { [sholat, cuaca] = await Promise.all([ getJadwalSholat(item.lat, item.lng), getCuacaRealtime(item.lat, item.lng) ]) } catch (err) { console.warn('[WilayahFullInfo Warning]:', err.message) } } return { wilayah: item, sholat, cuaca } } export default { getProvinsi, getKabupaten, getKecamatan, getKelurahan, getDetailWilayah, getJadwalSholat, getCuacaRealtime, getWilayahFullInfo }