import crypto from 'crypto'; import https from 'https'; import fs from 'fs'; const envContent = fs.readFileSync('.env.local', 'utf8'); const env = {}; envContent.split('\n').forEach(line => { const match = line.match(/^([^=]+)=(.*)$/); if (match) { let val = match[2].trim(); if (val.startsWith('"') && val.endsWith('"')) { val = val.slice(1, -1); } env[match[1].trim()] = val; } }); const CLIENT_ID = env.APPLE_SEARCH_ADS_CLIENT_ID; const TEAM_ID = env.APPLE_SEARCH_ADS_TEAM_ID; const KEY_ID = env.APPLE_SEARCH_ADS_KEY_ID; const PRIVATE_KEY = env.APPLE_SEARCH_ADS_PRIVATE_KEY; function generateClientSecret() { const header = { alg: "ES256", kid: KEY_ID, typ: "JWT" }; const now = Math.floor(Date.now() / 1000); const payload = { sub: CLIENT_ID, aud: "https://appleid.apple.com", iat: now, exp: now + 86400 * 180, iss: TEAM_ID }; const base64Header = Buffer.from(JSON.stringify(header)).toString("base64url"); const base64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url"); const signingInput = `${base64Header}.${base64Payload}`; const signer = crypto.createSign("SHA256"); signer.update(signingInput); const signature = signer.sign({ key: PRIVATE_KEY.replace(/\\n/g, '\n'), dsaEncoding: 'ieee-p1363' }, "base64url"); return `${signingInput}.${signature}`; } async function getAccessToken(clientSecret) { return new Promise((resolve, reject) => { const postData = new URLSearchParams({ grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: clientSecret, scope: 'searchadsorg' }).toString(); const options = { hostname: 'appleid.apple.com', port: 443, path: '/auth/oauth2/token', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData), 'User-Agent': 'Mozilla/5.0' } }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve(JSON.parse(data))); }); req.on('error', reject); req.write(postData); req.end(); }); } function requestAppleApi(path, token, orgId, method = 'GET', body = null) { return new Promise((resolve, reject) => { const headers = { 'Authorization': `Bearer ${token}`, 'User-Agent': 'Mozilla/5.0' }; if (orgId) { headers['X-AP-Context'] = `orgId=${orgId}`; } if (body) { headers['Content-Type'] = 'application/json'; } const options = { hostname: 'api.searchads.apple.com', port: 443, path: path, method: method, headers: headers }; const req = https.request(options, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(data); } }); }); req.on('error', reject); if (body) req.write(JSON.stringify(body)); req.end(); }); } async function main() { const secret = generateClientSecret(); const tokenRes = await getAccessToken(secret); const token = tokenRes.access_token; console.log("Abfrage /api/v5/acls für orgId..."); const acls = await requestAppleApi('/api/v5/acls', token); console.log("ACLs:", JSON.stringify(acls, null, 2)); const org = acls?.data?.[0]; const orgId = org?.orgId; console.log("Verwende OrgId:", orgId); if (orgId) { // In Search Ads API v5: POST /api/v5/keywords/search console.log("\nAbfrage Keywords via POST /api/v5/keywords/search ..."); const keywords = ['receipt scanner', 'receipt to excel', 'expense tracker', 'beleg scanner']; const testKw = await requestAppleApi('/api/v5/keywords/search', token, orgId, 'POST', { searchTerms: keywords, countryOrRegion: 'US' }); console.log("Keyword Search Result (US):", JSON.stringify(testKw, null, 2)); // Check DE const testKwDE = await requestAppleApi('/api/v5/keywords/search', token, orgId, 'POST', { searchTerms: ['beleg scanner', 'belege digitalisieren', 'kassenbon scanner', 'haushaltsbuch'], countryOrRegion: 'DE' }); console.log("Keyword Search Result (DE):", JSON.stringify(testKwDE, null, 2)); } } main();