yahoo-finance2
Version:
JS API for Yahoo Finance
134 lines (133 loc) • 4.92 kB
JavaScript
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;