Skip to content

Commit

Permalink
feat: napcat 事件
Browse files Browse the repository at this point in the history
  • Loading branch information
clansty committed Jul 12, 2024
1 parent 503a065 commit fd8cae8
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
max_line_length = 120
max_line_length = 999
tab_width = 2
trim_trailing_whitespace = true
ij_continuation_indent_size = 8
Expand Down
5 changes: 5 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

107 changes: 104 additions & 3 deletions main/src/client/NapCatClient/client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { CreateQQClientParamsBase, Friend, Group, QQClient } from '../QQClient';
import { CreateQQClientParamsBase, Friend, FriendIncreaseEvent, Group, GroupMemberDecreaseEvent, GroupMemberIncreaseEvent, MessageEvent, MessageRecallEvent, PokeEvent, QQClient } from '../QQClient';
import random from '../../utils/random';
import { getLogger, Logger } from 'log4js';
import posthog from '../../models/posthog';
import type { WSSendParam, WSSendReturn } from 'node-napcat-ts';
import type { Receive, WSReceiveHandler, WSSendParam, WSSendReturn } from 'node-napcat-ts';
import { NapCatFriend, NapCatGroup } from './entity';
import { napCatReceiveToMessageElem } from './convert';
import { NapCatFriendRequestEvent, NapCatGroupEvent, NapCatGroupInviteEvent } from './event';

