sa2kit 1.6.91 → 1.6.92
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/client-BlkUL2To.d.ts +26 -0
- package/dist/client-DpMIhrlS.d.mts +26 -0
- package/dist/index-C7yh6b5Q.d.mts +17 -0
- package/dist/index-CDapUIT5.d.mts +51 -0
- package/dist/index-Cv9jlnNz.d.ts +17 -0
- package/dist/index-D3UbkUai.d.ts +51 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1337 -58
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1306 -61
- package/dist/index.mjs.map +1 -1
- package/dist/mikuContest/index.d.mts +13 -0
- package/dist/mikuContest/index.d.ts +13 -0
- package/dist/mikuContest/index.js +1310 -0
- package/dist/mikuContest/index.js.map +1 -0
- package/dist/mikuContest/index.mjs +1253 -0
- package/dist/mikuContest/index.mjs.map +1 -0
- package/dist/mikuContest/logic/index.d.mts +32 -0
- package/dist/mikuContest/logic/index.d.ts +32 -0
- package/dist/mikuContest/logic/index.js +511 -0
- package/dist/mikuContest/logic/index.js.map +1 -0
- package/dist/mikuContest/logic/index.mjs +483 -0
- package/dist/mikuContest/logic/index.mjs.map +1 -0
- package/dist/mikuContest/routes/index.d.mts +80 -0
- package/dist/mikuContest/routes/index.d.ts +80 -0
- package/dist/mikuContest/routes/index.js +821 -0
- package/dist/mikuContest/routes/index.js.map +1 -0
- package/dist/mikuContest/routes/index.mjs +791 -0
- package/dist/mikuContest/routes/index.mjs.map +1 -0
- package/dist/mikuContest/server/index.d.mts +766 -0
- package/dist/mikuContest/server/index.d.ts +766 -0
- package/dist/mikuContest/server/index.js +705 -0
- package/dist/mikuContest/server/index.js.map +1 -0
- package/dist/mikuContest/server/index.mjs +672 -0
- package/dist/mikuContest/server/index.mjs.map +1 -0
- package/dist/mikuContest/service/index.d.mts +30 -0
- package/dist/mikuContest/service/index.d.ts +30 -0
- package/dist/mikuContest/service/index.js +139 -0
- package/dist/mikuContest/service/index.js.map +1 -0
- package/dist/mikuContest/service/index.mjs +135 -0
- package/dist/mikuContest/service/index.mjs.map +1 -0
- package/dist/mikuContest/types/index.d.mts +179 -0
- package/dist/mikuContest/types/index.d.ts +179 -0
- package/dist/mikuContest/types/index.js +4 -0
- package/dist/mikuContest/types/index.js.map +1 -0
- package/dist/mikuContest/types/index.mjs +3 -0
- package/dist/mikuContest/types/index.mjs.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.d.mts +3 -0
- package/dist/mikuContest/ui/miniapp/index.d.ts +3 -0
- package/dist/mikuContest/ui/miniapp/index.js +566 -0
- package/dist/mikuContest/ui/miniapp/index.js.map +1 -0
- package/dist/mikuContest/ui/miniapp/index.mjs +540 -0
- package/dist/mikuContest/ui/miniapp/index.mjs.map +1 -0
- package/dist/mikuContest/ui/web/index.d.mts +4 -0
- package/dist/mikuContest/ui/web/index.d.ts +4 -0
- package/dist/mikuContest/ui/web/index.js +353 -0
- package/dist/mikuContest/ui/web/index.js.map +1 -0
- package/dist/mikuContest/ui/web/index.mjs +343 -0
- package/dist/mikuContest/ui/web/index.mjs.map +1 -0
- package/dist/service-D7DM1wW-.d.ts +38 -0
- package/dist/service-DPr2rlvH.d.mts +38 -0
- package/dist/types-BS7Xz09b.d.mts +14 -0
- package/dist/types-k4koMp4m.d.ts +14 -0
- package/package.json +41 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,8 @@ var crypto = require('crypto');
|
|
|
18
18
|
require('bcryptjs');
|
|
19
19
|
require('jsonwebtoken');
|
|
20
20
|
var THREE2 = require('three');
|
|
21
|
+
var XLSX = require('xlsx');
|
|
22
|
+
var server = require('next/server');
|
|
21
23
|
|
|
22
24
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
23
25
|
|
|
@@ -42,13 +44,19 @@ function _interopNamespace(e) {
|
|
|
42
44
|
var React69__namespace = /*#__PURE__*/_interopNamespace(React69);
|
|
43
45
|
var Link__default = /*#__PURE__*/_interopDefault(Link);
|
|
44
46
|
var THREE2__namespace = /*#__PURE__*/_interopNamespace(THREE2);
|
|
47
|
+
var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
|
|
45
48
|
|
|
49
|
+
var __defProp = Object.defineProperty;
|
|
46
50
|
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
47
51
|
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
48
52
|
}) : x)(function(x) {
|
|
49
53
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
50
54
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
51
55
|
});
|
|
56
|
+
var __export = (target, all) => {
|
|
57
|
+
for (var name in all)
|
|
58
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
59
|
+
};
|
|
52
60
|
|
|
53
61
|
// src/logger/types.ts
|
|
54
62
|
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
@@ -76,10 +84,10 @@ var ConsoleLoggerAdapter = class {
|
|
|
76
84
|
};
|
|
77
85
|
}
|
|
78
86
|
log(entry) {
|
|
79
|
-
const { level, message, timestamp:
|
|
87
|
+
const { level, message, timestamp: timestamp6, data, context, error } = entry;
|
|
80
88
|
let logMessage = "";
|
|
81
|
-
if (
|
|
82
|
-
logMessage += "[" + this.formatTimestamp(
|
|
89
|
+
if (timestamp6) {
|
|
90
|
+
logMessage += "[" + this.formatTimestamp(timestamp6) + "] ";
|
|
83
91
|
}
|
|
84
92
|
const levelName = this.getLevelName(level);
|
|
85
93
|
logMessage += levelName + ": ";
|
|
@@ -572,7 +580,7 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
572
580
|
result: null
|
|
573
581
|
});
|
|
574
582
|
const pipelineRef = React69.useRef(null);
|
|
575
|
-
const analyze = React69.useCallback(async (
|
|
583
|
+
const analyze = React69.useCallback(async (text6) => {
|
|
576
584
|
setState((prev) => ({
|
|
577
585
|
...prev,
|
|
578
586
|
isProcessing: true,
|
|
@@ -619,12 +627,12 @@ var useSentimentAnalysis = (options = {}) => {
|
|
|
619
627
|
pipelineRef.current = await pipeline("sentiment-analysis", options.model || defaultModel);
|
|
620
628
|
}
|
|
621
629
|
setState((prev) => ({ ...prev, status: "analyzing" }));
|
|
622
|
-
const output = await pipelineRef.current(
|
|
630
|
+
const output = await pipelineRef.current(text6);
|
|
623
631
|
const resultData = output[0];
|
|
624
632
|
const label = resultData.label.toLowerCase();
|
|
625
633
|
let sentiment = "neutral";
|
|
626
634
|
const negativeKeywords = ["\u7D2F", "\u60E8", "\u7EDD\u671B", "\u96BE\u53D7", "\u4F24\u5FC3", "\u5DEE", "\u574F", "\u7CDF", "\u4E0D\u884C"];
|
|
627
|
-
const hasNegativeKeyword = negativeKeywords.some((k) =>
|
|
635
|
+
const hasNegativeKeyword = negativeKeywords.some((k) => text6.includes(k));
|
|
628
636
|
if (label.includes("positive") && !hasNegativeKeyword) {
|
|
629
637
|
sentiment = "positive";
|
|
630
638
|
} else if (label.includes("negative") || label.includes("0") || hasNegativeKeyword) {
|
|
@@ -656,12 +664,12 @@ var SentimentAnalyzer = ({
|
|
|
656
664
|
className = "",
|
|
657
665
|
placeholder = "\u8F93\u5165\u4E00\u6BB5\u4E2D\u6587\u6216\u82F1\u6587\uFF0C\u5206\u6790\u5176\u60C5\u611F\u503E\u5411..."
|
|
658
666
|
}) => {
|
|
659
|
-
const [
|
|
667
|
+
const [text6, setText] = React69.useState("");
|
|
660
668
|
const { analyze, isProcessing, status, result, error } = useSentimentAnalysis();
|
|
661
669
|
const handleAnalyze = async () => {
|
|
662
|
-
if (!
|
|
670
|
+
if (!text6.trim() || isProcessing) return;
|
|
663
671
|
try {
|
|
664
|
-
const res = await analyze(
|
|
672
|
+
const res = await analyze(text6);
|
|
665
673
|
onResult?.(res);
|
|
666
674
|
} catch (err) {
|
|
667
675
|
console.error("Sentiment Analysis Error:", err);
|
|
@@ -692,7 +700,7 @@ var SentimentAnalyzer = ({
|
|
|
692
700
|
return /* @__PURE__ */ React69__namespace.default.createElement("div", { className: clsx.clsx("p-6 border rounded-xl bg-white dark:bg-gray-800 shadow-sm", className) }, /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "flex items-center gap-2 mb-4 text-gray-700 dark:text-gray-300 font-medium" }, /* @__PURE__ */ React69__namespace.default.createElement(lucideReact.MessageSquare, { size: 20 }), /* @__PURE__ */ React69__namespace.default.createElement("span", null, "\u6587\u672C\u60C5\u611F\u5206\u6790")), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React69__namespace.default.createElement(
|
|
693
701
|
"textarea",
|
|
694
702
|
{
|
|
695
|
-
value:
|
|
703
|
+
value: text6,
|
|
696
704
|
onChange: (e) => setText(e.target.value),
|
|
697
705
|
placeholder,
|
|
698
706
|
className: "w-full h-32 p-4 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all outline-none resize-none text-gray-800 dark:text-gray-200",
|
|
@@ -702,7 +710,7 @@ var SentimentAnalyzer = ({
|
|
|
702
710
|
"button",
|
|
703
711
|
{
|
|
704
712
|
onClick: handleAnalyze,
|
|
705
|
-
disabled: !
|
|
713
|
+
disabled: !text6.trim() || isProcessing,
|
|
706
714
|
className: "absolute bottom-3 right-3 p-2 bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 text-white rounded-md transition-colors shadow-sm"
|
|
707
715
|
},
|
|
708
716
|
isProcessing ? /* @__PURE__ */ React69__namespace.default.createElement(lucideReact.Loader2, { className: "animate-spin", size: 18 }) : /* @__PURE__ */ React69__namespace.default.createElement(lucideReact.Send, { size: 18 })
|
|
@@ -890,13 +898,13 @@ var requestJson = async (options) => {
|
|
|
890
898
|
body: body ? JSON.stringify(body) : void 0,
|
|
891
899
|
signal: controller?.signal
|
|
892
900
|
});
|
|
893
|
-
const
|
|
901
|
+
const text6 = await response.text();
|
|
894
902
|
let data = null;
|
|
895
|
-
if (
|
|
903
|
+
if (text6) {
|
|
896
904
|
try {
|
|
897
|
-
data = JSON.parse(
|
|
905
|
+
data = JSON.parse(text6);
|
|
898
906
|
} catch {
|
|
899
|
-
data =
|
|
907
|
+
data = text6;
|
|
900
908
|
}
|
|
901
909
|
}
|
|
902
910
|
if (!response.ok) {
|
|
@@ -1357,20 +1365,20 @@ var japaneseUtils = {
|
|
|
1357
1365
|
/**
|
|
1358
1366
|
* 提取文本中的汉字
|
|
1359
1367
|
*/
|
|
1360
|
-
extractKanji(
|
|
1361
|
-
return
|
|
1368
|
+
extractKanji(text6) {
|
|
1369
|
+
return text6.match(/[\u4E00-\u9FAF]/g) || [];
|
|
1362
1370
|
},
|
|
1363
1371
|
/**
|
|
1364
1372
|
* 提取文本中的假名
|
|
1365
1373
|
*/
|
|
1366
|
-
extractKana(
|
|
1367
|
-
return
|
|
1374
|
+
extractKana(text6) {
|
|
1375
|
+
return text6.match(/[\u3040-\u309F\u30A0-\u30FF]/g) || [];
|
|
1368
1376
|
},
|
|
1369
1377
|
/**
|
|
1370
1378
|
* 清理文本,移除特殊字符但保留日语字符
|
|
1371
1379
|
*/
|
|
1372
|
-
cleanText(
|
|
1373
|
-
return
|
|
1380
|
+
cleanText(text6) {
|
|
1381
|
+
return text6.replace(/[^\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF\w\s]/g, "");
|
|
1374
1382
|
}
|
|
1375
1383
|
};
|
|
1376
1384
|
|
|
@@ -1459,11 +1467,11 @@ var fileUtils = {
|
|
|
1459
1467
|
* 生成唯一文件名
|
|
1460
1468
|
*/
|
|
1461
1469
|
generateUniqueFileName(originalName) {
|
|
1462
|
-
const
|
|
1470
|
+
const timestamp6 = Date.now();
|
|
1463
1471
|
const random = Math.random().toString(36).substring(2, 15);
|
|
1464
1472
|
const extension = this.getFileExtension(originalName);
|
|
1465
1473
|
const baseName = originalName.replace("." + extension, "");
|
|
1466
|
-
return extension ? baseName + "_" +
|
|
1474
|
+
return extension ? baseName + "_" + timestamp6 + "_" + random + "." + extension : baseName + "_" + timestamp6 + "_" + random;
|
|
1467
1475
|
},
|
|
1468
1476
|
/**
|
|
1469
1477
|
* 验证文件名是否有效
|
|
@@ -1533,28 +1541,28 @@ var stringUtils = {
|
|
|
1533
1541
|
/**
|
|
1534
1542
|
* 截断文本
|
|
1535
1543
|
*/
|
|
1536
|
-
truncate(
|
|
1537
|
-
if (
|
|
1538
|
-
return
|
|
1544
|
+
truncate(text6, length, suffix = "...") {
|
|
1545
|
+
if (text6.length <= length) return text6;
|
|
1546
|
+
return text6.substring(0, length - suffix.length) + suffix;
|
|
1539
1547
|
},
|
|
1540
1548
|
/**
|
|
1541
1549
|
* 首字母大写
|
|
1542
1550
|
*/
|
|
1543
|
-
capitalize(
|
|
1544
|
-
if (!
|
|
1545
|
-
return
|
|
1551
|
+
capitalize(text6) {
|
|
1552
|
+
if (!text6) return "";
|
|
1553
|
+
return text6.charAt(0).toUpperCase() + text6.slice(1).toLowerCase();
|
|
1546
1554
|
},
|
|
1547
1555
|
/**
|
|
1548
1556
|
* 驼峰转下划线
|
|
1549
1557
|
*/
|
|
1550
|
-
camelToSnake(
|
|
1551
|
-
return
|
|
1558
|
+
camelToSnake(text6) {
|
|
1559
|
+
return text6.replace(/[A-Z]/g, (letter) => "_" + letter.toLowerCase());
|
|
1552
1560
|
},
|
|
1553
1561
|
/**
|
|
1554
1562
|
* 下划线转驼峰
|
|
1555
1563
|
*/
|
|
1556
|
-
snakeToCamel(
|
|
1557
|
-
return
|
|
1564
|
+
snakeToCamel(text6) {
|
|
1565
|
+
return text6.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
1558
1566
|
},
|
|
1559
1567
|
/**
|
|
1560
1568
|
* 生成随机字符串
|
|
@@ -5673,9 +5681,9 @@ var UserInfoBar = ({ apiClient }) => {
|
|
|
5673
5681
|
))));
|
|
5674
5682
|
};
|
|
5675
5683
|
function DanmakuPanel({ onSend }) {
|
|
5676
|
-
const [
|
|
5684
|
+
const [text6, setText] = React69.useState("");
|
|
5677
5685
|
const emit = () => {
|
|
5678
|
-
const value =
|
|
5686
|
+
const value = text6.trim();
|
|
5679
5687
|
if (!value) {
|
|
5680
5688
|
return;
|
|
5681
5689
|
}
|
|
@@ -5686,7 +5694,7 @@ function DanmakuPanel({ onSend }) {
|
|
|
5686
5694
|
"input",
|
|
5687
5695
|
{
|
|
5688
5696
|
type: "text",
|
|
5689
|
-
value:
|
|
5697
|
+
value: text6,
|
|
5690
5698
|
onChange: (event) => setText(event.target.value),
|
|
5691
5699
|
onKeyDown: (event) => {
|
|
5692
5700
|
if (event.key === "Enter") {
|
|
@@ -5802,8 +5810,8 @@ function useDanmakuController(options) {
|
|
|
5802
5810
|
cursorRef.current += 1;
|
|
5803
5811
|
}, []);
|
|
5804
5812
|
const send = React69.useCallback(
|
|
5805
|
-
(
|
|
5806
|
-
const trimmed =
|
|
5813
|
+
(text6, color, sendOptions) => {
|
|
5814
|
+
const trimmed = text6.trim();
|
|
5807
5815
|
if (!trimmed) {
|
|
5808
5816
|
return null;
|
|
5809
5817
|
}
|
|
@@ -5840,26 +5848,26 @@ function useDanmakuController(options) {
|
|
|
5840
5848
|
[addIncoming, items, removeItem, send]
|
|
5841
5849
|
);
|
|
5842
5850
|
}
|
|
5843
|
-
function parseCommand(
|
|
5844
|
-
if (
|
|
5845
|
-
return { launchKind: "miku", content:
|
|
5851
|
+
function parseCommand(text6) {
|
|
5852
|
+
if (text6.startsWith("/miku ")) {
|
|
5853
|
+
return { launchKind: "miku", content: text6.replace("/miku ", "").trim() };
|
|
5846
5854
|
}
|
|
5847
|
-
if (
|
|
5855
|
+
if (text6 === "/miku") {
|
|
5848
5856
|
return { launchKind: "miku", content: "MIKU!" };
|
|
5849
5857
|
}
|
|
5850
|
-
if (
|
|
5851
|
-
return { launchKind: "avatar", content:
|
|
5858
|
+
if (text6.startsWith("/avatar ")) {
|
|
5859
|
+
return { launchKind: "avatar", content: text6.replace("/avatar ", "").trim() };
|
|
5852
5860
|
}
|
|
5853
|
-
if (
|
|
5861
|
+
if (text6 === "/avatar") {
|
|
5854
5862
|
return { launchKind: "avatar", content: "Avatar Firework!" };
|
|
5855
5863
|
}
|
|
5856
|
-
if (
|
|
5857
|
-
return { launchKind: "normal", content:
|
|
5864
|
+
if (text6.startsWith("/normal ")) {
|
|
5865
|
+
return { launchKind: "normal", content: text6.replace("/normal ", "").trim() };
|
|
5858
5866
|
}
|
|
5859
|
-
if (
|
|
5867
|
+
if (text6 === "/normal") {
|
|
5860
5868
|
return { launchKind: "normal", content: "Fireworks!" };
|
|
5861
5869
|
}
|
|
5862
|
-
return { content:
|
|
5870
|
+
return { content: text6 };
|
|
5863
5871
|
}
|
|
5864
5872
|
function createCircularSpriteTexture() {
|
|
5865
5873
|
const size = 64;
|
|
@@ -6541,12 +6549,12 @@ var WebSocketTransport = class {
|
|
|
6541
6549
|
}
|
|
6542
6550
|
};
|
|
6543
6551
|
function parseServerMessage(raw) {
|
|
6544
|
-
const
|
|
6545
|
-
if (!
|
|
6552
|
+
const text6 = decodeMessage(raw);
|
|
6553
|
+
if (!text6) {
|
|
6546
6554
|
return null;
|
|
6547
6555
|
}
|
|
6548
6556
|
try {
|
|
6549
|
-
return JSON.parse(
|
|
6557
|
+
return JSON.parse(text6);
|
|
6550
6558
|
} catch {
|
|
6551
6559
|
return null;
|
|
6552
6560
|
}
|
|
@@ -6813,8 +6821,8 @@ function MikuFireworks3D({
|
|
|
6813
6821
|
}
|
|
6814
6822
|
launch(payload);
|
|
6815
6823
|
};
|
|
6816
|
-
const handleSendDanmaku = (
|
|
6817
|
-
const result = send(
|
|
6824
|
+
const handleSendDanmaku = (text6) => {
|
|
6825
|
+
const result = send(text6, void 0, {
|
|
6818
6826
|
optimistic: !realtimeEnabled
|
|
6819
6827
|
});
|
|
6820
6828
|
if (!result) {
|
|
@@ -6913,9 +6921,9 @@ function useScreenReceiver(options) {
|
|
|
6913
6921
|
const peerRef = React69.useRef({ pendingCandidates: [] });
|
|
6914
6922
|
const videoRef = React69.useRef(null);
|
|
6915
6923
|
const appendLog = React69.useCallback(
|
|
6916
|
-
(
|
|
6924
|
+
(text6) => {
|
|
6917
6925
|
logIdRef.current += 1;
|
|
6918
|
-
setLogs((prev) => [...prev, { id: logIdRef.current, text:
|
|
6926
|
+
setLogs((prev) => [...prev, { id: logIdRef.current, text: text6 }].slice(-maxLogs));
|
|
6919
6927
|
},
|
|
6920
6928
|
[maxLogs]
|
|
6921
6929
|
);
|
|
@@ -7641,8 +7649,8 @@ var withRoundedClip = (ctx, left, top, width, height, radius, draw) => {
|
|
|
7641
7649
|
draw();
|
|
7642
7650
|
ctx.restore();
|
|
7643
7651
|
};
|
|
7644
|
-
var drawMultilineText = (ctx,
|
|
7645
|
-
const paragraphs =
|
|
7652
|
+
var drawMultilineText = (ctx, text6, left, top, maxWidth, lineHeight) => {
|
|
7653
|
+
const paragraphs = text6.split("\n");
|
|
7646
7654
|
let currentY = top;
|
|
7647
7655
|
paragraphs.forEach((paragraph, index) => {
|
|
7648
7656
|
const words = paragraph.split("");
|
|
@@ -8799,6 +8807,1244 @@ var BoothConfigPage = ({ initialConfig, onSave }) => {
|
|
|
8799
8807
|
)), /* @__PURE__ */ React69__namespace.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React69__namespace.default.createElement("button", { className: "rounded bg-indigo-600 px-3 py-2 text-white", disabled: saving, onClick: save }, saving ? "\u4FDD\u5B58\u4E2D..." : "\u4FDD\u5B58\u914D\u7F6E"), /* @__PURE__ */ React69__namespace.default.createElement("button", { className: "rounded border px-3 py-2", onClick: reset }, "\u6062\u590D\u9ED8\u8BA4")));
|
|
8800
8808
|
};
|
|
8801
8809
|
|
|
8810
|
+
// src/mikuContest/logic/shared/defaults.ts
|
|
8811
|
+
var defaultMikuVotingRules = {
|
|
8812
|
+
maxVotesPerDay: 3,
|
|
8813
|
+
forbidDuplicateVotePerWork: true,
|
|
8814
|
+
maxVotesPerDevicePerDay: 20,
|
|
8815
|
+
maxVotesPerIpPerDay: 100
|
|
8816
|
+
};
|
|
8817
|
+
var createDefaultMikuContestConfig = (overrides) => ({
|
|
8818
|
+
id: overrides?.id || "miku-contest-default",
|
|
8819
|
+
name: overrides?.name || "\u521D\u97F3\u672A\u6765\u793E\u56E2\u5F81\u7A3F\u5927\u8D5B",
|
|
8820
|
+
theme: overrides?.theme || "\u521D\u97F3\u672A\u6765\u4E3B\u9898\u521B\u4F5C\u5F81\u7A3F",
|
|
8821
|
+
organizer: overrides?.organizer || "\u521D\u97F3\u672A\u6765\u793E\u56E2",
|
|
8822
|
+
awards: overrides?.awards || ["\u4E00\u7B49\u5956", "\u4E8C\u7B49\u5956", "\u4E09\u7B49\u5956", "\u4EBA\u6C14\u5956"],
|
|
8823
|
+
rules: overrides?.rules || "\u8BF7\u786E\u4FDD\u4F5C\u54C1\u539F\u521B\u4E14\u7B26\u5408\u793E\u56E2\u89C4\u8303\u3002",
|
|
8824
|
+
copyright: overrides?.copyright || "\u6295\u7A3F\u5373\u89C6\u4E3A\u6388\u6743\u8D5B\u4E8B\u5C55\u793A\u4E0E\u516C\u793A\u3002",
|
|
8825
|
+
timeline: overrides?.timeline || {
|
|
8826
|
+
submissionStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8827
|
+
submissionEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8828
|
+
votingStartAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8829
|
+
votingEndAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8830
|
+
publicResultAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8831
|
+
},
|
|
8832
|
+
votingRules: {
|
|
8833
|
+
...defaultMikuVotingRules,
|
|
8834
|
+
...overrides?.votingRules || {}
|
|
8835
|
+
},
|
|
8836
|
+
toggles: {
|
|
8837
|
+
submissionEnabled: overrides?.toggles?.submissionEnabled ?? true,
|
|
8838
|
+
votingEnabled: overrides?.toggles?.votingEnabled ?? true,
|
|
8839
|
+
resultEnabled: overrides?.toggles?.resultEnabled ?? false
|
|
8840
|
+
}
|
|
8841
|
+
});
|
|
8842
|
+
|
|
8843
|
+
// src/mikuContest/logic/shared/validators.ts
|
|
8844
|
+
var DESCRIPTION_LIMIT = 500;
|
|
8845
|
+
var MINIAPP_DESCRIPTION_LIMIT = 200;
|
|
8846
|
+
var TEXT_CONTENT_LIMIT = 2e3;
|
|
8847
|
+
var MAX_TAGS = 3;
|
|
8848
|
+
var hasValue = (value) => {
|
|
8849
|
+
return typeof value === "string" ? value.trim().length > 0 : value !== null && value !== void 0;
|
|
8850
|
+
};
|
|
8851
|
+
var validateByType = (type, input) => {
|
|
8852
|
+
const errors = [];
|
|
8853
|
+
const { content } = input;
|
|
8854
|
+
switch (type) {
|
|
8855
|
+
case "visual": {
|
|
8856
|
+
const imageCount = content.images?.length || 0;
|
|
8857
|
+
if (imageCount < 1 || imageCount > 3) {
|
|
8858
|
+
errors.push("\u89C6\u89C9\u7C7B\u4F5C\u54C1\u9700\u4E0A\u4F20 1-3 \u5F20\u56FE\u7247");
|
|
8859
|
+
}
|
|
8860
|
+
break;
|
|
8861
|
+
}
|
|
8862
|
+
case "video": {
|
|
8863
|
+
if (!hasValue(content.videoLink)) {
|
|
8864
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u89C6\u9891\u94FE\u63A5");
|
|
8865
|
+
}
|
|
8866
|
+
if (!hasValue(content.coverImage)) {
|
|
8867
|
+
errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u5C01\u9762\u56FE");
|
|
8868
|
+
}
|
|
8869
|
+
break;
|
|
8870
|
+
}
|
|
8871
|
+
case "text": {
|
|
8872
|
+
const text6 = content.textContent || "";
|
|
8873
|
+
if (!text6.trim()) {
|
|
8874
|
+
errors.push("\u6587\u5B57\u7C7B\u4F5C\u54C1\u9700\u586B\u5199\u6B63\u6587");
|
|
8875
|
+
}
|
|
8876
|
+
if (text6.length > TEXT_CONTENT_LIMIT) {
|
|
8877
|
+
errors.push(`\u6587\u5B57\u6B63\u6587\u4E0D\u80FD\u8D85\u8FC7 ${TEXT_CONTENT_LIMIT} \u5B57`);
|
|
8878
|
+
}
|
|
8879
|
+
break;
|
|
8880
|
+
}
|
|
8881
|
+
case "audio": {
|
|
8882
|
+
if (!hasValue(content.audioLink)) {
|
|
8883
|
+
errors.push("\u97F3\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u97F3\u9891\u94FE\u63A5");
|
|
8884
|
+
}
|
|
8885
|
+
break;
|
|
8886
|
+
}
|
|
8887
|
+
}
|
|
8888
|
+
return errors;
|
|
8889
|
+
};
|
|
8890
|
+
var validateMikuSubmissionInput = (input, mode = "web") => {
|
|
8891
|
+
const errors = [];
|
|
8892
|
+
if (!input.contestId.trim()) errors.push("contestId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8893
|
+
if (!input.authorId.trim()) errors.push("authorId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
8894
|
+
if (!input.authorNickname.trim()) errors.push("\u4F5C\u8005\u6635\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8895
|
+
if (!input.title.trim()) errors.push("\u4F5C\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
|
|
8896
|
+
const descriptionLimit = mode === "miniapp" ? MINIAPP_DESCRIPTION_LIMIT : DESCRIPTION_LIMIT;
|
|
8897
|
+
if (input.description.length > descriptionLimit) {
|
|
8898
|
+
errors.push(`\u4F5C\u54C1\u7B80\u4ECB\u4E0D\u80FD\u8D85\u8FC7 ${descriptionLimit} \u5B57`);
|
|
8899
|
+
}
|
|
8900
|
+
if ((input.tags?.length || 0) > MAX_TAGS) {
|
|
8901
|
+
errors.push(`\u6807\u7B7E\u6700\u591A ${MAX_TAGS} \u4E2A`);
|
|
8902
|
+
}
|
|
8903
|
+
errors.push(...validateByType(input.type, input));
|
|
8904
|
+
return errors;
|
|
8905
|
+
};
|
|
8906
|
+
|
|
8907
|
+
// src/mikuContest/logic/shared/voting.ts
|
|
8908
|
+
var toVoteDayKey = (date = /* @__PURE__ */ new Date()) => {
|
|
8909
|
+
const y = date.getUTCFullYear();
|
|
8910
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
8911
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
8912
|
+
return `${y}-${m}-${d}`;
|
|
8913
|
+
};
|
|
8914
|
+
var checkVoteEligibility = (context) => {
|
|
8915
|
+
const { existingVotes, submissionId, voterId, dayKey, rules } = context;
|
|
8916
|
+
const userTodayVotes = existingVotes.filter((vote) => vote.voterId === voterId && vote.dayKey === dayKey);
|
|
8917
|
+
if (userTodayVotes.length >= rules.maxVotesPerDay) {
|
|
8918
|
+
return { ok: false, reason: "\u5DF2\u8FBE\u5230\u4ECA\u65E5\u6295\u7968\u4E0A\u9650" };
|
|
8919
|
+
}
|
|
8920
|
+
if (rules.forbidDuplicateVotePerWork) {
|
|
8921
|
+
const duplicated = userTodayVotes.some((vote) => vote.submissionId === submissionId);
|
|
8922
|
+
if (duplicated) return { ok: false, reason: "\u4E0D\u53EF\u91CD\u590D\u6295\u540C\u4E00\u4F5C\u54C1" };
|
|
8923
|
+
}
|
|
8924
|
+
return { ok: true };
|
|
8925
|
+
};
|
|
8926
|
+
var sortByVotesDesc = (items) => {
|
|
8927
|
+
return [...items].sort((a, b) => {
|
|
8928
|
+
if (b.voteCount === a.voteCount) {
|
|
8929
|
+
return (a.createdAt || "").localeCompare(b.createdAt || "");
|
|
8930
|
+
}
|
|
8931
|
+
return b.voteCount - a.voteCount;
|
|
8932
|
+
});
|
|
8933
|
+
};
|
|
8934
|
+
var randomId = (prefix) => {
|
|
8935
|
+
return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
8936
|
+
};
|
|
8937
|
+
var serialNo = () => {
|
|
8938
|
+
const now = /* @__PURE__ */ new Date();
|
|
8939
|
+
const y = now.getFullYear();
|
|
8940
|
+
const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
8941
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
8942
|
+
const seq = Math.floor(Math.random() * 9e3 + 1e3);
|
|
8943
|
+
return `MIKU-${y}${m}${d}-${seq}`;
|
|
8944
|
+
};
|
|
8945
|
+
var MikuContestService = class {
|
|
8946
|
+
constructor(options = {}) {
|
|
8947
|
+
this.submissions = /* @__PURE__ */ new Map();
|
|
8948
|
+
this.votes = [];
|
|
8949
|
+
this.announcements = /* @__PURE__ */ new Map();
|
|
8950
|
+
this.voterRestrictions = /* @__PURE__ */ new Map();
|
|
8951
|
+
this.contest = createDefaultMikuContestConfig(options.contestConfig);
|
|
8952
|
+
}
|
|
8953
|
+
getContestConfig() {
|
|
8954
|
+
return this.contest;
|
|
8955
|
+
}
|
|
8956
|
+
updateContestConfig(patch) {
|
|
8957
|
+
this.contest = {
|
|
8958
|
+
...this.contest,
|
|
8959
|
+
...patch,
|
|
8960
|
+
votingRules: {
|
|
8961
|
+
...this.contest.votingRules,
|
|
8962
|
+
...patch.votingRules || {}
|
|
8963
|
+
},
|
|
8964
|
+
toggles: {
|
|
8965
|
+
...this.contest.toggles,
|
|
8966
|
+
...patch.toggles || {}
|
|
8967
|
+
},
|
|
8968
|
+
timeline: {
|
|
8969
|
+
...this.contest.timeline,
|
|
8970
|
+
...patch.timeline || {}
|
|
8971
|
+
}
|
|
8972
|
+
};
|
|
8973
|
+
return this.contest;
|
|
8974
|
+
}
|
|
8975
|
+
createSubmission(input, mode = "web") {
|
|
8976
|
+
if (!this.contest.toggles.submissionEnabled) {
|
|
8977
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7A3F");
|
|
8978
|
+
}
|
|
8979
|
+
const errors = validateMikuSubmissionInput(input, mode);
|
|
8980
|
+
if (errors.length > 0) {
|
|
8981
|
+
throw new Error(errors.join("\uFF1B"));
|
|
8982
|
+
}
|
|
8983
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8984
|
+
const next = {
|
|
8985
|
+
id: randomId("submission"),
|
|
8986
|
+
serialNo: serialNo(),
|
|
8987
|
+
contestId: input.contestId,
|
|
8988
|
+
authorId: input.authorId,
|
|
8989
|
+
authorNickname: input.authorNickname,
|
|
8990
|
+
title: input.title,
|
|
8991
|
+
type: input.type,
|
|
8992
|
+
description: input.description,
|
|
8993
|
+
tags: input.tags || [],
|
|
8994
|
+
content: input.content,
|
|
8995
|
+
voteCount: 0,
|
|
8996
|
+
status: "pending",
|
|
8997
|
+
createdAt: now,
|
|
8998
|
+
updatedAt: now
|
|
8999
|
+
};
|
|
9000
|
+
this.submissions.set(next.id, next);
|
|
9001
|
+
return next;
|
|
9002
|
+
}
|
|
9003
|
+
listSubmissions(filter) {
|
|
9004
|
+
const authorKeyword = filter?.authorKeyword?.trim().toLowerCase();
|
|
9005
|
+
const titleKeyword = filter?.titleKeyword?.trim().toLowerCase();
|
|
9006
|
+
return [...this.submissions.values()].filter((item) => {
|
|
9007
|
+
if (filter?.status && item.status !== filter.status) return false;
|
|
9008
|
+
if (filter?.type && item.type !== filter.type) return false;
|
|
9009
|
+
if (filter?.authorId && item.authorId !== filter.authorId) return false;
|
|
9010
|
+
if (authorKeyword && !item.authorNickname.toLowerCase().includes(authorKeyword)) return false;
|
|
9011
|
+
if (titleKeyword && !item.title.toLowerCase().includes(titleKeyword)) return false;
|
|
9012
|
+
return true;
|
|
9013
|
+
});
|
|
9014
|
+
}
|
|
9015
|
+
getSubmission(submissionId) {
|
|
9016
|
+
return this.submissions.get(submissionId) || null;
|
|
9017
|
+
}
|
|
9018
|
+
reviewSubmission(input) {
|
|
9019
|
+
const current = this.submissions.get(input.submissionId);
|
|
9020
|
+
if (!current) throw new Error("\u6295\u7A3F\u4E0D\u5B58\u5728");
|
|
9021
|
+
if (input.action === "reject" && !input.rejectReason?.trim()) {
|
|
9022
|
+
throw new Error("\u9A73\u56DE\u9700\u586B\u5199\u539F\u56E0");
|
|
9023
|
+
}
|
|
9024
|
+
const reviewed = {
|
|
9025
|
+
...current,
|
|
9026
|
+
status: input.action === "approve" ? "approved" : "rejected",
|
|
9027
|
+
rejectReason: input.action === "reject" ? input.rejectReason?.trim() : void 0,
|
|
9028
|
+
reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9029
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9030
|
+
};
|
|
9031
|
+
this.submissions.set(reviewed.id, reviewed);
|
|
9032
|
+
return reviewed;
|
|
9033
|
+
}
|
|
9034
|
+
vote(input) {
|
|
9035
|
+
if (!this.contest.toggles.votingEnabled) {
|
|
9036
|
+
throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7968");
|
|
9037
|
+
}
|
|
9038
|
+
const restriction = this.voterRestrictions.get(input.voterId);
|
|
9039
|
+
if (restriction?.banned) {
|
|
9040
|
+
throw new Error("\u5F53\u524D\u8D26\u53F7\u5DF2\u88AB\u9650\u5236\u6295\u7968");
|
|
9041
|
+
}
|
|
9042
|
+
const target = this.submissions.get(input.submissionId);
|
|
9043
|
+
if (!target) throw new Error("\u4F5C\u54C1\u4E0D\u5B58\u5728");
|
|
9044
|
+
if (target.status !== "approved") throw new Error("\u4EC5\u53EF\u5BF9\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\u6295\u7968");
|
|
9045
|
+
const dayKey = toVoteDayKey();
|
|
9046
|
+
const eligible = checkVoteEligibility({
|
|
9047
|
+
existingVotes: this.votes,
|
|
9048
|
+
submissionId: input.submissionId,
|
|
9049
|
+
voterId: input.voterId,
|
|
9050
|
+
dayKey,
|
|
9051
|
+
rules: this.contest.votingRules
|
|
9052
|
+
});
|
|
9053
|
+
if (!eligible.ok) {
|
|
9054
|
+
throw new Error(eligible.reason || "\u6295\u7968\u5931\u8D25");
|
|
9055
|
+
}
|
|
9056
|
+
const vote = {
|
|
9057
|
+
id: randomId("vote"),
|
|
9058
|
+
contestId: input.contestId,
|
|
9059
|
+
submissionId: input.submissionId,
|
|
9060
|
+
voterId: input.voterId,
|
|
9061
|
+
votedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9062
|
+
dayKey,
|
|
9063
|
+
deviceId: input.deviceId,
|
|
9064
|
+
ip: input.ip
|
|
9065
|
+
};
|
|
9066
|
+
this.votes.push(vote);
|
|
9067
|
+
const updated = {
|
|
9068
|
+
...target,
|
|
9069
|
+
voteCount: target.voteCount + 1,
|
|
9070
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9071
|
+
};
|
|
9072
|
+
this.submissions.set(updated.id, updated);
|
|
9073
|
+
return updated;
|
|
9074
|
+
}
|
|
9075
|
+
getVoterRestriction(voterId) {
|
|
9076
|
+
return this.voterRestrictions.get(voterId) || null;
|
|
9077
|
+
}
|
|
9078
|
+
setVoterRestriction(input) {
|
|
9079
|
+
const next = {
|
|
9080
|
+
voterId: input.voterId,
|
|
9081
|
+
banned: input.banned,
|
|
9082
|
+
reason: input.reason,
|
|
9083
|
+
operatorId: input.operatorId,
|
|
9084
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9085
|
+
};
|
|
9086
|
+
this.voterRestrictions.set(input.voterId, next);
|
|
9087
|
+
return next;
|
|
9088
|
+
}
|
|
9089
|
+
resetVotes(input) {
|
|
9090
|
+
if (!input.submissionId && !input.voterId) {
|
|
9091
|
+
throw new Error("submissionId \u4E0E voterId \u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A");
|
|
9092
|
+
}
|
|
9093
|
+
const before = this.votes.length;
|
|
9094
|
+
const affected = /* @__PURE__ */ new Set();
|
|
9095
|
+
const remained = this.votes.filter((vote) => {
|
|
9096
|
+
const matchSubmission = input.submissionId ? vote.submissionId === input.submissionId : true;
|
|
9097
|
+
const matchVoter = input.voterId ? vote.voterId === input.voterId : true;
|
|
9098
|
+
const shouldRemove = matchSubmission && matchVoter;
|
|
9099
|
+
if (shouldRemove) affected.add(vote.submissionId);
|
|
9100
|
+
return !shouldRemove;
|
|
9101
|
+
});
|
|
9102
|
+
this.votes.length = 0;
|
|
9103
|
+
this.votes.push(...remained);
|
|
9104
|
+
this.recalculateVoteCounts();
|
|
9105
|
+
return {
|
|
9106
|
+
removedVotes: before - remained.length,
|
|
9107
|
+
affectedSubmissions: [...affected]
|
|
9108
|
+
};
|
|
9109
|
+
}
|
|
9110
|
+
listAnnouncements(contestId) {
|
|
9111
|
+
const all = [...this.announcements.values()];
|
|
9112
|
+
return contestId ? all.filter((item) => item.contestId === contestId) : all;
|
|
9113
|
+
}
|
|
9114
|
+
publishAnnouncement(input) {
|
|
9115
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9116
|
+
const announcement = {
|
|
9117
|
+
id: randomId("notice"),
|
|
9118
|
+
contestId: input.contestId,
|
|
9119
|
+
title: input.title,
|
|
9120
|
+
content: input.content,
|
|
9121
|
+
type: input.type,
|
|
9122
|
+
createdBy: input.createdBy,
|
|
9123
|
+
createdAt: now,
|
|
9124
|
+
updatedAt: now
|
|
9125
|
+
};
|
|
9126
|
+
this.announcements.set(announcement.id, announcement);
|
|
9127
|
+
return announcement;
|
|
9128
|
+
}
|
|
9129
|
+
getLeaderboard(limit = 10) {
|
|
9130
|
+
const ranked = sortByVotesDesc(this.listSubmissions({ status: "approved" })).slice(0, limit);
|
|
9131
|
+
return ranked.map((item, index) => ({
|
|
9132
|
+
submissionId: item.id,
|
|
9133
|
+
title: item.title,
|
|
9134
|
+
authorNickname: item.authorNickname,
|
|
9135
|
+
voteCount: item.voteCount,
|
|
9136
|
+
rank: index + 1
|
|
9137
|
+
}));
|
|
9138
|
+
}
|
|
9139
|
+
getSnapshot() {
|
|
9140
|
+
return {
|
|
9141
|
+
contest: this.contest,
|
|
9142
|
+
submissions: this.listSubmissions(),
|
|
9143
|
+
announcements: this.listAnnouncements(),
|
|
9144
|
+
leaderboard: this.getLeaderboard()
|
|
9145
|
+
};
|
|
9146
|
+
}
|
|
9147
|
+
getSubmissionExportRows(filter) {
|
|
9148
|
+
return this.listSubmissions(filter).map((item) => ({
|
|
9149
|
+
\u6295\u7A3F\u7F16\u53F7: item.serialNo,
|
|
9150
|
+
\u6295\u7A3FID: item.id,
|
|
9151
|
+
\u8D5B\u4E8BID: item.contestId,
|
|
9152
|
+
\u4F5C\u8005ID: item.authorId,
|
|
9153
|
+
\u4F5C\u8005\u6635\u79F0: item.authorNickname,
|
|
9154
|
+
\u4F5C\u54C1\u540D\u79F0: item.title,
|
|
9155
|
+
\u4F5C\u54C1\u7C7B\u578B: item.type,
|
|
9156
|
+
\u7B80\u4ECB: item.description,
|
|
9157
|
+
\u6807\u7B7E: item.tags.join(","),
|
|
9158
|
+
\u5BA1\u6838\u72B6\u6001: item.status,
|
|
9159
|
+
\u9A73\u56DE\u539F\u56E0: item.rejectReason || "",
|
|
9160
|
+
\u7968\u6570: item.voteCount,
|
|
9161
|
+
\u63D0\u4EA4\u65F6\u95F4: item.createdAt,
|
|
9162
|
+
\u66F4\u65B0\u65F6\u95F4: item.updatedAt
|
|
9163
|
+
}));
|
|
9164
|
+
}
|
|
9165
|
+
exportSubmissionExcel(filter) {
|
|
9166
|
+
const rows = this.getSubmissionExportRows(filter);
|
|
9167
|
+
const workbook = XLSX__namespace.utils.book_new();
|
|
9168
|
+
const worksheet = XLSX__namespace.utils.json_to_sheet(rows);
|
|
9169
|
+
XLSX__namespace.utils.book_append_sheet(workbook, worksheet, "submissions");
|
|
9170
|
+
return XLSX__namespace.write(workbook, { bookType: "xlsx", type: "buffer" });
|
|
9171
|
+
}
|
|
9172
|
+
recalculateVoteCounts() {
|
|
9173
|
+
const counts = /* @__PURE__ */ new Map();
|
|
9174
|
+
for (const vote of this.votes) {
|
|
9175
|
+
counts.set(vote.submissionId, (counts.get(vote.submissionId) || 0) + 1);
|
|
9176
|
+
}
|
|
9177
|
+
for (const [id, submission] of this.submissions.entries()) {
|
|
9178
|
+
const nextCount = counts.get(id) || 0;
|
|
9179
|
+
if (submission.voteCount === nextCount) continue;
|
|
9180
|
+
this.submissions.set(id, {
|
|
9181
|
+
...submission,
|
|
9182
|
+
voteCount: nextCount,
|
|
9183
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9184
|
+
});
|
|
9185
|
+
}
|
|
9186
|
+
}
|
|
9187
|
+
exportPersistenceState() {
|
|
9188
|
+
return {
|
|
9189
|
+
contest: this.contest,
|
|
9190
|
+
submissions: [...this.submissions.values()],
|
|
9191
|
+
votes: [...this.votes],
|
|
9192
|
+
announcements: [...this.announcements.values()],
|
|
9193
|
+
voterRestrictions: [...this.voterRestrictions.values()],
|
|
9194
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9195
|
+
};
|
|
9196
|
+
}
|
|
9197
|
+
importPersistenceState(state) {
|
|
9198
|
+
this.contest = state.contest;
|
|
9199
|
+
this.submissions.clear();
|
|
9200
|
+
this.announcements.clear();
|
|
9201
|
+
this.voterRestrictions.clear();
|
|
9202
|
+
this.votes.length = 0;
|
|
9203
|
+
for (const item of state.submissions) {
|
|
9204
|
+
this.submissions.set(item.id, item);
|
|
9205
|
+
}
|
|
9206
|
+
for (const item of state.announcements) {
|
|
9207
|
+
this.announcements.set(item.id, item);
|
|
9208
|
+
}
|
|
9209
|
+
for (const item of state.voterRestrictions) {
|
|
9210
|
+
this.voterRestrictions.set(item.voterId, item);
|
|
9211
|
+
}
|
|
9212
|
+
this.votes.push(...state.votes);
|
|
9213
|
+
}
|
|
9214
|
+
};
|
|
9215
|
+
var createMikuContestService = (options) => {
|
|
9216
|
+
return new MikuContestService(options);
|
|
9217
|
+
};
|
|
9218
|
+
var mikuContestConfigs = pgCore.pgTable("miku_contest_configs", {
|
|
9219
|
+
contestId: pgCore.text("contest_id").primaryKey(),
|
|
9220
|
+
config: pgCore.jsonb("config").$type().notNull(),
|
|
9221
|
+
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
9222
|
+
updatedAt: pgCore.timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
|
|
9223
|
+
});
|
|
9224
|
+
var mikuContestSubmissions = pgCore.pgTable("miku_contest_submissions", {
|
|
9225
|
+
id: pgCore.text("id").primaryKey(),
|
|
9226
|
+
contestId: pgCore.text("contest_id").notNull(),
|
|
9227
|
+
serialNo: pgCore.text("serial_no").notNull(),
|
|
9228
|
+
authorId: pgCore.text("author_id").notNull(),
|
|
9229
|
+
authorNickname: pgCore.text("author_nickname").notNull(),
|
|
9230
|
+
title: pgCore.text("title").notNull(),
|
|
9231
|
+
type: pgCore.text("type").notNull(),
|
|
9232
|
+
description: pgCore.text("description").notNull(),
|
|
9233
|
+
tags: pgCore.jsonb("tags").$type().notNull(),
|
|
9234
|
+
content: pgCore.jsonb("content").$type().notNull(),
|
|
9235
|
+
voteCount: pgCore.integer("vote_count").notNull().default(0),
|
|
9236
|
+
status: pgCore.text("status").notNull(),
|
|
9237
|
+
rejectReason: pgCore.text("reject_reason"),
|
|
9238
|
+
createdAt: pgCore.timestamp("created_at", { withTimezone: true }).notNull(),
|
|
9239
|
+
reviewedAt: pgCore.timestamp("reviewed_at", { withTimezone: true }),
|
|
9240
|
+
updatedAt: pgCore.timestamp("updated_at", { withTimezone: true }).notNull()
|
|
9241
|
+
});
|
|
9242
|
+
var mikuContestVotes = pgCore.pgTable("miku_contest_votes", {
|
|
9243
|
+
id: pgCore.text("id").primaryKey(),
|
|
9244
|
+
contestId: pgCore.text("contest_id").notNull(),
|
|
9245
|
+
submissionId: pgCore.text("submission_id").notNull(),
|
|
9246
|
+
voterId: pgCore.text("voter_id").notNull(),
|
|
9247
|
+
votedAt: pgCore.text("voted_at").notNull(),
|
|
9248
|
+
dayKey: pgCore.text("day_key").notNull(),
|
|
9249
|
+
deviceId: pgCore.text("device_id"),
|
|
9250
|
+
ip: pgCore.text("ip")
|
|
9251
|
+
});
|
|
9252
|
+
var mikuContestNotices = pgCore.pgTable("miku_contest_notices", {
|
|
9253
|
+
id: pgCore.text("id").primaryKey(),
|
|
9254
|
+
contestId: pgCore.text("contest_id").notNull(),
|
|
9255
|
+
title: pgCore.text("title").notNull(),
|
|
9256
|
+
content: pgCore.text("content").notNull(),
|
|
9257
|
+
type: pgCore.text("type").notNull(),
|
|
9258
|
+
createdBy: pgCore.text("created_by").notNull(),
|
|
9259
|
+
createdAt: pgCore.text("created_at").notNull(),
|
|
9260
|
+
updatedAt: pgCore.text("updated_at").notNull()
|
|
9261
|
+
});
|
|
9262
|
+
var mikuContestVoterRestrictions = pgCore.pgTable("miku_contest_voter_restrictions", {
|
|
9263
|
+
id: pgCore.text("id").primaryKey(),
|
|
9264
|
+
contestId: pgCore.text("contest_id").notNull(),
|
|
9265
|
+
data: pgCore.jsonb("data").$type().notNull()
|
|
9266
|
+
});
|
|
9267
|
+
var MikuContestStateDbService = class {
|
|
9268
|
+
constructor(db) {
|
|
9269
|
+
this.db = db;
|
|
9270
|
+
}
|
|
9271
|
+
async loadState(contestId) {
|
|
9272
|
+
const configRows = await this.db.select({ config: mikuContestConfigs.config }).from(mikuContestConfigs).where(drizzleOrm.eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9273
|
+
const config = configRows[0]?.config;
|
|
9274
|
+
if (!config) return null;
|
|
9275
|
+
const [submissions, votes, announcements, restrictions] = await Promise.all([
|
|
9276
|
+
this.db.select().from(mikuContestSubmissions).where(drizzleOrm.eq(mikuContestSubmissions.contestId, contestId)),
|
|
9277
|
+
this.db.select().from(mikuContestVotes).where(drizzleOrm.eq(mikuContestVotes.contestId, contestId)),
|
|
9278
|
+
this.db.select().from(mikuContestNotices).where(drizzleOrm.eq(mikuContestNotices.contestId, contestId)),
|
|
9279
|
+
this.db.select({ data: mikuContestVoterRestrictions.data }).from(mikuContestVoterRestrictions).where(drizzleOrm.eq(mikuContestVoterRestrictions.contestId, contestId))
|
|
9280
|
+
]);
|
|
9281
|
+
return {
|
|
9282
|
+
contest: config,
|
|
9283
|
+
submissions,
|
|
9284
|
+
votes,
|
|
9285
|
+
announcements,
|
|
9286
|
+
voterRestrictions: restrictions.map((item) => item.data),
|
|
9287
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9288
|
+
};
|
|
9289
|
+
}
|
|
9290
|
+
async saveState(state) {
|
|
9291
|
+
const contestId = state.contest.id;
|
|
9292
|
+
const exists = await this.db.select({ contestId: mikuContestConfigs.contestId }).from(mikuContestConfigs).where(drizzleOrm.eq(mikuContestConfigs.contestId, contestId)).limit(1);
|
|
9293
|
+
if (exists[0]) {
|
|
9294
|
+
await this.db.update(mikuContestConfigs).set({
|
|
9295
|
+
config: state.contest,
|
|
9296
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
9297
|
+
}).where(drizzleOrm.eq(mikuContestConfigs.contestId, contestId));
|
|
9298
|
+
} else {
|
|
9299
|
+
await this.db.insert(mikuContestConfigs).values({
|
|
9300
|
+
contestId,
|
|
9301
|
+
config: state.contest
|
|
9302
|
+
});
|
|
9303
|
+
}
|
|
9304
|
+
await this.db.delete(mikuContestSubmissions).where(drizzleOrm.eq(mikuContestSubmissions.contestId, contestId));
|
|
9305
|
+
await this.db.delete(mikuContestVotes).where(drizzleOrm.eq(mikuContestVotes.contestId, contestId));
|
|
9306
|
+
await this.db.delete(mikuContestNotices).where(drizzleOrm.eq(mikuContestNotices.contestId, contestId));
|
|
9307
|
+
await this.db.delete(mikuContestVoterRestrictions).where(drizzleOrm.eq(mikuContestVoterRestrictions.contestId, contestId));
|
|
9308
|
+
if (state.submissions.length > 0) {
|
|
9309
|
+
await this.db.insert(mikuContestSubmissions).values(
|
|
9310
|
+
state.submissions.map((item) => ({
|
|
9311
|
+
...item,
|
|
9312
|
+
contestId,
|
|
9313
|
+
createdAt: new Date(item.createdAt),
|
|
9314
|
+
reviewedAt: item.reviewedAt ? new Date(item.reviewedAt) : null,
|
|
9315
|
+
updatedAt: new Date(item.updatedAt)
|
|
9316
|
+
}))
|
|
9317
|
+
);
|
|
9318
|
+
}
|
|
9319
|
+
if (state.votes.length > 0) {
|
|
9320
|
+
await this.db.insert(mikuContestVotes).values(state.votes);
|
|
9321
|
+
}
|
|
9322
|
+
if (state.announcements.length > 0) {
|
|
9323
|
+
await this.db.insert(mikuContestNotices).values(state.announcements);
|
|
9324
|
+
}
|
|
9325
|
+
if (state.voterRestrictions.length > 0) {
|
|
9326
|
+
await this.db.insert(mikuContestVoterRestrictions).values(
|
|
9327
|
+
state.voterRestrictions.map((item) => ({
|
|
9328
|
+
id: `${contestId}:${item.voterId}`,
|
|
9329
|
+
contestId,
|
|
9330
|
+
data: item
|
|
9331
|
+
}))
|
|
9332
|
+
);
|
|
9333
|
+
}
|
|
9334
|
+
}
|
|
9335
|
+
};
|
|
9336
|
+
|
|
9337
|
+
// src/mikuContest/server/persistence/drizzle-adapter.ts
|
|
9338
|
+
var createMikuContestDrizzlePersistenceAdapter = (db) => {
|
|
9339
|
+
const service = new MikuContestStateDbService(db);
|
|
9340
|
+
return {
|
|
9341
|
+
loadState: (contestId) => service.loadState(contestId),
|
|
9342
|
+
saveState: (state) => service.saveState(state)
|
|
9343
|
+
};
|
|
9344
|
+
};
|
|
9345
|
+
|
|
9346
|
+
// src/mikuContest/server/persistence/service.ts
|
|
9347
|
+
var MikuContestPersistentService = class {
|
|
9348
|
+
constructor(options) {
|
|
9349
|
+
this.options = options;
|
|
9350
|
+
this.hydrated = false;
|
|
9351
|
+
this.hydrationPromise = null;
|
|
9352
|
+
this.engine = createMikuContestService(options);
|
|
9353
|
+
}
|
|
9354
|
+
async ensureHydrated() {
|
|
9355
|
+
if (this.hydrated) return;
|
|
9356
|
+
if (this.hydrationPromise) return this.hydrationPromise;
|
|
9357
|
+
this.hydrationPromise = (async () => {
|
|
9358
|
+
const contestId = this.engine.getContestConfig().id;
|
|
9359
|
+
const loaded = await this.options.persistenceAdapter.loadState(contestId);
|
|
9360
|
+
if (loaded) {
|
|
9361
|
+
this.engine.importPersistenceState(loaded);
|
|
9362
|
+
} else {
|
|
9363
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9364
|
+
}
|
|
9365
|
+
this.hydrated = true;
|
|
9366
|
+
})();
|
|
9367
|
+
await this.hydrationPromise;
|
|
9368
|
+
}
|
|
9369
|
+
async persist() {
|
|
9370
|
+
await this.options.persistenceAdapter.saveState(this.engine.exportPersistenceState());
|
|
9371
|
+
}
|
|
9372
|
+
async getContestConfig() {
|
|
9373
|
+
await this.ensureHydrated();
|
|
9374
|
+
return this.engine.getContestConfig();
|
|
9375
|
+
}
|
|
9376
|
+
async updateContestConfig(patch) {
|
|
9377
|
+
await this.ensureHydrated();
|
|
9378
|
+
const data = this.engine.updateContestConfig(patch);
|
|
9379
|
+
await this.persist();
|
|
9380
|
+
return data;
|
|
9381
|
+
}
|
|
9382
|
+
async createSubmission(input, mode = "web") {
|
|
9383
|
+
await this.ensureHydrated();
|
|
9384
|
+
const data = this.engine.createSubmission(input, mode);
|
|
9385
|
+
await this.persist();
|
|
9386
|
+
return data;
|
|
9387
|
+
}
|
|
9388
|
+
async listSubmissions(filter) {
|
|
9389
|
+
await this.ensureHydrated();
|
|
9390
|
+
return this.engine.listSubmissions(filter);
|
|
9391
|
+
}
|
|
9392
|
+
async getSubmission(submissionId) {
|
|
9393
|
+
await this.ensureHydrated();
|
|
9394
|
+
return this.engine.getSubmission(submissionId);
|
|
9395
|
+
}
|
|
9396
|
+
async reviewSubmission(input) {
|
|
9397
|
+
await this.ensureHydrated();
|
|
9398
|
+
const data = this.engine.reviewSubmission(input);
|
|
9399
|
+
await this.persist();
|
|
9400
|
+
return data;
|
|
9401
|
+
}
|
|
9402
|
+
async vote(input) {
|
|
9403
|
+
await this.ensureHydrated();
|
|
9404
|
+
const data = this.engine.vote(input);
|
|
9405
|
+
await this.persist();
|
|
9406
|
+
return data;
|
|
9407
|
+
}
|
|
9408
|
+
async getVoterRestriction(voterId) {
|
|
9409
|
+
await this.ensureHydrated();
|
|
9410
|
+
return this.engine.getVoterRestriction(voterId);
|
|
9411
|
+
}
|
|
9412
|
+
async setVoterRestriction(input) {
|
|
9413
|
+
await this.ensureHydrated();
|
|
9414
|
+
const data = this.engine.setVoterRestriction(input);
|
|
9415
|
+
await this.persist();
|
|
9416
|
+
return data;
|
|
9417
|
+
}
|
|
9418
|
+
async resetVotes(input) {
|
|
9419
|
+
await this.ensureHydrated();
|
|
9420
|
+
const data = this.engine.resetVotes(input);
|
|
9421
|
+
await this.persist();
|
|
9422
|
+
return data;
|
|
9423
|
+
}
|
|
9424
|
+
async listAnnouncements(contestId) {
|
|
9425
|
+
await this.ensureHydrated();
|
|
9426
|
+
return this.engine.listAnnouncements(contestId);
|
|
9427
|
+
}
|
|
9428
|
+
async publishAnnouncement(input) {
|
|
9429
|
+
await this.ensureHydrated();
|
|
9430
|
+
const data = this.engine.publishAnnouncement(input);
|
|
9431
|
+
await this.persist();
|
|
9432
|
+
return data;
|
|
9433
|
+
}
|
|
9434
|
+
async getLeaderboard(limit = 10) {
|
|
9435
|
+
await this.ensureHydrated();
|
|
9436
|
+
return this.engine.getLeaderboard(limit);
|
|
9437
|
+
}
|
|
9438
|
+
async getSnapshot() {
|
|
9439
|
+
await this.ensureHydrated();
|
|
9440
|
+
return this.engine.getSnapshot();
|
|
9441
|
+
}
|
|
9442
|
+
async getSubmissionExportRows(filter) {
|
|
9443
|
+
await this.ensureHydrated();
|
|
9444
|
+
return this.engine.getSubmissionExportRows(filter);
|
|
9445
|
+
}
|
|
9446
|
+
async exportSubmissionExcel(filter) {
|
|
9447
|
+
await this.ensureHydrated();
|
|
9448
|
+
return this.engine.exportSubmissionExcel(filter);
|
|
9449
|
+
}
|
|
9450
|
+
};
|
|
9451
|
+
var createMikuContestPersistentService = (options) => {
|
|
9452
|
+
return new MikuContestPersistentService(options);
|
|
9453
|
+
};
|
|
9454
|
+
|
|
9455
|
+
// src/mikuContest/server/db.ts
|
|
9456
|
+
var MikuContestDbService = class {
|
|
9457
|
+
constructor() {
|
|
9458
|
+
this._db = null;
|
|
9459
|
+
}
|
|
9460
|
+
setDb(db) {
|
|
9461
|
+
this._db = db;
|
|
9462
|
+
}
|
|
9463
|
+
isConfigured() {
|
|
9464
|
+
return Boolean(this._db);
|
|
9465
|
+
}
|
|
9466
|
+
get db() {
|
|
9467
|
+
if (!this._db) {
|
|
9468
|
+
throw new Error("MikuContestDbService: Database instance not set. Call setDb(db) first.");
|
|
9469
|
+
}
|
|
9470
|
+
return this._db;
|
|
9471
|
+
}
|
|
9472
|
+
};
|
|
9473
|
+
var mikuContestDbService = new MikuContestDbService();
|
|
9474
|
+
|
|
9475
|
+
// src/mikuContest/logic/hooks/useMikuContest.ts
|
|
9476
|
+
var useMikuContest = (options) => {
|
|
9477
|
+
const [service] = React69.useState(() => createMikuContestService(options));
|
|
9478
|
+
const [version, setVersion] = React69.useState(0);
|
|
9479
|
+
const refresh = () => setVersion((value) => value + 1);
|
|
9480
|
+
const snapshot = React69.useMemo(() => {
|
|
9481
|
+
return service.getSnapshot();
|
|
9482
|
+
}, [service, version]);
|
|
9483
|
+
return {
|
|
9484
|
+
service,
|
|
9485
|
+
snapshot,
|
|
9486
|
+
refresh
|
|
9487
|
+
};
|
|
9488
|
+
};
|
|
9489
|
+
|
|
9490
|
+
// src/mikuContest/service/api/client.ts
|
|
9491
|
+
var toQueryString = (filter) => {
|
|
9492
|
+
if (!filter) return "";
|
|
9493
|
+
const params = new URLSearchParams();
|
|
9494
|
+
if (filter.status) params.set("status", filter.status);
|
|
9495
|
+
if (filter.type) params.set("type", filter.type);
|
|
9496
|
+
if (filter.authorId) params.set("authorId", filter.authorId);
|
|
9497
|
+
if (filter.authorKeyword) params.set("authorKeyword", filter.authorKeyword);
|
|
9498
|
+
if (filter.titleKeyword) params.set("titleKeyword", filter.titleKeyword);
|
|
9499
|
+
const query = params.toString();
|
|
9500
|
+
return query ? `?${query}` : "";
|
|
9501
|
+
};
|
|
9502
|
+
var unwrap = (result) => {
|
|
9503
|
+
if (!result.success || result.data === void 0) {
|
|
9504
|
+
throw new Error(result.error || "\u8BF7\u6C42\u5931\u8D25");
|
|
9505
|
+
}
|
|
9506
|
+
return result.data;
|
|
9507
|
+
};
|
|
9508
|
+
var createMikuContestApiClient = (basePath, requester) => {
|
|
9509
|
+
return {
|
|
9510
|
+
async getSnapshot() {
|
|
9511
|
+
const result = await requester(`${basePath}/contest`, { method: "GET" });
|
|
9512
|
+
return unwrap(result);
|
|
9513
|
+
},
|
|
9514
|
+
async updateContestConfig(patch) {
|
|
9515
|
+
const result = await requester(`${basePath}/contest`, {
|
|
9516
|
+
method: "PATCH",
|
|
9517
|
+
body: patch
|
|
9518
|
+
});
|
|
9519
|
+
return unwrap(result);
|
|
9520
|
+
},
|
|
9521
|
+
async createSubmission(input, mode = "web") {
|
|
9522
|
+
const result = await requester(`${basePath}/submissions`, {
|
|
9523
|
+
method: "POST",
|
|
9524
|
+
body: { payload: input, mode }
|
|
9525
|
+
});
|
|
9526
|
+
return unwrap(result);
|
|
9527
|
+
},
|
|
9528
|
+
async listSubmissions(filter) {
|
|
9529
|
+
const result = await requester(
|
|
9530
|
+
`${basePath}/submissions${toQueryString(filter)}`,
|
|
9531
|
+
{ method: "GET" }
|
|
9532
|
+
);
|
|
9533
|
+
return unwrap(result);
|
|
9534
|
+
},
|
|
9535
|
+
async reviewSubmission(input) {
|
|
9536
|
+
const result = await requester(`${basePath}/submissions/review`, {
|
|
9537
|
+
method: "POST",
|
|
9538
|
+
body: input
|
|
9539
|
+
});
|
|
9540
|
+
return unwrap(result);
|
|
9541
|
+
},
|
|
9542
|
+
async vote(input) {
|
|
9543
|
+
const result = await requester(`${basePath}/votes`, {
|
|
9544
|
+
method: "POST",
|
|
9545
|
+
body: input
|
|
9546
|
+
});
|
|
9547
|
+
return unwrap(result);
|
|
9548
|
+
},
|
|
9549
|
+
async setVoterRestriction(input) {
|
|
9550
|
+
const result = await requester(`${basePath}/admin/voter-restrictions`, {
|
|
9551
|
+
method: "POST",
|
|
9552
|
+
body: input
|
|
9553
|
+
});
|
|
9554
|
+
return unwrap(result);
|
|
9555
|
+
},
|
|
9556
|
+
async resetVotes(input) {
|
|
9557
|
+
const result = await requester(
|
|
9558
|
+
`${basePath}/admin/votes/reset`,
|
|
9559
|
+
{
|
|
9560
|
+
method: "POST",
|
|
9561
|
+
body: input
|
|
9562
|
+
}
|
|
9563
|
+
);
|
|
9564
|
+
return unwrap(result);
|
|
9565
|
+
},
|
|
9566
|
+
async exportSubmissions(filter) {
|
|
9567
|
+
const response = await fetch(`${basePath}/admin/submissions/export${toQueryString(filter)}`);
|
|
9568
|
+
if (!response.ok) {
|
|
9569
|
+
throw new Error(`\u5BFC\u51FA\u5931\u8D25: ${response.status}`);
|
|
9570
|
+
}
|
|
9571
|
+
return response.arrayBuffer();
|
|
9572
|
+
}
|
|
9573
|
+
};
|
|
9574
|
+
};
|
|
9575
|
+
|
|
9576
|
+
// src/mikuContest/service/web/index.ts
|
|
9577
|
+
var web_exports = {};
|
|
9578
|
+
__export(web_exports, {
|
|
9579
|
+
createMikuContestWebClient: () => createMikuContestWebClient
|
|
9580
|
+
});
|
|
9581
|
+
|
|
9582
|
+
// src/mikuContest/service/web/client.ts
|
|
9583
|
+
var defaultRequester = (options) => {
|
|
9584
|
+
const baseUrl = options.baseUrl || "";
|
|
9585
|
+
const commonHeaders = options.headers || {};
|
|
9586
|
+
return async (url, requestOptions) => {
|
|
9587
|
+
const response = await fetch(`${baseUrl}${url}`, {
|
|
9588
|
+
method: requestOptions?.method || "GET",
|
|
9589
|
+
headers: {
|
|
9590
|
+
"Content-Type": "application/json",
|
|
9591
|
+
...commonHeaders
|
|
9592
|
+
},
|
|
9593
|
+
body: requestOptions?.body ? JSON.stringify(requestOptions.body) : void 0
|
|
9594
|
+
});
|
|
9595
|
+
const json = await response.json();
|
|
9596
|
+
return json;
|
|
9597
|
+
};
|
|
9598
|
+
};
|
|
9599
|
+
var createMikuContestWebClient = (options = {}) => {
|
|
9600
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9601
|
+
return createMikuContestApiClient(basePath, defaultRequester(options));
|
|
9602
|
+
};
|
|
9603
|
+
|
|
9604
|
+
// src/mikuContest/service/miniapp/index.ts
|
|
9605
|
+
var miniapp_exports = {};
|
|
9606
|
+
__export(miniapp_exports, {
|
|
9607
|
+
createMikuContestMiniappClient: () => createMikuContestMiniappClient
|
|
9608
|
+
});
|
|
9609
|
+
|
|
9610
|
+
// src/mikuContest/service/miniapp/client.ts
|
|
9611
|
+
var createMikuContestMiniappClient = (options) => {
|
|
9612
|
+
const basePath = options.basePath || "/api/miku-contest";
|
|
9613
|
+
return createMikuContestApiClient(basePath, options.requester);
|
|
9614
|
+
};
|
|
9615
|
+
var isDrizzleDb = (value) => {
|
|
9616
|
+
if (!value || typeof value !== "object") return false;
|
|
9617
|
+
const candidate = value;
|
|
9618
|
+
return typeof candidate.select === "function" && typeof candidate.insert === "function" && typeof candidate.update === "function" && typeof candidate.delete === "function";
|
|
9619
|
+
};
|
|
9620
|
+
var resolveService = (config) => {
|
|
9621
|
+
if (config?.service) return config.service;
|
|
9622
|
+
const adapter = config?.persistenceAdapter || (isDrizzleDb(config?.db) ? createMikuContestDrizzlePersistenceAdapter(config.db) : null);
|
|
9623
|
+
if (adapter) {
|
|
9624
|
+
return createMikuContestPersistentService({
|
|
9625
|
+
persistenceAdapter: adapter
|
|
9626
|
+
});
|
|
9627
|
+
}
|
|
9628
|
+
return createMikuContestService();
|
|
9629
|
+
};
|
|
9630
|
+
var createGetContestSnapshotHandler = (config) => {
|
|
9631
|
+
const service = resolveService(config);
|
|
9632
|
+
return async (_request) => {
|
|
9633
|
+
const data = await service.getSnapshot();
|
|
9634
|
+
return server.NextResponse.json({ success: true, data });
|
|
9635
|
+
};
|
|
9636
|
+
};
|
|
9637
|
+
var createUpdateContestConfigHandler = (config) => {
|
|
9638
|
+
const service = resolveService(config);
|
|
9639
|
+
return async (request) => {
|
|
9640
|
+
try {
|
|
9641
|
+
const payload = await request.json();
|
|
9642
|
+
const data = await service.updateContestConfig(payload);
|
|
9643
|
+
return server.NextResponse.json({ success: true, data });
|
|
9644
|
+
} catch (error) {
|
|
9645
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9646
|
+
}
|
|
9647
|
+
};
|
|
9648
|
+
};
|
|
9649
|
+
var createCreateSubmissionHandler = (config) => {
|
|
9650
|
+
const service = resolveService(config);
|
|
9651
|
+
return async (request) => {
|
|
9652
|
+
try {
|
|
9653
|
+
const body = await request.json();
|
|
9654
|
+
const mode = body.mode || "web";
|
|
9655
|
+
const payload = body.payload;
|
|
9656
|
+
if (!payload) {
|
|
9657
|
+
return server.NextResponse.json({ success: false, error: "payload \u4E0D\u80FD\u4E3A\u7A7A" }, { status: 400 });
|
|
9658
|
+
}
|
|
9659
|
+
const data = await service.createSubmission(payload, mode);
|
|
9660
|
+
return server.NextResponse.json({ success: true, data });
|
|
9661
|
+
} catch (error) {
|
|
9662
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9663
|
+
}
|
|
9664
|
+
};
|
|
9665
|
+
};
|
|
9666
|
+
var createVoteHandler = (config) => {
|
|
9667
|
+
const service = resolveService(config);
|
|
9668
|
+
return async (request) => {
|
|
9669
|
+
try {
|
|
9670
|
+
const payload = await request.json();
|
|
9671
|
+
const data = await service.vote(payload);
|
|
9672
|
+
return server.NextResponse.json({ success: true, data });
|
|
9673
|
+
} catch (error) {
|
|
9674
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9675
|
+
}
|
|
9676
|
+
};
|
|
9677
|
+
};
|
|
9678
|
+
var createReviewSubmissionHandler = (config) => {
|
|
9679
|
+
const service = resolveService(config);
|
|
9680
|
+
return async (request) => {
|
|
9681
|
+
try {
|
|
9682
|
+
const payload = await request.json();
|
|
9683
|
+
const data = await service.reviewSubmission(payload);
|
|
9684
|
+
return server.NextResponse.json({ success: true, data });
|
|
9685
|
+
} catch (error) {
|
|
9686
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9687
|
+
}
|
|
9688
|
+
};
|
|
9689
|
+
};
|
|
9690
|
+
var buildSubmissionFilterFromQuery = (request) => {
|
|
9691
|
+
const search = request.nextUrl.searchParams;
|
|
9692
|
+
const status = search.get("status");
|
|
9693
|
+
const type = search.get("type");
|
|
9694
|
+
return {
|
|
9695
|
+
status: status ? status : void 0,
|
|
9696
|
+
type: type ? type : void 0,
|
|
9697
|
+
authorId: search.get("authorId") || void 0,
|
|
9698
|
+
authorKeyword: search.get("authorKeyword") || void 0,
|
|
9699
|
+
titleKeyword: search.get("titleKeyword") || void 0
|
|
9700
|
+
};
|
|
9701
|
+
};
|
|
9702
|
+
var createListSubmissionsHandler = (config) => {
|
|
9703
|
+
const service = resolveService(config);
|
|
9704
|
+
return async (request) => {
|
|
9705
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9706
|
+
const data = await service.listSubmissions(filter);
|
|
9707
|
+
return server.NextResponse.json({ success: true, data });
|
|
9708
|
+
};
|
|
9709
|
+
};
|
|
9710
|
+
var createSetVoterRestrictionHandler = (config) => {
|
|
9711
|
+
const service = resolveService(config);
|
|
9712
|
+
return async (request) => {
|
|
9713
|
+
try {
|
|
9714
|
+
const payload = await request.json();
|
|
9715
|
+
const data = await service.setVoterRestriction(payload);
|
|
9716
|
+
return server.NextResponse.json({ success: true, data });
|
|
9717
|
+
} catch (error) {
|
|
9718
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9719
|
+
}
|
|
9720
|
+
};
|
|
9721
|
+
};
|
|
9722
|
+
var createResetVotesHandler = (config) => {
|
|
9723
|
+
const service = resolveService(config);
|
|
9724
|
+
return async (request) => {
|
|
9725
|
+
try {
|
|
9726
|
+
const payload = await request.json();
|
|
9727
|
+
const data = await service.resetVotes(payload);
|
|
9728
|
+
return server.NextResponse.json({ success: true, data });
|
|
9729
|
+
} catch (error) {
|
|
9730
|
+
return server.NextResponse.json({ success: false, error: error.message }, { status: 400 });
|
|
9731
|
+
}
|
|
9732
|
+
};
|
|
9733
|
+
};
|
|
9734
|
+
var createExportSubmissionsHandler = (config) => {
|
|
9735
|
+
const service = resolveService(config);
|
|
9736
|
+
return async (request) => {
|
|
9737
|
+
const filter = buildSubmissionFilterFromQuery(request);
|
|
9738
|
+
const data = await service.exportSubmissionExcel(filter);
|
|
9739
|
+
const body = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
|
|
9740
|
+
return new server.NextResponse(body, {
|
|
9741
|
+
status: 200,
|
|
9742
|
+
headers: {
|
|
9743
|
+
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
9744
|
+
"Content-Disposition": 'attachment; filename="miku-submissions.xlsx"'
|
|
9745
|
+
}
|
|
9746
|
+
});
|
|
9747
|
+
};
|
|
9748
|
+
};
|
|
9749
|
+
|
|
9750
|
+
// src/mikuContest/ui/web/index.ts
|
|
9751
|
+
var web_exports2 = {};
|
|
9752
|
+
__export(web_exports2, {
|
|
9753
|
+
MikuContestAdminPage: () => MikuContestAdminPage_default,
|
|
9754
|
+
MikuContestArtistPage: () => MikuContestArtistPage_default,
|
|
9755
|
+
MikuContestAudiencePage: () => MikuContestAudiencePage_default,
|
|
9756
|
+
MikuContestDashboard: () => MikuContestDashboard_default,
|
|
9757
|
+
MikuContestPage: () => MikuContestPage_default
|
|
9758
|
+
});
|
|
9759
|
+
var MikuContestDashboard = ({ snapshot }) => {
|
|
9760
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h2", null, snapshot.contest.name), /* @__PURE__ */ React69__namespace.default.createElement("p", null, snapshot.contest.theme), /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u6295\u7A3F\u6570\uFF1A", snapshot.submissions.length), /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u516C\u544A\u6570\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__namespace.default.createElement("ul", null, snapshot.leaderboard.map((item) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: item.submissionId }, "#", item.rank, " ", item.title, " - ", item.voteCount, "\u7968"))));
|
|
9761
|
+
};
|
|
9762
|
+
var MikuContestDashboard_default = MikuContestDashboard;
|
|
9763
|
+
var MikuContestAudiencePage = ({
|
|
9764
|
+
client,
|
|
9765
|
+
voterId,
|
|
9766
|
+
title = "\u89C2\u4F17\u6295\u7968\u533A"
|
|
9767
|
+
}) => {
|
|
9768
|
+
const api = React69.useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9769
|
+
const [snapshot, setSnapshot] = React69.useState(null);
|
|
9770
|
+
const [loading, setLoading] = React69.useState(false);
|
|
9771
|
+
const [error, setError] = React69.useState(null);
|
|
9772
|
+
const approvedWorks = React69.useMemo(() => {
|
|
9773
|
+
if (!snapshot) return [];
|
|
9774
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
9775
|
+
}, [snapshot]);
|
|
9776
|
+
const loadSnapshot = async () => {
|
|
9777
|
+
setLoading(true);
|
|
9778
|
+
setError(null);
|
|
9779
|
+
try {
|
|
9780
|
+
const data = await api.getSnapshot();
|
|
9781
|
+
setSnapshot(data);
|
|
9782
|
+
} catch (e) {
|
|
9783
|
+
setError(e.message);
|
|
9784
|
+
} finally {
|
|
9785
|
+
setLoading(false);
|
|
9786
|
+
}
|
|
9787
|
+
};
|
|
9788
|
+
React69.useEffect(() => {
|
|
9789
|
+
void loadSnapshot();
|
|
9790
|
+
}, []);
|
|
9791
|
+
const handleVote = async (submission) => {
|
|
9792
|
+
if (!snapshot) return;
|
|
9793
|
+
try {
|
|
9794
|
+
await api.vote({
|
|
9795
|
+
contestId: snapshot.contest.id,
|
|
9796
|
+
submissionId: submission.id,
|
|
9797
|
+
voterId
|
|
9798
|
+
});
|
|
9799
|
+
await loadSnapshot();
|
|
9800
|
+
} catch (e) {
|
|
9801
|
+
setError(e.message);
|
|
9802
|
+
}
|
|
9803
|
+
};
|
|
9804
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("section", null, /* @__PURE__ */ React69__namespace.default.createElement("h2", null, title), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void loadSnapshot(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__namespace.default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, !snapshot ? null : /* @__PURE__ */ React69__namespace.default.createElement(React69__namespace.default.Fragment, null, /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u8D5B\u4E8B\uFF1A", snapshot.contest.name, "\uFF5C\u4E3B\u9898\uFF1A", snapshot.contest.theme), /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\uFF1A", approvedWorks.length, "\uFF5C\u6BCF\u65E5\u4E0A\u9650\uFF1A", snapshot.contest.votingRules.maxVotesPerDay), /* @__PURE__ */ React69__namespace.default.createElement("ul", null, approvedWorks.map((work) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: work.id }, /* @__PURE__ */ React69__namespace.default.createElement("strong", null, work.title), "\uFF08", work.authorNickname, "\uFF09- \u5F53\u524D ", work.voteCount, " \u7968", " ", /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void handleVote(work) }, "\u6295\u7968"))))));
|
|
9805
|
+
};
|
|
9806
|
+
var MikuContestAudiencePage_default = MikuContestAudiencePage;
|
|
9807
|
+
var workTypes = ["visual", "video", "text", "audio"];
|
|
9808
|
+
var MikuContestArtistPage = ({
|
|
9809
|
+
client,
|
|
9810
|
+
authorId,
|
|
9811
|
+
authorNickname,
|
|
9812
|
+
title = "\u753B\u5E08\u6295\u7A3F\u533A"
|
|
9813
|
+
}) => {
|
|
9814
|
+
const api = React69.useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9815
|
+
const [snapshot, setSnapshot] = React69.useState(null);
|
|
9816
|
+
const [mySubmissions, setMySubmissions] = React69.useState([]);
|
|
9817
|
+
const [submitting, setSubmitting] = React69.useState(false);
|
|
9818
|
+
const [loading, setLoading] = React69.useState(false);
|
|
9819
|
+
const [error, setError] = React69.useState(null);
|
|
9820
|
+
const [titleInput, setTitleInput] = React69.useState("");
|
|
9821
|
+
const [descInput, setDescInput] = React69.useState("");
|
|
9822
|
+
const [coverImage, setCoverImage] = React69.useState("");
|
|
9823
|
+
const [workType, setWorkType] = React69.useState("visual");
|
|
9824
|
+
const loadData = async () => {
|
|
9825
|
+
setLoading(true);
|
|
9826
|
+
setError(null);
|
|
9827
|
+
try {
|
|
9828
|
+
const [contest, mine] = await Promise.all([
|
|
9829
|
+
api.getSnapshot(),
|
|
9830
|
+
api.listSubmissions({ authorId })
|
|
9831
|
+
]);
|
|
9832
|
+
setSnapshot(contest);
|
|
9833
|
+
setMySubmissions(mine);
|
|
9834
|
+
} catch (e) {
|
|
9835
|
+
setError(e.message);
|
|
9836
|
+
} finally {
|
|
9837
|
+
setLoading(false);
|
|
9838
|
+
}
|
|
9839
|
+
};
|
|
9840
|
+
React69.useEffect(() => {
|
|
9841
|
+
void loadData();
|
|
9842
|
+
}, []);
|
|
9843
|
+
const submitWork = async () => {
|
|
9844
|
+
if (!snapshot) return;
|
|
9845
|
+
const payload = {
|
|
9846
|
+
contestId: snapshot.contest.id,
|
|
9847
|
+
authorId,
|
|
9848
|
+
authorNickname,
|
|
9849
|
+
title: titleInput,
|
|
9850
|
+
description: descInput,
|
|
9851
|
+
type: workType,
|
|
9852
|
+
tags: ["web"],
|
|
9853
|
+
content: {
|
|
9854
|
+
coverImage,
|
|
9855
|
+
images: coverImage ? [coverImage] : void 0
|
|
9856
|
+
}
|
|
9857
|
+
};
|
|
9858
|
+
setSubmitting(true);
|
|
9859
|
+
setError(null);
|
|
9860
|
+
try {
|
|
9861
|
+
await api.createSubmission(payload, "web");
|
|
9862
|
+
setTitleInput("");
|
|
9863
|
+
setDescInput("");
|
|
9864
|
+
setCoverImage("");
|
|
9865
|
+
await loadData();
|
|
9866
|
+
} catch (e) {
|
|
9867
|
+
setError(e.message);
|
|
9868
|
+
} finally {
|
|
9869
|
+
setSubmitting(false);
|
|
9870
|
+
}
|
|
9871
|
+
};
|
|
9872
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("section", null, /* @__PURE__ */ React69__namespace.default.createElement("h2", null, title), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__namespace.default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u65B0\u5EFA\u6295\u7A3F"), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: titleInput, onChange: (e) => setTitleInput(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__namespace.default.createElement("br", null), /* @__PURE__ */ React69__namespace.default.createElement("textarea", { value: descInput, onChange: (e) => setDescInput(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__namespace.default.createElement("br", null), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: coverImage, onChange: (e) => setCoverImage(e.target.value), placeholder: "\u5C01\u9762 URL" }), /* @__PURE__ */ React69__namespace.default.createElement("br", null), /* @__PURE__ */ React69__namespace.default.createElement("select", { value: workType, onChange: (e) => setWorkType(e.target.value) }, workTypes.map((item) => /* @__PURE__ */ React69__namespace.default.createElement("option", { value: item, key: item }, item))), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void submitWork(), disabled: submitting || !snapshot }, submitting ? "\u63D0\u4EA4\u4E2D..." : "\u63D0\u4EA4\u7A3F\u4EF6")), /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u6211\u7684\u6295\u7A3F\uFF08", mySubmissions.length, "\uFF09"), /* @__PURE__ */ React69__namespace.default.createElement("ul", null, mySubmissions.map((item) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: item.id }, item.title, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.rejectReason ? `\uFF5C\u9A73\u56DE\uFF1A${item.rejectReason}` : "")))));
|
|
9873
|
+
};
|
|
9874
|
+
var MikuContestArtistPage_default = MikuContestArtistPage;
|
|
9875
|
+
var MikuContestAdminPage = ({
|
|
9876
|
+
client,
|
|
9877
|
+
adminId,
|
|
9878
|
+
title = "\u7BA1\u7406\u5458\u9762\u677F"
|
|
9879
|
+
}) => {
|
|
9880
|
+
const api = React69.useMemo(() => client || createMikuContestWebClient(), [client]);
|
|
9881
|
+
const [snapshot, setSnapshot] = React69.useState(null);
|
|
9882
|
+
const [submissions, setSubmissions] = React69.useState([]);
|
|
9883
|
+
const [loading, setLoading] = React69.useState(false);
|
|
9884
|
+
const [error, setError] = React69.useState(null);
|
|
9885
|
+
const [voterId, setVoterId] = React69.useState("");
|
|
9886
|
+
const loadData = async () => {
|
|
9887
|
+
setLoading(true);
|
|
9888
|
+
setError(null);
|
|
9889
|
+
try {
|
|
9890
|
+
const [contest, list] = await Promise.all([api.getSnapshot(), api.listSubmissions()]);
|
|
9891
|
+
setSnapshot(contest);
|
|
9892
|
+
setSubmissions(list);
|
|
9893
|
+
} catch (e) {
|
|
9894
|
+
setError(e.message);
|
|
9895
|
+
} finally {
|
|
9896
|
+
setLoading(false);
|
|
9897
|
+
}
|
|
9898
|
+
};
|
|
9899
|
+
React69.useEffect(() => {
|
|
9900
|
+
void loadData();
|
|
9901
|
+
}, []);
|
|
9902
|
+
const review = async (item, action) => {
|
|
9903
|
+
try {
|
|
9904
|
+
await api.reviewSubmission({
|
|
9905
|
+
submissionId: item.id,
|
|
9906
|
+
reviewerId: adminId,
|
|
9907
|
+
action,
|
|
9908
|
+
rejectReason: action === "reject" ? "\u7BA1\u7406\u5458\u9A73\u56DE" : void 0
|
|
9909
|
+
});
|
|
9910
|
+
await loadData();
|
|
9911
|
+
} catch (e) {
|
|
9912
|
+
setError(e.message);
|
|
9913
|
+
}
|
|
9914
|
+
};
|
|
9915
|
+
const toggleVoting = async (enabled) => {
|
|
9916
|
+
if (!snapshot) return;
|
|
9917
|
+
try {
|
|
9918
|
+
await api.updateContestConfig({
|
|
9919
|
+
toggles: {
|
|
9920
|
+
...snapshot.contest.toggles,
|
|
9921
|
+
votingEnabled: enabled
|
|
9922
|
+
}
|
|
9923
|
+
});
|
|
9924
|
+
await loadData();
|
|
9925
|
+
} catch (e) {
|
|
9926
|
+
setError(e.message);
|
|
9927
|
+
}
|
|
9928
|
+
};
|
|
9929
|
+
const setRestriction = async (banned) => {
|
|
9930
|
+
if (!snapshot || !voterId.trim()) return;
|
|
9931
|
+
try {
|
|
9932
|
+
await api.setVoterRestriction({
|
|
9933
|
+
voterId: voterId.trim(),
|
|
9934
|
+
banned,
|
|
9935
|
+
reason: banned ? "\u7BA1\u7406\u5458\u624B\u52A8\u5C01\u7981" : "\u7BA1\u7406\u5458\u89E3\u9664\u5C01\u7981",
|
|
9936
|
+
operatorId: adminId
|
|
9937
|
+
});
|
|
9938
|
+
setVoterId("");
|
|
9939
|
+
} catch (e) {
|
|
9940
|
+
setError(e.message);
|
|
9941
|
+
}
|
|
9942
|
+
};
|
|
9943
|
+
const resetVotesByVoter = async () => {
|
|
9944
|
+
if (!voterId.trim()) return;
|
|
9945
|
+
try {
|
|
9946
|
+
await api.resetVotes({ voterId: voterId.trim() });
|
|
9947
|
+
setVoterId("");
|
|
9948
|
+
await loadData();
|
|
9949
|
+
} catch (e) {
|
|
9950
|
+
setError(e.message);
|
|
9951
|
+
}
|
|
9952
|
+
};
|
|
9953
|
+
const exportExcel = async () => {
|
|
9954
|
+
try {
|
|
9955
|
+
const data = await api.exportSubmissions();
|
|
9956
|
+
const blob = new Blob([data], {
|
|
9957
|
+
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
9958
|
+
});
|
|
9959
|
+
const url = URL.createObjectURL(blob);
|
|
9960
|
+
const a = document.createElement("a");
|
|
9961
|
+
a.href = url;
|
|
9962
|
+
a.download = "miku-submissions.xlsx";
|
|
9963
|
+
a.click();
|
|
9964
|
+
URL.revokeObjectURL(url);
|
|
9965
|
+
} catch (e) {
|
|
9966
|
+
setError(e.message);
|
|
9967
|
+
}
|
|
9968
|
+
};
|
|
9969
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("section", null, /* @__PURE__ */ React69__namespace.default.createElement("h2", null, title), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void loadData(), disabled: loading }, loading ? "\u5237\u65B0\u4E2D..." : "\u5237\u65B0\u6570\u636E"), error ? /* @__PURE__ */ React69__namespace.default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u8D5B\u4E8B\u5F00\u5173"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void toggleVoting(true), disabled: !snapshot }, "\u5F00\u542F\u6295\u7968"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void toggleVoting(false), disabled: !snapshot }, "\u5173\u95ED\u6295\u7968")), /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u6295\u7A3F\u5BA1\u6838\uFF08", submissions.length, "\uFF09"), /* @__PURE__ */ React69__namespace.default.createElement("ul", null, submissions.map((item) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: item.id }, /* @__PURE__ */ React69__namespace.default.createElement("strong", null, item.title), "\uFF5C\u4F5C\u8005\uFF1A", item.authorNickname, "\uFF5C\u72B6\u6001\uFF1A", item.status, "\uFF5C\u7968\u6570\uFF1A", item.voteCount, item.status === "pending" ? /* @__PURE__ */ React69__namespace.default.createElement(React69__namespace.default.Fragment, null, " ", /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void review(item, "approve") }, "\u901A\u8FC7"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void review(item, "reject") }, "\u9A73\u56DE")) : null)))), /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u6295\u7968\u98CE\u63A7"), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void setRestriction(true), disabled: !snapshot }, "\u5C01\u7981\u6295\u7968"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void setRestriction(false), disabled: !snapshot }, "\u89E3\u9664\u5C01\u7981"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void resetVotesByVoter() }, "\u6E05\u96F6\u8BE5\u7528\u6237\u7968\u6570")), /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, "\u5BFC\u51FA"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => void exportExcel() }, "\u5BFC\u51FA\u6295\u7A3F Excel")));
|
|
9970
|
+
};
|
|
9971
|
+
var MikuContestAdminPage_default = MikuContestAdminPage;
|
|
9972
|
+
|
|
9973
|
+
// src/mikuContest/ui/web/pages/MikuContestPage.tsx
|
|
9974
|
+
var MikuContestPage = ({
|
|
9975
|
+
defaultView = "audience",
|
|
9976
|
+
viewerVoterId = "viewer-demo",
|
|
9977
|
+
artistId = "artist-demo",
|
|
9978
|
+
artistNickname = "Demo \u753B\u5E08",
|
|
9979
|
+
adminId = "admin-demo"
|
|
9980
|
+
}) => {
|
|
9981
|
+
const [view, setView] = React69.useState(defaultView);
|
|
9982
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h1", null, "Miku Contest"), /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => setView("audience") }, "\u89C2\u4F17\u7AEF"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => setView("artist") }, "\u753B\u5E08\u7AEF"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => setView("admin") }, "\u7BA1\u7406\u5458\u7AEF")), view === "audience" ? /* @__PURE__ */ React69__namespace.default.createElement(MikuContestAudiencePage_default, { voterId: viewerVoterId }) : null, view === "artist" ? /* @__PURE__ */ React69__namespace.default.createElement(MikuContestArtistPage_default, { authorId: artistId, authorNickname: artistNickname }) : null, view === "admin" ? /* @__PURE__ */ React69__namespace.default.createElement(MikuContestAdminPage_default, { adminId }) : null);
|
|
9983
|
+
};
|
|
9984
|
+
var MikuContestPage_default = MikuContestPage;
|
|
9985
|
+
|
|
9986
|
+
// src/mikuContest/ui/miniapp/index.ts
|
|
9987
|
+
var miniapp_exports2 = {};
|
|
9988
|
+
__export(miniapp_exports2, {
|
|
9989
|
+
MikuContestMiniappHome: () => MikuContestMiniappHome_default,
|
|
9990
|
+
MikuContestMiniappPage: () => MikuContestMiniappPage_default
|
|
9991
|
+
});
|
|
9992
|
+
var MikuContestMiniappHome = ({ snapshot }) => {
|
|
9993
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u6295\u7A3F\uFF1A", snapshot.submissions.length, " | \u516C\u544A\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React69__namespace.default.createElement("ol", null, snapshot.leaderboard.slice(0, 3).map((item) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: item.submissionId }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09"))));
|
|
9994
|
+
};
|
|
9995
|
+
var MikuContestMiniappHome_default = MikuContestMiniappHome;
|
|
9996
|
+
var MikuContestMiniappPage = () => {
|
|
9997
|
+
const { service, snapshot, refresh } = useMikuContest();
|
|
9998
|
+
const [tab, setTab] = React69.useState("vote");
|
|
9999
|
+
const [voterId, setVoterId] = React69.useState("miniapp-voter");
|
|
10000
|
+
const [authorId, setAuthorId] = React69.useState("miniapp-author");
|
|
10001
|
+
const [authorNickname, setAuthorNickname] = React69.useState("\u5C0F\u7A0B\u5E8F\u753B\u5E08");
|
|
10002
|
+
const [title, setTitle] = React69.useState("");
|
|
10003
|
+
const [desc, setDesc] = React69.useState("");
|
|
10004
|
+
const [type, setType] = React69.useState("visual");
|
|
10005
|
+
const [error, setError] = React69.useState(null);
|
|
10006
|
+
const approvedWorks = React69.useMemo(() => {
|
|
10007
|
+
return snapshot.submissions.filter((item) => item.status === "approved");
|
|
10008
|
+
}, [snapshot.submissions]);
|
|
10009
|
+
const vote = (submissionId) => {
|
|
10010
|
+
try {
|
|
10011
|
+
service.vote({
|
|
10012
|
+
contestId: snapshot.contest.id,
|
|
10013
|
+
submissionId,
|
|
10014
|
+
voterId
|
|
10015
|
+
});
|
|
10016
|
+
refresh();
|
|
10017
|
+
setError(null);
|
|
10018
|
+
} catch (e) {
|
|
10019
|
+
setError(e.message);
|
|
10020
|
+
}
|
|
10021
|
+
};
|
|
10022
|
+
const submit = () => {
|
|
10023
|
+
try {
|
|
10024
|
+
service.createSubmission(
|
|
10025
|
+
{
|
|
10026
|
+
contestId: snapshot.contest.id,
|
|
10027
|
+
authorId,
|
|
10028
|
+
authorNickname,
|
|
10029
|
+
title,
|
|
10030
|
+
description: desc,
|
|
10031
|
+
type,
|
|
10032
|
+
content: {}
|
|
10033
|
+
},
|
|
10034
|
+
"miniapp"
|
|
10035
|
+
);
|
|
10036
|
+
setTitle("");
|
|
10037
|
+
setDesc("");
|
|
10038
|
+
refresh();
|
|
10039
|
+
setError(null);
|
|
10040
|
+
} catch (e) {
|
|
10041
|
+
setError(e.message);
|
|
10042
|
+
}
|
|
10043
|
+
};
|
|
10044
|
+
return /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React69__namespace.default.createElement("p", null, "\u5C0F\u7A0B\u5E8F\u7AEF\u793A\u4F8B\u9875\u9762"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => setTab("vote") }, "\u89C2\u4F17\u6295\u7968"), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => setTab("submit") }, "\u753B\u5E08\u6295\u7A3F"), error ? /* @__PURE__ */ React69__namespace.default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, tab === "vote" ? /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React69__namespace.default.createElement("ul", null, approvedWorks.map((item) => /* @__PURE__ */ React69__namespace.default.createElement("li", { key: item.id }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09", /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: () => vote(item.id) }, "\u6295\u7968"))))) : null, tab === "submit" ? /* @__PURE__ */ React69__namespace.default.createElement("div", null, /* @__PURE__ */ React69__namespace.default.createElement("input", { value: authorId, onChange: (e) => setAuthorId(e.target.value), placeholder: "authorId" }), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: authorNickname, onChange: (e) => setAuthorNickname(e.target.value), placeholder: "\u4F5C\u8005\u6635\u79F0" }), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: title, onChange: (e) => setTitle(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React69__namespace.default.createElement("input", { value: desc, onChange: (e) => setDesc(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React69__namespace.default.createElement("select", { value: type, onChange: (e) => setType(e.target.value) }, /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "visual" }, "visual"), /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "video" }, "video"), /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "text" }, "text"), /* @__PURE__ */ React69__namespace.default.createElement("option", { value: "audio" }, "audio")), /* @__PURE__ */ React69__namespace.default.createElement("button", { onClick: submit }, "\u63D0\u4EA4")) : null);
|
|
10045
|
+
};
|
|
10046
|
+
var MikuContestMiniappPage_default = MikuContestMiniappPage;
|
|
10047
|
+
|
|
8802
10048
|
// src/storage/adapters/react-native-adapter.ts
|
|
8803
10049
|
var AsyncStorage = null;
|
|
8804
10050
|
try {
|
|
@@ -9252,6 +10498,9 @@ exports.LocalImageMappingPanel = LocalImageMappingPanel;
|
|
|
9252
10498
|
exports.LogLevel = LogLevel;
|
|
9253
10499
|
exports.Logger = Logger;
|
|
9254
10500
|
exports.MIKU_PALETTE = MIKU_PALETTE;
|
|
10501
|
+
exports.MikuContestPersistentService = MikuContestPersistentService;
|
|
10502
|
+
exports.MikuContestService = MikuContestService;
|
|
10503
|
+
exports.MikuContestStateDbService = MikuContestStateDbService;
|
|
9255
10504
|
exports.MikuFireworks3D = MikuFireworks3D;
|
|
9256
10505
|
exports.Modal = Modal;
|
|
9257
10506
|
exports.NORMAL_PALETTE = NORMAL_PALETTE;
|
|
@@ -9316,14 +10565,30 @@ exports.applyPromptTemplate = applyPromptTemplate;
|
|
|
9316
10565
|
exports.arrayUtils = arrayUtils;
|
|
9317
10566
|
exports.badgeVariants = badgeVariants;
|
|
9318
10567
|
exports.buttonVariants = buttonVariants;
|
|
10568
|
+
exports.checkVoteEligibility = checkVoteEligibility;
|
|
9319
10569
|
exports.cn = cn;
|
|
9320
10570
|
exports.createAiClient = createAiClient;
|
|
9321
10571
|
exports.createChatSession = createChatSession;
|
|
10572
|
+
exports.createCreateSubmissionHandler = createCreateSubmissionHandler;
|
|
10573
|
+
exports.createDefaultMikuContestConfig = createDefaultMikuContestConfig;
|
|
10574
|
+
exports.createExportSubmissionsHandler = createExportSubmissionsHandler;
|
|
10575
|
+
exports.createGetContestSnapshotHandler = createGetContestSnapshotHandler;
|
|
9322
10576
|
exports.createInMemoryFestivalCardDb = createInMemoryFestivalCardDb;
|
|
10577
|
+
exports.createListSubmissionsHandler = createListSubmissionsHandler;
|
|
9323
10578
|
exports.createLogger = createLogger;
|
|
10579
|
+
exports.createMikuContestApiClient = createMikuContestApiClient;
|
|
10580
|
+
exports.createMikuContestDrizzlePersistenceAdapter = createMikuContestDrizzlePersistenceAdapter;
|
|
10581
|
+
exports.createMikuContestPersistentService = createMikuContestPersistentService;
|
|
10582
|
+
exports.createMikuContestService = createMikuContestService;
|
|
9324
10583
|
exports.createOpenAICompatibleProvider = createOpenAICompatibleProvider;
|
|
10584
|
+
exports.createResetVotesHandler = createResetVotesHandler;
|
|
10585
|
+
exports.createReviewSubmissionHandler = createReviewSubmissionHandler;
|
|
10586
|
+
exports.createSetVoterRestrictionHandler = createSetVoterRestrictionHandler;
|
|
9325
10587
|
exports.createSkillRegistry = createSkillRegistry;
|
|
10588
|
+
exports.createUpdateContestConfigHandler = createUpdateContestConfigHandler;
|
|
10589
|
+
exports.createVoteHandler = createVoteHandler;
|
|
9326
10590
|
exports.debugUtils = debugUtils;
|
|
10591
|
+
exports.defaultMikuVotingRules = defaultMikuVotingRules;
|
|
9327
10592
|
exports.defaultVocaloidBoothConfig = defaultVocaloidBoothConfig;
|
|
9328
10593
|
exports.errorUtils = errorUtils;
|
|
9329
10594
|
exports.fileUtils = fileUtils;
|
|
@@ -9339,6 +10604,14 @@ exports.getCompletionStatusText = getCompletionStatusText;
|
|
|
9339
10604
|
exports.getExperimentCounts = getExperimentCounts;
|
|
9340
10605
|
exports.japaneseUtils = japaneseUtils;
|
|
9341
10606
|
exports.logger = logger;
|
|
10607
|
+
exports.mikuContestConfigs = mikuContestConfigs;
|
|
10608
|
+
exports.mikuContestDbService = mikuContestDbService;
|
|
10609
|
+
exports.mikuContestNotices = mikuContestNotices;
|
|
10610
|
+
exports.mikuContestSubmissions = mikuContestSubmissions;
|
|
10611
|
+
exports.mikuContestVoterRestrictions = mikuContestVoterRestrictions;
|
|
10612
|
+
exports.mikuContestVotes = mikuContestVotes;
|
|
10613
|
+
exports.miniappService = miniapp_exports;
|
|
10614
|
+
exports.miniappUI = miniapp_exports2;
|
|
9342
10615
|
exports.normalizeFestivalCardConfig = normalizeFestivalCardConfig;
|
|
9343
10616
|
exports.normalizeMatchCode = normalizeMatchCode;
|
|
9344
10617
|
exports.normalizePromptVariables = normalizePromptVariables;
|
|
@@ -9346,8 +10619,10 @@ exports.normalizeVocaloidBoothConfig = normalizeVocaloidBoothConfig;
|
|
|
9346
10619
|
exports.resizeFestivalCardPages = resizeFestivalCardPages;
|
|
9347
10620
|
exports.resolveScreenReceiverSignalUrl = resolveScreenReceiverSignalUrl;
|
|
9348
10621
|
exports.skillToToolDefinition = skillToToolDefinition;
|
|
10622
|
+
exports.sortByVotesDesc = sortByVotesDesc;
|
|
9349
10623
|
exports.sortExperiments = sortExperiments;
|
|
9350
10624
|
exports.stringUtils = stringUtils;
|
|
10625
|
+
exports.toVoteDayKey = toVoteDayKey;
|
|
9351
10626
|
exports.useAiChat = useAiChat;
|
|
9352
10627
|
exports.useAsyncStorage = useAsyncStorage;
|
|
9353
10628
|
exports.useBackgroundRemoval = useBackgroundRemoval;
|
|
@@ -9357,6 +10632,7 @@ exports.useFestivalCardConfig = useFestivalCardConfig;
|
|
|
9357
10632
|
exports.useFireworksEngine = useFireworksEngine;
|
|
9358
10633
|
exports.useFireworksRealtime = useFireworksRealtime;
|
|
9359
10634
|
exports.useLocalStorage = useLocalStorage;
|
|
10635
|
+
exports.useMikuContest = useMikuContest;
|
|
9360
10636
|
exports.useOCR = useOCR;
|
|
9361
10637
|
exports.useScreenReceiver = useScreenReceiver;
|
|
9362
10638
|
exports.useSentimentAnalysis = useSentimentAnalysis;
|
|
@@ -9364,6 +10640,9 @@ exports.useStorage = useStorage;
|
|
|
9364
10640
|
exports.useTaroStorage = useTaroStorage;
|
|
9365
10641
|
exports.useTextGeneration = useTextGeneration;
|
|
9366
10642
|
exports.validateExperiment = validateExperiment;
|
|
10643
|
+
exports.validateMikuSubmissionInput = validateMikuSubmissionInput;
|
|
9367
10644
|
exports.validators = validators;
|
|
10645
|
+
exports.webService = web_exports;
|
|
10646
|
+
exports.webUI = web_exports2;
|
|
9368
10647
|
//# sourceMappingURL=index.js.map
|
|
9369
10648
|
//# sourceMappingURL=index.js.map
|