feat: refactor token management and enhance logging in fetchKeepaDataBatch function
This commit is contained in:
48
src/keepa.ts
48
src/keepa.ts
@@ -22,15 +22,20 @@ async function waitForToken(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait until we regenerate at least 1 token
|
// Wait until we regenerate at least 1 token
|
||||||
const waitMs = Math.ceil((1 / refillRate) * 60_000) - (Date.now() - lastRequestTime);
|
const waitMs =
|
||||||
|
Math.ceil((1 / refillRate) * 60_000) - (Date.now() - lastRequestTime);
|
||||||
if (waitMs > 0) {
|
if (waitMs > 0) {
|
||||||
console.log(`Keepa tokens exhausted. Waiting ${Math.ceil(waitMs / 1000)}s for token regeneration...`);
|
console.log(
|
||||||
|
`Keepa tokens exhausted. Waiting ${Math.ceil(waitMs / 1000)}s for token regeneration...`,
|
||||||
|
);
|
||||||
await new Promise((r) => setTimeout(r, waitMs));
|
await new Promise((r) => setTimeout(r, waitMs));
|
||||||
}
|
}
|
||||||
tokensLeft = 1;
|
tokensLeft = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchKeepaDataBatch(asins: string[]): Promise<Map<string, KeepaData>> {
|
export async function fetchKeepaDataBatch(
|
||||||
|
asins: string[],
|
||||||
|
): Promise<Map<string, KeepaData>> {
|
||||||
const results = new Map<string, KeepaData>();
|
const results = new Map<string, KeepaData>();
|
||||||
|
|
||||||
// Split into chunks of MAX_ASINS_PER_REQUEST
|
// Split into chunks of MAX_ASINS_PER_REQUEST
|
||||||
@@ -41,7 +46,9 @@ export async function fetchKeepaDataBatch(asins: string[]): Promise<Map<string,
|
|||||||
const asinParam = chunk.join(",");
|
const asinParam = chunk.join(",");
|
||||||
const url = `${KEEPA_BASE}/product?key=${config.keepaApiKey}&domain=1&asin=${asinParam}&stats=90`;
|
const url = `${KEEPA_BASE}/product?key=${config.keepaApiKey}&domain=1&asin=${asinParam}&stats=90`;
|
||||||
|
|
||||||
console.log(`Keepa: fetching ${chunk.length} ASINs (tokens left: ${tokensLeft})...`);
|
console.log(
|
||||||
|
`Keepa: fetching ${chunk.length} ASINs (tokens left: ${tokensLeft})...`,
|
||||||
|
);
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
lastRequestTime = Date.now();
|
lastRequestTime = Date.now();
|
||||||
@@ -61,7 +68,9 @@ export async function fetchKeepaDataBatch(asins: string[]): Promise<Map<string,
|
|||||||
if (data.tokensLeft != null) tokensLeft = data.tokensLeft;
|
if (data.tokensLeft != null) tokensLeft = data.tokensLeft;
|
||||||
if (data.refillRate != null) refillRate = data.refillRate;
|
if (data.refillRate != null) refillRate = data.refillRate;
|
||||||
|
|
||||||
console.log(`Keepa: ${data.products?.length ?? 0} products returned, ${tokensLeft} tokens remaining (refill: ${refillRate}/min)`);
|
console.log(
|
||||||
|
`Keepa: ${data.products?.length ?? 0} products returned, ${tokensLeft} tokens remaining (refill: ${refillRate}/min)`,
|
||||||
|
);
|
||||||
|
|
||||||
if (data.products) {
|
if (data.products) {
|
||||||
for (const product of data.products) {
|
for (const product of data.products) {
|
||||||
@@ -78,6 +87,16 @@ export async function fetchKeepaDataBatch(asins: string[]): Promise<Map<string,
|
|||||||
function parseKeepaProduct(product: Record<string, any>): KeepaData {
|
function parseKeepaProduct(product: Record<string, any>): KeepaData {
|
||||||
const stats = product.stats;
|
const stats = product.stats;
|
||||||
const csv = product.csv;
|
const csv = product.csv;
|
||||||
|
const salesRankDrops30 = pickKeepaNumber(
|
||||||
|
product.salesRankDrops30,
|
||||||
|
stats?.salesRankDrops30,
|
||||||
|
);
|
||||||
|
const salesRankDrops90 =
|
||||||
|
pickKeepaNumber(product.salesRankDrops90, stats?.salesRankDrops90) ??
|
||||||
|
(salesRankDrops30 != null ? salesRankDrops30 * 3 : null);
|
||||||
|
const monthlySold =
|
||||||
|
pickKeepaNumber(product.monthlySold, stats?.monthlySold) ??
|
||||||
|
salesRankDrops30;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentPrice: extractCurrentPrice(csv),
|
currentPrice: extractCurrentPrice(csv),
|
||||||
@@ -86,16 +105,27 @@ function parseKeepaProduct(product: Record<string, any>): KeepaData {
|
|||||||
maxPrice90: stats?.max?.[0] != null ? stats.max[0] / 100 : null,
|
maxPrice90: stats?.max?.[0] != null ? stats.max[0] / 100 : null,
|
||||||
salesRank: stats?.current?.[3] ?? null,
|
salesRank: stats?.current?.[3] ?? null,
|
||||||
salesRankAvg90: stats?.avg?.[3] ?? null,
|
salesRankAvg90: stats?.avg?.[3] ?? null,
|
||||||
salesRankDrops30: product.salesRankDrops30 ?? null,
|
salesRankDrops30,
|
||||||
salesRankDrops90: product.salesRankDrops90 ?? null,
|
salesRankDrops90,
|
||||||
sellerCount: stats?.current?.[11] ?? null,
|
sellerCount: stats?.current?.[11] ?? null,
|
||||||
buyBoxSeller: product.buyBoxSellerId ?? null,
|
buyBoxSeller: product.buyBoxSellerId ?? null,
|
||||||
buyBoxPrice: stats?.current?.[18] != null ? stats.current[18] / 100 : null,
|
buyBoxPrice: stats?.current?.[18] != null ? stats.current[18] / 100 : null,
|
||||||
monthlySold: product.monthlySold ?? null,
|
monthlySold,
|
||||||
categoryTree: product.categoryTree?.map((c: { name: string }) => c.name) ?? [],
|
categoryTree:
|
||||||
|
product.categoryTree?.map((c: { name: string }) => c.name) ?? [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pickKeepaNumber(...values: unknown[]): number | null {
|
||||||
|
for (const value of values) {
|
||||||
|
if (typeof value !== "number" || !Number.isFinite(value)) continue;
|
||||||
|
// Keepa often uses -1 as "not available".
|
||||||
|
if (value < 0) continue;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function extractCurrentPrice(csv: number[][] | undefined): number | null {
|
function extractCurrentPrice(csv: number[][] | undefined): number | null {
|
||||||
if (!csv) return null;
|
if (!csv) return null;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user