export interface CreateNapCatParams extends CreateQQClientParamsBase {
type: 'napcat';
Expand Down Expand Up @@ -51,7 +53,7 @@ export class NapCatClient extends QQClient {

private async handleWebSocketMessage(message: string) {
this.logger.trace('receive', message);
const data = JSON.parse(message);
const data = JSON.parse(message) as WSReceiveHandler[keyof WSReceiveHandler] & { echo: string; status: 'ok' | 'error'; data: any; message: string };
if (data.echo) {
const promise = this.echoMap[data.echo];
if (!promise) return;
Expand All @@ -63,11 +65,110 @@ export class NapCatClient extends QQClient {
}
return;
}
if (data.post_type === 'message')
await this.handleMessage(data);
else if (data.post_type === 'notice' && data.notice_type === 'group_increase')
await this.handleGroupIncrease(data);
else if (data.post_type === 'notice' && data.notice_type === 'group_decrease')
await this.handleGroupDecrease(data);
else if (data.post_type === 'notice' && data.notice_type === 'friend_add')
await this.handleFriendIncrease(data);
else if (data.post_type === 'notice' && data.notice_type === 'friend_recall')
await this.handleMessageRecall(data);
else if (data.post_type === 'notice' && data.notice_type === 'group_recall')
await this.handleMessageRecall(data);
else if (data.post_type === 'notice' && data.notice_type === 'notify' && data.sub_type === 'poke')
await this.handlePoke(data);
else if (data.post_type === 'request' && data.request_type === 'friend')
await this.handleFriendRequest(data);
else if (data.post_type === 'request' && data.request_type === 'group' && data.sub_type === 'invite')
await this.handleGroupRequest(data);
}

public uin: number;
public nickname: string;

private async handleMessage(data: WSReceiveHandler['message']) {
let chat: Friend | Group;
if (data.message_type === 'private') {
// sender 一定是对方
chat = NapCatFriend.createExisted(this, { uid: data.user_id, remark: data.sender.card, nickname: data.sender.nickname });
}
else {
// 上报没有群名
chat = await this.pickGroup(data.group_id);
}
const message = (data.message as unknown as Receive[keyof Receive][]);
const replyNode = message.find(it => it.type === 'reply');
const replyMessage = replyNode ? await this.getMessage(replyNode.data.id) : undefined;
const event = new MessageEvent(
{ id: data.sender.user_id, card: data.sender.card, nickname: data.sender.nickname, name: data.sender.card || data.sender.nickname },
chat,
message.map(napCatReceiveToMessageElem),
data.message_id,
0, 0,
data.time,
data.raw_message,
replyMessage ? {
message: (replyMessage as any).message.filter(it => it.type !== 'reply').map(napCatReceiveToMessageElem),
rand: 0, fromId: replyMessage.sender.user_id, seq: replyMessage.message_id, time: replyMessage.time,
} : undefined,
undefined,
data.message_id.toString(),
replyMessage.sender.user_id === this.uin || message.some(it => it.type === 'at' && it.data.qq === this.uin),
message.some(it => it.type === 'at' && (it.data.qq === 0 || it.data.qq === 'all')),
);
for (const handler of this.onMessageHandlers) {
if (await handler(event)) {
break;
}
}
}

private async handleGroupIncrease(data: WSReceiveHandler['notice.group_increase']) {
const user = await this.callApi('get_stranger_info', { user_id: data.user_id });
const event = new GroupMemberIncreaseEvent(await this.pickGroup(data.group_id), data.user_id, user.nickname);
await this.callHandlers(this.onGroupMemberIncreaseHandlers, event);
}

private async handleGroupDecrease(data: WSReceiveHandler['notice.group_decrease']) {
const event = new GroupMemberDecreaseEvent(await this.pickGroup(data.group_id), data.user_id, data.operator_id, false);
await this.callHandlers(this.onGroupMemberDecreaseHandlers, event);
}

private async handleFriendIncrease(data: WSReceiveHandler['notice.friend_add']) {
const event = new FriendIncreaseEvent(await NapCatFriend.create(this, data.user_id));
await this.callHandlers(this.onFriendIncreaseHandlers, event);
}

private async handleMessageRecall(data: WSReceiveHandler['notice.friend_recall'] | WSReceiveHandler['notice.group_recall']) {
const chat = data.notice_type === 'friend_recall' ? await this.pickFriend(data.user_id) : await this.pickGroup(data.group_id);
const event = new MessageRecallEvent(chat, data.message_id, 0, data.time);
await this.callHandlers(this.onMessageRecallHandlers, event);
}

private async handlePoke(data: WSReceiveHandler['notice.notify.poke.group'] | WSReceiveHandler['notice.notify.poke.friend']) {
const chat = 'group_id' in data ? await this.pickGroup(data.group_id) : await this.pickFriend(data.user_id);
const operator = 'group_id' in data ? data.user_id : data.sender_id;
const event = new PokeEvent(chat, operator, data.target_id, '戳了戳', undefined);
await this.callHandlers(this.onPokeHandlers, event);
}

private async handleFriendRequest(data: WSReceiveHandler['request.friend']) {
const event = await NapCatFriendRequestEvent.create(this, data);
await this.callHandlers(this.onFriendRequestHandlers, event);
}

private async handleGroupRequest(data: WSReceiveHandler['request.group']) {
const event = await NapCatGroupEvent.create(this, data);
// 上面过滤过了
await this.callHandlers(this.onGroupInviteHandlers, event as NapCatGroupInviteEvent);
}

public async getMessage(messageId: number) {
return await this.callApi('get_msg', { message_id: messageId });
}

public async refreshSelf() {
const data = await this.callApi('get_login_info');
this.uin = data.user_id;
Expand Down
131 changes: 131 additions & 0 deletions main/src/client/NapCatClient/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { FriendRequestEvent, Gender, GroupInviteEvent, GroupRequestEvent, RequestEvent } from '@icqqjs/icqq';
import { NapCatClient } from './client';
import { getLogger, Logger } from 'log4js';
import posthog from '../../models/posthog';
import { WSReceiveHandler, WSSendReturn } from 'node-napcat-ts';
import { GroupRole } from '@icqqjs/icqq/lib/common';

export abstract class NapCatRequestEvent implements RequestEvent {
readonly post_type: 'request' = 'request';
readonly seq = 0;

protected constructor(
public readonly user_id: number,
public readonly nickname: string,
public readonly flag: string,
public readonly time: number,
) {
}

abstract approve(yes?: boolean): Promise<boolean>
}

export class NapCatFriendRequestEvent extends NapCatRequestEvent implements FriendRequestEvent {
public readonly request_type: 'friend' = 'friend';
public readonly sub_type: 'add' = 'add';
public readonly source: string = '';
private readonly logger: Logger;

private constructor(
public readonly client: NapCatClient,
user_id: number, nickname: string, flag: string, time: number,
public readonly comment: string,
public readonly age: number,
public readonly sex: Gender,
) {
super(user_id, nickname, flag, time);
this.logger = getLogger(`NapCatFriendRequestEvent - ${client.id} - ${user_id}`);
}

public static async create(client: NapCatClient, data: WSReceiveHandler['request.friend']): Promise<NapCatFriendRequestEvent> {
const info = await client.callApi('get_stranger_info', { user_id: data.user_id });
return new this(client, data.user_id, info.nickname, data.flag, data.time, data.comment, info.age, info.sex);
}

async approve(yes = true): Promise<boolean> {
try {
await this.client.callApi('set_friend_add_request', { flag: this.flag, approve: yes });
this.logger.info('已处理好友申请', yes ? '同意' : '拒绝');
return true;
}
catch (e) {
this.logger.error('处理好友申请失败', e);
posthog.capture('NapCat 处理好友申请失败', { error: e });
return false;
}
}
}

export abstract class NapCatGroupEvent extends NapCatRequestEvent {
public readonly request_type: 'group' = 'group';
public readonly sub_type: 'add' | 'invite';
private readonly logger: Logger;

protected constructor(
public readonly client: NapCatClient,
user_id: number, nickname: string, flag: string, time: number,
public readonly group_id: number,
public readonly group_name: string,
public readonly comment: string,
) {
super(user_id, nickname, flag, time);
this.logger = getLogger(`NapCatFriendRequestEvent - ${client.id} - ${user_id}`);
}

public static async create(client: NapCatClient, data: WSReceiveHandler['request.group']): Promise<NapCatGroupEvent> {
const info = await client.callApi('get_stranger_info', { user_id: data.user_id });
const groupInfo = await client.callApi('get_group_info', { group_id: data.group_id });
switch (data.sub_type) {
case 'add':
// 应该不会被用到
return new NapCatGroupRequestEvent(client, data.user_id, info.nickname, data.flag, data.time, data.group_id, groupInfo.group_name, data.comment);
case 'invite':
return await NapCatGroupInviteEvent.creatte(client, data, info, groupInfo);
}
}

async approve(yes = true): Promise<boolean> {
try {
await this.client.callApi('set_group_add_request', { flag: this.flag, approve: yes });
this.logger.info('已处理好友申请', yes ? '同意' : '拒绝');
return true;
}
catch (e) {
this.logger.error('处理好友申请失败', e);
posthog.capture('NapCat 处理好友申请失败', { error: e });
return false;
}
}
}

export class NapCatGroupRequestEvent extends NapCatGroupEvent implements GroupRequestEvent {
public readonly sub_type: 'add' = 'add';
public readonly inviter_id = 0;
public readonly tips: string = '';

public constructor(
client: NapCatClient,
user_id: number, nickname: string, flag: string, time: number,
group_id: number, group_name: string, comment: string,
) {
super(client, user_id, nickname, flag, time, group_id, group_name, comment);
}
}

export class NapCatGroupInviteEvent extends NapCatGroupEvent implements GroupInviteEvent {
public readonly sub_type: 'invite' = 'invite';

private constructor(
client: NapCatClient,
user_id: number, nickname: string, flag: string, time: number,
group_id: number, group_name: string, comment: string,
public readonly role: GroupRole,
) {
super(client, user_id, nickname, flag, time, group_id, group_name, comment);
}

public static async creatte(client: NapCatClient, data: WSReceiveHandler['request.group'], info: WSSendReturn['get_stranger_info'], groupInfo: WSSendReturn['get_group_info']): Promise<NapCatGroupInviteEvent> {
const memberInfo = await client.callApi('get_group_member_info', { group_id: data.group_id, user_id: data.user_id });
return new this(client, data.user_id, info.nickname, data.flag, data.time, data.group_id, groupInfo.group_name, data.comment, memberInfo.role);
}
}
1 change: 1 addition & 0 deletions main/src/client/NapCatClient/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './client';
export * from './entity';
export * from './event';
40 changes: 8 additions & 32 deletions main/src/client/OicqClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,64 +183,40 @@ export default class OicqClient extends QQClient {
'atme' in event ? event.atme : false,
'atall' in event ? event.atall : false,
);
for (const handler of this.onMessageHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onMessageHandlers, gEvent);
};

private onGroupMemberIncrease = async (event: MemberIncreaseEvent) => {
const gEvent = new GroupMemberIncreaseEvent(event.group, event.user_id, event.nickname);
for (const handler of this.onGroupMemberIncreaseHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onGroupMemberIncreaseHandlers, gEvent);
};

private onGroupMemberDecrease = async (event: MemberDecreaseEvent) => {
const gEvent = new GroupMemberDecreaseEvent(event.group, event.user_id, event.operator_id, event.dismiss);
for (const handler of this.onGroupMemberDecreaseHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onGroupMemberDecreaseHandlers, gEvent);
};

private onFriendIncrease = async (event: OicqFriendIncreaseEvent) => {
const gEvent = new FriendIncreaseEvent(event.friend);
for (const handler of this.onFriendIncreaseHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onFriendIncreaseHandlers, gEvent);
};

private onMessageRecall = async (event: FriendRecallEvent | GroupRecallEvent) => {
const gEvent = new MessageRecallEvent('friend' in event ? event.friend : event.group, event.seq, event.rand, event.time);
for (const handler of this.onMessageRecallHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onMessageRecallHandlers, gEvent);
};

private onPoke = async (event: FriendPokeEvent | GroupPokeEvent) => {
const gEvent = new PokeEvent('friend' in event ? event.friend : event.group, event.operator_id, event.target_id, event.action, event.suffix);
for (const handler of this.onPokeHandlers) {
const res = await handler(gEvent);
if (res) return;
}
await this.callHandlers(this.onPokeHandlers, gEvent);
};

private onFriendRequest = async (event: FriendRequestEvent) => {
for (const handler of this.onFriendRequestHandlers) {
const res = await handler(event);
if (res) return;
}
await this.callHandlers(this.onFriendRequestHandlers, event);
};

private onGroupInvite = async (event: GroupInviteEvent) => {
for (const handler of this.onGroupInviteHandlers) {
const res = await handler(event);
if (res) return;
}
await this.callHandlers(this.onGroupInviteHandlers, event);
};

public async makeForwardMsgSelf(msglist: Forwardable[] | Forwardable, dm?: boolean): Promise<{
Expand Down
2 changes: 1 addition & 1 deletion main/src/client/QQClient/entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { MessageRet, MfaceElem, Quotable, Sendable } from '@icqqjs/icqq';
import { Gender, GroupRole } from '@icqqjs/icqq/lib/common';
import { AtElem, FaceElem, ImageElem, PttElem, TextElem, VideoElem } from '@icqqjs/icqq/lib/message/elements';
import { QQClient } from './index';
import type { Receive } from 'node-napcat-ts';

// 全平台支持的 Elem
export type SendableElem = TextElem | FaceElem | ImageElem | AtElem | PttElem | VideoElem | MfaceElem;
Expand Down
Loading

0 comments on commit fd8cae8

Please sign in to comment.