Compare commits

...

17 Commits

Author SHA1 Message Date
手瓜一十雪
3c10b82bab Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
Some checks failed
Build Action / Build-LiteLoader (push) Failing after 11s
Build Action / Build-Shell (push) Failing after 14s
2024-11-16 20:35:31 +08:00
手瓜一十雪
9a65dae6a2 fix: #531 2024-11-16 20:32:52 +08:00
Mlikiowa
f26cd8cdc9 release: v4.1.3 2024-11-16 12:22:06 +00:00
手瓜一十雪
eeec905df0 fix: 反向ws 2024-11-16 20:21:38 +08:00
手瓜一十雪
0c6aac7f66 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-16 20:20:07 +08:00
手瓜一十雪
86d22db141 feat: remove hasBeenClosed 2024-11-16 20:15:02 +08:00
Mlikiowa
48a5d0eef3 release: v4.1.2 2024-11-16 12:14:28 +00:00
手瓜一十雪
bda174bed4 fix: 异常 2024-11-16 20:13:36 +08:00
Mlikiowa
caf98b8655 release: v4.1.1 2024-11-16 11:26:41 +00:00
手瓜一十雪
c9833c5988 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-16 19:25:58 +08:00
手瓜一十雪
55ef7e529e fix: 4.1.1 2024-11-16 19:25:54 +08:00
Mlikiowa
9b04ddcefd release: v4.1.0 2024-11-16 10:41:27 +00:00
手瓜一十雪
6dc4f38581 refactor: AdapterConfig 2024-11-16 18:38:44 +08:00
手瓜一十雪
93ce8bfb85 refactor: emitMsg 2024-11-16 18:31:24 +08:00
手瓜一十雪
e7d138448a refactor: reloadNetwork 2024-11-16 18:10:03 +08:00
手瓜一十雪
02c4a468cb fix 2024-11-16 16:56:34 +08:00
手瓜一十雪
d392e653e1 refactor: network 2024-11-16 16:56:20 +08:00
16 changed files with 380 additions and 308 deletions

View File

