← back to Whatsmystyle
scripts/clothing-apis/ebay.js
75 lines
/**
* eBay Browse API adapter — secondhand / vintage.
*
* Endpoint: GET https://api.ebay.com/buy/browse/v1/item_summary/search
* Key: Bearer <OAuth app token>. Free dev account at developer.ebay.com.
*
* Env:
* EBAY_OAUTH_TOKEN — required (production application token, refreshed via
* client-credentials flow; out of scope for this file)
*
* One call yields up to 200 items per page. Free tier = 5000 calls/day, plenty.
*/
const fetch = global.fetch || require('node-fetch');
const TOKEN = process.env.EBAY_OAUTH_TOKEN;
const UA = 'WhatsMyStyleBot/0.1';
const PAGE_SIZE = 50;
const MAX_PAGES = 4;
const PAGE_DELAY_MS = 800;
async function importCategory(db, q) {
if (!TOKEN) return { skipped: 'EBAY_OAUTH_TOKEN not set', source: `ebay:${q.id}` };
const insert = db.prepare(`
INSERT OR IGNORE INTO items
(source, external_id, title, brand, category, price_cents, currency, image_url, product_url, tags, pro_grade)
VALUES
(@source, @external_id, @title, @brand, @category, @price_cents, @currency, @image_url, @product_url, @tags, @pro_grade)
`);
let imported = 0, fetched = 0;
for (let page = 1; page <= MAX_PAGES; page++) {
const offset = (page - 1) * PAGE_SIZE;
const url = `https://api.ebay.com/buy/browse/v1/item_summary/search?category_ids=${q.category_id}&limit=${PAGE_SIZE}&offset=${offset}`;
const r = await fetch(url, { headers: { Authorization: `Bearer ${TOKEN}`, 'User-Agent': UA, Accept: 'application/json' } });
if (!r.ok) return { source: `ebay:${q.id}`, ok: false, status: r.status, imported };
const j = await r.json();
const items = j.itemSummaries || [];
fetched += items.length;
const tx = db.transaction(() => {
for (const it of items) {
const img = it.image?.imageUrl;
const cents = it.price?.value ? Math.round(Number(it.price.value) * 100) : null;
if (!img || !cents) continue;
const category = /shoe|boot|sneaker/i.test(it.title) ? 'shoes'
: /dress|gown/i.test(it.title) ? 'dress'
: /coat|jacket|blazer/i.test(it.title) ? 'outerwear'
: /pant|trouser|jean|skirt/i.test(it.title) ? 'bottom'
: /bag|tote|purse/i.test(it.title) ? 'bag'
: 'top';
const res = insert.run({
source: `ebay:${q.id}`,
external_id: `ebay:${it.itemId}`,
title: (it.title || '').slice(0, 200),
brand: it.brand || (it.seller?.username ? `eBay: ${it.seller.username}` : 'eBay seller'),
category,
price_cents: cents,
currency: it.price?.currency || 'USD',
image_url: img,
product_url: it.itemWebUrl,
tags: JSON.stringify(it.conditionId ? [`condition:${it.condition}`] : []),
pro_grade: 0, // one-off auction item — not restockable
});
if (res.changes > 0) imported++;
}
});
tx();
if (items.length < PAGE_SIZE) break;
await new Promise(r => setTimeout(r, PAGE_DELAY_MS));
}
return { source: `ebay:${q.id}`, ok: true, fetched, imported };
}
module.exports = { importCategory };