oicq-icalingua-plus-plus 1.26.30 → 1.26.31

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/http-api/api.js CHANGED
@@ -17,6 +17,7 @@ const available_actions = [
17
17
  "deleteFavoriteGroup",
18
18
  "setFavoriteGroup",
19
19
  "getFavoriteSpace",
20
+ "getFavoriteFileUrl",
20
21
 
21
22
  "sendPrivateMsg",
22
23
  "sendGroupMsg",
package/index.d.ts CHANGED
@@ -1550,6 +1550,28 @@ export interface FavoriteItemResults {
1550
1550
  results: { id: string | number; result: number }[];
1551
1551
  }
1552
1552
 
1553
+ /** 收藏文件资源的临时下载凭据;headers 应随资源请求发送。 */
1554
+ export interface FavoriteDownload {
1555
+ /** 收藏 ID。 */
1556
+ cid: string;
1557
+ /** 文件 ID。 */
1558
+ fid: string;
1559
+ /** 收藏的原始协议类型,例如 5 视频、6 文件。 */
1560
+ type: number;
1561
+ /** 原始文件名。 */
1562
+ name: string;
1563
+ /** 文件大小,单位为字节;大整数以十进制字符串表示。 */
1564
+ size: FavoriteInteger;
1565
+ /** 临时资源地址,过期后应重新调用 getFavoriteFileUrl。 */
1566
+ url: string;
1567
+ /** 资源专用鉴权头,应与 url 一起使用。 */
1568
+ headers: Record<string, string>;
1569
+ /** 服务端是否要求下载后重命名文件。 */
1570
+ rename_file: boolean;
1571
+ /** 服务端返回的风险提示。 */
1572
+ risk_tips: string;
1573
+ }
1574
+
1553
1575
  /**
1554
1576
  * 方法不会reject或抛出异常,使用retcode判断是否成功
1555
1577
  * @see {Ret}
@@ -1812,7 +1834,7 @@ export class Client extends EventEmitter {
1812
1834
  /**
1813
1835
  * 编辑收藏,先读取详情再合并指定字段,保留其余元数据和资源。
1814
1836
  * @param cid 收藏 ID。
1815
- * @param changes 需要修改的字段;type=1 支持文本正文,type=6 支持链接字段,type=8 支持 HTML。
1837
+ * @param changes 需要修改的字段;type=1 支持文本正文,type=2 支持链接字段,type=8 支持 HTML。
1816
1838
  * @returns 收藏 ID 与修改时间;其他类型支持元数据编辑。
1817
1839
  */
1818
1840
  editFavorite(cid: string, changes: FavoriteChanges): Promise<Ret<{ cid: string; modify_time: FavoriteInteger }>>;
@@ -1860,6 +1882,15 @@ export class Client extends EventEmitter {
1860
1882
  */
1861
1883
  setFavoriteGroup(cid: string | string[], custom_group_id: number): Promise<Ret<FavoriteItemResults>>;
1862
1884
 
1885
+ /**
1886
+ * 获取收藏文件或视频的临时下载地址,需同时使用返回的资源鉴权头。
1887
+ * 语音与高清音频存储为内联音频数据,不适用此接口。
1888
+ * @param cid 收藏 ID。
1889
+ * @param fid 文件 ID;多附件收藏必须指定。
1890
+ * @returns 下载链接、文件信息、资源鉴权头及服务端重命名要求。
1891
+ */
1892
+ getFavoriteFileUrl(cid: string, fid?: string): Promise<Ret<FavoriteDownload>>;
1893
+
1863
1894
  /**
1864
1895
  * 获取收藏空间使用情况。
1865
1896
  * @returns 空间总量 total_space 与已用量 used_space,单位为字节。
package/lib/client.js CHANGED
@@ -773,6 +773,16 @@ class Client extends EventEmitter {
773
773
  setFavoriteGroup(cid, custom_group_id) {
774
774
  return this._useProtocol(favorites.setFavoriteGroup, arguments);
775
775
  }
776
+ /**
777
+ * 获取收藏视频或文件的临时下载地址及资源鉴权头。
778
+ * @param {string} cid 收藏 ID
779
+ * @param {string} [fid] 文件 ID,多附件收藏需指定
780
+ * @returns {Promise<import("../").Ret<import("../").FavoriteDownload>>}
781
+ */
782
+ getFavoriteFileUrl(cid, fid) {
783
+ return this._useProtocol(favorites.getFavoriteFileUrl, arguments);
784
+ }
785
+
776
786
  /**
777
787
  * 获取收藏空间总量与已用量,单位为字节。
778
788
  * @returns {Promise<import("../").Ret<{total_space: import("../").FavoriteInteger, used_space: import("../").FavoriteInteger}>>}
@@ -307,7 +307,7 @@ async function editFavorite(cid, changes) {
307
307
  summary[8] = rich;
308
308
  }
309
309
  if (changes.url !== undefined || (collection[2] !== 8 && ["title", "publisher", "brief"].some(k => changes[k] !== undefined))) {
310
- if (collection[2] !== 6) throw new Error("Link fields can only edit a link favorite");
310
+ if (collection[2] !== 2) throw new Error("Link fields can only edit a link favorite");
311
311
  const link = fields(summary[2]);
312
312
  for (const [key, tag] of [["url", 1], ["title", 2], ["publisher", 3], ["brief", 4]])
313
313
  if (changes[key] !== undefined) link[tag] = changes[key];
@@ -413,6 +413,55 @@ async function setFavoriteGroup(cid, custom_group_id) {
413
413
  const result = await request(this, 20025, { 1: input, 2: uint(custom_group_id, "custom_group_id") });
414
414
  return result.result ? result : success(itemResults(result.data[1], input));
415
415
  }
416
+ /**
417
+ * 获取收藏文件或视频的临时下载地址,headers 包含该资源所需的鉴权信息。
418
+ * @this {import("./ref").Client}
419
+ * @param {string} cid 收藏 ID
420
+ * @param {string} [fid] 文件 ID;包含多个文件时必须指定
421
+ * @returns {import("./ref").ProtocolResponse}
422
+ */
423
+ async function getFavoriteFileUrl(cid, fid) {
424
+ if (fid !== undefined) string(fid, "fid");
425
+ const detail = await fullInfo(this, cid);
426
+ if (detail.result) return detail;
427
+ const collection = detail.data[1];
428
+ if (collection[3] !== undefined && collection[3] !== 1)
429
+ return error(-1, "Favorite is deleted or unavailable");
430
+ const summary = collection[15], content = detail.data[2];
431
+ const entries = [summary?.[6]?.[1], summary?.[5]?.[31], ...list(content?.[8]?.[5])];
432
+ const files = new Map(entries.filter(file => file && text(file[4])).map(file => [text(file[4]), file]));
433
+ const file = fid === undefined ? (files.size === 1 ? files.values().next().value : undefined) : files.get(fid);
434
+ if (!file) return error(-1, files.size > 1 && fid === undefined ? "Specify fid for a favorite containing multiple files" : "No downloadable file found in favorite");
435
+ const result = await request(this, 20014, { 1: cid, 2: file, 3: 0 });
436
+ if (result.result) return result;
437
+ const rsp = result.data, control = rsp[6];
438
+ const risk_tips = text(control?.[4]);
439
+ if (control && Number(control[1] || 0) !== 1)
440
+ return error(-1, risk_tips || "Server does not allow downloading this resource");
441
+ const https = Boolean(text(rsp[7]));
442
+ const host = text(https ? rsp[7] : rsp[2]);
443
+ if (!host || /[\s/\\?#@]/.test(host)) throw new Error("Invalid favorites download host");
444
+ const target = new global.URL(`${https ? "https" : "http"}://${host}`);
445
+ if (target.username || target.password || target.pathname !== "/" || target.search || target.hash || target.port)
446
+ throw new Error("Invalid favorites download host");
447
+ const port = uint(Number(https ? rsp[8] || 443 : rsp[3] || 80), "download port", 65535);
448
+ if (!port) throw new Error("Invalid favorites download port");
449
+ const token = rsp[1] && rsp[1].toBuffer();
450
+ if (!token || !token.length) throw new Error("Missing favorites download token");
451
+ const info = decode("FileInfo", file), name = info.name || "QQ_Favorite_File";
452
+ target.port = String(port);
453
+ target.pathname = "/ftn_handler/" + token.toString("hex").toUpperCase() + "/";
454
+ target.searchParams.set("fname", name);
455
+ const headers = {}, cookie_name = text(rsp[4]), cookie_value = text(rsp[5]);
456
+ if (cookie_name && cookie_value) {
457
+ if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(cookie_name) || /[;\r\n]/.test(cookie_value))
458
+ throw new Error("Invalid favorites download cookie");
459
+ headers.Cookie = `${cookie_name}=${cookie_value}`;
460
+ }
461
+ return success({ cid, fid: info.fid, type: Number(collection[2]), name, size: info.size ?? 0,
462
+ url: target.href, headers, rename_file: Number(control?.[3]) === 1, risk_tips });
463
+ }
464
+
416
465
  /**
417
466
  * 获取收藏空间总量与已用量,单位为字节。
418
467
  * @this {import("./ref").Client}
@@ -427,5 +476,5 @@ async function getFavoriteSpace() {
427
476
 
428
477
  module.exports = {
429
478
  getFavoriteList, getFavorite, addFavorite, editFavorite, deleteFavorite,
430
- getFavoriteGroups, addFavoriteGroup, editFavoriteGroup, deleteFavoriteGroup, setFavoriteGroup, getFavoriteSpace,
479
+ getFavoriteGroups, addFavoriteGroup, editFavoriteGroup, deleteFavoriteGroup, setFavoriteGroup, getFavoriteSpace, getFavoriteFileUrl,
431
480
  };
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "oicq-icalingua-plus-plus",
3
3
  "app_name": "oicq-icalingua-plus-plus",
4
- "version": "1.26.30",
5
- "app_version": "1.26.30",
6
- "upday": "2026/11/14",
4
+ "version": "1.26.31",
5
+ "app_version": "1.26.31",
6
+ "upday": "2026/11/15",
7
7
  "protocol_version": "v11",
8
8
  "description": "oicq(https://github.com/takayama-lily/oicq) v1 for Icalingua++",
9
9
  "main": "index.js",