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
JavaScript
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