Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added lib/routes/ebay/.DS_Store
Binary file not shown.
8 changes: 8 additions & 0 deletions lib/routes/ebay/namespace.ts
Original file line number Diff line number Diff line change
@@ -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.',
};
74 changes: 74 additions & 0 deletions lib/routes/ebay/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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: '/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`;

logger.info(`Fetching eBay search results: ${url}`);
const response = await ofetch(url);
logger.info(`eBay response status: ${response instanceof Response ? response.status : 'unknown'}`);
const $ = load(response);

const items = $('.s-item, .s-card, .s-item__wrapper.clearfix')
.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, '');
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 image =
$item.find('.s-item__image-img img, img.s-item__image-img').attr('src') ||
$item.find('.s-item__image-wrapper img').attr('src') ||
$item.find('.s-card__image-img img').attr('src') ||
$item.find('.s-item__image img').attr('src');

if (!title || !link || title.toLowerCase().includes('shop on ebay') || price === '') {
return null;
}

return {
title: `${title} - ${price}`,
link,
description: `<img src="${image}"><br>Price: ${price}`,
};
})
.filter(Boolean);

logger.info(`Found ${items.length} items on eBay`);

return {
title: `eBay Search: ${keywords}`,
link: url,
item: items,
};
},
};
62 changes: 62 additions & 0 deletions lib/routes/ebay/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { load } from 'cheerio';

import type { Route } from '@/types';
import ofetch from '@/utils/ofetch';

export const route: Route = {
path: ['/usr/:username', '/user/:username'],
categories: ['shopping'],
example: '/usr/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/:username'],
target: '/ebay/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;
}

return {
title: `${title} - ${price}`,
link,
description: `<img src="${image}"><br>Price: ${price}`,
author: username,
};
})
.filter(Boolean);

return {
title: `eBay User: ${username}`,
link: url,
item: items,
};
},
};
36 changes: 36 additions & 0 deletions lib/routes/ebay/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

/**
* Transforms an eBay image URL to prefer WebP format if it's a JPG/JPEG.
* @param url The original image URL.
* @returns The transformed URL.
*/
export const transformImage = (url?: string): string | undefined => {
if (!url) {
return undefined;
}
// eBay images often look like https://i.ebayimg.com/images/g/.../s-l500.jpg
// Replacing .jpg with .webp usually works if s-lXXX is used.
return url.replace(/\.jpe?g$/i, '.webp');
};

/**
* Common item structure for eBay routes.
*/
export interface eBayItem {
title: string;
link: string;
description: string;
category?: string;
author?: string;
}

/**
* Helper to extract common data from an eBay item element.
* Note: Since selectors vary, this might be less useful than specific logic in each route,
* but let's provide a way to standardize the output.
*/
export const createItem = (title: string, price: string, link: string, image?: string): eBayItem => ({
title: `${title} - ${price}`,
link,
description: `<img src="${transformImage(image)}"><br>Price: ${price}`,
});
Loading