storageService.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.ensureDir = ensureDir;
  7. exports.parseDetailUrl = parseDetailUrl;
  8. exports.downloadFile = downloadFile;
  9. exports.xorDecryptBuffer = xorDecryptBuffer;
  10. exports.getCreativeAssetsDir = getCreativeAssetsDir;
  11. exports.getBuildOutputDir = getBuildOutputDir;
  12. exports.scanAssetFiles = scanAssetFiles;
  13. const fs_1 = __importDefault(require("fs"));
  14. const path_1 = __importDefault(require("path"));
  15. const https_1 = __importDefault(require("https"));
  16. const http_1 = __importDefault(require("http"));
  17. function ensureDir(dir) {
  18. if (!fs_1.default.existsSync(dir)) {
  19. fs_1.default.mkdirSync(dir, { recursive: true });
  20. }
  21. }
  22. /**
  23. * 从填色详情页 URL 中提取素材 ID 并拼接 zip 下载地址。
  24. *
  25. * 输入:https://color2.jccytech.cn/app/zh/pages/detail/6a154397957ac783bac98e10
  26. * 输出:https://color2.jccytech.cn/zips/v2/number_mini/1501/6a154397957ac783bac98e10.zip
  27. */
  28. function parseDetailUrl(detailUrl) {
  29. try {
  30. const url = new URL(detailUrl);
  31. // 取路径最后一段作为 ID
  32. const segments = url.pathname.split("/").filter(Boolean);
  33. const id = segments[segments.length - 1];
  34. if (!id || id.length < 20)
  35. return null;
  36. const zipUrl = `https://color2.jccytech.cn/zips/v2/number_mini/1501/${id}.zip`;
  37. return { id, zipUrl };
  38. }
  39. catch {
  40. return null;
  41. }
  42. }
  43. /**
  44. * 下载远程文件到 Buffer
  45. */
  46. function downloadFile(url) {
  47. return new Promise((resolve, reject) => {
  48. const client = url.startsWith("https") ? https_1.default : http_1.default;
  49. client.get(url, (res) => {
  50. if (res.statusCode !== 200) {
  51. reject(new Error(`Download failed: HTTP ${res.statusCode}`));
  52. return;
  53. }
  54. const chunks = [];
  55. res.on("data", (chunk) => chunks.push(chunk));
  56. res.on("end", () => resolve(Buffer.concat(chunks)));
  57. res.on("error", reject);
  58. }).on("error", reject);
  59. });
  60. }
  61. /**
  62. * XOR 解密 zip 文件。
  63. *
  64. * 密钥 = 文件名(不含扩展名),即素材 ID。
  65. * 对整个文件逐字节异或解密,密钥循环使用。
  66. */
  67. function xorDecryptBuffer(encrypted, key) {
  68. const keyBuf = Buffer.from(key);
  69. const keyLen = keyBuf.length;
  70. const decrypted = Buffer.alloc(encrypted.length);
  71. for (let i = 0; i < encrypted.length; i++) {
  72. decrypted[i] = encrypted[i] ^ keyBuf[i % keyLen];
  73. }
  74. return decrypted;
  75. }
  76. function getCreativeAssetsDir(storageDir, creativeId) {
  77. const dir = path_1.default.join(storageDir, "creatives", creativeId, "assets");
  78. return dir;
  79. }
  80. function getBuildOutputDir(storageDir, creativeId, buildId) {
  81. const dir = path_1.default.join(storageDir, "creatives", creativeId, "builds", buildId);
  82. ensureDir(dir);
  83. return dir;
  84. }
  85. function scanAssetFiles(assetsDir) {
  86. const result = {
  87. config: false, page: false, map: false,
  88. special: null, logo: null, logoTxt: null,
  89. slogon: null, coloringPages: null,
  90. };
  91. if (!fs_1.default.existsSync(assetsDir))
  92. return result;
  93. const files = fs_1.default.readdirSync(assetsDir);
  94. for (const file of files) {
  95. const lower = file.toLowerCase();
  96. if (lower === "config.json")
  97. result.config = true;
  98. else if (lower === "page.png" || lower === "page.jpg" || lower === "page.jpeg")
  99. result.page = true;
  100. else if (lower === "map.png")
  101. result.map = true;
  102. else if (lower.startsWith("special."))
  103. result.special = file;
  104. else if (lower === "logo.png")
  105. result.logo = file;
  106. else if (lower === "logo-txt.png")
  107. result.logoTxt = file;
  108. else if (lower === "slogon.png")
  109. result.slogon = file;
  110. else if (lower === "coloring-pages.png")
  111. result.coloringPages = file;
  112. }
  113. return result;
  114. }
  115. //# sourceMappingURL=storageService.js.map