UNPKG

jimeng-web-mcp

Version:

MCP服务器项目,直接访问即梦AI Web端进行图像和视频生成(仅供学习研究使用)

1,618 lines (1,600 loc) 128 kB
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) { if (typeof require !== "undefined") return require.apply(this, arguments); throw Error('Dynamic require of "' + x + '" is not supported'); }); // src/server.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // src/utils/index.ts import { v4 as uuidv4 } from "uuid"; // src/types/constants.ts var MAX_IMAGES_PER_REQUEST = 4; var POLLING = { MAX_ATTEMPTS: 180, // 6 minutes (increased for continue generation) INTERVAL_MS: 2e3, TIMEOUT_MS: 36e4 // 6 minutes }; var CACHE_CONFIG = { TTL_MS: 30 * 60 * 1e3, // 30 minutes EVICTION_INTERVAL_MS: 5 * 60 * 1e3 // 5 minutes }; var LogLevel = /* @__PURE__ */ ((LogLevel2) => { LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG"; LogLevel2[LogLevel2["INFO"] = 1] = "INFO"; LogLevel2[LogLevel2["WARN"] = 2] = "WARN"; LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR"; return LogLevel2; })(LogLevel || {}); // src/utils/cache-manager.ts var CacheManager = class { /** * Initialize periodic eviction (called once) */ static initialize() { if (!this.initialized) { this.startPeriodicEviction(); this.initialized = true; } } /** * Store cache entry */ static set(historyId, entry) { this.initialize(); const now = Date.now(); const fullEntry = { ...entry, createdAt: now, expiresAt: now + CACHE_CONFIG.TTL_MS, state: "CREATED" /* CREATED */ }; this.cache.set(historyId, fullEntry); } /** * Retrieve cache entry */ static get(historyId) { const entry = this.cache.get(historyId); if (entry && Date.now() > entry.expiresAt) { this.cache.delete(historyId); return void 0; } return entry; } /** * Check if entry exists */ static has(historyId) { return this.get(historyId) !== void 0; } /** * Remove specific entry (cleanup) */ static cleanup(historyId) { return this.cache.delete(historyId); } /** * Get cache size */ static size() { return this.cache.size; } /** * Remove expired entries (TTL eviction) */ static evictExpired() { const now = Date.now(); let evictedCount = 0; for (const [historyId, entry] of this.cache.entries()) { if (now > entry.expiresAt) { this.cache.delete(historyId); evictedCount++; } } return evictedCount; } /** * Get cache statistics */ static getStats() { const stats = { size: this.cache.size, byState: { created: 0, active: 0, completed: 0 }, expired: 0, memoryEstimateKB: 0 }; const now = Date.now(); for (const entry of this.cache.values()) { switch (entry.state) { case "CREATED" /* CREATED */: stats.byState.created++; break; case "ACTIVE" /* ACTIVE */: stats.byState.active++; break; case "COMPLETED" /* COMPLETED */: stats.byState.completed++; break; } if (now > entry.expiresAt) { stats.expired++; } stats.memoryEstimateKB += this.estimateEntrySize(entry) / 1024; } return stats; } /** * Clear all entries (testing only) */ static clear() { this.cache.clear(); } /** * Start periodic eviction timer */ static startPeriodicEviction() { this.evictionTimer = setInterval(() => { this.evictExpired(); }, CACHE_CONFIG.EVICTION_INTERVAL_MS); if (this.evictionTimer.unref) { this.evictionTimer.unref(); } } /** * Stop periodic eviction (cleanup) */ static stopPeriodicEviction() { if (this.evictionTimer) { clearInterval(this.evictionTimer); this.evictionTimer = void 0; this.initialized = false; } } /** * Estimate entry size in bytes */ static estimateEntrySize(entry) { try { return JSON.stringify(entry).length; } catch { return 1024; } } }; CacheManager.cache = /* @__PURE__ */ new Map(); CacheManager.initialized = false; // src/utils/logger.ts var DEFAULT_CONFIG = { minLevel: process.env.DEBUG === "true" ? 0 /* DEBUG */ : 1 /* INFO */, redactKeys: [ "token", "sessionid", "password", "api_key", "secret", "credential", "auth" ], enableTimestamps: true }; var Logger = class { constructor(config = {}) { this.config = { ...DEFAULT_CONFIG, ...config }; } debug(message, context) { if (this.isLevelEnabled(0 /* DEBUG */)) { this.log(0 /* DEBUG */, message, context); } } info(message, context) { if (this.isLevelEnabled(1 /* INFO */)) { this.log(1 /* INFO */, message, context); } } warn(message, context) { if (this.isLevelEnabled(2 /* WARN */)) { this.log(2 /* WARN */, message, context); } } error(message, context) { if (this.isLevelEnabled(3 /* ERROR */)) { this.log(3 /* ERROR */, message, context, true); } } isLevelEnabled(level) { const currentMinLevel = process.env.DEBUG === "true" ? 0 /* DEBUG */ : 1 /* INFO */; return level >= currentMinLevel; } /** * Reconfigure logger (mainly for testing) */ reconfigure(config) { this.config = { ...this.config, ...config }; } log(level, message, context, useStderr = false) { const levelName = LogLevel[level]; const timestamp = this.config.enableTimestamps ? (/* @__PURE__ */ new Date()).toISOString() : ""; const sanitizedContext = context ? this.sanitizeContext(context) : void 0; let output = `[${levelName}]`; if (timestamp) { output = `${timestamp} ${output}`; } output += ` ${message}`; if (sanitizedContext) { output += ` ${JSON.stringify(sanitizedContext)}`; } process.stderr.write(output + "\n"); } sanitizeContext(context) { const sanitized = {}; for (const [key, value] of Object.entries(context)) { const shouldRedact = this.config.redactKeys.some( (redactKey) => key.toLowerCase().includes(redactKey.toLowerCase()) ); sanitized[key] = shouldRedact ? "[REDACTED]" : value; } return sanitized; } }; var logger = new Logger(); // src/utils/prompt-validator.ts var COUNT_PATTERNS = { FULL_PATTERN: /一共\s*(\d+)\s*张图?/, SHORT_PATTERN: /共\s*(\d+)\s*张/, TOTAL_PATTERN: /总共\s*(\d+)\s*张/, GENERIC_PATTERN: /(\d+)\s*张图/ }; var PromptValidator = class { /** * Check if prompt already contains a count declaration */ hasCountDeclaration(prompt) { return COUNT_PATTERNS.FULL_PATTERN.test(prompt) || COUNT_PATTERNS.SHORT_PATTERN.test(prompt) || COUNT_PATTERNS.TOTAL_PATTERN.test(prompt) || COUNT_PATTERNS.GENERIC_PATTERN.test(prompt); } /** * Safely append count to prompt, avoiding duplicates */ appendCountIfMissing(prompt, count) { if (this.hasCountDeclaration(prompt)) { return prompt; } const trimmed = prompt.trim(); if (trimmed.length === 0) { return `\u4E00\u5171${count}\u5F20\u56FE`; } return `${prompt}\uFF0C\u4E00\u5171${count}\u5F20\u56FE`; } /** * Extract declared count from prompt (optional enhancement) */ extractCount(prompt) { let match = prompt.match(COUNT_PATTERNS.FULL_PATTERN); if (match) return parseInt(match[1], 10); match = prompt.match(COUNT_PATTERNS.SHORT_PATTERN); if (match) return parseInt(match[1], 10); match = prompt.match(COUNT_PATTERNS.TOTAL_PATTERN); if (match) return parseInt(match[1], 10); match = prompt.match(COUNT_PATTERNS.GENERIC_PATTERN); if (match) return parseInt(match[1], 10); return null; } }; var promptValidator = new PromptValidator(); // src/utils/index.ts function toUrlParams(obj) { const params = new URLSearchParams(); Object.entries(obj).forEach(([key, value]) => { if (value !== void 0 && value !== null) { if (Array.isArray(value)) { value.forEach((v) => params.append(key, String(v))); } else { params.append(key, String(value)); } } }); return params.toString(); } var generateMsToken = (randomlength = 128) => { const baseStr = "ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789="; let random_str = ""; const length = baseStr.length - 1; for (let i = 0; i < randomlength; i++) { random_str += baseStr[Math.floor(Math.random() * length)]; } return random_str; }; var generateUuid = () => { return uuidv4(); }; var jsonEncode = (obj) => { return JSON.stringify(obj); }; // src/types/models.ts var MODEL_MAP = { // 图像生成模型 - 经过实际网络请求验证 "jimeng-4.0": "high_aes_general_v40", // 最新4.0模型,支持creation_agent模式 "jimeng-3.1": "high_aes_general_v30l_art_fangzhou:general_v3.0_18b", "jimeng-3.0": "high_aes_general_v30l:general_v3.0_18b", // 支持creation_agent_v30模式 "jimeng-2.1": "high_aes_general_v21_L:general_v2.1_L", "jimeng-2.0-pro": "high_aes_general_v20_L:general_v2.0_L", "jimeng-2.0": "high_aes_general_v20:general_v2.0", "jimeng-1.4": "high_aes_general_v14:general_v1.4", "jimeng-xl-pro": "text2img_xl_sft", // 视频生成模型 "jimeng-video-3.0-pro": "dreamina_ic_generate_video_model_vgfm_3.0_pro", "jimeng-video-3.0": "dreamina_ic_generate_video_model_vgfm_3.0", "jimeng-video-2.0": "dreamina_ic_generate_video_model_vgfm_lite", "jimeng-video-2.0-pro": "dreamina_ic_generate_video_model_vgfm1.0", // 智能多帧视频模型 "jimeng-video-multiframe": "dreamina_ic_generate_video_model_vgfm_3.0" }; var DEFAULT_MODEL = "jimeng-4.0"; var DRAFT_VERSION = "3.0.2"; var DEFAULT_ASSISTANT_ID = "513695"; var WEB_ID = Math.random() * 1e18 + 7e18; var USER_ID = generateUuid().replace(/-/g, ""); var UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; var ASPECT_RATIO_PRESETS = [ // ratio_type: 1 - 1:1 正方形 { name: "auto", ratio: 1, displayName: "\u667A\u80FD", imageRatio: 1, width: 2048, height: 2048, resolutionType: "2k" }, { name: "1:1", ratio: 1, displayName: "1:1", imageRatio: 1, width: 2048, height: 2048, resolutionType: "2k" }, // ratio_type: 2 - 3:4 竖屏 { name: "3:4", ratio: 3 / 4, displayName: "3:4", imageRatio: 2, width: 1728, height: 2304, resolutionType: "2k" }, // ratio_type: 3 - 16:9 横屏 { name: "16:9", ratio: 16 / 9, displayName: "16:9", imageRatio: 3, width: 2560, height: 1440, resolutionType: "2k" }, // ratio_type: 4 - 4:3 传统横屏 { name: "4:3", ratio: 4 / 3, displayName: "4:3", imageRatio: 4, width: 2304, height: 1728, resolutionType: "2k" }, // ratio_type: 5 - 9:16 手机竖屏 { name: "9:16", ratio: 9 / 16, displayName: "9:16", imageRatio: 5, width: 1440, height: 2560, resolutionType: "2k" }, // ratio_type: 6 - 2:3 书籍比例 { name: "2:3", ratio: 2 / 3, displayName: "2:3", imageRatio: 6, width: 1664, height: 2496, resolutionType: "2k" }, // ratio_type: 7 - 3:2 摄影比例 { name: "3:2", ratio: 3 / 2, displayName: "3:2", imageRatio: 7, width: 2496, height: 1664, resolutionType: "2k" }, // ratio_type: 8 - 21:9 超宽屏 { name: "21:9", ratio: 21 / 9, displayName: "21:9", imageRatio: 8, width: 3024, height: 1296, resolutionType: "2k" } ]; function getModel(model) { const mappedModel = MODEL_MAP[model]; if (!mappedModel) { logger.debug(`\u672A\u77E5\u6A21\u578B: ${model}\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u6A21\u578B: ${DEFAULT_MODEL}`); return MODEL_MAP[DEFAULT_MODEL]; } return mappedModel; } // src/utils/dimensions.ts var ImageDimensionCalculator = class { static calculateDimensions(aspectRatio, width, height) { if (width && height) { return { width, height, resolutionType: this.getResolutionType(width, height) }; } const preset = ASPECT_RATIO_PRESETS.find((p) => p.name === aspectRatio); if (!preset) { const defaultPreset = ASPECT_RATIO_PRESETS.find((p) => p.name === "1:1"); return { width: defaultPreset.width, height: defaultPreset.height, resolutionType: defaultPreset.resolutionType }; } return { width: preset.width, height: preset.height, resolutionType: preset.resolutionType }; } static getResolutionType(width, height) { return "2k"; } static getAspectRatioPreset(name) { return ASPECT_RATIO_PRESETS.find((preset) => preset.name === name); } static getAspectRatioByName(ratioName) { const preset = this.getAspectRatioPreset(ratioName); return preset ? preset.imageRatio : 1; } }; // src/utils/auth.ts function generateCookie(refreshToken) { const sessData = `sessionid=${refreshToken}; sessionid_ss=${refreshToken}; sid_tt=${refreshToken}; sid_guard=${refreshToken}%7C1703836801%7C5183999%7CSat%2C%2027-Jan-2024%2019%3A00%3A00%2BGMT; install_id=4074746043159691; ttreq=1$55b6aae6e1e6dd7b4b4c47ad31dc4d8b0b5d09ef`; const baseCookies = [ `passport_csrf_token=d103234c7bb2f1d6e94ee9abbc84f750`, `passport_csrf_token_default=d103234c7bb2f1d6e94ee9abbc84f750`, `is_staff_user=false`, `n_mh=KY1c93FEY4V91lp9CwdHvKGbMz87QH7gwbpJrqawy8Q`, `uid_tt=4d6536b62de9d2e51ff4bde1381be24a`, `uid_tt_ss=4d6536b62de9d2e51ff4bde1381be24a`, `sid_ucp_v1=1.0.0-KDRmNTFlNzIzNDA5MGY3YjRhZDg1ZTlmYmU5MmMzMzM2N2Q2ODI0ODAKHwjZicD3jczFBxCpvY7GBhifrR8gDDDZ37ewBjgIQCYaAmxxIiAxNjVmZTUwNjQxMWI5NWQ3NzFlNjE5YjdkNTA5YmIyOA`, `ssid_ucp_v1=1.0.0-KDRmNTFlNzIzNDA5MGY3YjRhZDg1ZTlmYmU5MmMzMzM2N2Q2ODI0ODAKHwjZicD3jczFBxCpvY7GBhifrR8gDDDZ37ewBjgIQCYaAmxxIiAxNjVmZTUwNjQxMWI5NWQ3NzFlNjE5YjdkNTA5YmIyOA`, sessData ]; return baseCookies.join("; "); } // src/api/HttpClient.ts import axios from "axios"; // src/utils/a_bogus.ts function rc4_encrypt(plaintext, key) { const s = []; for (let i2 = 0; i2 < 256; i2++) { s[i2] = i2; } let j = 0; for (let i2 = 0; i2 < 256; i2++) { j = (j + s[i2] + key.charCodeAt(i2 % key.length)) % 256; [s[i2], s[j]] = [s[j], s[i2]]; } let i = 0; j = 0; const cipher = []; for (let k = 0; k < plaintext.length; k++) { i = (i + 1) % 256; j = (j + s[i]) % 256; [s[i], s[j]] = [s[j], s[i]]; const t = (s[i] + s[j]) % 256; cipher.push(String.fromCharCode(s[t] ^ plaintext.charCodeAt(k))); } return cipher.join(""); } function le(e, r) { r %= 32; return (e << r | e >>> 32 - r) >>> 0; } function de(e) { if (0 <= e && e < 16) return 2043430169; if (16 <= e && e < 64) return 2055708042; console.error("invalid j for constant Tj"); return void 0; } function pe(e, r, t, n) { if (0 <= e && e < 16) return (r ^ t ^ n) >>> 0; if (16 <= e && e < 64) return (r & t | r & n | t & n) >>> 0; console.error("invalid j for bool function FF"); return 0; } function he(e, r, t, n) { if (0 <= e && e < 16) return (r ^ t ^ n) >>> 0; if (16 <= e && e < 64) return (r & t | ~r & n) >>> 0; console.error("invalid j for bool function GG"); return 0; } function reset() { this.reg[0] = 1937774191; this.reg[1] = 1226093241; this.reg[2] = 388252375; this.reg[3] = 3666478592; this.reg[4] = 2842636476; this.reg[5] = 372324522; this.reg[6] = 3817729613; this.reg[7] = 2969243214; this.chunk = []; this.size = 0; } function write(e) { const a = typeof e === "string" ? (() => { let n = encodeURIComponent(e).replace(/%([0-9A-F]{2})/g, (_, r) => String.fromCharCode(parseInt(r, 16))); const arr = new Array(n.length); Array.prototype.forEach.call(n, (ch, idx) => { arr[idx] = ch.charCodeAt(0); }); return arr; })() : e; this.size += a.length; let f = 64 - this.chunk.length; if (a.length < f) { this.chunk = this.chunk.concat(a); } else { this.chunk = this.chunk.concat(a.slice(0, f)); while (this.chunk.length >= 64) { this._compress(this.chunk); if (f < a.length) { this.chunk = a.slice(f, Math.min(f + 64, a.length)); } else { this.chunk = []; } f += 64; } } } function se(str, len, pad) { return pad.repeat(len - str.length) + str; } function sum(e, t) { if (e) { this.reset(); this.write(e); } this._fill(); for (let f = 0; f < this.chunk.length; f += 64) { this._compress(this.chunk.slice(f, f + 64)); } let i = null; if (t === "hex") { i = ""; for (let f = 0; f < 8; f++) { i += se(this.reg[f].toString(16), 8, "0"); } } else { i = new Array(32); for (let f = 0; f < 8; f++) { let c = this.reg[f]; i[4 * f + 3] = (255 & c) >>> 0; c >>>= 8; i[4 * f + 2] = (255 & c) >>> 0; c >>>= 8; i[4 * f + 1] = (255 & c) >>> 0; c >>>= 8; i[4 * f] = (255 & c) >>> 0; } } this.reset(); return i; } function _compress(t) { if (t.length < 64) { console.error("compress error: not enough data"); } else { const f = (() => { const r = new Array(132); for (let idx = 0; idx < 16; idx++) { r[idx] = idx * 4 < t.length ? t[idx * 4] << 24 : 0; r[idx] |= idx * 4 + 1 < t.length ? t[idx * 4 + 1] << 16 : 0; r[idx] |= idx * 4 + 2 < t.length ? t[idx * 4 + 2] << 8 : 0; r[idx] |= idx * 4 + 3 < t.length ? t[idx * 4 + 3] : 0; r[idx] >>>= 0; } for (let n = 16; n < 68; n++) { let a = r[n - 16] ^ r[n - 9] ^ le(r[n - 3], 15); a = a ^ le(a, 15) ^ le(a, 23); r[n] = (a ^ le(r[n - 13], 7) ^ r[n - 6]) >>> 0; } for (let n = 0; n < 64; n++) { r[n + 68] = (r[n] ^ r[n + 4]) >>> 0; } return r; })(); const i = this.reg.slice(0); for (let c = 0; c < 64; c++) { let o = le(i[0], 12) + i[4] + le(de(c), c); let s = ((o = le((4294967295 & o) >>> 0, 7)) ^ le(i[0], 12)) >>> 0; let u = pe(c, i[0], i[1], i[2]); u = (4294967295 & u + i[3] + s + f[c + 68]) >>> 0; let b = he(c, i[4], i[5], i[6]); b = (4294967295 & b + i[7] + o + f[c]) >>> 0; i[3] = i[2]; i[2] = le(i[1], 9); i[1] = i[0]; i[0] = u; i[7] = i[6]; i[6] = le(i[5], 19); i[5] = i[4]; i[4] = (b ^ le(b, 9) ^ le(b, 17)) >>> 0; } for (let l = 0; l < 8; l++) { this.reg[l] = (this.reg[l] ^ i[l]) >>> 0; } } } function _fill() { const a = 8 * this.size; let f = this.chunk.push(128) % 64; if (64 - f < 8) f -= 64; while (f < 56) { this.chunk.push(0); f++; } for (let i = 0; i < 4; i++) { const c = Math.floor(a / 4294967296); this.chunk.push(c >>> 8 * (3 - i) & 255); } for (let i = 0; i < 4; i++) { this.chunk.push(a >>> 8 * (3 - i) & 255); } } var SM3 = class { constructor() { this.reset = reset; this.write = write; this.sum = sum; this._compress = _compress; this._fill = _fill; this.reg = []; this.chunk = []; this.size = 0; this.reset(); } }; var s_obj = { s0: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", s1: "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", s2: "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", s3: "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe", s4: "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe" }; function result_encrypt(long_str, num = null) { const constant = { "0": 16515072, "1": 258048, "2": 4032, "str": num ? s_obj[num] : s_obj["s0"] }; let result = ""; let lound = 0; let long_int = get_long_int(lound, long_str); for (let i = 0; i < Math.floor(long_str.length / 3 * 4); i++) { if (Math.floor(i / 4) !== lound) { lound += 1; long_int = get_long_int(lound, long_str); } let key = i % 4; let temp_int; switch (key) { case 0: temp_int = (long_int & constant["0"]) >> 18; result += constant["str"].charAt(temp_int); break; case 1: temp_int = (long_int & constant["1"]) >> 12; result += constant["str"].charAt(temp_int); break; case 2: temp_int = (long_int & constant["2"]) >> 6; result += constant["str"].charAt(temp_int); break; case 3: temp_int = long_int & 63; result += constant["str"].charAt(temp_int); break; default: break; } } return result; } function get_long_int(round, long_str) { round = round * 3; return long_str.charCodeAt(round) << 16 | long_str.charCodeAt(round + 1) << 8 | long_str.charCodeAt(round + 2); } function gener_random(random, option) { return [ random & 255 & 170 | option[0] & 85, // 163 random & 255 & 85 | option[0] & 170, //87 random >> 8 & 255 & 170 | option[1] & 85, //37 random >> 8 & 255 & 85 | option[1] & 170 //41 ]; } function generate_rc4_bb_str(url_search_params, user_agent, window_env_str, suffix = "cus", Arguments = [0, 1, 14]) { const sm3 = new SM3(); const start_time = Date.now(); const url_search_params_list = sm3.sum(sm3.sum(url_search_params + suffix)); const cus = sm3.sum(sm3.sum(suffix)); const ua = sm3.sum(result_encrypt(rc4_encrypt(user_agent, String.fromCharCode(390625e-8, 1, 14)), "s3")); const end_time = Date.now(); const b = { 8: 3, 10: end_time, 15: { "aid": 6383, "pageId": 6241, "boe": false, "ddrt": 7, "paths": { "include": [{}, {}, {}, {}, {}, {}, {}], "exclude": [] }, "track": { "mode": 0, "delay": 300, "paths": [] }, "dump": true, "rpU": "" }, 16: start_time, 18: 44, 19: [1, 0, 1, 5] }; b[20] = b[16] >> 24 & 255; b[21] = b[16] >> 16 & 255; b[22] = b[16] >> 8 & 255; b[23] = b[16] & 255; b[24] = b[16] / 256 / 256 / 256 / 256 >> 0; b[25] = b[16] / 256 / 256 / 256 / 256 / 256 >> 0; b[26] = Arguments[0] >> 24 & 255; b[27] = Arguments[0] >> 16 & 255; b[28] = Arguments[0] >> 8 & 255; b[29] = Arguments[0] & 255; b[30] = Arguments[1] / 256 & 255; b[31] = Arguments[1] % 256 & 255; b[32] = Arguments[1] >> 24 & 255; b[33] = Arguments[1] >> 16 & 255; b[34] = Arguments[2] >> 24 & 255; b[35] = Arguments[2] >> 16 & 255; b[36] = Arguments[2] >> 8 & 255; b[37] = Arguments[2] & 255; b[38] = url_search_params_list[21]; b[39] = url_search_params_list[22]; b[40] = cus[21]; b[41] = cus[22]; b[42] = ua[23]; b[43] = ua[24]; b[44] = b[10] >> 24 & 255; b[45] = b[10] >> 16 & 255; b[46] = b[10] >> 8 & 255; b[47] = b[10] & 255; b[48] = b[8]; b[49] = b[10] / 256 / 256 / 256 / 256 >> 0; b[50] = b[10] / 256 / 256 / 256 / 256 / 256 >> 0; b[51] = b[15]["pageId"]; b[52] = b[15]["pageId"] >> 24 & 255; b[53] = b[15]["pageId"] >> 16 & 255; b[54] = b[15]["pageId"] >> 8 & 255; b[55] = b[15]["pageId"] & 255; b[56] = b[15]["aid"]; b[57] = b[15]["aid"] & 255; b[58] = b[15]["aid"] >> 8 & 255; b[59] = b[15]["aid"] >> 16 & 255; b[60] = b[15]["aid"] >> 24 & 255; const window_env_list = []; for (let index = 0; index < window_env_str.length; index++) { window_env_list.push(window_env_str.charCodeAt(index)); } b[64] = window_env_list.length; b[65] = b[64] & 255; b[66] = b[64] >> 8 & 255; b[69] = [].length; b[70] = b[69] & 255; b[71] = b[69] >> 8 & 255; b[72] = b[18] ^ b[20] ^ b[26] ^ b[30] ^ b[38] ^ b[40] ^ b[42] ^ b[21] ^ b[27] ^ b[31] ^ b[35] ^ b[39] ^ b[41] ^ b[43] ^ b[22] ^ b[28] ^ b[32] ^ b[36] ^ b[23] ^ b[29] ^ b[33] ^ b[37] ^ b[44] ^ b[45] ^ b[46] ^ b[47] ^ b[48] ^ b[49] ^ b[50] ^ b[24] ^ b[25] ^ b[52] ^ b[53] ^ b[54] ^ b[55] ^ b[57] ^ b[58] ^ b[59] ^ b[60] ^ b[65] ^ b[66] ^ b[70] ^ b[71]; let bb = [ b[18], b[20], b[52], b[26], b[30], b[34], b[58], b[38], b[40], b[53], b[42], b[21], b[27], b[54], b[55], b[31], b[35], b[57], b[39], b[41], b[43], b[22], b[28], b[32], b[60], b[36], b[23], b[29], b[33], b[37], b[44], b[45], b[59], b[46], b[47], b[48], b[49], b[50], b[24], b[25], b[65], b[66], b[70], b[71] ]; bb = bb.concat(window_env_list).concat(b[72]); return rc4_encrypt(String.fromCharCode(...bb), String.fromCharCode(121)); } function generate_random_str() { let random_str_list = []; random_str_list = random_str_list.concat(gener_random(Math.random() * 1e4, [3, 45])); random_str_list = random_str_list.concat(gener_random(Math.random() * 1e4, [1, 0])); random_str_list = random_str_list.concat(gener_random(Math.random() * 1e4, [1, 5])); return String.fromCharCode(...random_str_list); } function generate_a_bogus(url_search_params, user_agent) { const result_str = generate_random_str() + generate_rc4_bb_str( url_search_params, user_agent, "1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32" ); return result_encrypt(result_str, "s4") + "="; } // src/api/HttpClient.ts import crypto from "crypto"; var HttpClient = class { constructor(token) { this.refreshToken = token || process.env.JIMENG_API_TOKEN || ""; if (!this.refreshToken) { throw new Error("JIMENG_API_TOKEN \u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E"); } } /** * 执行HTTP请求 */ async request(options) { const { url, method = "POST", data = {}, headers = {}, params = {}, timeout = 12e4 // 增加超时时间到120秒 } = options; const baseUrl = "https://jimeng.jianying.com"; const fullUrl = url.includes("https://") ? url : `${baseUrl}${url}`; const FAKE_HEADERS = { Accept: "application/json, text/plain, */*", "Accept-Encoding": "gzip, deflate, br, zstd", "Accept-language": "zh-CN,zh;q=0.9", "Cache-control": "no-cache", "Content-Type": "application/json", // 🔥 添加Content-Type "Last-event-id": "undefined", Appid: DEFAULT_ASSISTANT_ID, Appvr: "5.8.0", Origin: "https://jimeng.jianying.com", Pragma: "no-cache", Priority: "u=1, i", Referer: "https://jimeng.jianying.com", Pf: "7", "Sec-Ch-Ua": '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": '"Windows"', "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-origin", "User-Agent": UA }; const requestHeaders = { ...FAKE_HEADERS, "Cookie": generateCookie(this.refreshToken), ...headers }; logger.debug(`[HttpClient] Request: ${method} ${fullUrl}`); try { const response = await axios({ method: method.toLowerCase(), url: fullUrl, data: method.toUpperCase() !== "GET" ? data : void 0, params: method.toUpperCase() === "GET" ? { ...data, ...params } : params, headers: requestHeaders, timeout }); return response.data; } catch (error) { return this.handleError(error); } } /** * 生成请求认证参数(用于图片上传等) */ generateRequestParams() { const rqParams = { "aid": parseInt("513695"), "device_platform": "web", "region": "cn", "webId": "7398608394939885067", "da_version": "3.3.2", "web_component_open_flag": 1, "web_version": "6.6.0", "aigc_features": "app_lip_sync", "msToken": generateMsToken() }; rqParams["a_bogus"] = generate_a_bogus( toUrlParams(rqParams), "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" ); return rqParams; } /** * 生成认证头和签名(用于ImageX API) */ async generateAuthorizationAndHeader(accessKeyId, secretAccessKey, sessionToken, region, service, method, params, data) { const now = /* @__PURE__ */ new Date(); const timestamp = now.toISOString().replace(/[:\-]|\.\d{3}/g, "").slice(0, 15) + "Z"; const uri = "/"; const bodyHash = data && Object.keys(data).length > 0 ? crypto.createHash("sha256").update(JSON.stringify(data)).digest("hex") : crypto.createHash("sha256").update("").digest("hex"); const authorization = await this.generateAuthorization( accessKeyId, secretAccessKey, sessionToken, region, service, method, uri, params, data, timestamp ); return { "X-Amz-Date": timestamp, "X-Amz-Security-Token": sessionToken, "X-Amz-Content-Sha256": bodyHash, "Authorization": authorization }; } /** * 生成Authorization签名(完整AWS4-HMAC-SHA256算法) */ async generateAuthorization(accessKeyId, secretAccessKey, sessionToken, region, service, method, uri, params, data, timestamp) { const amzDate = timestamp || (/* @__PURE__ */ new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, "").slice(0, 15) + "Z"; const amzDay = amzDate.substring(0, 8); const requestHeaders = { "x-amz-date": amzDate, "x-amz-security-token": sessionToken }; if (data && Object.keys(data).length > 0) { requestHeaders["x-amz-content-sha256"] = crypto.createHash("sha256").update(JSON.stringify(data)).digest("hex"); } const canonicalQueryString = params ? this.httpBuildQuery(params) : ""; const canonicalHeaders = this.buildCanonicalHeaders(requestHeaders); const signedHeaders = this.buildSignedHeaders(requestHeaders); const bodyHash = data && Object.keys(data).length > 0 ? crypto.createHash("sha256").update(JSON.stringify(data)).digest("hex") : crypto.createHash("sha256").update("").digest("hex"); const canonicalRequest = [ method.toUpperCase(), uri, canonicalQueryString, canonicalHeaders, signedHeaders, bodyHash ].join("\n"); const credentialScope = `${amzDay}/${region}/${service}/aws4_request`; const stringToSign = [ "AWS4-HMAC-SHA256", amzDate, credentialScope, crypto.createHash("sha256").update(canonicalRequest).digest("hex") ].join("\n"); const kDate = crypto.createHmac("sha256", "AWS4" + secretAccessKey).update(amzDay).digest(); const kRegion = crypto.createHmac("sha256", kDate).update(region).digest(); const kService = crypto.createHmac("sha256", kRegion).update(service).digest(); const signingKey = crypto.createHmac("sha256", kService).update("aws4_request").digest(); const signature = crypto.createHmac("sha256", signingKey).update(stringToSign).digest("hex"); const authorizationParams = [ "AWS4-HMAC-SHA256 Credential=" + accessKeyId + "/" + credentialScope, "SignedHeaders=" + signedHeaders, "Signature=" + signature ]; return authorizationParams.join(", "); } /** * 构建规范化的请求头字符串 */ buildCanonicalHeaders(headers) { const headerKeys = Object.keys(headers).sort(); const canonicalHeaders = []; for (const key of headerKeys) { canonicalHeaders.push(key.toLowerCase() + ":" + headers[key]); } return canonicalHeaders.join("\n") + "\n"; } /** * 构建已签名的请求头列表 */ buildSignedHeaders(headers) { const headerKeys = Object.keys(headers).map((k) => k.toLowerCase()).sort(); return headerKeys.join(";"); } /** * HTTP查询字符串构建 */ httpBuildQuery(params) { return Object.keys(params).sort().map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`).join("&"); } /** * 生成随机字符串 */ generateRandomString(length) { const characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; let result = ""; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * characters.length)); } return result; } /** * 获取refresh token */ getRefreshToken() { return this.refreshToken; } /** * 统一错误处理 */ handleError(error) { if (error.response) { throw new Error(`\u5373\u68A6API\u8BF7\u6C42\u9519\u8BEF: ${JSON.stringify(error.response.data)}`); } else if (error.request) { throw new Error(`[FINAL-DEBUG] HttpClient.handleError: Caught error with no response.`); } else { throw new Error(`\u5373\u68A6API\u8BF7\u6C42\u5931\u8D25: ${error.message}`); } } }; // src/api/ImageUploader.ts import sizeOf from "image-size"; import axios2 from "axios"; import fs from "fs"; import path from "path"; import crc32 from "crc32"; // src/utils/retry.ts var DEFAULT_RETRY_OPTIONS = { maxRetries: 3, baseDelay: 1e3, maxDelay: 1e4, shouldRetry: isRetryableError }; function isRetryableError(error) { if (error.code === "ETIMEDOUT" || error.code === "ECONNRESET" || error.code === "ECONNABORTED") { return true; } if (error.response) { const status = error.response.status; if (status >= 500 && status < 600) { return true; } if (status === 429) { return true; } if (status >= 400 && status < 500) { return false; } } if (error.request && !error.response) { return true; } return false; } function calculateBackoff(attempt, baseDelay, maxDelay) { const exponentialDelay = baseDelay * Math.pow(2, attempt - 1); const jitter = Math.random() * 200; return Math.min(exponentialDelay + jitter, maxDelay); } function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function retryAsync(fn, options = {}, context = "\u64CD\u4F5C") { const config = { ...DEFAULT_RETRY_OPTIONS, ...options }; const { maxRetries, baseDelay, maxDelay, shouldRetry } = config; let lastError; for (let attempt = 1; attempt <= maxRetries + 1; attempt++) { try { const result = await fn(); if (attempt > 1) { } return result; } catch (error) { lastError = error; const canRetry = shouldRetry ? shouldRetry(error) : isRetryableError(error); if (attempt > maxRetries) { logger.debug(`[FATAL] [RETRY-FATAL] ${context}\u91CD\u8BD5\u5931\u8D25, \u5DF2\u8FBE\u6700\u5927\u91CD\u8BD5\u6B21\u6570=${maxRetries}, \u9519\u8BEF=${error}`); throw error; } if (!canRetry) { logger.debug(`[FATAL] [RETRY-SKIP] ${context}\u9047\u5230\u4E0D\u53EF\u91CD\u8BD5\u9519\u8BEF, \u7ACB\u5373\u5931\u8D25, \u9519\u8BEF=${error}`); throw error; } const backoff = calculateBackoff(attempt, baseDelay, maxDelay); const err = error; logger.debug( `[RETRY] [RETRY-ATTEMPT] ${context}\u91CD\u8BD5 ${attempt}/${maxRetries}, \u7B49\u5F85${Math.round(backoff)}ms, \u9519\u8BEF=${err.message || err}` ); await sleep(backoff); } } throw lastError; } // src/api/ImageUploader.ts var ImageUploader = class { constructor(httpClient) { this.httpClient = httpClient; } /** * 上传单张图片 */ async upload(imagePath) { var _a2, _b; let uploadAuth; try { uploadAuth = await this.getUploadAuth(); } catch (error) { throw new Error(`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 [${imagePath}] at \u6B65\u9AA41 (\u83B7\u53D6\u4E0A\u4F20\u51ED\u8BC1): ${error}`); } const imageBuffer = await this.getFileContent(imagePath); const metadata = this.detectFormat(imageBuffer); const imageCrc32 = crc32(imageBuffer).toString(16); let uploadImgRes; try { const getUploadImageProofRequestParams = { Action: "ApplyImageUpload", FileSize: imageBuffer.length, ServiceId: "tb4s082cfz", Version: "2018-08-01", s: this.httpClient.generateRandomString(11) }; const requestHeadersInfo = await this.httpClient.generateAuthorizationAndHeader( uploadAuth.access_key_id, uploadAuth.secret_access_key, uploadAuth.session_token, "cn-north-1", "imagex", "GET", getUploadImageProofRequestParams ); const getUploadImageProofUrl = "https://imagex.bytedanceapi.com/"; uploadImgRes = await this.httpClient.request({ method: "GET", url: getUploadImageProofUrl + "?" + this.httpClient.httpBuildQuery(getUploadImageProofRequestParams), headers: requestHeadersInfo }); if ((_a2 = uploadImgRes == null ? void 0 : uploadImgRes["Response"]) == null ? void 0 : _a2.hasOwnProperty("Error")) { throw new Error(uploadImgRes["Response"]["Error"]["Message"]); } } catch (error) { throw new Error(`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 [${imagePath}] at \u6B65\u9AA42 (\u83B7\u53D6\u56FE\u7247\u4E0A\u4F20\u51ED\u8BC1): ${error}`); } const UploadAddress = uploadImgRes.Result.UploadAddress; try { const uploadImgUrl = `https://${UploadAddress.UploadHosts[0]}/upload/v1/${UploadAddress.StoreInfos[0].StoreUri}`; await this.uploadImageDataWithRetry( uploadImgUrl, imageBuffer, imageCrc32, UploadAddress.StoreInfos[0].Auth ); } catch (error) { throw new Error(`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 [${imagePath}] at \u6B65\u9AA43 (\u4E0A\u4F20\u56FE\u7247\u6570\u636E): ${error}`); } let commitRes; try { const commitImgParams = { Action: "CommitImageUpload", FileSize: imageBuffer.length, ServiceId: "tb4s082cfz", Version: "2018-08-01" }; const commitImgContent = { SessionKey: UploadAddress.SessionKey }; const commitImgHead = await this.httpClient.generateAuthorizationAndHeader( uploadAuth.access_key_id, uploadAuth.secret_access_key, uploadAuth.session_token, "cn-north-1", "imagex", "POST", commitImgParams, commitImgContent ); const commitImgUrl = "https://imagex.bytedanceapi.com/"; commitRes = await this.httpClient.request({ method: "POST", url: commitImgUrl + "?" + this.httpClient.httpBuildQuery(commitImgParams), data: commitImgContent, headers: commitImgHead }); if ((_b = commitRes == null ? void 0 : commitRes["Response"]) == null ? void 0 : _b.hasOwnProperty("Error")) { throw new Error(commitRes["Response"]["Error"]["Message"]); } } catch (error) { throw new Error(`\u56FE\u7247\u4E0A\u4F20\u5931\u8D25 [${imagePath}] at \u6B65\u9AA44 (\u63D0\u4EA4\u4E0A\u4F20): ${error}`); } const uri = commitRes.Result.PluginResult[0].ImageUri; return { uri, originalPath: imagePath, width: metadata.width, height: metadata.height, format: metadata.format }; } /** * 批量上传图片(并行处理) */ async uploadBatch(imagePaths) { return Promise.all(imagePaths.map((path2) => this.upload(path2))); } /** * 检测图片格式和尺寸(使用image-size库,替代132行手动解析) */ detectFormat(pathOrBuffer) { try { const dimensions = sizeOf(pathOrBuffer); if (!dimensions.width || !dimensions.height || !dimensions.type) { throw new Error("\u65E0\u6CD5\u89E3\u6790\u56FE\u7247\u5C3A\u5BF8"); } return { format: dimensions.type, width: dimensions.width, height: dimensions.height }; } catch (error) { logger.debug(`\u68C0\u6D4B\u56FE\u7247\u683C\u5F0F\u5931\u8D25: ${error}`); return { width: 0, height: 0, format: "png" }; } } /** * 读取文件内容(支持本地文件和HTTP URL) */ async getFileContent(filePath) { try { if (filePath.includes("https://") || filePath.includes("http://")) { const res = await axios2.get(filePath, { responseType: "arraybuffer" }); return Buffer.from(res.data); } else { const absolutePath = path.resolve(filePath); if (!fs.existsSync(absolutePath)) { throw new Error(`\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}`); } const stats = await fs.promises.stat(absolutePath); if (!stats.isFile()) { throw new Error(`\u8DEF\u5F84\u4E0D\u662F\u6587\u4EF6: ${absolutePath}`); } return await fs.promises.readFile(absolutePath); } } catch (error) { const errorMsg = error.message || String(error); const errorCode = error.code || "UNKNOWN"; throw new Error(`\u8BFB\u53D6\u6587\u4EF6\u5931\u8D25 [${filePath}]: ${errorMsg} (\u9519\u8BEF\u4EE3\u7801: ${errorCode})`); } } /** * 上传图片数据(带重试机制) * 这是唯一需要重试的步骤,因为网络传输最容易失败 */ async uploadImageDataWithRetry(url, imageBuffer, crc32Hash, authToken) { return retryAsync( async () => { const response = await this.httpClient.request({ method: "POST", url, data: imageBuffer, headers: { Authorization: authToken, "Content-Crc32": crc32Hash, "Content-Type": "application/octet-stream" } }); if (response.code !== 2e3) { throw new Error(response.message || "\u56FE\u7247\u4E0A\u4F20\u5931\u8D25"); } return response; }, { maxRetries: 3, baseDelay: 1e3, maxDelay: 1e4 }, "\u56FE\u7247\u4E0A\u4F20" ); } /** * 获取上传凭证 */ async getUploadAuth() { try { logger.debug("[ImageUploader] \u5F00\u59CB\u83B7\u53D6\u4E0A\u4F20\u51ED\u8BC1..."); const authRes = await this.httpClient.request({ method: "POST", url: "/mweb/v1/get_upload_token?aid=513695&da_version=3.2.2&aigc_features=app_lip_sync", data: { scene: 2 }, timeout: 3e4 // 明确设置30秒超时 }); logger.debug(`[ImageUploader] \u4E0A\u4F20\u51ED\u8BC1\u54CD\u5E94: ${JSON.stringify(authRes).substring(0, 200)}`); if (!authRes.data) { throw new Error(authRes.errmsg ?? "\u83B7\u53D6\u4E0A\u4F20\u51ED\u8BC1\u5931\u8D25,\u8D26\u53F7\u53EF\u80FD\u5DF2\u6389\u7EBF!"); } logger.debug("[ImageUploader] \u4E0A\u4F20\u51ED\u8BC1\u83B7\u53D6\u6210\u529F"); return authRes.data; } catch (error) { logger.debug(`[ImageUploader] \u83B7\u53D6\u4E0A\u4F20\u51ED\u8BC1\u5931\u8D25: ${error.message}`); logger.debug(`[ImageUploader] \u9519\u8BEF\u8BE6\u60C5: ${JSON.stringify({ message: error.message, code: error.code, hasResponse: !!error.response, hasRequest: !!error.request })}`); throw error; } } }; // src/api/NewCreditService.ts var NewCreditService = class { constructor(httpClient) { this.httpClient = httpClient; } /** * 获取积分余额 */ async getBalance() { try { const credit = await this.getCredit(); return credit.totalCredit; } catch (error) { logger.debug(`\u67E5\u8BE2\u79EF\u5206\u5931\u8D25: ${error}`); return 0; } } /** * 获取详细积分信息 */ async getCredit() { const result = await this.httpClient.request({ method: "POST", url: "/commerce/v1/benefits/user_credit", data: {}, headers: { "Referer": "https://jimeng.jianying.com/ai-tool/image/generate" } }); const credit = result.credit || {}; const giftCredit = credit.gift_credit || 0; const purchaseCredit = credit.purchase_credit || 0; const vipCredit = credit.vip_credit || 0; return { giftCredit, purchaseCredit, vipCredit, totalCredit: giftCredit + purchaseCredit + vipCredit }; } /** * 领取积分 */ async receiveCredit() { try { const credit = await this.httpClient.request({ method: "POST", url: "/commerce/v1/benefits/credit_receive", data: { "time_zone": "Asia/Shanghai" }, headers: { "Referer": "https://jimeng.jianying.com/ai-tool/image/generate" } }); if ((credit == null ? void 0 : credit.ret) && credit.ret !== "0") { if (credit.ret === "1014" && credit.errmsg === "system busy") { return; } else { return; } } } catch (error) { } } /** * 检查是否有足够积分 */ async hasEnoughCredits(amount) { const balance = await this.getBalance(); return balance >= amount; } /** * 扣除积分(记录原因) */ async deductCredits(amount, reason) { const hasEnough = await this.hasEnoughCredits(amount); if (!hasEnough) { const balance = await this.getBalance(); throw new InsufficientCreditsError(amount, balance); } } }; var InsufficientCreditsError = class extends Error { constructor(required, available) { super(`\u9700\u8981${required}\u79EF\u5206\uFF0C\u5F53\u524D${available}`); this.name = "InsufficientCreditsError"; } }; // src/api/VideoService.ts var VideoService = class { constructor(httpClient, imageUploader) { this.httpClient = httpClient; this.imageUploader = imageUploader; } // ==================== 公共方法:三种视频生成模式 ==================== /** * 文本生成视频(支持首尾帧) */ async generateTextToVideo(params) { const { prompt, firstFrameImage, lastFrameImage, async: asyncMode = false, resolution = "720p", videoAspectRatio = "16:9", fps = 24, duration = 5e3, model = "jimeng-video-3.0" } = params; if (duration < 3e3 || duration > 15e3) { throw new Error("duration\u5FC5\u987B\u57283000-15000\u6BEB\u79D2\u4E4B\u95F4"); } const actualModel = getModel(model); let first_frame_image = void 0; let end_frame_image = void 0; if (firstFrameImage || lastFrameImage) { const uploadResults = []; if (firstFrameImage) { const result = await this.imageUploader.upload(firstFrameImage); uploadResults.push(result); } if (lastFrameImage) { const result = await this.imageUploader.upload(lastFrameImage); if (!firstFrameImage) uploadResults.unshift(null); uploadResults.push(result); } if (uploadResults[0]) { first_frame_image = { format: uploadResults[0].format, height: uploadResults[0].height, id: this.generateUuid(), image_uri: uploadResults[0].uri, name: "", platform_type: 1, source_from: "upload", type: "image", uri: uploadResults[0].uri, width: uploadResults[0].width }; } if (uploadResults[1]) { end_frame_image = { format: uploadResults[1].format, height: uploadResults[1].height, id: this.generateUuid(), image_uri: uploadResults[1].uri, name: "", platform_type: 1, source_from: "upload", type: "image", uri: uploadResults[1].uri, width: uploadResults[1].width }; } } const componentId = this.generateUuid(); const submitId = this.generateUuid(); const metricsExtra = JSON.stringify({ "enterFrom": "click", "isDefaultSeed": 1, "promptSource": "custom", "isRegenerate": false, "originSubmitId": this.generateUuid() }); const requestBody = { "extend": { "root_model": end_frame_image ? "dreamina_ic_generate_video_model_vgfm_3.0" : actualModel, "m_video_commerce_info": { benefit_type: "basic_video_operation_vgfm_v_three", resource_id: "generate_video", resource_id_type: "str", resource_sub_type: "aigc" }, "m_video_commerce_info_list": [{ benefit_type: "basic_video_operation_vgfm_v_three", resource_id: "generate_video", resource_id_type: "str", resource_sub_type: "aigc" }] }, "submit_id": submitId, "metrics_extra": metricsExtra, "draft_content": JSON.stringify({ "type": "draft", "id": this.generateUuid(), "min_version": "3.0.5", "is_from_tsn": true, "version": "3.3.2", "main_component_id": componentId, "component_list": [{ "type": "video_base_component", "id": componentId, "min_version": "1.0.0", "metadata": { "type": "", "id": this.generateUuid(), "created_platform": 3, "created_platform_version": "", "created_time_in_ms": Date.now(), "created_did": "" }, "generate_type": "gen_video", "aigc_mode": "workbench", "abilities": { "type": "", "id": this.generateUuid(), "gen_video": { "id": this.generateUuid(), "type": "", "text_to_video_params": { "type": "", "id": this.generateUuid(), "model_req_key": actualModel, "priority": 0, "seed": Math.floor(Math.random() * 1e8) + 25e8, "video_aspect_ratio": videoAspectRatio, "video_gen_inputs": [{ duration_ms: duration, first_frame_image, end_frame_image, fps, id: this.generateUuid(), min_version: "3.0.5", prompt, resolution, type: "", video_mode: 2 }] }, "video_task_extra": metricsExtra } } }] }) }; const taskId = await this.submitTaskWithDraft(requestBody); if (asyncMode) { return { taskId, metadata: { model, resolution, duration, fps } }; } const videoUrl = await this.pollUntilComplete(taskId); return { videoUrl, metadata: { model, resolution, duration, fps } }; } /** * 生成UUID(本地方法) */ generateUuid() { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === "x" ? r : r & 3 | 8; return v.toString(16); }); } /** * 多帧视频生成(2-10帧) */ async generateMultiFrame(params) { const { frames, async: asyncMode = false, resolution = "720p", fps = 24, model = "jimeng-video-3.0", videoAspectRatio = "16:9" } = params; if (frames.length < 2 || frames.length > 10) { throw new Error