import fetchKijijiItems from "@/kijiji"; import fetchFacebookItems from "@/facebook"; import fetchEbayItems from "@/ebay"; const PORT = process.env.PORT || 4005; const server = Bun.serve({ port: PORT, idleTimeout: 0, routes: { // Static routes "/api/status": new Response("OK"), // Dynamic routes "/api/kijiji": async (req: Request) => { const reqUrl = new URL(req.url); const SEARCH_QUERY = req.headers.get("query") || reqUrl.searchParams.get("q") || null; if (!SEARCH_QUERY) return Response.json( { message: "Request didn't have 'query' header or 'q' search parameter!", }, { status: 400 }, ); // Parse optional parameters with enhanced defaults const location = reqUrl.searchParams.get("location"); const category = reqUrl.searchParams.get("category"); const maxPagesParam = reqUrl.searchParams.get("maxPages"); const maxPages = maxPagesParam ? Number.parseInt(maxPagesParam, 10) : 5; // Default: 5 pages const sortBy = reqUrl.searchParams.get("sortBy") as 'relevancy' | 'date' | 'price' | 'distance' | undefined; const sortOrder = reqUrl.searchParams.get("sortOrder") as 'asc' | 'desc' | undefined; // Build search options const locationValue = location ? (/^\d+$/.test(location) ? Number(location) : location) : 1700272; const categoryValue = category ? (/^\d+$/.test(category) ? Number(category) : category) : 0; const searchOptions: import("@/kijiji").SearchOptions = { location: locationValue, category: categoryValue, keywords: SEARCH_QUERY, sortBy: sortBy || 'relevancy', sortOrder: sortOrder || 'desc', maxPages, }; // Build listing fetch options with enhanced defaults const listingOptions: import("@/kijiji").ListingFetchOptions = { includeImages: true, // Always include full image arrays sellerDataDepth: 'detailed', // Default: detailed seller info includeClientSideData: false, // GraphQL reviews disabled by default }; try { const items = await fetchKijijiItems(SEARCH_QUERY, 1, undefined, searchOptions, listingOptions); if (!items || items.length === 0) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, ); return Response.json(items, { status: 200 }); } catch (error) { console.error("Kijiji scraping error:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return Response.json( { message: `Scraping failed: ${errorMessage}`, query: SEARCH_QUERY, options: { searchOptions, listingOptions } }, { status: 500 }, ); } }, "/api/facebook": async (req: Request) => { const reqUrl = new URL(req.url); const SEARCH_QUERY = req.headers.get("query") || reqUrl.searchParams.get("q") || null; if (!SEARCH_QUERY) return Response.json( { message: "Request didn't have 'query' header or 'q' search parameter!", }, { status: 400 }, ); const LOCATION = reqUrl.searchParams.get("location") || "toronto"; const COOKIES_SOURCE = reqUrl.searchParams.get("cookies") || undefined; try { const items = await fetchFacebookItems(SEARCH_QUERY, 5, LOCATION, 25, COOKIES_SOURCE); if (!items || items.length === 0) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, ); return Response.json(items, { status: 200 }); } catch (error) { console.error("Facebook scraping error:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return Response.json( { message: errorMessage }, { status: 400 }, ); } }, "/api/ebay": async (req: Request) => { const reqUrl = new URL(req.url); const SEARCH_QUERY = req.headers.get("query") || reqUrl.searchParams.get("q") || null; if (!SEARCH_QUERY) return Response.json( { message: "Request didn't have 'query' header or 'q' search parameter!", }, { status: 400 }, ); // Parse optional parameters with defaults const minPriceParam = reqUrl.searchParams.get("minPrice"); const minPrice = minPriceParam ? Number.parseInt(minPriceParam, 10) : undefined; const maxPriceParam = reqUrl.searchParams.get("maxPrice"); const maxPrice = maxPriceParam ? Number.parseInt(maxPriceParam, 10) : undefined; const strictMode = reqUrl.searchParams.get("strictMode") === "true"; const exclusionsParam = reqUrl.searchParams.get("exclusions"); const exclusions = exclusionsParam ? exclusionsParam.split(",").map(s => s.trim()) : []; const keywordsParam = reqUrl.searchParams.get("keywords"); const keywords = keywordsParam ? keywordsParam.split(",").map(s => s.trim()) : [SEARCH_QUERY]; try { const items = await fetchEbayItems(SEARCH_QUERY, 5, { minPrice, maxPrice, strictMode, exclusions, keywords, }); if (!items || items.length === 0) return Response.json( { message: "Search didn't return any results!" }, { status: 404 }, ); return Response.json(items, { status: 200 }); } catch (error) { console.error("eBay scraping error:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; return Response.json( { message: errorMessage }, { status: 400 }, ); } }, // Wildcard route for all routes that start with "/api/" and aren't otherwise matched "/api/*": Response.json({ message: "Not found" }, { status: 404 }), // // Serve a file by buffering it in memory // "/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), { // headers: { // "Content-Type": "image/x-icon", // }, // }), }, // (optional) fallback for unmatched routes: // Required if Bun's version < 1.2.3 fetch(req: Request) { return new Response("Not Found", { status: 404 }); }, }); console.log(`Serving on ${server.hostname}:${server.port}`);