@iflyrpa/actions 4.1.0-beta.5 → 4.1.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.
package/dist/bundle.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*! For license information please see bundle.js.LICENSE.txt */
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1e50231c-4e0f-5db5-a554-c11a95d7391d")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="bf64112a-57bc-5eae-9ede-8d23fc55671f")}catch(e){}}();
4
4
  var __webpack_modules__ = {
5
5
  "../../node_modules/.pnpm/agent-base@7.1.4/node_modules/agent-base/dist/helpers.js": function(__unused_webpack_module, exports1, __webpack_require__) {
6
6
  "use strict";
@@ -12491,9 +12491,11 @@ var __webpack_exports__ = {};
12491
12491
  origin: ()=>utils_origin
12492
12492
  });
12493
12493
  var package_namespaceObject = {
12494
- i8: "0.1.2"
12494
+ i8: "0.2.0"
12495
+ };
12496
+ var package_namespaceObject_0 = {
12497
+ i8: "4.1.0"
12495
12498
  };
12496
- var package_namespaceObject_0 = JSON.parse('{"i8":"4.1.0-beta.5"}');
12497
12499
  const external_node_fs_namespaceObject = require("node:fs");
12498
12500
  var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
12499
12501
  const external_node_http_namespaceObject = require("node:http");
@@ -12584,7 +12586,7 @@ var __webpack_exports__ = {};
12584
12586
  fileStream.on("error", (err)=>{
12585
12587
  reject({
12586
12588
  code: 500,
12587
- message: `图片Buffer写入失败: ${err.message}`
12589
+ message: "图片写入失败,请检查磁盘空间"
12588
12590
  });
12589
12591
  });
12590
12592
  } else {
@@ -27073,6 +27075,11 @@ var __webpack_exports__ = {};
27073
27075
  const RPA_ERROR_WEBHOOK_URL = "https://open.xfchat.iflytek.com/open-apis/bot/v2/hook/d202c0dc-5af5-40bc-83ed-abc677caa4a5";
27074
27076
  const ALARM_THROTTLE_MS = 60000;
27075
27077
  const lastSentAt = new Map();
27078
+ const THROTTLE_MAP_MAX_KEYS = 500;
27079
+ const pruneThrottleMap = (now)=>{
27080
+ if (lastSentAt.size < THROTTLE_MAP_MAX_KEYS) return;
27081
+ for (const [key, at] of lastSentAt)if (now - at >= ALARM_THROTTLE_MS) lastSentAt.delete(key);
27082
+ };
27076
27083
  const postFeishuWebhook = (webhookUrl, payload)=>lib_axios.post(webhookUrl, payload, {
27077
27084
  headers: {
27078
27085
  "Content-Type": "application/json"
@@ -27150,10 +27157,11 @@ var __webpack_exports__ = {};
27150
27157
  };
27151
27158
  const reportFeishuAlarm = (report)=>{
27152
27159
  try {
27153
- const key = `${report.platform}|${report.source}|${report.errorType}|${report.code ?? ""}`;
27160
+ const key = `${report.platform}|${report.source}|${report.stage}|${report.errorType}|${report.code ?? ""}`;
27154
27161
  const now = Date.now();
27155
27162
  const last = lastSentAt.get(key);
27156
27163
  if (last && now - last < ALARM_THROTTLE_MS) return;
27164
+ pruneThrottleMap(now);
27157
27165
  lastSentAt.set(key, now);
27158
27166
  postFeishuWebhook(RPA_ERROR_WEBHOOK_URL, buildFeishuPostMessage(report)).catch(()=>{});
27159
27167
  } catch {}
@@ -27300,6 +27308,19 @@ var __webpack_exports__ = {};
27300
27308
  461,
27301
27309
  471
27302
27310
  ]);
27311
+ function isLocalAddress(url) {
27312
+ let hostname;
27313
+ try {
27314
+ hostname = new URL(url).hostname.toLowerCase();
27315
+ } catch {
27316
+ return false;
27317
+ }
27318
+ if ("localhost" === hostname || "::1" === hostname || "[::1]" === hostname) return true;
27319
+ if (hostname.endsWith(".localhost") || hostname.endsWith(".local")) return true;
27320
+ if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname)) return true;
27321
+ if ("0.0.0.0" === hostname) return true;
27322
+ return false;
27323
+ }
27303
27324
  const HTTP_STATUS_MESSAGE = {
27304
27325
  400: "请求参数错误,请检查参数格式是否正确!",
27305
27326
  401: "登录状态已失效,请重新登录后重试!",
@@ -27336,7 +27357,14 @@ var __webpack_exports__ = {};
27336
27357
  };
27337
27358
  class Http {
27338
27359
  static handleApiError(error) {
27339
- if (error && "object" == typeof error && "code" in error && "message" in error) return error;
27360
+ if (error && "object" == typeof error && "code" in error && "message" in error) {
27361
+ const resp = error;
27362
+ if ("string" != typeof resp.message || !resp.message.trim()) return {
27363
+ ...resp,
27364
+ message: USER_MESSAGE.SYSTEM_ERROR
27365
+ };
27366
+ return resp;
27367
+ }
27340
27368
  return {
27341
27369
  code: 500,
27342
27370
  message: USER_MESSAGE.SYSTEM_ERROR,
@@ -27474,25 +27502,47 @@ var __webpack_exports__ = {};
27474
27502
  errorResponse.message = message;
27475
27503
  }
27476
27504
  if (error.message.includes("Proxy connection ended")) errorResponse.message = "所在区域代理连接超时,请更换区域或稍后重试!";
27477
- const status = error.response?.status;
27478
- reportFeishuAlarm({
27479
- level: status && ALARM_STATUS.has(status) ? "alarm" : "warning",
27480
- platform: this.platform || "unknown",
27481
- source: "http",
27482
- stage: `${(error.config?.method || "get").toUpperCase()} ${error.config?.url || "-"}`,
27483
- errorType: status ? `HTTP_${status}` : error.code || "NETWORK_ERROR",
27484
- code: errorResponse.code,
27485
- msg: errorResponse.message,
27486
- url: error.config?.url,
27487
- title: "RPA接口异常"
27488
- });
27505
+ errorResponse.extra = {
27506
+ ...errorResponse.extra,
27507
+ alarmStatus: error.response?.status,
27508
+ alarmErrorCode: error.code
27509
+ };
27489
27510
  throw errorResponse;
27490
27511
  });
27491
27512
  }
27513
+ reportRequestFailure(config, error, attempts) {
27514
+ const status = error.extra?.alarmStatus;
27515
+ const axiosCode = error.extra?.alarmErrorCode;
27516
+ const method = (config.method || "get").toUpperCase();
27517
+ const baseURL = config.baseURL || this.apiClient.defaults.baseURL || "";
27518
+ const fullUrl = config.url ? /^https?:\/\//.test(config.url) ? config.url : `${baseURL.replace(/\/$/, "")}${config.url}` : baseURL;
27519
+ if (!fullUrl) {
27520
+ this.logger?.debug(`[告警跳过] 请求失败但无 URL,不上报: ${error.message}`);
27521
+ return;
27522
+ }
27523
+ if (isLocalAddress(fullUrl)) {
27524
+ this.logger?.debug(`[告警跳过] 本机地址不上报: ${fullUrl}`);
27525
+ return;
27526
+ }
27527
+ const rawMsg = "string" == typeof error.message && error.message.trim() ? error.message : "";
27528
+ const baseMsg = rawMsg || `请求失败(无错误文案,code=${error.code ?? "unknown"})`;
27529
+ const retriedSuffix = attempts > 0 ? `(已重试${attempts}次仍失败)` : "";
27530
+ reportFeishuAlarm({
27531
+ level: status && ALARM_STATUS.has(status) ? "alarm" : "warning",
27532
+ platform: "@iflyrpa/playwright",
27533
+ source: "http",
27534
+ stage: `${method} ${fullUrl}`,
27535
+ errorType: status ? `HTTP_${status}` : axiosCode || "NETWORK_ERROR",
27536
+ code: error.code,
27537
+ msg: `${baseMsg}${retriedSuffix}`,
27538
+ url: fullUrl,
27539
+ title: "RPA接口异常"
27540
+ });
27541
+ }
27492
27542
  async api(config, options) {
27493
27543
  const retries = options?.retries ?? 0;
27494
27544
  const retryDelay = options?.retryDelay ?? 500;
27495
- const reqTimeout = options?.timeout ?? 30000;
27545
+ const reqTimeout = options?.timeout ?? 60000;
27496
27546
  const externalSignal = options?.signal;
27497
27547
  let agent;
27498
27548
  const sessionRt = async (Rtimes)=>{
@@ -27533,10 +27583,12 @@ var __webpack_exports__ = {};
27533
27583
  ].includes(handledError.code);
27534
27584
  if (Rtimes < retries && isRetry) {
27535
27585
  const url = config.url || "";
27536
- this.logger?.warn(`进入第${Rtimes + 1}次重试!错误码: ${handledError.code}, 请求地址: ${url}`);
27537
- await new Promise((resolve)=>setTimeout(resolve, retryDelay));
27586
+ const backoff = Math.min(retryDelay * 2 ** Rtimes, 5000);
27587
+ this.logger?.warn(`进入第${Rtimes + 1}次重试!错误码: ${handledError.code}, 等待: ${backoff}ms, 请求地址: ${url}`);
27588
+ await new Promise((resolve)=>setTimeout(resolve, backoff));
27538
27589
  return sessionRt(Rtimes + 1);
27539
27590
  }
27591
+ this.reportRequestFailure(config, handledError, Rtimes);
27540
27592
  return Promise.reject(handledError);
27541
27593
  }
27542
27594
  };
@@ -32227,6 +32279,14 @@ var __webpack_exports__ = {};
32227
32279
  };
32228
32280
  return new schemas_ZodObject(def);
32229
32281
  }
32282
+ function looseObject(shape, params) {
32283
+ return new schemas_ZodObject({
32284
+ type: "object",
32285
+ shape,
32286
+ catchall: schemas_unknown(),
32287
+ ...util_normalizeParams(params)
32288
+ });
32289
+ }
32230
32290
  const schemas_ZodUnion = /*@__PURE__*/ core_$constructor("ZodUnion", (inst, def)=>{
32231
32291
  schemas_$ZodUnion.init(inst, def);
32232
32292
  schemas_ZodType.init(inst, def);
@@ -32919,7 +32979,7 @@ var __webpack_exports__ = {};
32919
32979
  ".png"
32920
32980
  ].includes(ext)) throw {
32921
32981
  code: 414,
32922
- message: `图片格式不支持:${fileName}。百家号仅支持 jpg、png 格式,请转换后重试。`,
32982
+ message: "图片格式不支持,百家号仅支持 jpg、png 格式,请转换后重试。",
32923
32983
  data: ""
32924
32984
  };
32925
32985
  const image = await downloadImage(url, external_node_path_default().join(tmpCachePath, fileName));
@@ -33600,7 +33660,8 @@ var __webpack_exports__ = {};
33600
33660
  },
33601
33661
  _task.logger,
33602
33662
  params.proxyLoc,
33603
- params.accountId
33663
+ params.accountId,
33664
+ "xiaohongshu"
33604
33665
  ];
33605
33666
  const http = new Http(...args);
33606
33667
  const fans = {
@@ -33614,8 +33675,8 @@ var __webpack_exports__ = {};
33614
33675
  url: "https://creator.xiaohongshu.com/api/galaxy/creator/home/personal_info"
33615
33676
  }, {
33616
33677
  retries: 3,
33617
- retryDelay: 20,
33618
- timeout: 3000
33678
+ retryDelay: 300,
33679
+ timeout: 30000
33619
33680
  });
33620
33681
  fans.fans_count = Number(res.data.fans_count);
33621
33682
  fans.digg_count = Number(res.data.faved_count);
@@ -34752,7 +34813,8 @@ var __webpack_exports__ = {};
34752
34813
  },
34753
34814
  _task.logger,
34754
34815
  params.proxyLoc,
