sa2kit 1.2.0 → 1.3.0

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.
Files changed (49) hide show
  1. package/dist/{UniversalFileService-CEZRJ87g.d.mts → UniversalFileService-BuHN-jrR.d.ts} +47 -259
  2. package/dist/{UniversalFileService-CEZRJ87g.d.ts → UniversalFileService-CGGzYeeF.d.mts} +47 -259
  3. package/dist/{chunk-3XG5OHFD.mjs → chunk-CIVO4R6N.mjs} +2 -2
  4. package/dist/{chunk-3XG5OHFD.mjs.map → chunk-CIVO4R6N.mjs.map} +1 -1
  5. package/dist/chunk-EV6BCVOQ.mjs +204 -0
  6. package/dist/chunk-EV6BCVOQ.mjs.map +1 -0
  7. package/dist/chunk-W35VTQAW.js +211 -0
  8. package/dist/chunk-W35VTQAW.js.map +1 -0
  9. package/dist/{chunk-HWJ34NL6.js → chunk-ZRAW3HXA.js} +2 -2
  10. package/dist/{chunk-HWJ34NL6.js.map → chunk-ZRAW3HXA.js.map} +1 -1
  11. package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
  12. package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
  13. package/dist/mmd/admin/index.d.mts +487 -0
  14. package/dist/mmd/admin/index.d.ts +487 -0
  15. package/dist/mmd/admin/index.js +871 -0
  16. package/dist/mmd/admin/index.js.map +1 -0
  17. package/dist/mmd/admin/index.mjs +822 -0
  18. package/dist/mmd/admin/index.mjs.map +1 -0
  19. package/dist/mmd/index.d.mts +4 -193
  20. package/dist/mmd/index.d.ts +4 -193
  21. package/dist/mmd/server/index.d.mts +138 -0
  22. package/dist/mmd/server/index.d.ts +138 -0
  23. package/dist/mmd/server/index.js +245 -0
  24. package/dist/mmd/server/index.js.map +1 -0
  25. package/dist/mmd/server/index.mjs +207 -0
  26. package/dist/mmd/server/index.mjs.map +1 -0
  27. package/dist/testYourself/index.d.mts +145 -0
  28. package/dist/testYourself/index.d.ts +145 -0
  29. package/dist/testYourself/index.js +1004 -0
  30. package/dist/testYourself/index.js.map +1 -0
  31. package/dist/testYourself/index.mjs +993 -0
  32. package/dist/testYourself/index.mjs.map +1 -0
  33. package/dist/types-Bc_p-zAR.d.mts +194 -0
  34. package/dist/types-Bc_p-zAR.d.ts +194 -0
  35. package/dist/types-CK4We_aI.d.mts +270 -0
  36. package/dist/types-CK4We_aI.d.ts +270 -0
  37. package/dist/universalFile/index.d.mts +3 -2
  38. package/dist/universalFile/index.d.ts +3 -2
  39. package/dist/universalFile/index.js +48 -10
  40. package/dist/universalFile/index.js.map +1 -1
  41. package/dist/universalFile/index.mjs +43 -5
  42. package/dist/universalFile/index.mjs.map +1 -1
  43. package/dist/universalFile/server/index.d.mts +3 -2
  44. package/dist/universalFile/server/index.d.ts +3 -2
  45. package/dist/universalFile/server/index.js +239 -7
  46. package/dist/universalFile/server/index.js.map +1 -1
  47. package/dist/universalFile/server/index.mjs +234 -2
  48. package/dist/universalFile/server/index.mjs.map +1 -1
  49. package/package.json +19 -1
