@napuketto/kernel 0.4.0 → 0.5.1
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 +144 -49
- package/dist/index.d.cts +5 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +5 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +144 -49
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1186,7 +1186,20 @@ function toCanonicalElement(el) {
|
|
|
1186
1186
|
function toCanonicalElements(msg) {
|
|
1187
1187
|
return msg.elements.map(toCanonicalElement);
|
|
1188
1188
|
}
|
|
1189
|
-
/**
|
|
1189
|
+
/**
|
|
1190
|
+
* canonical json → ARK bytesData 字符串:字符串原样透传(OB11 json 段 /
|
|
1191
|
+
* satori 发送链给的已是卡片 JSON 文本),其余值 JSON 序列化。
|
|
1192
|
+
* 与接收方向 arkToCanonical 对称(bytesData 字段名 2026-10-25 真实消息实测)。
|
|
1193
|
+
*/
|
|
1194
|
+
function jsonToArkBytes(raw) {
|
|
1195
|
+
if (typeof raw === "string") return raw;
|
|
1196
|
+
try {
|
|
1197
|
+
return JSON.stringify(raw) ?? "";
|
|
1198
|
+
} catch (err) {
|
|
1199
|
+
throw kernelError(`json 元素序列化失败: ${String(err)}`, "INVALID_PARAM");
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
/** canonical 元素 → NT 发送元素(text/at/face/image/voice/reply 核心 + json ARK 透传)。 */
|
|
1190
1203
|
function toSendElements(elements) {
|
|
1191
1204
|
const out = [];
|
|
1192
1205
|
for (const el of elements) switch (el.type) {
|
|
@@ -1242,9 +1255,14 @@ function toSendElements(elements) {
|
|
|
1242
1255
|
});
|
|
1243
1256
|
break;
|
|
1244
1257
|
case "video": throw kernelError("视频发送需经 MsgApi 预处理(prepareVideoElement)", "INVALID_PARAM");
|
|
1258
|
+
case "json":
|
|
1259
|
+
out.push({
|
|
1260
|
+
elementType: ElementType.ARK,
|
|
1261
|
+
arkElement: { bytesData: jsonToArkBytes(el.raw) }
|
|
1262
|
+
});
|
|
1263
|
+
break;
|
|
1245
1264
|
case "file":
|
|
1246
1265
|
case "forward":
|
|
1247
|
-
case "json":
|
|
1248
1266
|
case "xml":
|
|
1249
1267
|
case "unknown": throw kernelError(`发送元素 ${el.type} 暂不支持(P2-2 探测后补)`, "INVALID_PARAM");
|
|
1250
1268
|
}
|
|
@@ -4538,6 +4556,33 @@ function tryCall(obj, name) {
|
|
|
4538
4556
|
};
|
|
4539
4557
|
}
|
|
4540
4558
|
}
|
|
4559
|
+
/** 服务 getter 判定(get*Service 命名约定)。 */
|
|
4560
|
+
function isServiceGetter(name) {
|
|
4561
|
+
return name.startsWith("get") && name.endsWith("Service");
|
|
4562
|
+
}
|
|
4563
|
+
/** 非空服务实例计数(逐个调用 get*Service 数非空返回;候选可用度评分)。
|
|
4564
|
+
* 自建宿主下 QQ 主 session 未被 init,get*Service 恒 null(2026-10-31 实测
|
|
4565
|
+
* napuketto-probe-late.json),kernel 自建 session 服务可用——主目标按此评分选取。 */
|
|
4566
|
+
function countNonNullServices(session) {
|
|
4567
|
+
if (session === null || session === void 0) return 0;
|
|
4568
|
+
let count = 0;
|
|
4569
|
+
for (const name of listMethods(session)) {
|
|
4570
|
+
if (!isServiceGetter(name)) continue;
|
|
4571
|
+
const call = tryCall(session, name);
|
|
4572
|
+
if (call.ok && call.value !== null && call.value !== void 0) count += 1;
|
|
4573
|
+
}
|
|
4574
|
+
return count;
|
|
4575
|
+
}
|
|
4576
|
+
/** 主探测目标选取:服务实例最多者优先,评分并列保持候选顺序(主 session 在前)。 */
|
|
4577
|
+
function pickPrimaryCandidate(candidates) {
|
|
4578
|
+
let best = candidates[0];
|
|
4579
|
+
for (const candidate of candidates.slice(1)) if (best !== void 0 && candidate.serviceNonNull > best.serviceNonNull) best = candidate;
|
|
4580
|
+
return best ?? {
|
|
4581
|
+
source: "own-create",
|
|
4582
|
+
session: null,
|
|
4583
|
+
serviceNonNull: 0
|
|
4584
|
+
};
|
|
4585
|
+
}
|
|
4541
4586
|
//#endregion
|
|
4542
4587
|
//#region src/wrapper/probe/probe-services.ts
|
|
4543
4588
|
/** 探测关键 export 构造器方法(get/create/getNTWrapperSession 等)。 */
|
|
@@ -4688,24 +4733,37 @@ function probeStartupChain(created) {
|
|
|
4688
4733
|
/** 从 getSessionIdList 结果提取 sessionIds(Map 值或键优先)。 */
|
|
4689
4734
|
function extractSessionIds(ids) {
|
|
4690
4735
|
if (!(ids.ok && ids.value instanceof Map)) return [];
|
|
4691
|
-
|
|
4692
|
-
for (const [k, v] of ids.value) out.push(typeof v === "string" ? v : String(k));
|
|
4693
|
-
return out;
|
|
4736
|
+
return [...ids.value].map(([k, v]) => sessionIdOfEntry(k, v));
|
|
4694
4737
|
}
|
|
4695
|
-
/**
|
|
4696
|
-
function
|
|
4738
|
+
/** 单条 sessionIds 项(Map 值是字符串直接用,否则退化键名)。 */
|
|
4739
|
+
function sessionIdOfEntry(k, v) {
|
|
4740
|
+
return typeof v === "string" ? v : String(k);
|
|
4741
|
+
}
|
|
4742
|
+
/** session 实例方法面(空值记 null)。 */
|
|
4743
|
+
function sessionFace(session) {
|
|
4744
|
+
return session !== null && session !== void 0 ? {
|
|
4745
|
+
methods: listMethods(session),
|
|
4746
|
+
ownKeys: Object.getOwnPropertyNames(session)
|
|
4747
|
+
} : null;
|
|
4748
|
+
}
|
|
4749
|
+
/** 未知异常 → 错误消息字符串。 */
|
|
4750
|
+
function errMsgString(e) {
|
|
4751
|
+
return e instanceof Error ? e.message : String(e);
|
|
4752
|
+
}
|
|
4753
|
+
/** try getNTWrapperSession(id) → session 方法面(fn 缺失记 null,失败记 error)。 */
|
|
4754
|
+
function trySessionFace(ctx, id) {
|
|
4697
4755
|
const fn = getNTWrapperSessionFn(ctx);
|
|
4698
4756
|
if (fn === null) return null;
|
|
4699
4757
|
try {
|
|
4700
|
-
|
|
4701
|
-
return value ? {
|
|
4702
|
-
methods: listMethods(value),
|
|
4703
|
-
ownKeys: Object.getOwnPropertyNames(value)
|
|
4704
|
-
} : null;
|
|
4758
|
+
return sessionFace(fn.call(ctx.exports.NodeIQQNTWrapperSession, id));
|
|
4705
4759
|
} catch (e) {
|
|
4706
|
-
return { error:
|
|
4760
|
+
return { error: errMsgString(e) };
|
|
4707
4761
|
}
|
|
4708
4762
|
}
|
|
4763
|
+
/** 用真实 sessionId 取主 session(getNTWrapperSession(nt_x))并记录方法面。 */
|
|
4764
|
+
function probeSessionById(ctx, id) {
|
|
4765
|
+
return trySessionFace(ctx, id);
|
|
4766
|
+
}
|
|
4709
4767
|
/** 兜底:候选名字 getNTWrapperSession(无 Map 数据时)。 */
|
|
4710
4768
|
function probeByNameFallback(ctx) {
|
|
4711
4769
|
const candidates = [
|
|
@@ -4721,31 +4779,39 @@ function probeByNameFallback(ctx) {
|
|
|
4721
4779
|
}
|
|
4722
4780
|
/** 探测单个候选名(getNTWrapperSession(name),记录方法面或错误)。 */
|
|
4723
4781
|
function probeSessionByName(ctx, name) {
|
|
4724
|
-
|
|
4725
|
-
const fn = getNTWrapperSessionFn(ctx);
|
|
4726
|
-
if (fn === null) return null;
|
|
4727
|
-
const value = fn.call(ctx.exports.NodeIQQNTWrapperSession, name);
|
|
4728
|
-
return value ? {
|
|
4729
|
-
methods: listMethods(value),
|
|
4730
|
-
ownKeys: Object.getOwnPropertyNames(value)
|
|
4731
|
-
} : null;
|
|
4732
|
-
} catch (e) {
|
|
4733
|
-
return { error: e instanceof Error ? e.message : String(e) };
|
|
4734
|
-
}
|
|
4782
|
+
return trySessionFace(ctx, name);
|
|
4735
4783
|
}
|
|
4736
|
-
/**
|
|
4737
|
-
function
|
|
4784
|
+
/** StartupSessionWrapper 可用性判定(缺 create 方法视为不可用)。 */
|
|
4785
|
+
function startupWrapper(ctx) {
|
|
4738
4786
|
const startup = ctx.exports.NodeIQQNTStartupSessionWrapper;
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4787
|
+
return startup === null || typeof startup.create !== "function" ? null : startup;
|
|
4788
|
+
}
|
|
4789
|
+
/** create() 调用结果面(成功记实例方法面 + 序列化值,失败记 error)。 */
|
|
4790
|
+
function createFace(created) {
|
|
4791
|
+
return created.ok ? {
|
|
4743
4792
|
methods: created.value ? listMethods(created.value) : [],
|
|
4744
4793
|
value: serialize(created.value)
|
|
4745
4794
|
} : { error: created.error };
|
|
4795
|
+
}
|
|
4796
|
+
/** create 成功时并入实例链探测结果。 */
|
|
4797
|
+
function assignCreatedChain(out, ctx, created) {
|
|
4746
4798
|
if (created.ok && created.value) Object.assign(out, probeCreatedChain(ctx, created.value));
|
|
4799
|
+
}
|
|
4800
|
+
/** 无 Map 数据时并入候选名兜底结果。 */
|
|
4801
|
+
function assignNameFallback(out, ctx) {
|
|
4802
|
+
if (out["sessionIds"] !== void 0) return;
|
|
4803
|
+
out["getNTWrapperSession"] = probeByNameFallback(ctx);
|
|
4804
|
+
}
|
|
4805
|
+
/** 探测 startup session 链路:create() → start() → getSessionIdList → getNTWrapperSession。 */
|
|
4806
|
+
function probeStartup(ctx) {
|
|
4807
|
+
const startup = startupWrapper(ctx);
|
|
4808
|
+
if (startup === null) return null;
|
|
4809
|
+
const out = { staticMethods: listMethods(startup) };
|
|
4810
|
+
const created = tryCall(startup, "create");
|
|
4811
|
+
out["create"] = createFace(created);
|
|
4812
|
+
assignCreatedChain(out, ctx, created);
|
|
4747
4813
|
out["enumSessionIds"] = enumerateSessionIds(ctx);
|
|
4748
|
-
|
|
4814
|
+
assignNameFallback(out, ctx);
|
|
4749
4815
|
return out;
|
|
4750
4816
|
}
|
|
4751
4817
|
/** 探测 create 后的实例链:start / getSessionIdList / 主 session。 */
|
|
@@ -4767,31 +4833,60 @@ function probeCreatedChain(ctx, created) {
|
|
|
4767
4833
|
function findMainProbeId(sessionIds) {
|
|
4768
4834
|
return sessionIds.find((id) => id.startsWith("nt_")) ?? sessionIds[0];
|
|
4769
4835
|
}
|
|
4770
|
-
/** 探测 session 与 service
|
|
4836
|
+
/** 探测 session 与 service 方法(候选中选服务实例最多者为主目标)。
|
|
4837
|
+
* 自建宿主下 QQ 主 session 未被 init(get*Service 恒 null,2026-10-31 实测
|
|
4838
|
+
* napuketto-probe-late.json 全服务空),kernel 自建 session 已带 adapter init
|
|
4839
|
+
* 服务可用——主目标按 countNonNullServices 评分选取,并列保持主 session 优先。 */
|
|
4771
4840
|
function probeSession(ctx) {
|
|
4772
4841
|
const out = {};
|
|
4773
|
-
const
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
if (
|
|
4842
|
+
const candidates = collectProbeCandidates(ctx).map(scoreCandidate);
|
|
4843
|
+
const primary = pickPrimaryCandidate(candidates);
|
|
4844
|
+
out["sessionSource"] = primary.source;
|
|
4845
|
+
out["session"] = sessionFace(primary.session);
|
|
4846
|
+
if (primary.session !== null && primary.session !== void 0) out["services"] = collectServiceGetters(primary.session);
|
|
4847
|
+
if (candidates.length > 1) out["sessionAlternates"] = alternateFaces(candidates, primary);
|
|
4779
4848
|
return out;
|
|
4780
4849
|
}
|
|
4781
|
-
/**
|
|
4782
|
-
function
|
|
4850
|
+
/** 候选评分(非空服务实例数)。 */
|
|
4851
|
+
function scoreCandidate(candidate) {
|
|
4852
|
+
return {
|
|
4853
|
+
...candidate,
|
|
4854
|
+
serviceNonNull: countNonNullServices(candidate.session)
|
|
4855
|
+
};
|
|
4856
|
+
}
|
|
4857
|
+
/** 落选候选的概要面(方法数 + 评分,诊断用)。 */
|
|
4858
|
+
function alternateFaces(candidates, primary) {
|
|
4859
|
+
return candidates.filter((candidate) => candidate !== primary).map((candidate) => ({
|
|
4860
|
+
source: candidate.source,
|
|
4861
|
+
methodCount: listMethods(candidate.session).length,
|
|
4862
|
+
serviceNonNull: candidate.serviceNonNull
|
|
4863
|
+
}));
|
|
4864
|
+
}
|
|
4865
|
+
/** 收集候选 session:QQ 主 session(若可得)在前 + kernel 自建 session 去重补充。 */
|
|
4866
|
+
function collectProbeCandidates(ctx) {
|
|
4867
|
+
const candidates = [];
|
|
4868
|
+
pushMainCandidate(ctx, candidates);
|
|
4869
|
+
pushOwnCandidate(ctx, candidates);
|
|
4870
|
+
return candidates;
|
|
4871
|
+
}
|
|
4872
|
+
/** 并入 QQ 主 session 候选(枚举失败静默跳过)。 */
|
|
4873
|
+
function pushMainCandidate(ctx, out) {
|
|
4783
4874
|
try {
|
|
4784
4875
|
const main = getMainSession(ctx);
|
|
4785
|
-
if (main !== null) {
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
}
|
|
4876
|
+
if (main !== null) out.push({
|
|
4877
|
+
source: "mainSession(getNTWrapperSession)",
|
|
4878
|
+
session: main
|
|
4879
|
+
});
|
|
4789
4880
|
} catch {}
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
return
|
|
4881
|
+
}
|
|
4882
|
+
/** 并入 kernel 自建 session 候选(与已有候选同一实例时去重)。 */
|
|
4883
|
+
function pushOwnCandidate(ctx, out) {
|
|
4884
|
+
if (ctx.session === null) return;
|
|
4885
|
+
if (out.some((candidate) => candidate.session === ctx.session)) return;
|
|
4886
|
+
out.push({
|
|
4887
|
+
source: "own-create",
|
|
4888
|
+
session: ctx.session
|
|
4889
|
+
});
|
|
4795
4890
|
}
|
|
4796
4891
|
/** 收集 session 的 get*Service getter 方法面与返回形状。 */
|
|
4797
4892
|
function collectServiceGetters(session) {
|