From 2b32ffa990b2a191b531aed1abb754fe1451fbf0 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sun, 31 May 2026 21:38:46 +0200 Subject: [PATCH] feat(mentions): add MentionReceived/MentionsList packets + parsers and composers --- packages/communication/src/NitroMessages.ts | 9 +++ .../src/messages/incoming/IncomingHeader.ts | 4 ++ .../src/messages/incoming/index.ts | 1 + .../incoming/mentions/MentionReceivedEvent.ts | 16 +++++ .../incoming/mentions/MentionsListEvent.ts | 16 +++++ .../src/messages/incoming/mentions/index.ts | 2 + .../src/messages/outgoing/OutgoingHeader.ts | 4 ++ .../src/messages/outgoing/index.ts | 1 + .../mentions/MarkMentionsReadComposer.ts | 21 ++++++ .../mentions/RequestMentionsComposer.ts | 21 ++++++ .../src/messages/outgoing/mentions/index.ts | 2 + .../src/messages/parser/index.ts | 1 + .../parser/mentions/MentionListItem.ts | 37 ++++++++++ .../parser/mentions/MentionReceivedParser.ts | 22 ++++++ .../parser/mentions/MentionsListParser.ts | 27 +++++++ .../__tests__/MentionsParsers.test.ts | 71 +++++++++++++++++++ .../src/messages/parser/mentions/index.ts | 3 + 17 files changed, 258 insertions(+) create mode 100644 packages/communication/src/messages/incoming/mentions/MentionReceivedEvent.ts create mode 100644 packages/communication/src/messages/incoming/mentions/MentionsListEvent.ts create mode 100644 packages/communication/src/messages/incoming/mentions/index.ts create mode 100644 packages/communication/src/messages/outgoing/mentions/MarkMentionsReadComposer.ts create mode 100644 packages/communication/src/messages/outgoing/mentions/RequestMentionsComposer.ts create mode 100644 packages/communication/src/messages/outgoing/mentions/index.ts create mode 100644 packages/communication/src/messages/parser/mentions/MentionListItem.ts create mode 100644 packages/communication/src/messages/parser/mentions/MentionReceivedParser.ts create mode 100644 packages/communication/src/messages/parser/mentions/MentionsListParser.ts create mode 100644 packages/communication/src/messages/parser/mentions/__tests__/MentionsParsers.test.ts create mode 100644 packages/communication/src/messages/parser/mentions/index.ts diff --git a/packages/communication/src/NitroMessages.ts b/packages/communication/src/NitroMessages.ts index 6e01288..a11ad81 100644 --- a/packages/communication/src/NitroMessages.ts +++ b/packages/communication/src/NitroMessages.ts @@ -12,6 +12,7 @@ import { RareValuesEvent, RequestRareValuesComposer } from './messages'; import { WheelBuySpinComposer, WheelDataEvent, WheelOpenComposer, WheelRecentWinsEvent, WheelResultEvent, WheelSpinComposer } from './messages'; import { WheelAdminGetPrizesComposer, WheelAdminPrizesEvent, WheelAdminSavePrizesComposer } from './messages'; import { SoundboardPlayEvent, SoundboardSettingsEvent, SoundboardPlayComposer, SoundboardSetEnabledComposer } from './messages'; +import { MarkMentionsReadComposer, MentionReceivedEvent, MentionsListEvent, RequestMentionsComposer } from './messages'; export class NitroMessages implements IMessageConfiguration { private _events: Map; @@ -300,6 +301,10 @@ export class NitroMessages implements IMessageConfiguration this._events.set(IncomingHeader.MARKETPLACE_ITEMS_SEARCHED, MarketPlaceOffersEvent); this._events.set(IncomingHeader.MARKETPLACE_OWN_ITEMS, MarketplaceOwnOffersEvent); + // MENTIONS + this._events.set(IncomingHeader.MENTION_RECEIVED, MentionReceivedEvent); + this._events.set(IncomingHeader.MENTIONS_LIST, MentionsListEvent); + // MODERATION this._events.set(IncomingHeader.USER_BANNED, UserBannedMessageEvent); this._events.set(IncomingHeader.MODERATION_CAUTION, ModeratorCautionEvent); @@ -1153,6 +1158,10 @@ export class NitroMessages implements IMessageConfiguration this._composers.set(OutgoingHeader.REQUEST_SELL_ITEM, GetMarketplaceCanMakeOfferComposer); this._composers.set(OutgoingHeader.REQUEST_MARKETPLACE_ITEM_STATS, GetMarketplaceItemStatsComposer); + // MENTIONS + this._composers.set(OutgoingHeader.REQUEST_MENTIONS, RequestMentionsComposer); + this._composers.set(OutgoingHeader.MARK_MENTIONS_READ, MarkMentionsReadComposer); + // BOTS this._composers.set(OutgoingHeader.USER_BOTS, GetBotInventoryComposer); diff --git a/packages/communication/src/messages/incoming/IncomingHeader.ts b/packages/communication/src/messages/incoming/IncomingHeader.ts index ca731c5..4f985c6 100644 --- a/packages/communication/src/messages/incoming/IncomingHeader.ts +++ b/packages/communication/src/messages/incoming/IncomingHeader.ts @@ -3,6 +3,10 @@ export class IncomingHeader // These packets do not belong to this revision, so these are custom packet ids public static AREA_HIDE = 6001; + // MENTIONS + public static MENTION_RECEIVED = 4801; + public static MENTIONS_LIST = 4802; + // Original packets public static ACHIEVEMENT_LIST = 305; public static AUTHENTICATED = 2491; diff --git a/packages/communication/src/messages/incoming/index.ts b/packages/communication/src/messages/incoming/index.ts index 774a269..a01ad24 100644 --- a/packages/communication/src/messages/incoming/index.ts +++ b/packages/communication/src/messages/incoming/index.ts @@ -40,6 +40,7 @@ export * from './inventory/trading'; export * from './landingview'; export * from './landingview/votes'; export * from './marketplace'; +export * from './mentions'; export * from './moderation'; export * from './mysterybox'; export * from './navigator'; diff --git a/packages/communication/src/messages/incoming/mentions/MentionReceivedEvent.ts b/packages/communication/src/messages/incoming/mentions/MentionReceivedEvent.ts new file mode 100644 index 0000000..9c32597 --- /dev/null +++ b/packages/communication/src/messages/incoming/mentions/MentionReceivedEvent.ts @@ -0,0 +1,16 @@ +import { IMessageEvent } from '@nitrots/api'; +import { MessageEvent } from '@nitrots/events'; +import { MentionReceivedParser } from '../../parser'; + +export class MentionReceivedEvent extends MessageEvent implements IMessageEvent +{ + constructor(callBack: Function) + { + super(callBack, MentionReceivedParser); + } + + public getParser(): MentionReceivedParser + { + return this.parser as MentionReceivedParser; + } +} diff --git a/packages/communication/src/messages/incoming/mentions/MentionsListEvent.ts b/packages/communication/src/messages/incoming/mentions/MentionsListEvent.ts new file mode 100644 index 0000000..734a93b --- /dev/null +++ b/packages/communication/src/messages/incoming/mentions/MentionsListEvent.ts @@ -0,0 +1,16 @@ +import { IMessageEvent } from '@nitrots/api'; +import { MessageEvent } from '@nitrots/events'; +import { MentionsListParser } from '../../parser'; + +export class MentionsListEvent extends MessageEvent implements IMessageEvent +{ + constructor(callBack: Function) + { + super(callBack, MentionsListParser); + } + + public getParser(): MentionsListParser + { + return this.parser as MentionsListParser; + } +} diff --git a/packages/communication/src/messages/incoming/mentions/index.ts b/packages/communication/src/messages/incoming/mentions/index.ts new file mode 100644 index 0000000..a4ffaed --- /dev/null +++ b/packages/communication/src/messages/incoming/mentions/index.ts @@ -0,0 +1,2 @@ +export * from './MentionReceivedEvent'; +export * from './MentionsListEvent'; diff --git a/packages/communication/src/messages/outgoing/OutgoingHeader.ts b/packages/communication/src/messages/outgoing/OutgoingHeader.ts index dbaf342..11dbe5d 100644 --- a/packages/communication/src/messages/outgoing/OutgoingHeader.ts +++ b/packages/communication/src/messages/outgoing/OutgoingHeader.ts @@ -3,6 +3,10 @@ export class OutgoingHeader public static CLICK_FURNI = 6002; public static CLICK_USER = 10020; + // MENTIONS + public static REQUEST_MENTIONS = 4803; + public static MARK_MENTIONS_READ = 4804; + public static ACHIEVEMENT_LIST = 219; public static AUTHENTICATION = -1; public static BOT_CONFIGURATION = 1986; diff --git a/packages/communication/src/messages/outgoing/index.ts b/packages/communication/src/messages/outgoing/index.ts index cbbc423..56a2f94 100644 --- a/packages/communication/src/messages/outgoing/index.ts +++ b/packages/communication/src/messages/outgoing/index.ts @@ -36,6 +36,7 @@ export * from './inventory/unseen'; export * from './landingview'; export * from './landingview/votes'; export * from './marketplace'; +export * from './mentions'; export * from './moderation'; export * from './mysterybox'; export * from './navigator'; diff --git a/packages/communication/src/messages/outgoing/mentions/MarkMentionsReadComposer.ts b/packages/communication/src/messages/outgoing/mentions/MarkMentionsReadComposer.ts new file mode 100644 index 0000000..0680401 --- /dev/null +++ b/packages/communication/src/messages/outgoing/mentions/MarkMentionsReadComposer.ts @@ -0,0 +1,21 @@ +import { IMessageComposer } from '@nitrots/api'; + +export class MarkMentionsReadComposer implements IMessageComposer> +{ + private _data: ConstructorParameters; + + constructor(mode: number, mentionId: number = 0) + { + this._data = [mode, mentionId]; + } + + public getMessageArray() + { + return this._data; + } + + public dispose(): void + { + return; + } +} diff --git a/packages/communication/src/messages/outgoing/mentions/RequestMentionsComposer.ts b/packages/communication/src/messages/outgoing/mentions/RequestMentionsComposer.ts new file mode 100644 index 0000000..181fb93 --- /dev/null +++ b/packages/communication/src/messages/outgoing/mentions/RequestMentionsComposer.ts @@ -0,0 +1,21 @@ +import { IMessageComposer } from '@nitrots/api'; + +export class RequestMentionsComposer implements IMessageComposer> +{ + private _data: ConstructorParameters; + + constructor() + { + this._data = []; + } + + public getMessageArray() + { + return this._data; + } + + public dispose(): void + { + return; + } +} diff --git a/packages/communication/src/messages/outgoing/mentions/index.ts b/packages/communication/src/messages/outgoing/mentions/index.ts new file mode 100644 index 0000000..3e1222f --- /dev/null +++ b/packages/communication/src/messages/outgoing/mentions/index.ts @@ -0,0 +1,2 @@ +export * from './RequestMentionsComposer'; +export * from './MarkMentionsReadComposer'; diff --git a/packages/communication/src/messages/parser/index.ts b/packages/communication/src/messages/parser/index.ts index 5567320..ccf664c 100644 --- a/packages/communication/src/messages/parser/index.ts +++ b/packages/communication/src/messages/parser/index.ts @@ -40,6 +40,7 @@ export * from './inventory/trading'; export * from './landingview'; export * from './landingview/votes'; export * from './marketplace'; +export * from './mentions'; export * from './moderation'; export * from './mysterybox'; export * from './navigator'; diff --git a/packages/communication/src/messages/parser/mentions/MentionListItem.ts b/packages/communication/src/messages/parser/mentions/MentionListItem.ts new file mode 100644 index 0000000..5f20d8b --- /dev/null +++ b/packages/communication/src/messages/parser/mentions/MentionListItem.ts @@ -0,0 +1,37 @@ +import { IMessageDataWrapper } from '@nitrots/api'; + +export class MentionListItem +{ + private _mentionId: number; + private _senderId: number; + private _senderUsername: string; + private _roomId: number; + private _roomName: string; + private _message: string; + private _mentionType: number; + private _timestamp: number; + private _read: boolean; + + constructor(wrapper: IMessageDataWrapper, withReadFlag: boolean) + { + this._mentionId = wrapper.readInt(); + this._senderId = wrapper.readInt(); + this._senderUsername = wrapper.readString(); + this._roomId = wrapper.readInt(); + this._roomName = wrapper.readString(); + this._message = wrapper.readString(); + this._mentionType = wrapper.readInt(); + this._timestamp = wrapper.readInt(); + this._read = withReadFlag ? wrapper.readBoolean() : false; + } + + public get mentionId(): number { return this._mentionId; } + public get senderId(): number { return this._senderId; } + public get senderUsername(): string { return this._senderUsername; } + public get roomId(): number { return this._roomId; } + public get roomName(): string { return this._roomName; } + public get message(): string { return this._message; } + public get mentionType(): number { return this._mentionType; } + public get timestamp(): number { return this._timestamp; } + public get read(): boolean { return this._read; } +} diff --git a/packages/communication/src/messages/parser/mentions/MentionReceivedParser.ts b/packages/communication/src/messages/parser/mentions/MentionReceivedParser.ts new file mode 100644 index 0000000..e6485e9 --- /dev/null +++ b/packages/communication/src/messages/parser/mentions/MentionReceivedParser.ts @@ -0,0 +1,22 @@ +import { IMessageDataWrapper, IMessageParser } from '@nitrots/api'; +import { MentionListItem } from './MentionListItem'; + +export class MentionReceivedParser implements IMessageParser +{ + private _mention: MentionListItem; + + public flush(): boolean + { + this._mention = null; + return true; + } + + public parse(wrapper: IMessageDataWrapper): boolean + { + if(!wrapper) return false; + this._mention = new MentionListItem(wrapper, false); + return true; + } + + public get mention(): MentionListItem { return this._mention; } +} diff --git a/packages/communication/src/messages/parser/mentions/MentionsListParser.ts b/packages/communication/src/messages/parser/mentions/MentionsListParser.ts new file mode 100644 index 0000000..6602aab --- /dev/null +++ b/packages/communication/src/messages/parser/mentions/MentionsListParser.ts @@ -0,0 +1,27 @@ +import { IMessageDataWrapper, IMessageParser } from '@nitrots/api'; +import { MentionListItem } from './MentionListItem'; + +export class MentionsListParser implements IMessageParser +{ + private _mentions: MentionListItem[]; + + public flush(): boolean + { + this._mentions = []; + return true; + } + + public parse(wrapper: IMessageDataWrapper): boolean + { + if(!wrapper) return false; + this._mentions = []; + const count = wrapper.readInt(); + for(let i = 0; i < count; i++) + { + this._mentions.push(new MentionListItem(wrapper, true)); + } + return true; + } + + public get mentions(): MentionListItem[] { return this._mentions; } +} diff --git a/packages/communication/src/messages/parser/mentions/__tests__/MentionsParsers.test.ts b/packages/communication/src/messages/parser/mentions/__tests__/MentionsParsers.test.ts new file mode 100644 index 0000000..6ee0e2c --- /dev/null +++ b/packages/communication/src/messages/parser/mentions/__tests__/MentionsParsers.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest'; +import { BinaryReader, BinaryWriter } from '@nitrots/utils'; +import { MentionReceivedParser } from '../MentionReceivedParser'; +import { MentionsListParser } from '../MentionsListParser'; + +class TestWrapper +{ + constructor(private reader: BinaryReader) {} + readByte() { return this.reader.readByte(); } + readBytes(n: number) { return this.reader.readBytes(n); } + readBoolean() { return this.reader.readByte() === 1; } + readShort() { return this.reader.readShort(); } + readInt() { return this.reader.readInt(); } + readFloat() { return this.reader.readFloat(); } + readDouble() { return this.reader.readDouble(); } + readString() { const len = this.reader.readShort(); return this.reader.readBytes(len).toString(); } + header = 0; + get bytesAvailable() { return this.reader.remaining() > 0; } +} + +describe('MentionReceivedParser', () => +{ + it('parses a single mention without read flag', () => + { + const w = new BinaryWriter(); + w.writeInt(7); w.writeInt(42); w.writeString('Bob'); w.writeInt(99); + w.writeString('My Room'); w.writeString('ciao @me'); w.writeInt(0); w.writeInt(1717000000); + const parser = new MentionReceivedParser(); + parser.flush(); + parser.parse(new TestWrapper(new BinaryReader(w.getBuffer())) as any); + const m = parser.mention; + expect(m.mentionId).toBe(7); + expect(m.senderId).toBe(42); + expect(m.senderUsername).toBe('Bob'); + expect(m.roomId).toBe(99); + expect(m.roomName).toBe('My Room'); + expect(m.message).toBe('ciao @me'); + expect(m.mentionType).toBe(0); + expect(m.timestamp).toBe(1717000000); + expect(m.read).toBe(false); + }); +}); + +describe('MentionsListParser', () => +{ + it('parses a count-prefixed list with read flags', () => + { + const w = new BinaryWriter(); + w.writeInt(1); w.writeInt(3); w.writeInt(42); w.writeString('Bob'); w.writeInt(99); + w.writeString('My Room'); w.writeString('@all festa'); w.writeInt(1); w.writeInt(1717000000); w.writeByte(1); + const parser = new MentionsListParser(); + parser.flush(); + parser.parse(new TestWrapper(new BinaryReader(w.getBuffer())) as any); + expect(parser.mentions).toHaveLength(1); + expect(parser.mentions[0].mentionId).toBe(3); + expect(parser.mentions[0].senderUsername).toBe('Bob'); + expect(parser.mentions[0].read).toBe(true); + expect(parser.mentions[0].mentionType).toBe(1); + expect(parser.mentions[0].message).toBe('@all festa'); + }); + + it('parses an empty list', () => + { + const w = new BinaryWriter(); + w.writeInt(0); + const parser = new MentionsListParser(); + parser.flush(); + parser.parse(new TestWrapper(new BinaryReader(w.getBuffer())) as any); + expect(parser.mentions).toHaveLength(0); + }); +}); diff --git a/packages/communication/src/messages/parser/mentions/index.ts b/packages/communication/src/messages/parser/mentions/index.ts new file mode 100644 index 0000000..50d43db --- /dev/null +++ b/packages/communication/src/messages/parser/mentions/index.ts @@ -0,0 +1,3 @@ +export * from './MentionListItem'; +export * from './MentionReceivedParser'; +export * from './MentionsListParser';