191 lines
6.7 KiB
JavaScript
191 lines
6.7 KiB
JavaScript
/**
|
|
* 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 = /<input[^>]+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);
|
|
});
|