import type { EnrichedPlace } from "../types.ts"; const XSSI_PREFIX = ")]}'\n"; const USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36"; export interface SearchResult { name: string; lat: number; lng: number; address: string | null; fullAddress: string | null; placeRef: string | null; rating: number | null; categories: string[]; phone: string | null; website: string | null; hours: string | null; placeId: string | null; mapsUrl: string; } export async function searchPlaces(query: string): Promise { const url = `https://www.google.com/search?tbm=map&hl=en&q=${encodeURIComponent(query)}`; const response = await fetch(url, { headers: { "User-Agent": USER_AGENT }, }); if (!response.ok) { throw new Error(`Google Maps search returned HTTP ${response.status}`); } 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 search response"); } return extractSearchResults(data); } export function extractPlaceDetails(data: unknown): PlaceDetails | null { if (!Array.isArray(data)) return null; const first = data[0]; if (!Array.isArray(first)) return null; const second = first[1]; if (!Array.isArray(second)) return null; const third = second[0]; if (!Array.isArray(third)) return null; const place = third[14]; if (!Array.isArray(place)) return null; return parsePlaceRecord(place); } export interface PlaceDetails { name: string; rating: number | null; categories: string[]; phone: string | null; website: string | null; hours: string | null; placeId: string | null; placeRef: string | null; lat: number | null; lng: number | null; address: string | null; fullAddress: string | null; } function parsePlaceRecord(place: unknown[]): PlaceDetails | null { const ratingArr = place[4]; const rating = Array.isArray(ratingArr) && typeof ratingArr[7] === "number" ? ratingArr[7] : null; const websiteArr = place[7]; const website = Array.isArray(websiteArr) && typeof websiteArr[1] === "string" ? websiteArr[1] : null; const coordsArr = place[9]; const lat = Array.isArray(coordsArr) && typeof coordsArr[2] === "number" ? coordsArr[2] : null; const lng = Array.isArray(coordsArr) && typeof coordsArr[3] === "number" ? coordsArr[3] : null; const rawCategories = place[13]; const categories: string[] = []; if (Array.isArray(rawCategories)) { for (const cat of rawCategories) { if (typeof cat === "string") { categories.push(cat); } } } const phoneArr = place[178]; let phone: string | null = null; if (Array.isArray(phoneArr)) { const firstPhone = phoneArr[0]; if (Array.isArray(firstPhone) && typeof firstPhone[0] === "string") { phone = firstPhone[0]; } } const hoursArr = place[203]; let hours: string | null = null; if (Array.isArray(hoursArr)) { const statusInfo = hoursArr[1]; if (Array.isArray(statusInfo) && typeof statusInfo[4] === "string") { hours = statusInfo[4]; } } const name = typeof place[11] === "string" ? place[11] : "Unknown"; const placeId = typeof place[78] === "string" ? place[78] : null; const placeRef = typeof place[89] === "string" ? place[89] : null; const shortAddr = typeof place[39] === "string" ? place[39] : null; const fullAddr = typeof place[18] === "string" ? place[18] : null; return { name, rating, categories, phone, website, hours, placeId, placeRef, lat, lng, address: shortAddr, fullAddress: fullAddr, }; } function extractSearchResults(data: unknown): SearchResult[] { if (!Array.isArray(data)) return []; const first = data[0]; if (!Array.isArray(first)) return []; const second = first[1]; if (!Array.isArray(second)) return []; const results: SearchResult[] = []; for (const entry of second) { if (!Array.isArray(entry)) continue; const record = entry[14]; if (!Array.isArray(record)) continue; const details = parsePlaceRecord(record); if (!details || details.lat === null || details.lng === null) continue; results.push({ name: details.name, lat: details.lat, lng: details.lng, address: details.address, fullAddress: details.fullAddress, placeRef: details.placeRef, rating: details.rating, categories: details.categories, phone: details.phone, website: details.website, hours: details.hours, placeId: details.placeId, mapsUrl: `https://www.google.com/maps/search/?api=1&query=${details.lat},${details.lng}`, }); } return results; } export function toEnrichedPlace(result: SearchResult): EnrichedPlace { return { ...result, note: "", addedAt: null, modifiedAt: null, }; }