diff --git a/lib/routes/ebay/namespace.ts b/lib/routes/ebay/namespace.ts new file mode 100644 index 000000000000..e00a101c98c0 --- /dev/null +++ b/lib/routes/ebay/namespace.ts @@ -0,0 +1,8 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'eBay', + url: 'ebay.com', + categories: ['shopping'], + description: 'eBay search results and user listings.', +}; diff --git a/lib/routes/ebay/search.ts b/lib/routes/ebay/search.ts new file mode 100644 index 000000000000..254e29b23279 --- /dev/null +++ b/lib/routes/ebay/search.ts @@ -0,0 +1,72 @@ +import { load } from 'cheerio'; + +import type { Route } from '@/types'; +import logger from '@/utils/logger'; +import ofetch from '@/utils/ofetch'; + +export const route: Route = { + path: '/search/:keywords', + categories: ['shopping'], + example: '/ebay/search/sodimm+ddr4+16gb', + parameters: { keywords: 'Keywords for search' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['ebay.com/sch/i.html'], + target: (params, url) => { + const searchKeywords = new URL(url).searchParams.get('_nkw'); + return `/ebay/search/${searchKeywords}`; + }, + }, + ], + name: 'Search Results', + maintainers: ['phoeagon'], + handler: async (ctx) => { + const { keywords } = ctx.req.param(); + const url = `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(keywords)}&_sop=10&_ipg=240`; + + const response = await ofetch(url); + const $ = load(response); + + const items = $('.srp-results .s-item, .srp-results .s-card, .s-card') + .toArray() + .map((item) => { + const $item = $(item); + const titleElement = $item.find('.s-item__title, .s-card__title, .s-item__title--has-tags'); + const title = titleElement.text().replace(/^new listing/i, '').trim(); + const link = $item.find('.s-item__link, .s-card__link').attr('href'); + const price = $item.find('.s-item__price, .s-card__price').text().trim(); + const imageElement = $item.find('.s-item__image-img img, img.s-item__image-img, .s-item__image-wrapper img, .s-card__image-img img, .s-item__image img, .s-card__link img'); + const image = imageElement.attr('data-src') || imageElement.attr('src'); + + if (!title || !link || title.toLowerCase().includes('shop on ebay') || price === '') { + return null; + } + + const cleanedLink = new URL(link); + cleanedLink.search = ''; + + return { + title: `${title} - ${price}`, + link: cleanedLink.toString(), + description: `
Price: ${price}`, + }; + }) + .filter(Boolean); + + logger.info(`Found ${items.length} items on eBay`); + + return { + title: `eBay Search: ${keywords}`, + link: url, + item: items, + }; + }, +}; diff --git a/lib/routes/ebay/user.ts b/lib/routes/ebay/user.ts new file mode 100644 index 000000000000..5f511fe4d4ed --- /dev/null +++ b/lib/routes/ebay/user.ts @@ -0,0 +1,65 @@ +import { load } from 'cheerio'; + +import type { Route } from '@/types'; +import ofetch from '@/utils/ofetch'; + +export const route: Route = { + path: ['/user/:username'], + categories: ['shopping'], + example: '/ebay/user/m.trotters', + parameters: { username: 'Username of the seller' }, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['ebay.com/usr/'], + target: '/user/:username', + }, + ], + name: 'User Listings', + maintainers: ['phoeagon'], + handler: async (ctx) => { + const { username } = ctx.req.param(); + const url = `https://www.ebay.com/usr/${username}`; + + const response = await ofetch(url); + const $ = load(response); + + const items = $('article.str-item-card.StoreFrontItemCard') + .toArray() + .map((item) => { + const $item = $(item); + const title = $item.find('.str-card-title .str-text-span').first().text().trim(); + const link = $item.find('.str-item-card__link').attr('href'); + const price = $item.find('.str-item-card__property-displayPrice').text().trim(); + const image = $item.find('.str-image img').attr('src'); + + if (!title || !link) { + return null; + } + + const cleanedLink = new URL(link); + cleanedLink.search = ''; + + return { + title: `${title} - ${price}`, + link: cleanedLink.toString(), + description: `
Price: ${price}`, + author: username, + }; + }) + .filter(Boolean); + + return { + title: `eBay User: ${username}`, + link: url, + item: items, + }; + }, +};