72 lines
2.7 KiB
JavaScript
72 lines
2.7 KiB
JavaScript
// 共用 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}"esCount=0`;
|
||
const j = await yahooJson(url);
|
||
await sleep(120);
|
||
return j?.news || [];
|
||
});
|
||
}
|
||
|
||
export function sleep(ms) {
|
||
return new Promise(r => setTimeout(r, ms));
|
||
} |