oicq-icalingua-plus-plus 1.26.29 → 1.26.30

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/index.d.ts CHANGED
@@ -975,6 +975,22 @@ export interface FlashTransferUploadSource {
975
975
  sha1: Buffer | string;
976
976
  }
977
977
 
978
+ /** 客户端分片上传所需的凭证。 */
979
+ export interface FlashTransferUploadCredential {
980
+ ukey: string;
981
+ appid: number;
982
+ file_id: string;
983
+ }
984
+
985
+ /** 由远程 QQ 客户端执行控制操作,文件内容在上传方本地传输。 */
986
+ export interface FlashTransferUploadController<T extends FlashTransferUploadCredential = FlashTransferUploadCredential> {
987
+ create(name: string, options?: FlashTransferCreateOptions): Promise<FlashTransferTask>;
988
+ addFiles(task: FlashTransferTask, files: FlashTransferMetadata[]): Promise<FlashTransferFile[]>;
989
+ applyUpload(task: FlashTransferTask, file: FlashTransferFile, source: FlashTransferUploadSource): Promise<T>;
990
+ completeFile(ticket: T): Promise<void>;
991
+ submit(task_id: string): Promise<void>;
992
+ }
993
+
978
994
  /** 由 applyUpload 返回,保留服务端的 IndexNode、存储参数和业务透传信息。 */
