koishi-plugin-memesluna 0.2.3 → 0.2.5
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/dist/index.js +1 -1
- package/lib/index.js +593 -42
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{icons as r}from"@koishijs/client";import{h as e}from"vue";const o="M1024 512C1024 229.230208 794.769792 0 512 0 229.230208 0 0 229.230208 0 512 0 794.769792 229.230208 1024 512 1024 629.410831 1024 740.826187 984.331046 830.768465 912.686662 841.557579 904.092491 843.33693 888.379234 834.742758 877.590121 826.148587 866.801009 810.43533 865.021658 799.646219 873.615827 718.470035 938.277495 618.001779 974.048781 512 974.048781 256.817504 974.048781 49.951219 767.182496 49.951219 512 49.951219 256.817504 256.817504 49.951219 512 49.951219 767.182496 49.951219 974.048781 256.817504 974.048781 512 974.048781 599.492834 949.714859 683.336764 904.470807 755.960693 897.177109 767.668243 900.755245 783.071797 912.462793 790.365493 924.170342 797.659191 939.573897 794.081058 946.867595 782.373508 997.013826 701.880796 1024 608.898379 1024 512ZM337.170731 499.512194C371.654852 499.512194 399.609756 471.557291 399.609756 437.073171 399.609756 402.58905 371.654852 374.634146 337.170731 374.634146 302.686611 374.634146 274.731708 402.58905 274.731708 437.073171 274.731708 471.557291 302.686611 499.512194 337.170731 499.512194ZM711.804879 499.512194C746.288998 499.512194 774.243902 471.557291 774.243902 437.073171 774.243902 402.58905 746.288998 374.634146 711.804879 374.634146 677.320757 374.634146 649.365854 402.58905 649.365854 437.073171 649.365854 471.557291 677.320757 499.512194 711.804879 499.512194ZM352.788105 768.770967C396.165222 803.472661 453.151987 824.195121 524.487806 824.195121 595.823622 824.195121 652.810387 803.472661 696.187505 768.770967 722.700531 747.560546 738.882517 725.984565 746.631548 710.486505 752.800254 698.149092 747.799529 683.146916 735.462114 676.978208 723.124702 670.809502 708.122526 675.810227 701.953818 688.147642 701.03616 689.982957 698.492224 694.172969 694.165854 699.941463 686.602473 710.025971 676.927317 720.210345 664.983226 729.765617 630.311565 757.502948 584.273939 774.243902 524.487806 774.243902 464.70167 774.243902 418.664045 757.502948 383.992384 729.765617 372.048292 720.210345 362.373137 710.025971 354.809756 699.941463 350.483386 694.172969 347.93945 689.982957 347.021792 688.147642 340.853084 675.810227 325.850908 670.809502 313.513495 676.978208 301.176081 683.146916 296.175356 698.149092 302.344062 710.486505 310.093092 725.984565 326.275078 747.560546 352.788105 768.770967Z",a={name:"MemesLunaIcon",render(){return e("svg",{class:"k-icon",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg"},[e("path",{fill:"currentColor",d:o})])}},s={name:"MemesLunaDashboard",render(){return e("iframe",{src:"/memesluna/",style:"width: 100%; height: calc(100vh - 120px); border: 0; background: transparent;"})}};r.register("MemesLunaEmoji",a);const
|
|
1
|
+
import{icons as r}from"@koishijs/client";import{h as e}from"vue";const o="M1024 512C1024 229.230208 794.769792 0 512 0 229.230208 0 0 229.230208 0 512 0 794.769792 229.230208 1024 512 1024 629.410831 1024 740.826187 984.331046 830.768465 912.686662 841.557579 904.092491 843.33693 888.379234 834.742758 877.590121 826.148587 866.801009 810.43533 865.021658 799.646219 873.615827 718.470035 938.277495 618.001779 974.048781 512 974.048781 256.817504 974.048781 49.951219 767.182496 49.951219 512 49.951219 256.817504 256.817504 49.951219 512 49.951219 767.182496 49.951219 974.048781 256.817504 974.048781 512 974.048781 599.492834 949.714859 683.336764 904.470807 755.960693 897.177109 767.668243 900.755245 783.071797 912.462793 790.365493 924.170342 797.659191 939.573897 794.081058 946.867595 782.373508 997.013826 701.880796 1024 608.898379 1024 512ZM337.170731 499.512194C371.654852 499.512194 399.609756 471.557291 399.609756 437.073171 399.609756 402.58905 371.654852 374.634146 337.170731 374.634146 302.686611 374.634146 274.731708 402.58905 274.731708 437.073171 274.731708 471.557291 302.686611 499.512194 337.170731 499.512194ZM711.804879 499.512194C746.288998 499.512194 774.243902 471.557291 774.243902 437.073171 774.243902 402.58905 746.288998 374.634146 711.804879 374.634146 677.320757 374.634146 649.365854 402.58905 649.365854 437.073171 649.365854 471.557291 677.320757 499.512194 711.804879 499.512194ZM352.788105 768.770967C396.165222 803.472661 453.151987 824.195121 524.487806 824.195121 595.823622 824.195121 652.810387 803.472661 696.187505 768.770967 722.700531 747.560546 738.882517 725.984565 746.631548 710.486505 752.800254 698.149092 747.799529 683.146916 735.462114 676.978208 723.124702 670.809502 708.122526 675.810227 701.953818 688.147642 701.03616 689.982957 698.492224 694.172969 694.165854 699.941463 686.602473 710.025971 676.927317 720.210345 664.983226 729.765617 630.311565 757.502948 584.273939 774.243902 524.487806 774.243902 464.70167 774.243902 418.664045 757.502948 383.992384 729.765617 372.048292 720.210345 362.373137 710.025971 354.809756 699.941463 350.483386 694.172969 347.93945 689.982957 347.021792 688.147642 340.853084 675.810227 325.850908 670.809502 313.513495 676.978208 301.176081 683.146916 296.175356 698.149092 302.344062 710.486505 310.093092 725.984565 326.275078 747.560546 352.788105 768.770967Z",a={name:"MemesLunaIcon",render(){return e("svg",{class:"k-icon",viewBox:"0 0 1024 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg"},[e("path",{fill:"currentColor",d:o})])}},s={name:"MemesLunaDashboard",render(){return e("iframe",{src:"/memesluna/",style:"width: 100%; height: calc(100vh - 120px); border: 0; background: transparent;"})}};r.register("MemesLunaEmoji",a);const c=n=>{n.page({name:"MemesLuna",path:"/memesluna/",icon:"MemesLunaEmoji",component:s,authority:3,order:500})};export{c as default};
|
package/lib/index.js
CHANGED
|
@@ -4,6 +4,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
8
|
var __export = (target, all) => {
|
|
8
9
|
for (var name2 in all)
|
|
9
10
|
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
@@ -72,6 +73,9 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
72
73
|
this._readyResolve();
|
|
73
74
|
});
|
|
74
75
|
}
|
|
76
|
+
static {
|
|
77
|
+
__name(this, "MemesLunaService");
|
|
78
|
+
}
|
|
75
79
|
_readyPromise;
|
|
76
80
|
_readyResolve;
|
|
77
81
|
static inject = ["database"];
|
|
@@ -426,7 +430,7 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
426
430
|
return {
|
|
427
431
|
id: row.id,
|
|
428
432
|
name: row.name,
|
|
429
|
-
group: row.group || "
|
|
433
|
+
group: row.group || "默认分组",
|
|
430
434
|
description: row.description || "",
|
|
431
435
|
url: row.url,
|
|
432
436
|
method: row.method || "redirect",
|
|
@@ -459,7 +463,7 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
459
463
|
await this.ctx.database.create("memesluna_endpoints", {
|
|
460
464
|
id,
|
|
461
465
|
name: input.name,
|
|
462
|
-
group: input.group || "
|
|
466
|
+
group: input.group || "默认分组",
|
|
463
467
|
description: input.description || "",
|
|
464
468
|
url: input.url,
|
|
465
469
|
method: input.method || "redirect",
|
|
@@ -483,7 +487,7 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
483
487
|
const payload = {
|
|
484
488
|
updated_at: /* @__PURE__ */ new Date()
|
|
485
489
|
};
|
|
486
|
-
if (input.group !== void 0) payload.group = input.group || "
|
|
490
|
+
if (input.group !== void 0) payload.group = input.group || "默认分组";
|
|
487
491
|
if (input.description !== void 0) payload.description = input.description || "";
|
|
488
492
|
if (input.url !== void 0) payload.url = input.url;
|
|
489
493
|
if (input.method !== void 0) payload.method = input.method;
|
|
@@ -526,12 +530,12 @@ var MemesLunaService = class extends import_koishi.Service {
|
|
|
526
530
|
// src/config.ts
|
|
527
531
|
var import_koishi2 = require("koishi");
|
|
528
532
|
var Config = import_koishi2.Schema.object({
|
|
529
|
-
backendPath: import_koishi2.Schema.string().default("/memesluna").description("
|
|
530
|
-
storagePath: import_koishi2.Schema.string().default("data/memesluna").description("
|
|
531
|
-
selfUrl: import_koishi2.Schema.string().default("").description("
|
|
532
|
-
injectVariables: import_koishi2.Schema.boolean().default(true).description("
|
|
533
|
-
variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("
|
|
534
|
-
injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(
|
|
533
|
+
backendPath: import_koishi2.Schema.string().default("/memesluna").description("后端服务路径前缀"),
|
|
534
|
+
storagePath: import_koishi2.Schema.string().default("data/memesluna").description("本地合集存储目录"),
|
|
535
|
+
selfUrl: import_koishi2.Schema.string().default("").description("服务公开地址,不填则优先使用 server.selfUrl"),
|
|
536
|
+
injectVariables: import_koishi2.Schema.boolean().default(true).description("是否向 ChatLuna 注入 {{endpoint}} 和 {{memesluna}} 变量"),
|
|
537
|
+
variableRefreshIntervalMs: import_koishi2.Schema.number().min(30 * 1e3).max(60 * 60 * 1e3).default(5 * 60 * 1e3).description("变量刷新间隔(毫秒)"),
|
|
538
|
+
injectVariablesPrompt: import_koishi2.Schema.string().role("textarea").default(`你可以使用表情包来丰富你的回复。表情包列表是{endpoint},基础URL是{base_url},你要把基础URL拼接到路径前面,不要加文件名,只加路径,用发送图片的方式发送。`).description("注入到 ChatLuna {{memesluna}} 变量的提示词模板,支持 {endpoint} 和 {base_url} 占位符")
|
|
535
539
|
});
|
|
536
540
|
var name = "memesluna";
|
|
537
541
|
|
|
@@ -554,6 +558,7 @@ var IMAGE_URL_REGEXP = /\.(jpeg|jpg|gif|png|webp|bmp|svg)(\?.*)?$/i;
|
|
|
554
558
|
function isReservedPath(name2) {
|
|
555
559
|
return RESERVED_PATHS.has(name2) || name2.includes(".");
|
|
556
560
|
}
|
|
561
|
+
__name(isReservedPath, "isReservedPath");
|
|
557
562
|
function getValueByDotNotation(obj, dotPath) {
|
|
558
563
|
if (!dotPath) return void 0;
|
|
559
564
|
const parts = dotPath.split(".").filter(Boolean);
|
|
@@ -567,10 +572,12 @@ function getValueByDotNotation(obj, dotPath) {
|
|
|
567
572
|
}
|
|
568
573
|
return current;
|
|
569
574
|
}
|
|
575
|
+
__name(getValueByDotNotation, "getValueByDotNotation");
|
|
570
576
|
function normalizeContentType(contentType) {
|
|
571
577
|
if (!contentType) return "";
|
|
572
578
|
return contentType.toLowerCase().split(";")[0].trim();
|
|
573
579
|
}
|
|
580
|
+
__name(normalizeContentType, "normalizeContentType");
|
|
574
581
|
function guessMimeByExt(filePath) {
|
|
575
582
|
const ext = import_path2.default.extname(filePath).toLowerCase();
|
|
576
583
|
switch (ext) {
|
|
@@ -590,6 +597,7 @@ function guessMimeByExt(filePath) {
|
|
|
590
597
|
return "image/jpeg";
|
|
591
598
|
}
|
|
592
599
|
}
|
|
600
|
+
__name(guessMimeByExt, "guessMimeByExt");
|
|
593
601
|
async function handleProxyRequest(targetUrl, proxySettings = {}) {
|
|
594
602
|
try {
|
|
595
603
|
const response = await fetch(targetUrl, {
|
|
@@ -668,9 +676,11 @@ async function handleProxyRequest(targetUrl, proxySettings = {}) {
|
|
|
668
676
|
};
|
|
669
677
|
}
|
|
670
678
|
}
|
|
679
|
+
__name(handleProxyRequest, "handleProxyRequest");
|
|
671
680
|
function toAbsoluteBaseUrl(ctx, config) {
|
|
672
681
|
return config.selfUrl || ctx.server?.selfUrl || "";
|
|
673
682
|
}
|
|
683
|
+
__name(toAbsoluteBaseUrl, "toAbsoluteBaseUrl");
|
|
674
684
|
async function applyDynamicForward(ctx, config, service, routeName, query) {
|
|
675
685
|
const endpoint = await service.getEndpointByName(routeName);
|
|
676
686
|
const isCollection = await service.collectionExists(routeName);
|
|
@@ -784,6 +794,7 @@ async function applyDynamicForward(ctx, config, service, routeName, query) {
|
|
|
784
794
|
contentType: guessMimeByExt(resource.value)
|
|
785
795
|
};
|
|
786
796
|
}
|
|
797
|
+
__name(applyDynamicForward, "applyDynamicForward");
|
|
787
798
|
function setKoaResponse(koa, result) {
|
|
788
799
|
if (result.redirectTo) {
|
|
789
800
|
koa.redirect(result.redirectTo);
|
|
@@ -800,6 +811,7 @@ function setKoaResponse(koa, result) {
|
|
|
800
811
|
}
|
|
801
812
|
koa.body = result.body;
|
|
802
813
|
}
|
|
814
|
+
__name(setKoaResponse, "setKoaResponse");
|
|
803
815
|
function getRequestBody(koa) {
|
|
804
816
|
const body = koa?.request?.body;
|
|
805
817
|
if (!body) return {};
|
|
@@ -815,13 +827,16 @@ function getRequestBody(koa) {
|
|
|
815
827
|
}
|
|
816
828
|
return {};
|
|
817
829
|
}
|
|
830
|
+
__name(getRequestBody, "getRequestBody");
|
|
818
831
|
function toTrimmedString(value) {
|
|
819
832
|
return typeof value === "string" ? value.trim() : "";
|
|
820
833
|
}
|
|
834
|
+
__name(toTrimmedString, "toTrimmedString");
|
|
821
835
|
function toStringArray(value) {
|
|
822
836
|
if (!Array.isArray(value)) return [];
|
|
823
837
|
return value.map((item) => typeof item === "string" ? item.trim() : "").filter((item) => item.length > 0);
|
|
824
838
|
}
|
|
839
|
+
__name(toStringArray, "toStringArray");
|
|
825
840
|
function parseJsonLike(value, fallback) {
|
|
826
841
|
if (value === null || value === void 0) return fallback;
|
|
827
842
|
if (typeof value === "string") {
|
|
@@ -838,9 +853,11 @@ function parseJsonLike(value, fallback) {
|
|
|
838
853
|
}
|
|
839
854
|
return fallback;
|
|
840
855
|
}
|
|
856
|
+
__name(parseJsonLike, "parseJsonLike");
|
|
841
857
|
function normalizeForwardMethod(value) {
|
|
842
858
|
return toTrimmedString(value) === "proxy" ? "proxy" : "redirect";
|
|
843
859
|
}
|
|
860
|
+
__name(normalizeForwardMethod, "normalizeForwardMethod");
|
|
844
861
|
function normalizeUrlConstruction(value) {
|
|
845
862
|
const normalized = toTrimmedString(value);
|
|
846
863
|
if (normalized === "special_forward" || normalized === "special_pollinations" || normalized === "special_draw_redirect") {
|
|
@@ -848,6 +865,7 @@ function normalizeUrlConstruction(value) {
|
|
|
848
865
|
}
|
|
849
866
|
return "normal";
|
|
850
867
|
}
|
|
868
|
+
__name(normalizeUrlConstruction, "normalizeUrlConstruction");
|
|
851
869
|
async function buildAdminState(service) {
|
|
852
870
|
const endpoints = await service.getEndpoints();
|
|
853
871
|
const collectionNames = await service.getCollections();
|
|
@@ -858,13 +876,14 @@ async function buildAdminState(service) {
|
|
|
858
876
|
collections: collections.filter(Boolean)
|
|
859
877
|
};
|
|
860
878
|
}
|
|
879
|
+
__name(buildAdminState, "buildAdminState");
|
|
861
880
|
function buildHomepageHtml(basePath) {
|
|
862
881
|
return `<!doctype html>
|
|
863
882
|
<html lang="zh-CN">
|
|
864
883
|
<head>
|
|
865
884
|
<meta charset="utf-8" />
|
|
866
885
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
867
|
-
<title
|
|
886
|
+
<title>图床转发 - 首页</title>
|
|
868
887
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
869
888
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
|
|
870
889
|
<style>
|
|
@@ -1052,8 +1071,8 @@ function buildHomepageHtml(basePath) {
|
|
|
1052
1071
|
animation: spin 1s linear infinite;
|
|
1053
1072
|
}
|
|
1054
1073
|
|
|
1055
|
-
|
|
1056
|
-
|
|
1074
|
+
.error-panel {
|
|
1075
|
+
display: none !important;
|
|
1057
1076
|
}
|
|
1058
1077
|
|
|
1059
1078
|
@keyframes spin {
|
|
@@ -1082,10 +1101,10 @@ function buildHomepageHtml(basePath) {
|
|
|
1082
1101
|
<div class="collapse navbar-collapse" id="navbarNav">
|
|
1083
1102
|
<ul class="navbar-nav me-auto">
|
|
1084
1103
|
<li class="nav-item">
|
|
1085
|
-
<a class="nav-link active" href="${basePath}/"
|
|
1104
|
+
<a class="nav-link active" href="${basePath}/">首页</a>
|
|
1086
1105
|
</li>
|
|
1087
1106
|
<li class="nav-item">
|
|
1088
|
-
<a class="nav-link" href="${basePath}/admin"
|
|
1107
|
+
<a class="nav-link" href="${basePath}/admin">管理</a>
|
|
1089
1108
|
</li>
|
|
1090
1109
|
</ul>
|
|
1091
1110
|
</div>
|
|
@@ -1094,17 +1113,17 @@ function buildHomepageHtml(basePath) {
|
|
|
1094
1113
|
|
|
1095
1114
|
<div class="container page-container mt-3 pb-4">
|
|
1096
1115
|
<div class="group-section">
|
|
1097
|
-
<h3 class="group-title-home"
|
|
1116
|
+
<h3 class="group-title-home">🌐 接口端点</h3>
|
|
1098
1117
|
<div class="cards-row" id="endpoint-cards"></div>
|
|
1099
1118
|
</div>
|
|
1100
1119
|
|
|
1101
1120
|
<div class="group-section">
|
|
1102
|
-
<h3 class="group-title-home"
|
|
1121
|
+
<h3 class="group-title-home">📷 本地图片合集</h3>
|
|
1103
1122
|
<div class="cards-row" id="collection-cards"></div>
|
|
1104
1123
|
</div>
|
|
1105
1124
|
|
|
1106
1125
|
<div class="alert alert-danger error-panel" id="error-panel">
|
|
1107
|
-
<strong
|
|
1126
|
+
<strong>加载失败:</strong><span id="error-text"></span>
|
|
1108
1127
|
</div>
|
|
1109
1128
|
</div>
|
|
1110
1129
|
|
|
@@ -1116,13 +1135,14 @@ function buildHomepageHtml(basePath) {
|
|
|
1116
1135
|
const panel = document.getElementById('error-panel')
|
|
1117
1136
|
const text = document.getElementById('error-text')
|
|
1118
1137
|
if (text) text.textContent = message
|
|
1138
|
+
if (panel) panel.classList.remove('error-panel')
|
|
1119
1139
|
if (panel) panel.style.display = 'block'
|
|
1120
1140
|
}
|
|
1121
1141
|
|
|
1122
1142
|
function createLoader() {
|
|
1123
1143
|
const loader = document.createElement('div')
|
|
1124
1144
|
loader.className = 'media-loader'
|
|
1125
|
-
loader.innerHTML = '<div class="loader-spinner"></div><span
|
|
1145
|
+
loader.innerHTML = '<div class="loader-spinner"></div><span>加载中...</span>'
|
|
1126
1146
|
return loader
|
|
1127
1147
|
}
|
|
1128
1148
|
|
|
@@ -1180,9 +1200,9 @@ function buildHomepageHtml(basePath) {
|
|
|
1180
1200
|
const target = String(item.url || '')
|
|
1181
1201
|
|
|
1182
1202
|
info.innerHTML =
|
|
1183
|
-
'<p class="api-hint"
|
|
1203
|
+
'<p class="api-hint">⚙️ ' + method + ' · ' + mode + '</p>' +
|
|
1184
1204
|
'<p class="api-url">' + endpointPath + '</p>' +
|
|
1185
|
-
'<p class="api-url"
|
|
1205
|
+
'<p class="api-url">→ ' + target + '</p>'
|
|
1186
1206
|
|
|
1187
1207
|
card.appendChild(info)
|
|
1188
1208
|
return card
|
|
@@ -1208,7 +1228,7 @@ function buildHomepageHtml(basePath) {
|
|
|
1208
1228
|
const totalCount = Number(item.totalCount || 0)
|
|
1209
1229
|
|
|
1210
1230
|
info.innerHTML =
|
|
1211
|
-
'<p class="api-hint"
|
|
1231
|
+
'<p class="api-hint">📁 本地 ' + localCount + ' · 外链 ' + linkCount + ' · 总计 ' + totalCount + '</p>' +
|
|
1212
1232
|
'<p class="api-url">' + route + '</p>'
|
|
1213
1233
|
|
|
1214
1234
|
card.appendChild(info)
|
|
@@ -1231,7 +1251,7 @@ function buildHomepageHtml(basePath) {
|
|
|
1231
1251
|
if (!endpoints.length) {
|
|
1232
1252
|
const empty = document.createElement('div')
|
|
1233
1253
|
empty.className = 'alert alert-secondary mb-0'
|
|
1234
|
-
empty.textContent = '
|
|
1254
|
+
empty.textContent = '暂无 endpoint'
|
|
1235
1255
|
endpointWrap.appendChild(empty)
|
|
1236
1256
|
} else {
|
|
1237
1257
|
endpoints.forEach((item) => endpointWrap.appendChild(buildEndpointCard(item)))
|
|
@@ -1244,7 +1264,7 @@ function buildHomepageHtml(basePath) {
|
|
|
1244
1264
|
if (!collections.length) {
|
|
1245
1265
|
const empty = document.createElement('div')
|
|
1246
1266
|
empty.className = 'alert alert-secondary mb-0'
|
|
1247
|
-
empty.textContent = '
|
|
1267
|
+
empty.textContent = '暂无 collection'
|
|
1248
1268
|
collectionWrap.appendChild(empty)
|
|
1249
1269
|
} else {
|
|
1250
1270
|
collections.forEach((item) => collectionWrap.appendChild(buildCollectionCard(item)))
|
|
@@ -1261,23 +1281,550 @@ function buildHomepageHtml(basePath) {
|
|
|
1261
1281
|
</body>
|
|
1262
1282
|
</html>`;
|
|
1263
1283
|
}
|
|
1284
|
+
__name(buildHomepageHtml, "buildHomepageHtml");
|
|
1285
|
+
function buildAdminHtml(basePath) {
|
|
1286
|
+
return `<!doctype html>
|
|
1287
|
+
<html lang="zh-CN">
|
|
1288
|
+
<head>
|
|
1289
|
+
<meta charset="utf-8" />
|
|
1290
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1291
|
+
<title>图床转发 - 管理</title>
|
|
1292
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
1293
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.3/font/bootstrap-icons.css">
|
|
1294
|
+
<style>
|
|
1295
|
+
body {
|
|
1296
|
+
min-height: 100vh;
|
|
1297
|
+
margin: 0;
|
|
1298
|
+
color: #1f2937;
|
|
1299
|
+
background-color: #f4f6fb;
|
|
1300
|
+
background-image: url('/project_bg/default_background.jpg');
|
|
1301
|
+
background-size: cover;
|
|
1302
|
+
background-position: center;
|
|
1303
|
+
background-repeat: no-repeat;
|
|
1304
|
+
background-attachment: fixed;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
body::before {
|
|
1308
|
+
content: '';
|
|
1309
|
+
position: fixed;
|
|
1310
|
+
inset: 0;
|
|
1311
|
+
background-color: rgba(255, 255, 255, 0.78);
|
|
1312
|
+
z-index: -1;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
.acrylic-navbar {
|
|
1316
|
+
background-color: rgba(248, 249, 250, 0.68);
|
|
1317
|
+
-webkit-backdrop-filter: blur(12px) saturate(150%);
|
|
1318
|
+
backdrop-filter: blur(12px) saturate(150%);
|
|
1319
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.06);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
.admin-shell {
|
|
1323
|
+
max-width: 1320px;
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.sidebar-panel,
|
|
1327
|
+
.main-panel,
|
|
1328
|
+
.sub-card {
|
|
1329
|
+
border-radius: 14px;
|
|
1330
|
+
border: 1px solid rgba(255, 255, 255, 0.5);
|
|
1331
|
+
background: rgba(255, 255, 255, 0.72);
|
|
1332
|
+
-webkit-backdrop-filter: blur(10px) saturate(130%);
|
|
1333
|
+
backdrop-filter: blur(10px) saturate(130%);
|
|
1334
|
+
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.sidebar-panel {
|
|
1338
|
+
padding: 1rem;
|
|
1339
|
+
position: sticky;
|
|
1340
|
+
top: 1rem;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
.main-panel {
|
|
1344
|
+
padding: 1rem;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
.collection-item {
|
|
1348
|
+
cursor: pointer;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.collection-item.active {
|
|
1352
|
+
background: rgba(59, 130, 246, 0.14);
|
|
1353
|
+
border-color: rgba(59, 130, 246, 0.35);
|
|
1354
|
+
color: #1d4ed8;
|
|
1355
|
+
font-weight: 700;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
.image-grid {
|
|
1359
|
+
display: grid;
|
|
1360
|
+
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
|
1361
|
+
gap: 0.8rem;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.image-card {
|
|
1365
|
+
border-radius: 10px;
|
|
1366
|
+
overflow: hidden;
|
|
1367
|
+
border: 1px solid rgba(148, 163, 184, 0.35);
|
|
1368
|
+
background: rgba(255, 255, 255, 0.9);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.image-card img {
|
|
1372
|
+
width: 100%;
|
|
1373
|
+
height: 140px;
|
|
1374
|
+
object-fit: cover;
|
|
1375
|
+
display: block;
|
|
1376
|
+
background: #f1f5f9;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
.sub-card {
|
|
1380
|
+
padding: 0.9rem;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
.code-url {
|
|
1384
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
1385
|
+
font-size: 0.8rem;
|
|
1386
|
+
word-break: break-all;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.section-title {
|
|
1390
|
+
font-size: 1rem;
|
|
1391
|
+
font-weight: 700;
|
|
1392
|
+
color: #334155;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.empty-tip {
|
|
1396
|
+
color: #64748b;
|
|
1397
|
+
font-size: 0.9rem;
|
|
1398
|
+
}
|
|
1399
|
+
</style>
|
|
1400
|
+
</head>
|
|
1401
|
+
<body>
|
|
1402
|
+
<nav class="navbar navbar-expand-lg navbar-light acrylic-navbar">
|
|
1403
|
+
<div class="container admin-shell">
|
|
1404
|
+
<a class="navbar-brand" href="${basePath}/">MemesLuna</a>
|
|
1405
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAdmin">
|
|
1406
|
+
<span class="navbar-toggler-icon"></span>
|
|
1407
|
+
</button>
|
|
1408
|
+
<div class="collapse navbar-collapse" id="navbarNavAdmin">
|
|
1409
|
+
<ul class="navbar-nav me-auto">
|
|
1410
|
+
<li class="nav-item"><a class="nav-link" href="${basePath}/">首页</a></li>
|
|
1411
|
+
<li class="nav-item"><a class="nav-link active" href="${basePath}/admin">管理</a></li>
|
|
1412
|
+
</ul>
|
|
1413
|
+
</div>
|
|
1414
|
+
</div>
|
|
1415
|
+
</nav>
|
|
1416
|
+
|
|
1417
|
+
<div class="container admin-shell mt-3 pb-4">
|
|
1418
|
+
<div class="row g-3">
|
|
1419
|
+
<div class="col-lg-3">
|
|
1420
|
+
<div class="sidebar-panel">
|
|
1421
|
+
<h5 class="mb-3">合集管理</h5>
|
|
1422
|
+
<div class="input-group mb-3">
|
|
1423
|
+
<input id="new-collection-name" class="form-control" placeholder="新合集名称" />
|
|
1424
|
+
<button id="create-collection" class="btn btn-primary">创建</button>
|
|
1425
|
+
</div>
|
|
1426
|
+
<div id="collection-list" class="list-group mb-3"></div>
|
|
1427
|
+
<button id="delete-collection" class="btn btn-outline-danger w-100" disabled>删除当前合集</button>
|
|
1428
|
+
|
|
1429
|
+
<hr>
|
|
1430
|
+
<h6 class="mb-2">合集描述</h6>
|
|
1431
|
+
<div class="input-group mb-3">
|
|
1432
|
+
<input id="collection-description" class="form-control" placeholder="为当前合集添加描述" disabled />
|
|
1433
|
+
<button id="save-description" class="btn btn-primary" disabled>保存</button>
|
|
1434
|
+
</div>
|
|
1435
|
+
|
|
1436
|
+
<hr>
|
|
1437
|
+
<h6 class="mb-2">快捷信息</h6>
|
|
1438
|
+
<div class="small text-muted">
|
|
1439
|
+
<div>管理链接:<code>${basePath}/admin</code></div>
|
|
1440
|
+
<div class="mt-1">随机访问:<code id="collection-random-url">-</code></div>
|
|
1441
|
+
</div>
|
|
1442
|
+
</div>
|
|
1443
|
+
</div>
|
|
1444
|
+
|
|
1445
|
+
<div class="col-lg-9">
|
|
1446
|
+
<div class="main-panel mb-3">
|
|
1447
|
+
<div class="d-flex justify-content-between align-items-center">
|
|
1448
|
+
<h5 class="mb-0">合集内容</h5>
|
|
1449
|
+
<span id="selected-collection-badge" class="badge bg-primary">未选择</span>
|
|
1450
|
+
</div>
|
|
1451
|
+
|
|
1452
|
+
<div class="row g-3 mt-1">
|
|
1453
|
+
<div class="col-md-6">
|
|
1454
|
+
<div class="sub-card h-100">
|
|
1455
|
+
<div class="section-title mb-2">上传本地图片</div>
|
|
1456
|
+
<input id="upload-files" type="file" class="form-control mb-2" multiple accept="image/*" />
|
|
1457
|
+
<button id="upload-images" class="btn btn-primary w-100" disabled>上传到当前合集</button>
|
|
1458
|
+
<div class="form-text">支持多选。上传后自动刷新列表。</div>
|
|
1459
|
+
</div>
|
|
1460
|
+
</div>
|
|
1461
|
+
<div class="col-md-6">
|
|
1462
|
+
<div class="sub-card h-100">
|
|
1463
|
+
<div class="section-title mb-2">添加外链</div>
|
|
1464
|
+
<textarea id="links-input" class="form-control mb-2" rows="4" placeholder="每行一个 http/https 链接"></textarea>
|
|
1465
|
+
<button id="add-links" class="btn btn-primary w-100" disabled>添加外链到当前合集</button>
|
|
1466
|
+
</div>
|
|
1467
|
+
</div>
|
|
1468
|
+
</div>
|
|
1469
|
+
|
|
1470
|
+
<div class="sub-card mt-3">
|
|
1471
|
+
<div class="section-title mb-2">本地图片</div>
|
|
1472
|
+
<div id="images-grid" class="image-grid"></div>
|
|
1473
|
+
<div id="images-empty" class="empty-tip mt-2">暂无本地图片</div>
|
|
1474
|
+
</div>
|
|
1475
|
+
|
|
1476
|
+
<div class="sub-card mt-3">
|
|
1477
|
+
<div class="section-title mb-2">外链列表</div>
|
|
1478
|
+
<div id="links-list" class="list-group"></div>
|
|
1479
|
+
<div id="links-empty" class="empty-tip mt-2">暂无外链</div>
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
</div>
|
|
1483
|
+
</div>
|
|
1484
|
+
|
|
1485
|
+
<div id="admin-alert" class="alert mt-3 d-none"></div>
|
|
1486
|
+
</div>
|
|
1487
|
+
|
|
1488
|
+
<script>
|
|
1489
|
+
const BASE_PATH = '${basePath}'
|
|
1490
|
+
const state = {
|
|
1491
|
+
collectionNames: [],
|
|
1492
|
+
collections: [],
|
|
1493
|
+
endpoints: [],
|
|
1494
|
+
selectedCollection: '',
|
|
1495
|
+
images: [],
|
|
1496
|
+
links: [],
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
const byId = (id) => document.getElementById(id)
|
|
1500
|
+
|
|
1501
|
+
function showAlert(message, type = 'info') {
|
|
1502
|
+
const el = byId('admin-alert')
|
|
1503
|
+
el.className = 'alert alert-' + type + ' mt-3'
|
|
1504
|
+
el.textContent = message
|
|
1505
|
+
el.classList.remove('d-none')
|
|
1506
|
+
setTimeout(() => el.classList.add('d-none'), 2200)
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
async function request(url, options = {}) {
|
|
1510
|
+
console.log('Requesting:', url)
|
|
1511
|
+
const headers = Object.assign({}, options.headers || {})
|
|
1512
|
+
if (options.body && !headers['Content-Type']) {
|
|
1513
|
+
headers['Content-Type'] = 'application/json'
|
|
1514
|
+
}
|
|
1515
|
+
const res = await fetch(url, Object.assign({}, options, { headers }))
|
|
1516
|
+
console.log('Response status:', res.status)
|
|
1517
|
+
let data = null
|
|
1518
|
+
try {
|
|
1519
|
+
data = await res.json()
|
|
1520
|
+
console.log('Response data:', data)
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
console.log('JSON parse error:', e)
|
|
1523
|
+
data = null
|
|
1524
|
+
}
|
|
1525
|
+
if (!res.ok) {
|
|
1526
|
+
throw new Error(data && data.error ? data.error : String(res.status) + ' ' + String(res.statusText))
|
|
1527
|
+
}
|
|
1528
|
+
return data
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
async function refreshState() {
|
|
1532
|
+
const data = await request(BASE_PATH + '/api/admin/state')
|
|
1533
|
+
state.collectionNames = Array.isArray(data.collectionNames) ? data.collectionNames : []
|
|
1534
|
+
state.collections = Array.isArray(data.collections) ? data.collections : []
|
|
1535
|
+
state.endpoints = Array.isArray(data.endpoints) ? data.endpoints : []
|
|
1536
|
+
|
|
1537
|
+
if (!state.selectedCollection || !state.collectionNames.includes(state.selectedCollection)) {
|
|
1538
|
+
state.selectedCollection = state.collectionNames[0] || ''
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
renderCollectionList()
|
|
1542
|
+
await refreshCollectionResources()
|
|
1543
|
+
syncSelectedCollectionUi()
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
async function refreshCollectionResources() {
|
|
1547
|
+
if (!state.selectedCollection) {
|
|
1548
|
+
state.images = []
|
|
1549
|
+
state.links = []
|
|
1550
|
+
renderImages()
|
|
1551
|
+
renderLinks()
|
|
1552
|
+
return
|
|
1553
|
+
}
|
|
1554
|
+
const data = await request(BASE_PATH + '/api/collections/' + encodeURIComponent(state.selectedCollection) + '/resources')
|
|
1555
|
+
state.images = Array.isArray(data.images) ? data.images : []
|
|
1556
|
+
state.links = Array.isArray(data.links) ? data.links : []
|
|
1557
|
+
renderImages()
|
|
1558
|
+
renderLinks()
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
function syncSelectedCollectionUi() {
|
|
1562
|
+
const selected = state.selectedCollection
|
|
1563
|
+
byId('selected-collection-badge').textContent = selected || '未选择'
|
|
1564
|
+
byId('delete-collection').disabled = !selected
|
|
1565
|
+
byId('upload-images').disabled = !selected
|
|
1566
|
+
byId('add-links').disabled = !selected
|
|
1567
|
+
byId('collection-random-url').textContent = selected ? BASE_PATH + '/' + selected : '-'
|
|
1568
|
+
byId('collection-description').disabled = !selected
|
|
1569
|
+
byId('save-description').disabled = !selected
|
|
1570
|
+
|
|
1571
|
+
const collectionInfo = state.collections.find((c) => c.name === selected)
|
|
1572
|
+
byId('collection-description').value = collectionInfo ? (collectionInfo.description || '') : ''
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function renderCollectionList() {
|
|
1576
|
+
const list = byId('collection-list')
|
|
1577
|
+
list.textContent = ''
|
|
1578
|
+
if (!state.collectionNames.length) {
|
|
1579
|
+
const empty = document.createElement('div')
|
|
1580
|
+
empty.className = 'text-muted small'
|
|
1581
|
+
empty.textContent = '暂无合集'
|
|
1582
|
+
list.appendChild(empty)
|
|
1583
|
+
return
|
|
1584
|
+
}
|
|
1585
|
+
state.collectionNames.forEach((name) => {
|
|
1586
|
+
const button = document.createElement('button')
|
|
1587
|
+
button.type = 'button'
|
|
1588
|
+
button.className = 'list-group-item list-group-item-action collection-item' + (name === state.selectedCollection ? ' active' : '')
|
|
1589
|
+
button.textContent = name
|
|
1590
|
+
button.addEventListener('click', async () => {
|
|
1591
|
+
state.selectedCollection = name
|
|
1592
|
+
renderCollectionList()
|
|
1593
|
+
syncSelectedCollectionUi()
|
|
1594
|
+
await refreshCollectionResources()
|
|
1595
|
+
})
|
|
1596
|
+
list.appendChild(button)
|
|
1597
|
+
})
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
function imagePreviewUrl(filename) {
|
|
1601
|
+
return BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(filename)
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
function renderImages() {
|
|
1605
|
+
const grid = byId('images-grid')
|
|
1606
|
+
const empty = byId('images-empty')
|
|
1607
|
+
grid.textContent = ''
|
|
1608
|
+
empty.style.display = state.images.length ? 'none' : 'block'
|
|
1609
|
+
|
|
1610
|
+
state.images.forEach((name) => {
|
|
1611
|
+
const card = document.createElement('div')
|
|
1612
|
+
card.className = 'image-card'
|
|
1613
|
+
|
|
1614
|
+
const img = document.createElement('img')
|
|
1615
|
+
img.src = imagePreviewUrl(name)
|
|
1616
|
+
img.alt = name
|
|
1617
|
+
card.appendChild(img)
|
|
1618
|
+
|
|
1619
|
+
const body = document.createElement('div')
|
|
1620
|
+
body.className = 'p-2'
|
|
1621
|
+
const title = document.createElement('div')
|
|
1622
|
+
title.className = 'small text-truncate mb-2'
|
|
1623
|
+
title.textContent = name
|
|
1624
|
+
body.appendChild(title)
|
|
1625
|
+
|
|
1626
|
+
const row = document.createElement('div')
|
|
1627
|
+
row.className = 'd-flex gap-1'
|
|
1628
|
+
|
|
1629
|
+
const moveSelect = document.createElement('select')
|
|
1630
|
+
moveSelect.className = 'form-select form-select-sm'
|
|
1631
|
+
const collections = state.collectionNames.filter((item) => item !== state.selectedCollection)
|
|
1632
|
+
const placeholder = document.createElement('option')
|
|
1633
|
+
placeholder.value = ''
|
|
1634
|
+
placeholder.textContent = '移动到...'
|
|
1635
|
+
moveSelect.appendChild(placeholder)
|
|
1636
|
+
collections.forEach((target) => {
|
|
1637
|
+
const opt = document.createElement('option')
|
|
1638
|
+
opt.value = target
|
|
1639
|
+
opt.textContent = target
|
|
1640
|
+
moveSelect.appendChild(opt)
|
|
1641
|
+
})
|
|
1642
|
+
|
|
1643
|
+
const moveBtn = document.createElement('button')
|
|
1644
|
+
moveBtn.className = 'btn btn-sm btn-outline-primary'
|
|
1645
|
+
moveBtn.textContent = '移动'
|
|
1646
|
+
moveBtn.disabled = collections.length === 0
|
|
1647
|
+
moveBtn.addEventListener('click', async () => {
|
|
1648
|
+
const targetCollection = moveSelect.value
|
|
1649
|
+
if (!targetCollection) return
|
|
1650
|
+
await request(
|
|
1651
|
+
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name) + '/move',
|
|
1652
|
+
{ method: 'POST', body: JSON.stringify({ targetCollection }) }
|
|
1653
|
+
)
|
|
1654
|
+
showAlert('图片已移动', 'success')
|
|
1655
|
+
await refreshState()
|
|
1656
|
+
})
|
|
1657
|
+
|
|
1658
|
+
const delBtn = document.createElement('button')
|
|
1659
|
+
delBtn.className = 'btn btn-sm btn-outline-danger'
|
|
1660
|
+
delBtn.textContent = '删除'
|
|
1661
|
+
delBtn.addEventListener('click', async () => {
|
|
1662
|
+
await request(
|
|
1663
|
+
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images/' + encodeURIComponent(name),
|
|
1664
|
+
{ method: 'DELETE' }
|
|
1665
|
+
)
|
|
1666
|
+
showAlert('图片已删除', 'success')
|
|
1667
|
+
await refreshCollectionResources()
|
|
1668
|
+
})
|
|
1669
|
+
|
|
1670
|
+
row.appendChild(moveSelect)
|
|
1671
|
+
row.appendChild(moveBtn)
|
|
1672
|
+
row.appendChild(delBtn)
|
|
1673
|
+
body.appendChild(row)
|
|
1674
|
+
card.appendChild(body)
|
|
1675
|
+
grid.appendChild(card)
|
|
1676
|
+
})
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function renderLinks() {
|
|
1680
|
+
const wrap = byId('links-list')
|
|
1681
|
+
const empty = byId('links-empty')
|
|
1682
|
+
wrap.textContent = ''
|
|
1683
|
+
empty.style.display = state.links.length ? 'none' : 'block'
|
|
1684
|
+
state.links.forEach((link) => {
|
|
1685
|
+
const row = document.createElement('div')
|
|
1686
|
+
row.className = 'list-group-item d-flex justify-content-between align-items-center gap-2'
|
|
1687
|
+
|
|
1688
|
+
const text = document.createElement('div')
|
|
1689
|
+
text.className = 'code-url flex-grow-1'
|
|
1690
|
+
text.textContent = link
|
|
1691
|
+
|
|
1692
|
+
const actions = document.createElement('div')
|
|
1693
|
+
actions.className = 'd-flex gap-1'
|
|
1694
|
+
|
|
1695
|
+
const open = document.createElement('a')
|
|
1696
|
+
open.className = 'btn btn-sm btn-outline-primary'
|
|
1697
|
+
open.textContent = '查看'
|
|
1698
|
+
open.href = link
|
|
1699
|
+
open.target = '_blank'
|
|
1700
|
+
|
|
1701
|
+
const del = document.createElement('button')
|
|
1702
|
+
del.className = 'btn btn-sm btn-outline-danger'
|
|
1703
|
+
del.textContent = '删除'
|
|
1704
|
+
del.addEventListener('click', async () => {
|
|
1705
|
+
await request(
|
|
1706
|
+
BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links',
|
|
1707
|
+
{ method: 'DELETE', body: JSON.stringify({ link }) }
|
|
1708
|
+
)
|
|
1709
|
+
showAlert('外链已删除', 'success')
|
|
1710
|
+
await refreshCollectionResources()
|
|
1711
|
+
})
|
|
1712
|
+
|
|
1713
|
+
actions.appendChild(open)
|
|
1714
|
+
actions.appendChild(del)
|
|
1715
|
+
row.appendChild(text)
|
|
1716
|
+
row.appendChild(actions)
|
|
1717
|
+
wrap.appendChild(row)
|
|
1718
|
+
})
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
function readFileAsDataURL(file) {
|
|
1722
|
+
return new Promise((resolve, reject) => {
|
|
1723
|
+
const reader = new FileReader()
|
|
1724
|
+
reader.onload = () => resolve(reader.result)
|
|
1725
|
+
reader.onerror = () => reject(new Error('读取文件失败'))
|
|
1726
|
+
reader.readAsDataURL(file)
|
|
1727
|
+
})
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
byId('create-collection').addEventListener('click', async () => {
|
|
1731
|
+
const name = byId('new-collection-name').value.trim()
|
|
1732
|
+
if (!name) return
|
|
1733
|
+
await request(BASE_PATH + '/api/admin/collections', {
|
|
1734
|
+
method: 'POST',
|
|
1735
|
+
body: JSON.stringify({ name }),
|
|
1736
|
+
})
|
|
1737
|
+
byId('new-collection-name').value = ''
|
|
1738
|
+
showAlert('合集创建成功', 'success')
|
|
1739
|
+
await refreshState()
|
|
1740
|
+
})
|
|
1741
|
+
|
|
1742
|
+
byId('delete-collection').addEventListener('click', async () => {
|
|
1743
|
+
if (!state.selectedCollection) return
|
|
1744
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection), {
|
|
1745
|
+
method: 'DELETE',
|
|
1746
|
+
})
|
|
1747
|
+
showAlert('合集已删除', 'success')
|
|
1748
|
+
await refreshState()
|
|
1749
|
+
})
|
|
1750
|
+
|
|
1751
|
+
byId('save-description').addEventListener('click', async () => {
|
|
1752
|
+
if (!state.selectedCollection) return
|
|
1753
|
+
const description = byId('collection-description').value.trim()
|
|
1754
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/description', {
|
|
1755
|
+
method: 'PATCH',
|
|
1756
|
+
body: JSON.stringify({ description }),
|
|
1757
|
+
})
|
|
1758
|
+
showAlert('描述已保存', 'success')
|
|
1759
|
+
await refreshState()
|
|
1760
|
+
})
|
|
1761
|
+
|
|
1762
|
+
byId('upload-images').addEventListener('click', async () => {
|
|
1763
|
+
if (!state.selectedCollection) return
|
|
1764
|
+
const files = byId('upload-files').files
|
|
1765
|
+
if (!files || !files.length) return
|
|
1766
|
+
const images = []
|
|
1767
|
+
for (const file of files) {
|
|
1768
|
+
const base64 = await readFileAsDataURL(file)
|
|
1769
|
+
images.push({ base64, originalName: file.name })
|
|
1770
|
+
}
|
|
1771
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/images', {
|
|
1772
|
+
method: 'POST',
|
|
1773
|
+
body: JSON.stringify({ images }),
|
|
1774
|
+
})
|
|
1775
|
+
byId('upload-files').value = ''
|
|
1776
|
+
showAlert('图片上传成功', 'success')
|
|
1777
|
+
await refreshCollectionResources()
|
|
1778
|
+
await refreshState()
|
|
1779
|
+
})
|
|
1780
|
+
|
|
1781
|
+
byId('add-links').addEventListener('click', async () => {
|
|
1782
|
+
if (!state.selectedCollection) return
|
|
1783
|
+
const text = byId('links-input').value
|
|
1784
|
+
const links = text.split(/\r?
|
|
1785
|
+
/g).map((line) => line.trim()).filter(Boolean)
|
|
1786
|
+
if (!links.length) return
|
|
1787
|
+
await request(BASE_PATH + '/api/admin/collections/' + encodeURIComponent(state.selectedCollection) + '/links', {
|
|
1788
|
+
method: 'POST',
|
|
1789
|
+
body: JSON.stringify({ links }),
|
|
1790
|
+
})
|
|
1791
|
+
byId('links-input').value = ''
|
|
1792
|
+
showAlert('外链添加成功', 'success')
|
|
1793
|
+
await refreshCollectionResources()
|
|
1794
|
+
await refreshState()
|
|
1795
|
+
})
|
|
1796
|
+
|
|
1797
|
+
byId('upload-files').addEventListener('change', () => {
|
|
1798
|
+
byId('upload-images').disabled = !state.selectedCollection
|
|
1799
|
+
})
|
|
1800
|
+
|
|
1801
|
+
refreshState().catch((error) => {
|
|
1802
|
+
showAlert(error instanceof Error ? error.message : String(error), 'danger')
|
|
1803
|
+
})
|
|
1804
|
+
</script>
|
|
1805
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
1806
|
+
</body>
|
|
1807
|
+
</html>`;
|
|
1808
|
+
}
|
|
1809
|
+
__name(buildAdminHtml, "buildAdminHtml");
|
|
1264
1810
|
async function updateMemesVariable(ctx, config, service) {
|
|
1265
1811
|
const baseUrl = toAbsoluteBaseUrl(ctx, config);
|
|
1266
1812
|
const inventory = await service.buildRouteInventory(config.backendPath);
|
|
1267
|
-
ctx.chatluna.promptRenderer.setVariable("endpoint", inventory || "-
|
|
1268
|
-
const memeslunaText = config.injectVariablesPrompt.replace("{endpoint}", inventory || "-
|
|
1813
|
+
ctx.chatluna.promptRenderer.setVariable("endpoint", inventory || "- 暂无可用路由");
|
|
1814
|
+
const memeslunaText = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- 暂无可用路由").replace("{base_url}", baseUrl);
|
|
1269
1815
|
ctx.chatluna.promptRenderer.setVariable("memesluna", memeslunaText);
|
|
1270
1816
|
}
|
|
1817
|
+
__name(updateMemesVariable, "updateMemesVariable");
|
|
1271
1818
|
function applyConsole(ctx, config, service) {
|
|
1272
1819
|
if (!ctx.console) return;
|
|
1273
1820
|
const consoleService = ctx.console;
|
|
1274
1821
|
const packageBase = import_path2.default.resolve(ctx.baseDir, "node_modules/koishi-plugin-memesluna");
|
|
1275
|
-
const withReady = (handler) => {
|
|
1822
|
+
const withReady = /* @__PURE__ */ __name((handler) => {
|
|
1276
1823
|
return async (...args) => {
|
|
1277
1824
|
await service.ready;
|
|
1278
1825
|
return await handler(...args);
|
|
1279
1826
|
};
|
|
1280
|
-
};
|
|
1827
|
+
}, "withReady");
|
|
1281
1828
|
consoleService.addEntry({
|
|
1282
1829
|
dev: import_path2.default.resolve(packageBase, "client/index.ts"),
|
|
1283
1830
|
prod: import_path2.default.resolve(packageBase, "dist")
|
|
@@ -1368,6 +1915,7 @@ function applyConsole(ctx, config, service) {
|
|
|
1368
1915
|
return `${toAbsoluteBaseUrl(ctx, config)}${config.backendPath}`;
|
|
1369
1916
|
});
|
|
1370
1917
|
}
|
|
1918
|
+
__name(applyConsole, "applyConsole");
|
|
1371
1919
|
function applyServer(ctx, config, service) {
|
|
1372
1920
|
if (!ctx.server) return;
|
|
1373
1921
|
const basePath = config.backendPath;
|
|
@@ -1377,7 +1925,7 @@ function applyServer(ctx, config, service) {
|
|
|
1377
1925
|
const collections = await service.getCollections();
|
|
1378
1926
|
const collectionInfos = await Promise.all(collections.map((name2) => service.getCollectionInfo(name2)));
|
|
1379
1927
|
const inventory = await service.buildRouteInventory(basePath);
|
|
1380
|
-
const llmPrompt = config.injectVariablesPrompt.replace("{endpoint}", inventory || "-
|
|
1928
|
+
const llmPrompt = config.injectVariablesPrompt.replace("{endpoint}", inventory || "- 暂无可用路由").replace("{base_url}", baseUrl);
|
|
1381
1929
|
koa.body = {
|
|
1382
1930
|
llmPrompt,
|
|
1383
1931
|
routeInventory: inventory,
|
|
@@ -1386,7 +1934,9 @@ function applyServer(ctx, config, service) {
|
|
|
1386
1934
|
};
|
|
1387
1935
|
});
|
|
1388
1936
|
ctx.server.get(`${basePath}/api/admin/state`, async (koa) => {
|
|
1389
|
-
|
|
1937
|
+
const state = await buildAdminState(service);
|
|
1938
|
+
console.log("Admin State:", JSON.stringify(state, null, 2));
|
|
1939
|
+
koa.body = state;
|
|
1390
1940
|
});
|
|
1391
1941
|
ctx.server.post(`${basePath}/api/admin/collections`, async (koa) => {
|
|
1392
1942
|
const body = getRequestBody(koa);
|
|
@@ -1548,7 +2098,7 @@ function applyServer(ctx, config, service) {
|
|
|
1548
2098
|
}
|
|
1549
2099
|
const payload = {
|
|
1550
2100
|
name: name2,
|
|
1551
|
-
group: toTrimmedString(body.group) || "
|
|
2101
|
+
group: toTrimmedString(body.group) || "默认分组",
|
|
1552
2102
|
description: toTrimmedString(body.description),
|
|
1553
2103
|
url,
|
|
1554
2104
|
method: normalizeForwardMethod(body.method),
|
|
@@ -1569,7 +2119,7 @@ function applyServer(ctx, config, service) {
|
|
|
1569
2119
|
const currentName = toTrimmedString(koa.params.name);
|
|
1570
2120
|
const body = getRequestBody(koa);
|
|
1571
2121
|
const payload = {};
|
|
1572
|
-
if (body.group !== void 0) payload.group = toTrimmedString(body.group) || "
|
|
2122
|
+
if (body.group !== void 0) payload.group = toTrimmedString(body.group) || "默认分组";
|
|
1573
2123
|
if (body.description !== void 0) payload.description = toTrimmedString(body.description);
|
|
1574
2124
|
if (body.url !== void 0) payload.url = toTrimmedString(body.url);
|
|
1575
2125
|
if (body.method !== void 0) payload.method = normalizeForwardMethod(body.method);
|
|
@@ -1599,10 +2149,9 @@ function applyServer(ctx, config, service) {
|
|
|
1599
2149
|
koa.body = { ok: true };
|
|
1600
2150
|
});
|
|
1601
2151
|
ctx.server.get(`${basePath}/admin`, async (koa) => {
|
|
1602
|
-
koa.
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
koa.redirect(`${basePath}/#endpoint`);
|
|
2152
|
+
koa.status = 200;
|
|
2153
|
+
koa.set("Content-Type", "text/html; charset=utf-8");
|
|
2154
|
+
koa.body = buildAdminHtml(basePath);
|
|
1606
2155
|
});
|
|
1607
2156
|
ctx.server.get(`${basePath}/api/collections/:name/resources`, async (koa) => {
|
|
1608
2157
|
const collectionName = koa.params.name;
|
|
@@ -1636,6 +2185,7 @@ function applyServer(ctx, config, service) {
|
|
|
1636
2185
|
setKoaResponse(koa, result);
|
|
1637
2186
|
});
|
|
1638
2187
|
}
|
|
2188
|
+
__name(applyServer, "applyServer");
|
|
1639
2189
|
function apply(ctx, config) {
|
|
1640
2190
|
ctx.plugin(MemesLunaService, config);
|
|
1641
2191
|
ctx.inject(["memesluna", "server"], async (ctx2) => {
|
|
@@ -1648,8 +2198,8 @@ function apply(ctx, config) {
|
|
|
1648
2198
|
applyConsole(ctx2, config, service);
|
|
1649
2199
|
});
|
|
1650
2200
|
ctx.inject(["memesluna"], (ctx2) => {
|
|
1651
|
-
const root = ctx2.command("memesluna", "MemesLuna
|
|
1652
|
-
root.subcommand(".list", "
|
|
2201
|
+
const root = ctx2.command("memesluna", "MemesLuna 命令");
|
|
2202
|
+
root.subcommand(".list", "查看当前可用表情路由").action(async () => {
|
|
1653
2203
|
const service = ctx2.memesluna;
|
|
1654
2204
|
await service.ready;
|
|
1655
2205
|
const [collectionNames, endpoints] = await Promise.all([
|
|
@@ -1660,14 +2210,14 @@ function apply(ctx, config) {
|
|
|
1660
2210
|
for (const collectionName of collectionNames) {
|
|
1661
2211
|
const info = await service.getCollectionInfo(collectionName);
|
|
1662
2212
|
if (!info?.hasContent) continue;
|
|
1663
|
-
lines.push(`${collectionName} ${collectionName}
|
|
2213
|
+
lines.push(`${collectionName} ${collectionName}表情包`);
|
|
1664
2214
|
}
|
|
1665
2215
|
for (const endpoint of endpoints) {
|
|
1666
|
-
const endpointLabel = endpoint.description || `${endpoint.name}
|
|
2216
|
+
const endpointLabel = endpoint.description || `${endpoint.name}端点`;
|
|
1667
2217
|
lines.push(`${endpoint.name} ${endpointLabel}`);
|
|
1668
2218
|
}
|
|
1669
2219
|
if (!lines.length) {
|
|
1670
|
-
return "
|
|
2220
|
+
return "暂无可用表情路由";
|
|
1671
2221
|
}
|
|
1672
2222
|
return lines.join("\n");
|
|
1673
2223
|
});
|
|
@@ -1676,9 +2226,9 @@ function apply(ctx, config) {
|
|
|
1676
2226
|
ctx.inject(["memesluna", "chatluna", "server"], async (ctx2) => {
|
|
1677
2227
|
const service = ctx2.memesluna;
|
|
1678
2228
|
await service.ready;
|
|
1679
|
-
const refresh = async () => {
|
|
2229
|
+
const refresh = /* @__PURE__ */ __name(async () => {
|
|
1680
2230
|
await updateMemesVariable(ctx2, config, service);
|
|
1681
|
-
};
|
|
2231
|
+
}, "refresh");
|
|
1682
2232
|
await refresh();
|
|
1683
2233
|
ctx2.setInterval(refresh, config.variableRefreshIntervalMs);
|
|
1684
2234
|
ctx2.effect(() => () => {
|
|
@@ -1689,6 +2239,7 @@ function apply(ctx, config) {
|
|
|
1689
2239
|
});
|
|
1690
2240
|
}
|
|
1691
2241
|
}
|
|
2242
|
+
__name(apply, "apply");
|
|
1692
2243
|
var inject = ["database", "chatluna", "server"];
|
|
1693
2244
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1694
2245
|
0 && (module.exports = {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-memesluna",
|
|
3
3
|
"description": "Image Forward service for Koishi with ChatLuna integration",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.5",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -82,4 +82,3 @@
|
|
|
82
82
|
"icon": "mdi:emoticon-outline"
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
|
-
|