UNPKG

wiki-saikou

Version:

The library provides the out of box accessing to MediaWiki API in both browsers & Node.js, and the syntax is very similar to vanilla `new mw.Api()`. TypeScript definition included~

674 lines (673 loc) 23.3 kB
var m = /* @__PURE__ */ ((a) => (a.BODY_USED = "BODY_USED", a.NO_BODY_READER = "NO_BODY_READER", a.TIMEOUT = "TIMEOUT", a.NETWORK_ERROR = "NETWORK_ERROR", a.BODY_NOT_ALLOWED = "BODY_NOT_ALLOWED", a.HOOK_CONTEXT_CHANGED = "HOOK_CONTEXT_CHANGED", a.ABORTED_BY_HOOK = "ABORTED_BY_HOOK", a.INVALID_HOOK_CALLBACK = "INVALID_HOOK_CALLBACK", a.UNEXPECTED_HOOK_RETURN = "UNEXPECTED_HOOK_RETURN", a))(m || {}); class y extends Error { constructor(e, o, r, s) { super(o, s), this.code = e, this.context = r, this.name = "FexiosError"; } } class U extends y { constructor(e, o, r) { super(o.statusText, e, void 0, r), this.response = o, this.name = "FexiosResponseError"; } } function C(a, e = 1024) { if (!(a instanceof Uint8Array)) throw new TypeError("Input must be a Uint8Array"); const o = a.slice(0, e), r = new TextDecoder("utf-8", { fatal: !0 }); try { const s = r.decode(o), n = /[\x00-\x08\x0E-\x1F\x7F]/g, i = s.match(n); return !(i && i.length / s.length > 0.1); } catch { return !1; } } function S(a) { if (typeof a != "object" || a === null || Object.prototype.toString.call(a) !== "[object Object]") return !1; const e = Object.getPrototypeOf(a); return e === Object.prototype || e === null; } function A(a, e = {}) { const o = {}; return Object.entries(a).forEach(([r, s]) => { s != null && (e.dropEmptyString && s === "" || (o[r] = s)); }), o; } class _ { constructor(e, o, r) { this.rawResponse = e, this.data = o, this.ok = e.ok, this.status = e.status, this.statusText = e.statusText, this.headers = e.headers, Object.entries(r || {}).forEach(([s, n]) => { this[s] = n; }); } } async function j(a, e, o) { var r; if (a.bodyUsed) throw new y( m.BODY_USED, "Response body has already been used or locked" ); const s = a.headers.get("content-type") || "", n = Number(a.headers.get("content-length")) || 0, i = (h, c) => c === "json" || h.startsWith("application/json"), t = (h, c, u) => u === "blob" || h.startsWith("image/") || h.startsWith("video/") || h.startsWith("audio/") || !C(c); if ((a.status === 101 || a.status === 426 || a.headers.get("upgrade")) && typeof globalThis.WebSocket < "u") { const h = new WebSocket(a.url); return await new Promise((c, u) => { h.onopen = c, h.onerror = u; }), new _(a, h, { ok: !0, status: 101, statusText: "Switching Protocols" }); } else if (s.startsWith("text/event-stream") && !["text", "json"].includes(e || "") && typeof globalThis.EventSource < "u") { const h = new EventSource(a.url); return await new Promise((c, u) => { h.onopen = c, h.onerror = u; }), new _(a, h); } else { if (e === "stream") return new _( a, a.body ); { const h = (r = a.clone().body) == null ? void 0 : r.getReader(); if (!h) throw new y( m.NO_BODY_READER, "Failed to get ReadableStream from response body" ); let c = new Uint8Array(); for (; ; ) { const { done: l, value: b } = await h.read(); if (l) break; if (b && (c = new Uint8Array([...c, ...b]), o && n > 0)) { const f = Math.min(c.length / n, 1); o(f, c); } } const u = new _(a, void 0); if (t(s, c, e) ? u.data = new Blob([c], { type: a.headers.get("content-type") || void 0 }) : u.data = new TextDecoder().decode(c), i(s, e)) try { u.data = JSON.parse(u.data); } catch { } if (typeof u.data == "string" && e !== "text") { const l = u.data.trim(), b = l[0], f = l[l.length - 1]; if (b === "{" && f === "}" || b === "[" && f === "]") try { u.data = JSON.parse(u.data); } catch { } } if (typeof u.data > "u" && (u.data = c.length > 0 ? c : void 0), u.ok) return u; throw new U( `Request failed with status code ${a.status}`, u ); } } } class H { /** * Build URLSearchParams from a record object with proper array handling * @param query - The query object containing key-value pairs * @returns URLSearchParams instance */ static makeSearchParams(e) { const o = new URLSearchParams(); return Object.entries(e).forEach(([r, s]) => { Array.isArray(s) ? s.forEach((n) => o.append(r, String(n))) : o.set(r, String(s)); }), o; } /** * Build query string from a record object with proper array handling * @param query - The query object containing key-value pairs * @returns URL-encoded query string */ static makeQueryString(e) { return this.makeSearchParams(e).toString(); } } function W(a) { return a && a.__esModule && Object.prototype.hasOwnProperty.call(a, "default") ? a.default : a; } var P, I; function B() { if (I) return P; I = 1; function a(e) { var o = this.constructor.prototype[e], r = function() { return o.apply(r, arguments); }; return Object.setPrototypeOf(r, this.constructor.prototype), Object.getOwnPropertyNames(o).forEach(function(s) { Object.defineProperty(r, s, Object.getOwnPropertyDescriptor(o, s)); }), r; } return a.prototype = Object.create(Function.prototype), P = a, P; } var M = B(); const F = /* @__PURE__ */ W(M); class T extends F { constructor(e = {}) { super("request"), this.baseConfigs = e, this.hooks = [], this.DEFAULT_CONFIGS = { baseURL: "", timeout: 60 * 1e3, credentials: "same-origin", headers: {}, query: {}, responseType: void 0 }, this.ALL_METHODS = [ "get", "post", "put", "patch", "delete", "head", "options", "trace" ], this.METHODS_WITHOUT_BODY = [ "get", "head", "options", "trace" ], this.interceptors = { request: this.createInterceptor("beforeRequest"), response: this.createInterceptor("afterResponse") }, this.create = T.create, this.dropUndefinedAndNull = A, this.checkIsPlainObject = S, this.ALL_METHODS.forEach(this.createMethodShortcut.bind(this)); } async request(e, o) { var r, s, n, i; let t = o = o || {}; typeof e == "string" || e instanceof URL ? t.url = e.toString() : typeof e == "object" && (t = { ...e, ...t }), t = await this.emit("beforeInit", t); const h = o.baseURL || this.baseConfigs.baseURL || ((r = globalThis.location) == null ? void 0 : r.href), c = h ? new URL(h, (s = globalThis.location) == null ? void 0 : s.href) : void 0, u = new URL(t.url.toString(), c); t.url = u.href, t.baseURL = c ? c.href : u.origin, t.headers = this.mergeHeaders( this.baseConfigs.headers, o.headers ); const l = c == null ? void 0 : c.searchParams, b = new URLSearchParams(u.searchParams); if (u.search = "", t.url = u.href, t.query = this.mergeQuery( l, // baseURL query (lowest priority) this.baseConfigs.query, // defaultOptions (baseOptions) b, // requestURL query (urlParams) o.query // requestOptions (highest priority) ), u.search = H.makeQueryString(t.query), t.url = u.toString(), this.METHODS_WITHOUT_BODY.includes( (n = t.method) == null ? void 0 : n.toLocaleLowerCase() ) && t.body) throw new y( m.BODY_NOT_ALLOWED, `Request method "${t.method}" does not allow body` ); t = await this.emit("beforeRequest", t); let f; typeof t.body < "u" && t.body !== null && (t.body instanceof Blob || t.body instanceof FormData || t.body instanceof URLSearchParams ? f = t.body : typeof t.body == "object" && t.body !== null ? (f = JSON.stringify(t.body), t.headers["content-type"] = "application/json") : f = t.body), !((i = o.headers) != null && i["content-type"]) && f && (f instanceof FormData || f instanceof URLSearchParams ? delete t.headers["content-type"] : typeof f == "string" && typeof t.body == "object" ? t.headers["content-type"] = "application/json" : f instanceof Blob && (t.headers["content-type"] = f.type)), t.body = f, t = await this.emit("afterBodyTransformed", t); const w = t.abortController || globalThis.AbortController ? new AbortController() : void 0, v = new Request(t.url, { method: t.method || "GET", credentials: t.credentials, cache: t.cache, mode: t.mode, headers: t.headers, body: t.body, signal: w == null ? void 0 : w.signal }); t.rawRequest = v, t = await this.emit("beforeActualFetch", t); const D = t.timeout || this.baseConfigs.timeout || 60 * 1e3; if (t.url.startsWith("ws")) try { const d = new WebSocket(t.url); return await new Promise((R, k) => { const E = setTimeout(() => { k( new y( m.TIMEOUT, `WebSocket connection timed out after ${D}ms`, t ) ); }, D); d.onopen = () => { clearTimeout(E), R(); }, d.onerror = (L) => { clearTimeout(E), k( new y( m.NETWORK_ERROR, "WebSocket connection failed", t ) ); }, d.onclose = (L) => { L.code !== 1e3 && (clearTimeout(E), k( new y( m.NETWORK_ERROR, `WebSocket closed with code ${L.code}`, t ) )); }; }), t.rawResponse = new Response(), t.response = new _(t.rawResponse, d, { ok: !0, status: 101, statusText: "Switching Protocols" }), t.data = d, t.headers = new Headers(), this.emit("afterResponse", t); } catch (d) { throw d instanceof y ? d : new y( m.NETWORK_ERROR, `WebSocket creation failed: ${d}`, t ); } let p; try { w && (p = setTimeout(() => { w.abort(); }, D)); const d = await fetch(t.rawRequest).catch((R) => { throw p && clearTimeout(p), w != null && w.signal.aborted ? new y( m.TIMEOUT, `Request timed out after ${D}ms`, t ) : new y(m.NETWORK_ERROR, R.message, t); }); return p && clearTimeout(p), t.rawResponse = d, t.response = await j( d, t.responseType, (R, k) => { var E; (E = o == null ? void 0 : o.onProgress) == null || E.call(o, R, k); } ), t.data = t.response.data, t.headers = t.response.headers, this.emit("afterResponse", t); } catch (d) { throw p && clearTimeout(p), d; } } mergeQuery(e, ...o) { const r = {}, s = (n) => { n && (S(n) ? Object.entries(n).forEach(([i, t]) => { t == null ? delete r[i] : Array.isArray(t) ? (i.endsWith("[]"), r[i] = t.map(String)) : r[i] = String(t); }) : new URLSearchParams(n).forEach((i, t) => { r[t] = i; })); }; return s(e), o.forEach(s), r; } mergeHeaders(e, ...o) { const r = {}, s = new Headers(e); for (const n of o) if (n != null) if (S(n)) { const i = A(n); if (Object.keys(i).length === 0) continue; new Headers(i).forEach((t, h) => { s.set(h, t); }); } else new Headers(n).forEach((i, t) => { s.set(t, i); }); return s.forEach((n, i) => { r[i] = n; }), r; } async emit(e, o) { const r = this.hooks.filter((s) => s.event === e); try { let s = 0; for (const n of r) { const i = `${e}#${n.action.name || `anonymous#${s}`}`, t = Symbol("FexiosHookContext"); o[t] = t; const h = await n.action.call(this, o); if (h === !1) throw new y( m.ABORTED_BY_HOOK, `Request aborted by hook "${i}"`, o ); if (typeof h == "object" && h[t] === t) o = h; else { const c = globalThis["".concat("console")]; try { throw new y( m.HOOK_CONTEXT_CHANGED, `Hook "${i}" should return the original FexiosContext or return false to abort the request, but got "${h}".` ); } catch (u) { c.warn(u.stack || u); } } delete o[t], s++; } } catch (s) { return Promise.reject(s); } return o; } on(e, o, r = !1) { if (typeof o != "function") throw new y( m.INVALID_HOOK_CALLBACK, `Hook should be a function, but got "${typeof o}"` ); return this.hooks[r ? "unshift" : "push"]({ event: e, action: o }), this; } off(e, o) { return e === "*" || !e ? this.hooks = this.hooks.filter((r) => r.action !== o) : this.hooks = this.hooks.filter( (r) => r.event !== e || r.action !== o ), this; } createInterceptor(e) { return { handlers: () => this.hooks.filter((o) => o.event === e).map((o) => o.action), use: (o, r = !1) => this.on(e, o, r), clear: () => { this.hooks = this.hooks.filter((o) => o.event !== e); } }; } createMethodShortcut(e) { return Object.defineProperty(this, e, { value: (o, r, s) => (this.METHODS_WITHOUT_BODY.includes( e.toLocaleLowerCase() ) ? s = r : (s = s || {}, s.body = r), this.request(o, { ...s, method: e })) }), this; } extends(e) { const o = new T({ ...this.baseConfigs, ...e }); return o.hooks = [...this.hooks], o; } static create(e) { return new T(e); } } /** * Fexios * @desc Fetch based HTTP client with similar API to axios for browser and Node.js * * @license MIT * @author dragon-fish <dragon-fish@qq.com> */ const K = T.create, N = K(); typeof globalThis < "u" ? globalThis.fexios = N : typeof window < "u" && (window.fexios = N); /** * MediaWiki Api for Axios * Provides the API call methods similar to `mw.Api` at non-mw environments * * @author Dragon-Fish <dragon-fish@qq.com> * @license MIT */ const g = class g { constructor(e, o, r) { var n, i; if (this.baseURL = e, this.version = "4.2.1", this.cookies = /* @__PURE__ */ new Map(), !e && typeof window == "object" && window.mediaWiki) { const { wgServer: t, wgScriptPath: h } = ((i = (n = window.mediaWiki) == null ? void 0 : n.config) == null ? void 0 : i.get(["wgServer", "wgScriptPath"])) || {}; typeof t == "string" && typeof h == "string" && (e = `${t}${h}/api.php`); } if (typeof e != "string") throw new Error("baseURL is undefined"); this.baseURL = e, this.tokens = {}, this.defaultParams = { ...g.INIT_DEFAULT_PARAMS, ...r }, this.defaultOptions = o || {}; const s = g.createRequestHandler(this.baseURL); this.request = s, "document" in globalThis || (s.interceptors.request.use((t) => (t.headers = t.headers || {}, t.headers.cookie = Array.from(this.cookies.entries()).map(([h, c]) => `${h}=${c}`).join("; "), t)), s.interceptors.response.use((t) => { const h = t.rawResponse.headers.get( "set-cookie" ), c = h == null ? void 0 : h.split(",").map((u) => u.trim()); return c == null || c.forEach((u) => { const [l, ...b] = u.split(";")[0].split("="); this.cookies.set(l, b.join("=")); }), t; })); } setBaseURL(e) { return this.request.baseConfigs.baseURL = e, this; } static normalizeParamValue(e) { return Array.isArray(e) ? e.join("|") : typeof e == "boolean" ? e ? "1" : void 0 : typeof e == "number" ? "" + e : e; } static createRequestHandler(e) { const o = new T({ baseURL: e, responseType: "json" }); return o.on("beforeInit", (r) => { var s; if (((s = r.method) == null ? void 0 : s.toLowerCase()) !== "post") return r; if (typeof r.body == "object" && r.body !== null && !(r.body instanceof URLSearchParams) && !(r.body instanceof FormData)) { const n = r.body; Object.keys(n).forEach((i) => { const t = g.normalizeParamValue(n[i]); typeof t > "u" || t === null ? delete n[i] : t !== n[i] && (n[i] = t); }), r.body = new URLSearchParams(r.body); } if (globalThis.FormData && r.body instanceof FormData || r.body instanceof URLSearchParams) { const n = r.body; n.forEach((t, h) => { const c = g.normalizeParamValue(t); typeof c > "u" || c === null ? n.delete(h) : c !== t && n.set(h, c); }); const i = new URLSearchParams(r.query); !i.has("format") && i.set("format", "" + (n.get("format") || "json")), !i.has("formatversion") && i.set( "formatversion", "" + (n.get("formatversion") || "2") ), n.has("origin") && i.set("origin", "" + n.get("origin")), r.query = Object.fromEntries(i.entries()), n.has("action") && (r.query.action = "" + n.get("action")); } return r; }), o.on("beforeInit", (r) => { r.query = r.query; for (const s in r.query) { const n = g.normalizeParamValue(r.query[s]); typeof n > "u" || n === null ? delete r.query[s] : n !== r.query[s] && (r.query[s] = "" + n); } return r; }), o.on("beforeRequest", (r) => { const s = new URL(r.url), n = s.searchParams; if (globalThis.location && (!n.has("origin") && location.origin !== new URL(e).origin ? (n.set("origin", location.origin), o.baseConfigs.credentials = "include", o.baseConfigs.mode = "cors") : location.origin === new URL(e).origin && (n.delete("origin"), o.baseConfigs.credentials = void 0, o.baseConfigs.mode = void 0)), s.searchParams.has("origin")) { const i = encodeURIComponent( s.searchParams.get("origin") || "" ).replace(/\./g, "%2E"); r.query = {}, s.searchParams.delete("origin"), r.url = `${s}${s.search ? "&" : "?"}origin=${i}`; } return r; }), o; } /** Base methods encapsulation */ get(e, o) { return this.request.get("", { ...this.defaultOptions, query: { ...this.defaultParams, ...this.defaultOptions.query, ...e }, ...o }); } post(e, o) { return this.request.post("", e, { ...this.defaultOptions, query: { ...this.defaultParams, ...this.defaultOptions.query }, ...o }); } async login(e, o, r, s) { var i, t, h, c, u; if (this.defaultOptions.credentials = "include", s = s || {}, s.retry ?? (s.retry = 3), s.retry < 1) throw new O( "LOGIN_RETRY_LIMIT_EXCEEDED", "The limit of the number of times to automatically re-login has been exceeded" ); let n; try { const l = await this.postWithToken( "login", { action: "login", lgname: e, lgpassword: o, ...r }, { tokenName: "lgtoken", ...s } ); if ((i = l == null ? void 0 : l.data) != null && i.login) n = l.data; else throw l; } catch (l) { if (l instanceof O) throw l; if ((l == null ? void 0 : l.ok) === !1) return this.login(e, o, r, { ...s, noCache: !0, retry: s.retry - 1 }); throw new O( "HTTP_ERROR", "The server returns an error, but it doesn't seem to be caused by MediaWiki", l ); } if (((t = n == null ? void 0 : n.login) == null ? void 0 : t.result) !== "Success") throw new O( "LOGIN_FAILED", ((c = (h = n == null ? void 0 : n.login) == null ? void 0 : h.reason) == null ? void 0 : c.text) || ((u = n == null ? void 0 : n.login) == null ? void 0 : u.result) || "Login failed with unknown reason", n ); return n.login; } async getUserInfo() { var o; const { data: e } = await this.get({ action: "query", meta: "userinfo", uiprop: ["groups", "rights", "blockinfo"] }); return (o = e == null ? void 0 : e.query) == null ? void 0 : o.userinfo; } /** Token Handler */ async getTokens(e = ["csrf"]) { this.defaultOptions.credentials = "include"; const { data: o } = await this.get({ action: "query", meta: "tokens", type: e }); return this.tokens = { ...this.tokens, ...o.query.tokens }, this.tokens; } async token(e = "csrf", o = !1) { return (!this.tokens[`${e}token`] || o) && (delete this.tokens[`${e}token`], await this.getTokens([e])), this.tokens[`${e}token`]; } async postWithToken(e, o, r) { const { tokenName: s = "token", retry: n = 3, noCache: i = !1 } = r || {}; if (n < 1) throw new O( "TOKEN_RETRY_LIMIT_EXCEEDED", "The limit of the number of times to automatically re-acquire the token has been exceeded" ); const t = await this.token(e, i), h = () => this.postWithToken(e, o, { tokenName: s, retry: n - 1, noCache: !0 }); return this.post({ [s]: t, ...o }).then((c) => { const u = c.data; return g.isBadTokenError(u) ? h() : c; }).catch((c) => { const u = c.data; if (g.isBadTokenError(u) || (c == null ? void 0 : c.ok) === !1) return h(); if (typeof u == "object" && u !== null) return Promise.reject(u); throw new O( "HTTP_ERROR", "The server returns an error, but it doesn’t seem to be caused by MediaWiki", c ); }); } postWithEditToken(e) { return this.postWithToken("csrf", e); } static isBadTokenError(e) { var o, r, s; return ((o = e == null ? void 0 : e.error) == null ? void 0 : o.code) === "badtoken" || ((r = e == null ? void 0 : e.errors) == null ? void 0 : r.some((n) => n.code === "badtoken")) || ["NeedToken", "WrongToken"].includes((s = e == null ? void 0 : e.login) == null ? void 0 : s.result); } async getMessages(e, o = "zh", r) { const { data: s } = await this.get({ action: "query", meta: "allmessages", ammessages: e, amlang: o, ...r }), n = {}; return s.query.allmessages.forEach(function(i) { i.missing || (n[i.name] = i.content); }), n; } async parseWikitext(e, o, r, s) { const { data: n } = await this.post( { action: "parse", title: o, text: e, ...r }, s ); return n.parse.text; } }; g.INIT_DEFAULT_PARAMS = { action: "query", errorformat: "plaintext", format: "json", formatversion: 2 }; let q = g; class Y extends q { constructor(e, o, r) { super( e, { credentials: "include", mode: "cors", ...o }, { origin: location.origin, ...r } ); } } var $ = /* @__PURE__ */ ((a) => (a.HTTP_ERROR = "HTTP_ERROR", a.LOGIN_FAILED = "LOGIN_FAILED", a.LOGIN_RETRY_LIMIT_EXCEEDED = "LOGIN_RETRY_LIMIT_EXCEEDED", a.TOKEN_RETRY_LIMIT_EXCEEDED = "TOKEN_RETRY_LIMIT_EXCEEDED", a))($ || {}); class O extends Error { constructor(e, o = "", r) { super(), this.code = e, this.message = o, this.cause = r, this.name = "WikiSaikouError"; } } export { Y as ForeignApi, q as MediaWikiApi, Y as MediaWikiForeignApi, q as MwApi, O as WikiSaikouError, $ as WikiSaikouErrorCode, q as default }; //# sourceMappingURL=index.js.map