finance-dashboard/lib/yahoo-session.js

72 lines
2.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 共用 Yahoo Finance cookie/crumb避免多模組並行請求互相打掛
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
let _auth = { cookie: null, crumb: null, at: 0 };
let _inflight = null;
let _queue = Promise.resolve();
export function resetYahooAuth() {
_auth = { cookie: null, crumb: null, at: 0 };
}
export async function yahooAuth(force = false) {
if (!force && _auth.crumb && Date.now() - _auth.at < 3600e3) return _auth;
if (_inflight) return _inflight;
_inflight = (async () => {
const r1 = await fetch('https://fc.yahoo.com/', { headers: { 'User-Agent': UA } }).catch(() => null);
const cookie = (r1?.headers.get('set-cookie') || '').split(';')[0] || '';
const r2 = await fetch('https://query2.finance.yahoo.com/v1/test/getcrumb', {
headers: { 'User-Agent': UA, Cookie: cookie },
});
const crumb = (await r2.text()).trim();
if (!crumb || crumb.includes('<')) throw new Error('Yahoo crumb');
_auth = { cookie, crumb, at: Date.now() };
return _auth;
})().finally(() => { _inflight = null; });
return _inflight;
}
async function yahooJson(url, retry = true) {
const { cookie, crumb } = await yahooAuth();
const sep = url.includes('?') ? '&' : '?';
const full = `${url}${sep}crumb=${encodeURIComponent(crumb)}`;
const res = await fetch(full, { headers: { 'User-Agent': UA, Cookie: cookie } });
if ((res.status === 401 || res.status === 429) && retry) {
resetYahooAuth();
await sleep(500);
await yahooAuth(true);
return yahooJson(url, false);
}
if (!res.ok) throw new Error(`Yahoo HTTP ${res.status}`);
return res.json();
}
function yahooQueued(fn) {
const run = _queue.then(() => fn());
_queue = run.catch(() => {});
return run;
}
/** quoteSummary 模組assetProfile、topHoldings 等)— 序列化避免並行打掛 crumb */
export async function yahooQuoteSummary(symbol, modules) {
return yahooQueued(async () => {
const mod = Array.isArray(modules) ? modules.join(',') : modules;
const url = `https://query2.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(symbol)}?modules=${encodeURIComponent(mod)}`;
const j = await yahooJson(url);
await sleep(120);
return j?.quoteSummary?.result?.[0] || null;
});
}
export async function yahooFinanceSearchNews(symbol, count = 12) {
return yahooQueued(async () => {
const url = `https://query1.finance.yahoo.com/v1/finance/search?q=${encodeURIComponent(symbol)}&newsCount=${count}&quotesCount=0`;
const j = await yahooJson(url);
await sleep(120);
return j?.news || [];
});
}
export function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}