#!/usr/bin/env bun import { mkdirSync, writeFileSync } from 'fs' import { dirname, join } from 'path' const root = process.cwd() const rawRoot = join(root, 'data', 'sources', 'apify', 'raw') const token = Bun.env.APIFY_TOKEN || Bun.env.APIFY_API_TOKEN const generatedAt = new Date().toISOString() if (!token) throw new Error('Missing APIFY_TOKEN or APIFY_API_TOKEN') const maxTotalChargeUsd = Number(Bun.env.APIFY_DEFF_SERP_MAX_USD || 0.5) function writeJson(path: string, data: any) { mkdirSync(dirname(path), { recursive: true }) writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf8') } async function apify(path: string, options: RequestInit = {}) { const sep = path.includes('?') ? '&' : '?' const res = await fetch(`https://api.apify.com${path}${sep}token=${encodeURIComponent(token!)}`, { ...options, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) } }) const text = await res.text() let data: any try { data = text ? JSON.parse(text) : null } catch { data = text } if (!res.ok) throw new Error(`Apify ${res.status} ${res.statusText}: ${typeof data === 'string' ? data : JSON.stringify(data)}`) return data } async function waitForRun(runId: string, timeoutMs = 1000 * 60 * 6) { const started = Date.now() let run = (await apify(`/v2/actor-runs/${runId}`)).data while (!['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'].includes(run.status)) { if (Date.now() - started > timeoutMs) throw new Error(`Timed out waiting for ${runId}`) await new Promise(resolve => setTimeout(resolve, 5000)) run = (await apify(`/v2/actor-runs/${runId}`)).data console.log(`${runId}: ${run.status} usd=${run.usageTotalUsd || 0}`) } return run } const queries = [ 'site:amazon.co.uk Just Vitamins "Vitamin D Effervescent" "1000iu"', 'site:amazon.co.uk "One A Day Vitamin D Effervescent" "Just Vitamins"', 'site:amazon.co.uk "Just Vitamins" "Peach" "Passion Fruit" "Vitamin D"' ] const input = { queries: queries.join('\n'), resultsPerPage: 10, maxPagesPerQuery: 1, countryCode: 'gb', searchLanguage: 'en', languageCode: 'en', mobileResults: false, includeUnfilteredResults: false, saveHtml: false, saveHtmlToKeyValueStore: false, includeIcons: false, aiMode: 'aiModeOff', perplexitySearch: { enablePerplexity: false, returnImages: false, returnRelatedQuestions: false }, chatGptSearch: { enableChatGpt: false }, proxyConfiguration: { useApifyProxy: true, apifyProxyGroups: ['GOOGLE_SERP'] } } console.log(`Starting apify/google-search-scraper for JV-DEFF1000 exact Amazon ASIN proof, queries=${queries.length}, cap=$${maxTotalChargeUsd}`) const start = await apify(`/v2/acts/apify~google-search-scraper/runs?maxTotalChargeUsd=${maxTotalChargeUsd}`, { method: 'POST', body: JSON.stringify(input) }) const run = await waitForRun(start.data.id) const rawItems = run.defaultDatasetId ? await apify(`/v2/datasets/${run.defaultDatasetId}/items?clean=true`) : [] const items = Array.isArray(rawItems) ? rawItems.map((item: any) => ({ ...item, sku: 'JV-DEFF1000', sourceType: 'amazon_exact_asin_serp_proof', label: 'JV-DEFF1000 Amazon exact-ASIN SERP proof', matchContext: 'exact_asin_absence_search', note: 'Bounded Apify Google SERP search to prove whether an exact Just Vitamins Vitamin D Effervescent Amazon listing is discoverable before using competitor-context reviews.' })) : rawItems const organicResults = Array.isArray(items) ? items.flatMap((item: any) => item.organicResults || []) : [] const exactLikeResults = organicResults.filter((row: any) => { const text = `${row.title || ''} ${row.description || ''} ${row.url || ''}`.toLowerCase() return text.includes('just vitamins') && text.includes('vitamin d') && text.includes('effervescent') }) const result = { capturedAt: generatedAt, job: 'deff-amazon-serp-proof', actor: 'apify/google-search-scraper', maxTotalChargeUsd, input, run: { id: run.id, status: run.status, statusMessage: run.statusMessage, defaultDatasetId: run.defaultDatasetId, startedAt: run.startedAt, finishedAt: run.finishedAt, usageTotalUsd: run.usageTotalUsd, usage: run.usage, stats: run.stats }, itemCount: Array.isArray(items) ? items.length : 0, exactLikeResultCount: exactLikeResults.length, exactLikeResults, conclusion: exactLikeResults.length ? 'exact_like_amazon_result_found_requires_manual_confirmation' : 'no_exact_jv_effervescent_amazon_result_found_in_bounded_serp', items } const stamp = generatedAt.replace(/[:.]/g, '-') writeJson(join(rawRoot, `top3-specialist-deff-amazon-serp-proof-${stamp}.json`), result) writeJson(join(rawRoot, 'top3-specialist-deff-amazon-serp-proof-latest.json'), result) console.log(`DEFF Amazon SERP proof ${run.status}: ${result.itemCount} query items, exactLike=${result.exactLikeResultCount}, usd=${run.usageTotalUsd || 0}`)