koishi-plugin-memesluna 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -5,8 +5,8 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __getProtoOf = Object.getPrototypeOf;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
7
  var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
8
+ for (var name2 in all)
9
+ __defProp(target, name2, { get: all[name2], enumerable: true });
10
10
  };
11
11
  var __copyProps = (to, from, except, desc) => {
12
12
  if (from && typeof from === "object" || typeof from === "function") {
@@ -16,7 +16,6 @@ var __copyProps = (to, from, except, desc) => {
16
16
  }
17
17
  return to;
18
18
  };
19
- var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
20
19
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
20
  // If the importer is in node compatibility mode or this is not an ESM
22
21
  // file that has been converted to a CommonJS file using a Babel-
@@ -26,18 +25,518 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
25
  mod
27
26
  ));
28
27
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.ts
29
30
  var index_exports = {};
30
31
  __export(index_exports, {
32
+ Config: () => Config,
33
+ MemesLunaService: () => MemesLunaService,
31
34
  apply: () => apply,
32
- inject: () => inject
35
+ inject: () => inject,
36
+ name: () => name
33
37
  });
34
38
  module.exports = __toCommonJS(index_exports);
39
+ var import_promises2 = __toESM(require("fs/promises"));
40
+ var import_path2 = __toESM(require("path"));
41
+
42
+ // src/service.ts
43
+ var import_crypto = require("crypto");
35
44
  var import_promises = __toESM(require("fs/promises"));
36
45
  var import_path = __toESM(require("path"));
