refactor: use domain objects for FollowList and MuteList providers
- Refactor FollowListProvider to use domain FollowList class - Refactor MuteListProvider to use domain MuteList class - Add hide untrusted interactions/notifications settings to GeneralSettingsPage - Maintain backward compatibility with existing Set<string> interface 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,14 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
mediaAutoLoadPolicy,
|
||||
setMediaAutoLoadPolicy
|
||||
} = useContentPolicy()
|
||||
const { hideUntrustedNotes, updateHideUntrustedNotes } = useUserTrust()
|
||||
const {
|
||||
hideUntrustedNotes,
|
||||
updateHideUntrustedNotes,
|
||||
hideUntrustedInteractions,
|
||||
updateHideUntrustedInteractions,
|
||||
hideUntrustedNotifications,
|
||||
updateHideUntrustedNotifications
|
||||
} = useUserTrust()
|
||||
const { quickReaction, updateQuickReaction, quickReactionEmoji, updateQuickReactionEmoji } =
|
||||
useUserPreferences()
|
||||
|
||||
@@ -99,6 +106,26 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
onCheckedChange={updateHideUntrustedNotes}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<Label htmlFor="hide-untrusted-interactions" className="text-base font-normal">
|
||||
{t('Hide untrusted interactions')}
|
||||
</Label>
|
||||
<Switch
|
||||
id="hide-untrusted-interactions"
|
||||
checked={hideUntrustedInteractions}
|
||||
onCheckedChange={updateHideUntrustedInteractions}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<Label htmlFor="hide-untrusted-notifications" className="text-base font-normal">
|
||||
{t('Hide untrusted notifications')}
|
||||
</Label>
|
||||
<Switch
|
||||
id="hide-untrusted-notifications"
|
||||
checked={hideUntrustedNotifications}
|
||||
onCheckedChange={updateHideUntrustedNotifications}
|
||||
/>
|
||||
</SettingItem>
|
||||
<SettingItem>
|
||||
<Label htmlFor="hide-content-mentioning-muted-users" className="text-base font-normal">
|
||||
{t('Hide content mentioning muted users')}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { createFollowListDraftEvent } from '@/lib/draft-event'
|
||||
import { getPubkeysFromPTags } from '@/lib/tag'
|
||||
import {
|
||||
FollowList,
|
||||
tryToFollowList,
|
||||
fromFollowListToHexSet,
|
||||
Pubkey,
|
||||
CannotFollowSelfError
|
||||
} from '@/domain'
|
||||
import client from '@/services/client.service'
|
||||
import { createContext, useContext, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@@ -7,6 +12,7 @@ import { useNostr } from './NostrProvider'
|
||||
|
||||
type TFollowListContext = {
|
||||
followingSet: Set<string>
|
||||
followList: FollowList | null
|
||||
follow: (pubkey: string) => Promise<void>
|
||||
unfollow: (pubkey: string) => Promise<void>
|
||||
}
|
||||
@@ -24,41 +30,73 @@ export const useFollowList = () => {
|
||||
export function FollowListProvider({ children }: { children: React.ReactNode }) {
|
||||
const { t } = useTranslation()
|
||||
const { pubkey: accountPubkey, followListEvent, publish, updateFollowListEvent } = useNostr()
|
||||
const followingSet = useMemo(
|
||||
() => new Set(followListEvent ? getPubkeysFromPTags(followListEvent.tags) : []),
|
||||
|
||||
// Create domain FollowList from event
|
||||
const followList = useMemo(
|
||||
() => tryToFollowList(followListEvent),
|
||||
[followListEvent]
|
||||
)
|
||||
|
||||
// Legacy compatibility: expose as Set<string> for existing consumers
|
||||
const followingSet = useMemo(
|
||||
() => (followList ? fromFollowListToHexSet(followList) : new Set<string>()),
|
||||
[followList]
|
||||
)
|
||||
|
||||
const follow = async (pubkey: string) => {
|
||||
if (!accountPubkey) return
|
||||
|
||||
const followListEvent = await client.fetchFollowListEvent(accountPubkey)
|
||||
if (!followListEvent) {
|
||||
// Fetch latest follow list event
|
||||
const latestEvent = await client.fetchFollowListEvent(accountPubkey)
|
||||
if (!latestEvent) {
|
||||
const result = confirm(t('FollowListNotFoundConfirmation'))
|
||||
if (!result) return
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
// Create or update FollowList using domain object
|
||||
const ownerPubkey = Pubkey.fromHex(accountPubkey)
|
||||
const currentFollowList = latestEvent
|
||||
? FollowList.fromEvent(latestEvent)
|
||||
: FollowList.empty(ownerPubkey)
|
||||
|
||||
// Use domain logic for following
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
try {
|
||||
const change = currentFollowList.follow(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Publish the updated follow list
|
||||
const draftEvent = currentFollowList.toDraftEvent()
|
||||
const newFollowListEvent = await publish(draftEvent)
|
||||
await updateFollowListEvent(newFollowListEvent)
|
||||
} catch (error) {
|
||||
if (error instanceof CannotFollowSelfError) {
|
||||
// Silently ignore self-follow attempts
|
||||
return
|
||||
}
|
||||
throw error
|
||||
}
|
||||
const newFollowListDraftEvent = createFollowListDraftEvent(
|
||||
(followListEvent?.tags ?? []).concat([['p', pubkey]]),
|
||||
followListEvent?.content
|
||||
)
|
||||
const newFollowListEvent = await publish(newFollowListDraftEvent)
|
||||
await updateFollowListEvent(newFollowListEvent)
|
||||
}
|
||||
|
||||
const unfollow = async (pubkey: string) => {
|
||||
if (!accountPubkey) return
|
||||
|
||||
const followListEvent = await client.fetchFollowListEvent(accountPubkey)
|
||||
if (!followListEvent) return
|
||||
const latestEvent = await client.fetchFollowListEvent(accountPubkey)
|
||||
if (!latestEvent) return
|
||||
|
||||
const newFollowListDraftEvent = createFollowListDraftEvent(
|
||||
followListEvent.tags.filter(([tagName, tagValue]) => tagName !== 'p' || tagValue !== pubkey),
|
||||
followListEvent.content
|
||||
)
|
||||
const newFollowListEvent = await publish(newFollowListDraftEvent)
|
||||
// Use domain object for unfollowing
|
||||
const currentFollowList = FollowList.fromEvent(latestEvent)
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
const change = currentFollowList.unfollow(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Publish the updated follow list
|
||||
const draftEvent = currentFollowList.toDraftEvent()
|
||||
const newFollowListEvent = await publish(draftEvent)
|
||||
await updateFollowListEvent(newFollowListEvent)
|
||||
}
|
||||
|
||||
@@ -66,6 +104,7 @@ export function FollowListProvider({ children }: { children: React.ReactNode })
|
||||
<FollowListContext.Provider
|
||||
value={{
|
||||
followingSet,
|
||||
followList,
|
||||
follow,
|
||||
unfollow
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { createMuteListDraftEvent } from '@/lib/draft-event'
|
||||
import { getPubkeysFromPTags } from '@/lib/tag'
|
||||
import {
|
||||
MuteList,
|
||||
tryToMuteList,
|
||||
fromMuteListToHexSet,
|
||||
Pubkey,
|
||||
CannotMuteSelfError,
|
||||
MuteVisibility
|
||||
} from '@/domain'
|
||||
import client from '@/services/client.service'
|
||||
import indexedDb from '@/services/indexed-db.service'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -12,9 +18,10 @@ import { useNostr } from './NostrProvider'
|
||||
|
||||
type TMuteListContext = {
|
||||
mutePubkeySet: Set<string>
|
||||
muteList: MuteList | null
|
||||
changing: boolean
|
||||
getMutePubkeys: () => string[]
|
||||
getMuteType: (pubkey: string) => 'public' | 'private' | null
|
||||
getMuteType: (pubkey: string) => MuteVisibility | null
|
||||
mutePubkeyPublicly: (pubkey: string) => Promise<void>
|
||||
mutePubkeyPrivately: (pubkey: string) => Promise<void>
|
||||
unmutePubkey: (pubkey: string) => Promise<void>
|
||||
@@ -42,35 +49,27 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
nip04Decrypt,
|
||||
nip04Encrypt
|
||||
} = useNostr()
|
||||
const [tags, setTags] = useState<string[][]>([])
|
||||
const [privateTags, setPrivateTags] = useState<string[][]>([])
|
||||
const publicMutePubkeySet = useMemo(() => new Set(getPubkeysFromPTags(tags)), [tags])
|
||||
const privateMutePubkeySet = useMemo(
|
||||
() => new Set(getPubkeysFromPTags(privateTags)),
|
||||
[privateTags]
|
||||
)
|
||||
const mutePubkeySet = useMemo(() => {
|
||||
return new Set([...Array.from(privateMutePubkeySet), ...Array.from(publicMutePubkeySet)])
|
||||
}, [publicMutePubkeySet, privateMutePubkeySet])
|
||||
const [changing, setChanging] = useState(false)
|
||||
|
||||
// Decrypt private tags from mute list event
|
||||
const getPrivateTags = useCallback(
|
||||
async (muteListEvent: Event) => {
|
||||
if (!muteListEvent.content) return []
|
||||
async (event: Event) => {
|
||||
if (!event.content) return []
|
||||
|
||||
try {
|
||||
const storedPlainText = await indexedDb.getDecryptedContent(muteListEvent.id)
|
||||
const storedPlainText = await indexedDb.getDecryptedContent(event.id)
|
||||
|
||||
let plainText: string
|
||||
if (storedPlainText) {
|
||||
plainText = storedPlainText
|
||||
} else {
|
||||
plainText = await nip04Decrypt(muteListEvent.pubkey, muteListEvent.content)
|
||||
await indexedDb.putDecryptedContent(muteListEvent.id, plainText)
|
||||
plainText = await nip04Decrypt(event.pubkey, event.content)
|
||||
await indexedDb.putDecryptedContent(event.id, plainText)
|
||||
}
|
||||
|
||||
const privateTags = z.array(z.array(z.string())).parse(JSON.parse(plainText))
|
||||
return privateTags
|
||||
const tags = z.array(z.array(z.string())).parse(JSON.parse(plainText))
|
||||
return tags
|
||||
} catch (error) {
|
||||
console.error('Failed to decrypt mute list content', error)
|
||||
return []
|
||||
@@ -79,73 +78,92 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
[nip04Decrypt]
|
||||
)
|
||||
|
||||
// Update private tags when mute list event changes
|
||||
useEffect(() => {
|
||||
const updateMuteTags = async () => {
|
||||
const updatePrivateTags = async () => {
|
||||
if (!muteListEvent) {
|
||||
setTags([])
|
||||
setPrivateTags([])
|
||||
return
|
||||
}
|
||||
|
||||
const privateTags = await getPrivateTags(muteListEvent).catch(() => {
|
||||
return []
|
||||
})
|
||||
setPrivateTags(privateTags)
|
||||
setTags(muteListEvent.tags)
|
||||
const tags = await getPrivateTags(muteListEvent).catch(() => [])
|
||||
setPrivateTags(tags)
|
||||
}
|
||||
updateMuteTags()
|
||||
}, [muteListEvent])
|
||||
updatePrivateTags()
|
||||
}, [muteListEvent, getPrivateTags])
|
||||
|
||||
const getMutePubkeys = () => {
|
||||
return Array.from(mutePubkeySet)
|
||||
}
|
||||
|
||||
const getMuteType = useCallback(
|
||||
(pubkey: string): 'public' | 'private' | null => {
|
||||
if (publicMutePubkeySet.has(pubkey)) return 'public'
|
||||
if (privateMutePubkeySet.has(pubkey)) return 'private'
|
||||
return null
|
||||
},
|
||||
[publicMutePubkeySet, privateMutePubkeySet]
|
||||
// Create domain MuteList from event and decrypted private tags
|
||||
const muteList = useMemo(
|
||||
() => tryToMuteList(muteListEvent, privateTags),
|
||||
[muteListEvent, privateTags]
|
||||
)
|
||||
|
||||
const publishNewMuteListEvent = async (tags: string[][], content?: string) => {
|
||||
// Legacy compatibility: expose as Set<string> for existing consumers
|
||||
const mutePubkeySet = useMemo(
|
||||
() => (muteList ? fromMuteListToHexSet(muteList) : new Set<string>()),
|
||||
[muteList]
|
||||
)
|
||||
|
||||
const getMutePubkeys = useCallback(() => {
|
||||
return Array.from(mutePubkeySet)
|
||||
}, [mutePubkeySet])
|
||||
|
||||
const getMuteType = useCallback(
|
||||
(pubkey: string): MuteVisibility | null => {
|
||||
if (!muteList) return null
|
||||
const pk = Pubkey.tryFromString(pubkey)
|
||||
return pk ? muteList.getMuteVisibility(pk) : null
|
||||
},
|
||||
[muteList]
|
||||
)
|
||||
|
||||
// Publish updated mute list with rate limiting
|
||||
const publishMuteList = async (updatedMuteList: MuteList, encryptedContent: string) => {
|
||||
if (dayjs().unix() === muteListEvent?.created_at) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
}
|
||||
const newMuteListDraftEvent = createMuteListDraftEvent(tags, content)
|
||||
const event = await publish(newMuteListDraftEvent)
|
||||
|
||||
const draftEvent = updatedMuteList.toDraftEvent(encryptedContent)
|
||||
const event = await publish(draftEvent)
|
||||
toast.success(t('Successfully updated mute list'))
|
||||
return event
|
||||
}
|
||||
|
||||
const checkMuteListEvent = (muteListEvent: Event | null) => {
|
||||
if (!muteListEvent) {
|
||||
const result = confirm(t('MuteListNotFoundConfirmation'))
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Mute list not found')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mutePubkeyPublicly = async (pubkey: string) => {
|
||||
if (!accountPubkey || changing) return
|
||||
|
||||
setChanging(true)
|
||||
try {
|
||||
const muteListEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
checkMuteListEvent(muteListEvent)
|
||||
if (
|
||||
muteListEvent &&
|
||||
muteListEvent.tags.some(([tagName, tagValue]) => tagName === 'p' && tagValue === pubkey)
|
||||
) {
|
||||
return
|
||||
const latestEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!latestEvent) {
|
||||
const result = confirm(t('MuteListNotFoundConfirmation'))
|
||||
if (!result) return
|
||||
}
|
||||
|
||||
const ownerPubkey = Pubkey.fromHex(accountPubkey)
|
||||
const decryptedPrivateTags = latestEvent ? await getPrivateTags(latestEvent) : []
|
||||
const currentMuteList = latestEvent
|
||||
? MuteList.fromEvent(latestEvent, decryptedPrivateTags)
|
||||
: MuteList.empty(ownerPubkey)
|
||||
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
try {
|
||||
const change = currentMuteList.mutePublicly(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Encrypt private tags if there are any
|
||||
const encryptedContent = currentMuteList.hasPrivateMutes()
|
||||
? await nip04Encrypt(accountPubkey, JSON.stringify(currentMuteList.toPrivateTags()))
|
||||
: ''
|
||||
|
||||
const newEvent = await publishMuteList(currentMuteList, encryptedContent)
|
||||
await updateMuteListEvent(newEvent, currentMuteList.toPrivateTags())
|
||||
} catch (error) {
|
||||
if (error instanceof CannotMuteSelfError) return
|
||||
throw error
|
||||
}
|
||||
const newTags = (muteListEvent?.tags ?? []).concat([['p', pubkey]])
|
||||
const newMuteListEvent = await publishNewMuteListEvent(newTags, muteListEvent?.content)
|
||||
const privateTags = await getPrivateTags(newMuteListEvent)
|
||||
await updateMuteListEvent(newMuteListEvent, privateTags)
|
||||
} catch (error) {
|
||||
toast.error(t('Failed to mute user publicly') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
@@ -158,17 +176,37 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
setChanging(true)
|
||||
try {
|
||||
const muteListEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
checkMuteListEvent(muteListEvent)
|
||||
const privateTags = muteListEvent ? await getPrivateTags(muteListEvent) : []
|
||||
if (privateTags.some(([tagName, tagValue]) => tagName === 'p' && tagValue === pubkey)) {
|
||||
return
|
||||
const latestEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!latestEvent) {
|
||||
const result = confirm(t('MuteListNotFoundConfirmation'))
|
||||
if (!result) return
|
||||
}
|
||||
|
||||
const newPrivateTags = privateTags.concat([['p', pubkey]])
|
||||
const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags))
|
||||
const newMuteListEvent = await publishNewMuteListEvent(muteListEvent?.tags ?? [], cipherText)
|
||||
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
|
||||
const ownerPubkey = Pubkey.fromHex(accountPubkey)
|
||||
const decryptedPrivateTags = latestEvent ? await getPrivateTags(latestEvent) : []
|
||||
const currentMuteList = latestEvent
|
||||
? MuteList.fromEvent(latestEvent, decryptedPrivateTags)
|
||||
: MuteList.empty(ownerPubkey)
|
||||
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
try {
|
||||
const change = currentMuteList.mutePrivately(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Always encrypt when adding private mutes
|
||||
const encryptedContent = await nip04Encrypt(
|
||||
accountPubkey,
|
||||
JSON.stringify(currentMuteList.toPrivateTags())
|
||||
)
|
||||
|
||||
const newEvent = await publishMuteList(currentMuteList, encryptedContent)
|
||||
await updateMuteListEvent(newEvent, currentMuteList.toPrivateTags())
|
||||
} catch (error) {
|
||||
if (error instanceof CannotMuteSelfError) return
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(t('Failed to mute user privately') + ': ' + (error as Error).message)
|
||||
} finally {
|
||||
@@ -181,21 +219,25 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
setChanging(true)
|
||||
try {
|
||||
const muteListEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!muteListEvent) return
|
||||
const latestEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!latestEvent) return
|
||||
|
||||
const privateTags = await getPrivateTags(muteListEvent)
|
||||
const newPrivateTags = privateTags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey)
|
||||
let cipherText = muteListEvent.content
|
||||
if (newPrivateTags.length !== privateTags.length) {
|
||||
cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags))
|
||||
}
|
||||
const decryptedPrivateTags = await getPrivateTags(latestEvent)
|
||||
const currentMuteList = MuteList.fromEvent(latestEvent, decryptedPrivateTags)
|
||||
|
||||
const newMuteListEvent = await publishNewMuteListEvent(
|
||||
muteListEvent.tags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey),
|
||||
cipherText
|
||||
)
|
||||
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
const change = currentMuteList.unmute(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Re-encrypt if there are still private mutes
|
||||
const encryptedContent = currentMuteList.hasPrivateMutes()
|
||||
? await nip04Encrypt(accountPubkey, JSON.stringify(currentMuteList.toPrivateTags()))
|
||||
: ''
|
||||
|
||||
const newEvent = await publishMuteList(currentMuteList, encryptedContent)
|
||||
await updateMuteListEvent(newEvent, currentMuteList.toPrivateTags())
|
||||
} finally {
|
||||
setChanging(false)
|
||||
}
|
||||
@@ -206,23 +248,25 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
setChanging(true)
|
||||
try {
|
||||
const muteListEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!muteListEvent) return
|
||||
const latestEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!latestEvent) return
|
||||
|
||||
const privateTags = await getPrivateTags(muteListEvent)
|
||||
const newPrivateTags = privateTags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey)
|
||||
if (newPrivateTags.length === privateTags.length) {
|
||||
return
|
||||
}
|
||||
const decryptedPrivateTags = await getPrivateTags(latestEvent)
|
||||
const currentMuteList = MuteList.fromEvent(latestEvent, decryptedPrivateTags)
|
||||
|
||||
const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags))
|
||||
const newMuteListEvent = await publishNewMuteListEvent(
|
||||
muteListEvent.tags
|
||||
.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey)
|
||||
.concat([['p', pubkey]]),
|
||||
cipherText
|
||||
)
|
||||
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
const change = currentMuteList.switchToPublic(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Re-encrypt private tags
|
||||
const encryptedContent = currentMuteList.hasPrivateMutes()
|
||||
? await nip04Encrypt(accountPubkey, JSON.stringify(currentMuteList.toPrivateTags()))
|
||||
: ''
|
||||
|
||||
const newEvent = await publishMuteList(currentMuteList, encryptedContent)
|
||||
await updateMuteListEvent(newEvent, currentMuteList.toPrivateTags())
|
||||
} finally {
|
||||
setChanging(false)
|
||||
}
|
||||
@@ -233,21 +277,26 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
setChanging(true)
|
||||
try {
|
||||
const muteListEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!muteListEvent) return
|
||||
const latestEvent = await client.fetchMuteListEvent(accountPubkey)
|
||||
if (!latestEvent) return
|
||||
|
||||
const newTags = muteListEvent.tags.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey)
|
||||
if (newTags.length === muteListEvent.tags.length) {
|
||||
return
|
||||
}
|
||||
const decryptedPrivateTags = await getPrivateTags(latestEvent)
|
||||
const currentMuteList = MuteList.fromEvent(latestEvent, decryptedPrivateTags)
|
||||
|
||||
const privateTags = await getPrivateTags(muteListEvent)
|
||||
const newPrivateTags = privateTags
|
||||
.filter((tag) => tag[0] !== 'p' || tag[1] !== pubkey)
|
||||
.concat([['p', pubkey]])
|
||||
const cipherText = await nip04Encrypt(accountPubkey, JSON.stringify(newPrivateTags))
|
||||
const newMuteListEvent = await publishNewMuteListEvent(newTags, cipherText)
|
||||
await updateMuteListEvent(newMuteListEvent, newPrivateTags)
|
||||
const targetPubkey = Pubkey.tryFromString(pubkey)
|
||||
if (!targetPubkey) return
|
||||
|
||||
const change = currentMuteList.switchToPrivate(targetPubkey)
|
||||
if (change.type === 'no_change') return
|
||||
|
||||
// Encrypt the updated private tags
|
||||
const encryptedContent = await nip04Encrypt(
|
||||
accountPubkey,
|
||||
JSON.stringify(currentMuteList.toPrivateTags())
|
||||
)
|
||||
|
||||
const newEvent = await publishMuteList(currentMuteList, encryptedContent)
|
||||
await updateMuteListEvent(newEvent, currentMuteList.toPrivateTags())
|
||||
} finally {
|
||||
setChanging(false)
|
||||
}
|
||||
@@ -257,6 +306,7 @@ export function MuteListProvider({ children }: { children: React.ReactNode }) {
|
||||
<MuteListContext.Provider
|
||||
value={{
|
||||
mutePubkeySet,
|
||||
muteList,
|
||||
changing,
|
||||
getMutePubkeys,
|
||||
getMuteType,
|
||||
|
||||
Reference in New Issue
Block a user