-
Notifications
You must be signed in to change notification settings - Fork 9.5k
feat(route): Add routes for ebay #21323
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
cfc0cab
d3ff772
f5ad403
449def2
ec93a56
878edce
edf35d0
826359a
a99c86c
1233ae7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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.', | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| import { Route } from '@/types'; | ||
|
||
| import { load } from 'cheerio'; | ||
|
||
| import ofetch from '@/utils/ofetch'; | ||
| import logger from '@/utils/logger'; | ||
|
|
||
| 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 `/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}`); | ||
TonyRL marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const response = await ofetch(url); | ||
| logger.info(`eBay response status: ${response instanceof Response ? response.status : 'unknown'}`); | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| const $ = load(response); | ||
|
|
||
| const items = $('.s-item, .s-card, .s-item__wrapper.clearfix') | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .toArray() | ||
| .map((item) => { | ||
| const $item = $(item); | ||
| const titleElement = $item.find('.s-item__title, .s-card__title, .s-item__title--has-tags'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you provide an example that the site does have |
||
| const title = titleElement.text().replace(/^New Listing/i, '').trim(); | ||
| const link = $item.find('.s-item__link, .s-card__link').attr('href'); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you provide an example that the site does have |
||
| const price = $item.find('.s-item__price, .s-card__price').text().trim(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you provide an example that the site does have |
||
| 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'); | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (!title || !link || title.toLowerCase().includes('shop on ebay') || price === '') { | ||
| return null; | ||
| } | ||
|
|
||
| return { | ||
| title: `${title} - ${price}`, | ||
| link, | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| description: `<img src="${image}"><br>Price: ${price}`, | ||
| category: 'eBay Search', | ||
| }; | ||
| }) | ||
| .filter(Boolean); | ||
|
|
||
| logger.info(`Found ${items.length} items on eBay`); | ||
|
|
||
| return { | ||
| title: `eBay Search: ${keywords}`, | ||
| link: url, | ||
| item: items, | ||
| }; | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { Route } from '@/types'; | ||
|
||
| import { load } from 'cheerio'; | ||
|
||
| import ofetch from '@/utils/ofetch'; | ||
|
|
||
| export const route: Route = { | ||
| path: ['/usr/:username', '/user/:username'], | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| categories: ['shopping'], | ||
| example: '/ebay/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: '/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, | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| description: `<img src="${image}"><br>Price: ${price}`, | ||
| author: username, | ||
| }; | ||
| }) | ||
| .filter(Boolean); | ||
|
|
||
| return { | ||
| title: `eBay User: ${username}`, | ||
| link: url, | ||
| item: items, | ||
| }; | ||
| }, | ||
| }; | ||
phoeagon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { CheerioAPI, Element } from 'cheerio'; | ||
|
||
|
|
||
| /** | ||
| * 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}`, | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.