You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
648 lines
22 KiB
648 lines
22 KiB
import { useCallback, useEffect, useMemo, useState } from 'react' |
|
import type { Event } from 'nostr-tools' |
|
import { kinds as nostrKinds } from 'nostr-tools' |
|
import storage from '@/services/local-storage.service' |
|
import { ExtendedKind, DEFAULT_FEED_SHOW_KINDS } from '@/constants' |
|
import { getPubkeysFromPTags } from '@/lib/tag' |
|
import { normalizeUrl } from '@/lib/url' |
|
import { |
|
augmentSubRequestsWithFavoritesFastReadAndInbox, |
|
getRelayUrlsWithFavoritesFastReadAndInbox, |
|
userReadInboxUrls, |
|
userWriteOutboxUrls |
|
} from '@/lib/favorites-feed-relays' |
|
import { stableSpellFeedFilterKey } from '@/lib/spell-feed-request-identity' |
|
import { isUserInEventMentions } from '@/lib/event' |
|
import { |
|
isNotificationThreadInteractionEvent, |
|
parseThreadWatchListRefs, |
|
threadWatchMatchesRefs |
|
} from '@/lib/notification-thread-watch' |
|
import { isIncomingNotificationsPaymentEvent } from '@/lib/superchat' |
|
import { |
|
decodeFollowSetSpellId, |
|
getFollowSetDTag, |
|
isFollowSetSpellId, |
|
pubkeysFromFollowSetEvent |
|
} from '@/lib/follow-set-spell' |
|
import client from '@/services/client.service' |
|
import { |
|
buildBookmarksSubRequests, |
|
buildCalendarSpellFilter, |
|
buildDiscussionFilter, |
|
buildInterestsSubRequests, |
|
buildMediaSpellFilter, |
|
buildNostrSpecsSpellFilter, |
|
buildNotificationsFollowedThreadSubRequests, |
|
buildNotificationsSpellSubRequests, |
|
buildWebBookmarksSpellSubRequests, |
|
NOTIFICATION_SPELL_LOADING_SAFETY_MS, |
|
FAUX_SPELL_EVENT_LIMIT, |
|
MEDIA_SPELL_KINDS, |
|
NOTIFICATION_SPELL_KINDS, |
|
applyFauxSpellCapsToSubRequests, |
|
ensureFauxSpellRelayStackTouchesFastRead |
|
} from './fauxSpellFeeds' |
|
import { getRelaysForSpell, spellEventToFilter } from '@/services/spell.service' |
|
import type { TFeedSubRequest } from '@/types' |
|
import { isFollowFeedFauxSpellId } from './fauxSpellConfig' |
|
import { appendMoneroNostrRelays } from '@/lib/monero-nostr-relays' |
|
import { hexPubkeysEqual, normalizeHexPubkey } from '@/lib/pubkey' |
|
|
|
/** `fetchReplaceableEvent(kind 3)` / relay-list hydration can hang; never block the Following spell on it. */ |
|
const FOLLOWING_FETCH_FOLLOWINGS_TIMEOUT_MS = 10_000 |
|
/** Per-shard relay-list batch has a UI budget; still cap so a wedged promise cannot blank the feed forever. */ |
|
const FOLLOWING_GENERATE_SUBREQ_TIMEOUT_MS = 16_000 |
|
const FOLLOWING_INBOX_SHARD_AUTHOR_CAP = 512 |
|
|
|
function racePromiseWithTimeout<T>(promise: Promise<T>, ms: number, onTimeout: () => T): Promise<T> { |
|
return new Promise((resolve) => { |
|
const t = window.setTimeout(() => resolve(onTimeout()), ms) |
|
promise |
|
.then((v) => { |
|
window.clearTimeout(t) |
|
resolve(v) |
|
}) |
|
.catch(() => { |
|
window.clearTimeout(t) |
|
resolve(onTimeout()) |
|
}) |
|
}) |
|
} |
|
|
|
function buildInboxShardFollowingSubRequests(args: { |
|
authors: string[] |
|
favoriteRelays: string[] |
|
blockedRelays: string[] |
|
relayList: { read: string[]; write: string[]; httpRead?: string[]; httpWrite?: string[] } | null | undefined |
|
cacheRelayListEvent?: Event | null |
|
augment: (raw: TFeedSubRequest[]) => TFeedSubRequest[] |
|
}): TFeedSubRequest[] { |
|
const { authors, favoriteRelays, blockedRelays, relayList, cacheRelayListEvent, augment } = args |
|
const feedUrls = getRelayUrlsWithFavoritesFastReadAndInbox( |
|
favoriteRelays, |
|
blockedRelays, |
|
userReadInboxUrls(relayList, cacheRelayListEvent), |
|
{ userWriteRelays: userWriteOutboxUrls(relayList, cacheRelayListEvent) } |
|
) |
|
if (!feedUrls.length) return [] |
|
const capped = authors.slice(0, FOLLOWING_INBOX_SHARD_AUTHOR_CAP) |
|
return augment([ |
|
{ |
|
urls: feedUrls, |
|
filter: { |
|
authors: capped, |
|
kinds: [...DEFAULT_FEED_SHOW_KINDS], |
|
limit: FAUX_SPELL_EVENT_LIMIT |
|
} |
|
} |
|
]) |
|
} |
|
|
|
function useNoteListHideReplies() { |
|
const [hideReplies, setHideReplies] = useState(() => storage.getNoteListMode() === 'posts') |
|
|
|
useEffect(() => { |
|
const sync = () => setHideReplies(storage.getNoteListMode() === 'posts') |
|
window.addEventListener('noteListModeChanged', sync) |
|
return () => window.removeEventListener('noteListModeChanged', sync) |
|
}, []) |
|
|
|
return hideReplies |
|
} |
|
|
|
export type UseSpellsPageFeedArgs = { |
|
selectedFauxSpell: string | null |
|
selectedSpell: Event | null |
|
pubkey: string | null | undefined |
|
relayList: { read: string[]; write: string[]; httpRead?: string[]; httpWrite?: string[] } | null | undefined |
|
cacheRelayListEvent?: Event | null |
|
favoriteRelays: string[] |
|
blockedRelays: string[] |
|
notificationsFeedPubkey: string | null |
|
interestListEvent: Event | null | undefined |
|
bookmarkListEvent: Event | null | undefined |
|
followListEvent: Event | null | undefined |
|
contacts: string[] |
|
contactsSyncKey: string |
|
followSetListEvents: Event[] |
|
followSetCatalogLoading: boolean |
|
kindFilterShowKinds: number[] |
|
notificationEventsIFollowListEvent: Event | null | undefined |
|
notificationEventsIMutedListEvent: Event | null | undefined |
|
} |
|
|
|
export function useSpellsPageFeed(a: UseSpellsPageFeedArgs) { |
|
const { |
|
selectedFauxSpell, |
|
selectedSpell, |
|
pubkey, |
|
relayList, |
|
cacheRelayListEvent, |
|
favoriteRelays, |
|
blockedRelays, |
|
notificationsFeedPubkey, |
|
interestListEvent, |
|
bookmarkListEvent, |
|
followListEvent, |
|
contacts, |
|
contactsSyncKey, |
|
followSetListEvents, |
|
followSetCatalogLoading, |
|
kindFilterShowKinds, |
|
notificationEventsIFollowListEvent, |
|
notificationEventsIMutedListEvent |
|
} = a |
|
|
|
const hideRepliesFollowing = useNoteListHideReplies() |
|
const [followingSubRequests, setFollowingSubRequests] = useState<TFeedSubRequest[]>([]) |
|
|
|
const normalizedReadSorted = relayList ? [...userReadInboxUrls(relayList, cacheRelayListEvent)].sort() : [] |
|
const normalizedWriteSorted = relayList |
|
? [...userWriteOutboxUrls(relayList, cacheRelayListEvent)].sort() |
|
: [] |
|
|
|
const relayMailboxStableKey = |
|
relayList == null |
|
? '' |
|
: JSON.stringify({ r: normalizedReadSorted, w: normalizedWriteSorted }) |
|
|
|
const relayListWriteKey = useMemo(() => { |
|
if (!relayList) return '[]' |
|
return JSON.stringify(normalizedWriteSorted) |
|
}, [relayMailboxStableKey]) |
|
|
|
const sortedFavoriteRelaysKey = useMemo( |
|
() => |
|
JSON.stringify( |
|
[...favoriteRelays].map((u) => normalizeUrl(u) || u).filter(Boolean).sort((x, y) => x.localeCompare(y)) |
|
), |
|
[favoriteRelays] |
|
) |
|
const sortedBlockedRelaysKey = useMemo( |
|
() => |
|
JSON.stringify( |
|
[...blockedRelays].map((u) => normalizeUrl(u) || u).filter(Boolean).sort((x, y) => x.localeCompare(y)) |
|
), |
|
[blockedRelays] |
|
) |
|
|
|
const followSetListStableKey = useMemo( |
|
() => |
|
followSetListEvents |
|
.map((e) => { |
|
const d = getFollowSetDTag(e) ?? '' |
|
return `${d}:${e.id}:${e.created_at}` |
|
}) |
|
.sort() |
|
.join('|'), |
|
[followSetListEvents] |
|
) |
|
|
|
useEffect(() => { |
|
if (!pubkey || !isFollowFeedFauxSpellId(selectedFauxSpell)) { |
|
setFollowingSubRequests([]) |
|
return |
|
} |
|
|
|
const followSetD = |
|
selectedFauxSpell && isFollowSetSpellId(selectedFauxSpell) |
|
? decodeFollowSetSpellId(selectedFauxSpell) |
|
: null |
|
|
|
if (followSetD && followSetCatalogLoading) { |
|
setFollowingSubRequests([]) |
|
return |
|
} |
|
|
|
let cancelled = false |
|
void (async () => { |
|
const augment = (raw: TFeedSubRequest[]) => |
|
augmentSubRequestsWithFavoritesFastReadAndInbox( |
|
raw, |
|
favoriteRelays, |
|
blockedRelays, |
|
userReadInboxUrls(relayList, cacheRelayListEvent), |
|
{ userWriteRelays: userWriteOutboxUrls(relayList, cacheRelayListEvent) } |
|
) |
|
try { |
|
if (selectedFauxSpell === 'following') { |
|
const fromTags = followListEvent ? getPubkeysFromPTags(followListEvent.tags) : [] |
|
const provisionalAuthors = [...new Set([pubkey, ...fromTags])] |
|
|
|
const inboxFallbackArgs = { |
|
favoriteRelays, |
|
blockedRelays, |
|
relayList, |
|
cacheRelayListEvent, |
|
augment |
|
} |
|
const syncProvisional = buildInboxShardFollowingSubRequests({ |
|
authors: provisionalAuthors, |
|
...inboxFallbackArgs |
|
}) |
|
if (!cancelled && syncProvisional.length > 0) setFollowingSubRequests(syncProvisional) |
|
|
|
const [rawProv, followings] = await Promise.all([ |
|
racePromiseWithTimeout<TFeedSubRequest[]>( |
|
client.generateSubRequestsForPubkeys(provisionalAuthors, pubkey) as Promise<TFeedSubRequest[]>, |
|
FOLLOWING_GENERATE_SUBREQ_TIMEOUT_MS, |
|
() => [] |
|
), |
|
racePromiseWithTimeout( |
|
client.fetchFollowings(pubkey).catch(() => fromTags), |
|
FOLLOWING_FETCH_FOLLOWINGS_TIMEOUT_MS, |
|
() => fromTags |
|
) |
|
]) |
|
|
|
const provisionalNext = |
|
rawProv.length > 0 |
|
? augment(rawProv) |
|
: buildInboxShardFollowingSubRequests({ |
|
authors: provisionalAuthors, |
|
...inboxFallbackArgs |
|
}) |
|
if (!cancelled) setFollowingSubRequests(provisionalNext) |
|
|
|
const fullAuthors = [...new Set([pubkey, ...followings])] |
|
const sameSet = |
|
fullAuthors.length === provisionalAuthors.length && |
|
fullAuthors.every((p) => provisionalAuthors.includes(p)) && |
|
provisionalAuthors.every((p) => fullAuthors.includes(p)) |
|
if (sameSet) { |
|
return |
|
} |
|
|
|
const rawFull = await racePromiseWithTimeout<TFeedSubRequest[]>( |
|
client.generateSubRequestsForPubkeys(fullAuthors, pubkey) as Promise<TFeedSubRequest[]>, |
|
FOLLOWING_GENERATE_SUBREQ_TIMEOUT_MS, |
|
() => [] |
|
) |
|
const fullNext = |
|
rawFull.length > 0 |
|
? augment(rawFull) |
|
: buildInboxShardFollowingSubRequests({ authors: fullAuthors, ...inboxFallbackArgs }) |
|
if (!cancelled) setFollowingSubRequests(fullNext) |
|
} else if (followSetD) { |
|
const ev = followSetListEvents.find((e) => getFollowSetDTag(e) === followSetD) |
|
if (!ev) { |
|
if (!cancelled) setFollowingSubRequests([]) |
|
return |
|
} |
|
const listed = pubkeysFromFollowSetEvent(ev) |
|
const authorPubkeys = [pubkey, ...listed] |
|
const syncReq = buildInboxShardFollowingSubRequests({ |
|
authors: authorPubkeys, |
|
favoriteRelays, |
|
blockedRelays, |
|
relayList, |
|
cacheRelayListEvent, |
|
augment |
|
}) |
|
if (!cancelled && syncReq.length > 0) setFollowingSubRequests(syncReq) |
|
const rawFs = await racePromiseWithTimeout<TFeedSubRequest[]>( |
|
client.generateSubRequestsForPubkeys(authorPubkeys, pubkey) as Promise<TFeedSubRequest[]>, |
|
FOLLOWING_GENERATE_SUBREQ_TIMEOUT_MS, |
|
() => [] |
|
) |
|
const req = |
|
rawFs.length > 0 |
|
? augment(rawFs) |
|
: buildInboxShardFollowingSubRequests({ |
|
authors: authorPubkeys, |
|
favoriteRelays, |
|
blockedRelays, |
|
relayList, |
|
cacheRelayListEvent, |
|
augment |
|
}) |
|
if (!cancelled) setFollowingSubRequests(req) |
|
} else { |
|
if (!cancelled) setFollowingSubRequests([]) |
|
} |
|
} catch { |
|
if (!cancelled) setFollowingSubRequests([]) |
|
} |
|
})() |
|
return () => { |
|
cancelled = true |
|
} |
|
}, [ |
|
selectedFauxSpell, |
|
pubkey, |
|
sortedFavoriteRelaysKey, |
|
sortedBlockedRelaysKey, |
|
relayMailboxStableKey, |
|
cacheRelayListEvent, |
|
followSetCatalogLoading, |
|
followSetListStableKey, |
|
followListEvent?.id, |
|
favoriteRelays, |
|
blockedRelays, |
|
relayList, |
|
followListEvent |
|
]) |
|
|
|
const interestTagsStableKey = interestListEvent |
|
? JSON.stringify( |
|
[...interestListEvent.tags].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) |
|
) |
|
: '' |
|
const bookmarkTagsStableKey = bookmarkListEvent |
|
? JSON.stringify( |
|
[...bookmarkListEvent.tags].sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b))) |
|
) |
|
: '' |
|
const notificationFollowTagsStableKey = notificationEventsIFollowListEvent |
|
? JSON.stringify( |
|
[...notificationEventsIFollowListEvent.tags].sort((a, b) => |
|
JSON.stringify(a).localeCompare(JSON.stringify(b)) |
|
) |
|
) |
|
: '' |
|
const notificationMutedTagsStableKey = notificationEventsIMutedListEvent |
|
? JSON.stringify( |
|
[...notificationEventsIMutedListEvent.tags].sort((a, b) => |
|
JSON.stringify(a).localeCompare(JSON.stringify(b)) |
|
) |
|
) |
|
: '' |
|
|
|
const fauxFeedRelaysDepsKey = [ |
|
sortedFavoriteRelaysKey, |
|
sortedBlockedRelaysKey, |
|
interestListEvent?.id ?? '', |
|
String(interestListEvent?.created_at ?? ''), |
|
interestTagsStableKey, |
|
bookmarkListEvent?.id ?? '', |
|
String(bookmarkListEvent?.created_at ?? ''), |
|
bookmarkTagsStableKey, |
|
notificationEventsIFollowListEvent?.id ?? '', |
|
String(notificationEventsIFollowListEvent?.created_at ?? ''), |
|
notificationFollowTagsStableKey, |
|
notificationEventsIMutedListEvent?.id ?? '', |
|
String(notificationEventsIMutedListEvent?.created_at ?? ''), |
|
notificationMutedTagsStableKey |
|
].join('\0') |
|
|
|
const syncFauxSubRequests = useMemo<TFeedSubRequest[]>(() => { |
|
if ( |
|
!selectedFauxSpell || |
|
isFollowFeedFauxSpellId(selectedFauxSpell) || |
|
selectedFauxSpell === 'heatMap' || |
|
selectedFauxSpell === 'topicMap' |
|
) |
|
return [] |
|
const fauxSpellSkipSocialKindBlocked = |
|
selectedFauxSpell === 'calendar' || |
|
selectedFauxSpell === 'followPacks' || |
|
selectedFauxSpell === 'media' || |
|
selectedFauxSpell === 'nostrSpecs' || |
|
selectedFauxSpell === 'bookmarks' || |
|
selectedFauxSpell === 'interests' |
|
const feedUrls = ensureFauxSpellRelayStackTouchesFastRead( |
|
getRelayUrlsWithFavoritesFastReadAndInbox( |
|
favoriteRelays, |
|
blockedRelays, |
|
userReadInboxUrls(relayList, cacheRelayListEvent), |
|
{ |
|
userWriteRelays: userWriteOutboxUrls(relayList, cacheRelayListEvent), |
|
applySocialKindBlockedFilter: fauxSpellSkipSocialKindBlocked ? false : undefined |
|
} |
|
) |
|
) |
|
|
|
if (selectedFauxSpell === 'notifications') { |
|
if (!notificationsFeedPubkey || !feedUrls.length) return [] |
|
const notificationUrls = appendMoneroNostrRelays(feedUrls) |
|
const base = buildNotificationsSpellSubRequests(notificationUrls, notificationsFeedPubkey) |
|
const extra = buildNotificationsFollowedThreadSubRequests( |
|
notificationUrls, |
|
notificationEventsIFollowListEvent ?? null |
|
) |
|
return [...base, ...extra] |
|
} |
|
if (selectedFauxSpell === 'discussions') { |
|
if (!feedUrls.length) return [] |
|
return [{ urls: feedUrls, filter: buildDiscussionFilter() }] |
|
} |
|
if (selectedFauxSpell === 'media') { |
|
if (!feedUrls.length) return [] |
|
return [{ urls: feedUrls, filter: buildMediaSpellFilter() }] |
|
} |
|
if (selectedFauxSpell === 'calendar') { |
|
if (!feedUrls.length) return [] |
|
return [{ urls: feedUrls, filter: buildCalendarSpellFilter() }] |
|
} |
|
if (selectedFauxSpell === 'nostrSpecs') { |
|
if (!feedUrls.length) return [] |
|
return [{ urls: feedUrls, filter: buildNostrSpecsSpellFilter() }] |
|
} |
|
if (selectedFauxSpell === 'interests') { |
|
if (!pubkey || !interestListEvent) return [] |
|
const topics = interestListEvent.tags.filter((tag) => tag[0] === 't' && tag[1]).map((tag) => tag[1]!) |
|
return buildInterestsSubRequests(feedUrls, topics, DEFAULT_FEED_SHOW_KINDS) |
|
} |
|
if (selectedFauxSpell === 'bookmarks') { |
|
if (!pubkey) return [] |
|
const idReqs = buildBookmarksSubRequests(bookmarkListEvent ?? null, feedUrls) |
|
const webReqs = buildWebBookmarksSpellSubRequests(pubkey, feedUrls) |
|
return [...idReqs, ...webReqs] |
|
} |
|
if (selectedFauxSpell === 'followPacks') { |
|
if (!feedUrls.length) return [] |
|
return [ |
|
{ |
|
urls: feedUrls, |
|
filter: { kinds: [ExtendedKind.FOLLOW_PACK], limit: FAUX_SPELL_EVENT_LIMIT } |
|
} |
|
] |
|
} |
|
return [] |
|
}, [ |
|
selectedFauxSpell, |
|
pubkey, |
|
notificationsFeedPubkey, |
|
fauxFeedRelaysDepsKey, |
|
relayMailboxStableKey, |
|
interestListEvent, |
|
bookmarkListEvent, |
|
notificationEventsIFollowListEvent, |
|
notificationEventsIMutedListEvent, |
|
favoriteRelays, |
|
blockedRelays, |
|
relayList |
|
]) |
|
|
|
const fauxSubRequests = useMemo<TFeedSubRequest[]>(() => { |
|
const base = isFollowFeedFauxSpellId(selectedFauxSpell ?? '') |
|
? followingSubRequests |
|
: syncFauxSubRequests |
|
return applyFauxSpellCapsToSubRequests(base) |
|
}, [selectedFauxSpell, followingSubRequests, syncFauxSubRequests]) |
|
|
|
const spellSubRequests = useMemo<TFeedSubRequest[]>(() => { |
|
if (!selectedSpell) return [] |
|
const relayListWrite = userWriteOutboxUrls(relayList, cacheRelayListEvent) |
|
const ctx = { pubkey: pubkey ?? null, contacts } |
|
const filter = spellEventToFilter(selectedSpell, ctx) |
|
if (!filter) return [] |
|
const relays = getRelaysForSpell(selectedSpell, { relayListWrite }) |
|
if (!relays.length) return [] |
|
return [{ urls: relays, filter }] |
|
}, [selectedSpell, pubkey, contactsSyncKey, relayListWriteKey, contacts, relayList]) |
|
|
|
const subRequests = useMemo<TFeedSubRequest[]>(() => { |
|
if (selectedFauxSpell) return fauxSubRequests |
|
return spellSubRequests |
|
}, [selectedFauxSpell, fauxSubRequests, spellSubRequests]) |
|
|
|
const spellFeedSubscriptionKey = useMemo(() => { |
|
if (selectedFauxSpell) { |
|
const filters = subRequests.map((req) => stableSpellFeedFilterKey(req.filter)).join('|') |
|
return `faux-spell:${selectedFauxSpell}:${pubkey ?? ''}:${filters}` |
|
} |
|
if (selectedSpell) { |
|
const filters = subRequests.map((req) => stableSpellFeedFilterKey(req.filter)).join('|') |
|
return `spell:${selectedSpell.id}:${filters}` |
|
} |
|
return '' |
|
}, [selectedFauxSpell, selectedSpell, subRequests, pubkey]) |
|
|
|
const spellBrowseRelayUrls = useMemo(() => { |
|
const set = new Set<string>() |
|
for (const req of subRequests) { |
|
for (const u of req.urls) { |
|
const n = normalizeUrl(u) || u |
|
if (n) set.add(n) |
|
} |
|
} |
|
return [...set].sort() |
|
}, [subRequests]) |
|
|
|
const spellBrowseRelayUrlsKey = spellBrowseRelayUrls.join('|') |
|
|
|
const showKindsTagKey = useMemo(() => { |
|
if (!selectedSpell) return '' |
|
return selectedSpell.tags |
|
.filter((tag) => tag[0] === 'k') |
|
.map((tag) => tag[1]) |
|
.sort() |
|
.join(',') |
|
}, [selectedSpell?.id]) |
|
|
|
const followingShowKindsKey = |
|
selectedFauxSpell && isFollowFeedFauxSpellId(selectedFauxSpell) |
|
? JSON.stringify(kindFilterShowKinds) |
|
: '' |
|
|
|
const showKinds = useMemo(() => { |
|
if (selectedFauxSpell === 'notifications') { |
|
return [...NOTIFICATION_SPELL_KINDS] |
|
} |
|
if (selectedFauxSpell === 'discussions') { |
|
return [ExtendedKind.DISCUSSION] |
|
} |
|
if (selectedFauxSpell && isFollowFeedFauxSpellId(selectedFauxSpell)) { |
|
const k = kindFilterShowKinds |
|
const out = [...k] |
|
if (!out.includes(nostrKinds.Repost)) out.push(nostrKinds.Repost) |
|
if (!out.includes(ExtendedKind.GENERIC_REPOST)) out.push(ExtendedKind.GENERIC_REPOST) |
|
return out.sort((x, y) => x - y) |
|
} |
|
if (selectedFauxSpell === 'followPacks') { |
|
return [ExtendedKind.FOLLOW_PACK] |
|
} |
|
if (selectedFauxSpell === 'media') { |
|
return [...MEDIA_SPELL_KINDS] |
|
} |
|
if (selectedFauxSpell === 'calendar') { |
|
return [ExtendedKind.CALENDAR_EVENT_DATE, ExtendedKind.CALENDAR_EVENT_TIME] |
|
} |
|
if (selectedFauxSpell === 'nostrSpecs') { |
|
return [ExtendedKind.NOSTR_SPECIFICATION] |
|
} |
|
if (selectedFauxSpell === 'interests') { |
|
return [...DEFAULT_FEED_SHOW_KINDS] |
|
} |
|
if (selectedFauxSpell === 'bookmarks') { |
|
const out = [...DEFAULT_FEED_SHOW_KINDS] |
|
if (!out.includes(ExtendedKind.WEB_BOOKMARK)) out.push(ExtendedKind.WEB_BOOKMARK) |
|
return out.sort((a, b) => a - b) |
|
} |
|
if (!selectedSpell) return [1] |
|
const kinds = selectedSpell.tags |
|
.filter((tag) => tag[0] === 'k') |
|
.map((tag) => parseInt(tag[1], 10)) |
|
.filter((n) => !Number.isNaN(n)) |
|
return kinds.length ? kinds : [1] |
|
}, [selectedFauxSpell, selectedSpell?.id, showKindsTagKey, followingShowKindsKey]) |
|
|
|
const fauxNoteListUseFilterAsIs = useMemo(() => { |
|
if (!selectedFauxSpell) return true |
|
if (selectedFauxSpell && isFollowFeedFauxSpellId(selectedFauxSpell)) return false |
|
return true |
|
}, [selectedFauxSpell]) |
|
|
|
const spellFauxMergeTimeline = useMemo( |
|
() => !!selectedFauxSpell && isFollowFeedFauxSpellId(selectedFauxSpell), |
|
[selectedFauxSpell] |
|
) |
|
|
|
const notificationsMentionExtraHide = useCallback( |
|
(evt: Event) => { |
|
if (!notificationsFeedPubkey) return false |
|
const pk = normalizeHexPubkey(notificationsFeedPubkey) |
|
const followRefs = parseThreadWatchListRefs(notificationEventsIFollowListEvent ?? null) |
|
const mutedRefs = parseThreadWatchListRefs(notificationEventsIMutedListEvent ?? null) |
|
|
|
// Never list your own authored events in this account's notifications (`#p` REQ still returns self-replies, self-`#p`, etc.). |
|
if (hexPubkeysEqual(evt.pubkey, pk)) return true |
|
|
|
if ( |
|
threadWatchMatchesRefs(evt, mutedRefs) && |
|
isNotificationThreadInteractionEvent(evt) |
|
) { |
|
return true |
|
} |
|
|
|
if (isIncomingNotificationsPaymentEvent(evt, pk)) return false |
|
|
|
if (isUserInEventMentions(evt, pk)) return false |
|
|
|
if ( |
|
threadWatchMatchesRefs(evt, followRefs) && |
|
isNotificationThreadInteractionEvent(evt) |
|
) { |
|
return false |
|
} |
|
|
|
return true |
|
}, |
|
[ |
|
notificationsFeedPubkey, |
|
notificationEventsIFollowListEvent, |
|
notificationEventsIMutedListEvent |
|
] |
|
) |
|
|
|
return { |
|
relayMailboxStableKey, |
|
sortedFavoriteRelaysKey, |
|
sortedBlockedRelaysKey, |
|
followingSubRequests, |
|
fauxSubRequests, |
|
subRequests, |
|
spellFeedSubscriptionKey, |
|
spellBrowseRelayUrls, |
|
spellBrowseRelayUrlsKey, |
|
showKinds, |
|
fauxNoteListUseFilterAsIs, |
|
spellFauxMergeTimeline, |
|
notificationsMentionExtraHide, |
|
hideRepliesFollowing, |
|
NOTIFICATION_SPELL_LOADING_SAFETY_MS, |
|
NOTIFICATION_SPELL_KINDS |
|
} |
|
}
|
|
|