37
- var import_service = require("./service");
38
- __reExport(index_exports, require("./config"), module.exports);
39
- __reExport(index_exports, require("./service"), module.exports);
40
- const RESERVED_PATHS = /* @__PURE__ */ new Set([
46
+ var import_koishi = require("koishi");
47
+ var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([
48
+ ".jpg",
49
+ ".jpeg",
50
+ ".png",
51
+ ".gif",
52
+ ".bmp",
53
+ ".webp",
54
+ ".svg",
55
+ ".tif",
56
+ ".tiff",
57
+ ".avif",
58
+ ".psd"
59
+ ]);
60
+ var COLLECTION_NAME_REGEXP = /^[a-zA-Z0-9_-]+$/;
61
+ var ENDPOINT_NAME_REGEXP = /^[a-zA-Z0-9_-]+$/;
62
+ var MemesLunaService = class extends import_koishi.Service {
63
+ constructor(ctx, config) {
64
+ super(ctx, "memesluna", true);
65
+ this.config = config;
66
+ this.defineDatabase();
67
+ this._readyPromise = new Promise((resolve) => {
68
+ this._readyResolve = resolve;
69
+ });
70
+ ctx.on("ready", async () => {
71
+ await this.ensureStorage();
72
+ this._readyResolve();
73
+ });
74
+ }
75
+ _readyPromise;
76
+ _readyResolve;
77
+ static inject = ["database"];
78
+ get ready() {
79
+ return this._readyPromise;
80
+ }
81
+ defineDatabase() {
82
+ this.ctx.database.extend(
83
+ "memesluna_endpoints",
84
+ {
85
+ id: "string",
86
+ name: "string",
87
+ group: "string",
88
+ description: "string",
89
+ url: "string",
90
+ method: "string",
91
+ url_construction: "string",
92
+ model_name: "string",
93
+ query_params: "string",
94
+ proxy_settings: "string",
95
+ created_at: "timestamp",
96
+ updated_at: "timestamp"
97
+ },
98
+ {
99
+ primary: "id",
100
+ unique: ["name"]
101
+ }
102
+ );
103
+ }
104
+ getStorageRoot() {
105
+ return import_path.default.resolve(this.ctx.baseDir, this.config.storagePath);
106
+ }
107
+ async ensureStorage() {
108
+ await import_promises.default.mkdir(this.getStorageRoot(), { recursive: true });
109
+ }
110
+ ensureCollectionName(name2) {
111
+ if (!name2 || !COLLECTION_NAME_REGEXP.test(name2)) {
112
+ throw new Error("Invalid collection name: only letters, numbers, _ and - are allowed.");
113
+ }
114
+ }
115
+ ensureEndpointName(name2) {
116
+ if (!name2 || !ENDPOINT_NAME_REGEXP.test(name2)) {
117
+ throw new Error("Invalid endpoint name: only letters, numbers, _ and - are allowed.");
118
+ }
119
+ }
120
+ getCollectionDir(collectionName) {
121
+ return import_path.default.join(this.getStorageRoot(), collectionName);
122
+ }
123
+ getCollectionLinksFile(collectionName) {
124
+ return import_path.default.join(this.getCollectionDir(collectionName), `${collectionName}.txt`);
125
+ }
126
+ getCollectionDescriptionFile(collectionName) {
127
+ return import_path.default.join(this.getCollectionDir(collectionName), ".description");
128
+ }
129
+ async getCollectionDescription(collectionName) {
130
+ try {
131
+ return (await import_promises.default.readFile(this.getCollectionDescriptionFile(collectionName), "utf8")).trim();
132
+ } catch {
133
+ return "";
134
+ }
135
+ }
136
+ async setCollectionDescription(collectionName, description) {
137
+ if (!await this.collectionExists(collectionName)) {
138
+ return false;
139
+ }
140
+ await import_promises.default.writeFile(this.getCollectionDescriptionFile(collectionName), description.trim(), "utf8");
141
+ return true;
142
+ }
143
+ async collectionExists(collectionName) {
144
+ const dir = this.getCollectionDir(collectionName);
145
+ try {
146
+ const stat = await import_promises.default.stat(dir);
147
+ return stat.isDirectory();
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+ async getCollections() {
153
+ const root = this.getStorageRoot();
154
+ try {
155
+ const entries = await import_promises.default.readdir(root, { withFileTypes: true });
156
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
157
+ } catch {
158
+ return [];
159
+ }
160
+ }
161
+ async createCollection(collectionName) {
162
+ this.ensureCollectionName(collectionName);
163
+ const dir = this.getCollectionDir(collectionName);
164
+ try {
165
+ await import_promises.default.mkdir(dir);
166
+ return true;
167
+ } catch (error) {
168
+ if (error.code === "EEXIST") {
169
+ return false;
170
+ }
171
+ throw error;
172
+ }
173
+ }
174
+ async deleteCollection(collectionName) {
175
+ this.ensureCollectionName(collectionName);
176
+ const dir = this.getCollectionDir(collectionName);
177
+ try {
178
+ await import_promises.default.rm(dir, { recursive: true, force: true });
179
+ return true;
180
+ } catch {
181
+ return false;
182
+ }
183
+ }
184
+ isImageFile(filename) {
185
+ const ext = import_path.default.extname(filename).toLowerCase();
186
+ return IMAGE_EXTENSIONS.has(ext);
187
+ }
188
+ ensureSafeImageFilename(filename) {
189
+ const normalized = import_path.default.basename(filename || "");
190
+ if (!filename || normalized !== filename || filename.includes("/") || filename.includes("\\")) {
191
+ throw new Error("Invalid image filename");
192
+ }
193
+ if (!this.isImageFile(normalized)) {
194
+ throw new Error("Invalid image filename");
195
+ }
196
+ return normalized;
197
+ }
198
+ getMimeByFilename(filename) {
199
+ const ext = import_path.default.extname(filename).toLowerCase();
200
+ if (ext === ".png") return "image/png";
201
+ if (ext === ".gif") return "image/gif";
202
+ if (ext === ".webp") return "image/webp";
203
+ if (ext === ".bmp") return "image/bmp";
204
+ if (ext === ".svg") return "image/svg+xml";
205
+ if (ext === ".avif") return "image/avif";
206
+ if (ext === ".tif" || ext === ".tiff") return "image/tiff";
207
+ return "image/jpeg";
208
+ }
209
+ resolveLocalImagePath(collectionName, filename) {
210
+ const safeName = this.ensureSafeImageFilename(filename);
211
+ return import_path.default.join(this.getCollectionDir(collectionName), safeName);
212
+ }
213
+ async getLocalImageBuffer(collectionName, filename) {
214
+ if (!await this.collectionExists(collectionName)) {
215
+ return null;
216
+ }
217
+ let fullPath;
218
+ try {
219
+ fullPath = this.resolveLocalImagePath(collectionName, filename);
220
+ } catch {
221
+ return null;
222
+ }
223
+ try {
224
+ const buffer = await import_promises.default.readFile(fullPath);
225
+ return {
226
+ buffer,
227
+ mime: this.getMimeByFilename(fullPath)
228
+ };
229
+ } catch {
230
+ return null;
231
+ }
232
+ }
233
+ async getCollectionImages(collectionName) {
234
+ if (!await this.collectionExists(collectionName)) {
235
+ return [];
236
+ }
237
+ const dir = this.getCollectionDir(collectionName);
238
+ const entries = await import_promises.default.readdir(dir, { withFileTypes: true });
239
+ return entries.filter((entry) => entry.isFile() && this.isImageFile(entry.name)).map((entry) => entry.name).sort();
240
+ }
241
+ async getCollectionLinks(collectionName) {
242
+ if (!await this.collectionExists(collectionName)) {
243
+ return [];
244
+ }
245
+ const linksPath = this.getCollectionLinksFile(collectionName);
246
+ try {
247
+ const text = await import_promises.default.readFile(linksPath, "utf8");
248
+ return text.split(/\r?\n/g).map((line) => line.trim()).filter((line) => line.startsWith("http://") || line.startsWith("https://"));
249
+ } catch {
250
+ return [];
251
+ }
252
+ }
253
+ async addLinksToCollection(collectionName, links) {
254
+ if (!await this.collectionExists(collectionName)) {
255
+ throw new Error(`Collection not found: ${collectionName}`);
256
+ }
257
+ const normalized = links.map((link) => link.trim()).filter((link) => link.startsWith("http://") || link.startsWith("https://"));
258
+ if (!normalized.length) {
259
+ return 0;
260
+ }
261
+ const current = await this.getCollectionLinks(collectionName);
262
+ const merged = [...current];
263
+ for (const link of normalized) {
264
+ if (!merged.includes(link)) {
265
+ merged.push(link);
266
+ }
267
+ }
268
+ const linksPath = this.getCollectionLinksFile(collectionName);
269
+ await import_promises.default.writeFile(linksPath, `${merged.join("\n")}${merged.length ? "\n" : ""}`, "utf8");
270
+ return merged.length - current.length;
271
+ }
272
+ async removeLinkFromCollection(collectionName, link) {
273
+ if (!await this.collectionExists(collectionName)) {
274
+ return false;
275
+ }
276
+ const current = await this.getCollectionLinks(collectionName);
277
+ const next = current.filter((item) => item !== link);
278
+ if (next.length === current.length) {
279
+ return false;
280
+ }
281
+ const linksPath = this.getCollectionLinksFile(collectionName);
282
+ await import_promises.default.writeFile(linksPath, `${next.join("\n")}${next.length ? "\n" : ""}`, "utf8");
283
+ return true;
284
+ }
285
+ detectExtFromDataUrl(dataUrl) {
286
+ const matched = /^data:image\/([a-zA-Z0-9+.-]+);base64,/i.exec(dataUrl);
287
+ const ext = matched?.[1]?.toLowerCase();
288
+ if (!ext) return "png";
289
+ if (ext === "jpeg") return "jpg";
290
+ return ext;
291
+ }
292
+ normalizeBase64(input) {
293
+ const trimmed = input.trim();
294
+ if (trimmed.startsWith("data:")) {
295
+ const extHint = this.detectExtFromDataUrl(trimmed);
296
+ const index = trimmed.indexOf(",");
297
+ return {
298
+ base64: index >= 0 ? trimmed.slice(index + 1) : trimmed,
299
+ extHint
300
+ };
301
+ }
302
+ return { base64: trimmed };
303
+ }
304
+ buildSafeFilename(originalName, extHint) {
305
+ const fallbackName = `${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
306
+ const src = (originalName ?? fallbackName).trim();
307
+ const parsed = import_path.default.parse(src);
308
+ const sanitizedBase = (parsed.name || fallbackName).replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
309
+ const rawExt = (parsed.ext || (extHint ? `.${extHint}` : "") || ".png").toLowerCase();
310
+ const normalizedExt = rawExt === ".jpeg" ? ".jpg" : rawExt;
311
+ const finalExt = IMAGE_EXTENSIONS.has(normalizedExt) ? normalizedExt : ".png";
312
+ return `${sanitizedBase}${finalExt}`;
313
+ }
314
+ async deduplicateFilename(collectionDir, filename) {
315
+ const parsed = import_path.default.parse(filename);
316
+ let counter = 1;
317
+ let candidate = filename;
318
+ while (true) {
319
+ try {
320
+ await import_promises.default.access(import_path.default.join(collectionDir, candidate));
321
+ candidate = `${parsed.name}_${counter}${parsed.ext}`;
322
+ counter++;
323
+ } catch {
324
+ return candidate;
325
+ }
326
+ }
327
+ }
328
+ async addLocalImageBase64(collectionName, base64Data, originalName) {
329
+ if (!await this.collectionExists(collectionName)) {
330
+ throw new Error(`Collection not found: ${collectionName}`);
331
+ }
332
+ const { base64, extHint } = this.normalizeBase64(base64Data);
333
+ const buffer = Buffer.from(base64, "base64");
334
+ if (!buffer.length) {
335
+ throw new Error("Invalid image base64 payload");
336
+ }
337
+ const dir = this.getCollectionDir(collectionName);
338
+ const initialName = this.buildSafeFilename(originalName, extHint);
339
+ const finalName = await this.deduplicateFilename(dir, initialName);
340
+ await import_promises.default.writeFile(import_path.default.join(dir, finalName), buffer);
341
+ return finalName;
342
+ }
343
+ async deleteImageFromCollection(collectionName, filename) {
344
+ if (!await this.collectionExists(collectionName)) {
345
+ return false;
346
+ }
347
+ let safeName;
348
+ try {
349
+ safeName = this.ensureSafeImageFilename(filename);
350
+ } catch {
351
+ return false;
352
+ }
353
+ const fullPath = import_path.default.join(this.getCollectionDir(collectionName), safeName);
354
+ try {
355
+ await import_promises.default.unlink(fullPath);
356
+ return true;
357
+ } catch {
358
+ return false;
359
+ }
360
+ }
361
+ async moveImageToCollection(sourceCollection, targetCollection, filename) {
362
+ if (!await this.collectionExists(sourceCollection) || !await this.collectionExists(targetCollection)) {
363
+ return null;
364
+ }
365
+ let safeName;
366
+ try {
367
+ safeName = this.ensureSafeImageFilename(filename);
368
+ } catch {
369
+ return null;
370
+ }
371
+ const sourcePath = import_path.default.join(this.getCollectionDir(sourceCollection), safeName);
372
+ const targetDir = this.getCollectionDir(targetCollection);
373
+ const targetName = await this.deduplicateFilename(targetDir, safeName);
374
+ const targetPath = import_path.default.join(targetDir, targetName);
375
+ try {
376
+ await import_promises.default.rename(sourcePath, targetPath);
377
+ return targetName;
378
+ } catch {
379
+ return null;
380
+ }
381
+ }
382
+ async getCollectionInfo(collectionName) {
383
+ if (!await this.collectionExists(collectionName)) {
384
+ return null;
385
+ }
386
+ const localImages = await this.getCollectionImages(collectionName);
387
+ const links = await this.getCollectionLinks(collectionName);
388
+ const description = await this.getCollectionDescription(collectionName);
389
+ return {
390
+ name: collectionName,
391
+ description,
392
+ localCount: localImages.length,
393
+ linkCount: links.length,
394
+ totalCount: localImages.length + links.length,
395
+ hasContent: localImages.length > 0 || links.length > 0,
396
+ cover: localImages[0]
397
+ };
398
+ }
399
+ async getRandomResource(collectionName) {
400
+ if (!await this.collectionExists(collectionName)) {
401
+ return null;
402
+ }
403
+ const localImages = await this.getCollectionImages(collectionName);
404
+ const links = await this.getCollectionLinks(collectionName);
405
+ const pool = [
406
+ ...localImages.map((name2) => ({
407
+ type: "local",
408
+ value: import_path.default.join(this.getCollectionDir(collectionName), name2)
409
+ })),
410
+ ...links.map((link) => ({ type: "external", value: link }))
411
+ ];
412
+ if (!pool.length) {
413
+ return null;
414
+ }
415
+ return pool[Math.floor(Math.random() * pool.length)];
416
+ }
417
+ parseJsonField(value, fallback) {
418
+ if (!value) return fallback;
419
+ try {
420
+ return JSON.parse(value);
421
+ } catch {
422
+ return fallback;
423
+ }
424
+ }
425
+ mapEndpoint(row) {
426
+ return {
427
+ id: row.id,
428
+ name: row.name,
429
+ group: row.group || "\u9ED8\u8BA4\u5206\u7EC4",
430
+ description: row.description || "",
431
+ url: row.url,
432
+ method: row.method || "redirect",
433
+ urlConstruction: row.url_construction || "normal",
434
+ modelName: row.model_name || "",
435
+ queryParams: this.parseJsonField(row.query_params, []),
436
+ proxySettings: this.parseJsonField(row.proxy_settings, {
437
+ fallbackAction: "returnJson"
438
+ }),
439
+ createdAt: row.created_at,
440
+ updatedAt: row.updated_at
441
+ };
442
+ }
443
+ async getEndpoints() {
444
+ const rows = await this.ctx.database.get("memesluna_endpoints", {});
445
+ return rows.map((row) => this.mapEndpoint(row)).sort((a, b) => a.name.localeCompare(b.name));
446
+ }
447
+ async getEndpointByName(name2) {
448
+ const rows = await this.ctx.database.get("memesluna_endpoints", { name: name2 });
449
+ if (!rows.length) return null;
450
+ return this.mapEndpoint(rows[0]);
451
+ }
452
+ async addEndpoint(input) {
453
+ this.ensureEndpointName(input.name);
454
+ if (!input.url) {
455
+ throw new Error("Endpoint URL is required.");
456
+ }
457
+ const id = (0, import_crypto.randomUUID)();
458
+ const now = /* @__PURE__ */ new Date();
459
+ await this.ctx.database.create("memesluna_endpoints", {
460
+ id,
461
+ name: input.name,
462
+ group: input.group || "\u9ED8\u8BA4\u5206\u7EC4",
463
+ description: input.description || "",
464
+ url: input.url,
465
+ method: input.method || "redirect",
466
+ url_construction: input.urlConstruction || "normal",
467
+ model_name: input.modelName || "",
468
+ query_params: JSON.stringify(input.queryParams || []),
469
+ proxy_settings: JSON.stringify({
470
+ fallbackAction: "returnJson",
471
+ ...input.proxySettings || {}
472
+ }),
473
+ created_at: now,
474
+ updated_at: now
475
+ });
476
+ return id;
477
+ }
478
+ async updateEndpoint(name2, input) {
479
+ const current = await this.getEndpointByName(name2);
480
+ if (!current) {
481
+ return false;
482
+ }
483
+ const payload = {
484
+ updated_at: /* @__PURE__ */ new Date()
485
+ };
486
+ if (input.group !== void 0) payload.group = input.group || "\u9ED8\u8BA4\u5206\u7EC4";
487
+ if (input.description !== void 0) payload.description = input.description || "";
488
+ if (input.url !== void 0) payload.url = input.url;
489
+ if (input.method !== void 0) payload.method = input.method;
490
+ if (input.urlConstruction !== void 0) payload.url_construction = input.urlConstruction;
491
+ if (input.modelName !== void 0) payload.model_name = input.modelName;
492
+ if (input.queryParams !== void 0) payload.query_params = JSON.stringify(input.queryParams);
493
+ if (input.proxySettings !== void 0) payload.proxy_settings = JSON.stringify(input.proxySettings);
494
+ await this.ctx.database.set("memesluna_endpoints", { name: name2 }, payload);
495
+ return true;
496
+ }
497
+ async deleteEndpoint(name2) {
498
+ const before = await this.ctx.database.get("memesluna_endpoints", { name: name2 });
499
+ if (!before.length) {
500
+ return false;
501
+ }
502
+ await this.ctx.database.remove("memesluna_endpoints", { name: name2 });
503
+ return true;
504
+ }
505
+ async buildRouteInventory(backendPath) {
506
+ const endpoints = await this.getEndpoints();
507
+ const collections = await this.getCollections();
508
+ const lines = [];
509
+ for (const endpoint of endpoints) {
510
+ const queryPart = endpoint.queryParams.filter((param) => param.required).map((param) => `${param.name}=<${param.name}>`).join("&");
511
+ const suffix = queryPart ? `?${queryPart}` : "";
512
+ const desc = endpoint.description || endpoint.name;
513
+ lines.push(`- ${endpoint.name} ${desc} ${backendPath}/${endpoint.name}${suffix}`);
514
+ }
515
+ for (const collection of collections) {
516
+ const info = await this.getCollectionInfo(collection);
517
+ if (info?.hasContent) {
518
+ const desc = info.description || collection;
519
+ lines.push(`- ${collection} ${desc} ${backendPath}/${collection}`);
520
+ }
521
+ }
522
+ return lines.join("\n");
523
+ }
524
+ };
525
+
526
+ // src/config.ts
527
+ var import_koishi2 = require("koishi");
528
+ var Config = import_koishi2.Schema.object({
529
+ backendPath: import_koishi2.Schema.string().default("/memesluna").description("\u540E\u7AEF\u670D\u52A1\u8DEF\u5F84\u524D\u7F00"),
530
+ storagePath: import_koishi2.Schema.string().default("data/memesluna").description("\u672C\u5730\u5408\u96C6\u5B58\u50A8\u76EE\u5F55"),
531
+ selfUrl: import_koishi2.Schema.string().default("").description("\u670D\u52A1\u516C\u5F00\u5730\u5740\uFF0C\u4E0D\u586B\u5219\u4F18\u5148\u4F7F\u7528 server.selfUrl"),
532
+ injectVariables: import_koishi2.Schema.boolean().default(true).description("\u662F\u5426\u5411 ChatLuna \u6CE8\u5165 {{endpoint}} \u548C {{memesluna}} \u53D8\u91CF"),
533
+ variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("\u53D8\u91CF\u5237\u65B0\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09"),
534
+ injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`\u4F60\u53EF\u4EE5\u4F7F\u7528\u8868\u60C5\u5305\u6765\u4E30\u5BCC\u4F60\u7684\u56DE\u590D\u3002\u8868\u60C5\u5305\u5217\u8868\u662F{endpoint}\uFF0C\u57FA\u7840URL\u662F{base_url}\uFF0C\u4F60\u8981\u628A\u57FA\u7840URL\u62FC\u63A5\u5230\u8DEF\u5F84\u524D\u9762,\u4E0D\u8981\u52A0\u6587\u4EF6\u540D,\u53EA\u52A0\u8DEF\u5F84,\u7528\u53D1\u9001\u56FE\u7247\u7684\u65B9\u5F0F\u53D1\u9001\u3002`).description("\u6CE8\u5165\u5230 ChatLuna {{memesluna}} \u53D8\u91CF\u7684\u63D0\u793A\u8BCD\u6A21\u677F\uFF0C\u652F\u6301 {endpoint} \u548C {base_url} \u5360\u4F4D\u7B26")
535
+ });
536
+ var name = "memesluna";
537
+
538
+ // src/index.ts
539
+ var RESERVED_PATHS = /* @__PURE__ */ new Set([
41
540
  "config",
42
541
  "admin",
43
542
  "admin-login",
@@ -51,9 +550,9 @@ const RESERVED_PATHS = /* @__PURE__ */ new Set([
51
550
  "static",
52
551
  "favicon.ico"
53
552
  ]);
54
- const IMAGE_URL_REGEXP = /\.(jpeg|jpg|gif|png|webp|bmp|svg)(\?.*)?$/i;
55
- function isReservedPath(name) {
56
- return RESERVED_PATHS.has(name) || name.includes(".");
553
+ var IMAGE_URL_REGEXP = /\.(jpeg|jpg|gif|png|webp|bmp|svg)(\?.*)?$/i;
554
+ function isReservedPath(name2) {
555
+ return RESERVED_PATHS.has(name2) || name2.includes(".");
57
556
  }
58
557
  function getValueByDotNotation(obj, dotPath) {
59
558
  if (!dotPath) return void 0;
@@ -73,7 +572,7 @@ function normalizeContentType(contentType) {
73
572
  return contentType.toLowerCase().split(";")[0].trim();
74
573
  }
75
574
  function guessMimeByExt(filePath) {
76
- const ext = import_path.default.extname(filePath).toLowerCase();
575
+ const ext = import_path2.default.extname(filePath).toLowerCase();
77
576
  switch (ext) {
78
577
  case ".png":
79
578
  return "image/png";
@@ -229,23 +728,23 @@ async function applyDynamicForward(ctx, config, service, routeName, query) {
229
728
  const validated = new URLSearchParams();
230
729
  const errors = [];
231
730
  for (const param of endpoint.queryParams) {
232
- const name = param.name;
233
- const raw = query[name];
731
+ const name2 = param.name;
732
+ const raw = query[name2];
234
733
  const value = Array.isArray(raw) ? raw[0] : raw;
235
734
  if (typeof value === "string") {
236
735
  if (param.validValues && param.validValues.length > 0 && !param.validValues.includes(value)) {
237
- errors.push(`Invalid value for '${name}'`);
736
+ errors.push(`Invalid value for '${name2}'`);
238
737
  } else {
239
- validated.set(name, value);
738
+ validated.set(name2, value);
240
739
  }
241
740
  continue;
242
741
  }
243
742
  if (param.required) {
244
- errors.push(`Missing required parameter: ${name}`);
743
+ errors.push(`Missing required parameter: ${name2}`);
245
744
  continue;
246
745
  }
247
746
  if (param.defaultValue !== void 0) {
248
- validated.set(name, param.defaultValue);
747
+ validated.set(name2, param.defaultValue);
249
748
  }
250
749
  }
251
750
  if (errors.length > 0) {
@@ -278,7 +777,7 @@ async function applyDynamicForward(ctx, config, service, routeName, query) {
278
777
  if (resource.type === "external") {
279
778
  return { redirectTo: resource.value };
280
779
  }
281
- const fileBuffer = await import_promises.default.readFile(resource.value);
780
+ const fileBuffer = await import_promises2.default.readFile(resource.value);
282
781
  return {
283
782
  status: 200,
284
783
  body: fileBuffer,
@@ -352,7 +851,7 @@ function normalizeUrlConstruction(value) {
352
851
  async function buildAdminState(service) {
353
852
  const endpoints = await service.getEndpoints();
354
853
  const collectionNames = await service.getCollections();
355
- const collections = await Promise.all(collectionNames.map((name) => service.getCollectionInfo(name)));
854
+ const collections = await Promise.all(collectionNames.map((name2) => service.getCollectionInfo(name2)));
356
855
  return {
357
856
  endpoints,
358
857
  collectionNames,
@@ -588,6 +1087,9 @@ function buildHomepageHtml(basePath) {
588
1087
  <li class="nav-item">
589
1088
  <a class="nav-link" href="${basePath}/admin">\u7BA1\u7406</a>
590
1089
  </li>
1090
+ <li class="nav-item">
1091
+ <a class="nav-link" href="${basePath}/admin/endpoint">\u7AEF\u70B9</a>
1092
+ </li>
591
1093
  </ul>
592
1094
  </div>
593
1095
  </div>
@@ -1798,7 +2300,7 @@ async function updateMemesVariable(ctx, config, service) {
1798
2300
  function applyConsole(ctx, config, service) {
1799
2301
  if (!ctx.console) return;
1800
2302
  const consoleService = ctx.console;
1801
- const packageBase = import_path.default.resolve(ctx.baseDir, "node_modules/koishi-plugin-memesluna");
2303
+ const packageBase = import_path2.default.resolve(ctx.baseDir, "node_modules/koishi-plugin-memesluna");
1802
2304
  const withReady = (handler) => {
1803
2305
  return async (...args) => {
1804
2306
  await service.ready;
@@ -1806,8 +2308,8 @@ function applyConsole(ctx, config, service) {
1806
2308
  };
1807
2309
  };
1808
2310
  consoleService.addEntry({
1809
- dev: import_path.default.resolve(packageBase, "client/index.ts"),
1810
- prod: import_path.default.resolve(packageBase, "dist")
2311
+ dev: import_path2.default.resolve(packageBase, "client/index.ts"),
2312
+ prod: import_path2.default.resolve(packageBase, "dist")
1811
2313
  });
1812
2314
  consoleService.addListener(
1813
2315
  "memesluna/getState",
@@ -1815,7 +2317,7 @@ function applyConsole(ctx, config, service) {
1815
2317
  const endpoints = await service.getEndpoints();
1816
2318
  const collections = await service.getCollections();
1817
2319
  const detailedCollections = await Promise.all(
1818
- collections.map(async (name) => service.getCollectionInfo(name))
2320
+ collections.map(async (name2) => service.getCollectionInfo(name2))
1819
2321
  );
1820
2322
  return {
1821
2323
  backendPath: config.backendPath,
@@ -1826,20 +2328,20 @@ function applyConsole(ctx, config, service) {
1826
2328
  );
1827
2329
  consoleService.addListener(
1828
2330
  "memesluna/createCollection",
1829
- withReady(async (name) => {
1830
- return await service.createCollection(name);
2331
+ withReady(async (name2) => {
2332
+ return await service.createCollection(name2);
1831
2333
  })
1832
2334
  );
1833
2335
  consoleService.addListener(
1834
2336
  "memesluna/deleteCollection",
1835
- withReady(async (name) => {
1836
- return await service.deleteCollection(name);
2337
+ withReady(async (name2) => {
2338
+ return await service.deleteCollection(name2);
1837
2339
  })
1838
2340
  );
1839
2341
  consoleService.addListener(
1840
2342
  "memesluna/setCollectionDescription",
1841
- withReady(async (name, description) => {
1842
- return await service.setCollectionDescription(name, description);
2343
+ withReady(async (name2, description) => {
2344
+ return await service.setCollectionDescription(name2, description);
1843
2345
  })
1844
2346
  );
1845
2347
  consoleService.addListener(
@@ -1881,14 +2383,14 @@ function applyConsole(ctx, config, service) {
1881
2383
  );
1882
2384
  consoleService.addListener(
1883
2385
  "memesluna/updateEndpoint",
1884
- withReady(async (name, payload) => {
1885
- return await service.updateEndpoint(name, payload);
2386
+ withReady(async (name2, payload) => {
2387
+ return await service.updateEndpoint(name2, payload);
1886
2388
  })
1887
2389
  );
1888
2390
  consoleService.addListener(
1889
2391
  "memesluna/deleteEndpoint",
1890
- withReady(async (name) => {
1891
- return await service.deleteEndpoint(name);
2392
+ withReady(async (name2) => {
2393
+ return await service.deleteEndpoint(name2);
1892
2394
  })
1893
2395
  );
1894
2396
  consoleService.addListener("memesluna/getBaseUrl", async () => {
@@ -1902,7 +2404,7 @@ function applyServer(ctx, config, service) {
1902
2404
  const baseUrl = toAbsoluteBaseUrl(ctx, config);
1903
2405
  const endpoints = await service.getEndpoints();
1904
2406
  const collections = await service.getCollections();
1905
- const collectionInfos = await Promise.all(collections.map((name) => service.getCollectionInfo(name)));
2407
+ const collectionInfos = await Promise.all(collections.map((name2) => service.getCollectionInfo(name2)));
1906
2408
  const inventory = await service.buildRouteInventory(basePath);
1907
2409
  const llmPrompt = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- \u6682\u65E0\u53EF\u7528\u8DEF\u7531").replace("{base_url}", baseUrl);
1908
2410
  koa.body = {
@@ -1917,14 +2419,14 @@ function applyServer(ctx, config, service) {
1917
2419
  });
1918
2420
  ctx.server.post(`${basePath}/api/admin/collections`, async (koa) => {
1919
2421
  const body = getRequestBody(koa);
1920
- const name = toTrimmedString(body.name);
1921
- if (!name) {
2422
+ const name2 = toTrimmedString(body.name);
2423
+ if (!name2) {
1922
2424
  koa.status = 400;
1923
2425
  koa.body = { error: "Collection name is required" };
1924
2426
  return;
1925
2427
  }
1926
2428
  try {
1927
- const created = await service.createCollection(name);
2429
+ const created = await service.createCollection(name2);
1928
2430
  if (!created) {
1929
2431
  koa.status = 409;
1930
2432
  koa.body = { error: "Collection already exists" };
@@ -1937,13 +2439,13 @@ function applyServer(ctx, config, service) {
1937
2439
  }
1938
2440
  });
1939
2441
  ctx.server.delete(`${basePath}/api/admin/collections/:name`, async (koa) => {
1940
- const name = toTrimmedString(koa.params.name);
1941
- if (!name) {
2442
+ const name2 = toTrimmedString(koa.params.name);
2443
+ if (!name2) {
1942
2444
  koa.status = 400;
1943
2445
  koa.body = { error: "Collection name is required" };
1944
2446
  return;
1945
2447
  }
1946
- const deleted = await service.deleteCollection(name);
2448
+ const deleted = await service.deleteCollection(name2);
1947
2449
  if (!deleted) {
1948
2450
  koa.status = 404;
1949
2451
  koa.body = { error: "Collection not found" };
@@ -1952,10 +2454,10 @@ function applyServer(ctx, config, service) {
1952
2454
  koa.body = { ok: true };
1953
2455
  });
1954
2456
  ctx.server.patch(`${basePath}/api/admin/collections/:name/description`, async (koa) => {
1955
- const name = toTrimmedString(koa.params.name);
2457
+ const name2 = toTrimmedString(koa.params.name);
1956
2458
  const body = getRequestBody(koa);
1957
2459
  const description = toTrimmedString(body.description);
1958
- const updated = await service.setCollectionDescription(name, description);
2460
+ const updated = await service.setCollectionDescription(name2, description);
1959
2461
  if (!updated) {
1960
2462
  koa.status = 404;
1961
2463
  koa.body = { error: "Collection not found" };
@@ -2066,15 +2568,15 @@ function applyServer(ctx, config, service) {
2066
2568
  });
2067
2569
  ctx.server.post(`${basePath}/api/admin/endpoints`, async (koa) => {
2068
2570
  const body = getRequestBody(koa);
2069
- const name = toTrimmedString(body.name);
2571
+ const name2 = toTrimmedString(body.name);
2070
2572
  const url = toTrimmedString(body.url);
2071
- if (!name || !url) {
2573
+ if (!name2 || !url) {
2072
2574
  koa.status = 400;
2073
2575
  koa.body = { error: "name and url are required" };
2074
2576
  return;
2075
2577
  }
2076
2578
  const payload = {
2077
- name,
2579
+ name: name2,
2078
2580
  group: toTrimmedString(body.group) || "\u9ED8\u8BA4\u5206\u7EC4",
2079
2581
  description: toTrimmedString(body.description),
2080
2582
  url,
@@ -2116,8 +2618,8 @@ function applyServer(ctx, config, service) {
2116
2618
  koa.body = { ok: true };
2117
2619
  });
2118
2620
  ctx.server.delete(`${basePath}/api/admin/endpoints/:name`, async (koa) => {
2119
- const name = toTrimmedString(koa.params.name);
2120
- const deleted = await service.deleteEndpoint(name);
2621
+ const name2 = toTrimmedString(koa.params.name);
2622
+ const deleted = await service.deleteEndpoint(name2);
2121
2623
  if (!deleted) {
2122
2624
  koa.status = 404;
2123
2625
  koa.body = { error: "Endpoint not found" };
@@ -2126,10 +2628,14 @@ function applyServer(ctx, config, service) {
2126
2628
  koa.body = { ok: true };
2127
2629
  });
2128
2630
  ctx.server.get(`${basePath}/admin`, async (koa) => {
2129
- koa.redirect(`${basePath}/#admin`);
2631
+ koa.status = 200;
2632
+ koa.set("Content-Type", "text/html; charset=utf-8");
2633
+ koa.body = buildAdminHtml(basePath);
2130
2634
  });
2131
2635
  ctx.server.get(`${basePath}/admin/endpoint`, async (koa) => {
2132
- koa.redirect(`${basePath}/#endpoint`);
2636
+ koa.status = 200;
2637
+ koa.set("Content-Type", "text/html; charset=utf-8");
2638
+ koa.body = buildAdminEndpointHtml(basePath);
2133
2639
  });
2134
2640
  ctx.server.get(`${basePath}/api/collections/:name/resources`, async (koa) => {
2135
2641
  const collectionName = koa.params.name;
@@ -2164,7 +2670,7 @@ function applyServer(ctx, config, service) {
2164
2670
  });
2165
2671
  }
2166
2672
  function apply(ctx, config) {
2167
- ctx.plugin(import_service.MemesLunaService, config);
2673
+ ctx.plugin(MemesLunaService, config);
2168
2674
  ctx.inject(["memesluna", "server"], async (ctx2) => {
2169
2675
  const service = ctx2.memesluna;
2170
2676
  await service.ready;
@@ -2216,11 +2722,12 @@ function apply(ctx, config) {
2216
2722
  });
2217
2723
  }
2218
2724
  }
2219
- const inject = ["database", "chatluna", "server"];
2725
+ var inject = ["database", "chatluna", "server"];
2220
2726
  // Annotate the CommonJS export names for ESM import in node:
2221
2727
  0 && (module.exports = {
2728
+ Config,
2729
+ MemesLunaService,
2222
2730
  apply,
2223
2731
  inject,
2224
- ...require("./config"),
2225
- ...require("./service")
2732
+ name
2226
2733
  });