oicq-icalingua-plus-plus 1.26.28 → 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 +26 -2
- package/index.js +2 -1
- package/lib/message/qfile.js +58 -16
- package/package.json +2 -2
package/index.d.ts
CHANGED
|
@@ -960,7 +960,13 @@ export interface FlashTransferMetadata {
|
|
|
960
960
|
file_count?: number;
|
|
961
961
|
}
|
|
962
962
|
|
|
963
|
-
export
|
|
963
|
+
export interface FlashTransferUploadDirectory {
|
|
964
|
+
name: string;
|
|
965
|
+
/** 虚拟目录的子项;文件仍从各自的源路径读取 */
|
|
966
|
+
children: FlashTransferUploadInput[];
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
export type FlashTransferUploadInput = MediaFile | { file: MediaFile; name?: string } | FlashTransferUploadDirectory;
|
|
964
970
|
|
|
965
971
|
export interface FlashTransferUploadSource {
|
|
966
972
|
name: string;
|
|
@@ -969,6 +975,22 @@ export interface FlashTransferUploadSource {
|
|
|
969
975
|
sha1: Buffer | string;
|
|
970
976
|
}
|
|
971
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
|
+
|
|
972
994
|
/** 由 applyUpload 返回,保留服务端的 IndexNode、存储参数和业务透传信息。 */
|
|
973
995
|
export interface FlashTransferUploadTicket {
|
|
974
996
|
upload: Record<number, any>;
|
|
@@ -1027,6 +1049,8 @@ export interface FlashTransferDownload {
|
|
|
1027
1049
|
/** 与 Gfs 一样,以下方法会 reject;控制接口保留官网的 snake_case 字段。 */
|
|
1028
1050
|
export class FlashTransfer {
|
|
1029
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>;
|
|
1030
1054
|
/** 分享码或 https://qfile.qq.com/q/... 链接转换为文件集 ID */
|
|
1031
1055
|
resolve(code: string): Promise<string>;
|
|
1032
1056
|
/** 原生创建文件集,需要在线客户端;保留 task_id 用于完成提交 */
|
|
@@ -1036,7 +1060,7 @@ export class FlashTransfer {
|
|
|
1036
1060
|
/** 分批登记目录及已计算摘要的文件,返回顺序与请求一致 */
|
|
1037
1061
|
addFiles(task: Pick<FlashTransferTask, "fileset_id" | "task_id">, files: FlashTransferMetadata[]): Promise<FlashTransferFile[]>;
|
|
1038
1062
|
/** 底层上传申请;文件必须是 addFiles 返回的节点,source 提供其名称、大小及摘要 */
|
|
1039
|
-
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>;
|
|
1040
1064
|
/** 分片上传或秒传成功后同步单个文件,随后才能提交整个任务 */
|
|
1041
1065
|
completeFile(ticket: FlashTransferUploadTicket): Promise<void>;
|
|
1042
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,
|
package/lib/message/qfile.js
CHANGED
|
@@ -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
|
-
|
|
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
|
/**
|
|
@@ -251,7 +252,7 @@ class FlashTransfer {
|
|
|
251
252
|
}
|
|
252
253
|
|
|
253
254
|
/**
|
|
254
|
-
*
|
|
255
|
+
* 本地文件、目录、虚拟目录或 Buffer 的完整上传;直到文件同步和任务提交均成功才返回。
|
|
255
256
|
* @param {import("../../").FlashTransferUploadInput | import("../../").FlashTransferUploadInput[]} files
|
|
256
257
|
* @param {string} [name]
|
|
257
258
|
* @param {(percentage: string) => void} [callback]
|
|
@@ -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)
|
|
@@ -274,7 +308,7 @@ class FlashTransfer {
|
|
|
274
308
|
let task;
|
|
275
309
|
let stage = "hash";
|
|
276
310
|
/**
|
|
277
|
-
* @param {import("../../").MediaFile} file
|
|
311
|
+
* @param {import("../../").MediaFile | import("../../").FlashTransferUploadDirectory} file
|
|
278
312
|
* @param {string} [file_name]
|
|
279
313
|
* @param {string} [parent_id]
|
|
280
314
|
* @param {number} [depth=1]
|
|
@@ -283,9 +317,15 @@ class FlashTransfer {
|
|
|
283
317
|
const collect = async (file, file_name, parent_id = "", depth = 1) => {
|
|
284
318
|
if (file_name !== undefined)
|
|
285
319
|
file_name = checkString(file_name, "file name");
|
|
320
|
+
const virtual = !!file && typeof file === "object" && "children" in file;
|
|
321
|
+
if (virtual) {
|
|
322
|
+
if (!Array.isArray(file.children))
|
|
323
|
+
throw new TypeError("directory children must be an array");
|
|
324
|
+
file_name = checkString(file_name || file.name, "directory name");
|
|
325
|
+
}
|
|
286
326
|
const binary = file instanceof Uint8Array || file instanceof ArrayBuffer || file instanceof SharedArrayBuffer;
|
|
287
327
|
let stat;
|
|
288
|
-
if (!binary) {
|
|
328
|
+
if (!virtual && !binary) {
|
|
289
329
|
if (typeof file !== "string")
|
|
290
330
|
throw new TypeError("upload source must be a local path or binary data");
|
|
291
331
|
file = path.resolve(file);
|
|
@@ -298,17 +338,19 @@ class FlashTransfer {
|
|
|
298
338
|
cli_fileid, parent_id, depth,
|
|
299
339
|
name: file_name || (!binary ? path.basename(file) : ""),
|
|
300
340
|
cli_file_index: metadata.length + 1,
|
|
301
|
-
is_dir: !!stat?.isDirectory(),
|
|
341
|
+
is_dir: virtual || !!stat?.isDirectory(),
|
|
302
342
|
file_size: 0,
|
|
303
343
|
file_count: 0,
|
|
304
344
|
};
|
|
305
345
|
metadata.push(meta);
|
|
306
346
|
if (meta.is_dir) {
|
|
307
347
|
meta.file_type = 25;
|
|
308
|
-
const children = await fs.promises.readdir(file, { withFileTypes: true })
|
|
309
|
-
|
|
348
|
+
const children = virtual ? file.children : (await fs.promises.readdir(file, { withFileTypes: true }))
|
|
349
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
350
|
+
.map(child => ({ file: path.join(file, child.name), name: child.name }));
|
|
310
351
|
for (const child of children) {
|
|
311
|
-
const
|
|
352
|
+
const input = child && typeof child === "object" && "file" in child ? child : { file: child };
|
|
353
|
+
const entry = await collect(input.file, input.name, cli_fileid, depth + 1);
|
|
312
354
|
meta.file_size += entry.file_size;
|
|
313
355
|
++meta.file_count;
|
|
314
356
|
}
|
|
@@ -333,14 +375,14 @@ class FlashTransfer {
|
|
|
333
375
|
}
|
|
334
376
|
stage = "create";
|
|
335
377
|
const total = [...sources.values()].reduce((size, source) => size + source.size, 0);
|
|
336
|
-
task = await
|
|
378
|
+
task = await control.create(name || metadata[0].name, {
|
|
337
379
|
...options,
|
|
338
380
|
total_file_count: sources.size,
|
|
339
381
|
total_file_size: total,
|
|
340
382
|
first_level_item_count: metadata.filter(file => !file.parent_id).length,
|
|
341
383
|
});
|
|
342
384
|
stage = "add_files";
|
|
343
|
-
const added = await
|
|
385
|
+
const added = await control.addFiles(task, metadata);
|
|
344
386
|
let transferred = 0, completed = 0;
|
|
345
387
|
for (const file of added) {
|
|
346
388
|
if (file.is_dir)
|
|
@@ -349,7 +391,7 @@ class FlashTransfer {
|
|
|
349
391
|
if (!source)
|
|
350
392
|
throw new FlashTransferError(-1, "upload response has an unknown client file id");
|
|
351
393
|
stage = "apply_upload";
|
|
352
|
-
const ticket = await
|
|
394
|
+
const ticket = await control.applyUpload(task, file, source);
|
|
353
395
|
stage = "upload";
|
|
354
396
|
if (ticket.ukey && source.size) {
|
|
355
397
|
await uploadFile(source, ticket.ukey, ticket.appid, this._http, callback ? percentage => {
|
|
@@ -358,15 +400,15 @@ class FlashTransfer {
|
|
|
358
400
|
}
|
|
359
401
|
await source.close();
|
|
360
402
|
stage = "complete_file";
|
|
361
|
-
await
|
|
362
|
-
file.physical = { id: String(ticket.
|
|
403
|
+
await control.completeFile(ticket);
|
|
404
|
+
file.physical = { id: String(ticket.file_id), status: 2 };
|
|
363
405
|
transferred += source.size;
|
|
364
406
|
++completed;
|
|
365
407
|
if (callback)
|
|
366
408
|
callback((total ? transferred / total * 100 : completed / sources.size * 100).toFixed(2));
|
|
367
409
|
}
|
|
368
410
|
stage = "submit";
|
|
369
|
-
await
|
|
411
|
+
await control.submit(task.task_id);
|
|
370
412
|
return { ...task, files: added };
|
|
371
413
|
} catch (error) {
|
|
372
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.
|
|
5
|
-
"app_version": "1.26.
|
|
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++",
|