34755
- params.accountId
34816
+ params.accountId,
34817
+ "xiaohongshu"
34756
34818
  ];
34757
34819
  const http = new Http(...args);
34758
34820
  http.addResponseInterceptor((response)=>{
@@ -34800,8 +34862,8 @@ var __webpack_exports__ = {};
34800
34862
  headers: loginBaseXsHeader
34801
34863
  }, {
34802
34864
  retries: 3,
34803
- retryDelay: 20,
34804
- timeout: 5000
34865
+ retryDelay: 300,
34866
+ timeout: 30000
34805
34867
  }).catch((e)=>{
34806
34868
  const clientTimestamp = Date.now();
34807
34869
  const serverDate = e?.extra?.serverDate;
@@ -34833,8 +34895,8 @@ var __webpack_exports__ = {};
34833
34895
  headers: webSessionXsHeader
34834
34896
  }, {
34835
34897
  retries: 3,
34836
- retryDelay: 20,
34837
- timeout: 5000
34898
+ retryDelay: 300,
34899
+ timeout: 30000
34838
34900
  });
34839
34901
  const [baseInfo, web_session] = await Promise.all([
34840
34902
  _baseInfo,
@@ -34926,6 +34988,10 @@ var __webpack_exports__ = {};
34926
34988
  };
34927
34989
  return success(data, message);
34928
34990
  };
34991
+ const extractEncFileKey = (downloadUrl)=>{
34992
+ if (!downloadUrl) return "";
34993
+ return downloadUrl.split("encfilekey=")[1]?.split("&")[0] || "";
34994
+ };
34929
34995
  const rid = ()=>`${Math.floor(Date.now() / 1e3).toString(16)}-${[
34930
34996
  ...Array(8)
34931
34997
  ].map(()=>Math.floor(16 * Math.random()).toString(16)).join("")}`;
@@ -40571,7 +40637,8 @@ var __webpack_exports__ = {};
40571
40637
  },
40572
40638
  _task.logger,
40573
40639
  params.proxyLoc,
40574
- params.accountId
40640
+ params.accountId,
40641
+ "xiaohongshu"
40575
40642
  ];
40576
40643
  const http = new Http(...args);
40577
40644
  let unreadCount = {
@@ -40600,8 +40667,8 @@ var __webpack_exports__ = {};
40600
40667
  headers: xsHeader
40601
40668
  }, {
40602
40669
  retries: 3,
40603
- retryDelay: 20,
40604
- timeout: 3000
40670
+ retryDelay: 300,
40671
+ timeout: 30000
40605
40672
  });
40606
40673
  const isSuccess = 0 === res.code;
40607
40674
  if (isSuccess) unreadCount = res.data;
@@ -41014,7 +41081,8 @@ var __webpack_exports__ = {};
41014
41081
  },
41015
41082
  _task.logger,
41016
41083
  params.proxyLoc,
41017
- params.accountId
41084
+ params.accountId,
41085
+ "xiaohongshu"
41018
41086
  ];
41019
41087
  const http = new Http(...args);
41020
41088
  const xsEncrypt = new Xhshow();
@@ -41030,8 +41098,8 @@ var __webpack_exports__ = {};
41030
41098
  url: "https://creator.xiaohongshu.com/api/galaxy/creator/home/personal_info"
41031
41099
  }, {
41032
41100
  retries: 3,
41033
- retryDelay: 20,
41034
- timeout: 3000
41101
+ retryDelay: 300,
41102
+ timeout: 30000
41035
41103
  }),
41036
41104
  http.api({
41037
41105
  method: "get",
@@ -41040,8 +41108,8 @@ var __webpack_exports__ = {};
41040
41108
  headers: sevenDataXsHeader
41041
41109
  }, {
41042
41110
  retries: 3,
41043
- retryDelay: 20,
41044
- timeout: 3000
41111
+ retryDelay: 300,
41112
+ timeout: 30000
41045
41113
  })
41046
41114
  ]);
41047
41115
  const xhsData = {
@@ -43903,7 +43971,7 @@ var __webpack_exports__ = {};
43903
43971
  name: params.music.name,
43904
43972
  artist: params.music.authorName,
43905
43973
  mediaStreamingUrl: params.music.url,
43906
- docType: params.music.raw.playableInfo.type
43974
+ docType: 2 === params.music.raw.bgmSource ? 0 : 1
43907
43975
  },
43908
43976
  groupId: params.music.id,
43909
43977
  hasBgm: 1,
@@ -44080,7 +44148,7 @@ var __webpack_exports__ = {};
44080
44148
  const resultMsg = publishResult.data?.baseResp?.errmsg ?? publishResult.errMsg;
44081
44149
  if (0 === resultCode) {
44082
44150
  task.logger.info("[shipinhaoPublish] 发布成功");
44083
- const publishId = uploadedImages[0]?.thumbUrl?.split("encfilekey=")[1]?.split("&")[0] || "";
44151
+ const publishId = extractEncFileKey(uploadedImages[0]?.thumbUrl);
44084
44152
  await updateTaskState?.({
44085
44153
  state: types_TaskState.SUCCESS,
44086
44154
  result: {
@@ -44872,13 +44940,110 @@ var __webpack_exports__ = {};
44872
44940
  pictureFileType
44873
44941
  };
44874
44942
  }
44943
+ const MENTION_TEXT_SUFFIX = "\u0020";
44944
+ const MENTION_XML_SUFFIX = "\u2005";
44945
+ function payload_buildDescription(params) {
44946
+ let description = params.description || "";
44947
+ for (const topic of params.topics || [])description += `#${topic}`;
44948
+ for (const user of params.mentionedUsers || [])description += `@${user.nickname}${MENTION_TEXT_SUFFIX}`;
44949
+ return description;
44950
+ }
44951
+ function buildMentionedUser(mentionedUsers) {
44952
+ return (mentionedUsers || []).map((user)=>({
44953
+ nickname: `${user.nickname}${MENTION_TEXT_SUFFIX}`
44954
+ }));
44955
+ }
44956
+ function payload_buildTopicXml(params) {
44957
+ const values = [];
44958
+ let atIndex = null;
44959
+ if (params.description) values.push(`<![CDATA[${params.description}]]>`);
44960
+ for (const topic of params.topics || [])values.push(`<topic><![CDATA[#${topic}#]]></topic>`);
44961
+ for (const user of params.mentionedUsers || []){
44962
+ if (null === atIndex) atIndex = values.length;
44963
+ values.push(`<![CDATA[@${user.nickname}${MENTION_XML_SUFFIX}]]>`);
44964
+ }
44965
+ let xml = "<finder>";
44966
+ xml += "<version>1</version>";
44967
+ xml += `<valuecount>${values.length}</valuecount>`;
44968
+ xml += `<style><at>${atIndex ?? ""}</at></style>`;
44969
+ values.forEach((value, index)=>{
44970
+ xml += `<value${index}>${value}</value${index}>`;
44971
+ });
44972
+ xml += "</finder>";
44973
+ return xml;
44974
+ }
44975
+ function payload_buildLocation(location) {
44976
+ if (!location) return {
44977
+ latitude: 0,
44978
+ longitude: 0,
44979
+ city: "",
44980
+ poiName: "",
44981
+ address: "",
44982
+ poiClassifyId: ""
44983
+ };
44984
+ return {
44985
+ latitude: location.latitude,
44986
+ longitude: location.longitude,
44987
+ city: location.city,
44988
+ poiName: location.poiName || "",
44989
+ address: location.address || "",
44990
+ poiClassifyId: location.poiClassifyId || ""
44991
+ };
44992
+ }
44993
+ function buildTopic(params) {
44994
+ const topic = {
44995
+ finderTopicInfo: payload_buildTopicXml(params)
44996
+ };
44997
+ if (params.collection) {
44998
+ topic.collectionId = params.collection.collectionId;
44999
+ topic.collectionName = params.collection.collectionName;
45000
+ }
45001
+ return topic;
45002
+ }
45003
+ function buildEvent(event) {
45004
+ if (!event) return {};
45005
+ return {
45006
+ eventTopicId: event.eventTopicId,
45007
+ eventName: event.eventName,
45008
+ eventCreatorNickname: event.eventCreatorNickname || ""
45009
+ };
45010
+ }
45011
+ function buildExtReading(link) {
45012
+ if (!link) return {
45013
+ link: "",
45014
+ title: "",
45015
+ urlType: 1
45016
+ };
45017
+ return {
45018
+ link: link.link.replace(/[\s\u200b]/g, ""),
45019
+ title: link.title,
45020
+ urlType: link.urlType ?? 1
45021
+ };
45022
+ }
45023
+ function buildTagInfo(tagInfo, tagKey) {
45024
+ return {
45025
+ ...tagInfo,
45026
+ tagKey
45027
+ };
45028
+ }
44875
45029
  const CHUNK_SIZE = 8388608;
45030
+ const UPLOAD_STAGE_TIMEOUT = 180000;
45031
+ const DEFAULT_TUNING = {
45032
+ metaTimeout: UPLOAD_STAGE_TIMEOUT,
45033
+ partTimeout: UPLOAD_STAGE_TIMEOUT,
45034
+ partRetries: 3,
45035
+ completeTimeout: UPLOAD_STAGE_TIMEOUT
45036
+ };
44876
45037
  async function uploader_uploadFile(opts) {
44877
- const { filePath, fileType, uin, authKey, http, logger } = opts;
45038
+ const { filePath, fileType, uin, authKey, http, logger, tuning } = opts;
44878
45039
  const stat = external_node_fs_default().statSync(filePath);
44879
45040
  const fileSize = stat.size;
44880
45041
  const fileName = filePath.split(/[\\/]/).pop() || "file";
44881
- logger?.info(`开始上传: ${fileName} (${fileSize} B, filetype=${fileType})`);
45042
+ const metaTimeout = tuning?.metaTimeout ?? DEFAULT_TUNING.metaTimeout;
45043
+ const partTimeout = tuning?.partTimeout ?? DEFAULT_TUNING.partTimeout;
45044
+ const partRetries = tuning?.partRetries ?? DEFAULT_TUNING.partRetries;
45045
+ const completeTimeout = tuning?.completeTimeout ?? DEFAULT_TUNING.completeTimeout;
45046
+ logger?.info(`[shipinhaoPublishVideo] 开始上传: ${fileName} (${fileSize} B, filetype=${fileType})`);
44882
45047
  const fileMd5 = await computeFileMd5(filePath);
44883
45048
  const taskId = generateTaskId(fileName, fileSize, fileMd5);
44884
45049
  const baseUrl = "https://finderassistancea.video.qq.com";
@@ -44889,7 +45054,7 @@ var __webpack_exports__ = {};
44889
45054
  const chunkCount = Math.ceil(fileSize / CHUNK_SIZE);
44890
45055
  const blockPartLength = [];
44891
45056
  for(let i = 0; i < chunkCount; i++)blockPartLength.push(Math.min((i + 1) * CHUNK_SIZE, fileSize));
44892
- logger?.info(`申请 UploadID: ${chunkCount} 片`);
45057
+ logger?.info(`[shipinhaoPublishVideo] 视频分片: ${chunkCount} 片`);
44893
45058
  const applyRes = await http.api({
44894
45059
  method: "PUT",
44895
45060
  url: `${baseUrl}/applyuploaddfs`,
@@ -44901,12 +45066,15 @@ var __webpack_exports__ = {};
44901
45066
  BlockSum: chunkCount,
44902
45067
  BlockPartLength: blockPartLength
44903
45068
  }
45069
+ }, {
45070
+ timeout: metaTimeout,
45071
+ retries: 2,
45072
+ retryDelay: 2000
44904
45073
  });
44905
45074
  if (!applyRes.UploadID && !applyRes.ListPartsResult) throw new Error("申请 UploadID 失败: " + JSON.stringify(applyRes));
44906
45075
  let uploadId = applyRes.UploadID;
44907
45076
  const uploadedParts = new Set();
44908
45077
  if (applyRes.ListPartsResult) {
44909
- logger?.info("检测到已上传分片,执行续传");
44910
45078
  const parts = Array.isArray(applyRes.ListPartsResult.Part) ? applyRes.ListPartsResult.Part : applyRes.ListPartsResult.Part ? [
44911
45079
  applyRes.ListPartsResult.Part
44912
45080
  ] : [];
@@ -44922,6 +45090,10 @@ var __webpack_exports__ = {};
44922
45090
  BlockSum: chunkCount,
44923
45091
  BlockPartLength: blockPartLength
44924
45092
  }
45093
+ }, {
45094
+ timeout: metaTimeout,
45095
+ retries: 2,
45096
+ retryDelay: 2000
44925
45097
  });
