From c4a5db51fed495bc494eea279d2e9e031ac55bf9 Mon Sep 17 00:00:00 2001 From: izzy Date: Wed, 4 Jun 2025 15:46:17 +0100 Subject: [PATCH] feat: support for everyone/online/role mentions on messages --- src/classes/Message.ts | 26 +++++++++++++++++++++- src/classes/ServerRole.ts | 47 +++++++++++++++++++++++++++++++++++++++ src/classes/index.ts | 1 + src/hydration/message.ts | 23 ++++++++++++++++++- src/hydration/server.ts | 11 +++++---- 5 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 src/classes/ServerRole.ts diff --git a/src/classes/Message.ts b/src/classes/Message.ts index c08b58de..703eb81c 100644 --- a/src/classes/Message.ts +++ b/src/classes/Message.ts @@ -11,12 +11,14 @@ import { decodeTime } from "ulid"; import type { Client } from "../Client.js"; import type { MessageCollection } from "../collections/MessageCollection.js"; +import { MessageFlags } from "../hydration/message.js"; import type { Channel } from "./Channel.js"; import { File } from "./File.js"; import type { MessageEmbed } from "./MessageEmbed.js"; import type { Server } from "./Server.js"; import type { ServerMember } from "./ServerMember.js"; +import { ServerRole } from "./ServerRole.js"; import type { SystemMessage } from "./SystemMessage.js"; import type { User } from "./User.js"; @@ -177,11 +179,33 @@ export class Message { return this.#collection.getUnderlyingObject(this.id).mentionIds; } + /** + * IDs of roles this message mentions + */ + get roleMentionIds(): string[] | undefined { + return this.#collection.getUnderlyingObject(this.id).roleMentionIds; + } + + /** + * Roles this message mentions + */ + get roleMentions(): ServerRole[] | undefined { + return this.roleMentionIds + ?.map((roleId) => this.server?.roles.get(roleId) as ServerRole) + .filter((role) => role); + } + /** * Whether this message mentions us */ get mentioned(): boolean { - return this.mentionIds?.includes(this.#collection.client.user!.id) ?? false; + return ( + !!(this.flags & MessageFlags.MentionsEveryone) || + !!(this.flags & MessageFlags.MentionsOnline) || + this.mentionIds?.includes(this.#collection.client.user!.id) || + this.roleMentions?.some((role) => role.assigned) || + false + ); } /** diff --git a/src/classes/ServerRole.ts b/src/classes/ServerRole.ts new file mode 100644 index 00000000..28e9c97f --- /dev/null +++ b/src/classes/ServerRole.ts @@ -0,0 +1,47 @@ +import type { Role as APIRole, OverrideField } from "revolt-api"; + +import type { Client } from "../Client.js"; + +/** + * Server Role + */ +export class ServerRole { + protected client: Client; + protected serverId: string; + + readonly id: string; + readonly name: string; + readonly permissions: OverrideField; + readonly colour?: string; + readonly hoist: boolean; + readonly rank: number; + + /** + * Construct server role + * @param client Client + * @param serverId Server ID + * @param id Role ID + * @param data Role data + */ + constructor(client: Client, serverId: string, id: string, data: APIRole) { + this.client = client; + this.serverId = serverId; + + this.id = id; + this.name = data.name; + this.permissions = data.permissions; + this.colour = data.colour ?? undefined; + this.hoist = data.hoist || false; + this.rank = data.rank ?? 0; + } + + /** + * Whether this role is assigned to our server member + */ + get assigned() { + return ( + this.client.servers.get(this.serverId)?.member?.roles.includes(this.id) || + false + ); + } +} diff --git a/src/classes/index.ts b/src/classes/index.ts index c34f5cd1..9bd7e417 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -10,6 +10,7 @@ export * from "./Message.js"; export * from "./MessageEmbed.js"; export * from "./PublicBot.js"; export * from "./PublicInvite.js"; +export * from "./ServerRole.js"; export * from "./Server.js"; export * from "./ServerBan.js"; export * from "./ServerMember.js"; diff --git a/src/hydration/message.ts b/src/hydration/message.ts index 22744988..9c8fbbbd 100644 --- a/src/hydration/message.ts +++ b/src/hydration/message.ts @@ -23,11 +23,12 @@ export type HydratedMessage = { editedAt?: Date; embeds?: MessageEmbed[]; mentionIds?: string[]; + roleMentionIds?: string[]; replyIds?: string[]; reactions: ReactiveMap>; interactions?: Interactions; masquerade?: Masquerade; - flags?: number; + flags?: MessageFlags; }; export const messageHydration: Hydrate, HydratedMessage> = { @@ -58,6 +59,7 @@ export const messageHydration: Hydrate, HydratedMessage> = { embeds: (message, ctx) => message.embeds!.map((embed) => MessageEmbed.from(ctx as Client, embed)), mentionIds: (message) => message.mentions!, + roleMentionIds: (message) => message.role_mentions!, replyIds: (message) => message.replies!, reactions: (message) => { const map = new ReactiveMap>(); @@ -76,3 +78,22 @@ export const messageHydration: Hydrate, HydratedMessage> = { reactions: new ReactiveMap(), }), }; + +/** + * Flags attributed to messages + */ +export enum MessageFlags { + /** + * Message will not send push / desktop notifications + */ + SuppressNotifications = 1, + /** + * Message will mention all users who can see the channel + */ + MentionsEveryone = 2, + /** + * Message will mention all users who are online and can see the channel. + * This cannot be true if MentionsEveryone is true + */ + MentionsOnline = 3, +} diff --git a/src/hydration/server.ts b/src/hydration/server.ts index 0cb19b03..acc66633 100644 --- a/src/hydration/server.ts +++ b/src/hydration/server.ts @@ -3,12 +3,12 @@ import { ReactiveSet } from "@solid-primitives/set"; import type { Server as APIServer, Category, - Role, SystemMessageChannels, } from "revolt-api"; import type { Client } from "../Client.js"; import { File } from "../classes/File.js"; +import { ServerRole } from "../classes/ServerRole.js"; import type { Hydrate } from "./index.js"; @@ -26,7 +26,7 @@ export type HydratedServer = { categories?: Category[]; systemMessages?: SystemMessageChannels; - roles: ReactiveMap; + roles: ReactiveMap; defaultPermissions: number; flags: ServerFlags; @@ -51,9 +51,12 @@ export const serverHydration: Hydrate = { channelIds: (server) => new ReactiveSet(server.channels), categories: (server) => server.categories ?? [], systemMessages: (server) => server.system_messages ?? {}, - roles: (server) => + roles: (server, ctx) => new ReactiveMap( - Object.keys(server.roles!).map((id) => [id, server.roles![id]]), + Object.keys(server.roles!).map((id) => [ + id, + new ServerRole(ctx as Client, server._id, id, server.roles![id]), + ]), ), defaultPermissions: (server) => server.default_permissions, icon: (server, ctx) => new File(ctx as Client, server.icon!),