feat(sounds): add shouldPlayMessageSound filter (DM + federation-aware mention)
This commit is contained in:
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user