feat(sounds): add shouldPlayMessageSound filter (DM + federation-aware mention)

This commit is contained in:
Jannis Braun
2026-04-28 14:13:56 +02:00
parent eede7aeb38
commit 40dd2ab52f
2 changed files with 164 additions and 0 deletions
@@ -0,0 +1,135 @@
import { describe, it, expect } from 'vitest';
import { shouldPlayMessageSound } from './notificationFilters';
describe('shouldPlayMessageSound', () => {
const myIds = new Set(['local-snowflake', 'home-uid-42']);
it('suppresses messages authored by self (local id)', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'local-snowflake',
myIds,
isDmChannel: true,
content: 'hi',
allChannels: false,
}),
).toBe(false);
});
it('suppresses messages authored by self (home id)', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'home-uid-42',
myIds,
isDmChannel: true,
content: 'hi',
allChannels: false,
}),
).toBe(false);
});
it('plays for DM messages from others', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: true,
content: 'yo',
allChannels: false,
}),
).toBe(true);
});
it('suppresses non-DM, non-mention messages from others', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: 'general chatter',
allChannels: false,
}),
).toBe(false);
});
it('plays when content mentions me by local id', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: 'hey <@local-snowflake> look',
allChannels: false,
}),
).toBe(true);
});
it('plays when content mentions me by home id (federated)', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: 'cc <@home-uid-42>',
allChannels: false,
}),
).toBe(true);
});
it('does not play for mentions of someone else', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: 'pinging <@third-party>',
allChannels: false,
}),
).toBe(false);
});
it('plays for any non-self message when allChannels=true', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: 'general chatter',
allChannels: true,
}),
).toBe(true);
});
it('still suppresses self-authored even when allChannels=true', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'local-snowflake',
myIds,
isDmChannel: false,
content: 'my own message',
allChannels: true,
}),
).toBe(false);
});
it('handles null content (attachment-only) gracefully', () => {
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: true,
content: null,
allChannels: false,
}),
).toBe(true);
expect(
shouldPlayMessageSound({
authorUserId: 'someone-else',
myIds,
isDmChannel: false,
content: null,
allChannels: false,
}),
).toBe(false);
});
});
@@ -0,0 +1,29 @@
/**
* Decides whether a freshly-arrived chat message should fire the in-app
* `message.mp3` cue. Pure, federation-aware (matches against any of the
* caller's known self-ids).
*
* Rule (Discord-default):
* - Suppress messages authored by self (any id in myIds).
* - When allChannels=true, fire for every non-self message.
* - Otherwise, fire only if the channel is a DM, or if the content contains
* a `<@${id}>` mention for any id in myIds.
*/
export interface ShouldPlayMessageSoundInput {
authorUserId: string;
myIds: Set<string>;
isDmChannel: boolean;
content: string | null;
allChannels: boolean;
}
export function shouldPlayMessageSound(input: ShouldPlayMessageSoundInput): boolean {
if (input.myIds.has(input.authorUserId)) return false;
if (input.allChannels) return true;
if (input.isDmChannel) return true;
if (!input.content) return false;
for (const id of input.myIds) {
if (input.content.includes(`<@${id}>`)) return true;
}
return false;
}