@bhavjit/khan-api
Version:
A Khan Academy API client and wrapper
1,497 lines (1,480 loc) • 126 kB
JavaScript
/**
* Khan API v0.7.4
* https://khan-api.bhavjit.com
* Licensed under MIT license
* Copyright 2025 Bhavjit Chauhan
*/
"use strict";
var KhanAPI = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __typeError = (msg) => {
throw TypeError(msg);
};
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
// node_modules/cross-fetch/dist/browser-ponyfill.js
var require_browser_ponyfill = __commonJS({
"node_modules/cross-fetch/dist/browser-ponyfill.js"(exports, module) {
var __global__ = typeof globalThis !== "undefined" && globalThis || typeof self !== "undefined" && self || typeof global !== "undefined" && global;
var __globalThis__ = function() {
function F() {
this.fetch = false;
this.DOMException = __global__.DOMException;
}
F.prototype = __global__;
return new F();
}();
(function(globalThis2) {
var irrelevant = function(exports2) {
var g = typeof globalThis2 !== "undefined" && globalThis2 || typeof self !== "undefined" && self || // eslint-disable-next-line no-undef
typeof global !== "undefined" && global || {};
var support = {
searchParams: "URLSearchParams" in g,
iterable: "Symbol" in g && "iterator" in Symbol,
blob: "FileReader" in g && "Blob" in g && function() {
try {
new Blob();
return true;
} catch (e) {
return false;
}
}(),
formData: "FormData" in g,
arrayBuffer: "ArrayBuffer" in g
};
function isDataView(obj) {
return obj && DataView.prototype.isPrototypeOf(obj);
}
if (support.arrayBuffer) {
var viewClasses = [
"[object Int8Array]",
"[object Uint8Array]",
"[object Uint8ClampedArray]",
"[object Int16Array]",
"[object Uint16Array]",
"[object Int32Array]",
"[object Uint32Array]",
"[object Float32Array]",
"[object Float64Array]"
];
var isArrayBufferView = ArrayBuffer.isView || function(obj) {
return obj && viewClasses.indexOf(Object.prototype.toString.call(obj)) > -1;
};
}
function normalizeName(name) {
if (typeof name !== "string") {
name = String(name);
}
if (/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(name) || name === "") {
throw new TypeError('Invalid character in header field name: "' + name + '"');
}
return name.toLowerCase();
}
function normalizeValue(value) {
if (typeof value !== "string") {
value = String(value);
}
return value;
}
function iteratorFor(items) {
var iterator = {
next: function() {
var value = items.shift();
return { done: value === void 0, value };
}
};
if (support.iterable) {
iterator[Symbol.iterator] = function() {
return iterator;
};
}
return iterator;
}
function Headers(headers) {
this.map = {};
if (headers instanceof Headers) {
headers.forEach(function(value, name) {
this.append(name, value);
}, this);
} else if (Array.isArray(headers)) {
headers.forEach(function(header) {
if (header.length != 2) {
throw new TypeError("Headers constructor: expected name/value pair to be length 2, found" + header.length);
}
this.append(header[0], header[1]);
}, this);
} else if (headers) {
Object.getOwnPropertyNames(headers).forEach(function(name) {
this.append(name, headers[name]);
}, this);
}
}
Headers.prototype.append = function(name, value) {
name = normalizeName(name);
value = normalizeValue(value);
var oldValue = this.map[name];
this.map[name] = oldValue ? oldValue + ", " + value : value;
};
Headers.prototype["delete"] = function(name) {
delete this.map[normalizeName(name)];
};
Headers.prototype.get = function(name) {
name = normalizeName(name);
return this.has(name) ? this.map[name] : null;
};
Headers.prototype.has = function(name) {
return this.map.hasOwnProperty(normalizeName(name));
};
Headers.prototype.set = function(name, value) {
this.map[normalizeName(name)] = normalizeValue(value);
};
Headers.prototype.forEach = function(callback, thisArg) {
for (var name in this.map) {
if (this.map.hasOwnProperty(name)) {
callback.call(thisArg, this.map[name], name, this);
}
}
};
Headers.prototype.keys = function() {
var items = [];
this.forEach(function(value, name) {
items.push(name);
});
return iteratorFor(items);
};
Headers.prototype.values = function() {
var items = [];
this.forEach(function(value) {
items.push(value);
});
return iteratorFor(items);
};
Headers.prototype.entries = function() {
var items = [];
this.forEach(function(value, name) {
items.push([name, value]);
});
return iteratorFor(items);
};
if (support.iterable) {
Headers.prototype[Symbol.iterator] = Headers.prototype.entries;
}
function consumed(body) {
if (body._noBody) return;
if (body.bodyUsed) {
return Promise.reject(new TypeError("Already read"));
}
body.bodyUsed = true;
}
function fileReaderReady(reader) {
return new Promise(function(resolve, reject) {
reader.onload = function() {
resolve(reader.result);
};
reader.onerror = function() {
reject(reader.error);
};
});
}
function readBlobAsArrayBuffer(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
reader.readAsArrayBuffer(blob);
return promise;
}
function readBlobAsText(blob) {
var reader = new FileReader();
var promise = fileReaderReady(reader);
var match = /charset=([A-Za-z0-9_-]+)/.exec(blob.type);
var encoding = match ? match[1] : "utf-8";
reader.readAsText(blob, encoding);
return promise;
}
function readArrayBufferAsText(buf) {
var view = new Uint8Array(buf);
var chars = new Array(view.length);
for (var i = 0; i < view.length; i++) {
chars[i] = String.fromCharCode(view[i]);
}
return chars.join("");
}
function bufferClone(buf) {
if (buf.slice) {
return buf.slice(0);
} else {
var view = new Uint8Array(buf.byteLength);
view.set(new Uint8Array(buf));
return view.buffer;
}
}
function Body() {
this.bodyUsed = false;
this._initBody = function(body) {
this.bodyUsed = this.bodyUsed;
this._bodyInit = body;
if (!body) {
this._noBody = true;
this._bodyText = "";
} else if (typeof body === "string") {
this._bodyText = body;
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
this._bodyBlob = body;
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
this._bodyFormData = body;
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this._bodyText = body.toString();
} else if (support.arrayBuffer && support.blob && isDataView(body)) {
this._bodyArrayBuffer = bufferClone(body.buffer);
this._bodyInit = new Blob([this._bodyArrayBuffer]);
} else if (support.arrayBuffer && (ArrayBuffer.prototype.isPrototypeOf(body) || isArrayBufferView(body))) {
this._bodyArrayBuffer = bufferClone(body);
} else {
this._bodyText = body = Object.prototype.toString.call(body);
}
if (!this.headers.get("content-type")) {
if (typeof body === "string") {
this.headers.set("content-type", "text/plain;charset=UTF-8");
} else if (this._bodyBlob && this._bodyBlob.type) {
this.headers.set("content-type", this._bodyBlob.type);
} else if (support.searchParams && URLSearchParams.prototype.isPrototypeOf(body)) {
this.headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
}
}
};
if (support.blob) {
this.blob = function() {
var rejected = consumed(this);
if (rejected) {
return rejected;
}
if (this._bodyBlob) {
return Promise.resolve(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(new Blob([this._bodyArrayBuffer]));
} else if (this._bodyFormData) {
throw new Error("could not read FormData body as blob");
} else {
return Promise.resolve(new Blob([this._bodyText]));
}
};
}
this.arrayBuffer = function() {
if (this._bodyArrayBuffer) {
var isConsumed = consumed(this);
if (isConsumed) {
return isConsumed;
} else if (ArrayBuffer.isView(this._bodyArrayBuffer)) {
return Promise.resolve(
this._bodyArrayBuffer.buffer.slice(
this._bodyArrayBuffer.byteOffset,
this._bodyArrayBuffer.byteOffset + this._bodyArrayBuffer.byteLength
)
);
} else {
return Promise.resolve(this._bodyArrayBuffer);
}
} else if (support.blob) {
return this.blob().then(readBlobAsArrayBuffer);
} else {
throw new Error("could not read as ArrayBuffer");
}
};
this.text = function() {
var rejected = consumed(this);
if (rejected) {
return rejected;
}
if (this._bodyBlob) {
return readBlobAsText(this._bodyBlob);
} else if (this._bodyArrayBuffer) {
return Promise.resolve(readArrayBufferAsText(this._bodyArrayBuffer));
} else if (this._bodyFormData) {
throw new Error("could not read FormData body as text");
} else {
return Promise.resolve(this._bodyText);
}
};
if (support.formData) {
this.formData = function() {
return this.text().then(decode);
};
}
this.json = function() {
return this.text().then(JSON.parse);
};
return this;
}
var methods = ["CONNECT", "DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT", "TRACE"];
function normalizeMethod(method) {
var upcased = method.toUpperCase();
return methods.indexOf(upcased) > -1 ? upcased : method;
}
function Request(input, options) {
if (!(this instanceof Request)) {
throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
}
options = options || {};
var body = options.body;
if (input instanceof Request) {
if (input.bodyUsed) {
throw new TypeError("Already read");
}
this.url = input.url;
this.credentials = input.credentials;
if (!options.headers) {
this.headers = new Headers(input.headers);
}
this.method = input.method;
this.mode = input.mode;
this.signal = input.signal;
if (!body && input._bodyInit != null) {
body = input._bodyInit;
input.bodyUsed = true;
}
} else {
this.url = String(input);
}
this.credentials = options.credentials || this.credentials || "same-origin";
if (options.headers || !this.headers) {
this.headers = new Headers(options.headers);
}
this.method = normalizeMethod(options.method || this.method || "GET");
this.mode = options.mode || this.mode || null;
this.signal = options.signal || this.signal || function() {
if ("AbortController" in g) {
var ctrl = new AbortController();
return ctrl.signal;
}
}();
this.referrer = null;
if ((this.method === "GET" || this.method === "HEAD") && body) {
throw new TypeError("Body not allowed for GET or HEAD requests");
}
this._initBody(body);
if (this.method === "GET" || this.method === "HEAD") {
if (options.cache === "no-store" || options.cache === "no-cache") {
var reParamSearch = /([?&])_=[^&]*/;
if (reParamSearch.test(this.url)) {
this.url = this.url.replace(reParamSearch, "$1_=" + (/* @__PURE__ */ new Date()).getTime());
} else {
var reQueryString = /\?/;
this.url += (reQueryString.test(this.url) ? "&" : "?") + "_=" + (/* @__PURE__ */ new Date()).getTime();
}
}
}
}
Request.prototype.clone = function() {
return new Request(this, { body: this._bodyInit });
};
function decode(body) {
var form = new FormData();
body.trim().split("&").forEach(function(bytes) {
if (bytes) {
var split = bytes.split("=");
var name = split.shift().replace(/\+/g, " ");
var value = split.join("=").replace(/\+/g, " ");
form.append(decodeURIComponent(name), decodeURIComponent(value));
}
});
return form;
}
function parseHeaders(rawHeaders) {
var headers = new Headers();
var preProcessedHeaders = rawHeaders.replace(/\r?\n[\t ]+/g, " ");
preProcessedHeaders.split("\r").map(function(header) {
return header.indexOf("\n") === 0 ? header.substr(1, header.length) : header;
}).forEach(function(line) {
var parts = line.split(":");
var key = parts.shift().trim();
if (key) {
var value = parts.join(":").trim();
try {
headers.append(key, value);
} catch (error) {
console.warn("Response " + error.message);
}
}
});
return headers;
}
Body.call(Request.prototype);
function Response(bodyInit, options) {
if (!(this instanceof Response)) {
throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');
}
if (!options) {
options = {};
}
this.type = "default";
this.status = options.status === void 0 ? 200 : options.status;
if (this.status < 200 || this.status > 599) {
throw new RangeError("Failed to construct 'Response': The status provided (0) is outside the range [200, 599].");
}
this.ok = this.status >= 200 && this.status < 300;
this.statusText = options.statusText === void 0 ? "" : "" + options.statusText;
this.headers = new Headers(options.headers);
this.url = options.url || "";
this._initBody(bodyInit);
}
Body.call(Response.prototype);
Response.prototype.clone = function() {
return new Response(this._bodyInit, {
status: this.status,
statusText: this.statusText,
headers: new Headers(this.headers),
url: this.url
});
};
Response.error = function() {
var response = new Response(null, { status: 200, statusText: "" });
response.ok = false;
response.status = 0;
response.type = "error";
return response;
};
var redirectStatuses = [301, 302, 303, 307, 308];
Response.redirect = function(url, status) {
if (redirectStatuses.indexOf(status) === -1) {
throw new RangeError("Invalid status code");
}
return new Response(null, { status, headers: { location: url } });
};
exports2.DOMException = g.DOMException;
try {
new exports2.DOMException();
} catch (err) {
exports2.DOMException = function(message, name) {
this.message = message;
this.name = name;
var error = Error(message);
this.stack = error.stack;
};
exports2.DOMException.prototype = Object.create(Error.prototype);
exports2.DOMException.prototype.constructor = exports2.DOMException;
}
function fetch2(input, init) {
return new Promise(function(resolve, reject) {
var request = new Request(input, init);
if (request.signal && request.signal.aborted) {
return reject(new exports2.DOMException("Aborted", "AbortError"));
}
var xhr = new XMLHttpRequest();
function abortXhr() {
xhr.abort();
}
xhr.onload = function() {
var options = {
statusText: xhr.statusText,
headers: parseHeaders(xhr.getAllResponseHeaders() || "")
};
if (request.url.indexOf("file://") === 0 && (xhr.status < 200 || xhr.status > 599)) {
options.status = 200;
} else {
options.status = xhr.status;
}
options.url = "responseURL" in xhr ? xhr.responseURL : options.headers.get("X-Request-URL");
var body = "response" in xhr ? xhr.response : xhr.responseText;
setTimeout(function() {
resolve(new Response(body, options));
}, 0);
};
xhr.onerror = function() {
setTimeout(function() {
reject(new TypeError("Network request failed"));
}, 0);
};
xhr.ontimeout = function() {
setTimeout(function() {
reject(new TypeError("Network request timed out"));
}, 0);
};
xhr.onabort = function() {
setTimeout(function() {
reject(new exports2.DOMException("Aborted", "AbortError"));
}, 0);
};
function fixUrl(url) {
try {
return url === "" && g.location.href ? g.location.href : url;
} catch (e) {
return url;
}
}
xhr.open(request.method, fixUrl(request.url), true);
if (request.credentials === "include") {
xhr.withCredentials = true;
} else if (request.credentials === "omit") {
xhr.withCredentials = false;
}
if ("responseType" in xhr) {
if (support.blob) {
xhr.responseType = "blob";
} else if (support.arrayBuffer) {
xhr.responseType = "arraybuffer";
}
}
if (init && typeof init.headers === "object" && !(init.headers instanceof Headers || g.Headers && init.headers instanceof g.Headers)) {
var names = [];
Object.getOwnPropertyNames(init.headers).forEach(function(name) {
names.push(normalizeName(name));
xhr.setRequestHeader(name, normalizeValue(init.headers[name]));
});
request.headers.forEach(function(value, name) {
if (names.indexOf(name) === -1) {
xhr.setRequestHeader(name, value);
}
});
} else {
request.headers.forEach(function(value, name) {
xhr.setRequestHeader(name, value);
});
}
if (request.signal) {
request.signal.addEventListener("abort", abortXhr);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
request.signal.removeEventListener("abort", abortXhr);
}
};
}
xhr.send(typeof request._bodyInit === "undefined" ? null : request._bodyInit);
});
}
fetch2.polyfill = true;
if (!g.fetch) {
g.fetch = fetch2;
g.Headers = Headers;
g.Request = Request;
g.Response = Response;
}
exports2.Headers = Headers;
exports2.Request = Request;
exports2.Response = Response;
exports2.fetch = fetch2;
return exports2;
}({});
})(__globalThis__);
__globalThis__.fetch.ponyfill = true;
delete __globalThis__.fetch.polyfill;
var ctx = __global__.fetch ? __global__ : __globalThis__;
exports = ctx.fetch;
exports.default = ctx.fetch;
exports.fetch = ctx.fetch;
exports.Headers = ctx.Headers;
exports.Request = ctx.Request;
exports.Response = ctx.Response;
module.exports = exports;
}
});
// src/index.ts
var index_exports = {};
__export(index_exports, {
Answer: () => Answer,
BadgeCategory: () => BadgeCategory,
BaseMessage: () => BaseMessage,
Client: () => Client,
EmailRegex: () => EmailRegex,
EmailRegexMatch: () => EmailRegexMatch,
FeedbackFocusKind: () => FeedbackFocusKind,
FeedbackKeyRegex: () => FeedbackKeyRegex,
FeedbackKeyRegexMatch: () => FeedbackKeyRegexMatch,
FeedbackSort: () => FeedbackSort,
FeedbackType: () => FeedbackType,
FeedbackTypename: () => FeedbackTypename,
GoogleIDRegex: () => GoogleIDRegex,
GoogleIDRegexMatch: () => GoogleIDRegexMatch,
KaidRegex: () => KaidRegex,
KaidRegexMatch: () => KaidRegexMatch,
ListProgramSortOrder: () => ListProgramSortOrder,
MESSAGE_ENCRYPTED_KEY_LENGTHS: () => MESSAGE_ENCRYPTED_KEY_LENGTHS,
MESSAGE_KEY_LENGTHS: () => MESSAGE_KEY_LENGTHS,
Message: () => Message,
MessageEncryptedKeyRegex: () => MessageEncryptedKeyRegex,
MessageEncryptedKeyRegexMatch: () => MessageEncryptedKeyRegexMatch,
MessageType: () => MessageType,
PROGRAM_ID_LENGTHS: () => PROGRAM_ID_LENGTHS,
PROGRAM_KEY_LENGTHS: () => PROGRAM_KEY_LENGTHS,
PROGRAM_URL_PATHS: () => PROGRAM_URL_PATHS,
Program: () => Program,
ProgramEditorType: () => ProgramEditorType,
ProgramIDRegex: () => ProgramIDRegex,
ProgramIDRegexMatch: () => ProgramIDRegexMatch,
ProgramImagePathRegex: () => ProgramImagePathRegex,
ProgramImagePathRegexMatch: () => ProgramImagePathRegexMatch,
ProgramKeyRegex: () => ProgramKeyRegex,
ProgramKeyRegexMatch: () => ProgramKeyRegexMatch,
ProgramType: () => ProgramType,
ProgramURLRegex: () => ProgramURLRegex,
ProgramURLRegexMatch: () => ProgramURLRegexMatch,
QualarooIDRegex: () => QualarooIDRegex,
QualarooIDRegexMatch: () => QualarooIDRegexMatch,
Question: () => Question,
Reply: () => Reply,
ServiceErrorCode: () => ServiceErrorCode,
ServiceErrorName: () => ServiceErrorName,
TipsAndThanks: () => TipsAndThanks,
TypeToMessageClass: () => TypeToMessageClass,
URL_LOCALES: () => URL_LOCALES,
URL_TLDS: () => URL_TLDS,
User: () => User,
UserAccessLevel: () => UserAccessLevel,
UserURLRegex: () => UserURLRegex,
UserURLRegexMatch: () => UserURLRegexMatch,
assertDataResponse: () => assertDataResponse,
avatarNameToSlug: () => avatarNameToSlug,
avatarNames: () => avatarNames,
avatarSlugToName: () => avatarSlugToName,
avatarSlugs: () => avatarSlugs,
convertAvatarPNGToSVG: () => convertAvatarPNGToSVG,
convertAvatarSVGToPNG: () => convertAvatarSVGToPNG,
extractAvatarSlug: () => extractAvatarSlug,
generateAvatarPNG: () => generateAvatarPNG,
generateAvatarSVG: () => generateAvatarSVG,
get: () => get,
getLatestFragment: () => getLatestFragment,
getLatestMutation: () => getLatestMutation,
getLatestQuery: () => getLatestQuery,
getLatestQueryHash: () => getLatestQueryHash,
graphql: () => graphql,
hashQuery: () => hashQuery,
http: () => http,
isAnswerSchema: () => isAnswerSchema,
isDataResponse: () => isDataResponse,
isEmail: () => isEmail,
isEncryptedFeedbackKey: () => isEncryptedFeedbackKey,
isFeedbackKey: () => isFeedbackKey,
isGoogleID: () => isGoogleID,
isInputErrorResponse: () => isInputErrorResponse,
isKaid: () => isKaid,
isProgramID: () => isProgramID,
isProgramKey: () => isProgramKey,
isProgramURL: () => isProgramURL,
isQualarooID: () => isQualarooID,
isQuestionSchema: () => isQuestionSchema,
isReplySchema: () => isReplySchema,
isServiceErrorsResponse: () => isServiceErrorsResponse,
isTipsAndThanksSchema: () => isTipsAndThanksSchema,
isUserURL: () => isUserURL,
mutations: () => mutations_exports,
post: () => post,
programIDtoKey: () => programIDtoKey,
programKeyToID: () => programKeyToID,
queries: () => queries_exports,
resolveFeedbackKey: () => resolveFeedbackKey,
resolveKaid: () => resolveKaid,
resolveProgramID: () => resolveProgramID,
resolveUsername: () => resolveUsername
});
// src/queries/index.ts
var queries_exports = {};
__export(queries_exports, {
AvatarDataForProfile: () => AvatarDataForProfile,
FeedbackQuery: () => FeedbackQuery,
GetFeedbackReplies: () => GetFeedbackReplies,
GetFeedbackRepliesPage: () => GetFeedbackRepliesPage,
GetFullUserProfile: () => GetFullUserProfile,
GetProfileWidgets: () => GetProfileWidgets,
GetUserByUsernameOrEmail: () => GetUserByUsernameOrEmail,
GetUserHoverCardProfile: () => GetUserHoverCardProfile,
Hotlist: () => Hotlist,
ProgramQuery: () => ProgramQuery,
ProjectsAuthoredByUser: () => ProjectsAuthoredByUser,
QAExpandKeyInfo: () => QAExpandKeyInfo,
QaExpandKeyInfo: () => QaExpandKeyInfo,
avatarDataForProfile: () => avatarDataForProfile,
feedbackQuery: () => feedbackQuery,
getFeedbackReplies: () => getFeedbackReplies,
getFeedbackRepliesPage: () => getFeedbackRepliesPage,
getFullUserProfile: () => getFullUserProfile,
getProfileWidgets: () => getProfileWidgets,
getUserByUsernameOrEmail: () => getUserByUsernameOrEmail,
getUserHoverCardProfile: () => getUserHoverCardProfile,
hotlist: () => hotlist,
programQuery: () => programQuery,
projectsAuthoredByUser: () => projectsAuthoredByUser
});
// src/lib/constants.ts
var KHAN_GRAPHQL_URL = "https://www.khanacademy.org/api/internal/graphql";
var SAFELIST_URL = "https://cdn.jsdelivr.net/gh/bhavjitChauhan/khan-api@safelist";
var PROXY_DOMAIN = "khan-proxy.bhavjit.com";
var FKEY = `bhavjitchauhan/khan-api_${Date.now()}`;
var PLACEHOLDER_PROGRAM_ID = 4669512406581248;
// src/utils/fetch.ts
var import_cross_fetch = __toESM(require_browser_ponyfill(), 1);
// src/utils/safelist.ts
async function getLatestQuery(query) {
const response = await http(`${SAFELIST_URL}/query/${query}`);
if (response.status === 404) return null;
const text = await response.text();
return text;
}
async function getLatestMutation(mutation) {
const response = await http(`${SAFELIST_URL}/mutation/${mutation}`);
if (response.status === 404) return null;
const text = await response.text();
return text;
}
async function getLatestFragment(fragment) {
const response = await http(`${SAFELIST_URL}/fragment/${fragment}`);
if (response.status === 404) return null;
const text = await response.text();
return text;
}
function hashQuery(document) {
let hash = 5381, i = document.length;
while (i) hash = hash * 33 ^ document.charCodeAt(--i);
return hash >>> 0;
}
async function getLatestQueryHash(query) {
const text = await getLatestQuery(query);
if (!text) return null;
return hashQuery(text);
}
// src/utils/fetch.ts
async function http(url, init) {
return await (0, import_cross_fetch.default)(url, init);
}
async function get(url, init) {
init = { method: "get", ...init };
return await http(url, init);
}
async function post(url, body, init) {
init = { method: "post", body: JSON.stringify(body), ...init };
return await http(url, init);
}
async function graphql(url, query, variables = {}, init) {
const body = { query, variables };
if (typeof window !== "undefined" && window.location.hostname !== "khanacademy.org")
url = url.replace("www.khanacademy.org", PROXY_DOMAIN);
const response = await post(url, body, init);
if (response.status === 403) {
const isQuery = query.startsWith("query"), operationName = query.match(/^(?:query|mutation) (\w+)/)?.[1];
if (!operationName)
throw new Error(`An unknown query is no longer in the safelist`);
console.warn(
`The query for operation "${operationName}" is no longer in the safelist. Attempting to fetch the latest version from the safelist...`
);
const latestQuery = isQuery ? await getLatestQuery(operationName) : await getLatestMutation(operationName);
if (!latestQuery)
throw new Error(
`The query for operation "${operationName}" was not found in the safelist`
);
return await post(url, { ...body, query: latestQuery }, init);
}
return response;
}
// src/queries/avatarDataForProfile.ts
var AvatarDataForProfile;
((AvatarDataForProfile2) => {
AvatarDataForProfile2.query = `query avatarDataForProfile($kaid: String!) {
user(kaid: $kaid) {
id
avatar {
name
imageSrc
__typename
}
__typename
}
}`;
})(AvatarDataForProfile || (AvatarDataForProfile = {}));
function avatarDataForProfile(variablesOrKaid, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/avatarDataForProfile`,
AvatarDataForProfile.query,
typeof variablesOrKaid === "string" ? { kaid: variablesOrKaid } : variablesOrKaid,
init
);
}
// src/queries/feedbackQuery.ts
var FeedbackQuery;
((FeedbackQuery2) => {
FeedbackQuery2.query = `query feedbackQuery($topicId: String!, $focusKind: String!, $cursor: String, $limit: Int, $feedbackType: FeedbackType!, $currentSort: Int, $qaExpandKey: String) {
feedback(
focusId: $topicId
cursor: $cursor
limit: $limit
feedbackType: $feedbackType
focusKind: $focusKind
sort: $currentSort
qaExpandKey: $qaExpandKey
answersLimit: 1
) {
feedback {
isLocked
isPinned
replyCount
appearsAsDeleted
author {
id
kaid
nickname
avatar {
name
imageSrc
__typename
}
__typename
}
badges {
name
icons {
smallUrl
__typename
}
description
__typename
}
content
date
definitelyNotSpam
deleted
downVoted
expandKey
feedbackType
flaggedBy
flaggedByUser
flags
focusUrl
focus {
kind
id
translatedTitle
relativeUrl
__typename
}
fromVideoAuthor
key
lowQualityScore
notifyOnAnswer
permalink
qualityKind
replyCount
replyExpandKeys
showLowQualityNotice
sumVotesIncremented
upVoted
... on QuestionFeedback {
hasAnswered
answers {
isLocked
isPinned
replyCount
appearsAsDeleted
author {
id
kaid
nickname
avatar {
name
imageSrc
__typename
}
__typename
}
badges {
name
icons {
smallUrl
__typename
}
description
__typename
}
content
date
definitelyNotSpam
deleted
downVoted
expandKey
feedbackType
flaggedBy
flaggedByUser
flags
focusUrl
focus {
kind
id
translatedTitle
relativeUrl
__typename
}
fromVideoAuthor
key
lowQualityScore
notifyOnAnswer
permalink
qualityKind
replyCount
replyExpandKeys
showLowQualityNotice
sumVotesIncremented
upVoted
__typename
}
isOld
answerCount
__typename
}
... on AnswerFeedback {
question {
isLocked
isPinned
replyCount
appearsAsDeleted
author {
id
kaid
nickname
avatar {
name
imageSrc
__typename
}
__typename
}
badges {
name
icons {
smallUrl
__typename
}
description
__typename
}
content
date
definitelyNotSpam
deleted
downVoted
expandKey
feedbackType
flaggedBy
flaggedByUser
flags
focusUrl
focus {
kind
id
translatedTitle
relativeUrl
__typename
}
fromVideoAuthor
key
lowQualityScore
notifyOnAnswer
permalink
qualityKind
replyCount
replyExpandKeys
showLowQualityNotice
sumVotesIncremented
upVoted
__typename
}
__typename
}
__typename
}
cursor
isComplete
sortedByDate
__typename
}
}`;
})(FeedbackQuery || (FeedbackQuery = {}));
function feedbackQuery(variables, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/feedbackQuery`,
FeedbackQuery.query,
variables,
init
);
}
// src/queries/getFeedbackReplies.ts
var GetFeedbackReplies;
((GetFeedbackReplies2) => {
GetFeedbackReplies2.query = `query getFeedbackReplies($postKey: String!) {
feedbackReplies(feedbackKey: $postKey) {
isLocked
isPinned
expandKey
appearsAsDeleted
author {
id
kaid
nickname
avatar {
name
imageSrc
__typename
}
__typename
}
content
date
definitelyNotSpam
deleted
downVoted
expandKey
feedbackType
flaggedBy
flaggedByUser
flags
focusUrl
fromVideoAuthor
key
lowQualityScore
notifyOnAnswer
permalink
qualityKind
replyCount
replyExpandKeys
showLowQualityNotice
sumVotesIncremented
upVoted
__typename
}
}`;
})(GetFeedbackReplies || (GetFeedbackReplies = {}));
function getFeedbackReplies(variablesOrPostKey, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/getFeedbackReplies`,
GetFeedbackReplies.query,
typeof variablesOrPostKey === "string" ? { postKey: variablesOrPostKey } : variablesOrPostKey,
init
);
}
// src/queries/getFeedbackRepliesPage.ts
var GetFeedbackRepliesPage;
((GetFeedbackRepliesPage2) => {
GetFeedbackRepliesPage2.query = `query getFeedbackRepliesPage($postKey: String!, $cursor: String, $limit: Int!) {
feedbackRepliesPaginated(feedbackKey: $postKey, cursor: $cursor, limit: $limit) {
cursor
isComplete
feedback {
isLocked
isPinned
expandKey
appearsAsDeleted
author {
id
kaid
nickname
avatar {
name
imageSrc
__typename
}
__typename
}
content
date
definitelyNotSpam
deleted
downVoted
expandKey
feedbackType
flaggedBy
flaggedByUser
flags
focusUrl
fromVideoAuthor
key
lowQualityScore
notifyOnAnswer
permalink
qualityKind
replyCount
replyExpandKeys
showLowQualityNotice
sumVotesIncremented
upVoted
__typename
}
__typename
}
}`;
})(GetFeedbackRepliesPage || (GetFeedbackRepliesPage = {}));
function getFeedbackRepliesPage(variables, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/getFeedbackRepliesPage`,
GetFeedbackRepliesPage.query,
variables,
init
);
}
// src/utils/format.ts
function truncate(str, length, postfix = "...") {
length = Math.max(0, length);
return str.length > length ? str.slice(0, length - postfix.length) + postfix : str;
}
function toStandardBase64(base64) {
return base64.replace(/-/g, "+").replace(/_/g, "/");
}
function toURLSafeBase64(base64) {
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
}
// src/utils/regexes.ts
function matchify(regex) {
return new RegExp(`(${regex.source.slice(1, -1)})`);
}
var URL_TLDS = ["com", "org"];
var URL_LOCALES = [
"as",
"az",
"cs",
"da",
"el",
"gu",
"hu",
"id",
"it",
"lt",
"ja",
"kk",
"kn",
"ky",
"lv",
"mn",
"mr",
"my",
"nl",
"pt-pt",
"ru",
"sv",
"ta",
"uz",
"bg",
"bn",
"de",
"en",
"es",
"fr",
"hi",
"hy",
"ka",
"km",
"ko",
"nb",
"pa",
"pl",
"pt",
"ro",
"sr",
"tr",
"vi",
"zh-hans",
"sgn-us"
];
var KaidRegex = /^kaid_\d{20,25}$/;
var KaidRegexMatch = matchify(KaidRegex);
var isKaid = (str) => KaidRegex.test(str);
var UserURLRegex = new RegExp(
`^https?:\\/\\/(?:(?:www|${URL_LOCALES.join(
"|"
)})\\.)?khanacademy\\.(?:${URL_TLDS.join(
"|"
)})\\/profile\\/((?!kaid_|kaid_\\D)\\w+|(?:${KaidRegex.toString().slice(
2,
-2
)}))(?:\\/.*)?$`,
"i"
);
var UserURLRegexMatch = matchify(UserURLRegex);
var isUserURL = (str) => UserURLRegex.test(str);
var PROGRAM_ID_LENGTHS = [9, 10, 16];
var ProgramIDRegex = new RegExp(
`^[1-9](?:(?:${PROGRAM_ID_LENGTHS.map((v) => `\\d{${v - 1}}`).join("|")}))$`
);
var ProgramIDRegexMatch = new RegExp(
`(?:^|\\D)(${ProgramIDRegex.source.slice(1, -1)})(?:$|\\D)`
);
function isProgramID(strOrNum) {
return ProgramIDRegex.test(strOrNum.toString());
}
var PROGRAM_URL_PATHS = [
"computer-programming",
"cs",
"pixar",
"nasa",
"piab-sandbox",
"computer-science",
"hour-of-code",
"math",
"differential-equations",
"electrical-engineering",
"mcat",
"apchem-topic",
"chemistry",
"art-history-basics",
"biology"
];
var ProgramURLRegex = new RegExp(
`^https?:\\/\\/(?:(?:www|${URL_LOCALES.join(
"|"
)})\\.)?khanacademy\\.(?:${URL_TLDS.join("|")})\\/(?:${PROGRAM_URL_PATHS.join(
"|"
)})\\/[\\w\\d-.~()'!*:@,;]+\\/(${ProgramIDRegex.toString().slice(2, -2)})$`,
"i"
);
var ProgramURLRegexMatch = matchify(ProgramURLRegex);
function isProgramURL(str) {
return ProgramURLRegex.test(str);
}
var ProgramImagePathRegex = new RegExp(
`^\\/(?:${PROGRAM_URL_PATHS.join(
"|"
)})\\/[\\w\\d-.~()'!*:@,;]+\\/\\d+\\/(\\d+)\\.png$`
);
var ProgramImagePathRegexMatch = matchify(ProgramImagePathRegex);
var PROGRAM_KEY_LENGTHS = [51, 54];
var ProgramKeyRegex = /^ag5zfmtoYW4tYWNhZGVteXI(?:U|X)CxIKU2NyYXRjaHBhZB(?:i|j)(?=[\w-]*$)(?:.{7}w|.{9}C(?:g|w|A|Q)w)$/;
var ProgramKeyRegexMatch = matchify(ProgramKeyRegex);
function isProgramKey(str) {
if (PROGRAM_KEY_LENGTHS.includes(str.length)) return false;
if (!ProgramKeyRegex.test(str)) return false;
try {
atob(toStandardBase64(str));
return true;
} catch {
return false;
}
}
var MESSAGE_KEY_LENGTHS = [106, 107, 108, 110, 111];
var FeedbackKeyRegex = /^ag5zfmtoYW4tYWNhZGVteX(?:I|J)(?:A|B|7|9|-|_)CxIIVXNlckRhdGEi(?:Gm|GG|G2|Hm|HG|HW)thaWRf(?:M|N|O)(?:\w{34}|\w{29,31}|\w{27})(?:LEghGZWVkYmFjaxiAg[I-P](?:O|P|W|X|2|3)|DAsSCEZlZWRiYWNrGICA|MCxIIRmVlZGJhY2sYgI(?:C|D)|wLEghGZWVkYmFjaxiAg|DAsSCEZlZWRiYWNrGICA)(?:[\w-]{5}|[\w-]{7,8})(?:M|DA|C(?:g|w|A|Q)w)$/;
var FeedbackKeyRegexMatch = matchify(FeedbackKeyRegex);
var isFeedbackKey = (str) => FeedbackKeyRegex.test(str);
var MESSAGE_ENCRYPTED_KEY_LENGTHS = [301];
var MessageEncryptedKeyRegex = /^kaencrypted_[a-z0-9]{32}_[a-z0-9]{256}$/;
var MessageEncryptedKeyRegexMatch = matchify(MessageEncryptedKeyRegex);
var isEncryptedFeedbackKey = (str) => MessageEncryptedKeyRegex.test(str);
var GoogleIDRegex = /^http:\/\/googleid\.khanacademy\.org\/(\d+)$/;
var GoogleIDRegexMatch = matchify(GoogleIDRegex);
var isGoogleID = (str) => GoogleIDRegex.test(str);
var QualarooIDRegex = /^_gae_bingo_random:(\w+-\w+-\w+)$/;
var QualarooIDRegexMatch = matchify(QualarooIDRegex);
var isQualarooID = (str) => QualarooIDRegex.test(str);
var EmailRegex = /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
var EmailRegexMatch = matchify(EmailRegex);
var isEmail = (str) => EmailRegex.test(str);
// src/queries/getFullUserProfile.ts
var GetFullUserProfile;
((GetFullUserProfile2) => {
GetFullUserProfile2.query = `query getFullUserProfile($kaid: String, $username: String) {
user(kaid: $kaid, username: $username) {
id
kaid
key
userId
email
username
profileRoot
gaUserId
isPhantom
isDeveloper: hasPermission(name: "can_do_what_only_admins_can_do")
isPublisher: hasPermission(name: "can_publish", scope: ANY_ON_CURRENT_LOCALE)
isModerator: hasPermission(name: "can_moderate_users", scope: GLOBAL)
isParent
isTeacher
isFormalTeacher
isK4dStudent
isKmapStudent
isDataCollectible
isChild
isOrphan
isCoachingLoggedInUser
canModifyCoaches
nickname
hideVisual
joined
points
countVideosCompleted
bio
profile {
accessLevel
__typename
}
soundOn
muteVideos
showCaptions
prefersReducedMotion
noColorInVideos
newNotificationCount
canHellban: hasPermission(name: "can_ban_users", scope: GLOBAL)
canMessageUsers: hasPermission(
name: "can_send_moderator_messages"
scope: GLOBAL
)
isSelf: isActor
hasStudents: hasCoachees
hasClasses
hasChildren
hasCoach
badgeCounts
homepageUrl
isMidsignupPhantom
includesDistrictOwnedData
includesKmapDistrictOwnedData
includesK4dDistrictOwnedData
canAccessDistrictsHomepage
underAgeGate {
parentEmail
daysUntilCutoff
approvalGivenAt
__typename
}
authEmails
signupDataIfUnverified {
email
emailBounced
__typename
}
pendingEmailVerifications {
email
__typename
}
hasAccessToAIGuideCompanionMode
hasAccessToAIGuideLearner
hasAccessToAIGuideDistrictAdmin
hasAccessToAIGuideParent
hasAccessToAIGuideTeacher
tosAccepted
shouldShowAgeCheck
birthMonthYear
lastLoginCountry
region
userDistrictInfos {
id
isKAD
district {
id
region
__typename
}
__typename
}
schoolAffiliation {
id
location
__typename
}
__typename
}
actorIsImpersonatingUser
isAIGuideEnabled
hasAccessToAIGuideDev
}`;
})(GetFullUserProfile || (GetFullUserProfile = {}));
function getFullUserProfile(variablesOrIdentifier, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/getFullUserProfile`,
GetFullUserProfile.query,
typeof variablesOrIdentifier === "string" ? isKaid(variablesOrIdentifier) ? { kaid: variablesOrIdentifier } : { username: variablesOrIdentifier } : variablesOrIdentifier,
init
);
}
// src/queries/getProfileWidgets.ts
var GetProfileWidgets;
((GetProfileWidgets2) => {
GetProfileWidgets2.query = `query getProfileWidgets($kaid: String!) {
user(kaid: $kaid) {
id
kaid
badgeCounts
isChild
profile {
programs {
id
authorKaid
authorNickname
deleted
displayableSpinoffCount
imagePath
key
sumVotesIncremented
translatedTitle: title
url
__typename
}
__typename
}
programs(sort: TOP, pageInfo: {itemsPerPage: 2}) {
programs {
id
authorKaid
authorNickname
deleted
displayableSpinoffCount
imagePath
key
sumVotesIncremented
translatedTitle: title
url
__typename
}
__typename
}
__typename
}
userSummary(kaid: $kaid) {
statistics {
answers
comments
flags
projectanswers
projectquestions
questions
replies
votes
__typename
}
__typename
}
}`;
})(GetProfileWidgets || (GetProfileWidgets = {}));
function getProfileWidgets(variablesOrKaid, init) {
return graphql(
`${KHAN_GRAPHQL_URL}/getProfileWidgets`,
GetProfileWidgets.query,
typeof variablesOrKaid === "string" && isKaid(variablesOrKaid) ? { kaid: variablesOrKaid } : variablesOrKaid,
init
);
}
// src/queries/getUserByUsernameOrEmail.ts
var GetUserByUsernameOrEmail;
((GetUserByUsernameOrEmail2) => {
GetUserByUsernameOrEmail2.query = `query getUserByUserna