@napuketto/adapter 0.0.20 → 0.0.22
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/onebot11/index.d.mts +44 -4
- package/dist/onebot11/index.d.mts.map +1 -1
- package/dist/onebot11/index.mjs +428 -89
- package/dist/onebot11/index.mjs.map +1 -1
- package/dist/{raw-message-DfHdOVHi.mjs → raw-message-Bx18pIQS.mjs} +2 -2
- package/dist/raw-message-Bx18pIQS.mjs.map +1 -0
- package/dist/satori/index.d.mts +5 -0
- package/dist/satori/index.d.mts.map +1 -1
- package/dist/satori/index.mjs +30 -6
- package/dist/satori/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/raw-message-DfHdOVHi.mjs.map +0 -1
package/dist/onebot11/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { i as BaseAction, o as ActionRegistry, r as BaseProtocolAdapter, t as ensureSilk } from "../core-of7sLiQX.mjs";
|
|
2
|
-
import { i as createWsServerSchema, n as collectGrayTipUids, r as createHttpServerSchema, t as forEachRawMessage } from "../raw-message-
|
|
3
|
-
import { ChatType, GrayTipSubType, GroupApi, GroupNotifyMsgType, NTGroupMemberRole, NTGroupRequestOperateTypes, TicketApi, TipGroupElementType, WebHonorType, kernelError, toCanonicalElements } from "@napuketto/kernel";
|
|
2
|
+
import { i as createWsServerSchema, n as collectGrayTipUids, r as createHttpServerSchema, t as forEachRawMessage } from "../raw-message-Bx18pIQS.mjs";
|
|
3
|
+
import { ChatType, GrayTipSubType, GroupApi, GroupNotifyMsgStatus, GroupNotifyMsgType, NTGroupMemberRole, NTGroupRequestOperateTypes, TicketApi, TipGroupElementType, WebHonorType, kernelError, toCanonicalElements } from "@napuketto/kernel";
|
|
4
|
+
import { downloadUrl, inferExtension } from "@napuketto/media";
|
|
4
5
|
import { z } from "zod";
|
|
6
|
+
import { randomUUID } from "node:crypto";
|
|
5
7
|
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
6
8
|
import { basename, join } from "node:path";
|
|
7
9
|
import { HttpClient, HttpServer, WsClient, WsServer } from "@napuketto/network";
|
|
@@ -1534,6 +1536,10 @@ const CANONICAL_TO_SEGMENT = {
|
|
|
1534
1536
|
type: "video",
|
|
1535
1537
|
data: mediaData(el.path, el.url)
|
|
1536
1538
|
}),
|
|
1539
|
+
file: (el) => ({
|
|
1540
|
+
type: "file",
|
|
1541
|
+
data: { file: el.name ?? el.path }
|
|
1542
|
+
}),
|
|
1537
1543
|
reply: (el) => ({
|
|
1538
1544
|
type: "reply",
|
|
1539
1545
|
data: { id: el.messageId }
|
|
@@ -1624,6 +1630,10 @@ const CQ_SEGMENT_BUILDERS = {
|
|
|
1624
1630
|
type: "video",
|
|
1625
1631
|
data: mediaData(params["file"] ?? "", params["url"])
|
|
1626
1632
|
}),
|
|
1633
|
+
file: (params) => ({
|
|
1634
|
+
type: "file",
|
|
1635
|
+
data: { file: params["file"] ?? "" }
|
|
1636
|
+
}),
|
|
1627
1637
|
reply: (params) => ({
|
|
1628
1638
|
type: "reply",
|
|
1629
1639
|
data: { id: params["id"] ?? "" }
|
|
@@ -1719,6 +1729,12 @@ const ob11ConfigSchema = z.object({
|
|
|
1719
1729
|
reportSelfMessage: z.boolean().default(false),
|
|
1720
1730
|
/** 消息内容格式:array = 消息段数组(标准),string = CQ 码字符串。 */
|
|
1721
1731
|
messagePostFormat: z.enum(MESSAGE_FORMATS).default("array"),
|
|
1732
|
+
/**
|
|
1733
|
+
* 群文件报形式(B4,2026-09-08):false(缺省)= message 事件 + file 段
|
|
1734
|
+
* (go-cqhttp 兼容;此前 file 元素被静默丢弃,本版本起透出);true = 含
|
|
1735
|
+
* fileElement 的群消息改报 group_upload notice 事件(替代 message 事件)。
|
|
1736
|
+
*/
|
|
1737
|
+
groupUploadAsNotice: z.boolean().default(false),
|
|
1722
1738
|
/** HTTP 反向服务器列表。 */
|
|
1723
1739
|
httpServers: z.array(httpServerSchema).default([]),
|
|
1724
1740
|
/** HTTP 正向上报列表。 */
|
|
@@ -1853,7 +1869,7 @@ function cqMessageToCanonical(text) {
|
|
|
1853
1869
|
//#endregion
|
|
1854
1870
|
//#region src/onebot11/helper/message-info.ts
|
|
1855
1871
|
/** 毫秒 → 秒(Unix 时间戳)。 */
|
|
1856
|
-
const MS_TO_SEC$
|
|
1872
|
+
const MS_TO_SEC$4 = 1e3;
|
|
1857
1873
|
/** RawMessage → OB11 消息信息(message_id 经 MessageUnique 映射)。
|
|
1858
1874
|
* ctx(可选):接收方向 ID 转换上下文(at uid→uin、reply NT msgId→OB11 id,P2-19)。 */
|
|
1859
1875
|
function toOb11MessageInfo(msg, unique, ctx = {}) {
|
|
@@ -1870,7 +1886,7 @@ function toOb11MessageInfo(msg, unique, ctx = {}) {
|
|
|
1870
1886
|
user_id: Number(msg.senderUin),
|
|
1871
1887
|
nickname: msg.sendNickName
|
|
1872
1888
|
},
|
|
1873
|
-
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$
|
|
1889
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$4),
|
|
1874
1890
|
message: canonicalToSegments(elements),
|
|
1875
1891
|
raw_message: canonicalToCqMessage(elements)
|
|
1876
1892
|
};
|
|
@@ -1884,58 +1900,81 @@ function toOb11MessageInfo(msg, unique, ctx = {}) {
|
|
|
1884
1900
|
}
|
|
1885
1901
|
//#endregion
|
|
1886
1902
|
//#region src/onebot11/helper/notice.ts
|
|
1887
|
-
/**
|
|
1888
|
-
* OB11 notice 事件翻译(P2-7,2026-08-05)
|
|
1889
|
-
*
|
|
1890
|
-
* NT QQ 中群成员变动/撤回等系统事件通过**消息的灰色提示元素(grayTip)**广播。
|
|
1891
|
-
* 本模块把 RawMessage 里的 grayTip 翻译为 OB11 notice 事件(纯函数,ADR-008)。
|
|
1892
|
-
*
|
|
1893
|
-
* 支持:
|
|
1894
|
-
* - group_recall(撤回,grayTip.subElementType=REVOKE)
|
|
1895
|
-
* - group_increase / group_decrease(群成员变动,subElementType=GROUP + TipGroupElement.type)
|
|
1896
|
-
* - group_admin(管理员变更,TipGroupElement.type=BLOCK/UNBLOCK 的 role 语义)
|
|
1897
|
-
* - group_ban(禁言,TipGroupElement.type=SHUT_UP)
|
|
1898
|
-
*
|
|
1899
|
-
* user_id/operator_id 都是 uin:接收 uidToUin Map(调用方批量转换后传入,保持纯函数)。
|
|
1900
|
-
*/
|
|
1901
1903
|
/** 毫秒 → 秒(Unix 时间戳)。 */
|
|
1902
|
-
const MS_TO_SEC$
|
|
1904
|
+
const MS_TO_SEC$3 = 1e3;
|
|
1905
|
+
/** 群文件业务类型(group_upload 的 file.busid,与 kernel 群文件系列同源语义)。 */
|
|
1906
|
+
const GROUP_FILE_BUS_ID = 102;
|
|
1907
|
+
/** 检查消息是否含文件元素(group_upload 开关分支判定)。 */
|
|
1908
|
+
function hasFileElement(msg) {
|
|
1909
|
+
return msg.elements.some((el) => el.fileElement !== void 0);
|
|
1910
|
+
}
|
|
1903
1911
|
/** 检查消息是否含可翻译的 grayTip 元素。 */
|
|
1904
1912
|
function hasGrayTip(msg) {
|
|
1905
1913
|
return msg.elements.some((el) => el.grayTipElement !== void 0);
|
|
1906
1914
|
}
|
|
1907
1915
|
/** uid → uin(上下文映射缺省退化为 uid)。 */
|
|
1908
|
-
function toUin(uid, ctx) {
|
|
1916
|
+
function toUin$1(uid, ctx) {
|
|
1909
1917
|
const uin = ctx.uidToUin.get(uid);
|
|
1910
1918
|
return Number(uin ?? uid);
|
|
1911
1919
|
}
|
|
1912
1920
|
/** 基础字段(time/self_id/post_type)。 */
|
|
1913
1921
|
function base(msg, ctx) {
|
|
1914
1922
|
return {
|
|
1915
|
-
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$
|
|
1923
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$3),
|
|
1916
1924
|
self_id: Number(ctx.selfUin),
|
|
1917
1925
|
post_type: "notice",
|
|
1918
1926
|
group_id: Number(msg.peerUid)
|
|
1919
1927
|
};
|
|
1920
1928
|
}
|
|
1921
|
-
/** 撤回事件(group_recall)。 */
|
|
1929
|
+
/** 撤回事件(chatType 分流:GROUP→group_recall / C2C→friend_recall,2026-09-08)。 */
|
|
1922
1930
|
function toRecall(msg, g, ctx) {
|
|
1923
1931
|
if (g?.revokeElement === void 0) return null;
|
|
1924
1932
|
const revoke = g.revokeElement;
|
|
1933
|
+
if (msg.chatType === ChatType.C2C) return {
|
|
1934
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$3),
|
|
1935
|
+
self_id: Number(ctx.selfUin),
|
|
1936
|
+
post_type: "notice",
|
|
1937
|
+
notice_type: "friend_recall",
|
|
1938
|
+
user_id: toUin$1(revoke.operatorUid, ctx),
|
|
1939
|
+
message_id: Number(msg.msgSeq)
|
|
1940
|
+
};
|
|
1925
1941
|
return {
|
|
1926
1942
|
...base(msg, ctx),
|
|
1927
1943
|
notice_type: "group_recall",
|
|
1928
|
-
user_id: toUin(revoke.operatorUid, ctx),
|
|
1929
|
-
operator_id: toUin(revoke.operatorUid, ctx),
|
|
1944
|
+
user_id: toUin$1(revoke.operatorUid, ctx),
|
|
1945
|
+
operator_id: toUin$1(revoke.operatorUid, ctx),
|
|
1930
1946
|
message_id: Number(msg.msgSeq)
|
|
1931
1947
|
};
|
|
1932
1948
|
}
|
|
1949
|
+
/**
|
|
1950
|
+
* 戳一戳(notify.poke,aioOpGrayTipElement,2026-09-08)。
|
|
1951
|
+
*
|
|
1952
|
+
* ⚠️ 待真实 poke 事件验证:aioOp 载荷的完整字段形状未经实测(实体类型仅有
|
|
1953
|
+
* operateType/peerUid 两个已证字段)。当前口径:user_id = 消息发送者,
|
|
1954
|
+
* target_id = aioOp.peerUid(C2C 即对端;群内为目标 uid 的假设待校准),
|
|
1955
|
+
* group_id = 群号(C2C 为 0)。poke 路径始终打 raw 日志积累校准数据。
|
|
1956
|
+
*/
|
|
1957
|
+
function toPoke(msg, g, ctx) {
|
|
1958
|
+
const aioOp = g?.aioOpGrayTipElement;
|
|
1959
|
+
ctx.logger?.warn({ raw: JSON.stringify(g)?.slice(0, 2e3) }, "ob11: poke grayTip raw(待真实事件校准)");
|
|
1960
|
+
const isGroup = msg.chatType === ChatType.GROUP;
|
|
1961
|
+
return {
|
|
1962
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$3),
|
|
1963
|
+
self_id: Number(ctx.selfUin),
|
|
1964
|
+
post_type: "notice",
|
|
1965
|
+
notice_type: "notify",
|
|
1966
|
+
sub_type: "poke",
|
|
1967
|
+
group_id: isGroup ? Number(msg.peerUid) : 0,
|
|
1968
|
+
user_id: toUin$1(msg.senderUid, ctx),
|
|
1969
|
+
target_id: toUin$1(aioOp?.peerUid ?? "", ctx)
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1933
1972
|
/** 群成员变动(group_increase / group_decrease / group_admin / group_ban)。 */
|
|
1934
1973
|
function toGroupChange(msg, g, ctx) {
|
|
1935
1974
|
const grp = g?.groupElement;
|
|
1936
1975
|
if (grp === void 0 || grp.type === void 0) return null;
|
|
1937
1976
|
const common = {
|
|
1938
|
-
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$
|
|
1977
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$3),
|
|
1939
1978
|
selfId: Number(ctx.selfUin),
|
|
1940
1979
|
groupId: Number(msg.peerUid),
|
|
1941
1980
|
memberUid: grp.memberUid ?? "",
|
|
@@ -1960,8 +1999,8 @@ function toMemberAdd(common, ctx) {
|
|
|
1960
1999
|
notice_type: "group_increase",
|
|
1961
2000
|
sub_type: adminUid === memberUid ? "approve" : "invite",
|
|
1962
2001
|
group_id: groupId,
|
|
1963
|
-
operator_id: toUin(adminUid, ctx),
|
|
1964
|
-
user_id: toUin(memberUid, ctx)
|
|
2002
|
+
operator_id: toUin$1(adminUid, ctx),
|
|
2003
|
+
user_id: toUin$1(memberUid, ctx)
|
|
1965
2004
|
};
|
|
1966
2005
|
}
|
|
1967
2006
|
/** 群成员退出(leave)。 */
|
|
@@ -1974,8 +2013,8 @@ function toQuit(common, ctx) {
|
|
|
1974
2013
|
notice_type: "group_decrease",
|
|
1975
2014
|
sub_type: "leave",
|
|
1976
2015
|
group_id: groupId,
|
|
1977
|
-
operator_id: toUin(adminUid, ctx),
|
|
1978
|
-
user_id: toUin(memberUid, ctx)
|
|
2016
|
+
operator_id: toUin$1(adminUid, ctx),
|
|
2017
|
+
user_id: toUin$1(memberUid, ctx)
|
|
1979
2018
|
};
|
|
1980
2019
|
}
|
|
1981
2020
|
/** 管理员变更(BLOCK=set / UNBLOCK=unset)。 */
|
|
@@ -1988,7 +2027,7 @@ function toAdminChange(common, grp, ctx) {
|
|
|
1988
2027
|
notice_type: "group_admin",
|
|
1989
2028
|
sub_type: grp.type === TipGroupElementType.BLOCK ? "set" : "unset",
|
|
1990
2029
|
group_id: groupId,
|
|
1991
|
-
user_id: toUin(memberUid === "" ? adminUid : memberUid, ctx)
|
|
2030
|
+
user_id: toUin$1(memberUid === "" ? adminUid : memberUid, ctx)
|
|
1992
2031
|
};
|
|
1993
2032
|
}
|
|
1994
2033
|
/** 禁言(duration>0=ban / 否则 lift_ban)。 */
|
|
@@ -2002,29 +2041,167 @@ function toBan(common, grp, ctx) {
|
|
|
2002
2041
|
notice_type: "group_ban",
|
|
2003
2042
|
sub_type: Number(shutUp?.duration ?? 0) > 0 ? "ban" : "lift_ban",
|
|
2004
2043
|
group_id: groupId,
|
|
2005
|
-
operator_id: toUin(shutUp?.admin?.uid ?? adminUid, ctx),
|
|
2006
|
-
user_id: toUin(shutUp?.member?.uid ?? memberUid, ctx),
|
|
2044
|
+
operator_id: toUin$1(shutUp?.admin?.uid ?? adminUid, ctx),
|
|
2045
|
+
user_id: toUin$1(shutUp?.member?.uid ?? memberUid, ctx),
|
|
2007
2046
|
duration: Number(shutUp?.duration ?? 0)
|
|
2008
2047
|
};
|
|
2009
2048
|
}
|
|
2010
|
-
/**
|
|
2049
|
+
/**
|
|
2050
|
+
* 好友添加(friend_add,B2,2026-09-08)。
|
|
2051
|
+
* 数据源 = kernel BuddyCache 快照 diff(Buddy/onBuddyListChange 全量快照对比,
|
|
2052
|
+
* 首帧 baseline 不发)——非 grayTip 路径。user_id 取 coreInfo.uin(顶层 uin 兜底)。
|
|
2053
|
+
*/
|
|
2054
|
+
function toFriendAdd(entry, selfUin) {
|
|
2055
|
+
const uin = entry.coreInfo?.uin ?? entry.uin ?? "";
|
|
2056
|
+
return {
|
|
2057
|
+
time: Math.floor(Date.now() / MS_TO_SEC$3),
|
|
2058
|
+
self_id: Number(selfUin),
|
|
2059
|
+
post_type: "notice",
|
|
2060
|
+
notice_type: "friend_add",
|
|
2061
|
+
user_id: Number(uin)
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* 群文件上传(group_upload,B4,2026-09-08)。
|
|
2066
|
+
* 数据源 = 含 fileElement 的群消息(开关 groupUploadAsNotice=true 时替代
|
|
2067
|
+
* message 事件报出)。file.id 取 fileUuid(缺省文件名)、size 取 fileSize、
|
|
2068
|
+
* busid 固定 102(群文件业务类型,与 kernel GROUP_FILE_BIZ_TYPE 同源语义)。
|
|
2069
|
+
*/
|
|
2070
|
+
function toGroupUpload(msg, selfUin) {
|
|
2071
|
+
const file = msg.elements.find((e) => e.fileElement !== void 0)?.fileElement;
|
|
2072
|
+
if (file === void 0) return null;
|
|
2073
|
+
return {
|
|
2074
|
+
time: Math.floor(Number(msg.msgTime) / MS_TO_SEC$3),
|
|
2075
|
+
self_id: Number(selfUin),
|
|
2076
|
+
post_type: "notice",
|
|
2077
|
+
notice_type: "group_upload",
|
|
2078
|
+
group_id: Number(msg.peerUid),
|
|
2079
|
+
user_id: Number(msg.senderUin),
|
|
2080
|
+
file: {
|
|
2081
|
+
id: file.fileUuid ?? file.fileName ?? "",
|
|
2082
|
+
name: file.fileName ?? "",
|
|
2083
|
+
size: Number(file.fileSize ?? 0),
|
|
2084
|
+
busid: GROUP_FILE_BUS_ID
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
}
|
|
2088
|
+
/** 单个 grayTip 元素 → notice 事件(null = 不可翻译;未知子类型打 raw 日志)。 */
|
|
2089
|
+
function translateGrayTip(msg, g, ctx) {
|
|
2090
|
+
const subType = g.subElementType;
|
|
2091
|
+
if (subType === GrayTipSubType.REVOKE) return toRecall(msg, g, ctx);
|
|
2092
|
+
if (subType === GrayTipSubType.GROUP) return toGroupChange(msg, g, ctx);
|
|
2093
|
+
if (g.aioOpGrayTipElement !== void 0) return toPoke(msg, g, ctx);
|
|
2094
|
+
ctx.logger?.warn({
|
|
2095
|
+
subType,
|
|
2096
|
+
raw: JSON.stringify(g)?.slice(0, 2e3)
|
|
2097
|
+
}, "ob11: 未翻译的 grayTip 子类型(raw 校准数据)");
|
|
2098
|
+
return null;
|
|
2099
|
+
}
|
|
2100
|
+
/** RawMessage → OB11 notice 事件(无 grayTip 返回 null)。
|
|
2101
|
+
* 未知/未翻译子类型打 raw JSON 日志(校准数据积累,2026-09-08)。 */
|
|
2011
2102
|
function toOb11NoticeEvent(msg, ctx) {
|
|
2012
2103
|
for (const el of msg.elements) {
|
|
2013
2104
|
const g = el.grayTipElement;
|
|
2014
2105
|
if (g === void 0) continue;
|
|
2015
|
-
const
|
|
2016
|
-
if (
|
|
2017
|
-
const recall = toRecall(msg, g, ctx);
|
|
2018
|
-
if (recall !== null) return recall;
|
|
2019
|
-
}
|
|
2020
|
-
if (subType === GrayTipSubType.GROUP) {
|
|
2021
|
-
const change = toGroupChange(msg, g, ctx);
|
|
2022
|
-
if (change !== null) return change;
|
|
2023
|
-
}
|
|
2106
|
+
const event = translateGrayTip(msg, g, ctx);
|
|
2107
|
+
if (event !== null) return event;
|
|
2024
2108
|
}
|
|
2025
2109
|
return null;
|
|
2026
2110
|
}
|
|
2027
2111
|
//#endregion
|
|
2112
|
+
//#region src/onebot11/helper/request.ts
|
|
2113
|
+
/**
|
|
2114
|
+
* OB11 request 事件翻译(2026-09-08)
|
|
2115
|
+
*
|
|
2116
|
+
* 事件源(kernel 事件通道,本次接线):
|
|
2117
|
+
* - `Group/onGroupNotifiesUpdated`(GroupBridge,群系统通知推送)→ group_add / group_invite
|
|
2118
|
+
* - `Buddy/onBuddyReqChange`(FriendBridge,好友申请推送)→ friend
|
|
2119
|
+
*
|
|
2120
|
+
* flag 语义(与应答动作匹配路径一致):
|
|
2121
|
+
* - 群请求 flag = GroupNotify.seq(set_group_add_request 按 `item.seq === flag` 匹配)
|
|
2122
|
+
* - 好友请求 flag = BuddyReq.reqTime(set_friend_add_request 按 `reqTime === flag` 匹配)
|
|
2123
|
+
*
|
|
2124
|
+
* 纯函数(ADR-008):uid→uin 由调用方批量转换后传入。
|
|
2125
|
+
* ⚠️ onBuddyReqChange 回调参数形状未经真实事件校准(wrapper 字符串证据仅有方法名),
|
|
2126
|
+
* narrowBuddyReqs 防御性收窄,未知形状返回 null 由调用方打 raw 日志积累校准数据。
|
|
2127
|
+
*/
|
|
2128
|
+
/** 毫秒 → 秒(Unix 时间戳)。 */
|
|
2129
|
+
const MS_TO_SEC$2 = 1e3;
|
|
2130
|
+
/** uid → uin(上下文映射缺省时:数字 uid 直取数值,非数字 uid 返回 0 表示未知)。 */
|
|
2131
|
+
function toUin(uid, ctx) {
|
|
2132
|
+
if (uid === void 0 || uid === "") return 0;
|
|
2133
|
+
const uin = ctx.uidToUin.get(uid);
|
|
2134
|
+
if (uin !== void 0) return Number(uin);
|
|
2135
|
+
const n = Number(uid);
|
|
2136
|
+
return Number.isFinite(n) ? n : 0;
|
|
2137
|
+
}
|
|
2138
|
+
/**
|
|
2139
|
+
* GroupNotify → OB11 群请求事件(group_add / group_invite)。
|
|
2140
|
+
*
|
|
2141
|
+
* type 语义与 get_group_system_msg 动作同一解释:1/5=邀请(invite)、7=申请(add);
|
|
2142
|
+
* 仅未处理状态(KUNHANDLE)推送——已处理通知重复推送不再发事件。
|
|
2143
|
+
* 非请求类通知(踢人/转让等处理结果回执)返回 null。
|
|
2144
|
+
*/
|
|
2145
|
+
function toOb11GroupRequestEvent(notify, ctx) {
|
|
2146
|
+
if (notify.status !== GroupNotifyMsgStatus.KUNHANDLE) return null;
|
|
2147
|
+
let subType;
|
|
2148
|
+
switch (notify.type) {
|
|
2149
|
+
case GroupNotifyMsgType.INVITED_BY_MEMBER:
|
|
2150
|
+
case GroupNotifyMsgType.INVITED_NEED_ADMINI_STRATOR_PASS:
|
|
2151
|
+
subType = "invite";
|
|
2152
|
+
break;
|
|
2153
|
+
case GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS:
|
|
2154
|
+
subType = "add";
|
|
2155
|
+
break;
|
|
2156
|
+
default: return null;
|
|
2157
|
+
}
|
|
2158
|
+
return {
|
|
2159
|
+
time: Math.floor(Date.now() / MS_TO_SEC$2),
|
|
2160
|
+
self_id: Number(ctx.selfUin),
|
|
2161
|
+
post_type: "request",
|
|
2162
|
+
request_type: "group",
|
|
2163
|
+
sub_type: subType,
|
|
2164
|
+
group_id: Number(notify.group?.groupCode ?? 0),
|
|
2165
|
+
user_id: toUin(notify.user1?.uid, ctx),
|
|
2166
|
+
comment: notify.postscript ?? "",
|
|
2167
|
+
flag: String(notify.seq)
|
|
2168
|
+
};
|
|
2169
|
+
}
|
|
2170
|
+
/** BuddyReq 防御性收窄:仅接受字段形状可用的对象(onBuddyReqChange 参数待真实事件校准)。 */
|
|
2171
|
+
function isBuddyReqLike(value) {
|
|
2172
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2173
|
+
const v = value;
|
|
2174
|
+
return typeof v["reqTime"] === "string" && typeof v["friendUid"] === "string";
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
2177
|
+
* onBuddyReqChange 回调参数 → BuddyReq 列表(防御性收窄)。
|
|
2178
|
+
* 已知候选形状:BuddyReq[] / { buddyReqs: BuddyReq[] };其余返回 null(调用方打 raw 日志)。
|
|
2179
|
+
*/
|
|
2180
|
+
function narrowBuddyReqs(arg) {
|
|
2181
|
+
if (Array.isArray(arg)) return arg.every(isBuddyReqLike) ? arg : null;
|
|
2182
|
+
if (typeof arg === "object" && arg !== null) {
|
|
2183
|
+
const reqs = arg["buddyReqs"];
|
|
2184
|
+
if (Array.isArray(reqs) && reqs.every(isBuddyReqLike)) return reqs;
|
|
2185
|
+
}
|
|
2186
|
+
return null;
|
|
2187
|
+
}
|
|
2188
|
+
/**
|
|
2189
|
+
* BuddyReq → OB11 好友请求事件(friend)。
|
|
2190
|
+
* comment 取 words 字段(验证留言,形状待校准,缺省空串);flag = reqTime。
|
|
2191
|
+
*/
|
|
2192
|
+
function toOb11FriendRequestEvent(req, ctx) {
|
|
2193
|
+
const words = req["words"];
|
|
2194
|
+
return {
|
|
2195
|
+
time: Math.floor(Date.now() / MS_TO_SEC$2),
|
|
2196
|
+
self_id: Number(ctx.selfUin),
|
|
2197
|
+
post_type: "request",
|
|
2198
|
+
request_type: "friend",
|
|
2199
|
+
user_id: toUin(req.friendUid, ctx),
|
|
2200
|
+
comment: typeof words === "string" ? words : "",
|
|
2201
|
+
flag: req.reqTime
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
//#endregion
|
|
2028
2205
|
//#region src/onebot11/action/message/get-forward-msg.ts
|
|
2029
2206
|
/**
|
|
2030
2207
|
* get_forward_msg 动作:获取合并转发内容(P2-12 接 kernel MsgApi.fetchForwardMessage)
|
|
@@ -2156,10 +2333,17 @@ var GetGroupMsgHistoryAction = class extends BaseAction {
|
|
|
2156
2333
|
//#endregion
|
|
2157
2334
|
//#region src/onebot11/action/message/get-media.ts
|
|
2158
2335
|
/**
|
|
2159
|
-
* get_image / get_record
|
|
2336
|
+
* get_image / get_record 动作:获取图片/语音文件(P2-14;2026-09-08 T5 接主动下载;
|
|
2337
|
+
* 2026-09-08 B1 语音接原生 downloadRichMedia)
|
|
2160
2338
|
*
|
|
2161
|
-
* message_id 反查 → fetchMsgsByMsgId → 找 PIC/PTT
|
|
2162
|
-
*
|
|
2339
|
+
* message_id 反查 → fetchMsgsByMsgId → 找 PIC/PTT 元素:
|
|
2340
|
+
* - 本地文件解析:NT 相对路径(sourcePath/filePath)按 mediaBaseDir(QQ NT
|
|
2341
|
+
* global 目录)解析为绝对路径,命中磁盘即返回 file
|
|
2342
|
+
* - 图片主动下载:本地未命中且有 picUrl → @napuketto/media downloadUrl 落
|
|
2343
|
+
* cacheDir/media/,返回 file(绝对路径)+ url + file_size/file_name
|
|
2344
|
+
* - 语音主动下载(B1):本地未命中 → kernel MsgApi.downloadPtt(原生
|
|
2345
|
+
* downloadRichMedia + 轮询元素回查)→ filePath 落盘后返回绝对路径;
|
|
2346
|
+
* 失败回退原始 filePath(NT 相对/绝对)+ 元数据,不抛错
|
|
2163
2347
|
*/
|
|
2164
2348
|
const getMediaSchema = z.object({
|
|
2165
2349
|
/** CQ 码 file 字段或 message_id(简化:message_id 反查)。 */
|
|
@@ -2167,6 +2351,35 @@ const getMediaSchema = z.object({
|
|
|
2167
2351
|
message_id: z.union([z.number(), z.string()]).optional()
|
|
2168
2352
|
});
|
|
2169
2353
|
/**
|
|
2354
|
+
* NT 相对路径 → 本地绝对路径(存在才返回)。
|
|
2355
|
+
* 候选:原样绝对路径 / mediaBaseDir 下相对路径(反斜杠归一化)。
|
|
2356
|
+
*/
|
|
2357
|
+
function resolveLocalMediaFile(relPath, deps) {
|
|
2358
|
+
if (relPath === "") return null;
|
|
2359
|
+
if (existsSync(relPath)) return relPath;
|
|
2360
|
+
const base = deps.mediaBaseDir;
|
|
2361
|
+
if (base !== void 0 && base !== "") {
|
|
2362
|
+
const joined = join(base, relPath.replaceAll("\\", "/"));
|
|
2363
|
+
if (existsSync(joined)) return joined;
|
|
2364
|
+
}
|
|
2365
|
+
return null;
|
|
2366
|
+
}
|
|
2367
|
+
/** 图片主动下载(picUrl → cacheDir/media/<uuid>.<ext>;无 cacheDir/url 返回 null)。 */
|
|
2368
|
+
async function downloadImageToCache(url, deps) {
|
|
2369
|
+
const cacheDir = deps.cacheDir;
|
|
2370
|
+
if (cacheDir === void 0 || cacheDir === "") return null;
|
|
2371
|
+
const mediaDir = join(cacheDir, "media");
|
|
2372
|
+
const { mkdir } = await import("node:fs/promises");
|
|
2373
|
+
await mkdir(mediaDir, { recursive: true });
|
|
2374
|
+
const dest = join(mediaDir, `${randomUUID()}${inferExtension(null, url)}`);
|
|
2375
|
+
try {
|
|
2376
|
+
await downloadUrl(url, dest);
|
|
2377
|
+
return dest;
|
|
2378
|
+
} catch {
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2170
2383
|
* 反查消息并提取媒体元素路径。
|
|
2171
2384
|
* 返回 null = 消息不存在;extract 返回 undefined = 无匹配元素。
|
|
2172
2385
|
*/
|
|
@@ -2180,26 +2393,13 @@ async function resolveMedia(id, label, deps, extract) {
|
|
|
2180
2393
|
}
|
|
2181
2394
|
throw kernelError(`消息 ${id} 不包含${label}`, "NOT_FOUND");
|
|
2182
2395
|
}
|
|
2183
|
-
/**
|
|
2184
|
-
function
|
|
2185
|
-
const
|
|
2186
|
-
if (
|
|
2187
|
-
|
|
2188
|
-
const path = pic.sourcePath ?? pic.picPath;
|
|
2189
|
-
if (path !== void 0) out.file = path;
|
|
2190
|
-
if (pic.picUrl !== void 0) out.url = pic.picUrl;
|
|
2191
|
-
if (Object.keys(out).length === 0) return;
|
|
2192
|
-
return out;
|
|
2193
|
-
}
|
|
2194
|
-
const ptt = element.pttElement;
|
|
2195
|
-
if (ptt !== void 0 && ptt.filePath !== void 0) return { file: ptt.filePath };
|
|
2196
|
-
}
|
|
2197
|
-
/** 提取语音元素(get_record 用)。 */
|
|
2198
|
-
function extractPttPath(element) {
|
|
2199
|
-
const ptt = element.pttElement;
|
|
2200
|
-
if (ptt !== void 0 && ptt.filePath !== void 0) return { file: ptt.filePath };
|
|
2396
|
+
/** 取 id 参数(message_id 优先,file 兜底)。 */
|
|
2397
|
+
function takeId(payload, actionName) {
|
|
2398
|
+
const id = payload.message_id ?? payload.file;
|
|
2399
|
+
if (id === void 0) throw kernelError(`${actionName} 需要 message_id 或 file`, "INVALID_PARAM");
|
|
2400
|
+
return id;
|
|
2201
2401
|
}
|
|
2202
|
-
/**
|
|
2402
|
+
/** 获取图片信息(本地优先,picUrl 主动下载兜底)。 */
|
|
2203
2403
|
var GetImageAction = class extends BaseAction {
|
|
2204
2404
|
name = "get_image";
|
|
2205
2405
|
schema = getMediaSchema;
|
|
@@ -2209,16 +2409,29 @@ var GetImageAction = class extends BaseAction {
|
|
|
2209
2409
|
super();
|
|
2210
2410
|
this.deps = deps;
|
|
2211
2411
|
}
|
|
2212
|
-
_handle(payload) {
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2412
|
+
async _handle(payload) {
|
|
2413
|
+
const base = await resolveMedia(takeId(payload, "get_image"), "图片", this.deps, (el) => {
|
|
2414
|
+
const pic = el.picElement;
|
|
2415
|
+
if (pic === void 0) return;
|
|
2416
|
+
const out = {};
|
|
2417
|
+
if (pic.fileSize !== void 0) out.file_size = pic.fileSize;
|
|
2418
|
+
if (pic.fileName !== void 0) out.file_name = pic.fileName;
|
|
2419
|
+
if (pic.picUrl !== void 0) out.url = pic.picUrl;
|
|
2420
|
+
const rel = pic.sourcePath ?? pic.picPath;
|
|
2421
|
+
if (rel !== void 0) {
|
|
2422
|
+
const local = resolveLocalMediaFile(rel, this.deps);
|
|
2423
|
+
if (local !== null) out.file = local;
|
|
2424
|
+
}
|
|
2425
|
+
return out;
|
|
2426
|
+
});
|
|
2427
|
+
if (base.file === void 0 && base.url !== void 0) {
|
|
2428
|
+
const downloaded = await downloadImageToCache(base.url, this.deps);
|
|
2429
|
+
if (downloaded !== null) base.file = downloaded;
|
|
2430
|
+
}
|
|
2431
|
+
return base;
|
|
2219
2432
|
}
|
|
2220
2433
|
};
|
|
2221
|
-
/**
|
|
2434
|
+
/** 获取语音信息(本地优先;未命中走原生 downloadRichMedia,失败回退原始路径)。 */
|
|
2222
2435
|
var GetRecordAction = class extends BaseAction {
|
|
2223
2436
|
name = "get_record";
|
|
2224
2437
|
schema = getMediaSchema;
|
|
@@ -2229,9 +2442,37 @@ var GetRecordAction = class extends BaseAction {
|
|
|
2229
2442
|
this.deps = deps;
|
|
2230
2443
|
}
|
|
2231
2444
|
async _handle(payload) {
|
|
2232
|
-
const id = payload
|
|
2233
|
-
|
|
2234
|
-
|
|
2445
|
+
const id = takeId(payload, "get_record");
|
|
2446
|
+
const { msgId, peer } = resolveMsgIdAndPeer(id, this.deps.messageUnique);
|
|
2447
|
+
const [first] = await this.deps.msgApi.fetchMsgsByMsgId(peer, [msgId]);
|
|
2448
|
+
if (first === void 0) throw kernelError(`消息 ${id} 不存在或已被撤回`, "NOT_FOUND");
|
|
2449
|
+
const ptt = first.elements.find((e) => e.pttElement !== void 0)?.pttElement;
|
|
2450
|
+
if (ptt === void 0 || ptt.filePath === void 0) throw kernelError(`消息 ${id} 不包含语音`, "NOT_FOUND");
|
|
2451
|
+
const meta = () => ({
|
|
2452
|
+
...ptt.fileSize !== void 0 ? { file_size: ptt.fileSize } : {},
|
|
2453
|
+
...ptt.fileName !== void 0 ? { file_name: ptt.fileName } : {}
|
|
2454
|
+
});
|
|
2455
|
+
const local = resolveLocalMediaFile(ptt.filePath, this.deps);
|
|
2456
|
+
if (local !== null) return {
|
|
2457
|
+
...meta(),
|
|
2458
|
+
file: local
|
|
2459
|
+
};
|
|
2460
|
+
try {
|
|
2461
|
+
const downloaded = await this.deps.msgApi.downloadPtt(msgId, peer);
|
|
2462
|
+
const rel = downloaded.filePath;
|
|
2463
|
+
if (rel !== void 0 && rel !== "") {
|
|
2464
|
+
const localAfter = resolveLocalMediaFile(rel, this.deps);
|
|
2465
|
+
if (localAfter !== null) return {
|
|
2466
|
+
...downloaded.fileSize !== void 0 ? { file_size: downloaded.fileSize } : {},
|
|
2467
|
+
...downloaded.fileName !== void 0 ? { file_name: downloaded.fileName } : {},
|
|
2468
|
+
file: localAfter
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
} catch {}
|
|
2472
|
+
return {
|
|
2473
|
+
...meta(),
|
|
2474
|
+
file: ptt.filePath
|
|
2475
|
+
};
|
|
2235
2476
|
}
|
|
2236
2477
|
};
|
|
2237
2478
|
//#endregion
|
|
@@ -3287,6 +3528,8 @@ var OneBotApi = class {
|
|
|
3287
3528
|
cleanCache;
|
|
3288
3529
|
/** 缓存目录(download_file 用)。 */
|
|
3289
3530
|
cacheDir;
|
|
3531
|
+
/** QQ NT global 目录(媒体 NT 相对路径解析用)。 */
|
|
3532
|
+
mediaBaseDir;
|
|
3290
3533
|
/** 进程退出回调(bot_exit 用)。 */
|
|
3291
3534
|
exit;
|
|
3292
3535
|
/** 进程重启回调(set_restart 用)。 */
|
|
@@ -3308,6 +3551,7 @@ var OneBotApi = class {
|
|
|
3308
3551
|
this.appVersion = opts.system.appVersion;
|
|
3309
3552
|
this.cleanCache = opts.system.cleanCache;
|
|
3310
3553
|
this.cacheDir = opts.system.cacheDir;
|
|
3554
|
+
this.mediaBaseDir = opts.system.mediaBaseDir;
|
|
3311
3555
|
this.exit = opts.system.exit;
|
|
3312
3556
|
this.restart = opts.system.restart;
|
|
3313
3557
|
}
|
|
@@ -3391,10 +3635,13 @@ function toOb11MessageEvent(msg, selfUin, unique, messageFormat = "array", ctx =
|
|
|
3391
3635
|
*/
|
|
3392
3636
|
/** Authorization 头前缀。 */
|
|
3393
3637
|
const BEARER_PREFIX = "Bearer ";
|
|
3394
|
-
/** 校验 URL 查询参数里的 access_token(WS client 连接 URL 可能带)。
|
|
3638
|
+
/** 校验 URL 查询参数里的 access_token(WS client 连接 URL 可能带)。
|
|
3639
|
+
* ⚠️ req.url 是纯路径(无 origin),new URL(相对路径) 必抛——2026-09-08
|
|
3640
|
+
* T10 E2E 实测抓到的 bug(查询参数鉴权从未生效,WS 全被 4401 关闭),
|
|
3641
|
+
* 用 dummy base 解析。 */
|
|
3395
3642
|
function hasAccessTokenQuery(url, token) {
|
|
3396
3643
|
try {
|
|
3397
|
-
return new URL(url).searchParams.get("access_token") === token;
|
|
3644
|
+
return new URL(url, "http://localhost").searchParams.get("access_token") === token;
|
|
3398
3645
|
} catch {
|
|
3399
3646
|
return false;
|
|
3400
3647
|
}
|
|
@@ -3525,15 +3772,23 @@ const MS_TO_SEC = 1e3;
|
|
|
3525
3772
|
var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
|
|
3526
3773
|
protocol = "onebot11";
|
|
3527
3774
|
msgChannel;
|
|
3775
|
+
groupChannel;
|
|
3776
|
+
friendChannel;
|
|
3777
|
+
buddyCacheEvents;
|
|
3778
|
+
calibLogger;
|
|
3528
3779
|
selfUin;
|
|
3529
3780
|
oneBotApi;
|
|
3530
3781
|
/** 动作注册表(公共只读:IPC 桥等装配方枚举动作名直接挂载)。 */
|
|
3531
3782
|
registry;
|
|
3532
|
-
|
|
3783
|
+
unsubscribes = [];
|
|
3533
3784
|
transports = null;
|
|
3534
3785
|
heartbeatTimer = null;
|
|
3535
3786
|
reportSelfMessage = false;
|
|
3536
3787
|
messageFormat = "array";
|
|
3788
|
+
/** 群文件报形式(B4):true = fileElement 群消息报 group_upload notice 替代 message。 */
|
|
3789
|
+
groupUploadAsNotice = false;
|
|
3790
|
+
/** IPC 桥模式标记(subscribeOnly 启动;reload 时跳过传输重建)。 */
|
|
3791
|
+
subscribedOnly = false;
|
|
3537
3792
|
constructor(opts) {
|
|
3538
3793
|
super({
|
|
3539
3794
|
config: opts.config,
|
|
@@ -3541,20 +3796,38 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
|
|
|
3541
3796
|
hooks: {
|
|
3542
3797
|
onStart: (config) => this.startTransports(config),
|
|
3543
3798
|
onStop: () => this.stopAll(),
|
|
3544
|
-
onReload: () =>
|
|
3545
|
-
return Promise.resolve();
|
|
3546
|
-
}
|
|
3799
|
+
onReload: (config) => this.reloadTransports(config)
|
|
3547
3800
|
}
|
|
3548
3801
|
});
|
|
3549
3802
|
this.msgChannel = opts.msgChannel;
|
|
3803
|
+
this.groupChannel = opts.groupChannel;
|
|
3804
|
+
this.friendChannel = opts.friendChannel;
|
|
3805
|
+
this.buddyCacheEvents = opts.buddyCacheEvents;
|
|
3806
|
+
this.calibLogger = opts.logger;
|
|
3550
3807
|
this.selfUin = opts.self.uin;
|
|
3551
3808
|
this.oneBotApi = new OneBotApi(opts);
|
|
3552
3809
|
this.registry = createOb11ActionRegistry({ api: this.oneBotApi });
|
|
3553
3810
|
}
|
|
3811
|
+
/**
|
|
3812
|
+
* 配置热更新(P2-6,2026-09-08 实现):stop 旧传输/心跳/退订 → 按新配置重建。
|
|
3813
|
+
* IPC 桥模式(subscribeOnly,无传输)只刷新上报开关字段,不重建。
|
|
3814
|
+
*/
|
|
3815
|
+
async reloadTransports(config) {
|
|
3816
|
+
if (this.subscribedOnly) {
|
|
3817
|
+
this.reportSelfMessage = config.reportSelfMessage;
|
|
3818
|
+
this.messageFormat = config.messagePostFormat;
|
|
3819
|
+
this.groupUploadAsNotice = config.groupUploadAsNotice;
|
|
3820
|
+
return;
|
|
3821
|
+
}
|
|
3822
|
+
await this.stopAll();
|
|
3823
|
+
await this.startTransports(config);
|
|
3824
|
+
}
|
|
3554
3825
|
/** 启动传输:装配(HTTP/WS)+ 打开 server/client + 广播 lifecycle enable + 起心跳。 */
|
|
3555
3826
|
async startTransports(config) {
|
|
3827
|
+
this.subscribedOnly = false;
|
|
3556
3828
|
this.reportSelfMessage = config.reportSelfMessage;
|
|
3557
3829
|
this.messageFormat = config.messagePostFormat;
|
|
3830
|
+
this.groupUploadAsNotice = config.groupUploadAsNotice;
|
|
3558
3831
|
const broadcaster = this.getBroadcaster();
|
|
3559
3832
|
if (broadcaster !== void 0) {
|
|
3560
3833
|
this.transports = assembleOb11Transports({
|
|
@@ -3616,19 +3889,46 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
|
|
|
3616
3889
|
this.transports = null;
|
|
3617
3890
|
}
|
|
3618
3891
|
}
|
|
3619
|
-
/** 订阅 kernel
|
|
3892
|
+
/** 订阅 kernel 事件(消息 + 群通知 + 好友请求,幂等)。 */
|
|
3620
3893
|
subscribe() {
|
|
3621
|
-
if (this.
|
|
3622
|
-
this.
|
|
3894
|
+
if (this.unsubscribes.length > 0) return;
|
|
3895
|
+
this.unsubscribes.push(this.msgChannel.on("Msg/onRecvMsg", (msgs) => {
|
|
3623
3896
|
forEachRawMessage(msgs, (msg) => {
|
|
3624
3897
|
if (hasGrayTip(msg)) {
|
|
3625
3898
|
this.broadcastNotice(msg).catch(() => {});
|
|
3626
3899
|
return;
|
|
3627
3900
|
}
|
|
3628
3901
|
if (!this.reportSelfMessage && String(msg.senderUin) === this.selfUin) return;
|
|
3902
|
+
if (this.groupUploadAsNotice && msg.chatType === ChatType.GROUP && hasFileElement(msg)) {
|
|
3903
|
+
const notice = toGroupUpload(msg, this.selfUin);
|
|
3904
|
+
if (notice !== null) {
|
|
3905
|
+
this.broadcastEvent(notice);
|
|
3906
|
+
return;
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3629
3909
|
this.broadcastMessageEvent(msg);
|
|
3630
3910
|
});
|
|
3631
|
-
});
|
|
3911
|
+
}));
|
|
3912
|
+
if (this.groupChannel !== void 0) this.unsubscribes.push(this.groupChannel.on("Group/onGroupNotifiesUpdated", (doubt, notifies) => {
|
|
3913
|
+
if (doubt || !Array.isArray(notifies)) return;
|
|
3914
|
+
this.broadcastGroupRequests(notifies);
|
|
3915
|
+
}));
|
|
3916
|
+
if (this.friendChannel !== void 0) {
|
|
3917
|
+
this.unsubscribes.push(this.friendChannel.on("Buddy/onBuddyReqChange", (arg) => {
|
|
3918
|
+
const reqs = narrowBuddyReqs(arg);
|
|
3919
|
+
if (reqs === null) {
|
|
3920
|
+
this.calibLogger?.warn({ raw: JSON.stringify(arg)?.slice(0, 2e3) }, "ob11: onBuddyReqChange 未知参数形状");
|
|
3921
|
+
return;
|
|
3922
|
+
}
|
|
3923
|
+
this.broadcastFriendRequests(reqs);
|
|
3924
|
+
}));
|
|
3925
|
+
for (const evt of ["Buddy/onBuddyListChange", "Buddy/onBuddyListChangedV2"]) this.unsubscribes.push(this.friendChannel.on(evt, (arg) => {
|
|
3926
|
+
this.calibLogger?.info({ raw: JSON.stringify(arg)?.slice(0, 2e3) }, `ob11: ${evt} raw(friend_add 校准数据)`);
|
|
3927
|
+
}));
|
|
3928
|
+
}
|
|
3929
|
+
if (this.buddyCacheEvents !== void 0) this.unsubscribes.push(this.buddyCacheEvents.on("BuddyCache/onBuddyAdded", (entry) => {
|
|
3930
|
+
this.broadcastEvent(toFriendAdd(entry, this.selfUin));
|
|
3931
|
+
}));
|
|
3632
3932
|
}
|
|
3633
3933
|
/**
|
|
3634
3934
|
* 仅启动接收链路(IPC 桥模式,2026-08-27):订阅消息通道维护 messageUnique +
|
|
@@ -3639,10 +3939,13 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
|
|
|
3639
3939
|
const config = await this.config.load();
|
|
3640
3940
|
this.reportSelfMessage = config.reportSelfMessage;
|
|
3641
3941
|
this.messageFormat = config.messagePostFormat;
|
|
3942
|
+
this.groupUploadAsNotice = config.groupUploadAsNotice;
|
|
3943
|
+
this.subscribedOnly = true;
|
|
3642
3944
|
this.subscribe();
|
|
3643
3945
|
}
|
|
3644
3946
|
/** 仅退订(与 subscribeOnly 配对的进程级清理;传输关闭仍走 stop())。 */
|
|
3645
3947
|
unsubscribeOnly() {
|
|
3948
|
+
this.subscribedOnly = false;
|
|
3646
3949
|
this.unsubscribeAll();
|
|
3647
3950
|
}
|
|
3648
3951
|
/**
|
|
@@ -3669,21 +3972,57 @@ var NapukettoOneBot11Adapter = class extends BaseProtocolAdapter {
|
|
|
3669
3972
|
if (uids.length > 0) uidToUin = await this.oneBotApi.uidToUin(uids);
|
|
3670
3973
|
const notice = toOb11NoticeEvent(msg, {
|
|
3671
3974
|
selfUin: this.selfUin,
|
|
3672
|
-
uidToUin
|
|
3975
|
+
uidToUin,
|
|
3976
|
+
...this.calibLogger !== void 0 ? { logger: this.calibLogger } : {}
|
|
3673
3977
|
});
|
|
3674
3978
|
if (notice !== null) this.broadcastEvent(notice);
|
|
3675
3979
|
}
|
|
3980
|
+
/** 广播群系统通知 → OB11 group request 事件(批量 uidToUin 后翻译,纯函数)。 */
|
|
3981
|
+
async broadcastGroupRequests(notifies) {
|
|
3982
|
+
const uids = [];
|
|
3983
|
+
for (const n of notifies) {
|
|
3984
|
+
const notify = n;
|
|
3985
|
+
for (const u of [notify.user1, notify.user2]) if (typeof u?.uid === "string" && u.uid !== "") uids.push(u.uid);
|
|
3986
|
+
}
|
|
3987
|
+
let uidToUin = /* @__PURE__ */ new Map();
|
|
3988
|
+
if (uids.length > 0) try {
|
|
3989
|
+
uidToUin = await this.oneBotApi.uidToUin(uids);
|
|
3990
|
+
} catch {}
|
|
3991
|
+
const ctx = {
|
|
3992
|
+
selfUin: this.selfUin,
|
|
3993
|
+
uidToUin
|
|
3994
|
+
};
|
|
3995
|
+
for (const n of notifies) {
|
|
3996
|
+
const event = toOb11GroupRequestEvent(n, ctx);
|
|
3997
|
+
if (event !== null) this.broadcastEvent(event);
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
/** 广播好友申请 → OB11 friend request 事件(批量 uidToUin 后翻译,纯函数)。 */
|
|
4001
|
+
async broadcastFriendRequests(reqs) {
|
|
4002
|
+
const uids = reqs.map((r) => r.friendUid).filter((uid) => uid !== "");
|
|
4003
|
+
let uidToUin = /* @__PURE__ */ new Map();
|
|
4004
|
+
if (uids.length > 0) try {
|
|
4005
|
+
uidToUin = await this.oneBotApi.uidToUin(uids);
|
|
4006
|
+
} catch {}
|
|
4007
|
+
const ctx = {
|
|
4008
|
+
selfUin: this.selfUin,
|
|
4009
|
+
uidToUin
|
|
4010
|
+
};
|
|
4011
|
+
for (const req of reqs) this.broadcastEvent(toOb11FriendRequestEvent(req, ctx));
|
|
4012
|
+
}
|
|
3676
4013
|
/** 退订(幂等)。 */
|
|
3677
4014
|
unsubscribeAll() {
|
|
3678
|
-
this.
|
|
3679
|
-
this.
|
|
4015
|
+
for (const off of this.unsubscribes) off();
|
|
4016
|
+
this.unsubscribes = [];
|
|
3680
4017
|
}
|
|
3681
4018
|
/**
|
|
3682
4019
|
* 请求分发(挂到 network transport 的 onRequest):
|
|
3683
4020
|
* OB11 标准请求 { action, params, echo } → 动作注册表 → handle(HTTP)/websocketHandle(WS)。
|
|
3684
4021
|
*/
|
|
3685
4022
|
async handleRequest(req, respond) {
|
|
3686
|
-
const
|
|
4023
|
+
const parsed = req ?? {};
|
|
4024
|
+
const { action: rawAction, echo } = parsed;
|
|
4025
|
+
const params = parsed.params ?? {};
|
|
3687
4026
|
let action = "";
|
|
3688
4027
|
if (typeof rawAction === "string") action = rawAction;
|
|
3689
4028
|
if (action === "") {
|