44926
45098
  uploadId = retryRes.UploadID;
44927
45099
  if (!uploadId) throw new Error("续传获取 UploadID 失败");
@@ -44938,7 +45110,6 @@ var __webpack_exports__ = {};
44938
45110
  PartNumber: partNumber,
44939
45111
  ETag: existing.ETag
44940
45112
  });
44941
- logger?.info(`分片 ${partNumber}/${chunkCount} 已存在,跳过`);
44942
45113
  continue;
44943
45114
  }
44944
45115
  }
@@ -44948,6 +45119,7 @@ var __webpack_exports__ = {};
44948
45119
  const chunk = Buffer.alloc(chunkSize);
44949
45120
  external_node_fs_default().readSync(fd, chunk, 0, chunkSize, start);
44950
45121
  const chunkMd5 = external_node_crypto_default().createHash("md5").update(chunk).digest("hex");
45122
+ const partStart = Date.now();
44951
45123
  await http.api({
44952
45124
  method: "PUT",
44953
45125
  url: `${baseUrl}/uploadpartdfs?PartNumber=${partNumber}&UploadID=${encodeURIComponent(uploadId)}`,
@@ -44957,17 +45129,20 @@ var __webpack_exports__ = {};
44957
45129
  "Content-MD5": chunkMd5
44958
45130
  },
44959
45131
  data: chunk
45132
+ }, {
45133
+ timeout: partTimeout,
45134
+ retries: partRetries,
45135
+ retryDelay: 2000
44960
45136
  });
45137
+ Date.now();
44961
45138
  partInfo.push({
44962
45139
  PartNumber: partNumber,
44963
45140
  ETag: `"${chunkMd5}"`
44964
45141
  });
44965
- logger?.info(`分片 ${partNumber}/${chunkCount} 完成 (${chunkSize} B)`);
44966
45142
  }
44967
45143
  } finally{
44968
45144
  external_node_fs_default().closeSync(fd);
44969
45145
  }
44970
- logger?.info("合并分片...");
44971
45146
  const completeRes = await http.api({
44972
45147
  method: "POST",
44973
45148
  url: `${baseUrl}/completepartuploaddfs?UploadID=${encodeURIComponent(uploadId)}`,
@@ -44979,9 +45154,13 @@ var __webpack_exports__ = {};
44979
45154
  TransFlag: "0_0",
44980
45155
  PartInfo: partInfo
44981
45156
  }
45157
+ }, {
45158
+ timeout: completeTimeout,
45159
+ retries: 1,
45160
+ retryDelay: 3000
44982
45161
  });
44983
45162
  if (!completeRes.DownloadURL) throw new Error("合并分片失败: " + JSON.stringify(completeRes));
44984
- logger?.info(`上传成功: ${completeRes.DownloadURL.slice(0, 80)}...`);
45163
+ logger?.info(`[shipinhaoPublishVideo] ${fileName} 上传成功`);
44985
45164
  return {
44986
45165
  downloadUrl: completeRes.DownloadURL,
44987
45166
  md5: fileMd5,
@@ -45001,85 +45180,233 @@ var __webpack_exports__ = {};
45001
45180
  const input = `${fileName}-${fileSize}-${fileMd5}`;
45002
45181
  return external_node_crypto_default().createHash("md5").update(input).digest("hex").slice(0, 32);
45003
45182
  }