@@ -0,0 +1,207 @@
1
+ export { mmdPlaylistNodes, mmdPlaylistNodesRelations, mmdPlaylists, mmdPlaylistsRelations, mmdPresetItems, mmdResourceOptions } from '../../chunk-EV6BCVOQ.mjs';
2
+ import '../../chunk-BJTO5JO5.mjs';
3
+ import AdmZip from 'adm-zip';
4
+ import { randomUUID } from 'crypto';
5
+ import { mkdir, rm, writeFile } from 'fs/promises';
6
+ import path from 'path';
7
+
8
+ var MMD_MODEL_ARCHIVE_MIME_TYPES = [
9
+ "application/zip",
10
+ "application/x-zip-compressed",
11
+ "multipart/x-zip"
12
+ ];
13
+ async function processMmdModelArchive(buffer, options) {
14
+ const folderName = options.folderName ?? randomUUID();
15
+ const targetDir = path.join(options.storageRoot, folderName);
16
+ await mkdir(targetDir, { recursive: true });
17
+ const zip = new AdmZip(buffer);
18
+ const entries = zip.getEntries();
19
+ if (!entries.length) {
20
+ await rm(targetDir, { recursive: true, force: true });
21
+ throw new Error("\u538B\u7F29\u5305\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5\u4E0A\u4F20\u6587\u4EF6\u5185\u5BB9");
22
+ }
23
+ let modelRelativePath = null;
24
+ let filesExtracted = 0;
25
+ for (const entry of entries) {
26
+ if (!entry.entryName || entry.entryName.startsWith("__MACOSX") || entry.entryName.endsWith(".DS_Store")) {
27
+ continue;
28
+ }
29
+ const safeRelativePath = sanitizeEntryPath(entry.entryName);
30
+ if (!safeRelativePath) {
31
+ continue;
32
+ }
33
+ const destinationPath = path.join(targetDir, safeRelativePath);
34
+ if (entry.isDirectory) {
35
+ await mkdir(destinationPath, { recursive: true });
36
+ continue;
37
+ }
38
+ await mkdir(path.dirname(destinationPath), { recursive: true });
39
+ await writeFile(destinationPath, entry.getData());
40
+ filesExtracted += 1;
41
+ const entryExt = path.extname(safeRelativePath).toLowerCase();
42
+ if (!modelRelativePath && (entryExt === ".pmx" || entryExt === ".pmd")) {
43
+ modelRelativePath = safeRelativePath.split(path.sep).join("/");
44
+ }
45
+ }
46
+ if (!modelRelativePath) {
47
+ await rm(targetDir, { recursive: true, force: true });
48
+ throw new Error("\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX/PMD \u6A21\u578B\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u76EE\u5F55\u7ED3\u6784\u662F\u5426\u6B63\u786E");
49
+ }
50
+ const format = path.extname(modelRelativePath).slice(1).toLowerCase();
51
+ const publicRoot = options.publicRoot ?? `/uploads/mmd/models`;
52
+ const modelUrl = joinPublicPath(publicRoot, folderName, modelRelativePath);
53
+ return {
54
+ directory: targetDir,
55
+ relativeDirectory: folderName,
56
+ modelRelativePath,
57
+ modelUrl,
58
+ format,
59
+ filesExtracted
60
+ };
61
+ }
62
+ function sanitizeEntryPath(entryName) {
63
+ const normalized = path.normalize(entryName).replace(/^(\.\.(\/|\\|$))+/, "");
64
+ if (!normalized || normalized === "." || normalized.startsWith("..") || path.isAbsolute(normalized)) {
65
+ return null;
66
+ }
67
+ return normalized;
68
+ }
69
+ function joinPublicPath(...segments) {
70
+ return segments.map((segment) => segment.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "")).filter(Boolean).join("/").replace(/\/{2,}/g, "/").replace(/^/, "/");
71
+ }
72
+
73
+ // src/mmd/server/playlistBuilder.ts
74
+ var defaultNormalizer = (value) => {
75
+ if (!value) return "";
76
+ if (value.startsWith("http://") || value.startsWith("https://")) {
77
+ return value;
78
+ }
79
+ return value.startsWith("/") ? value : `/${value}`;
80
+ };
81
+ function buildMmdPlaylistFromSources(options) {
82
+ if (!options.models.length) {
83
+ throw new Error("\u6784\u5EFA MMD \u64AD\u653E\u5217\u8868\u5931\u8D25\uFF1Amodels \u4E3A\u7A7A");
84
+ }
85
+ const limit = Math.max(1, Math.min(options.limit ?? options.models.length, options.models.length));
86
+ const normalizeUrl = options.normalizeUrl ?? defaultNormalizer;
87
+ const motions = options.motions ?? [];
88
+ const hasMotions = motions.length > 0;
89
+ const duration = options.nodeDuration ?? 30;
90
+ const nodes = options.models.slice(0, limit).map((model, index) => {
91
+ const motion = hasMotions ? motions[index % motions.length] : void 0;
92
+ return {
93
+ id: String(model.id ?? index),
94
+ name: model.name,
95
+ loop: options.loop ?? true,
96
+ duration,
97
+ thumbnail: model.thumbnailPath ? normalizeUrl(model.thumbnailPath) : void 0,
98
+ resources: {
99
+ modelPath: normalizeUrl(model.filePath),
100
+ motionPath: motion ? normalizeUrl(motion.filePath) : void 0,
101
+ cameraPath: void 0,
102
+ audioPath: void 0,
103
+ stageModelPath: void 0,
104
+ additionalMotions: void 0
105
+ }
106
+ };
107
+ });
108
+ return {
109
+ id: options.playlistId,
110
+ name: options.playlistName ?? `MMD \u64AD\u653E\u5217\u8868 - ${options.playlistId}`,
111
+ nodes,
112
+ loop: options.loop ?? true,
113
+ preload: options.preload ?? "next",
114
+ autoPlay: options.autoPlay ?? true
115
+ };
116
+ }
117
+
118
+ // src/mmd/server/mmdUpload.ts
119
+ var MMD_SUPPORTED_TYPES = {
120
+ model: [...MMD_MODEL_ARCHIVE_MIME_TYPES],
121
+ animation: ["application/octet-stream", "animation/vmd"],
122
+ audio: ["audio/wav", "audio/mp3", "audio/mpeg", "audio/ogg"]
123
+ };
124
+ var MMD_FILE_EXTENSIONS = {
125
+ model: [".zip"],
126
+ animation: [".vmd"],
127
+ audio: [".wav", ".mp3", ".ogg"]
128
+ };
129
+ async function uploadMmdResource(fileService, options) {
130
+ const { file, resourceType, name, description, userId } = options;
131
+ const ext = `.${file.name.split(".").pop()?.toLowerCase()}`;
132
+ const allowedExtensions = MMD_FILE_EXTENSIONS[resourceType];
133
+ if (!allowedExtensions.includes(ext)) {
134
+ throw new Error(
135
+ `\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u6269\u5C55\u540D: ${ext}\u3002\u652F\u6301\u7684\u683C\u5F0F: ${allowedExtensions.join(", ")}`
136
+ );
137
+ }
138
+ const moduleId = `mmd-${resourceType}s`;
139
+ const metadata = await fileService.uploadFile({
140
+ file,
141
+ moduleId,
142
+ businessId: "default",
143
+ permission: "public",
144
+ metadata: {
145
+ uploadedBy: userId,
146
+ uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
147
+ originalFileName: file.name,
148
+ resourceType,
149
+ name,
150
+ description: description || ""
151
+ },
152
+ needsProcessing: false
153
+ });
154
+ const format = ext.slice(1).toUpperCase();
155
+ const fileUrl = metadata.cdnUrl || metadata.storagePath;
156
+ return {
157
+ id: metadata.id,
158
+ name,
159
+ url: fileUrl,
160
+ filePath: metadata.storagePath,
161
+ fileSize: metadata.size,
162
+ type: resourceType,
163
+ format,
164
+ uploadTime: metadata.uploadTime,
165
+ metadata
166
+ };
167
+ }
168
+ async function uploadMmdModel(fileService, options) {
169
+ const { file, storageRoot, publicRoot } = options;
170
+ const baseResult = await uploadMmdResource(fileService, options);
171
+ try {
172
+ const fileBuffer = Buffer.from(await file.arrayBuffer());
173
+ const archiveResult = await processMmdModelArchive(fileBuffer, {
174
+ storageRoot,
175
+ publicRoot,
176
+ folderName: baseResult.id
177
+ });
178
+ return {
179
+ ...baseResult,
180
+ url: archiveResult.modelUrl,
181
+ filePath: archiveResult.modelUrl,
182
+ format: archiveResult.format.toUpperCase(),
183
+ extractedPath: archiveResult.modelUrl
184
+ };
185
+ } catch (extractError) {
186
+ console.error("\u6A21\u578B\u538B\u7F29\u5305\u5904\u7406\u5931\u8D25:", extractError);
187
+ throw new Error(
188
+ extractError instanceof Error ? extractError.message : "\u6A21\u578B\u538B\u7F29\u5305\u5904\u7406\u5931\u8D25"
189
+ );
190
+ }
191
+ }
192
+ async function batchUploadMmdResources(fileService, uploads) {
193
+ const results = [];
194
+ for (const uploadOptions of uploads) {
195
+ try {
196
+ const result = await uploadMmdResource(fileService, uploadOptions);
197
+ results.push(result);
198
+ } catch (error) {
199
+ console.error(`\u4E0A\u4F20\u5931\u8D25: ${uploadOptions.file.name}`, error);
200
+ }
201
+ }
202
+ return results;
203
+ }
204
+
205
+ export { MMD_FILE_EXTENSIONS, MMD_MODEL_ARCHIVE_MIME_TYPES, MMD_SUPPORTED_TYPES, batchUploadMmdResources, buildMmdPlaylistFromSources, processMmdModelArchive, uploadMmdModel, uploadMmdResource };
206
+ //# sourceMappingURL=index.mjs.map
207
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/mmd/server/modelArchive.ts","../../../src/mmd/server/playlistBuilder.ts","../../../src/mmd/server/mmdUpload.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import AdmZip from 'adm-zip';\nimport { randomUUID } from 'crypto';\nimport { mkdir, rm, writeFile } from 'fs/promises';\nimport path from 'path';\n\nexport type SupportedModelFormat = 'pmx' | 'pmd';\n\nexport interface ProcessMmdModelArchiveOptions {\n /**\n * 绝对路径,指向模型解压根目录,例如 /app/uploads/mmd/models\n */\n storageRoot: string;\n /**\n * 对外暴露的公共路径前缀,例如 /uploads/mmd/models\n * 默认与 storageRoot 中的叶子目录一致\n */\n publicRoot?: string;\n /**\n * 自定义文件夹名称,默认使用随机 UUID\n */\n folderName?: string;\n}\n\nexport interface ProcessMmdModelArchiveResult {\n /** 解压出来的目录(绝对路径) */\n directory: string;\n /** 与 storageRoot 相对的目录名 */\n relativeDirectory: string;\n /** 模型文件相对目录(包含子目录) */\n modelRelativePath: string;\n /** 可供前端使用的模型 URL */\n modelUrl: string;\n /** 模型格式 */\n format: SupportedModelFormat;\n /** 解压得到的文件数量 */\n filesExtracted: number;\n}\n\nexport const MMD_MODEL_ARCHIVE_MIME_TYPES = [\n 'application/zip',\n 'application/x-zip-compressed',\n 'multipart/x-zip',\n] as const;\n\n/**\n * 解析上传的 MMD 模型压缩包,保留目录结构并返回模型路径\n */\nexport async function processMmdModelArchive(\n buffer: Buffer,\n options: ProcessMmdModelArchiveOptions,\n): Promise<ProcessMmdModelArchiveResult> {\n const folderName = options.folderName ?? randomUUID();\n const targetDir = path.join(options.storageRoot, folderName);\n await mkdir(targetDir, { recursive: true });\n\n const zip = new AdmZip(buffer);\n const entries = zip.getEntries();\n if (!entries.length) {\n await rm(targetDir, { recursive: true, force: true });\n throw new Error('压缩包为空,请检查上传文件内容');\n }\n\n let modelRelativePath: string | null = null;\n let filesExtracted = 0;\n\n for (const entry of entries) {\n if (!entry.entryName || entry.entryName.startsWith('__MACOSX') || entry.entryName.endsWith('.DS_Store')) {\n continue;\n }\n\n const safeRelativePath = sanitizeEntryPath(entry.entryName);\n if (!safeRelativePath) {\n continue;\n }\n\n const destinationPath = path.join(targetDir, safeRelativePath);\n\n if (entry.isDirectory) {\n await mkdir(destinationPath, { recursive: true });\n continue;\n }\n\n await mkdir(path.dirname(destinationPath), { recursive: true });\n await writeFile(destinationPath, entry.getData());\n filesExtracted += 1;\n\n const entryExt = path.extname(safeRelativePath).toLowerCase();\n if (!modelRelativePath && (entryExt === '.pmx' || entryExt === '.pmd')) {\n modelRelativePath = safeRelativePath.split(path.sep).join('/');\n }\n }\n\n if (!modelRelativePath) {\n await rm(targetDir, { recursive: true, force: true });\n throw new Error('压缩包中未找到 PMX/PMD 模型文件,请确认目录结构是否正确');\n }\n\n const format = path.extname(modelRelativePath).slice(1).toLowerCase() as SupportedModelFormat;\n const publicRoot = options.publicRoot ?? `/uploads/mmd/models`;\n const modelUrl = joinPublicPath(publicRoot, folderName, modelRelativePath);\n\n return {\n directory: targetDir,\n relativeDirectory: folderName,\n modelRelativePath,\n modelUrl,\n format,\n filesExtracted,\n };\n}\n\nfunction sanitizeEntryPath(entryName: string): string | null {\n const normalized = path.normalize(entryName).replace(/^(\\.\\.(\\/|\\\\|$))+/, '');\n if (!normalized || normalized === '.' || normalized.startsWith('..') || path.isAbsolute(normalized)) {\n return null;\n }\n return normalized;\n}\n\nfunction joinPublicPath(...segments: string[]) {\n return segments\n .map((segment) => segment.replace(/\\/+/g, '/').replace(/^\\//, '').replace(/\\/$/, ''))\n .filter(Boolean)\n .join('/')\n .replace(/\\/{2,}/g, '/')\n .replace(/^/, '/');\n}\n\n","import type { MMDPlaylistConfig, MMDPlaylistNode } from '../types';\n\nexport interface PlaylistModelSource {\n id: string | number;\n name: string;\n filePath: string;\n thumbnailPath?: string | null;\n}\n\nexport interface PlaylistMotionSource {\n id: string | number;\n name?: string;\n filePath: string;\n}\n\nexport interface BuildMmdPlaylistOptions {\n playlistId: string;\n playlistName?: string;\n models: PlaylistModelSource[];\n motions?: PlaylistMotionSource[];\n limit?: number;\n loop?: boolean;\n preload?: 'none' | 'next' | 'all';\n autoPlay?: boolean;\n nodeDuration?: number;\n normalizeUrl?: (pathOrUrl: string) => string;\n}\n\nconst defaultNormalizer = (value: string) => {\n if (!value) return '';\n if (value.startsWith('http://') || value.startsWith('https://')) {\n return value;\n }\n return value.startsWith('/') ? value : `/${value}`;\n};\n\n/**\n * 根据数据库中的模型/动作记录快速构建 MMDPlaylistConfig\n */\nexport function buildMmdPlaylistFromSources(options: BuildMmdPlaylistOptions): MMDPlaylistConfig {\n if (!options.models.length) {\n throw new Error('构建 MMD 播放列表失败:models 为空');\n }\n\n const limit = Math.max(1, Math.min(options.limit ?? options.models.length, options.models.length));\n const normalizeUrl = options.normalizeUrl ?? defaultNormalizer;\n const motions = options.motions ?? [];\n const hasMotions = motions.length > 0;\n const duration = options.nodeDuration ?? 30;\n\n const nodes: MMDPlaylistNode[] = options.models.slice(0, limit).map((model, index) => {\n const motion = hasMotions ? motions[index % motions.length] : undefined;\n return {\n id: String(model.id ?? index),\n name: model.name,\n loop: options.loop ?? true,\n duration,\n thumbnail: model.thumbnailPath ? normalizeUrl(model.thumbnailPath) : undefined,\n resources: {\n modelPath: normalizeUrl(model.filePath),\n motionPath: motion ? normalizeUrl(motion.filePath) : undefined,\n cameraPath: undefined,\n audioPath: undefined,\n stageModelPath: undefined,\n additionalMotions: undefined,\n },\n };\n });\n\n return {\n id: options.playlistId,\n name: options.playlistName ?? `MMD 播放列表 - ${options.playlistId}`,\n nodes,\n loop: options.loop ?? true,\n preload: options.preload ?? 'next',\n autoPlay: options.autoPlay ?? true,\n };\n}\n\n","/**\n * MMD 资源上传辅助函数\n * \n * 整合 UniversalFileService 用于 MMD 资源上传\n */\n\nimport type { UniversalFileService } from '../../universalFile/server/UniversalFileService';\nimport type { FileMetadata } from '../../universalFile/types';\nimport { processMmdModelArchive, MMD_MODEL_ARCHIVE_MIME_TYPES } from './modelArchive';\n\nexport const MMD_SUPPORTED_TYPES = {\n model: [...MMD_MODEL_ARCHIVE_MIME_TYPES],\n animation: ['application/octet-stream', 'animation/vmd'],\n audio: ['audio/wav', 'audio/mp3', 'audio/mpeg', 'audio/ogg'],\n};\n\nexport const MMD_FILE_EXTENSIONS = {\n model: ['.zip'],\n animation: ['.vmd'],\n audio: ['.wav', '.mp3', '.ogg'],\n};\n\nexport interface MmdUploadOptions {\n file: File;\n resourceType: 'model' | 'animation' | 'audio';\n name: string;\n description?: string;\n userId: string;\n}\n\nexport interface MmdUploadResult {\n id: string;\n name: string;\n url: string;\n filePath: string;\n fileSize: number;\n type: string;\n format: string;\n uploadTime: Date;\n metadata?: FileMetadata;\n}\n\n/**\n * 使用 UniversalFileService 上传 MMD 资源\n * \n * @param fileService - UniversalFileService 实例\n * @param options - 上传选项\n * @returns 上传结果\n */\nexport async function uploadMmdResource(\n fileService: UniversalFileService,\n options: MmdUploadOptions\n): Promise<MmdUploadResult> {\n const { file, resourceType, name, description, userId } = options;\n\n // 验证文件扩展名\n const ext = `.${file.name.split('.').pop()?.toLowerCase()}`;\n const allowedExtensions = MMD_FILE_EXTENSIONS[resourceType];\n\n if (!allowedExtensions.includes(ext)) {\n throw new Error(\n `不支持的文件扩展名: ${ext}。支持的格式: ${allowedExtensions.join(', ')}`\n );\n }\n\n // 确定模块ID\n const moduleId = `mmd-${resourceType}s`;\n\n // 上传文件\n const metadata = await fileService.uploadFile({\n file,\n moduleId,\n businessId: 'default',\n permission: 'public',\n metadata: {\n uploadedBy: userId,\n uploadedAt: new Date().toISOString(),\n originalFileName: file.name,\n resourceType,\n name,\n description: description || '',\n },\n needsProcessing: false,\n });\n\n // 构建返回结果\n const format = ext.slice(1).toUpperCase();\n const fileUrl = metadata.cdnUrl || metadata.storagePath;\n\n return {\n id: metadata.id,\n name,\n url: fileUrl,\n filePath: metadata.storagePath,\n fileSize: metadata.size,\n type: resourceType,\n format,\n uploadTime: metadata.uploadTime,\n metadata,\n };\n}\n\n/**\n * 上传 MMD 模型 (ZIP 压缩包)\n * \n * @param fileService - UniversalFileService 实例\n * @param options - 上传选项\n * @returns 上传结果,包含解压后的模型路径\n */\nexport async function uploadMmdModel(\n fileService: UniversalFileService,\n options: MmdUploadOptions & { storageRoot: string; publicRoot: string }\n): Promise<MmdUploadResult & { extractedPath?: string }> {\n const { file, storageRoot, publicRoot } = options;\n\n // 首先上传 ZIP 文件\n const baseResult = await uploadMmdResource(fileService, options);\n\n // 处理 ZIP 压缩包\n try {\n const fileBuffer = Buffer.from(await file.arrayBuffer());\n const archiveResult = await processMmdModelArchive(fileBuffer, {\n storageRoot,\n publicRoot,\n folderName: baseResult.id,\n });\n\n return {\n ...baseResult,\n url: archiveResult.modelUrl,\n filePath: archiveResult.modelUrl,\n format: archiveResult.format.toUpperCase(),\n extractedPath: archiveResult.modelUrl,\n };\n } catch (extractError) {\n console.error('模型压缩包处理失败:', extractError);\n throw new Error(\n extractError instanceof Error ? extractError.message : '模型压缩包处理失败'\n );\n }\n}\n\n/**\n * 批量上传 MMD 资源\n * \n * @param fileService - UniversalFileService 实例\n * @param uploads - 上传选项数组\n * @returns 上传结果数组\n */\nexport async function batchUploadMmdResources(\n fileService: UniversalFileService,\n uploads: MmdUploadOptions[]\n): Promise<MmdUploadResult[]> {\n const results: MmdUploadResult[] = [];\n\n for (const uploadOptions of uploads) {\n try {\n const result = await uploadMmdResource(fileService, uploadOptions);\n results.push(result);\n } catch (error) {\n console.error(`上传失败: ${uploadOptions.file.name}`, error);\n // 继续处理其他文件\n }\n }\n\n return results;\n}\n\n"]}
@@ -0,0 +1,145 @@
1
+ import React__default from 'react';
2
+
3
+ /**
4
+ * 测测你是什么 - 类型定义
5
+ * Test Yourself Game - Type Definitions
6
+ */
7
+ /**
8
+ * 测试结果项
9
+ */
10
+ interface TestResult {
11
+ /** 唯一标识 */
12
+ id: string;
13
+ /** 标题/题目 */
14
+ title: string;
15
+ /** 描述 */
16
+ description: string;
17
+ /** 图片URL或emoji */
18
+ image: string;
19
+ /** 图片类型 */
20
+ imageType?: 'url' | 'emoji';
21
+ /** 额外属性(可扩展) */
22
+ extra?: Record<string, any>;
23
+ }
24
+ /**
25
+ * 测试配置
26
+ */
27
+ interface TestConfig {
28
+ /** 游戏标题 */
29
+ gameTitle: string;
30
+ /** 游戏描述 */
31
+ gameDescription?: string;
32
+ /** 按钮文本 */
33
+ buttonText?: string;
34
+ /** 长按时间(毫秒) */
35
+ longPressDuration?: number;
36
+ /** 结果数据集 */
37
+ results: TestResult[];
38
+ /** 是否启用IP获取 */
39
+ enableIPFetch?: boolean;
40
+ /** 自定义盐值 */
41
+ customSalt?: string;
42
+ /** 结果展示样式 */
43
+ resultStyle?: 'card' | 'full' | 'minimal';
44
+ }
45
+ /**
46
+ * 设备指纹信息
47
+ */
48
+ interface DeviceFingerprint {
49
+ /** User Agent */
50
+ userAgent: string;
51
+ /** IP地址(如果可获取) */
52
+ ip?: string;
53
+ /** 屏幕分辨率 */
54
+ screenResolution: string;
55
+ /** 时区 */
56
+ timezone: string;
57
+ /** 语言 */
58
+ language: string;
59
+ /** 平台 */
60
+ platform: string;
61
+ /** 颜色深度 */
62
+ colorDepth?: number;
63
+ /** 像素比 */
64
+ devicePixelRatio?: number;
65
+ /** 硬件并发数 */
66
+ hardwareConcurrency?: number;
67
+ /** 最大触摸点数 */
68
+ maxTouchPoints?: number;
69
+ /** Canvas指纹 */
70
+ canvasFingerprint?: string;
71
+ /** WebGL指纹 */
72
+ webglFingerprint?: string;
73
+ /** 字体列表 */
74
+ fonts?: string;
75
+ /** 是否支持cookie */
76
+ cookieEnabled?: boolean;
77
+ /** 是否支持本地存储 */
78
+ localStorageEnabled?: boolean;
79
+ /** 是否支持会话存储 */
80
+ sessionStorageEnabled?: boolean;
81
+ /** 是否支持IndexedDB */
82
+ indexedDBEnabled?: boolean;
83
+ }
84
+ /**
85
+ * 测试状态
86
+ */
87
+ type TestStatus = 'idle' | 'pressing' | 'completed';
88
+ /**
89
+ * 组件属性
90
+ */
91
+ interface TestYourselfProps {
92
+ /** 配置 */
93
+ config: TestConfig;
94
+ /** 结果回调 */
95
+ onResult?: (result: TestResult) => void;
96
+ /** 自定义样式类名 */
97
+ className?: string;
98
+ }
99
+
100
+ /**
101
+ * 测测你是什么 - 主组件
102
+ * Test Yourself Game - Main Component
103
+ */
104
+
105
+ declare const TestYourself: React__default.FC<TestYourselfProps>;
106
+
107
+ /**
108
+ * 设备指纹生成工具
109
+ * Device Fingerprint Generator
110
+ */
111
+
112
+ /**
113
+ * 获取设备指纹信息(增强版)
114
+ */
115
+ declare function getDeviceFingerprint(): DeviceFingerprint;
116
+ /**
117
+ * 尝试获取IP地址
118
+ * 注意:由于浏览器安全限制,直接获取IP地址需要外部API
119
+ */
120
+ declare function tryGetIPAddress(): Promise<string | null>;
121
+ /**
122
+ * 生成设备唯一标识
123
+ * @param fingerprint 设备指纹
124
+ * @param salt 盐值
125
+ * @returns 唯一标识哈希值
126
+ */
127
+ declare function generateDeviceHash(fingerprint: DeviceFingerprint, salt?: string): string;
128
+ /**
129
+ * 根据哈希值选择结果索引
130
+ * @param hash 设备哈希值
131
+ * @param totalResults 总结果数
132
+ * @returns 结果索引 (0 到 totalResults-1)
133
+ */
134
+ declare function selectResultIndex(hash: string, totalResults: number): number;
135
+
136
+ /**
137
+ * 默认测试结果数据
138
+ * Default Test Results Data
139
+ *
140
+ * 包含45个预设结果
141
+ */
142
+
143
+ declare const DEFAULT_RESULTS: TestResult[];
144
+
145
+ export { DEFAULT_RESULTS, type DeviceFingerprint, type TestConfig, type TestResult, type TestStatus, TestYourself, type TestYourselfProps, generateDeviceHash, getDeviceFingerprint, selectResultIndex, tryGetIPAddress };
@@ -0,0 +1,145 @@
1
+ import React__default from 'react';
2
+
3
+ /**
4
+ * 测测你是什么 - 类型定义
5
+ * Test Yourself Game - Type Definitions
6
+ */
7
+ /**
8
+ * 测试结果项
9
+ */
10
+ interface TestResult {
11
+ /** 唯一标识 */
12
+ id: string;
13
+ /** 标题/题目 */
14
+ title: string;
15
+ /** 描述 */
16
+ description: string;
17
+ /** 图片URL或emoji */
18
+ image: string;
19
+ /** 图片类型 */
20
+ imageType?: 'url' | 'emoji';
21
+ /** 额外属性(可扩展) */
22
+ extra?: Record<string, any>;
23
+ }
24
+ /**
25
+ * 测试配置
26
+ */
27
+ interface TestConfig {
28
+ /** 游戏标题 */
29
+ gameTitle: string;
30
+ /** 游戏描述 */
31
+ gameDescription?: string;
32
+ /** 按钮文本 */
33
+ buttonText?: string;
34
+ /** 长按时间(毫秒) */
35
+ longPressDuration?: number;
36
+ /** 结果数据集 */
37
+ results: TestResult[];
38
+ /** 是否启用IP获取 */
39
+ enableIPFetch?: boolean;
40
+ /** 自定义盐值 */
41
+ customSalt?: string;
42
+ /** 结果展示样式 */
43
+ resultStyle?: 'card' | 'full' | 'minimal';
44
+ }
45
+ /**
46
+ * 设备指纹信息
47
+ */
48
+ interface DeviceFingerprint {
49
+ /** User Agent */
50
+ userAgent: string;
51
+ /** IP地址(如果可获取) */
52
+ ip?: string;
53
+ /** 屏幕分辨率 */
54
+ screenResolution: string;
55
+ /** 时区 */
56
+ timezone: string;
57
+ /** 语言 */
58
+ language: string;
59
+ /** 平台 */
60
+ platform: string;
61
+ /** 颜色深度 */
62
+ colorDepth?: number;
63
+ /** 像素比 */
64
+ devicePixelRatio?: number;
65
+ /** 硬件并发数 */
66
+ hardwareConcurrency?: number;
67
+ /** 最大触摸点数 */
68
+ maxTouchPoints?: number;
69
+ /** Canvas指纹 */
70
+ canvasFingerprint?: string;
71
+ /** WebGL指纹 */
72
+ webglFingerprint?: string;
73
+ /** 字体列表 */
74
+ fonts?: string;
75
+ /** 是否支持cookie */
76
+ cookieEnabled?: boolean;
77
+ /** 是否支持本地存储 */
78
+ localStorageEnabled?: boolean;
79
+ /** 是否支持会话存储 */
80
+ sessionStorageEnabled?: boolean;
81
+ /** 是否支持IndexedDB */
82
+ indexedDBEnabled?: boolean;
83
+ }
84
+ /**
85
+ * 测试状态
86
+ */
87
+ type TestStatus = 'idle' | 'pressing' | 'completed';
88
+ /**
89
+ * 组件属性
90
+ */
91
+ interface TestYourselfProps {
92
+ /** 配置 */
93
+ config: TestConfig;
94
+ /** 结果回调 */
95
+ onResult?: (result: TestResult) => void;
96
+ /** 自定义样式类名 */
97
+ className?: string;
98
+ }
99
+
100
+ /**
101
+ * 测测你是什么 - 主组件
102
+ * Test Yourself Game - Main Component
103
+ */
104
+
105
+ declare const TestYourself: React__default.FC<TestYourselfProps>;
106
+
107
+ /**
108
+ * 设备指纹生成工具
109
+ * Device Fingerprint Generator
110
+ */
111
+
112
+ /**
113
+ * 获取设备指纹信息(增强版)
114
+ */
115
+ declare function getDeviceFingerprint(): DeviceFingerprint;
116
+ /**
117
+ * 尝试获取IP地址
118
+ * 注意:由于浏览器安全限制,直接获取IP地址需要外部API
119
+ */
120
+ declare function tryGetIPAddress(): Promise<string | null>;
121
+ /**
122
+ * 生成设备唯一标识
123
+ * @param fingerprint 设备指纹
124
+ * @param salt 盐值
125
+ * @returns 唯一标识哈希值
126
+ */
127
+ declare function generateDeviceHash(fingerprint: DeviceFingerprint, salt?: string): string;
128
+ /**
129
+ * 根据哈希值选择结果索引
130
+ * @param hash 设备哈希值
131
+ * @param totalResults 总结果数
132
+ * @returns 结果索引 (0 到 totalResults-1)
133
+ */
134
+ declare function selectResultIndex(hash: string, totalResults: number): number;
135
+
136
+ /**
137
+ * 默认测试结果数据
138
+ * Default Test Results Data
139
+ *
140
+ * 包含45个预设结果
141
+ */
142
+
143
+ declare const DEFAULT_RESULTS: TestResult[];
144
+
145
+ export { DEFAULT_RESULTS, type DeviceFingerprint, type TestConfig, type TestResult, type TestStatus, TestYourself, type TestYourselfProps, generateDeviceHash, getDeviceFingerprint, selectResultIndex, tryGetIPAddress };