@satorijs/core
Version:
Core components of Satorijs
957 lines (947 loc) • 31.6 kB
JavaScript
"use strict";
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 __name = (target, value) => __defProp(target, "name", { value, configurable: true });
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 __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
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);
// src/index.ts
var index_exports = {};
__export(index_exports, {
Adapter: () => Adapter,
Bot: () => Bot,
Context: () => SatoriContext,
Element: () => import_element3.default,
HTTP: () => import_plugin_http.HTTP,
InternalRouter: () => InternalRouter,
JsonForm: () => JsonForm,
MessageEncoder: () => MessageEncoder,
Messenger: () => MessageEncoder,
Modulator: () => MessageEncoder,
Quester: () => import_plugin_http.HTTP,
Satori: () => Satori,
Session: () => Session,
Universal: () => Universal,
default: () => index_default,
defineAccessor: () => defineAccessor,
h: () => import_element3.default,
segment: () => import_element3.default
});
module.exports = __toCommonJS(index_exports);
var import_cordis5 = require("cordis");
var import_cosmokit5 = require("cosmokit");
// src/internal.ts
var import_cordis = require("cordis");
var import_cosmokit = require("cosmokit");
var import_path_to_regexp = require("path-to-regexp");
var InternalRouter = class {
constructor(ctx) {
this.ctx = ctx;
}
static {
__name(this, "InternalRouter");
}
[import_cordis.Service.tracker] = {
property: "ctx"
};
routes = [];
define(path, callback) {
return this.ctx.effect(() => {
const route = {
...(0, import_path_to_regexp.pathToRegexp)(path),
callback
};
this.routes.push(route);
return () => (0, import_cosmokit.remove)(this.routes, route);
});
}
handle(bot, method, path, query, headers, body) {
for (const route of this.routes) {
const capture = route.regexp.exec(path);
if (!capture) continue;
const params = {};
route.keys.forEach(({ name }, index) => {
params[name] = capture[index + 1];
});
return route.callback({
bot,
method,
params,
query,
body,
headers: Object.fromEntries(headers.entries())
});
}
}
};
var JsonForm;
((JsonForm2) => {
function load(data, path, form) {
const value = form.get(path);
if (value instanceof File) return value;
if (!data || typeof data !== "object") return data;
if (Array.isArray(data)) {
return data.map((value2, index) => load(value2, `${path}.${index}`, form));
}
return (0, import_cosmokit.valueMap)(data, (value2, key) => {
return load(value2, `${path}.${key}`, form);
});
}
JsonForm2.load = load;
__name(load, "load");
function dump(data, path, form) {
if (!data || typeof data !== "object") return data;
if (data instanceof Blob) {
form.append(path, data);
return null;
}
if (Array.isArray(data)) {
return data.map((value, index) => dump(value, `${path}.${index}`, form));
}
return (0, import_cosmokit.valueMap)(data, (value, key) => {
return dump(value, `${path}.${key}`, form);
});
}
JsonForm2.dump = dump;
__name(dump, "dump");
async function decode(body) {
const type = body.headers.get("content-type");
if (type?.startsWith("multipart/form-data")) {
const response = new globalThis.Response(body.body, { headers: body.headers });
const form = await response.formData();
const json = form.get("$");
return load(JSON.parse(json), "$", form);
} else if (type?.startsWith("application/json")) {
return JSON.parse(new TextDecoder().decode(body.body));
} else {
throw new Error(`Unsupported content type: ${type}`);
}
}
JsonForm2.decode = decode;
__name(decode, "decode");
async function encode(data) {
const form = new FormData();
const json = JSON.stringify(JsonForm2.dump(data, "$", form));
if ([...form.entries()].length) {
form.append("$", json);
const request = new Request("stub:", {
method: "POST",
body: form
});
return {
body: await request.arrayBuffer(),
headers: request.headers
};
} else {
const body = new TextEncoder().encode(json).buffer;
const headers = new Headers({
"content-type": "application/json"
});
return { body, headers };
}
}
JsonForm2.encode = encode;
__name(encode, "encode");
})(JsonForm || (JsonForm = {}));
// src/index.ts
var import_plugin_http = require("@cordisjs/plugin-http");
var import_element3 = __toESM(require("@satorijs/element"), 1);
__reExport(index_exports, require("cordis"), module.exports);
__reExport(index_exports, require("cosmokit"), module.exports);
var Universal = __toESM(require("@satorijs/protocol"), 1);
// src/bot.ts
var import_cosmokit3 = require("cosmokit");
var import_cordis3 = require("cordis");
// src/session.ts
var import_protocol = require("@satorijs/protocol");
var import_cosmokit2 = require("cosmokit");
var import_cordis2 = require("cordis");
var import_element = __toESM(require("@satorijs/element"), 1);
var Session = class {
static {
__name(this, "Session");
}
[import_cordis2.Service.tracker] = {
associate: "session",
property: "ctx"
};
id;
// for backward compatibility
sn;
bot;
app;
event;
locales = [];
constructor(bot, event) {
event.selfId ??= bot.selfId;
event.platform ??= bot.platform;
event.timestamp ??= Date.now();
this.event = event;
this.sn = this.id = ++bot.ctx.satori._sessionSeq;
(0, import_cosmokit2.defineProperty)(this, "bot", bot);
(0, import_cosmokit2.defineProperty)(this, "app", bot.ctx.root);
(0, import_cosmokit2.defineProperty)(this, import_cordis2.Context.current, bot.ctx);
return import_cordis2.Context.associate(this, "session");
}
/** @deprecated */
get data() {
return this.event;
}
get isDirect() {
return this.event.channel?.type === import_protocol.Channel.Type.DIRECT;
}
set isDirect(value) {
(this.event.channel ??= {}).type = value ? import_protocol.Channel.Type.DIRECT : import_protocol.Channel.Type.TEXT;
}
get author() {
return {
...this.event.user,
...this.event.member,
userId: this.event.user?.id,
username: this.event.user?.name,
nickname: this.event.member?.name
};
}
get uid() {
return `${this.platform}:${this.userId}`;
}
get gid() {
return `${this.platform}:${this.guildId}`;
}
get cid() {
return `${this.platform}:${this.channelId}`;
}
get fid() {
return `${this.platform}:${this.channelId}:${this.userId}`;
}
get sid() {
return `${this.platform}:${this.selfId}`;
}
get elements() {
return this.event.message?.elements;
}
set elements(value) {
this.event.message ??= {};
this.event.message.elements = value;
}
get content() {
return this.event.message?.elements?.join("");
}
set content(value) {
this.event.message ??= {};
this.event.message.quote = void 0;
this.event.message.elements = (0, import_cosmokit2.isNullable)(value) ? value : import_element.default.parse(value);
if (this.event.message.elements?.[0]?.type === "quote") {
const el = this.event.message.elements.shift();
this.event.message.quote = import_protocol.Resource.decode(el);
}
}
setInternal(type, data) {
this.event._type = type;
this.event._data = data;
const internal = Object.create(this.bot.internal);
(0, import_cosmokit2.defineProperty)(this, type, Object.assign(internal, data));
}
async transform(elements) {
return await import_element.default.transformAsync(elements, async ({ type, attrs, children }, session) => {
const render = type === "component" ? attrs.is : this.app.get("component:" + type);
if (!render) return true;
children = await render(attrs, children, session);
return this.transform(import_element.default.toElementArray(children));
}, this);
}
toJSON() {
const event = {
...(0, import_cosmokit2.clone)(this.event),
sn: this.sn,
login: this.bot.toJSON(),
["id"]: this.sn
// for backward compatibility
};
if (event.message?.elements) {
event.message.content = this.content;
delete event.message.elements;
if (event.message.quote) {
event.message.content = import_protocol.Resource.encode("quote", event.message.quote) + event.message.content;
}
}
return event;
}
};
function defineAccessor(prototype, name, keys) {
Object.defineProperty(prototype, name, {
get() {
return keys.reduce((data, key) => data?.[key], this);
},
set(value) {
if (value === void 0) return;
const _keys = keys.slice();
const last = _keys.pop();
const data = _keys.reduce((data2, key) => data2[key] ??= {}, this);
data[last] = value;
}
});
}
__name(defineAccessor, "defineAccessor");
defineAccessor(Session.prototype, "type", ["event", "type"]);
defineAccessor(Session.prototype, "subtype", ["event", "subtype"]);
defineAccessor(Session.prototype, "selfId", ["event", "selfId"]);
defineAccessor(Session.prototype, "platform", ["event", "platform"]);
defineAccessor(Session.prototype, "timestamp", ["event", "timestamp"]);
defineAccessor(Session.prototype, "userId", ["event", "user", "id"]);
defineAccessor(Session.prototype, "channelId", ["event", "channel", "id"]);
defineAccessor(Session.prototype, "channelName", ["event", "channel", "name"]);
defineAccessor(Session.prototype, "guildId", ["event", "guild", "id"]);
defineAccessor(Session.prototype, "guildName", ["event", "guild", "name"]);
defineAccessor(Session.prototype, "messageId", ["event", "message", "id"]);
defineAccessor(Session.prototype, "operatorId", ["event", "operator", "id"]);
defineAccessor(Session.prototype, "roleId", ["event", "role", "id"]);
defineAccessor(Session.prototype, "quote", ["event", "message", "quote"]);
defineAccessor(Session.prototype, "referrer", ["event", "referrer"]);
// src/bot.ts
var import_protocol2 = require("@satorijs/protocol");
var eventAliases = [
["message-created", "message"],
["guild-removed", "guild-deleted"],
["guild-member-removed", "guild-member-deleted"]
];
var Bot = class {
constructor(ctx, config, adapterName) {
this.ctx = ctx;
this.config = config;
this.adapterName = adapterName;
this.sn = ++ctx.satori._loginSeq;
this.internal = null;
this._internalRouter = new InternalRouter(ctx);
this.context = ctx;
ctx.bots.push(this);
this.context.emit("bot-added", this);
this.logger = ctx.logger(adapterName);
this.platform = adapterName;
this.features = Object.entries(import_protocol2.Methods).filter(([, value]) => this[value.name]).map(([key]) => key);
ctx.on("ready", async () => {
await Promise.resolve();
this.dispatchLoginEvent("login-added");
return this.start();
});
ctx.on("dispose", () => this.dispose());
ctx.on("interaction/button", (session) => {
const cb = this.callbacks[session.event.button.id];
if (cb) cb(session);
});
}
static {
__name(this, "Bot");
}
static reusable = true;
static MessageEncoder;
[import_cordis3.Service.tracker] = {
associate: "bot",
property: "ctx"
};
sn;
user;
platform;
features;
hidden = false;
adapter;
error;
callbacks = {};
logger;
_internalRouter;
// Same as `this.ctx`, but with a more specific type.
context;
_status = import_protocol2.Status.OFFLINE;
getInternalUrl(path, init, slash) {
let search = new URLSearchParams(init).toString();
if (search) search = "?" + search;
return `internal${slash ? "/" : ":"}${this.platform}/${this.selfId}${path}${search}`;
}
defineInternalRoute(path, callback) {
return this._internalRouter.define(path, callback);
}
update(login) {
const { sn, status, ...rest } = login;
Object.assign(this, rest);
this.status = status;
}
dispose() {
const index = this.ctx.bots.findIndex((bot) => bot.sid === this.sid);
if (index >= 0) {
this.ctx.bots.splice(index, 1);
this.context.emit("bot-removed", this);
this.dispatchLoginEvent("login-removed");
}
return this.stop();
}
dispatchLoginEvent(type) {
const session = this.session();
session.type = type;
session.event.login = this.toJSON();
this.dispatch(session);
}
get status() {
return this._status;
}
set status(value) {
if (value === this._status) return;
this._status = value;
if (this.ctx.bots?.some((bot) => bot.sid === this.sid)) {
this.context.emit("bot-status-updated", this);
this.dispatchLoginEvent("login-updated");
}
}
get isActive() {
return this._status !== import_protocol2.Status.OFFLINE && this._status !== import_protocol2.Status.DISCONNECT;
}
online() {
this.status = import_protocol2.Status.ONLINE;
this.error = void 0;
}
offline(error) {
this.status = import_protocol2.Status.OFFLINE;
this.error = error;
}
async start() {
if (this.isActive) return;
this.status = import_protocol2.Status.CONNECT;
try {
await this.context.parallel("bot-connect", this);
await this.adapter?.connect(this);
} catch (error) {
this.offline(error);
}
}
async stop() {
if (!this.isActive) return;
this.status = import_protocol2.Status.DISCONNECT;
try {
await this.context.parallel("bot-disconnect", this);
await this.adapter?.disconnect(this);
} catch (error) {
this.context.emit(this.ctx, "internal/error", error);
} finally {
this.offline();
}
}
get sid() {
return `${this.platform}:${this.selfId}`;
}
session(event = {}) {
return new Session(this, event);
}
dispatch(session) {
if (!this.ctx.lifecycle.isActive) return;
let events = [session.type];
for (const aliases of eventAliases) {
if (aliases.includes(session.type)) {
events = aliases;
session.type = aliases[0];
break;
}
}
this.context.emit("internal/session", session);
if (session.type === "internal") {
this.context.emit(session.event._type, session.event._data, session.bot);
return;
}
for (const event of events) {
this.context.emit(session, event, session);
}
}
async createMessage(channelId, content, referrer, options) {
const { MessageEncoder: MessageEncoder2 } = this.constructor;
return new MessageEncoder2(this, channelId, referrer, options).send(content);
}
async sendMessage(channelId, content, referrer, options) {
const messages = await this.createMessage(channelId, content, referrer, options);
return messages.map((message) => message.id).filter(import_cosmokit3.isNonNullable);
}
async sendPrivateMessage(userId, content, guildId, options) {
const { id } = await this.createDirectChannel(userId, guildId ?? options?.session?.guildId);
return this.sendMessage(id, content, null, options);
}
async createUpload(...uploads) {
const ids = [];
for (const upload of uploads) {
const id = Math.random().toString(36).slice(2);
const headers = new Headers();
headers.set("content-type", upload.type);
if (upload.filename) {
headers.set("content-disposition", `attachment; filename*=UTF-8''${encodeURIComponent(upload.filename)}`);
}
this.ctx.satori._tempStore[id] = {
status: 200,
body: upload.data,
headers
};
ids.push(id);
}
const timer = setTimeout(() => dispose(), 6e5);
const dispose = /* @__PURE__ */ __name(() => {
_dispose();
clearTimeout(timer);
for (const id of ids) {
delete this.ctx.satori._tempStore[id];
}
}, "dispose");
const _dispose = this.ctx.on("dispose", dispose);
return ids.map((id) => this.getInternalUrl(`/_tmp/${id}`));
}
async supports(name, session = {}) {
return !!this[import_protocol2.Methods[name]?.name];
}
async checkPermission(name, session) {
if (name.startsWith("bot.")) {
return this.supports(name.slice(4), session);
}
}
toJSON() {
return (0, import_cosmokit3.clone)({
...(0, import_cosmokit3.pick)(this, ["sn", "user", "platform", "selfId", "status", "hidden", "features"]),
adapter: this.adapterName
});
}
async getLogin() {
return this.toJSON();
}
/** @deprecated use `bot.getLogin()` instead */
async getSelf() {
const { user } = await this.getLogin();
return user;
}
};
var iterableMethods = [
"getMessage",
"getReaction",
"getFriend",
"getGuild",
"getGuildMember",
"getGuildRole",
"getChannel"
];
for (const name of iterableMethods) {
Bot.prototype[name + "Iter"] = function(...args) {
let list;
if (!this[name + "List"]) throw new Error(`not implemented: ${name}List`);
const getList = /* @__PURE__ */ __name(async () => {
list = await this[name + "List"](...args, list?.next);
if (name === "getMessage") list.data.reverse();
}, "getList");
return {
async next() {
if (list?.data.length) return { done: false, value: list.data.shift() };
if (list && !list?.next) return { done: true, value: void 0 };
await getList();
return this.next();
},
[Symbol.asyncIterator]() {
return this;
}
};
};
}
defineAccessor(Bot.prototype, "selfId", ["user", "id"]);
defineAccessor(Bot.prototype, "userId", ["user", "id"]);
// src/adapter.ts
var import_cosmokit4 = require("cosmokit");
var import_protocol3 = require("@satorijs/protocol");
var import_cordis4 = require("cordis");
var Adapter = class {
constructor(ctx) {
this.ctx = ctx;
}
static {
__name(this, "Adapter");
}
static schema = false;
bots = [];
async connect(bot) {
}
async disconnect(bot) {
}
fork(ctx, bot) {
bot.adapter = this;
this.bots.push(bot);
ctx.on("dispose", () => {
(0, import_cosmokit4.remove)(this.bots, bot);
});
}
};
((Adapter2) => {
Adapter2.WsClientConfig = import_cordis4.z.object({
retryTimes: import_cordis4.z.natural().description("初次连接时的最大重试次数。").default(6),
retryInterval: import_cordis4.z.natural().role("ms").description("初次连接时的重试时间间隔。").default(5 * import_cosmokit4.Time.second),
retryLazy: import_cordis4.z.natural().role("ms").description("连接关闭后的重试时间间隔。").default(import_cosmokit4.Time.minute)
}).description("连接设置");
class WsClientBase extends Adapter2 {
constructor(ctx, config) {
super(ctx);
this.config = config;
}
static {
__name(this, "WsClientBase");
}
socket;
connectionId = 0;
async start() {
let retryCount = 0;
const connectionId = ++this.connectionId;
const logger = this.ctx.logger("adapter");
const { retryTimes, retryInterval, retryLazy } = this.config;
const reconnect = /* @__PURE__ */ __name((initial, message) => {
if (!this.getActive() || connectionId !== this.connectionId) return;
let timeout = retryInterval;
if (retryCount >= retryTimes) {
if (initial) {
return this.setStatus(import_protocol3.Status.OFFLINE, new Error(message));
} else {
timeout = retryLazy;
}
}
retryCount++;
this.setStatus(import_protocol3.Status.RECONNECT);
logger.warn(`${message}, will retry in ${import_cosmokit4.Time.format(timeout)}...`);
setTimeout(() => {
if (!this.getActive() || connectionId !== this.connectionId) return;
connect();
}, timeout);
}, "reconnect");
const connect = /* @__PURE__ */ __name(async (initial = false) => {
logger.debug("websocket client opening");
let socket;
try {
socket = await this.prepare();
} catch (error) {
reconnect(initial, error.toString() || `failed to prepare websocket`);
return;
}
const url = socket.url.replace(/\?.+/, "");
socket.addEventListener("error", (event) => {
if (event.message) logger.warn(event.message);
});
socket.addEventListener("close", ({ code, reason }) => {
if (this.socket === socket) this.socket = void 0;
logger.debug(`websocket closed with ${code}`);
reconnect(initial, reason.toString() || `failed to connect to ${url}, code: ${code}`);
});
socket.addEventListener("open", () => {
retryCount = 0;
this.socket = socket;
logger.info("connect to server: %c", url);
this.accept(socket);
});
}, "connect");
connect(true);
}
async stop() {
this.socket?.close();
}
}
Adapter2.WsClientBase = WsClientBase;
class WsClient extends WsClientBase {
constructor(ctx, bot) {
super(ctx, bot.config);
this.bot = bot;
bot.adapter = this;
}
static {
__name(this, "WsClient");
}
static reusable = true;
getActive() {
return this.bot.isActive;
}
setStatus(status, error) {
this.bot.status = status;
this.bot.error = error;
}
async connect(bot) {
this.start();
}
async disconnect(bot) {
this.stop();
}
}
Adapter2.WsClient = WsClient;
})(Adapter || (Adapter = {}));
// src/message.ts
var import_element2 = __toESM(require("@satorijs/element"), 1);
var AggregateError = class extends Error {
constructor(errors, message = "") {
super(message);
this.errors = errors;
}
static {
__name(this, "AggregateError");
}
};
var MessageEncoder = class {
constructor(bot, channelId, referrer, options = {}) {
this.bot = bot;
this.channelId = channelId;
this.referrer = referrer;
this.options = options;
}
static {
__name(this, "MessageEncoder");
}
errors = [];
results = [];
session;
async prepare() {
}
async render(elements, flush) {
for (const element of elements) {
await this.visit(element);
}
if (flush) {
await this.flush();
}
}
async send(content) {
this.session = this.bot.session({
type: "send",
channel: { id: this.channelId, ...this.options.session?.event.channel },
guild: this.options.session?.event.guild
});
for (const key in this.options.session || {}) {
if (key === "id" || key === "event") continue;
this.session[key] = this.options.session[key];
}
await this.prepare();
const session = this.options.session ?? this.session;
this.session.elements = await session.transform(import_element2.default.normalize(content));
const btns = import_element2.default.select(this.session.elements, "button").filter((v) => v.attrs.type !== "link" && !v.attrs.id);
for (const btn of btns) {
const r = Math.random().toString(36).slice(2);
btn.attrs.id ||= r;
if (typeof btn.attrs.action === "function") this.bot.callbacks[btn.attrs.id] = btn.attrs.action;
}
if (await this.session.app.serial(this.session, "before-send", this.session, this.options)) return [];
await this.render(this.session.elements);
await this.flush();
if (this.errors.length) {
throw new AggregateError(this.errors);
} else {
return this.results;
}
}
};
// src/index.ts
import_element3.default.warn = new import_cordis5.Logger("element").warn;
import_plugin_http.HTTP.createConfig = /* @__PURE__ */ __name(function createConfig(endpoint) {
return import_cordis5.z.object({
endpoint: import_cordis5.z.string().role("link").description("要连接的服务器地址。").default(typeof endpoint === "string" ? endpoint : void 0).required(typeof endpoint === "boolean" ? endpoint : false),
headers: import_cordis5.z.dict(String).role("table").description("要附加的额外请求头。"),
...this.Config.dict
}).description("请求设置");
}, "createConfig");
var SatoriContext = class extends import_cordis5.Context {
static {
__name(this, "SatoriContext");
}
constructor(config) {
super(config);
this.provide("satori", void 0, true);
this.plugin(Satori);
}
};
var DisposableSet = class {
constructor(ctx) {
this.ctx = ctx;
(0, import_cosmokit5.defineProperty)(this, import_cordis5.Service.tracker, {
property: "ctx"
});
}
static {
__name(this, "DisposableSet");
}
sn = 0;
map1 = /* @__PURE__ */ new Map();
map2 = /* @__PURE__ */ new Map();
add(...values) {
const sn = ++this.sn;
return this.ctx.effect(() => {
let hasUpdate = false;
for (const value of values) {
if (!this.map2.has(value)) {
this.map2.set(value, /* @__PURE__ */ new Set());
hasUpdate = true;
}
this.map2.get(value).add(sn);
}
this.map1.set(sn, values);
if (hasUpdate) this.ctx.emit("satori/meta");
return () => {
let hasUpdate2 = false;
this.map1.delete(sn);
for (const value of values) {
this.map2.get(value).delete(sn);
if (this.map2.get(value).size === 0) {
this.map2.delete(value);
hasUpdate2 = true;
}
}
if (hasUpdate2) this.ctx.emit("satori/meta");
};
});
}
[Symbol.iterator]() {
return new Set([].concat(...this.map1.values()))[Symbol.iterator]();
}
};
var Satori = class extends import_cordis5.Service {
static {
__name(this, "Satori");
}
static [import_cordis5.Service.provide] = "satori";
static [import_cordis5.Service.immediate] = true;
uid = Math.random().toString(36).slice(2);
proxyUrls = new DisposableSet(this.ctx);
_internalRouter;
_tempStore = /* @__PURE__ */ Object.create(null);
_loginSeq = 0;
_sessionSeq = 0;
constructor(ctx) {
super(ctx);
ctx.mixin("satori", ["bots", "component"]);
(0, import_cosmokit5.defineProperty)(this.bots, import_cordis5.Service.tracker, {});
const self = this;
ctx.on("http/file", async function(_url, options) {
const url = new URL(_url);
if (url.protocol !== "internal:") return;
const { status, body, headers } = await self.handleInternalRoute("GET", url);
if (status >= 400) throw new Error(`Failed to fetch ${_url}, status code: ${status}`);
if (status >= 300) {
const location = headers?.get("location");
return this.file(location, options);
}
const type = headers?.get("content-type");
const filename = headers?.get("content-disposition")?.split("filename=")[1];
return { data: body, filename, type, mime: type };
});
this._internalRouter = new InternalRouter(ctx);
this.defineInternalRoute("/_tmp/:id", async ({ params, method }) => {
if (method !== "GET") return { status: 405 };
return this._tempStore[params.id] ?? { status: 404 };
});
this.defineInternalRoute("/_api/:name", async ({ bot, headers, params, method, body }) => {
if (method !== "POST") return { status: 405 };
const args = await JsonForm.decode({ body, headers: new Headers(headers) });
if (!args) return { status: 400 };
try {
let root = bot.internal;
for (const part of params.name.split(".")) {
root = root[part];
}
let result = root(...args);
if (headers["satori-pagination"]) {
if (!result?.[/* @__PURE__ */ Symbol.for("satori.pagination")]) {
return { status: 400, statusText: "This API does not support pagination" };
}
result = await result[/* @__PURE__ */ Symbol.for("satori.pagination")]();
} else {
result = await result;
}
return { ...await JsonForm.encode(result), status: 200 };
} catch (error) {
if (!ctx.http.isError(error) || !error.response) throw error;
return error.response;
}
});
}
bots = new Proxy([], {
get(target, prop) {
if (prop in target || typeof prop === "symbol") {
return Reflect.get(target, prop);
}
return target.find((bot) => bot.sid === prop);
},
deleteProperty(target, prop) {
if (prop in target || typeof prop === "symbol") {
return Reflect.deleteProperty(target, prop);
}
const bot = target.findIndex((bot2) => bot2.sid === prop);
if (bot < 0) return true;
target.splice(bot, 1);
return true;
}
});
component(name, component, options = {}) {
const render = /* @__PURE__ */ __name(async (attrs, children, session) => {
if (options.session && session.type === "send") {
throw new Error("interactive components is not available outside sessions");
}
const result = await component(attrs, children, session);
return session.transform(import_element3.default.normalize(result));
}, "render");
return this.ctx.set("component:" + name, render);
}
defineInternalRoute(path, callback) {
return this._internalRouter.define(path, callback);
}
async handleInternalRoute(method, url, headers = new Headers(), body) {
const capture = /^([^/]+)\/([^/]+)(\/.+)$/.exec(url.pathname);
if (!capture) return { status: 400 };
const [, platform, selfId, path] = capture;
const bot = this.bots[`${platform}:${selfId}`];
if (!bot) return { status: 404 };
let response = await this._internalRouter.handle(bot, method, path, url.searchParams, headers, body);
response ??= await bot._internalRouter.handle(bot, method, path, url.searchParams, headers, body);
if (!response) return { status: 404 };
return response;
}
toJSON(meta = false) {
return {
logins: meta ? void 0 : this.bots.map((bot) => bot.toJSON()),
proxyUrls: [...this.proxyUrls]
};
}
};
var index_default = Satori;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Adapter,
Bot,
Context,
Element,
HTTP,
InternalRouter,
JsonForm,
MessageEncoder,
Messenger,
Modulator,
Quester,
Satori,
Session,
Universal,
defineAccessor,
h,
segment,
...require("cordis"),
...require("cosmokit")
});