jimeng-web-mcp
Version:
MCP服务器项目,直接访问即梦AI Web端进行图像和视频生成(仅供学习研究使用)
1,618 lines (1,600 loc) • 128 kB
JavaScript
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