45183
+ const MAX_METADATA_BOX_SIZE = 268435456;
45184
+ function readBoxHeader(fd, offset, limit) {
45185
+ if (offset + 8 > limit) return null;
45186
+ const head = Buffer.alloc(16);
45187
+ const read = external_node_fs_default().readSync(fd, head, 0, 16, offset);
45188
+ if (read < 8) return null;
45189
+ let size = head.readUInt32BE(0);
45190
+ const type = head.toString("latin1", 4, 8);
45191
+ let headerSize = 8;
45192
+ if (1 === size) {
45193
+ if (read < 16) return null;
45194
+ size = head.readUInt32BE(8) * 2 ** 32 + head.readUInt32BE(12);
45195
+ headerSize = 16;
45196
+ } else if (0 === size) size = limit - offset;
45197
+ if (size < headerSize || offset + size > limit) return null;
45198
+ return {
45199
+ type,
45200
+ size,
45201
+ headerSize
45202
+ };
45203
+ }
45004
45204
  function parseVideoMeta(filePath) {
45005
- let buf;
45205
+ let fd;
45206
+ let fileSize;
45207
+ try {
45208
+ fd = external_node_fs_default().openSync(filePath, "r");
45209
+ } catch {
45210
+ return null;
45211
+ }
45006
45212
  try {
45007
- buf = external_node_fs_default().readFileSync(filePath);
45213
+ fileSize = external_node_fs_default().fstatSync(fd).size;
45214
+ const state = {
45215
+ movie: null,
45216
+ tracks: [],
45217
+ trexDefaults: new Map(),
45218
+ fragmentDurations: new Map(),
45219
+ trafTrackId: 0,
45220
+ trafDefaultSampleDuration: 0
45221
+ };
45222
+ let offset = 0;
45223
+ while(offset + 8 <= fileSize){
45224
+ const header = readBoxHeader(fd, offset, fileSize);
45225
+ if (!header) break;
45226
+ if ("moov" === header.type || "moof" === header.type) {
45227
+ const bodyLength = header.size - header.headerSize;
45228
+ if (bodyLength > 0 && bodyLength <= MAX_METADATA_BOX_SIZE) {
45229
+ const body = Buffer.alloc(bodyLength);
45230
+ const read = external_node_fs_default().readSync(fd, body, 0, bodyLength, offset + header.headerSize);
45231
+ state.trafTrackId = 0;
45232
+ state.trafDefaultSampleDuration = 0;
45233
+ walkBoxes(body.subarray(0, read), 0, read, state);
45234
+ }
45235
+ }
45236
+ offset += header.size;
45237
+ }
45238
+ const { movie, tracks } = state;
45239
+ const video = tracks.find((t)=>"vide" === t.handler) || tracks.find((t)=>t.width > 0 && t.height > 0);
45240
+ if (!video) return null;
45241
+ let { width, height } = video;
45242
+ if (90 === video.rotation || 270 === video.rotation) [width, height] = [
45243
+ height,
45244
+ width
45245
+ ];
45246
+ return {
45247
+ width: Math.round(width),
45248
+ height: Math.round(height),
45249
+ duration: resolveDuration(movie, video, state.fragmentDurations),
45250
+ rotation: video.rotation,
45251
+ fileSize,
45252
+ codec: video.codec
45253
+ };
45008
45254
  } catch {
45009
45255
  return null;
45256
+ } finally{
45257
+ try {
45258
+ external_node_fs_default().closeSync(fd);
45259
+ } catch {}
45010
45260
  }
45011
- let movie = null;
45012
- const tracks = [];
45013
- function walk(start, end) {
45014
- let off = start;
45015
- while(off + 8 <= end){
45016
- let size = buf.readUInt32BE(off);
45017
- const type = buf.toString("latin1", off + 4, off + 8);
45018
- let headerSize = 8;
45019
- if (1 === size) {
45020
- if (off + 16 > end) break;
45021
- const hi = buf.readUInt32BE(off + 8);
45022
- const lo = buf.readUInt32BE(off + 12);
45023
- size = hi * 2 ** 32 + lo;
45024
- headerSize = 16;
45025
- } else if (0 === size) size = end - off;
45026
- if (size < headerSize || off + size > end) break;
45027
- const bodyStart = off + headerSize;
45028
- const bodyEnd = off + size;
45029
- switch(type){
45030
- case "moov":
45031
- case "trak":
45032
- case "mdia":
45033
- case "minf":
45034
- case "stbl":
45035
- walk(bodyStart, bodyEnd);
45261
+ }
45262
+ function walkBoxes(buf, start, end, state) {
45263
+ let off = start;
45264
+ while(off + 8 <= end){
45265
+ let size = buf.readUInt32BE(off);
45266
+ const type = buf.toString("latin1", off + 4, off + 8);
45267
+ let headerSize = 8;
45268
+ if (1 === size) {
45269
+ if (off + 16 > end) break;
45270
+ const hi = buf.readUInt32BE(off + 8);
45271
+ const lo = buf.readUInt32BE(off + 12);
45272
+ size = hi * 2 ** 32 + lo;
45273
+ headerSize = 16;
45274
+ } else if (0 === size) size = end - off;
45275
+ if (size < headerSize || off + size > end) break;
45276
+ const bodyStart = off + headerSize;
45277
+ const bodyEnd = off + size;
45278
+ switch(type){
45279
+ case "trak":
45280
+ case "mdia":
45281
+ case "minf":
45282
+ case "stbl":
45283
+ case "mvex":
45284
+ case "traf":
45285
+ walkBoxes(buf, bodyStart, bodyEnd, state);
45286
+ break;
45287
+ case "mvhd":
45288
+ state.movie = parseMvhd(buf, bodyStart, bodyEnd);
45289
+ break;
45290
+ case "tkhd":
45291
+ state.tracks.push({
45292
+ ...parseTkhd(buf, bodyStart, bodyEnd),
45293
+ handler: null,
45294
+ codec: null,
45295
+ timescale: 0,
45296
+ mdhdDuration: 0,
45297
+ sttsDuration: 0
45298
+ });
45299
+ break;
45300
+ case "mdhd":
45301
+ {
45302
+ const mdhd = parseMvhd(buf, bodyStart, bodyEnd);
45303
+ if (mdhd && state.tracks.length) {
45304
+ const track = state.tracks[state.tracks.length - 1];
45305
+ track.timescale = mdhd.timescale;
45306
+ track.mdhdDuration = mdhd.duration;
45307
+ }
45036
45308
  break;
45037
- case "mvhd":
45038
- movie = parseMvhd(buf, bodyStart, bodyEnd);
45309
+ }
45310
+ case "hdlr":
45311
+ if (bodyEnd - bodyStart >= 12 && state.tracks.length) state.tracks[state.tracks.length - 1].handler = buf.toString("latin1", bodyStart + 8, bodyStart + 12);
45312
+ break;
45313
+ case "stsd":
45314
+ {
45315
+ const codec = parseStsdCodec(buf, bodyStart, bodyEnd);
45316
+ if (codec && state.tracks.length) state.tracks[state.tracks.length - 1].codec = codec;
45039
45317
  break;
45040
- case "tkhd":
45041
- tracks.push({
45042
- ...parseTkhd(buf, bodyStart, bodyEnd),
45043
- handler: null,
45044
- codec: null
45045
- });
45318
+ }
45319
+ case "stts":
45320
+ if (state.tracks.length) state.tracks[state.tracks.length - 1].sttsDuration = parseSttsDuration(buf, bodyStart, bodyEnd);
45321
+ break;
45322
+ case "trex":
45323
+ if (bodyStart + 20 > bodyEnd) break;
45324
+ state.trexDefaults.set(buf.readUInt32BE(bodyStart + 4), buf.readUInt32BE(bodyStart + 12));
45325
+ break;
45326
+ case "tfhd":
45327
+ {
45328
+ const tfhd = parseTfhd(buf, bodyStart, bodyEnd);
45329
+ state.trafTrackId = tfhd.trackId;
45330
+ state.trafDefaultSampleDuration = tfhd.defaultSampleDuration || state.trexDefaults.get(tfhd.trackId) || 0;
45046
45331
  break;
45047
- case "hdlr":
45048
- if (bodyEnd - bodyStart >= 12) {
45049
- const handler = buf.toString("latin1", bodyStart + 8, bodyStart + 12);
45050
- if (tracks.length) tracks[tracks.length - 1].handler = handler;
45051
- }
45332
+ }
45333
+ case "trun":
45334
+ {
45335
+ const duration = parseTrunDuration(buf, bodyStart, bodyEnd, state.trafDefaultSampleDuration);
45336
+ state.fragmentDurations.set(state.trafTrackId, (state.fragmentDurations.get(state.trafTrackId) || 0) + duration);
45052
45337
  break;
45053
- case "stsd":
45054
- {
45055
- const codec = parseStsdCodec(buf, bodyStart, bodyEnd);
45056
- if (codec && tracks.length) tracks[tracks.length - 1].codec = codec;
45057
- break;
45058
- }
45059
- }
45060
- off += size;
45338
+ }
45061
45339
  }
45062
- }
45063
- walk(0, buf.length);
45064
- const video = tracks.find((t)=>"vide" === t.handler) || tracks.find((t)=>t.width > 0 && t.height > 0);
45065
- if (!movie || !video) return null;
45066
- const movieData = movie;
45067
- let { width, height } = video;
45068
- if (90 === video.rotation || 270 === video.rotation) [width, height] = [
45069
- height,
45070
- width
45071
- ];
45072
- return {
45073
- width: Math.round(width),
45074
- height: Math.round(height),
45075
- duration: movieData.timescale ? movieData.duration / movieData.timescale : 0,
45076
- rotation: video.rotation,
45077
- fileSize: buf.length,
45078
- codec: video.codec
45340
+ off += size;
45341
+ }
45342
+ }
45343
+ const UNKNOWN_DURATION_32 = 0xffffffff;
45344
+ function isUsableDuration(duration) {
45345
+ return duration > 0 && duration !== UNKNOWN_DURATION_32;
45346
+ }
45347
+ function resolveDuration(movie, video, fragmentDurations) {
45348
+ if (movie && movie.timescale && isUsableDuration(movie.duration)) return movie.duration / movie.timescale;
45349
+ if (video.timescale) {
45350
+ if (isUsableDuration(video.mdhdDuration)) return video.mdhdDuration / video.timescale;
45351
+ if (video.sttsDuration > 0) return video.sttsDuration / video.timescale;
45352
+ const fragment = fragmentDurations.get(video.trackId) ?? (1 === fragmentDurations.size ? [
45353
+ ...fragmentDurations.values()
45354
+ ][0] : 0);
45355
+ if (fragment > 0) return fragment / video.timescale;
45356
+ }
45357
+ return 0;
45358
+ }
45359
+ function parseSttsDuration(buf, start, end) {
45360
+ if (start + 8 > end) return 0;
45361
+ const entryCount = buf.readUInt32BE(start + 4);
45362
+ let total = 0;
45363
+ for(let i = 0; i < entryCount; i++){
45364
+ const off = start + 8 + 8 * i;
45365
+ if (off + 8 > end) break;
45366
+ total += buf.readUInt32BE(off) * buf.readUInt32BE(off + 4);
45367
+ }
45368
+ return total;
45369
+ }
45370
+ function parseTfhd(buf, start, end) {
45371
+ if (start + 8 > end) return {
45372
+ trackId: 0,
45373
+ defaultSampleDuration: 0
45374
+ };
45375
+ const flags = buf.readUIntBE(start + 1, 3);
45376
+ const trackId = buf.readUInt32BE(start + 4);
45377
+ let off = start + 8;
45378
+ if (0x000001 & flags) off += 8;
45379
+ if (0x000002 & flags) off += 4;
45380
+ if (0x000008 & flags && off + 4 <= end) return {
45381
+ trackId,
45382
+ defaultSampleDuration: buf.readUInt32BE(off)
45079
45383
  };
45384
+ return {
45385
+ trackId,
45386
+ defaultSampleDuration: 0
45387
+ };
45388
+ }
45389
+ function parseTrunDuration(buf, start, end, defaultSampleDuration) {
45390
+ if (start + 8 > end) return 0;
45391
+ const flags = buf.readUIntBE(start + 1, 3);
45392
+ const sampleCount = buf.readUInt32BE(start + 4);
45393
+ let off = start + 8;
45394
+ if (0x000001 & flags) off += 4;
45395
+ if (0x000004 & flags) off += 4;
45396
+ const hasDuration = (0x000100 & flags) !== 0;
45397
+ if (!hasDuration) return sampleCount * defaultSampleDuration;
45398
+ const entrySize = 4 + ((0x000200 & flags) !== 0 ? 4 : 0) + ((0x000400 & flags) !== 0 ? 4 : 0) + ((0x000800 & flags) !== 0 ? 4 : 0);
45399
+ let total = 0;
45400
+ for(let i = 0; i < sampleCount; i++){
45401
+ const entryOff = off + i * entrySize;
45402
+ if (entryOff + 4 > end) break;
45403
+ total += buf.readUInt32BE(entryOff);
45404
+ }
45405
+ return total;
45080
45406
  }
45081
45407
  function parseStsdCodec(buf, start, end) {
45082
45408
  if (start + 16 > end) return null;
45409
+ if (0 === buf.readUInt32BE(start + 4)) return null;
45083
45410
  return buf.toString("latin1", start + 12, start + 16).toLowerCase();
45084
45411
  }
45085
45412
  function parseMvhd(buf, start, end) {
@@ -45105,7 +45432,10 @@ var __webpack_exports__ = {};
45105
45432
  const afterDuration = 1 === version ? 36 : 24;
45106
45433
  const matrixOff = start + afterDuration + 16;
45107
45434
  const whOff = matrixOff + 36;
45435
+ const trackIdOff = start + (1 === version ? 20 : 12);
45436
+ const trackId = trackIdOff + 4 <= end ? buf.readUInt32BE(trackIdOff) : 0;
45108
45437
  if (whOff + 8 > end) return {
45438
+ trackId,
45109
45439
  width: 0,
45110
45440
  height: 0,
45111
45441
  rotation: 0
@@ -45119,11 +45449,25 @@ var __webpack_exports__ = {};
45119
45449
  else if (Math.abs(a + 1) < 0.01 && Math.abs(b) < 0.01) rotation = 180;
45120
45450
  else if (Math.abs(a) < 0.01 && Math.abs(b + 1) < 0.01) rotation = 270;
45121
45451
  return {
45452
+ trackId,
45122
45453
  width,
45123
45454
  height,
45124
45455
  rotation
45125
45456
  };
45126
45457
  }
45458
+ function parseVideoCodec(filePath) {
45459
+ return parseVideoMeta(filePath)?.codec ?? null;
45460
+ }
45461
+ function buildVideoMetaFromParams(filePath, metadata) {
45462
+ return {
45463
+ width: Math.round(metadata.width),
45464
+ height: Math.round(metadata.height),
45465
+ duration: metadata.duration,
45466
+ rotation: 0,
45467
+ fileSize: metadata.fileSize,
45468
+ codec: parseVideoCodec(filePath)
45469
+ };
45470
+ }
45127
45471
  const MAX_DURATION_SECONDS = 28800;
45128
45472
  const MAX_FILE_SIZE = 21474836480;
45129
45473
  const ALLOWED_EXTENSIONS = [
@@ -45134,6 +45478,7 @@ var __webpack_exports__ = {};
45134
45478
  "avc3"
45135
45479
  ];
45136
45480
  const MIN_TITLE_LENGTH = 6;
45481
+ const MAX_TITLE_LENGTH = 16;
45137
45482
  function formatFileSize(bytes) {
45138
45483
  if (bytes < 1048576) return `${(bytes / 1024).toFixed(2)} KB`;
45139
45484
  if (bytes < 1073741824) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
@@ -45160,26 +45505,62 @@ var __webpack_exports__ = {};
45160
45505
  function validateShipinhaoTitle(title) {
45161
45506
  if (void 0 === title) return null;
45162
45507
  const trimmed = title.trim();
45508
+ if ("" === trimmed) return null;
45163
45509
  const length = [
45164
45510
  ...trimmed
45165
45511
  ].length;
45166
- if (length < MIN_TITLE_LENGTH) return `视频号标题至少需要 ${MIN_TITLE_LENGTH} 个字符,请补充后重试。`;
45512
+ if (length < MIN_TITLE_LENGTH || length > MAX_TITLE_LENGTH) return `视频号标题需要在 ${MIN_TITLE_LENGTH}-${MAX_TITLE_LENGTH} 个字符之间,当前 ${length} 个字符,请调整后重试。`;
45167
45513
  return null;
45168
45514
  }
45169
- async function getTraceKey(auth, http, logger) {
45170
- logger.info("[getTraceKey] 开始获取 traceKey...");
45515
+ const POST_CREATE_PAGE_URL = "https://channels.weixin.qq.com/micro/content/post/create";
45516
+ const MICRO_CONTENT_BASE = "https://channels.weixin.qq.com/micro/content/cgi-bin/mmfinderassistant-bin";
45517
+ function resolveClientContext(params, fallbackUin) {
45518
+ const extra = params.extraParam || {};
45519
+ const deviceIdCookie = params.cookies.find((c)=>"device_id" === c.name || "finger_print_device_id" === c.name)?.value;
45520
+ return {
45521
+ aId: "string" == typeof extra.aId ? extra.aId : "",
45522
+ fingerPrintDeviceId: "string" == typeof extra.fingerPrintDeviceId ? extra.fingerPrintDeviceId : deviceIdCookie || "",
45523
+ uin: "string" == typeof extra.uin ? extra.uin : String(fallbackUin)
45524
+ };
45525
+ }
45526
+ function buildPublishHeaders(cookieString, client) {
45527
+ const headers = {
45528
+ cookie: cookieString,
45529
+ referer: POST_CREATE_PAGE_URL,
45530
+ origin: "https://channels.weixin.qq.com",
45531
+ "content-type": "application/json"
45532
+ };
45533
+ if (client.fingerPrintDeviceId) headers["finger-print-device-id"] = client.fingerPrintDeviceId;
45534
+ if (client.uin) headers["x-wechat-uin"] = client.uin;
45535
+ return headers;
45536
+ }
45537
+ function buildPublishQuery(client) {
45538
+ const query = {
45539
+ _rid: rid(),
45540
+ _pageUrl: POST_CREATE_PAGE_URL
45541
+ };
45542
+ if (client.aId) query._aid = client.aId;
45543
+ return query;
45544
+ }
45545
+ function buildCommonBody(finderUsername) {
45546
+ return {
45547
+ timestamp: String(Date.now()),
45548
+ _log_finder_uin: "",
45549
+ _log_finder_id: finderUsername,
45550
+ rawKeyBuff: "",
45551
+ pluginSessionId: null,
45552
+ scene: 7,
45553
+ reqScene: 7
45554
+ };
45555
+ }
45556
+ async function getTraceKey(auth, client, http, logger) {
45171
45557
  const res = await http.api({
45172
45558
  method: "POST",
45173
- url: "https://channels.weixin.qq.com/micro/content/cgi-bin/mmfinderassistant-bin/post/get-finder-post-trace-key",
45559
+ url: `${MICRO_CONTENT_BASE}/post/get-finder-post-trace-key`,
45560
+ params: buildPublishQuery(client),
45174
45561
  data: {
45175
45562
  objectId: "",
45176
- timestamp: String(Date.now()),
45177
- _log_finder_uin: "",
45178
- _log_finder_id: auth.finderUsername,
45179
- rawKeyBuff: "",
45180
- pluginSessionId: null,
45181
- scene: 7,
45182
- reqScene: 7
45563
+ ...buildCommonBody(auth.finderUsername)
45183
45564
  },
45184
45565
  defaultErrorMsg: "获取 traceKey 失败"
45185
45566
  });
@@ -45187,18 +45568,38 @@ var __webpack_exports__ = {};
45187
45568
  logger.error("[getTraceKey] 获取失败:", JSON.stringify(res));
45188
45569
  throw new Error(`获取 traceKey 失败: ${JSON.stringify(res)}`);
45189
45570
  }
45190
- logger.info(`[getTraceKey] 获取成功: ${res.data.traceKey}`);
45191
45571
  return res.data.traceKey;
45192
45572
  }
45573
+ async function getObjectTagKey(auth, client, http, logger) {
45574
+ try {
45575
+ const res = await http.api({
45576
+ method: "POST",
45577
+ url: `${MICRO_CONTENT_BASE}/post/finder_get_object_tag_list`,
45578
+ params: buildPublishQuery(client),
45579
+ data: {
45580
+ source: 1,
45581
+ ...buildCommonBody(auth.finderUsername)
45582
+ },
45583
+ defaultErrorMsg: "获取内容声明标注失败"
45584
+ });
45585
+ if (0 !== res.errCode || !res.data?.tagKey) {
45586
+ logger.warn(`[getObjectTagKey] 未取到 tagKey: ${JSON.stringify(res)}`);
45587
+ return null;
45588
+ }
45589
+ return res.data.tagKey;
45590
+ } catch (error) {
45591
+ logger.warn(`[getObjectTagKey] 获取 tagKey 异常: ${stringifyError(error)}`);
45592
+ return null;
45593
+ }
45594
+ }
45193
45595
  async function submitAndPollTranscode(opts) {
45194
- const { videoUrl, videoMeta, traceKey, uploadStartTime, uploadEndTime, finderUsername, http, logger } = opts;
45195
- logger.info("[submitAndPollTranscode] 开始提交转码任务...");
45596
+ const { videoUrl, videoMeta, traceKey, uploadStartTime, uploadEndTime, finderUsername, client, http, logger } = opts;
45196
45597
  const finderUrl = videoUrl.includes("wxapp.tc.qq.com") ? `https://finder.video.qq.com${videoUrl.split("qq.com")[1]}` : videoUrl;
45197
- logger.info(`[submitAndPollTranscode] 视频URL: ${finderUrl}`);
45198
45598
  logger.info(`[submitAndPollTranscode] 视频尺寸: ${videoMeta.width}x${videoMeta.height}, 时长: ${videoMeta.duration}s`);
45199
45599
  const submitRes = await http.api({
45200
45600
  method: "POST",
45201
- url: "https://channels.weixin.qq.com/micro/content/cgi-bin/mmfinderassistant-bin/post/post_clip_video",
45601
+ url: `${MICRO_CONTENT_BASE}/post/post_clip_video`,
45602
+ params: buildPublishQuery(client),
45202
45603
  data: {
45203
45604
  url: finderUrl,
45204
45605
  timeStart: 0,
@@ -45222,13 +45623,7 @@ var __webpack_exports__ = {};
45222
45623
  targetHeight: videoMeta.height,
45223
45624
  type: 4,
45224
45625
  useAstraThumbCover: 1,
45225
- timestamp: String(Date.now()),
45226
- _log_finder_uin: "",
45227
- _log_finder_id: finderUsername,
45228
- rawKeyBuff: "",
45229
- pluginSessionId: null,
45230
- scene: 7,
45231
- reqScene: 7
45626
+ ...buildCommonBody(finderUsername)
45232
45627
  },
45233
45628
  defaultErrorMsg: "提交转码失败"
45234
45629
  });
@@ -45237,43 +45632,39 @@ var __webpack_exports__ = {};
45237
45632
  throw new Error(`提交转码失败: ${JSON.stringify(submitRes)}`);
45238
45633
  }
45239
45634
  const { clipKey, draftId } = submitRes.data;
45240
- logger.info(`[submitAndPollTranscode] 转码任务已提交,clipKey: ${clipKey}, draftId: ${draftId}`);
45241
- const maxPolls = 60;
45242
45635
  const pollInterval = 5000;
45636
+ const pollBudget = Math.min(1800000, 300000 + 1000 * Math.ceil(1.5 * videoMeta.duration));
45637
+ const maxPolls = Math.ceil(pollBudget / pollInterval);
45243
45638
  let pollCount = 0;
45244
- logger.info(`[submitAndPollTranscode] 开始轮询转码结果,最多 ${maxPolls} 次,间隔 ${pollInterval / 1000}s`);
45245
45639
  while(pollCount < maxPolls){
45246
45640
  await mock_sleep(pollInterval);
45247
45641
  pollCount++;
45248
45642
  const pollRes = await http.api({
45249
45643
  method: "POST",
45250
- url: "https://channels.weixin.qq.com/micro/content/cgi-bin/mmfinderassistant-bin/post/post_clip_video_result",
45644
+ url: `${MICRO_CONTENT_BASE}/post/post_clip_video_result`,
45645
+ params: buildPublishQuery(client),
45251
45646
  data: {
45252
45647
  clipKey,
45253
45648
  draftId,
45254
- timestamp: String(Date.now()),
45255
- _log_finder_uin: "",
45256
- _log_finder_id: finderUsername,
45257
- rawKeyBuff: "",
45258
- pluginSessionId: null,
45259
- scene: 7,
45260
- reqScene: 7
45649
+ ...buildCommonBody(finderUsername)
45261
45650
  },
45262
45651
  defaultErrorMsg: "转码轮询失败"
45652
+ }, {
45653
+ timeout: 60000,
45654
+ retries: 2,
45655
+ retryDelay: 3000
45263
45656
  });
45264
45657
  if (0 !== pollRes.errCode) {
45265
45658
  logger.error(`[submitAndPollTranscode] 转码轮询失败 (poll ${pollCount}):`, JSON.stringify(pollRes));
45266
45659
  throw new Error(`转码轮询失败 (poll ${pollCount}): ${JSON.stringify(pollRes)}`);
45267
45660
  }
45268
45661
  const { flag, url, width, height, duration, md5, fileSize } = pollRes.data || {};
45269
- logger.info(`[submitAndPollTranscode] 轮询第 ${pollCount} 次,flag=${flag}`);
45270
45662
  if (1 === flag) {
45271
45663
  if (!url || !width || !height || !duration || !md5 || !fileSize) {
45272
45664
  logger.error("[submitAndPollTranscode] 转码完成但返回数据不完整:", JSON.stringify(pollRes.data));
45273
45665
  throw new Error(`转码完成但返回数据不完整: ${JSON.stringify(pollRes.data)}`);
45274
45666
  }
45275
45667
  logger.info(`[submitAndPollTranscode] 转码完成! 用时: ${pollCount * pollInterval / 1000}s`);
45276
- logger.info(`[submitAndPollTranscode] 视频信息: ${width}x${height}, 时长: ${duration}s, 大小: ${fileSize}`);
45277
45668
  return {
45278
45669
  clipKey,
45279
45670
  url,
@@ -45284,7 +45675,7 @@ var __webpack_exports__ = {};
45284
45675
  fileSize
45285
45676
  };
45286
45677
  }
45287
- if (2 === flag) logger.info(`[submitAndPollTranscode] 转码中... (${pollCount}/${maxPolls})`);
45678
+ if (2 === flag) ;
45288
45679
  else {
45289
45680
  logger.error(`[submitAndPollTranscode] 转码失败,未知 flag=${flag}:`, JSON.stringify(pollRes.data));
45290
45681
  throw new Error(`转码失败,未知 flag=${flag}: ${JSON.stringify(pollRes.data)}`);
@@ -45294,162 +45685,151 @@ var __webpack_exports__ = {};
45294
45685
  throw new Error(`转码超时 (${maxPolls * pollInterval / 1000}s)`);
45295
45686
  }
45296
45687
  async function publishVideo(opts) {
45297
- const { params, auth, clipResult, videoUpload, coverUpload, traceKey, uploadStartTime, uploadEndTime, proxyHttp, logger } = opts;
45298
- logger.info("[publishVideo] 开始构建发布请求...");
45299
- const coverFinderUrl = coverUpload.downloadUrl.includes("wxapp.tc.qq.com") ? `https://finder.video.qq.com${coverUpload.downloadUrl.split("qq.com")[1]}` : coverUpload.downloadUrl;
45300
- logger.info(`[publishVideo] 封面URL: ${coverFinderUrl}`);
45688
+ const { params, auth, client, clipResult, videoUpload, coverUpload, verticalCoverUpload, videoMeta, traceKey, tagKey, uploadStartTime, uploadEndTime, proxyHttp, logger } = opts;
45689
+ const toFinderUrl = (url)=>url.includes("wxapp.tc.qq.com") ? `https://finder.video.qq.com${url.split("qq.com")[1]}` : url;
45690
+ const thumbFinderUrl = toFinderUrl(coverUpload.downloadUrl);
45691
+ const coverFinderUrl = toFinderUrl(verticalCoverUpload.downloadUrl);
45301
45692
  const md5sumUuid = external_node_crypto_default().randomUUID();
45302
- logger.info("[publishVideo] 发布参数:");
45303
- logger.info(` - 标题: ${params.title || "(无)"}`);
45304
- logger.info(` - 描述: ${params.description?.substring(0, 50)}${params.description?.length > 50 ? "..." : ""}`);
45305
- logger.info(` - 定时发布: ${params.scheduleTime ? new Date(1000 * params.scheduleTime).toLocaleString() : "立即发布"}`);
45306
- logger.info(` - 地理位置: ${params.location ? `${params.location.city} (${params.location.latitude},${params.location.longitude})` : "(无)"}`);
45307
- const publishRes = await proxyHttp.api({
45308
- method: "POST",
45309
- url: "https://channels.weixin.qq.com/micro/content/cgi-bin/mmfinderassistant-bin/post/post_create",
45310
- data: {
45311
- objectType: 0,
45312
- longitude: params.location?.longitude || 0,
45313
- latitude: params.location?.latitude || 0,
45314
- feedLongitude: 0,
45315
- feedLatitude: 0,
45316
- originalFlag: 0,
45317
- topics: [],
45318
- isFullPost: 1,
45319
- handleFlag: 2,
45320
- videoClipTaskId: clipResult.clipKey,
45321
- traceInfo: {
45322
- traceKey,
45323
- uploadCdnStart: uploadStartTime,
45324
- uploadCdnEnd: uploadEndTime
45325
- },
45326
- objectDesc: {
45327
- mpTitle: "",
45328
- description: params.description,
45329
- shortTitle: params.title || "",
45330
- extReading: {},
45693
+ const description = params.description;
45694
+ const objectDesc = {
45695
+ mpTitle: "",
45696
+ description,
45697
+ extReading: buildExtReading(params.link),
45698
+ mediaType: 4,
45699
+ location: payload_buildLocation(params.location),
45700
+ topic: buildTopic({
45701
+ description: params.description,
45702
+ topics: params.topics,
45703
+ mentionedUsers: params.mentionedUsers,
45704
+ collection: params.collection
45705
+ }),
45706
+ event: buildEvent(params.event),
45707
+ mentionedUser: buildMentionedUser(params.mentionedUsers),
45708
+ media: [
45709
+ {
45710
+ url: clipResult.url,
45711
+ fileSize: clipResult.fileSize,
45712
+ thumbUrl: thumbFinderUrl,
45713
+ fullThumbUrl: thumbFinderUrl,
45714
+ coverUrl: coverFinderUrl,
45715
+ fullCoverUrl: coverFinderUrl,
45716
+ shareCoverUrl: coverFinderUrl,
45331
45717
  mediaType: 4,
45332
- location: params.location ? {
45333
- latitude: params.location.latitude,
45334
- longitude: params.location.longitude,
45335
- city: params.location.city,
45336
- poiClassifyId: params.location.poiClassifyId || ""
45337
- } : {
45338
- latitude: 0,
45339
- longitude: 0,
45340
- city: "",
45341
- poiClassifyId: ""
45342
- },
45343
- topic: {
45344
- finderTopicInfo: `<finder><version>1</version><valuecount>1</valuecount><style><at></at></style><value0><![CDATA[${params.description}]]></value0></finder>`
45345
- },
45346
- event: {},
45347
- mentionedUser: [],
45348
- media: [
45349
- {
45350
- url: clipResult.url,
45351
- fileSize: clipResult.fileSize,
45352
- thumbUrl: coverFinderUrl,
45353
- fullThumbUrl: coverFinderUrl,
45354
- coverUrl: coverFinderUrl,
45355
- fullCoverUrl: coverFinderUrl,
45356
- shareCoverUrl: coverFinderUrl,
45357
- mediaType: 4,
45358
- videoPlayLen: Math.round(clipResult.duration),
45359
- width: clipResult.width,
45360
- height: clipResult.height,
45361
- md5sum: md5sumUuid,
45362
- cardShowStyle: 2,
45363
- urlCdnTaskId: clipResult.clipKey
45364
- }
45365
- ],
45366
- member: {}
45367
- },
45368
- effectiveTime: params.scheduleTime || 0,
45369
- report: {
45370
- clipKey: clipResult.clipKey,
45371
- draftId: clipResult.clipKey,
45372
- timestamp: String(Date.now()),
45373
- _log_finder_uin: "",
45374
- _log_finder_id: auth.finderUsername,
45375
- rawKeyBuff: "",
45376
- pluginSessionId: null,
45377
- scene: 7,
45378
- reqScene: 7,
45379
- height: clipResult.height,
45718
+ videoPlayLen: Math.round(clipResult.duration),
45380
45719
  width: clipResult.width,
45381
- duration: clipResult.duration,
45382
- fileSize: videoUpload.fileSize,
45383
- uploadCost: (uploadEndTime - uploadStartTime) * 1000
45384
- },
45385
- postFlag: 0,
45386
- mode: 1,
45387
- clientid: external_node_crypto_default().randomUUID(),
45388
- timestamp: String(Date.now()),
45389
- _log_finder_uin: "",
45390
- _log_finder_id: auth.finderUsername,
45391
- rawKeyBuff: "",
45392
- pluginSessionId: null,
45393
- scene: 7,
45394
- reqScene: 7
45720
+ height: clipResult.height,
45721
+ md5sum: md5sumUuid,
45722
+ cardShowStyle: 2,
45723
+ urlCdnTaskId: clipResult.clipKey
45724
+ }
45725
+ ],
45726
+ shortTitle: params.title ? [
45727
+ {
45728
+ shortTitle: params.title
45729
+ }
45730
+ ] : [],
45731
+ member: {}
45732
+ };
45733
+ const publishData = {
45734
+ objectType: 0,
45735
+ longitude: 0,
45736
+ latitude: 0,
45737
+ feedLongitude: 0,
45738
+ feedLatitude: 0,
45739
+ originalFlag: params.originalFlag ?? 0,
45740
+ topics: params.topics || [],
45741
+ isFullPost: 1,
45742
+ handleFlag: 2,
45743
+ videoClipTaskId: clipResult.clipKey,
45744
+ traceInfo: {
45745
+ traceKey,
45746
+ uploadCdnStart: uploadStartTime,
45747
+ uploadCdnEnd: uploadEndTime
45395
45748
  },
45749
+ objectDesc,
45750
+ report: {
45751
+ clipKey: clipResult.clipKey,
45752
+ draftId: clipResult.clipKey,
45753
+ ...buildCommonBody(auth.finderUsername),
45754
+ height: videoMeta.height,
45755
+ width: videoMeta.width,
45756
+ duration: videoMeta.duration,
45757
+ fileSize: videoUpload.fileSize,
45758
+ uploadCost: (uploadEndTime - uploadStartTime) * 1000
45759
+ },
45760
+ postFlag: 0,
45761
+ mode: 1,
45762
+ clientid: external_node_crypto_default().randomUUID(),
45763
+ ...buildCommonBody(auth.finderUsername)
45764
+ };
45765
+ if (params.scheduledTime) publishData.effectiveTime = params.scheduledTime;
45766
+ if (params.tagInfo && tagKey) publishData.tagInfo = buildTagInfo(params.tagInfo, tagKey);
45767
+ const publishRes = await proxyHttp.api({
45768
+ method: "POST",
45769
+ url: `${MICRO_CONTENT_BASE}/post/post_create`,
45770
+ params: buildPublishQuery(client),
45771
+ data: publishData,
45396
45772
  defaultErrorMsg: "发布视频失败"
45397
45773
  });
45398
- logger.info(`[publishVideo] 发布响应: errCode=${publishRes.errCode}, baseResp.errcode=${publishRes.data?.baseResp?.errcode}`);
45774
+ logger.info(`[publishVideo] 发布结果: errCode=${publishRes.errCode}, baseResp.errcode=${publishRes.data?.baseResp?.errcode}`);
45399
45775
  return publishRes;
45400
45776
  }
45401
45777
  function mock_sleep(ms) {
45402
45778
  return new Promise((resolve)=>setTimeout(resolve, ms));
45403
45779
  }
45780
+ async function resolveLocalCoverPath(coverPath, label, tmpCachePath, logger) {
45781
+ if (!/^https?:\/\//i.test(coverPath)) return coverPath;
45782
+ const fileName = getFilenameFromUrl(coverPath);
45783
+ const savePath = external_node_path_default().join(tmpCachePath, `${Date.now()}-${label}-${fileName}`);
45784
+ await downloadImage(coverPath, savePath);
45785
+ return savePath;
45786
+ }
45404
45787
  const shipinhaoPublishVideo_mock_mockAction = async (task, params)=>{
45405
- task.logger.info("[shipinhaoPublishVideo] 开始执行视频号视频发布 - Mock API 方式");
45406
45788
  const updateTaskState = task.taskStageStore?.update?.bind(task.taskStageStore, task.taskId || "");
45407
45789
  let currentStep = "初始化";
45408
45790
  try {
45409
45791
  currentStep = "解析认证信息";
45410
45792
  const cookieString = params.cookies.map((c)=>`${c.name}=${c.value}`).join("; ");
45411
- const headers = {
45412
- cookie: cookieString
45413
- };
45793
+ const http = new Http({
45794
+ headers: {
45795
+ cookie: cookieString
45796
+ }
45797
+ });
45798
+ currentStep = "验证发布参数";
45799
+ if (!params.videoPath) return utils_response(414, "视频文件路径不能为空", "");
45800
+ if (!params.coverPath) return utils_response(414, "横屏封面图片路径不能为空", "");
45801
+ currentStep = "获取上传认证";
45802
+ const auth = await getShipinhaoUploadAuth(cookieString, http);
45803
+ const client = resolveClientContext(params, auth.uin);
45804
+ const publishHeaders = buildPublishHeaders(cookieString, client);
45805
+ const microHttp = new Http({
45806
+ headers: publishHeaders
45807
+ });
45414
45808
  const args = [
45415
45809
  {
45416
- headers
45810
+ headers: publishHeaders
45417
45811
  },
45418
45812
  task.logger,
45419
45813
  params.proxyLoc,
45420
45814
  params.accountId,
45421
45815
  "shipinhao"
45422
45816
  ];
45423
- const http = new Http({
45424
- headers
45425
- });
45426
45817
  const proxyHttp = new Http(...args);
45427
- currentStep = "验证发布参数";
45428
- if (!params.videoPath) return utils_response(414, "视频文件路径不能为空", "");
45429
- if (!params.coverPath) return utils_response(414, "封面图片路径不能为空", "");
45430
- currentStep = "获取上传认证";
45431
- task.logger.info("[shipinhaoPublishVideo] 获取上传认证...");
45432
- const auth = await getShipinhaoUploadAuth(cookieString, http);
45433
- currentStep = "解析视频元数据";
45434
- task.logger.info("[shipinhaoPublishVideo] 解析视频元数据...");
45435
- const videoMeta = parseVideoMeta(params.videoPath);
45436
- if (!videoMeta) return utils_response(414, "视频文件解析失败,请检查文件格式(仅支持 MP4)", "");
45818
+ currentStep = "组装视频元数据";
45819
+ const videoMeta = buildVideoMetaFromParams(params.videoPath, params.videoMetadata);
45437
45820
  task.logger.info(`[shipinhaoPublishVideo] 视频: ${videoMeta.width}×${videoMeta.height}, ${videoMeta.duration.toFixed(2)}s, ${(videoMeta.fileSize / 1024 / 1024).toFixed(2)}MB, codec=${videoMeta.codec || "unknown"}`);
45438
45821
  currentStep = "校验视频限制";
45439
45822
  const validationError = validateShipinhaoVideo(params.videoPath, videoMeta);
45440
45823
  if (validationError) {
45441
- task.logger.error(`[shipinhaoPublishVideo] 视频校验未通过: ${validationError}`);
45442
45824
  await updateTaskState?.({
45443
45825
  state: types_TaskState.FAILED,
45444
45826
  error: validationError
45445
45827
  });
45446
45828
  return utils_response(414, validationError, "");
45447
45829
  }
45448
- task.logger.info("[shipinhaoPublishVideo] 视频校验通过");
45449
45830
  currentStep = "校验标题";
45450
45831
  const titleError = validateShipinhaoTitle(params.title);
45451
45832
  if (titleError) {
45452
- task.logger.error(`[shipinhaoPublishVideo] 标题校验未通过: ${titleError}`);
45453
45833
  await updateTaskState?.({
45454
45834
  state: types_TaskState.FAILED,
45455
45835
  error: titleError
@@ -45457,14 +45837,14 @@ var __webpack_exports__ = {};
45457
45837
  return utils_response(414, titleError, "");
45458
45838
  }
45459
45839
  currentStep = "获取 traceKey";
45460
- task.logger.info("[shipinhaoPublishVideo] 获取 traceKey...");
45461
- const traceKey = await getTraceKey(auth, http, task.logger);
45840
+ const traceKey = await getTraceKey(auth, client, microHttp, task.logger);
45841
+ let tagKey = null;
45842
+ if (params.tagInfo) {
45843
+ currentStep = "获取内容声明 tagKey";
45844
+ tagKey = await getObjectTagKey(auth, client, microHttp, task.logger);
45845
+ }
45462
45846
  const uploadStartTime = Math.floor(Date.now() / 1000);
45463
- task.logger.info(`[shipinhaoPublishVideo] 上传开始时间: ${uploadStartTime}`);
45464
45847
  currentStep = "上传视频";
45465
- task.logger.info("[shipinhaoPublishVideo] 上传视频...");
45466
- task.logger.info(`[shipinhaoPublishVideo] 视频路径: ${params.videoPath}`);
45467
- task.logger.info(`[shipinhaoPublishVideo] 视频文件类型: ${auth.videoFileType}`);
45468
45848
  const videoUpload = await uploader_uploadFile({
45469
45849
  filePath: params.videoPath,
45470
45850
  fileType: auth.videoFileType,
@@ -45473,24 +45853,32 @@ var __webpack_exports__ = {};
45473
45853
  http,
45474
45854
  logger: task.logger
45475
45855
  });
45476
- task.logger.info(`[shipinhaoPublishVideo] 视频上传完成,URL: ${videoUpload.downloadUrl}`);
45477
45856
  const uploadEndTime = Math.floor(Date.now() / 1000);
45478
- task.logger.info(`[shipinhaoPublishVideo] 上传结束时间: ${uploadEndTime}, 耗时: ${uploadEndTime - uploadStartTime}s`);
45479
- currentStep = "上传封面";
45480
- task.logger.info("[shipinhaoPublishVideo] 上传封面...");
45481
- task.logger.info(`[shipinhaoPublishVideo] 封面路径: ${params.coverPath}`);
45482
- task.logger.info(`[shipinhaoPublishVideo] 封面文件类型: ${auth.pictureFileType}`);
45857
+ task.logger.info(`[shipinhaoPublishVideo] 耗时: ${uploadEndTime - uploadStartTime}s`);
45858
+ currentStep = "上传横屏封面";
45859
+ const localCoverPath = await resolveLocalCoverPath(params.coverPath, "横屏封面", task.getTmpPath(), task.logger);
45483
45860
  const coverUpload = await uploader_uploadFile({
45484
- filePath: params.coverPath,
45861
+ filePath: localCoverPath,
45485
45862
  fileType: auth.pictureFileType,
45486
45863
  uin: auth.uin,
45487
45864
  authKey: auth.authKey,
45488
45865
  http,
45489
45866
  logger: task.logger
45490
45867
  });
45491
- task.logger.info(`[shipinhaoPublishVideo] 封面上传完成,URL: ${coverUpload.downloadUrl}`);
45868
+ let verticalCoverUpload = coverUpload;
45869
+ if (params.verticalCoverPath) {
45870
+ currentStep = "上传竖屏封面";
45871
+ const localVerticalPath = await resolveLocalCoverPath(params.verticalCoverPath, "竖屏封面", task.getTmpPath(), task.logger);
45872
+ verticalCoverUpload = await uploader_uploadFile({
45873
+ filePath: localVerticalPath,
45874
+ fileType: auth.pictureFileType,
45875
+ uin: auth.uin,
45876
+ authKey: auth.authKey,
45877
+ http,
45878
+ logger: task.logger
45879
+ });
45880
+ }
45492
45881
  currentStep = "提交转码";
45493
- task.logger.info("[shipinhaoPublishVideo] 提交转码...");
45494
45882
  const clipResult = await submitAndPollTranscode({
45495
45883
  videoUrl: videoUpload.downloadUrl,
45496
45884
  videoMeta,
@@ -45498,21 +45886,24 @@ var __webpack_exports__ = {};
45498
45886
  uploadStartTime,
45499
45887
  uploadEndTime,
45500
45888
  finderUsername: auth.finderUsername,
45501
- http,
45889
+ client,
45890
+ http: microHttp,
45502
45891
  logger: task.logger
45503
45892
  });
45504
45893
  currentStep = "发布视频";
45505
- task.logger.info("[shipinhaoPublishVideo] 发布视频...");
45506
- task.logger.info(`[shipinhaoPublishVideo] clipKey: ${clipResult.clipKey}`);
45507
45894
  let publishResult;
45508
45895
  try {
45509
45896
  publishResult = await publishVideo({
45510
45897
  params,
45511
45898
  auth,
45899
+ client,
45512
45900
  clipResult,
45513
45901
  videoUpload,
45514
45902
  coverUpload,
45903
+ verticalCoverUpload,
45904
+ videoMeta,
45515
45905
  traceKey,
45906
+ tagKey,
45516
45907
  uploadStartTime,
45517
45908
  uploadEndTime,
45518
45909
  proxyHttp,
@@ -45524,7 +45915,6 @@ var __webpack_exports__ = {};
45524
45915
  const classified = classifyPublishError(handledError);
45525
45916
  if (classified) {
45526
45917
  const message = `${classified.userMessage}${task.debug ? ` ${http.proxyInfo}` : ""}`;
45527
- task.logger.error(`[shipinhaoPublishVideo] ${classified.category},直接返回: ${handledError.message} (code=${handledError.code})`, stringifyError(handledError));
45528
45918
  await updateTaskState?.({
45529
45919
  state: types_TaskState.FAILED,
45530
45920
  error: message
@@ -45539,12 +45929,10 @@ var __webpack_exports__ = {};
45539
45929
  }
45540
45930
  task.logger.info(`[shipinhaoPublishVideo] publishResult: ${JSON.stringify(publishResult)}`);
45541
45931
  const resultCode = publishResult.data?.baseResp?.errcode ?? publishResult.errCode;
45542
- const resultMsg = publishResult.data?.baseResp?.errmsg ?? "发布成功";
45932
+ const resultMsg = publishResult.data?.baseResp?.errmsg ?? (0 === resultCode ? "发布成功" : `发布失败(errCode=${resultCode})`);
45543
45933
  if (0 === resultCode) {
45544
- task.logger.info("[shipinhaoPublishVideo] 发布成功");
45545
- task.logger.info(`[shipinhaoPublishVideo] 作品ID: ${clipResult.clipKey}`);
45546
- task.logger.info(`[shipinhaoPublishVideo] 视频URL: ${clipResult.url}`);
45547
- task.logger.info(`[shipinhaoPublishVideo] 封面URL: ${coverUpload.downloadUrl}`);
45934
+ const publishId = extractEncFileKey(verticalCoverUpload.downloadUrl);
45935
+ if (!publishId) task.logger.error(`[shipinhaoPublishVideo] 封面 DownloadURL 中未解析到 encfilekey,关联 id 为空: ${verticalCoverUpload.downloadUrl}`);
45548
45936
  await updateTaskState?.({
45549
45937
  state: types_TaskState.SUCCESS,
45550
45938
  result: {
@@ -45560,41 +45948,49 @@ var __webpack_exports__ = {};
45560
45948
  uid: params.uid,
45561
45949
  publishParams: {
45562
45950
  videoPath: params.videoPath,
45563
- coverPath: params.coverPath
45951
+ coverPath: params.coverPath,
45952
+ verticalCoverPath: params.verticalCoverPath,
45953
+ title: params.title,
45954
+ topics: params.topics,
45955
+ mentionedUsers: params.mentionedUsers,
45956
+ collection: params.collection,
45957
+ event: params.event,
45958
+ link: params.link,
45959
+ tagInfo: params.tagInfo,
45960
+ originalFlag: params.originalFlag,
45961
+ scheduledTime: params.scheduledTime
45564
45962
  },
45565
45963
  platform: "shipinhao"
45566
45964
  });
45567
- task.logger.info("[shipinhaoPublishVideo] 日志上报完成");
45568
- return utils_response(0, "发布成功", clipResult.clipKey);
45965
+ return utils_response(0, "发布成功", publishId);
45569
45966
  }
45570
45967
  let errorMessage = resultMsg;
45571
45968
  if (-11224 === resultCode) errorMessage = "视频号管理员完成实名且绑定手机号后才可以发表";
45572
45969
  else if (300333 === resultCode || 300334 === resultCode) errorMessage = "登录失效";
45573
45970
  else if (300330 === resultCode) errorMessage = "未登录";
45971
+ else if (300002 === resultCode) errorMessage = "官方平台在校验音乐/位置/定时信息时失败了,请重新编辑后发布";
45574
45972
  task.logger.error(`[shipinhaoPublishVideo] 发布失败: ${errorMessage} (errCode=${resultCode})`);
45575
45973
  await updateTaskState?.({
45576
45974
  state: types_TaskState.FAILED,
45577
45975
  error: errorMessage
45578
45976
  });
45579
- return utils_response(resultCode || 414, errorMessage, "");
45977
+ return utils_response(414, errorMessage, "");
45580
45978
  } catch (error) {
45581
45979
  const handledError = Http.handleApiError(error);
45582
45980
  const errorMsg = handledError.message || "发布失败,请稍后重试";
45583
- const errorCode = handledError.code || 414;
45584
45981
  task.logger.error(`[shipinhaoPublishVideo] 发布流程异常 [${currentStep}]: ${errorMsg}`, stringifyError(error), handledError.extra);
45585
- task.logger.error(`[shipinhaoPublishVideo] 错误码: ${errorCode}, 当前步骤: ${currentStep}`);
45586
45982
  await updateTaskState?.({
45587
45983
  state: types_TaskState.FAILED,
45588
45984
  error: errorMsg
45589
45985
  });
45590
- return utils_response(errorCode, errorMsg, "");
45986
+ return utils_response(414, errorMsg, "");
45591
45987
  }
45592
45988
  };
45593
45989
  const shipinhaoPublishVideo_rpa_rpaAction = async (task, params)=>{
45594
45990
  task.logger.info("开始微信视频号视频发布(RPA 模式)");
45595
- const videoMeta = parseVideoMeta(params.videoPath);
45596
- if (!videoMeta) return utils_response(414, "视频文件解析失败,请检查文件格式(仅支持 MP4)", "");
45991
+ const videoMeta = buildVideoMetaFromParams(params.videoPath, params.videoMetadata);
45597
45992
  task.logger.info(`视频: ${videoMeta.width}×${videoMeta.height}, ${videoMeta.duration.toFixed(2)}s, ${(videoMeta.fileSize / 1024 / 1024).toFixed(2)}MB, codec=${videoMeta.codec || "unknown"}`);
45993
+ if (!videoMeta.codec) task.logger.warn("未能读取视频编码格式,跳过 H.264 预检,交由服务端判断");
45598
45994
  const validationError = validateShipinhaoVideo(params.videoPath, videoMeta);
45599
45995
  if (validationError) {
45600
45996
  task.logger.error(`视频校验未通过: ${validationError}`);
@@ -45606,6 +46002,17 @@ var __webpack_exports__ = {};
45606
46002
  task.logger.error(`标题校验未通过: ${titleError}`);
45607
46003
  return utils_response(414, titleError, "");
45608
46004
  }
46005
+ const unsupported = [
46006
+ params.verticalCoverPath && "verticalCoverPath",
46007
+ params.collection && "collection",
46008
+ params.originalFlag && "originalFlag",
46009
+ params.postWithMemberZoneLink && "postWithMemberZoneLink"
46010
+ ].filter(Boolean);
46011
+ if (unsupported.length) task.logger.warn(`RPA 模式不支持以下参数,将被忽略: ${unsupported.join("、")};如需生效请使用 mockApi 模式`);
46012
+ if (params.tagInfo && 5 === params.tagInfo.tagType) {
46013
+ const shootInfo = params.tagInfo.shootInfo;
46014
+ if (shootInfo && (shootInfo.provinceCode || shootInfo.cityCode)) task.logger.warn("tagInfo.tagType=5 在 RPA 模式下仅支持拍摄时间和国家选择,省市选择需要使用 mockApi 模式");
46015
+ }
45609
46016
  const tmpCachePath = task.getTmpPath();
45610
46017
  const page = await task.createPage({
45611
46018
  url: "https://channels.weixin.qq.com/platform/post/create",
@@ -45716,7 +46123,12 @@ var __webpack_exports__ = {};
45716
46123
  }
45717
46124
  });
45718
46125
  }
45719
- if (params.description) {
46126
+ const descriptionText = payload_buildDescription({
46127
+ description: params.description,
46128
+ topics: params.topics,
46129
+ mentionedUsers: params.mentionedUsers
46130
+ });
46131
+ if (descriptionText) {
45720
46132
  task.logger.info("填写视频描述");
45721
46133
  await retryAction(async ()=>{
45722
46134
  const descEditor = await waitForElement(".input-editor", 10000);
@@ -45728,7 +46140,7 @@ var __webpack_exports__ = {};
45728
46140
  });
45729
46141
  await page.waitForTimeout(300);
45730
46142
  task.logger.info("已清空编辑器内容");
45731
- await descEditor.pressSequentially(params.description, {
46143
+ await descEditor.pressSequentially(descriptionText, {
45732
46144
  delay: 10
45733
46145
  });
45734
46146
  await page.waitForTimeout(500);
@@ -45763,7 +46175,59 @@ var __webpack_exports__ = {};
45763
46175
  await poperInstance.nth(1).click();
45764
46176
  task.logger.info("地点选择完成");
45765
46177
  }
45766
- if (params.scheduleTime) {
46178
+ if (params.collection) {
46179
+ task.logger.info(`选择合集: ${params.collection.collectionName}`);
46180
+ const instanceCollection = page.locator(".post-album-display-wrap");
46181
+ await instanceCollection.click();
46182
+ await page.waitForTimeout(1000);
46183
+ page.locator(".post-album-wrap .option-item").filter({
46184
+ hasText: params.collection.collectionName
46185
+ }).first().click({
46186
+ force: true
46187
+ });
46188
+ }
46189
+ if (params.link) {
46190
+ task.logger.info(`设置扩展阅读链接: ${params.link.title}`);
46191
+ await page.locator(".post-link-wrap .link-display-wrap").click();
46192
+ await page.waitForTimeout(300);
46193
+ const linkTypeText = 2 === params.link.urlType ? "红包封面" : "公众号文章";
46194
+ await page.locator(".link-option-item .title-wrap span").filter({
46195
+ hasText: linkTypeText
46196
+ }).click();
46197
+ await page.waitForTimeout(300);
46198
+ const placeholder = 2 === params.link.urlType ? "粘贴红包封面链接" : "粘贴公众号文章链接";
46199
+ await page.locator(`.link-input-wrap input[placeholder="${placeholder}"]`).fill(params.link.link);
46200
+ await page.waitForTimeout(500);
46201
+ task.logger.info(`已设置${linkTypeText}: ${params.link.link}`);
46202
+ }
46203
+ if (params.event) {
46204
+ task.logger.info(`选择活动: ${params.event.eventName}`);
46205
+ await page.locator(".post-activity-wrap .activity-display").click();
46206
+ await page.waitForTimeout(500);
46207
+ await page.locator(".activity-filter-wrap .weui-desktop-form__input[placeholder='搜索活动']").fill(params.event.eventName);
46208
+ await page.waitForTimeout(500);
46209
+ const searchLoading = page.locator(".search-loading");
46210
+ await searchLoading.waitFor({
46211
+ state: "hidden",
46212
+ timeout: 5000
46213
+ }).catch(()=>{
46214
+ task.logger.warn("活动搜索加载超时,继续尝试选择");
46215
+ });
46216
+ const activityItem = page.locator(".option-item .activity-item .activity-item-info .name").filter({
46217
+ hasText: params.event.eventName
46218
+ });
46219
+ const count = await activityItem.count();
46220
+ if (count > 0) {
46221
+ await activityItem.first().click();
46222
+ await page.waitForTimeout(300);
46223
+ task.logger.info(`已选择活动: ${params.event.eventName}`);
46224
+ } else {
46225
+ task.logger.warn(`未找到活动: ${params.event.eventName},将不参与活动`);
46226
+ await page.locator(".post-activity-wrap .activity-display").click();
46227
+ await page.waitForTimeout(300);
46228
+ }
46229
+ }
46230
+ if (params.scheduledTime) {
45767
46231
  task.logger.info("设置定时发布");
45768
46232
  const timingRadio = page.locator(".weui-desktop-form__check-label").filter({
45769
46233
  hasNotText: "不定时"
@@ -45772,10 +46236,10 @@ var __webpack_exports__ = {};
45772
46236
  await page.waitForTimeout(500);
45773
46237
  const instance = page.locator(".weui-desktop-picker__date");
45774
46238
  await instance.click();
45775
- const dateD = utils_TimeFormatter.format(1000 * params.scheduleTime, "d");
46239
+ const dateD = utils_TimeFormatter.format(1000 * params.scheduledTime, "d");
45776
46240
  const nowMonth = utils_TimeFormatter.format(Date.now(), "MM月");
45777
46241
  const nowMonthText = utils_TimeFormatter.format(Date.now(), "M月");
45778
- const month = utils_TimeFormatter.format(1000 * params.scheduleTime, "MM月");
46242
+ const month = utils_TimeFormatter.format(1000 * params.scheduledTime, "MM月");
45779
46243
  const monthLocator = await page.locator("weui-desktop-picker__panel__label").filter({
45780
46244
  hasText: month
45781
46245
  }).first();
@@ -45792,14 +46256,85 @@ var __webpack_exports__ = {};
45792
46256
  await page.locator(".weui-desktop-picker__table-row td a").filter({
45793
46257
  hasText: dateD
45794
46258
  }).first().click();
45795
- await page.locator(".weui-desktop-form__input-wrp input[placeholder*='请选择时间']").fill(utils_TimeFormatter.format(1000 * params.scheduleTime, "hh:mm"));
46259
+ await page.locator(".weui-desktop-form__input-wrp input[placeholder*='请选择时间']").fill(utils_TimeFormatter.format(1000 * params.scheduledTime, "hh:mm"));
45796
46260
  await page.locator("i.weui-desktop-icon__time").click();
45797
46261
  await page.locator(".post-time-wrap .form-item .label").filter({
45798
46262
  hasText: "发表时间"
45799
46263
  }).click();
45800
46264
  }
46265
+ if (params.tagInfo) {
46266
+ task.logger.info(`设置视频标注: tagType=${params.tagInfo.tagType}`);
46267
+ await page.locator(".mark-tag-select").click();
46268
+ await page.waitForTimeout(300);
46269
+ const tagTypeTextMap = {
46270
+ 0: "无需标注",
46271
+ 1: "含AI生成内容",
46272
+ 2: "内容包含营销广告",
46273
+ 3: "内容为虚构剧情,仅供娱乐",
46274
+ 5: "内容为自行拍摄",
46275
+ 7: "内容为转载",
46276
+ 8: "个人观点,仅供参考"
46277
+ };
46278
+ const tagText = tagTypeTextMap[params.tagInfo.tagType];
46279
+ if (tagText) {
46280
+ await page.locator(".mark-tag-option .option-main").filter({
46281
+ hasText: tagText
46282
+ }).click();
46283
+ await page.waitForTimeout(300);
46284
+ if (5 === params.tagInfo.tagType) {
46285
+ const shootInfo = params.tagInfo.shootInfo;
46286
+ if (shootInfo) {
46287
+ task.logger.info("填写拍摄时间和地点...");
46288
+ await page.waitForTimeout(500);
46289
+ if (shootInfo.postTimestamp) {
46290
+ task.logger.info(`设置拍摄时间: ${shootInfo.postTimestamp}`);
46291
+ const timestamp = 1000 * parseInt(shootInfo.postTimestamp, 10);
46292
+ const date = new Date(timestamp);
46293
+ await page.locator(".original-dialog-content .weui-desktop-picker__date input[placeholder*='请选择拍摄时间']").click();
46294
+ await page.waitForTimeout(300);
46295
+ const dayNum = date.getDate();
46296
+ await page.locator(".weui-desktop-picker__table a").filter({
46297
+ hasText: new RegExp(`^\\s*${dayNum}\\s*$`)
46298
+ }).first().click();
46299
+ await page.waitForTimeout(300);
46300
+ }
46301
+ if (shootInfo.countryCode || shootInfo.provinceCode || shootInfo.cityCode) {
46302
+ task.logger.info("设置拍摄地点...");
46303
+ await page.locator(".original-dialog-content .weui-desktop-form__dropdowncascade .weui-desktop-form__dropdowncascade__dt").click();
46304
+ await page.waitForTimeout(300);
46305
+ if (1156 === shootInfo.countryCode) {
46306
+ await page.locator(".weui-desktop-dropdown__list-ele .weui-desktop-dropdown__list-ele__text").filter({
46307
+ hasText: "中国"
46308
+ }).click();
46309
+ await page.waitForTimeout(300);
46310
+ task.logger.warn("RPA 模式下暂不支持选择具体省份和城市,仅选择了国家");
46311
+ } else task.logger.warn(`不支持的国家代码: ${shootInfo.countryCode},跳过地点设置`);
46312
+ }
46313
+ const confirmBtn = await waitForElement(".weui-desktop-dialog .weui-desktop-btn_primary");
46314
+ if (confirmBtn) {
46315
+ await confirmBtn.click();
46316
+ await page.waitForTimeout(300);
46317
+ }
46318
+ } else task.logger.warn("tagType=5 需要提供 shootInfo 字段(拍摄时间和地点)");
46319
+ }
46320
+ if (7 === params.tagInfo.tagType) {
46321
+ const repostSource = params.tagInfo.repostSource;
46322
+ if (repostSource) {
46323
+ task.logger.info(`填写转载来源: ${repostSource}`);
46324
+ await page.waitForTimeout(500);
46325
+ await page.locator(".repost-dialog-content .repost-textarea").fill(repostSource);
46326
+ await page.waitForTimeout(300);
46327
+ const confirmBtn = await waitForElement(".weui-desktop-dialog .weui-desktop-btn_primary");
46328
+ if (confirmBtn) {
46329
+ await confirmBtn.click();
46330
+ await page.waitForTimeout(300);
46331
+ }
46332
+ } else task.logger.warn("tagType=7 需要提供 repostSource 字段(转载来源)");
46333
+ }
46334
+ } else task.logger.warn(`未知的 tagType: ${params.tagInfo.tagType},跳过标注设置`);
46335
+ }
45801
46336
  task.logger.info("准备发布...");
45802
- await page.waitForTimeout(300);
46337
+ await page.waitForTimeout(500);
45803
46338
  let videoId = "";
45804
46339
  const handleResponse = async (response)=>{
45805
46340
  const url = response.url();
@@ -45865,19 +46400,60 @@ var __webpack_exports__ = {};
45865
46400
  };
45866
46401
  const ShipinhaoPublishVideoParamsSchema = ActionCommonParamsSchema.extend({
45867
46402
  videoPath: classic_schemas_string().min(1),
46403
+ videoMetadata: classic_schemas_object({
46404
+ duration: classic_schemas_number().positive(),
46405
+ width: classic_schemas_number().int().positive(),
46406
+ height: classic_schemas_number().int().positive(),
46407
+ fileSize: classic_schemas_number().int().positive(),
46408
+ path: classic_schemas_string().min(1).optional(),
46409
+ fileName: classic_schemas_string().min(1)
46410
+ }),
45868
46411
  coverPath: classic_schemas_string().min(1),
46412
+ verticalCoverPath: classic_schemas_string().min(1).optional(),
45869
46413
  description: classic_schemas_string(),
45870
46414
  title: classic_schemas_string().optional(),
45871
- scheduleTime: classic_schemas_number().int().positive().optional(),
46415
+ scheduledTime: classic_schemas_number().int().positive().optional(),
46416
+ isImmediatelyPublish: classic_schemas_boolean().optional(),
46417
+ topics: classic_schemas_array(classic_schemas_string()).optional(),
46418
+ mentionedUsers: classic_schemas_array(classic_schemas_object({
46419
+ nickname: classic_schemas_string()
46420
+ })).optional(),
46421
+ collection: classic_schemas_object({
46422
+ collectionId: classic_schemas_string(),
46423
+ collectionName: classic_schemas_string()
46424
+ }).optional(),
46425
+ event: classic_schemas_object({
46426
+ eventTopicId: classic_schemas_string(),
46427
+ eventName: classic_schemas_string(),
46428
+ eventCreatorNickname: classic_schemas_string().optional()
46429
+ }).optional(),
46430
+ link: classic_schemas_object({
46431
+ link: classic_schemas_string(),
46432
+ title: classic_schemas_string(),
46433
+ urlType: classic_schemas_number().int().default(1)
46434
+ }).optional(),
46435
+ tagInfo: looseObject({
46436
+ tagType: classic_schemas_number().int()
46437
+ }).optional(),
46438
+ originalFlag: schemas_union([
46439
+ literal(0),
46440
+ literal(1)
46441
+ ]).optional(),
46442
+ postWithMemberZoneLink: schemas_union([
46443
+ literal(0),
46444
+ literal(1)
46445
+ ]).optional(),
45872
46446
  location: classic_schemas_object({
45873
46447
  latitude: classic_schemas_number(),
45874
46448
  longitude: classic_schemas_number(),
45875
46449
  city: classic_schemas_string(),
46450
+ poiName: classic_schemas_string().optional(),
46451
+ address: classic_schemas_string().optional(),
45876
46452
  poiClassifyId: classic_schemas_string().optional()
45877
46453
  }).optional()
45878
46454
  });
45879
46455
  const shipinhaoPublishVideo = async (task, params)=>{
45880
- task.logger.info(`shipinhaoPublishVideo actionType: ${params.actionType}`);
46456
+ task.logger.info(`[shipinhaoPublishVideo] actionType: ${params.actionType}`);
45881
46457
  if ("rpa" === params.actionType) return shipinhaoPublishVideo_rpa_rpaAction(task, params);
45882
46458
  if ("mockApi" === params.actionType) return shipinhaoPublishVideo_mock_mockAction(task, params);
45883
46459
  return executeAction(shipinhaoPublishVideo_mock_mockAction, shipinhaoPublishVideo_rpa_rpaAction)(task, params);
@@ -51979,4 +52555,4 @@ if (__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_targe
51979
52555
  });
51980
52556
 
51981
52557
  //# sourceMappingURL=bundle.js.map
51982
- //# debugId=1e50231c-4e0f-5db5-a554-c11a95d7391d
52558
+ //# debugId=bf64112a-57bc-5eae-9ede-8d23fc55671f