@napuketto/kernel 0.5.3 → 0.7.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/index.cjs +463 -13
- package/dist/index.d.cts +190 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +190 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +462 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
4
4
|
import { parse, stringify } from "smol-toml";
|
|
@@ -1515,6 +1515,72 @@ var GroupApi = class {
|
|
|
1515
1515
|
} else res = await this.service.removeGroupEssence(param);
|
|
1516
1516
|
checkLooseResult(label, res);
|
|
1517
1517
|
}
|
|
1518
|
+
/**
|
|
1519
|
+
* 群公告列表(get_group_announcements;getGroupBulletin 返回形状待探测校准,
|
|
1520
|
+
* 兼容 result 数组 / result.bulletins 等宽松提取)。
|
|
1521
|
+
*/
|
|
1522
|
+
async getGroupAnnouncements(groupCode) {
|
|
1523
|
+
let raw;
|
|
1524
|
+
try {
|
|
1525
|
+
raw = await this.service.getGroupBulletin(groupCode);
|
|
1526
|
+
} catch (e) {
|
|
1527
|
+
if (isKernelError(e)) throw e;
|
|
1528
|
+
throw kernelError(`getGroupBulletin 调用失败: ${errMsgString$3(e)}`, "UNKNOWN");
|
|
1529
|
+
}
|
|
1530
|
+
unwrap("getGroupBulletin", toResultNumber(raw.result), raw.errMsg);
|
|
1531
|
+
return extractBulletins(raw.result);
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* 发群公告(send_group_announcement;imagePath 提供时先 uploadGroupBulletinPic
|
|
1535
|
+
* 再带 picId 发布——两步参数形状均待探测校准)。
|
|
1536
|
+
*/
|
|
1537
|
+
async publishGroupAnnouncement(groupCode, content, imagePath) {
|
|
1538
|
+
let picId;
|
|
1539
|
+
if (imagePath !== void 0 && imagePath !== "") picId = await this.uploadBulletinPic(groupCode, imagePath);
|
|
1540
|
+
try {
|
|
1541
|
+
checkLooseResult("publishGroupBulletin", await this.service.publishGroupBulletin({
|
|
1542
|
+
groupCode,
|
|
1543
|
+
content,
|
|
1544
|
+
...picId !== void 0 ? { picId } : {}
|
|
1545
|
+
}));
|
|
1546
|
+
} catch (e) {
|
|
1547
|
+
if (isKernelError(e)) throw e;
|
|
1548
|
+
throw kernelError(`publishGroupBulletin 调用失败: ${errMsgString$3(e)}`, "UNKNOWN");
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/** 删群公告(delete_group_announcement;参数待探测校准)。 */
|
|
1552
|
+
async deleteGroupAnnouncement(groupCode, announcementId) {
|
|
1553
|
+
try {
|
|
1554
|
+
checkLooseResult("deleteGroupBulletin", await this.service.deleteGroupBulletin(groupCode, announcementId));
|
|
1555
|
+
} catch (e) {
|
|
1556
|
+
if (isKernelError(e)) throw e;
|
|
1557
|
+
throw kernelError(`deleteGroupBulletin 调用失败: ${errMsgString$3(e)}`, "UNKNOWN");
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
/** 专属头衔(set_group_member_special_title;setIdentityTitleInfo 参数待校准)。 */
|
|
1561
|
+
async setMemberSpecialTitle(groupCode, uid, title) {
|
|
1562
|
+
try {
|
|
1563
|
+
checkLooseResult("setIdentityTitleInfo", await this.service.setIdentityTitleInfo(groupCode, uid, title));
|
|
1564
|
+
} catch (e) {
|
|
1565
|
+
if (isKernelError(e)) throw e;
|
|
1566
|
+
throw kernelError(`setIdentityTitleInfo 调用失败: ${errMsgString$3(e)}`, "UNKNOWN");
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/** 群头像(set_group_avatar;GroupService.setHeader 参数待校准)。 */
|
|
1570
|
+
async setGroupAvatar(groupCode, imagePath) {
|
|
1571
|
+
try {
|
|
1572
|
+
checkLooseResult("setHeader", await this.service.setHeader(groupCode, imagePath));
|
|
1573
|
+
} catch (e) {
|
|
1574
|
+
if (isKernelError(e)) throw e;
|
|
1575
|
+
throw kernelError(`setHeader(group) 调用失败: ${errMsgString$3(e)}`, "UNKNOWN");
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
/** 公告图片上传(uploadGroupBulletinPic 返回 picId 待校准,失败原样上抛)。 */
|
|
1579
|
+
async uploadBulletinPic(groupCode, imagePath) {
|
|
1580
|
+
const res = await this.service.uploadGroupBulletinPic(groupCode, imagePath);
|
|
1581
|
+
checkLooseResult("uploadGroupBulletinPic", res);
|
|
1582
|
+
return typeof res?.picId === "string" ? res.picId : "";
|
|
1583
|
+
}
|
|
1518
1584
|
/** @all 剩余次数(get_group_at_all_remain)。 */
|
|
1519
1585
|
async getGroupRemainAtTimes(groupCode) {
|
|
1520
1586
|
const raw = await this.service.getGroupRemainAtTimes(groupCode);
|
|
@@ -1526,6 +1592,29 @@ var GroupApi = class {
|
|
|
1526
1592
|
};
|
|
1527
1593
|
}
|
|
1528
1594
|
};
|
|
1595
|
+
/** result 字段宽松取数(数字直接用,缺省 0 = 成功语义)。 */
|
|
1596
|
+
function toResultNumber(result) {
|
|
1597
|
+
return typeof result === "number" ? result : 0;
|
|
1598
|
+
}
|
|
1599
|
+
/** 公告列表宽松提取(result 数组 / { bulletins } / { result: 数组 } 兼容,形状待校准)。 */
|
|
1600
|
+
function extractBulletins(result) {
|
|
1601
|
+
const candidates = [result];
|
|
1602
|
+
if (result !== null && typeof result === "object") {
|
|
1603
|
+
const rec = result;
|
|
1604
|
+
for (const key of [
|
|
1605
|
+
"bulletins",
|
|
1606
|
+
"announcementList",
|
|
1607
|
+
"result",
|
|
1608
|
+
"list"
|
|
1609
|
+
]) candidates.push(rec[key]);
|
|
1610
|
+
}
|
|
1611
|
+
for (const candidate of candidates) if (Array.isArray(candidate)) return candidate.filter((item) => item !== null && typeof item === "object");
|
|
1612
|
+
return [];
|
|
1613
|
+
}
|
|
1614
|
+
/** 未知异常 → 消息字符串。 */
|
|
1615
|
+
function errMsgString$3(e) {
|
|
1616
|
+
return e instanceof Error ? e.message : String(e);
|
|
1617
|
+
}
|
|
1529
1618
|
/**
|
|
1530
1619
|
* 从 getGroupDetailInfo 返回提取群详情(兼容三种形状):
|
|
1531
1620
|
* - result 为对象 → 详情直接在其中(最常见)
|
|
@@ -1699,6 +1788,28 @@ function sleep$1(ms) {
|
|
|
1699
1788
|
setTimeout(resolve, ms);
|
|
1700
1789
|
});
|
|
1701
1790
|
}
|
|
1791
|
+
/** 未知异常 → 消息字符串。 */
|
|
1792
|
+
function errMsgString$2(e) {
|
|
1793
|
+
return e instanceof Error ? e.message : String(e);
|
|
1794
|
+
}
|
|
1795
|
+
/** 语音下载完成判定:有路径且(传输完成或末次轮询)才返回元素,否则 null。 */
|
|
1796
|
+
function pttDownloadResult(latest, isLastPoll) {
|
|
1797
|
+
if (latest === void 0) return null;
|
|
1798
|
+
const hasPath = latest.filePath !== void 0 && latest.filePath !== "";
|
|
1799
|
+
const done = latest.transferStatus === 2 || latest.transferStatus === 4;
|
|
1800
|
+
return hasPath && (done || isLastPoll) ? latest : null;
|
|
1801
|
+
}
|
|
1802
|
+
/** 收藏表情项 → url(字段名待校准,url/imageUrl/emojiUrl 逐个兜底)。 */
|
|
1803
|
+
function extractFavEmojiUrls(list) {
|
|
1804
|
+
const out = [];
|
|
1805
|
+
for (const item of list) {
|
|
1806
|
+
if (item === null || typeof item !== "object") continue;
|
|
1807
|
+
const rec = item;
|
|
1808
|
+
const url = rec["url"] ?? rec["imageUrl"] ?? rec["emojiUrl"];
|
|
1809
|
+
if (typeof url === "string" && url !== "") out.push(url);
|
|
1810
|
+
}
|
|
1811
|
+
return out;
|
|
1812
|
+
}
|
|
1702
1813
|
/** 读文件 stat(富媒体发送预处理;文件不存在抛 INVALID_PARAM)。 */
|
|
1703
1814
|
async function statFile(path, label = "图片") {
|
|
1704
1815
|
try {
|
|
@@ -1808,6 +1919,9 @@ var MsgApi = class {
|
|
|
1808
1919
|
case "video":
|
|
1809
1920
|
out.push(await this.prepareVideoElement(el));
|
|
1810
1921
|
break;
|
|
1922
|
+
case "file":
|
|
1923
|
+
out.push(await this.prepareFileElement(el));
|
|
1924
|
+
break;
|
|
1811
1925
|
default: out.push(...toSendElements([el]));
|
|
1812
1926
|
}
|
|
1813
1927
|
return out;
|
|
@@ -1928,6 +2042,28 @@ var MsgApi = class {
|
|
|
1928
2042
|
};
|
|
1929
2043
|
}
|
|
1930
2044
|
/**
|
|
2045
|
+
* FILE 元素预处理(P2-2 预留口子,upload_private/group_file 落地):
|
|
2046
|
+
* pic/ptt/video 同款 NapCat 式链路——md5 → getRichMediaFilePathForGuild(FILE)
|
|
2047
|
+
* → copyFile → 完整 fileElement。链路模式已验证(三类媒体同构);fileElement
|
|
2048
|
+
* 字段值(fileSize 字符串形态、fileUuid 空串等)待探测校准。
|
|
2049
|
+
*/
|
|
2050
|
+
async prepareFileElement(el) {
|
|
2051
|
+
const file = await statFile(el.path, "文件");
|
|
2052
|
+
const md5 = await hashFile(el.path, "文件");
|
|
2053
|
+
const fileName = el.name !== void 0 && el.name !== "" ? el.name : basename(el.path);
|
|
2054
|
+
const relPath = await this.placeMediaFile(el.path, ElementType.FILE, md5, fileName);
|
|
2055
|
+
return {
|
|
2056
|
+
elementType: ElementType.FILE,
|
|
2057
|
+
elementId: "",
|
|
2058
|
+
fileElement: {
|
|
2059
|
+
fileName,
|
|
2060
|
+
filePath: relPath,
|
|
2061
|
+
fileSize: String(file.size),
|
|
2062
|
+
fileUuid: ""
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
}
|
|
2066
|
+
/**
|
|
1931
2067
|
* 富媒体文件放置(PIC/PTT/VIDEO 共用):md5 + 文件名 → QQ 内部目标路径
|
|
1932
2068
|
* (getRichMediaFilePathForGuild)→ util.copyFile 放置 → 返回目标路径。
|
|
1933
2069
|
*
|
|
@@ -2021,31 +2157,72 @@ var MsgApi = class {
|
|
|
2021
2157
|
* 调用方),完全无路径抛 NOT_FOUND。
|
|
2022
2158
|
*/
|
|
2023
2159
|
async downloadPtt(msgId, target) {
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
if (el === null || ptt === void 0) throw kernelError("消息中不包含语音", "NOT_FOUND");
|
|
2027
|
-
const elemId = el.elementId ?? "";
|
|
2160
|
+
const found = await this.pttElementOf(msgId, target);
|
|
2161
|
+
if (found === null) throw kernelError("消息中不包含语音", "NOT_FOUND");
|
|
2028
2162
|
await this.service.downloadRichMedia({
|
|
2029
2163
|
msgId,
|
|
2030
|
-
elemId,
|
|
2164
|
+
elemId: found.elemId,
|
|
2031
2165
|
chatType: target.chatType,
|
|
2032
2166
|
downloadType: DOWNLOAD_TYPE_COMMON,
|
|
2033
2167
|
thumbSize: THUMB_SIZE_NONE
|
|
2034
2168
|
});
|
|
2035
2169
|
for (let i = 0; i < PTT_POLL_TIMES; i++) {
|
|
2036
2170
|
await sleep$1(PTT_POLL_INTERVAL_MS);
|
|
2037
|
-
const
|
|
2038
|
-
if (
|
|
2039
|
-
const done = latest.transferStatus === 2 || latest.transferStatus === 4;
|
|
2040
|
-
if (latest.filePath !== void 0 && latest.filePath !== "" && done) return latest;
|
|
2041
|
-
if (i === 33 && latest.filePath !== void 0 && latest.filePath !== "") return latest;
|
|
2171
|
+
const result = pttDownloadResult(findPttElement(await this.fetchMsgsByMsgId(target, [msgId]))?.pttElement, i === 33);
|
|
2172
|
+
if (result !== null) return result;
|
|
2042
2173
|
}
|
|
2043
2174
|
throw kernelError("语音下载未完成(超时)", "TIMEOUT");
|
|
2044
2175
|
}
|
|
2176
|
+
/** 取消息内语音元素(无则 null;附 elemId 供主动下载)。 */
|
|
2177
|
+
async pttElementOf(msgId, target) {
|
|
2178
|
+
const el = findPttElement(await this.fetchMsgsByMsgId(target, [msgId]));
|
|
2179
|
+
const ptt = el?.pttElement;
|
|
2180
|
+
if (el === null || ptt === void 0) return null;
|
|
2181
|
+
return {
|
|
2182
|
+
elemId: el.elementId ?? "",
|
|
2183
|
+
ptt
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2045
2186
|
/** 发送输入状态(set_input_status;eventType=1 输入中,0 停止)。 */
|
|
2046
2187
|
async setInputStatus(target, eventType) {
|
|
2047
2188
|
await this.service.sendShowInputStatusReq(target.chatType, eventType, target.peerUid);
|
|
2048
2189
|
}
|
|
2190
|
+
/** 戳一戳发送(send_friend/group_nudge;参数形状待探测校准,失败抛 UNKNOWN)。 */
|
|
2191
|
+
async sendNudge(target, opts) {
|
|
2192
|
+
const param = {
|
|
2193
|
+
receiverUid: opts.receiverUid,
|
|
2194
|
+
callerUid: opts.callerUid,
|
|
2195
|
+
...target.chatType === ChatType.GROUP ? { confId: target.peerUid } : {}
|
|
2196
|
+
};
|
|
2197
|
+
try {
|
|
2198
|
+
checkLooseResult("sendNudge", await this.service.sendNudge(param));
|
|
2199
|
+
} catch (e) {
|
|
2200
|
+
if (isKernelError(e)) throw e;
|
|
2201
|
+
throw kernelError(`sendNudge 调用失败: ${errMsgString$2(e)}`, "UNKNOWN");
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
/** 会话置顶(set_peer_pin;setContactLocalTop 参数待探测校准)。 */
|
|
2205
|
+
async setContactTop(target, isTop) {
|
|
2206
|
+
try {
|
|
2207
|
+
checkLooseResult("setContactLocalTop", await this.service.setContactLocalTop(target, isTop));
|
|
2208
|
+
} catch (e) {
|
|
2209
|
+
if (isKernelError(e)) throw e;
|
|
2210
|
+
throw kernelError(`setContactLocalTop 调用失败: ${errMsgString$2(e)}`, "UNKNOWN");
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
/** 收藏表情列表(get_custom_face_url_list;queryFavEmojiByDesc 形状待校准)。 */
|
|
2214
|
+
async queryFavEmojis() {
|
|
2215
|
+
let raw;
|
|
2216
|
+
try {
|
|
2217
|
+
raw = await this.service.queryFavEmojiByDesc("");
|
|
2218
|
+
} catch (e) {
|
|
2219
|
+
throw kernelError(`queryFavEmojiByDesc 调用失败: ${errMsgString$2(e)}`, "UNKNOWN");
|
|
2220
|
+
}
|
|
2221
|
+
checkLooseResult("queryFavEmojiByDesc", raw);
|
|
2222
|
+
const list = raw.emojiInfoList;
|
|
2223
|
+
if (!Array.isArray(list)) return [];
|
|
2224
|
+
return extractFavEmojiUrls(list);
|
|
2225
|
+
}
|
|
2049
2226
|
/**
|
|
2050
2227
|
* 发送合并转发(send_group/private_forward_msg)。
|
|
2051
2228
|
* buildMultiForwardMsg 组装 MULTI_FORWARD 元素 → 直接作 sendMsg 元素发送。
|
|
@@ -2212,6 +2389,76 @@ var ProfileLikeApi = class {
|
|
|
2212
2389
|
}
|
|
2213
2390
|
};
|
|
2214
2391
|
//#endregion
|
|
2392
|
+
//#region src/apis/recent-contact.ts
|
|
2393
|
+
/**
|
|
2394
|
+
* RecentContactApi:会话列表语义化 API(P2 批次二,get_peer_pins 数据源)
|
|
2395
|
+
*
|
|
2396
|
+
* 2026-10-31 实机探测确认 getRecentContactService 有 getRecentContactList /
|
|
2397
|
+
* setContactListTop 等方法;返回形状待探测校准(宽松提取)。
|
|
2398
|
+
* 置顶数据源:isTop 过滤(ChatType 区分 friend/group 场景)。
|
|
2399
|
+
*/
|
|
2400
|
+
/** 会话列表拉取条数(覆盖置顶集合足够大;参数值待探测校准)。 */
|
|
2401
|
+
const RECENT_CONTACT_FETCH_COUNT = 200;
|
|
2402
|
+
/** 会话列表 API:从 session 拿 recent contact service,包装成语义化方法。 */
|
|
2403
|
+
var RecentContactApi = class {
|
|
2404
|
+
service;
|
|
2405
|
+
constructor(session) {
|
|
2406
|
+
const service = session.getRecentContactService();
|
|
2407
|
+
if (service === null || service === void 0) throw kernelError("getRecentContactService() 返回空(session 未 init)", "INVALID_STATE");
|
|
2408
|
+
this.service = service;
|
|
2409
|
+
}
|
|
2410
|
+
/** 置顶会话列表(get_peer_pins;getRecentContactList 返回形状待探测校准)。 */
|
|
2411
|
+
async getPinnedContacts() {
|
|
2412
|
+
let raw;
|
|
2413
|
+
try {
|
|
2414
|
+
raw = await this.service.getRecentContactList({ count: RECENT_CONTACT_FETCH_COUNT });
|
|
2415
|
+
} catch (e) {
|
|
2416
|
+
if (isKernelError(e)) throw e;
|
|
2417
|
+
throw kernelError(`getRecentContactList 调用失败: ${errMsgString$1(e)}`, "UNKNOWN");
|
|
2418
|
+
}
|
|
2419
|
+
checkLooseResult("getRecentContactList", raw);
|
|
2420
|
+
return extractPinnedContacts(raw);
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
/** 从返回形状提取置顶会话(result 数组 / 顶层 contacts / 嵌套容器兼容,待校准)。 */
|
|
2424
|
+
function extractPinnedContacts(raw) {
|
|
2425
|
+
const candidates = [raw.result, raw];
|
|
2426
|
+
for (const container of [raw.result, raw]) {
|
|
2427
|
+
if (container === null || typeof container !== "object") continue;
|
|
2428
|
+
const rec = container;
|
|
2429
|
+
for (const key of [
|
|
2430
|
+
"contacts",
|
|
2431
|
+
"data",
|
|
2432
|
+
"result",
|
|
2433
|
+
"list"
|
|
2434
|
+
]) candidates.push(rec[key]);
|
|
2435
|
+
}
|
|
2436
|
+
for (const candidate of candidates) {
|
|
2437
|
+
if (!Array.isArray(candidate)) continue;
|
|
2438
|
+
return candidate.flatMap((item) => pinnedOf(item));
|
|
2439
|
+
}
|
|
2440
|
+
return [];
|
|
2441
|
+
}
|
|
2442
|
+
/** 单条会话 → 置顶项(isTop 为真且场景可判定才产出)。 */
|
|
2443
|
+
function pinnedOf(item) {
|
|
2444
|
+
if (item === null || typeof item !== "object") return [];
|
|
2445
|
+
const contact = item;
|
|
2446
|
+
if (contact.isTop !== true || typeof contact.peerUid !== "string") return [];
|
|
2447
|
+
if (contact.chatType === ChatType.GROUP) return [{
|
|
2448
|
+
peerUid: contact.peerUid,
|
|
2449
|
+
scene: "group"
|
|
2450
|
+
}];
|
|
2451
|
+
if (contact.chatType === ChatType.C2C) return [{
|
|
2452
|
+
peerUid: contact.peerUid,
|
|
2453
|
+
scene: "friend"
|
|
2454
|
+
}];
|
|
2455
|
+
return [];
|
|
2456
|
+
}
|
|
2457
|
+
/** 未知异常 → 消息字符串。 */
|
|
2458
|
+
function errMsgString$1(e) {
|
|
2459
|
+
return e instanceof Error ? e.message : String(e);
|
|
2460
|
+
}
|
|
2461
|
+
//#endregion
|
|
2215
2462
|
//#region src/apis/richmedia.ts
|
|
2216
2463
|
/**
|
|
2217
2464
|
* RichMediaApi:富媒体语义化 API(ADR-009 统一错误语义,P2-14)
|
|
@@ -2257,10 +2504,11 @@ var RichMediaApi = class {
|
|
|
2257
2504
|
showOnlinedocFolder: HIDE_ONLINEDOC_FOLDER
|
|
2258
2505
|
};
|
|
2259
2506
|
const raw = await this.service.getGroupFileList(groupCode, params);
|
|
2260
|
-
|
|
2507
|
+
checkLooseResult("getGroupFileList", raw);
|
|
2261
2508
|
const list = extractFileList(raw.result);
|
|
2262
|
-
|
|
2263
|
-
|
|
2509
|
+
const items = list.length > 0 ? list : extractFileList(raw);
|
|
2510
|
+
if (parentFolderId === "") return items;
|
|
2511
|
+
return items.filter((item) => item.fileInfo !== void 0 && item.fileInfo.parent === parentFolderId || item.folderInfo !== void 0 && item.folderInfo.folderUid === parentFolderId);
|
|
2264
2512
|
}
|
|
2265
2513
|
/** 群文件系统信息(get_group_file_system_info;getGroupFileList 的 groupSpaceResult)。 */
|
|
2266
2514
|
async getGroupFileSystemInfo(groupCode) {
|
|
@@ -2309,6 +2557,26 @@ var RichMediaApi = class {
|
|
|
2309
2557
|
const raw = await this.service.moveGroupFile(groupCode, [GROUP_FILE_BIZ_TYPE], fileIds, "", targetFolderId);
|
|
2310
2558
|
if (raw.result !== 0) unwrap("moveGroupFile", raw.result, raw.errMsg);
|
|
2311
2559
|
}
|
|
2560
|
+
/** 重命名群文件夹(rename_group_folder;形态镜像 renameGroupFile,待校准)。 */
|
|
2561
|
+
async renameGroupFolder(groupCode, folderId, newName) {
|
|
2562
|
+
await this.service.renameGroupFolder(groupCode, GROUP_FILE_BIZ_TYPE, folderId, newName, "");
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* 群文件下载链接(get_group_file_download_url):getGroupFileList 根列表按
|
|
2566
|
+
* fileId 查找取 url 字段(字段待实机确认)。已知差异:仅根目录可达——
|
|
2567
|
+
* 子目录文件需 parentFolderId 逐层定位,暂不支持。
|
|
2568
|
+
*/
|
|
2569
|
+
async getGroupFileDownloadUrl(groupCode, fileId) {
|
|
2570
|
+
const items = await this.getGroupFileList(groupCode);
|
|
2571
|
+
for (const item of items) {
|
|
2572
|
+
const info = item.fileInfo;
|
|
2573
|
+
if (info !== void 0 && info.fileId === fileId) {
|
|
2574
|
+
if (typeof info.url === "string" && info.url !== "") return info.url;
|
|
2575
|
+
throw kernelError(`群文件 ${fileId} 无下载链接(url 字段缺失,待实机校准)`, "NOT_FOUND");
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
throw kernelError(`群文件 ${fileId} 不在根目录或不存在(仅支持根目录查找)`, "NOT_FOUND");
|
|
2579
|
+
}
|
|
2312
2580
|
/** 转存群文件(trans_group_file)。 */
|
|
2313
2581
|
async transGroupFile(groupCode, fileId) {
|
|
2314
2582
|
const raw = await this.service.transGroupFile(groupCode, fileId);
|
|
@@ -4899,6 +5167,185 @@ function writeProbeFile(result, file) {
|
|
|
4899
5167
|
}
|
|
4900
5168
|
}
|
|
4901
5169
|
//#endregion
|
|
5170
|
+
//#region src/wrapper/probe/probe-listener.ts
|
|
5171
|
+
/**
|
|
5172
|
+
* listener 录制器(批次三前置探测,docs/protocols/milky.md §0.8.1、本包 design.md §10)
|
|
5173
|
+
*
|
|
5174
|
+
* 动机:无源事件缺的不是方法名而是回调证据——现有 bridge 只注册已知回调名,
|
|
5175
|
+
* 原生 listener 回调面之外的派发行为从未被观测。原理:原生 listener 是普通
|
|
5176
|
+
* JS 对象 + NAPI 按名反射回调(MsgBridge 模式),用 Proxy get 陷阱构造万能
|
|
5177
|
+
* 监听器,原生调什么记什么,不需要预先知道回调名。
|
|
5178
|
+
*
|
|
5179
|
+
* 开关:NAPUTO_LISTENER_RECORD=1(loader bootstrap 挂起,位置与 runProbePhase 相邻)。
|
|
5180
|
+
* 产物:NAPUTO_CFG_DIR/napuketto-listener-record.jsonl(JSONL 追加,注册过程
|
|
5181
|
+
* 逐行落盘,原生崩溃保留断点定位肇事服务)。
|
|
5182
|
+
*
|
|
5183
|
+
* 安全边界:记录函数只序列化入参后回调 onEvent,绝不反向调用原生;录制器与
|
|
5184
|
+
* 业务 bridge 并存(addKernelXListener 多监听各自独立 id),不干扰协议链路。
|
|
5185
|
+
*/
|
|
5186
|
+
/** 默认仪器面:与 5 个无源事件 + sysmsg 样本采集相关的 listener 服务(design.md §10)。 */
|
|
5187
|
+
const INSTRUMENTED_SERVICE_GETTERS = [
|
|
5188
|
+
"getMsgService",
|
|
5189
|
+
"getGroupService",
|
|
5190
|
+
"getRecentContactService",
|
|
5191
|
+
"getBdhUploadService",
|
|
5192
|
+
"getOnlineStatusService",
|
|
5193
|
+
"getLockService",
|
|
5194
|
+
"getFileAssistantService",
|
|
5195
|
+
"getBuddyService",
|
|
5196
|
+
"getProfileLikeService",
|
|
5197
|
+
"getNodeMiscService"
|
|
5198
|
+
];
|
|
5199
|
+
/** 服务 getter 前缀 / 后缀(getMsgService → Msg)。 */
|
|
5200
|
+
const GETTER_PREFIX_RE = /^get/;
|
|
5201
|
+
const SERVICE_SUFFIX_RE = /Service$/;
|
|
5202
|
+
/** listener 注册 / 注销方法名(addKernelMsgListener / 裸 addListener 同规则)。 */
|
|
5203
|
+
const ADD_LISTENER_RE = /^add(Kernel)?\w*Listener$/;
|
|
5204
|
+
const REMOVE_LISTENER_RE = /^remove(Kernel)?\w*Listener$/;
|
|
5205
|
+
/** 服务简称(getMsgService → Msg;getBdhUploadService → BdhUpload)。 */
|
|
5206
|
+
function serviceShortName(getter) {
|
|
5207
|
+
return getter.replace(GETTER_PREFIX_RE, "").replace(SERVICE_SUFFIX_RE, "");
|
|
5208
|
+
}
|
|
5209
|
+
/** 从服务方法面选 listener 注册方法(addKernelMsgListener / 裸 addListener 同规则)。 */
|
|
5210
|
+
function findAddMethod(service) {
|
|
5211
|
+
return listMethods(service).find((m) => ADD_LISTENER_RE.test(m)) ?? null;
|
|
5212
|
+
}
|
|
5213
|
+
/** 从服务方法面选 listener 注销方法(缺失允许——录制器不主动注销)。 */
|
|
5214
|
+
function findRemoveMethod(service) {
|
|
5215
|
+
return listMethods(service).find((m) => REMOVE_LISTENER_RE.test(m)) ?? null;
|
|
5216
|
+
}
|
|
5217
|
+
/**
|
|
5218
|
+
* 构造万能监听器:Proxy get 陷阱对任意字符串属性返回记录函数(同时记录原生
|
|
5219
|
+
* 查过的名字,即使从未调用);has / ownKeys 也记录(判定原生按名 Get 还是
|
|
5220
|
+
* 枚举发现回调);非字符串键(Symbol,如 util.inspect 触发)放行默认行为。
|
|
5221
|
+
*/
|
|
5222
|
+
function createRecordingListener(service, onEvent) {
|
|
5223
|
+
const record = (event, args) => {
|
|
5224
|
+
onEvent({
|
|
5225
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5226
|
+
service,
|
|
5227
|
+
event,
|
|
5228
|
+
args
|
|
5229
|
+
});
|
|
5230
|
+
};
|
|
5231
|
+
return new Proxy({}, {
|
|
5232
|
+
get(target, prop, receiver) {
|
|
5233
|
+
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
|
5234
|
+
record("<get>", [prop]);
|
|
5235
|
+
return (...args) => {
|
|
5236
|
+
record(prop, [...args].map((a) => serialize(a)));
|
|
5237
|
+
};
|
|
5238
|
+
},
|
|
5239
|
+
has(_target, prop) {
|
|
5240
|
+
if (typeof prop === "string") record("<has>", [prop]);
|
|
5241
|
+
return true;
|
|
5242
|
+
},
|
|
5243
|
+
ownKeys() {
|
|
5244
|
+
record("<ownKeys>", []);
|
|
5245
|
+
return [];
|
|
5246
|
+
}
|
|
5247
|
+
});
|
|
5248
|
+
}
|
|
5249
|
+
/**
|
|
5250
|
+
* 启动 listener 录制:session 十服务 + LoginService(exports 单例)逐服务注册
|
|
5251
|
+
* 万能监听器,JSONL 逐行追加。返回摘要;文件路径写入结果(bootstrap 日志用)。
|
|
5252
|
+
*
|
|
5253
|
+
* ⚠️ 调用时序约束:必须在任何探测(probeRuntime / getMainSession)之前调用——
|
|
5254
|
+
* resolveMainFromStartup 会把空壳主 session 写回 ctx.session(session-resolver.ts),
|
|
5255
|
+
* 空壳的 service getter 函数仍在但调用返回 null(late probe 61/63 null 的根因)。
|
|
5256
|
+
*/
|
|
5257
|
+
function startListenerRecording(ctx, file = "napuketto-listener-record.jsonl") {
|
|
5258
|
+
const dir = process.env["NAPUTO_CFG_DIR"] ?? process.cwd();
|
|
5259
|
+
mkdirSync(dir, { recursive: true });
|
|
5260
|
+
const full = join(dir, file);
|
|
5261
|
+
writeFileSync(full, "", "utf8");
|
|
5262
|
+
const emit = (record) => {
|
|
5263
|
+
try {
|
|
5264
|
+
appendFileSync(full, `${JSON.stringify(record)}\n`, "utf8");
|
|
5265
|
+
} catch {}
|
|
5266
|
+
};
|
|
5267
|
+
const registered = {};
|
|
5268
|
+
const session = ctx.session;
|
|
5269
|
+
if (session !== null && session !== void 0) for (const getter of INSTRUMENTED_SERVICE_GETTERS) registered[serviceShortName(getter)] = recordService(session, getter, emit);
|
|
5270
|
+
registered["Login"] = recordLoginService(ctx, emit);
|
|
5271
|
+
return {
|
|
5272
|
+
file: full,
|
|
5273
|
+
registered
|
|
5274
|
+
};
|
|
5275
|
+
}
|
|
5276
|
+
/** 对单个服务注册万能监听器(getter 失败/null/无 add 方法均记录原因)。 */
|
|
5277
|
+
function recordService(session, getter, emit) {
|
|
5278
|
+
const name = serviceShortName(getter);
|
|
5279
|
+
try {
|
|
5280
|
+
const fn = session[getter];
|
|
5281
|
+
if (typeof fn !== "function") return {
|
|
5282
|
+
ok: false,
|
|
5283
|
+
error: `getter 缺失(typeof=${typeof fn})`
|
|
5284
|
+
};
|
|
5285
|
+
const service = fn.call(session);
|
|
5286
|
+
if (service === null || service === void 0) return {
|
|
5287
|
+
ok: false,
|
|
5288
|
+
error: "null/undefined"
|
|
5289
|
+
};
|
|
5290
|
+
return attachRecordingListener(name, service, emit);
|
|
5291
|
+
} catch (e) {
|
|
5292
|
+
return {
|
|
5293
|
+
ok: false,
|
|
5294
|
+
error: e instanceof Error ? e.message : String(e)
|
|
5295
|
+
};
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5298
|
+
/** LoginService 单例录制(NodeIKernelLoginService.get(),bot_offline 候选源)。 */
|
|
5299
|
+
function recordLoginService(ctx, emit) {
|
|
5300
|
+
const ctor = ctx.exports["NodeIKernelLoginService"];
|
|
5301
|
+
try {
|
|
5302
|
+
if (ctor === null || ctor === void 0 || typeof ctor.get !== "function") return {
|
|
5303
|
+
ok: false,
|
|
5304
|
+
error: "NodeIKernelLoginService.get 缺失"
|
|
5305
|
+
};
|
|
5306
|
+
const service = ctor.get();
|
|
5307
|
+
if (service === null || service === void 0) return {
|
|
5308
|
+
ok: false,
|
|
5309
|
+
error: "null/undefined"
|
|
5310
|
+
};
|
|
5311
|
+
return attachRecordingListener("Login", service, emit);
|
|
5312
|
+
} catch (e) {
|
|
5313
|
+
return {
|
|
5314
|
+
ok: false,
|
|
5315
|
+
error: e instanceof Error ? e.message : String(e)
|
|
5316
|
+
};
|
|
5317
|
+
}
|
|
5318
|
+
}
|
|
5319
|
+
/** 在服务实例上挂万能监听器(add 方法自动选取;注册事件即行落盘)。 */
|
|
5320
|
+
function attachRecordingListener(name, service, emit) {
|
|
5321
|
+
const addMethod = findAddMethod(service);
|
|
5322
|
+
if (addMethod === null) return {
|
|
5323
|
+
ok: false,
|
|
5324
|
+
error: "无 add*Listener 方法"
|
|
5325
|
+
};
|
|
5326
|
+
const add = service[addMethod];
|
|
5327
|
+
if (typeof add !== "function") return {
|
|
5328
|
+
ok: false,
|
|
5329
|
+
error: `${addMethod} 非函数`
|
|
5330
|
+
};
|
|
5331
|
+
const listener = createRecordingListener(name, emit);
|
|
5332
|
+
const listenerId = add.call(service, listener);
|
|
5333
|
+
emit({
|
|
5334
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5335
|
+
service: name,
|
|
5336
|
+
event: "<registered>",
|
|
5337
|
+
args: [{
|
|
5338
|
+
addMethod,
|
|
5339
|
+
removeMethod: findRemoveMethod(service),
|
|
5340
|
+
listenerId: serialize(listenerId)
|
|
5341
|
+
}]
|
|
5342
|
+
});
|
|
5343
|
+
return {
|
|
5344
|
+
ok: true,
|
|
5345
|
+
...typeof listenerId === "number" ? { listenerId } : {}
|
|
5346
|
+
};
|
|
5347
|
+
}
|
|
5348
|
+
//#endregion
|
|
4902
5349
|
//#region src/wrapper/wrapper-version.ts
|
|
4903
5350
|
/**
|
|
4904
5351
|
* QQ 安装目录探测(ADR-018)
|
|
@@ -4956,6 +5403,6 @@ function listQQVersions(installDir) {
|
|
|
4956
5403
|
}).sort().reverse();
|
|
4957
5404
|
}
|
|
4958
5405
|
//#endregion
|
|
4959
|
-
export { BuddyCache, ChatType, ConfigBase, ConfigError, DEFAULT_DATA_ROOT_NAME, EXIT_CONFIG_INVALID, ElementType, FriendApi, FriendBridge, GrayTipSubType, GroupApi, GroupBridge, GroupCache, GroupListUpdateType, GroupNotifyApi, GroupNotifyMsgStatus, GroupNotifyMsgType, KERNEL_ERROR_CODES, KernelError, MAIN_CONFIG_FILE, MsgApi, MsgBridge, NTEventChannel, NTGroupMemberRole, NTGroupRequestOperateTypes, NapukettoCore, PathWrapper, ProfileApi, ProfileLikeApi, QrLoginSession, RichMediaApi, TicketApi, TipGroupElementType, WebApi, WebHonorType, buildEngineConfig, buildLoginConfig, buildSessionConfig, configError, createCoreContext, createSessionListener as createLifecycleSessionListener, createSessionListener, createLogger, createLoginListener, createSession, createWrapper, electronProcessType, getExistingSession, getMainSession, initAndStartSession, initEngine, initSession, isKernelError, kernelError, listLoginAccounts, listQQVersions, narrowBuddyCategories, parseAppidFromMajor, parseLogConfig, parseToml, probeRuntime, quickLogin, readMachineGuid, resolveAppidQua, resolveConfigPath, resolveDataRoot, resolveQQVersion, resolveQqGlobalPath, resolveQqUserDataRoot, resolveWrapperPath, startNapuketto, startSession, stringifyToml, toCanonicalElements, toSendElements, waitForNetworkConnection, waitSessionReady };
|
|
5406
|
+
export { BuddyCache, ChatType, ConfigBase, ConfigError, DEFAULT_DATA_ROOT_NAME, EXIT_CONFIG_INVALID, ElementType, FriendApi, FriendBridge, GrayTipSubType, GroupApi, GroupBridge, GroupCache, GroupListUpdateType, GroupNotifyApi, GroupNotifyMsgStatus, GroupNotifyMsgType, KERNEL_ERROR_CODES, KernelError, MAIN_CONFIG_FILE, MsgApi, MsgBridge, NTEventChannel, NTGroupMemberRole, NTGroupRequestOperateTypes, NapukettoCore, PathWrapper, ProfileApi, ProfileLikeApi, QrLoginSession, RecentContactApi, RichMediaApi, TicketApi, TipGroupElementType, WebApi, WebHonorType, buildEngineConfig, buildLoginConfig, buildSessionConfig, configError, createCoreContext, createSessionListener as createLifecycleSessionListener, createSessionListener, createLogger, createLoginListener, createRecordingListener, createSession, createWrapper, electronProcessType, getExistingSession, getMainSession, initAndStartSession, initEngine, initSession, isKernelError, kernelError, listLoginAccounts, listQQVersions, narrowBuddyCategories, parseAppidFromMajor, parseLogConfig, parseToml, probeRuntime, quickLogin, readMachineGuid, resolveAppidQua, resolveConfigPath, resolveDataRoot, resolveQQVersion, resolveQqGlobalPath, resolveQqUserDataRoot, resolveWrapperPath, startListenerRecording, startNapuketto, startSession, stringifyToml, toCanonicalElements, toSendElements, waitForNetworkConnection, waitSessionReady };
|
|
4960
5407
|
|
|
4961
5408
|
//# sourceMappingURL=index.mjs.map
|