UNPKG

yahoo-finance2

Version:
134 lines (133 loc) 4.92 kB
import Queue from "./queue.js"; import errors from "./errors.js"; import getCrumb from "./getCrumb.js"; const _queue = new Queue(); // deno-lint-ignore no-explicit-any function assertQueueOptions(queue, opts) { opts; //? if (typeof opts.concurrency === "number" && queue.concurrency !== opts.concurrency) { queue.concurrency = opts.concurrency; } if (typeof opts.timeout === "number" && queue.timeout !== opts.timeout) { queue.timeout = opts.timeout; } } function substituteVariables(urlBase) { return urlBase.replace(/\$\{([^}]+)\}/g, (match, varName) => { if (varName === "YF_QUERY_HOST") { // const hosts = ["query1.finance.yahoo.com", "query2.finance.yahoo.com"]; // return hosts[Math.floor(Math.random() * hosts.length)]; return this._opts.YF_QUERY_HOST || "query2.finance.yahoo.com"; } else { // i.e. return unsubstituted original variable expression ${VAR} return match; } }); } async function yahooFinanceFetch(urlBase, params = {}, moduleOpts = {}, func = "json", needsCrumb = false) { if (!(this && this._env)) { throw new errors.NoEnvironmentError("yahooFinanceFetch called without this._env set"); } // TODO: adds func type to json schema which is not supported const queue = moduleOpts.queue?._queue || _queue; // const queue = _queue; assertQueueOptions(queue, { ...this._opts.queue, ...moduleOpts.queue }); const { fetch: envFetch, fetchDevel } = this._env; /* istanbul ignore next */ // no need to force coverage on real network request. const fetchFunc = moduleOpts.devel ? await fetchDevel() : moduleOpts.fetch || envFetch || this._opts.fetch || globalThis.fetch; const fetchOptionsBase = { ...this._opts.fetchOptions, ...moduleOpts.fetchOptions, devel: moduleOpts.devel, headers: { ...this._opts.fetchOptions?.headers, ...moduleOpts.fetchOptions?.headers, }, }; if (needsCrumb) { if (!this._opts.cookieJar) throw new Error("No cookieJar set"); if (!this._opts.logger) throw new Error("Logger was unset."); const crumb = await getCrumb(this._opts.cookieJar, fetchFunc, fetchOptionsBase, this._opts.logger, this._notices); if (crumb) params.crumb = crumb; } const urlSearchParams = new URLSearchParams(params); const url = substituteVariables.call(this, urlBase) + "?" + urlSearchParams.toString(); // console.log(url); // console.log(cookieJar.serializeSync()); if (!this._opts.cookieJar) throw new Error("No cookieJar set"); const fetchOptions = { ...fetchOptionsBase, headers: { ...fetchOptionsBase.headers, cookie: await this._opts.cookieJar.getCookieString(url, { allPaths: true, }), }, }; // console.log("fetch", url, fetchOptions); // used in moduleExec.ts if (func === "csv") func = "text"; const response = (await queue.add(() => fetchFunc(url, fetchOptions))); const setCookieHeaders = response.headers.getSetCookie(); if (setCookieHeaders) { if (!this._opts.cookieJar) throw new Error("No cookieJar set"); this._opts.cookieJar.setFromSetCookieHeaders(setCookieHeaders, url); } const responseText = await response.text(); let result = null; try { result = JSON.parse(responseText); } catch (error) { if (response.ok) { if (error instanceof Error) { throw new Error(`Response.ok where we expect JSON, but the response was not parsable. ` + `Response: "${responseText}". Error: "${error.message}"`); } } } /* { finance: { // or quoteSummary, or any other single key result: null, error: { code: 'Bad Request', description: 'Missing required query parameter=q' } } } */ if (result && func === "json") { const keys = Object.keys(result); if (keys.length === 1) { const errorObj = result[keys[0]].error; if (errorObj) { const errorName = errorObj.code.replace(/ /g, "") + "Error"; const ErrorClass = errors[errorName] || Error; throw new ErrorClass(errorObj.description); } } } // We do this last as it generally contains less information (e.g. no desc). if (!response.ok) { console.error(url); const error = new errors.HTTPError(responseText || response.statusText); error.code = response.status; throw error; } return result; } export { substituteVariables }; export default yahooFinanceFetch;