979
995
  export interface FlashTransferUploadTicket {
980
996
  upload: Record<number, any>;
@@ -1033,6 +1049,8 @@ export interface FlashTransferDownload {
1033
1049
  /** 与 Gfs 一样,以下方法会 reject;控制接口保留官网的 snake_case 字段。 */
1034
1050
  export class FlashTransfer {
1035
1051
  private constructor();
1052
+ /** 文件在本机分片上传,创建、登记及完成提交由 control 执行;无需本机登录 QQ */
1053
+ static upload<T extends FlashTransferUploadCredential>(files: FlashTransferUploadInput | FlashTransferUploadInput[], control: FlashTransferUploadController<T>, name?: string, callback?: (percentage: string) => void, options?: FlashTransferCreateOptions & { timeout?: number }): Promise<FlashTransferUploadResult>;
1036
1054
  /** 分享码或 https://qfile.qq.com/q/... 链接转换为文件集 ID */
1037
1055
  resolve(code: string): Promise<string>;
1038
1056
  /** 原生创建文件集,需要在线客户端;保留 task_id 用于完成提交 */
@@ -1042,7 +1060,7 @@ export class FlashTransfer {
1042
1060
  /** 分批登记目录及已计算摘要的文件,返回顺序与请求一致 */
1043
1061
  addFiles(task: Pick<FlashTransferTask, "fileset_id" | "task_id">, files: FlashTransferMetadata[]): Promise<FlashTransferFile[]>;
1044
1062
  /** 底层上传申请;文件必须是 addFiles 返回的节点,source 提供其名称、大小及摘要 */
1045
- applyUpload(task: Pick<FlashTransferTask, "fileset_id">, file: FlashTransferFile, source: FlashTransferUploadSource): Promise<FlashTransferUploadTicket>;
1063
+ applyUpload(task: Pick<FlashTransferTask, "fileset_id">, file: FlashTransferFile, source: FlashTransferUploadSource): Promise<FlashTransferUploadTicket & FlashTransferUploadCredential>;
1046
1064
  /** 分片上传或秒传成功后同步单个文件,随后才能提交整个任务 */
1047
1065
  completeFile(ticket: FlashTransferUploadTicket): Promise<void>;
1048
1066
  /** 文件级完成同步之后提交整个任务,参数是 task_id */
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  const { Client } = require("./lib/oicq");
3
+ const { FlashTransfer } = require("./lib/message/qfile");
3
4
  const { segment, cqcode } = require("./util");
4
5
  const { checkUin, NOOP } = require("./lib/common");
5
6
  const pb = require("./lib/algo/pb");
@@ -20,7 +21,7 @@ const constants = {
20
21
  }
21
22
 
22
23
  module.exports = {
23
- Client, createClient,
24
+ Client, createClient, FlashTransfer,
24
25
  segment, cqcode,
25
26
  /** @deprecated */
26
27
  setGlobalConfig: NOOP,
@@ -236,10 +236,11 @@ class FlashTransfer {
236
236
  * @param {Pick<import("../../").FlashTransferTask, "fileset_id">} task
237
237
  * @param {import("../../").FlashTransferFile} file
238
238
  * @param {import("../../").FlashTransferUploadSource} source
239
- * @returns {Promise<import("../../").FlashTransferUploadTicket>}
239
+ * @returns {Promise<import("../../").FlashTransferUploadTicket & import("../../").FlashTransferUploadCredential>}
240
240
  */
241
- applyUpload(task, file, source) {
242
- return native.applyUpload(this.c, source, native.getBizTransInfo(task, file), file.is_cover ? 23 : 22);
241
+ async applyUpload(task, file, source) {
242
+ const ticket = await native.applyUpload(this.c, source, native.getBizTransInfo(task, file), file.is_cover ? 23 : 22);
243
+ return { ...ticket, file_id: String(ticket.info[1][2]) };
243
244
  }
244
245
 
245
246
  /**
@@ -260,6 +261,39 @@ class FlashTransfer {
260
261
  */
261
262
  async upload(files, name, callback, options = {}) {
262
263
  native.checkOnline(this.c);
264
+ return this._upload(files, name, callback, options, this);
265
+ }
266
+
267
+ /**
268
+ * 文件在本机读取和上传,控制操作由调用方提供;无需在本机登录 QQ。
269
+ * @param {import("../../").FlashTransferUploadInput | import("../../").FlashTransferUploadInput[]} files
270
+ * @template {import("../../").FlashTransferUploadCredential} T
271
+ * @param {import("../../").FlashTransferUploadController<T>} control
272
+ * @param {string} [name]
273
+ * @param {(percentage: string) => void} [callback]
274
+ * @param {import("../../").FlashTransferCreateOptions & {timeout?: number}} [options]
275
+ * @returns {Promise<import("../../").FlashTransferUploadResult>}
276
+ */
277
+ static async upload(files, control, name, callback, options = {}) {
278
+ for (const method of ["create", "addFiles", "applyUpload", "completeFile", "submit"]) {
279
+ if (typeof control?.[method] !== "function")
280
+ throw new TypeError("upload controller has no " + method);
281
+ }
282
+ const { timeout, ...createOptions } = options;
283
+ return new FlashTransfer(undefined, { timeout })._upload(files, name, callback, createOptions, control);
284
+ }
285
+
286
+ /**
287
+ * @private
288
+ * @param {import("../../").FlashTransferUploadInput | import("../../").FlashTransferUploadInput[]} files
289
+ * @param {string} name
290
+ * @param {(percentage: string) => void} callback
291
+ * @param {import("../../").FlashTransferCreateOptions} options
292
+ * @template {import("../../").FlashTransferUploadCredential} T
293
+ * @param {import("../../").FlashTransferUploadController<T>} control
294
+ * @returns {Promise<import("../../").FlashTransferUploadResult>}
295
+ */
296
+ async _upload(files, name, callback, options, control) {
263
297
  if (callback !== undefined && typeof callback !== "function")
264
298
  throw new TypeError("upload callback must be a function");
265
299
  if (name !== undefined)
@@ -341,14 +375,14 @@ class FlashTransfer {
341
375
  }
342
376
  stage = "create";
343
377
  const total = [...sources.values()].reduce((size, source) => size + source.size, 0);
344
- task = await this.create(name || metadata[0].name, {
378
+ task = await control.create(name || metadata[0].name, {
345
379
  ...options,
346
380
  total_file_count: sources.size,
347
381
  total_file_size: total,
348
382
  first_level_item_count: metadata.filter(file => !file.parent_id).length,
349
383
  });
350
384
  stage = "add_files";
351
- const added = await this.addFiles(task, metadata);
385
+ const added = await control.addFiles(task, metadata);
352
386
  let transferred = 0, completed = 0;
353
387
  for (const file of added) {
354
388
  if (file.is_dir)
@@ -357,7 +391,7 @@ class FlashTransfer {
357
391
  if (!source)
358
392
  throw new FlashTransferError(-1, "upload response has an unknown client file id");
359
393
  stage = "apply_upload";
360
- const ticket = await this.applyUpload(task, file, source);
394
+ const ticket = await control.applyUpload(task, file, source);
361
395
  stage = "upload";
362
396
  if (ticket.ukey && source.size) {
363
397
  await uploadFile(source, ticket.ukey, ticket.appid, this._http, callback ? percentage => {
@@ -366,15 +400,15 @@ class FlashTransfer {
366
400
  }
367
401
  await source.close();
368
402
  stage = "complete_file";
369
- await this.completeFile(ticket);
370
- file.physical = { id: String(ticket.info[1][2]), status: 2 };
403
+ await control.completeFile(ticket);
404
+ file.physical = { id: String(ticket.file_id), status: 2 };
371
405
  transferred += source.size;
372
406
  ++completed;
373
407
  if (callback)
374
408
  callback((total ? transferred / total * 100 : completed / sources.size * 100).toFixed(2));
375
409
  }
376
410
  stage = "submit";
377
- await this.submit(task.task_id);
411
+ await control.submit(task.task_id);
378
412
  return { ...task, files: added };
379
413
  } catch (error) {
380
414
  if (error instanceof Error) {
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "oicq-icalingua-plus-plus",
3
3
  "app_name": "oicq-icalingua-plus-plus",
4
- "version": "1.26.29",
5
- "app_version": "1.26.29",
4
+ "version": "1.26.30",
5
+ "app_version": "1.26.30",
6
6
  "upday": "2026/11/14",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",