@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "4.0.3",
"version": "4.1.3",
"icon": "./logo.png",
"authors": [
{

View File

@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.0.3",
"version": "4.1.3",
"scripts": {
"build:framework": "npm run build:webui && vite build --mode framework",
"build:shell": "npm run build:webui && vite build --mode shell",

View File

@ -36,7 +36,7 @@ export class LogWrapper {
this.logger = winston.createLogger({
level: 'debug',
format: format.combine(
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
format.timestamp({ format: 'MM-DD HH:mm:ss' }),
format.printf(({ timestamp, level, message, ...meta }) => {
const userInfo = meta.userInfo ? `${meta.userInfo} | ` : '';
return `${timestamp} [${level}] ${userInfo}${message}`;
@ -61,7 +61,7 @@ export class LogWrapper {
]
});
this.setLogSelfInfo({ nick: '', uin: '', uid: '' });
this.setLogSelfInfo({ nick: '', uid: '' });
this.cleanOldLogs(logDir);
}
@ -111,8 +111,8 @@ export class LogWrapper {
});
}
setLogSelfInfo(selfInfo: { nick: string, uin: string, uid: string }) {
const userInfo = `${selfInfo.nick}(${selfInfo.uin})`;
setLogSelfInfo(selfInfo: { nick: string, uid: string }) {
const userInfo = `${selfInfo.nick}`;
this.logger.defaultMeta = { userInfo };
}

View File

@ -1 +1 @@
export const napCatVersion = '4.0.3';
export const napCatVersion = '4.1.3';

View File

@ -4,6 +4,7 @@ import { ActionName } from '../types';
import { ChatType } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import { AdapterConfigWrap } from '@/onebot/config/config';
interface Response {
messages: OB11Message[];
@ -45,7 +46,7 @@ export default class GetFriendMsgHistory extends BaseAction<Payload, Response> {
await Promise.all(msgList.map(async msg => {
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
const network = Object.values(this.obContext.configLoader.configData.network) as Array<typeof this.obContext.configLoader.configData.network[keyof typeof this.obContext.configLoader.configData.network]>;
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
//烘焙消息
const ob11MsgList = (await Promise.all(
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array')))

View File

@ -4,6 +4,7 @@ import { ActionName } from '../types';
import { ChatType, Peer } from '@/core/entities';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import { AdapterConfigWrap } from '@/onebot/config/config';
interface Response {
messages: OB11Message[];
@ -43,7 +44,7 @@ export default class GoCQHTTPGetGroupMsgHistory extends BaseAction<Payload, Resp
await Promise.all(msgList.map(async msg => {
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
}));
const network = Object.values(this.obContext.configLoader.configData.network) as Array<typeof this.obContext.configLoader.configData.network[keyof typeof this.obContext.configLoader.configData.network]>;
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
//烘焙消息
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
const ob11MsgList = (await Promise.all(

View File

@ -4,6 +4,7 @@ import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import crypto from 'crypto';
import { AdapterConfigWrap } from '@/onebot/config/config';
const SchemaData = {
type: 'object',
@ -31,7 +32,7 @@ export class GetGroupEssence extends BaseAction<Payload, any> {
}
async _handle(payload: Payload, adapter: string) {
const network = Object.values(this.obContext.configLoader.configData.network) as Array<typeof this.obContext.configLoader.configData.network[keyof typeof this.obContext.configLoader.configData.network]>;
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
const msglist = (await this.core.apis.WebApi.getGroupEssenceMsgAll(payload.group_id.toString())).flatMap((e) => e.data.msg_list);
if (!msglist) {

View File

@ -4,6 +4,7 @@ import { ActionName } from '../types';
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import { MessageUnique } from '@/common/message-unique';
import { RawMessage } from '@/core';
import { AdapterConfigWrap } from '@/onebot/config/config';
export type ReturnDataType = OB11Message
@ -24,7 +25,7 @@ class GetMsg extends BaseAction<Payload, OB11Message> {
async _handle(payload: Payload, adapter: string) {
// log("history msg ids", Object.keys(msgHistory));
const network = Object.values(this.obContext.configLoader.configData.network) as Array<typeof this.obContext.configLoader.configData.network[keyof typeof this.obContext.configLoader.configData.network]>;
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
if (!payload.message_id) {
throw Error('参数message_id不能为空');

View File

@ -1,6 +1,7 @@
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
import BaseAction from '../BaseAction';
import { ActionName } from '../types';
import { AdapterConfigWrap } from '@/onebot/config/config';
const SchemaData = {
type: 'object',
@ -17,7 +18,7 @@ export default class GetRecentContact extends BaseAction<Payload, any> {
async _handle(payload: Payload, adapter: string) {
const ret = await this.core.apis.UserApi.getRecentContactListSnapShot(+(payload.count || 10));
const network = Object.values(this.obContext.configLoader.configData.network) as Array<typeof this.obContext.configLoader.configData.network[keyof typeof this.obContext.configLoader.configData.network]>;
const network = Object.values(this.obContext.configLoader.configData.network) as Array<AdapterConfigWrap>;
//烘焙消息
const msgFormat = network.flat().find(e => e.name === adapter)?.messagePostFormat ?? 'array';
return await Promise.all(ret.info.changedList.map(async (t) => {

View File

@ -25,10 +25,13 @@ interface v1Config {
reportSelfMessage: boolean;
token: string;
}
export interface AdapterConfig {
export interface AdapterConfigInner {
name: string;
enable: boolean;
}
export type AdapterConfigWrap = AdapterConfigInner & Partial<NetworkConfigAdapter>;
export interface AdapterConfig extends AdapterConfigInner {
[key: string]: any;
}
@ -78,6 +81,7 @@ export const websocketClientDefaultConfigs = createDefaultAdapterConfig({
url: 'ws://localhost:8082',
messagePostFormat: 'array',
reportSelfMessage: false,
reconnectInterval: 5000,
token: '',
debug: false,
heartInterval: 30000,
@ -121,6 +125,7 @@ export const mergeNetworkDefaultConfig = {
websocketClients: websocketClientDefaultConfigs,
} as const;
export type NetworkConfigAdapter = HttpServerConfig | HttpClientConfig | WebsocketServerConfig | WebsocketClientConfig;
type NetworkConfigKeys = keyof typeof mergeNetworkDefaultConfig;
export function mergeOneBotConfigs(
@ -204,3 +209,17 @@ export function migrateOneBotConfigsV1(config: Partial<v1Config>): OneBotConfig
}
return mergedConfig;
}
export function getConfigBoolKey(
configs: Array<NetworkConfigAdapter>,
prediction: (config: NetworkConfigAdapter) => boolean
): { positive: Array<string>, negative: Array<string> } {
const result: { positive: string[], negative: string[] } = { positive: [], negative: [] };
configs.forEach(config => {
if (prediction(config)) {
result.positive.push(config.name);
} else {
result.negative.push(config.name);
}
});
return result;
}

View File

@ -16,9 +16,11 @@ import {
} from '@/core';
import { OB11ConfigLoader } from '@/onebot/config';
import {
IOB11NetworkAdapter,
OB11ActiveHttpAdapter,
OB11ActiveWebSocketAdapter,
OB11NetworkManager,
OB11NetworkReloadType,
OB11PassiveHttpAdapter,
OB11PassiveWebSocketAdapter,
} from '@/onebot/network';
@ -45,7 +47,7 @@ import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecal
import { LRUCache } from '@/common/lru-cache';
import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener';
import { BotOfflineEvent } from './event/notice/BotOfflineEvent';
import { mergeOneBotConfigs, migrateOneBotConfigsV1, OneBotConfig } from './config/config';
import { AdapterConfigWrap, mergeOneBotConfigs, migrateOneBotConfigsV1, NetworkConfigAdapter, OneBotConfig } from './config/config';
import { OB11Message } from './types';
//OneBot实现类
@ -110,14 +112,14 @@ export class NapCatOneBot11Adapter {
for (const key of ob11Config.network.httpServers) {
if (key.enable) {
this.networkManager.registerAdapter(
new OB11PassiveHttpAdapter(key.name, key.port, key.token, this.core, this.actions)
new OB11PassiveHttpAdapter(key.name, key, this.core, this.actions)
);
}
}
for (const key of ob11Config.network.httpClients) {
if (key.enable) {
this.networkManager.registerAdapter(
new OB11ActiveHttpAdapter(key.name, key.url, key.token, this.core, this)
new OB11ActiveHttpAdapter(key.name, key, this.core, this, this.actions)
);
}
}
@ -126,10 +128,7 @@ export class NapCatOneBot11Adapter {
this.networkManager.registerAdapter(
new OB11PassiveWebSocketAdapter(
key.name,
key.host,
key.port,
key.heartInterval,
key.token,
key,
this.core,
this.actions
)
@ -141,10 +140,7 @@ export class NapCatOneBot11Adapter {
this.networkManager.registerAdapter(
new OB11ActiveWebSocketAdapter(
key.name,
key.url,
5000,
key.heartInterval,
key.token,
key,
this.core,
this.actions
)
@ -185,164 +181,56 @@ export class NapCatOneBot11Adapter {
const newLog = await this.creatOneBotLog(now);
this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`);
this.context.logger.log(`[Notice] [OneBot11] 配置变更后:\n${newLog}`);
const { added: addedHttpServers, removed: removedHttpServers } = this.findDifference(prev.network.httpServers, now.network.httpServers);
const { added: addedHttpClients, removed: removedHttpClients } = this.findDifference(prev.network.httpClients, now.network.httpClients);
const { added: addedWebSocketServers, removed: removedWebSocketServers } = this.findDifference(prev.network.websocketServers, now.network.websocketServers);
const { added: addedWebSocketClients, removed: removedWebSocketClients } = this.findDifference(prev.network.websocketClients, now.network.websocketClients);
// 移除旧的 HTTP 服务器
for (const server of removedHttpServers) {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === server.name);
}
await this.handleRemovedAdapters(removedHttpServers);
await this.handleRemovedAdapters(removedHttpClients);
await this.handleRemovedAdapters(removedWebSocketServers);
await this.handleRemovedAdapters(removedWebSocketClients);
// 移除旧的 HTTP 客户端
for (const client of removedHttpClients) {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === client.name);
}
await this.handlerConfigChange(now.network.httpServers);
await this.handlerConfigChange(now.network.httpClients);
await this.handlerConfigChange(now.network.websocketServers);
await this.handlerConfigChange(now.network.websocketClients);
// 移除旧的 WebSocket 服务器
for (const server of removedWebSocketServers) {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === server.name);
}
await this.handleAddedAdapters(addedHttpServers, OB11PassiveHttpAdapter);
await this.handleAddedAdapters(addedHttpClients, OB11ActiveHttpAdapter);
await this.handleAddedAdapters(addedWebSocketServers, OB11PassiveWebSocketAdapter);
await this.handleAddedAdapters(addedWebSocketClients, OB11ActiveWebSocketAdapter);
}
// 移除旧的 WebSocket 客户端
for (const client of removedWebSocketClients) {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === client.name);
}
private async handlerConfigChange(adapters: Array<NetworkConfigAdapter>) {
for (const adapterConfig of adapters) {
const existingAdapter = this.networkManager.findSomeAdapter(adapterConfig.name);
if (existingAdapter) {
let networkChange = await existingAdapter.reload(adapterConfig);
if (networkChange === OB11NetworkReloadType.NetWorkClose) {
this.networkManager.closeSomeAdapters([existingAdapter]);
// 处理 enable 状态变化的 HTTP 服务器
for (const server of now.network.httpServers) {
const prevServer = prev.network.httpServers.find(s => s.name === server.name);
if (prevServer && prevServer.enable !== server.enable) {
if (server.enable) {
let adapter = new OB11PassiveHttpAdapter(server.name, server.port, server.token, this.core, this.actions);
adapter.open();
this.networkManager.registerAdapter(
adapter
);
} else {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === server.name);
}
}
}
// 处理 enable 状态变化的 HTTP 客户端
for (const client of now.network.httpClients) {
const prevClient = prev.network.httpClients.find(c => c.name === client.name);
if (prevClient && prevClient.enable !== client.enable) {
if (client.enable) {
let adapter = new OB11ActiveHttpAdapter(client.name, client.url, client.token, this.core, this);
adapter.open();
this.networkManager.registerAdapter(
adapter
);
} else {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === client.name);
}
}
}
// 处理 enable 状态变化的 WebSocket 服务器
for (const server of now.network.websocketServers) {
const prevServer = prev.network.websocketServers.find(s => s.name === server.name);
if (prevServer && prevServer.enable !== server.enable) {
if (server.enable) {
let adapter = new OB11PassiveWebSocketAdapter(
server.name,
server.host,
server.port,
server.heartInterval,
server.token,
this.core,
this.actions
);
adapter.open();
this.networkManager.registerAdapter(
adapter
);
} else {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === server.name);
}
}
}
// 处理 enable 状态变化的 WebSocket 客户端
for (const client of now.network.websocketClients) {
const prevClient = prev.network.websocketClients.find(c => c.name === client.name);
if (prevClient && prevClient.enable !== client.enable) {
if (client.enable) {
let adapter = new OB11ActiveWebSocketAdapter(
client.name,
client.url,
5000,
client.heartInterval,
client.token,
this.core,
this.actions
)
this.networkManager.registerAdapter(
adapter
);
} else {
await this.networkManager.closeAdapterByPredicate((adapter) => adapter.name === client.name);
}
}
}
// 注册新的 HTTP 服务器
for (const server of addedHttpServers) {
if (server.enable) {
let adapter = new OB11PassiveHttpAdapter(server.name, server.port, server.token, this.core, this.actions);
adapter.open();
this.networkManager.registerAdapter(adapter);
}
}
// 注册新的 HTTP 客户端
for (const client of addedHttpClients) {
if (client.enable) {
let adapter = new OB11ActiveHttpAdapter(client.name, client.url, client.token, this.core, this);
adapter.open();
this.networkManager.registerAdapter(adapter);
}
}
// 注册新的 WebSocket 服务器
for (const server of addedWebSocketServers) {
if (server.enable) {
let adapter = new OB11PassiveWebSocketAdapter(
server.name,
server.host,
server.port,
server.heartInterval,
server.token,
this.core,
this.actions
);
adapter.open();
this.networkManager.registerAdapter(adapter);
}
}
// 注册新的 WebSocket 客户端
for (const client of addedWebSocketClients) {
if (client.enable) {
let adapter = new OB11ActiveWebSocketAdapter(
client.name,
client.url,
5000,
client.heartInterval,
client.token,
this.core,
this.actions
)
adapter.open();
this.networkManager.registerAdapter(adapter);
}
}
}
private async handleRemovedAdapters(adapters: Array<{ name: string }>): Promise<void> {
for (const adapter of adapters) {
await this.networkManager.closeAdapterByPredicate((existingAdapter) => existingAdapter.name === adapter.name);
}
}
private async handleAddedAdapters<T extends new (...args: any[]) => IOB11NetworkAdapter>(addedAdapters: Array<NetworkConfigAdapter>, AdapterClass: T) {
for (const adapter of addedAdapters) {
if (adapter.enable) {
const newAdapter = new AdapterClass(adapter.name, adapter, this.core, this.actions);
await newAdapter.open();
this.networkManager.registerAdapter(newAdapter);
}
}
}
private findDifference<T>(prev: T[], now: T[]): { added: T[]; removed: T[] } {
const added = now.filter((item) => !prev.includes(item));
const removed = prev.filter((item) => !now.includes(item));
@ -655,76 +543,95 @@ export class NapCatOneBot11Adapter {
}
private async emitMsg(message: RawMessage) {
const network = Object.values(this.configLoader.configData.network) as Array<
(typeof this.configLoader.configData.network)[keyof typeof this.configLoader.configData.network]
>;
const network = Object.values(this.configLoader.configData.network) as Array<AdapterConfigWrap>;
this.context.logger.logDebug('收到新消息 RawMessage', message);
this.apis.MsgApi.parseMessageV2(message)
.then((ob11Msg) => {
if (!ob11Msg) return;
const isSelfMsg =
ob11Msg.stringMsg.user_id.toString() == this.core.selfInfo.uin ||
ob11Msg.arrayMsg.user_id.toString() == this.core.selfInfo.uin;
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
const msgMap: Map<string, OB11Message> = new Map();
const enable_client: string[] = [];
network
.flat()
.filter((e) => e.enable)
.map((e) => {
enable_client.push(e.name);
if (e.messagePostFormat == 'string') {
msgMap.set(e.name, structuredClone(ob11Msg.stringMsg));
} else {
msgMap.set(e.name, structuredClone(ob11Msg.arrayMsg));
}
if (isSelfMsg) {
ob11Msg.stringMsg.target_id = parseInt(message.peerUin);
ob11Msg.arrayMsg.target_id = parseInt(message.peerUin);
}
});
try {
const ob11Msg = await this.apis.MsgApi.parseMessageV2(message);
if (!ob11Msg) return;
const debug_network = network.flat().filter((e) => e.enable && e.debug);
if (debug_network.length > 0) {
for (const adapter of debug_network) {
if (adapter.name) {
const msg = msgMap.get(adapter.name);
if (msg) {
msg.raw = message;
}
}
}
} else if (ob11Msg.stringMsg.message.length === 0 || ob11Msg.arrayMsg.message.length == 0) {
return;
}
const notreportSelf_network = network.flat().filter((e) => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
if (isSelfMsg) {
for (const adapter of notreportSelf_network) {
msgMap.delete(adapter.name);
}
}
const isSelfMsg = this.isSelfMessage(ob11Msg);
this.context.logger.logDebug('转化为 OB11Message', ob11Msg);
this.networkManager.emitEventByNames(msgMap);
})
.catch((e) => this.context.logger.logError.bind(this.context.logger)('constructMessage error: ', e));
const msgMap = this.createMsgMap(network, ob11Msg, isSelfMsg, message);
this.handleDebugNetwork(network, msgMap, message);
this.handleNotReportSelfNetwork(network, msgMap, isSelfMsg);
this.apis.GroupApi.parseGroupEvent(message)
.then((groupEvent) => {
if (groupEvent) {
// log("post group event", groupEvent);
this.networkManager.emitEvent(groupEvent);
}
})
.catch((e) => this.context.logger.logError.bind(this.context.logger)('constructGroupEvent error: ', e));
this.networkManager.emitEventByNames(msgMap);
} catch (e) {
this.context.logger.logError('constructMessage error: ', e);
}
this.apis.MsgApi.parsePrivateMsgEvent(message)
.then((privateEvent) => {
if (privateEvent) {
// log("post private event", privateEvent);
this.networkManager.emitEvent(privateEvent);
this.handleGroupEvent(message);
this.handlePrivateMsgEvent(message);
}
private isSelfMessage(ob11Msg: {
stringMsg: OB11Message;
arrayMsg: OB11Message;
}): boolean {
return ob11Msg.stringMsg.user_id.toString() == this.core.selfInfo.uin ||
ob11Msg.arrayMsg.user_id.toString() == this.core.selfInfo.uin;
}
private createMsgMap(network: Array<AdapterConfigWrap>, ob11Msg: any, isSelfMsg: boolean, message: RawMessage): Map<string, OB11Message> {
const msgMap: Map<string, OB11Message> = new Map();
network.flat().filter(e => e.enable).forEach(e => {
if (e.messagePostFormat == 'string') {
msgMap.set(e.name, structuredClone(ob11Msg.stringMsg));
} else {
msgMap.set(e.name, structuredClone(ob11Msg.arrayMsg));
}
if (isSelfMsg) {
ob11Msg.stringMsg.target_id = parseInt(message.peerUin);
ob11Msg.arrayMsg.target_id = parseInt(message.peerUin);
}
});
return msgMap;
}
private handleDebugNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, message: RawMessage) {
const debugNetwork = network.flat().filter(e => e.enable && e.debug);
if (debugNetwork.length > 0) {
debugNetwork.forEach(adapter => {
const msg = msgMap.get(adapter.name);
if (msg) {
msg.raw = message;
}
})
.catch((e) => this.context.logger.logError.bind(this.context.logger)('constructPrivateEvent error: ', e));
});
} else if (msgMap.size === 0) {
return;
}
}
private handleNotReportSelfNetwork(network: Array<AdapterConfigWrap>, msgMap: Map<string, OB11Message>, isSelfMsg: boolean) {
if (isSelfMsg) {
const notReportSelfNetwork = network.flat().filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e)));
notReportSelfNetwork.forEach(adapter => {
msgMap.delete(adapter.name);
});
}
}
private async handleGroupEvent(message: RawMessage) {
try {
const groupEvent = await this.apis.GroupApi.parseGroupEvent(message);
if (groupEvent) {
this.networkManager.emitEvent(groupEvent);
}
} catch (e) {
this.context.logger.logError('constructGroupEvent error: ', e);
}
}
private async handlePrivateMsgEvent(message: RawMessage) {
try {
const privateEvent = await this.apis.MsgApi.parsePrivateMsgEvent(message);
if (privateEvent) {
this.networkManager.emitEvent(privateEvent);
}
} catch (e) {
this.context.logger.logError('constructPrivateEvent error: ', e);
}
}
private async emitRecallMsg(msgList: RawMessage[], cache: LRUCache<string, boolean>) {
for (const message of msgList) {

View File

@ -1,27 +1,31 @@
import { IOB11NetworkAdapter, OB11EmitEventContent } from '@/onebot/network/index';
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index';
import { createHmac } from 'crypto';
import { LogWrapper } from '@/common/log';
import { QuickAction, QuickActionEvent } from '../types';
import { NapCatCore } from '@/core';
import { NapCatOneBot11Adapter } from '..';
import { RequestUtil } from '@/common/request';
import { HttpClientConfig } from '../config/config';
import { ActionMap } from '../action';
export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
logger: LogWrapper;
isOpen: boolean = false;
isEnable: boolean = false;
public config: HttpClientConfig;
constructor(
public name: string,
public url: string,
public secret: string | undefined,
config: HttpClientConfig,
public core: NapCatCore,
public obContext: NapCatOneBot11Adapter,
public actions: ActionMap,
) {
this.logger = core.context.logger;
this.config = structuredClone(config);
}
onEvent<T extends OB11EmitEventContent>(event: T) {
if (!this.isOpen) {
if (!this.isEnable) {
return;
}
const headers: Record<string, string> = {
@ -29,13 +33,13 @@ export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
'x-self-id': this.core.selfInfo.uin,
};
const msgStr = JSON.stringify(event);
if (this.secret && this.secret.length > 0) {
const hmac = createHmac('sha1', this.secret);
if (this.config.token && this.config.token.length > 0) {
const hmac = createHmac('sha1', this.config.token);
hmac.update(msgStr);
const sig = hmac.digest('hex');
headers['x-signature'] = 'sha1=' + sig;
}
RequestUtil.HttpGetText(this.url, 'POST', msgStr, headers).then(async (res) => {
RequestUtil.HttpGetText(this.config.url, 'POST', msgStr, headers).then(async (res) => {
let resJson: QuickAction;
try {
resJson = JSON.parse(res);
@ -57,10 +61,26 @@ export class OB11ActiveHttpAdapter implements IOB11NetworkAdapter {
}
open() {
this.isOpen = true;
this.isEnable = true;
}
close() {
this.isOpen = false;
this.isEnable = false;
}
async reload(newconfig: HttpClientConfig) {
const wasEnabled = this.isEnable;
const oldUrl = this.config.url;
this.config = newconfig;
if (newconfig.enable && !wasEnabled) {
this.open();
return OB11NetworkReloadType.NetWorkOpen;
} else if (!newconfig.enable && wasEnabled) {
this.close();
return OB11NetworkReloadType.NetWorkClose;
}
if (oldUrl !== newconfig.url) {
return OB11NetworkReloadType.NetWorkReload;
}
return OB11NetworkReloadType.Normal;
}
}

View File

@ -1,4 +1,4 @@
import { IOB11NetworkAdapter, OB11EmitEventContent } from '@/onebot/network/index';
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from '@/onebot/network/index';
import { WebSocket } from 'ws';
import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
import { NapCatCore } from '@/core';
@ -7,23 +7,23 @@ import { OB11Response } from '@/onebot/action/OB11Response';
import { LogWrapper } from '@/common/log';
import { ActionMap } from '@/onebot/action';
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
import { WebsocketClientConfig } from '../config/config';
export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
isClosed: boolean = false;
isEnable: boolean = false;
logger: LogWrapper;
private connection: WebSocket | null = null;
private heartbeatRef: NodeJS.Timeout | null = null;
public config: WebsocketClientConfig;
constructor(
public name: string,
public url: string,
public reconnectIntervalInMillis: number,
public heartbeatIntervalInMillis: number,
private readonly token: string,
confg: WebsocketClientConfig,
public core: NapCatCore,
public actions: ActionMap,
) {
this.logger = core.context.logger;
this.config = structuredClone(confg);
}
onEvent<T extends OB11EmitEventContent>(event: T) {
@ -36,23 +36,23 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
if (this.connection) {
return;
}
if (this.heartbeatIntervalInMillis > 0) {
if (this.config.heartInterval > 0) {
this.heartbeatRef = setInterval(() => {
if (this.connection && this.connection.readyState === WebSocket.OPEN) {
this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.heartbeatIntervalInMillis, this.core.selfInfo.online ?? true, true)));
this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, this.config.heartInterval, this.core.selfInfo.online ?? true, true)));
}
}, this.heartbeatIntervalInMillis);
}, this.config.heartInterval);
}
this.isEnable = true;
await this.tryConnect();
}
close() {
if (this.isClosed) {
if (!this.isEnable) {
this.logger.logDebug('Cannot close a closed WebSocket connection');
return;
}
this.isClosed = true;
this.isEnable = false;
if (this.connection) {
this.connection.close();
this.connection = null;
@ -70,16 +70,16 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
}
private async tryConnect() {
if (!this.connection && !this.isClosed) {
if (!this.connection && this.isEnable) {
let isClosedByError = false;
this.connection = new WebSocket(this.url, {
this.connection = new WebSocket(this.config.url, {
maxPayload: 1024 * 1024 * 1024,
handshakeTimeout: 2000,
perMessageDeflate: false,
headers: {
'X-Self-ID': this.core.selfInfo.uin,
'Authorization': `Bearer ${this.token}`,
'Authorization': `Bearer ${this.config.token}`,
'x-client-role': 'Universal', // koishi-adapter-onebot 需要这个字段
'User-Agent': 'OneBot/11',
},
@ -104,21 +104,21 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
});
this.connection.once('close', () => {
if (!isClosedByError) {
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.url}) 连接意外关闭`);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.reconnectIntervalInMillis / 1000)} 秒后尝试重新连接`);
if (!this.isClosed) {
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接意外关闭`);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`);
if (this.isEnable) {
this.connection = null;
setTimeout(() => this.tryConnect(), this.reconnectIntervalInMillis);
setTimeout(() => this.tryConnect(), this.config.reconnectInterval);
}
}
});
this.connection.on('error', (err) => {
isClosedByError = true;
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.url}) 连接错误`, err);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.reconnectIntervalInMillis / 1000)} 秒后尝试重新连接`);
if (!this.isClosed) {
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 反向WebSocket (${this.config.url}) 连接错误`, err);
this.logger.logError.bind(this.logger)(`[OneBot] [WebSocket Client] 在 ${Math.floor(this.config.reconnectInterval / 1000)} 秒后尝试重新连接`);
if (this.isEnable) {
this.connection = null;
setTimeout(() => this.tryConnect(), this.reconnectIntervalInMillis);
setTimeout(() => this.tryConnect(), this.config.reconnectInterval);
}
});
}
@ -154,4 +154,43 @@ export class OB11ActiveWebSocketAdapter implements IOB11NetworkAdapter {
const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name);
this.checkStateAndReply<any>({ ...retdata });
}
async reload(newConfig: WebsocketClientConfig) {
const wasEnabled = this.isEnable;
const oldUrl = this.config.url;
const oldHeartInterval = this.config.heartInterval;
this.config = newConfig;
if (newConfig.enable && !wasEnabled) {
this.open();
return OB11NetworkReloadType.NetWorkOpen;
} else if (!newConfig.enable && wasEnabled) {
this.close();
return OB11NetworkReloadType.NetWorkClose;
}
if (oldUrl !== newConfig.url) {
this.close();
if (newConfig.enable) {
this.open();
}
return OB11NetworkReloadType.NetWorkReload;
}
if (oldHeartInterval !== newConfig.heartInterval) {
if (this.heartbeatRef) {
clearInterval(this.heartbeatRef);
this.heartbeatRef = null;
}
if (newConfig.heartInterval > 0 && this.isEnable) {
this.heartbeatRef = setInterval(() => {
if (this.connection && this.connection.readyState === WebSocket.OPEN) {
this.connection.send(JSON.stringify(new OB11HeartbeatEvent(this.core, newConfig.heartInterval, this.core.selfInfo.online ?? true, true)));
}
}, newConfig.heartInterval);
}
return OB11NetworkReloadType.NetWorkReload;
}
return OB11NetworkReloadType.Normal;
}
}

View File

@ -1,18 +1,29 @@
import { OB11BaseEvent } from '@/onebot/event/OB11BaseEvent';
import { OB11Message } from '@/onebot';
import { ActionMap } from '@/onebot/action';
import { NetworkConfigAdapter } from '../config/config';
export type OB11EmitEventContent = OB11BaseEvent | OB11Message;
export enum OB11NetworkReloadType {
Normal = 0,
ConfigChange = 1,
NetWorkReload = 2,
NetWorkClose = 3,
NetWorkOpen = 4
}
export interface IOB11NetworkAdapter {
actions?: ActionMap;
actions: ActionMap;
name: string;
isEnable: boolean;
config: NetworkConfigAdapter;
onEvent<T extends OB11EmitEventContent>(event: T): void;
open(): void | Promise<void>;
close(): void | Promise<void>;
reload(config: any): OB11NetworkReloadType | Promise<OB11NetworkReloadType>;
}
export class OB11NetworkManager {
@ -34,7 +45,7 @@ export class OB11NetworkManager {
}
}));
}
async emitEventByNames(map:Map<string,OB11EmitEventContent>){
async emitEventByNames(map: Map<string, OB11EmitEventContent>) {
return Promise.all(Array.from(map.entries()).map(([name, event]) => {
const adapter = this.adapters.get(name);
if (adapter) {
@ -71,6 +82,16 @@ export class OB11NetworkManager {
await Promise.all(Array.from(this.adapters.values()).map(adapter => adapter.close()));
this.adapters.clear();
}
async readloadAdapter<T>(name: string, config: T) {
const adapter = this.adapters.get(name);
if (adapter) {
await adapter.reload(config);
}
}
async readloadSomeAdapters<T>(configMap: Map<string, T>) {
await Promise.all(Array.from(configMap.entries()).map(([name, config]) => this.readloadAdapter(name, config)));
}
}
export * from './active-http';

View File

@ -1,23 +1,25 @@
import { IOB11NetworkAdapter } from './index';
import { IOB11NetworkAdapter, OB11NetworkReloadType } from './index';
import express, { Express, Request, Response } from 'express';
import http from 'http';
import { NapCatCore } from '@/core';
import { OB11Response } from '../action/OB11Response';
import { ActionMap } from '@/onebot/action';
import cors from 'cors';
import { HttpServerConfig } from '../config/config';
export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
private app: Express | undefined;
private server: http.Server | undefined;
private isOpen: boolean = false;
isEnable: boolean = false;
public config: HttpServerConfig;
constructor(
public name: string,
public port: number,
public token: string,
config: HttpServerConfig,
public core: NapCatCore,
public actions: ActionMap,
) {
this.config = structuredClone(config);
}
onEvent() {
@ -26,13 +28,13 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
open() {
try {
if (this.isOpen) {
if (this.isEnable) {
this.core.context.logger.logError('Cannot open a closed HTTP server');
return;
}
if (!this.isOpen) {
if (!this.isEnable) {
this.initializeServer();
this.isOpen = true;
this.isEnable = true;
}
} catch (e) {
this.core.context.logger.logError(`[OneBot] [HTTP Server Adapter] Boot Error: ${e}`);
@ -41,7 +43,7 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
}
async close() {
this.isOpen = false;
this.isEnable = false;
this.server?.close();
this.app = undefined;
}
@ -64,12 +66,12 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
});
});
this.app.use((req, res, next) => this.authorize(this.token, req, res, next));
this.app.use((req, res, next) => this.authorize(this.config.token, req, res, next));
this.app.use(async (req, res, _) => {
await this.handleRequest(req, res);
});
this.server.listen(this.port, () => {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.port}`);
this.server.listen(this.config.port, () => {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Start On Port ${this.config.port}`);
});
}
@ -86,7 +88,7 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
}
private async handleRequest(req: Request, res: Response) {
if (!this.isOpen) {
if (!this.isEnable) {
this.core.context.logger.log(`[OneBot] [HTTP Server Adapter] Server is closed`);
return res.json(OB11Response.error('Server is closed', 200));
}
@ -102,7 +104,7 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
const action = this.actions.get(actionName);
if (action) {
try {
const result = await action.handle(payload,this.name);
const result = await action.handle(payload, this.name);
return res.json(result);
} catch (error: any) {
return res.json(OB11Response.error(error?.stack?.toString() || error?.message || 'Error Handle', 200));
@ -111,4 +113,28 @@ export class OB11PassiveHttpAdapter implements IOB11NetworkAdapter {
return res.json(OB11Response.error('不支持的api ' + actionName, 200));
}
}
async reload(newConfig: HttpServerConfig) {
const wasEnabled = this.isEnable;
const oldPort = this.config.port;
this.config = newConfig;
if (newConfig.enable && !wasEnabled) {
this.open();
return OB11NetworkReloadType.NetWorkOpen;
} else if (!newConfig.enable && wasEnabled) {
this.close();
return OB11NetworkReloadType.NetWorkClose;
}
if (oldPort !== newConfig.port) {
this.close();
if (newConfig.enable) {
this.open();
}
return OB11NetworkReloadType.NetWorkReload;
}
return OB11NetworkReloadType.Normal;
}
}

View File

@ -1,4 +1,4 @@
import { IOB11NetworkAdapter, OB11EmitEventContent } from './index';
import { IOB11NetworkAdapter, OB11EmitEventContent, OB11NetworkReloadType } from './index';
import urlParse from 'url';
import { WebSocket, WebSocketServer } from 'ws';
import { Mutex } from 'async-mutex';
@ -10,47 +10,43 @@ import { OB11HeartbeatEvent } from '../event/meta/OB11HeartbeatEvent';
import { IncomingMessage } from 'http';
import { ActionMap } from '@/onebot/action';
import { LifeCycleSubType, OB11LifeCycleEvent } from '../event/meta/OB11LifeCycleEvent';
import { WebsocketServerConfig } from '../config/config';
export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
wsServer: WebSocketServer;
wsClients: WebSocket[] = [];
wsClientsMutex = new Mutex();
isOpen: boolean = false;
hasBeenClosed: boolean = false;
isEnable: boolean = false;
heartbeatInterval: number = 0;
core: NapCatCore;
logger: LogWrapper;
public config: WebsocketServerConfig;
private heartbeatIntervalId: NodeJS.Timeout | null = null;
wsClientWithEvent: WebSocket[] = [];
constructor(
public name: string,
ip: string,
port: number,
heartbeatInterval: number,
token: string,
core: NapCatCore,
config: WebsocketServerConfig,
public core: NapCatCore,
public actions: ActionMap,
) {
this.core = core;
this.config = structuredClone(config);
this.logger = core.context.logger;
if (ip === '0.0.0.0') {
if (this.config.host === '0.0.0.0') {
//兼容配置同时处理0.0.0.0逻辑
ip = '';
this.config.host = '';
}
this.heartbeatInterval = heartbeatInterval;
this.wsServer = new WebSocketServer({
port: port,
host: ip,
port: this.config.port,
host: this.config.host,
maxPayload: 1024 * 1024 * 1024,
});
this.wsServer.on('connection', async (wsClient, wsReq) => {
if (!this.isOpen) {
if (!this.isEnable) {
wsClient.close();
return;
}
//鉴权
this.authorize(token, wsClient, wsReq);
this.authorize(this.config.token, wsClient, wsReq);
const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
const isApiConnect = paramUrl === '/api' || paramUrl === '/api/';
if (!isApiConnect) {
@ -106,18 +102,14 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
}
open() {
if (this.isOpen) {
if (this.isEnable) {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Cannot open a opened WebSocket server');
return;
}
if (this.hasBeenClosed) {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Cannot open a WebSocket server that has been closed');
return;
}
const addressInfo = this.wsServer.address();
this.logger.log('[OneBot] [WebSocket Server] Server Started', typeof (addressInfo) === 'string' ? addressInfo : addressInfo?.address + ':' + addressInfo?.port);
this.isOpen = true;
this.isEnable = true;
if (this.heartbeatInterval > 0) {
this.registerHeartBeat();
}
@ -125,7 +117,7 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
}
async close() {
this.isOpen = false;
this.isEnable = false;
this.wsServer.close((err) => {
if (err) {
this.logger.logError.bind(this.logger)('[OneBot] [WebSocket Server] Error closing server:', err.message);
@ -198,5 +190,48 @@ export class OB11PassiveWebSocketAdapter implements IOB11NetworkAdapter {
const retdata = await action.websocketHandle(receiveData.params, echo ?? '', this.name);
this.checkStateAndReply<any>({ ...retdata }, wsClient);
}
async reload(newConfig: WebsocketServerConfig) {
const wasEnabled = this.isEnable;
const oldPort = this.config.port;
const oldHost = this.config.host;
const oldHeartbeatInterval = this.heartbeatInterval;
this.config = newConfig;
if (newConfig.enable && !wasEnabled) {
this.open();
return OB11NetworkReloadType.NetWorkOpen;
} else if (!newConfig.enable && wasEnabled) {
this.close();
return OB11NetworkReloadType.NetWorkClose;
}
if (oldPort !== newConfig.port || oldHost !== newConfig.host) {
this.close();
this.wsServer = new WebSocketServer({
port: newConfig.port,
host: newConfig.host === '0.0.0.0' ? '' : newConfig.host,
maxPayload: 1024 * 1024 * 1024,
});
if (newConfig.enable) {
this.open();
}
return OB11NetworkReloadType.NetWorkReload;
}
if (oldHeartbeatInterval !== newConfig.heartInterval) {
if (this.heartbeatIntervalId) {
clearInterval(this.heartbeatIntervalId);
this.heartbeatIntervalId = null;
}
this.heartbeatInterval = newConfig.heartInterval;
if (newConfig.heartInterval > 0 && this.isEnable) {
this.registerHeartBeat();
}
return OB11NetworkReloadType.NetWorkReload;
}
return OB11NetworkReloadType.Normal;
}
}