/** * auth4.js - Amazon Japan OpenID フローを正しく再現するカスタム認証スクリプト * alexa-cookie2 の古いエンドポイント問題を回避して直接フォームを処理する */ const https = require('https'); const fs = require('fs'); const path = require('path'); const EMAIL = process.env.AMAZON_EMAIL; const PASSWORD = process.env.AMAZON_PASSWORD; if (!EMAIL || !PASSWORD) { console.error('[ERROR] 環境変数 AMAZON_EMAIL と AMAZON_PASSWORD を設定してください'); process.exit(1); } const ALEXA_LOGIN_URL = 'https://www.amazon.co.jp/ap/signin?' + new URLSearchParams({ 'openid.assoc_handle': 'amzn_dp_project_dee_jp', 'openid.mode': 'checkid_setup', 'openid.ns': 'http://specs.openid.net/auth/2.0', 'openid.claimed_id': 'http://specs.openid.net/auth/2.0/identifier_select', 'openid.identity': 'http://specs.openid.net/auth/2.0/identifier_select', 'openid.return_to': 'https://alexa.amazon.co.jp/api/apps/v1/token', 'pageId': 'amzn_dp_project_dee_jp', }).toString(); const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'; let cookieJar = {}; function setCookies(setCookieHeaders) { if (!setCookieHeaders) return; const headers = Array.isArray(setCookieHeaders) ? setCookieHeaders : [setCookieHeaders]; for (const h of headers) { const [kv] = h.split(';'); const [k, v] = kv.trim().split('='); if (k && v !== undefined) cookieJar[k.trim()] = v.trim(); } } function getCookieHeader() { return Object.entries(cookieJar) .map(([k, v]) => `${k}=${v}`) .join('; '); } function request(url, options = {}) { return new Promise((resolve, reject) => { const parsed = new URL(url); const reqOpts = { hostname: parsed.hostname, path: parsed.pathname + parsed.search, method: options.method || 'GET', headers: { 'User-Agent': USER_AGENT, 'Accept-Language': 'ja-JP,ja;q=0.9', 'Accept': 'text/html,application/xhtml+xml,*/*;q=0.8', 'Cookie': getCookieHeader(), ...(options.headers || {}), }, }; const req = https.request(reqOpts, (res) => { setCookies(res.headers['set-cookie']); let body = ''; res.on('data', (d) => (body += d)); res.on('end', () => { resolve({ status: res.statusCode, headers: res.headers, body }); }); }); req.on('error', reject); if (options.body) req.write(options.body); req.end(); }); } // HTML の hidden フィールドを抽出 function extractHiddenFields(html) { const fields = {}; const re = /]+type=["']?hidden["']?[^>]*>/gi; let match; while ((match = re.exec(html)) !== null) { const tag = match[0]; const name = (tag.match(/name=["']([^"']+)["']/) || [])[1]; const value = (tag.match(/value=["']([^"']*)["']/) || ['', ''])[1]; if (name) fields[name] = value; } return fields; } // フォームの action URL を抽出 function extractFormAction(html) { const m = html.match(/id="ap_login_form"[^>]+action="([^"]+)"/); if (m) return m[1].replace(/&/g, '&'); const m2 = html.match(/name="signIn"[^>]+action="([^"]+)"/); if (m2) return m2[1].replace(/&/g, '&'); return null; } async function main() { console.log('[1] ログインページ取得中...'); const page1 = await request(ALEXA_LOGIN_URL); console.log(` Status: ${page1.status}, Cookies: ${Object.keys(cookieJar).join(', ')}`); if (page1.status !== 200) { console.error(`[ERROR] ログインページ取得失敗: ${page1.status}`); process.exit(1); } // フォーム情報を抽出 const action = extractFormAction(page1.body); const hidden = extractHiddenFields(page1.body); if (!action) { console.error('[ERROR] ログインフォームが見つかりません。HTMLを確認します:'); console.error(page1.body.substring(0, 500)); process.exit(1); } console.log(`[2] フォーム送信先: ${action}`); console.log(` Hidden fields: ${Object.keys(hidden).join(', ')}`); // フォームデータ構築 const formData = new URLSearchParams({ ...hidden, email: EMAIL, password: PASSWORD, rememberMe: 'true', }).toString(); console.log('[3] 認証送信中...'); const page2 = await request(action, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': ALEXA_LOGIN_URL, }, body: formData, }); console.log(` Status: ${page2.status}`); console.log(` Location: ${page2.headers.location || '(none)'}`); console.log(` Cookies after login: ${Object.keys(cookieJar).join(', ')}`); // リダイレクトをたどる let current = page2; let redirectCount = 0; while (current.status >= 300 && current.status < 400 && current.headers.location && redirectCount < 10) { const loc = current.headers.location; const nextUrl = loc.startsWith('http') ? loc : `https://www.amazon.co.jp${loc}`; console.log(`[${4 + redirectCount}] Redirect → ${nextUrl.substring(0, 80)}...`); current = await request(nextUrl); console.log(` Status: ${current.status}, New Cookies: ${Object.keys(cookieJar).join(', ')}`); redirectCount++; } // 成功判定: at-acbjp または session-token が含まれているか const cookie = getCookieHeader(); const hasAlexaToken = cookie.includes('at-acbjp') || cookie.includes('session-token'); if (!hasAlexaToken) { console.error('[ERROR] 認証に失敗しました。取得できたCookieに認証トークンが含まれていません。'); console.error('取得済みCookieキー:', Object.keys(cookieJar).join(', ')); if (current.body.includes('captcha') || current.body.includes('CAPTCHA')) { console.error('※ CAPTCHA が要求されています。しばらく待ってから再試行してください。'); } if (current.body.includes('password') && current.body.includes('error')) { console.error('※ パスワードが間違っている可能性があります。'); } process.exit(1); } // .env に保存 const envPath = path.join(__dirname, '.env'); fs.writeFileSync(envPath, `ALEXA_COOKIE=${cookie}\n`); console.log('\n=============================================='); console.log(' 認証成功!'); console.log('=============================================='); console.log(`.env を保存しました: ${envPath}`); console.log(`Cookie 長さ: ${cookie.length} 文字`); } main().catch((err) => { console.error('[FATAL]', err); process.exit(1); });