Files
buzz/desktop/src/features/notifications/lib/shouldNotify.ts
Tom Brow 4b21a10c4b fix: don't notify workflow owners through their attribution tag (#8694)
A workflow message names its owner in a marked p tag, ["p", owner, "", "owner"], for attribution. #p live, history and push matching, operator listeners, desktop, mobile and the iOS NSE treat that tag as attribution rather than a mention. A workflow that mentions its owner adds a plain p, so the owner is notified only when actually mentioned. NOSTR.md records the #p exception.

Co-authored-by: studio <dc2fc481343672df31a689840c09299972e704dac592e0da6f18486646a7ee3e@buzz.block.builderlab.xyz>
Signed-off-by: Tom Brow <tomb@squareup.com>
2026-10-29 15:21:56 -07:00

101 lines
2.2 KiB
TypeScript

import type { RelayEvent } from "@/shared/api/types";
import {
getThreadReference,
isBroadcastReply,
} from "@/features/messages/lib/threading";
/**
* Whether the event's `p` tags address `currentPubkey`. A workflow message
* names its owner in `["p", owner, "", "owner"]` for attribution only; that
* tag is not a mention. A workflow that mentions its owner adds a plain `p`.
*/
export function hasMentionForEvent(
event: RelayEvent,
currentPubkey: string,
): boolean {
if (currentPubkey.length === 0) {
return false;
}
const workflow = event.tags.some(
(tag) => tag[0] === "buzz:workflow" && tag[1] === "true",
);
return event.tags.some(
(tag) =>
tag[0] === "p" &&
tag[1]?.toLowerCase() === currentPubkey &&
!(workflow && tag[3] === "owner"),
);
}
export type NotifyOptions = {
participatedRootIds: ReadonlySet<string>;
followedRootIds: ReadonlySet<string>;
authoredRootIds: ReadonlySet<string>;
mutedRootIds?: ReadonlySet<string>;
mutedChannelIds?: ReadonlySet<string>;
channelId?: string | null;
};
export function shouldNotifyForEvent(
event: RelayEvent,
currentPubkey: string,
options: NotifyOptions,
): boolean {
const {
participatedRootIds,
followedRootIds,
authoredRootIds,
mutedRootIds = new Set(),
mutedChannelIds = new Set(),
channelId = null,
} = options;
const { parentId, rootId } = getThreadReference(event.tags);
if (isBroadcastReply(event.tags)) {
return true;
}
if (hasMentionForEvent(event, currentPubkey)) {
return true;
}
if (channelId !== null && mutedChannelIds.has(channelId)) {
return false;
}
if (parentId === null) {
return true;
}
if (rootId !== null && mutedRootIds.has(rootId)) {
return false;
}
if (rootId !== null && participatedRootIds.has(rootId)) {
return true;
}
if (rootId !== null && followedRootIds.has(rootId)) {
return true;
}
if (rootId !== null && authoredRootIds.has(rootId)) {
return true;
}
return false;
}
export function isHighPriorityEventForUser(
event: RelayEvent,
currentPubkey: string,
): boolean {
if (hasMentionForEvent(event, currentPubkey)) {
return true;
}
if (isBroadcastReply(event.tags)) {
return true;
}
return false;
}