import type { Place, PlaceList } from "../types.ts"; const XSSI_PREFIX = ")]}'\n"; export async function fetchList(listId: string): Promise { const pb = `!1m1!1s${listId}!2e2!3e2!4i10000!16b1`; const url = `https://www.google.com/maps/preview/entitylist/getlist?authuser=0&hl=en&pb=${pb}`; const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36", }, }); if (!response.ok) { throw new Error( `Google Maps returned HTTP ${response.status}. The list may be private or the URL may be invalid.`, ); } const text = await response.text(); const json = text.startsWith(XSSI_PREFIX) ? text.slice(XSSI_PREFIX.length) : text; let data: unknown; try { data = JSON.parse(json); } catch { throw new Error( "Failed to parse Google Maps response. The list may be private or the URL may be invalid.", ); } if (!Array.isArray(data) || !Array.isArray(data[0])) { throw new Error("List not found or not publicly shared"); } const root = data[0] as unknown[]; return { id: listId, name: getString(root, 4) ?? "Untitled", description: getString(root, 5) ?? "", owner: getNestedString(root, 3, 0) ?? "Unknown", totalCount: getNumber(root, 12) ?? 0, places: parsePlaces(root), createdAt: parseTimestamp(root, 10), updatedAt: parseTimestamp(root, 11), }; } function parsePlaces(root: unknown[]): Place[] { const rawPlaces = root[8]; if (!Array.isArray(rawPlaces)) return []; const places: Place[] = []; for (const raw of rawPlaces) { if (!Array.isArray(raw)) continue; const info = raw[1] as unknown[] | undefined; if (!Array.isArray(info)) continue; const coords = info[5] as unknown[] | undefined; const lat = Array.isArray(coords) && typeof coords[2] === "number" ? coords[2] : null; const lng = Array.isArray(coords) && typeof coords[3] === "number" ? coords[3] : null; if (lat === null || lng === null) continue; const name = typeof raw[2] === "string" ? raw[2] : "Unknown"; places.push({ name, lat, lng, address: typeof info[4] === "string" && info[4] !== "" ? info[4] : null, fullAddress: typeof info[2] === "string" && info[2] !== "" ? info[2] : null, placeRef: typeof info[7] === "string" ? info[7] : null, note: typeof raw[3] === "string" ? raw[3] : "", addedAt: parseTimestamp(raw as unknown[], 9), modifiedAt: parseTimestamp(raw as unknown[], 10), }); } return places; } function getString(arr: unknown[], index: number): string | null { const val = arr[index]; return typeof val === "string" ? val : null; } function getNumber(arr: unknown[], index: number): number | null { const val = arr[index]; return typeof val === "number" ? val : null; } function getNestedString( arr: unknown[], index: number, subIndex: number, ): string | null { const nested = arr[index]; if (!Array.isArray(nested)) return null; const val = nested[subIndex]; return typeof val === "string" ? val : null; } function parseTimestamp(arr: unknown[], index: number): Date | null { const ts = arr[index]; if (!Array.isArray(ts)) return null; const seconds = ts[0]; if (typeof seconds !== "number") return null; return new Date(seconds * 1000); }