100 lines
3.0 KiB
TypeScript
100 lines
3.0 KiB
TypeScript
import { fetchKijijiItems } from "@marketplace-scrapers/core";
|
|
import { logger } from "../logger";
|
|
import {
|
|
emptySearchResponse,
|
|
getRequiredSearchQuery,
|
|
parseDollarPriceParam,
|
|
parseNonNegativeIntegerParam,
|
|
} from "./helpers";
|
|
|
|
/**
|
|
* GET /api/kijiji?q={query}
|
|
* Search Kijiji marketplace for listings
|
|
*/
|
|
export async function kijijiRoute(req: Request): Promise<Response> {
|
|
const reqUrl = new URL(req.url);
|
|
|
|
const SEARCH_QUERY = getRequiredSearchQuery(req);
|
|
if (SEARCH_QUERY instanceof Response) {
|
|
return SEARCH_QUERY;
|
|
}
|
|
|
|
const maxPages = parseNonNegativeIntegerParam(
|
|
reqUrl.searchParams,
|
|
"maxPages",
|
|
5,
|
|
);
|
|
if (maxPages instanceof Response) {
|
|
return maxPages;
|
|
}
|
|
const priceMin = parseDollarPriceParam(reqUrl.searchParams, "priceMin");
|
|
if (priceMin instanceof Response) {
|
|
return priceMin;
|
|
}
|
|
const priceMax = parseDollarPriceParam(reqUrl.searchParams, "priceMax");
|
|
if (priceMax instanceof Response) {
|
|
return priceMax;
|
|
}
|
|
const hideUnstableResults =
|
|
reqUrl.searchParams.get("unstableFilter") === "true";
|
|
|
|
const searchOptions = {
|
|
location: reqUrl.searchParams.get("location") || undefined,
|
|
category: reqUrl.searchParams.get("category") || undefined,
|
|
keywords: reqUrl.searchParams.get("keywords") || undefined,
|
|
sortBy:
|
|
(reqUrl.searchParams.get("sortBy") as
|
|
| "relevancy"
|
|
| "date"
|
|
| "price"
|
|
| "distance"
|
|
| undefined) || undefined,
|
|
sortOrder:
|
|
(reqUrl.searchParams.get("sortOrder") as "desc" | "asc" | undefined) ||
|
|
undefined,
|
|
maxPages,
|
|
priceMin,
|
|
priceMax,
|
|
};
|
|
|
|
try {
|
|
if (hideUnstableResults) {
|
|
const items = await fetchKijijiItems(
|
|
SEARCH_QUERY,
|
|
4, // 4 requests per second for faster scraping
|
|
"https://www.kijiji.ca",
|
|
searchOptions,
|
|
{},
|
|
{ hideUnstableResults: true },
|
|
);
|
|
if (items.results.length === 0 && items.unstableResults.length === 0) {
|
|
return emptySearchResponse(
|
|
`Kijiji matches ALL words in the query against listing titles. ` +
|
|
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
|
|
);
|
|
}
|
|
return Response.json(items, { status: 200 });
|
|
}
|
|
|
|
const items = await fetchKijijiItems(
|
|
SEARCH_QUERY,
|
|
4, // 4 requests per second for faster scraping
|
|
"https://www.kijiji.ca",
|
|
searchOptions,
|
|
{},
|
|
);
|
|
if (!items || items.length === 0) {
|
|
return emptySearchResponse(
|
|
`Kijiji matches ALL words in the query against listing titles. ` +
|
|
`Try a shorter or more common query (e.g. "macbook air m1" instead of "macbook air m1 apple silicon").`,
|
|
);
|
|
}
|
|
return Response.json(items, { status: 200 });
|
|
} catch (error) {
|
|
logger.error("Kijiji scraping error:", error);
|
|
const errorMessage =
|
|
error instanceof Error ? error.message : "Unknown error occurred";
|
|
return Response.json({ message: errorMessage }, { status: 400 });
|
|
}
|
|
}
|