Fix stale settings getting used

This commit is contained in:
Jon Staab 2024-06-21 15:47:01 -07:00
parent b7f6d4b8be
commit abd306ccac
12 changed files with 163 additions and 173 deletions

View File

@ -10,7 +10,7 @@
deriveGroup, deriveGroup,
deriveGroupStatus, deriveGroupStatus,
deriveAdminKeyForGroup, deriveAdminKeyForGroup,
deriveUserCommunities, getUserCommunities,
} from "src/engine" } from "src/engine"
export let address export let address
@ -51,9 +51,9 @@
}) })
} }
const join = () => publishCommunitiesList(deriveUserCommunities().get().concat(address)) const join = () => publishCommunitiesList(getUserCommunities(session.get()).concat(address))
const leave = () => publishCommunitiesList(without([address], deriveUserCommunities().get())) const leave = () => publishCommunitiesList(without([address], getUserCommunities(session.get())))
</script> </script>
<div class="flex items-center gap-3" on:click|stopPropagation> <div class="flex items-center gap-3" on:click|stopPropagation>

View File

@ -53,7 +53,7 @@
unmuteNote, unmuteNote,
muteNote, muteNote,
deriveHandlersForKind, deriveHandlersForKind,
deriveIsGroupMember, userIsGroupMember,
publishToZeroOrMoreGroups, publishToZeroOrMoreGroups,
deleteEvent, deleteEvent,
getSetting, getSetting,
@ -192,12 +192,12 @@
window.open(templateTag[1].replace("<bech32>", entity)) window.open(templateTag[1].replace("<bech32>", entity))
} }
const groupOptions = session.derived($session => { const groupOptions = derived(session, $session => {
const options = [] const options = []
for (const addr of Object.keys($session?.groups || {})) { for (const addr of Object.keys($session?.groups || {})) {
const group = groups.key(addr).get() const group = groups.key(addr).get()
const isMember = deriveIsGroupMember(addr).get() const isMember = $userIsGroupMember(addr)
if (group && isMember && addr !== address) { if (group && isMember && addr !== address) {
options.push(group) options.push(group)
@ -212,9 +212,7 @@
let handlersShown = false let handlersShown = false
$: disableActions = $: disableActions =
!$canSign || !$canSign || ($muted && !showMuted) || (note.wrap && address && !$userIsGroupMember(address))
($muted && !showMuted) ||
(note.wrap && address && !deriveIsGroupMember(address).get())
$: like = likes.find(e => e.pubkey === $session?.pubkey) $: like = likes.find(e => e.pubkey === $session?.pubkey)
$: $likesCount = likes.length $: $likesCount = likes.length
$: zap = zaps.find(e => e.request.pubkey === $session?.pubkey) $: zap = zaps.find(e => e.request.pubkey === $session?.pubkey)

View File

@ -9,6 +9,7 @@
import { import {
env, env,
pubkey, pubkey,
session,
initGroup, initGroup,
publishGroupMeta, publishGroupMeta,
publishGroupInvites, publishGroupInvites,
@ -16,7 +17,7 @@
publishCommunityMeta, publishCommunityMeta,
publishCommunitiesList, publishCommunitiesList,
publishGroupMembers, publishGroupMembers,
deriveUserCommunities, getUserCommunities,
} from "src/engine" } from "src/engine"
import {router} from "src/app/util/router" import {router} from "src/app/util/router"
@ -50,7 +51,7 @@
if (kind === COMMUNITY) { if (kind === COMMUNITY) {
await publishCommunityMeta(address, identifier, meta) await publishCommunityMeta(address, identifier, meta)
await publishCommunitiesList(deriveUserCommunities().get().concat(address)) await publishCommunitiesList(getUserCommunities(session.get()).concat(address))
} else { } else {
await publishGroupMeta(address, identifier, meta, listing_is_public) await publishGroupMeta(address, identifier, meta, listing_is_public)
await publishGroupMembers(address, "set", members) await publishGroupMembers(address, "set", members)

View File

@ -23,7 +23,7 @@
deriveGroupMeta, deriveGroupMeta,
deriveAdminKeyForGroup, deriveAdminKeyForGroup,
deriveSharedKeyForGroup, deriveSharedKeyForGroup,
deriveIsGroupMember, userIsGroupMember,
deriveGroupStatus, deriveGroupStatus,
loadGroups, loadGroups,
loadGroupMessages, loadGroupMessages,
@ -38,7 +38,6 @@
const group = deriveGroup(address) const group = deriveGroup(address)
const meta = deriveGroupMeta(address) const meta = deriveGroupMeta(address)
const status = deriveGroupStatus(address) const status = deriveGroupStatus(address)
const isGroupMember = deriveIsGroupMember(address)
const sharedKey = deriveSharedKeyForGroup(address) const sharedKey = deriveSharedKeyForGroup(address)
const adminKey = deriveAdminKeyForGroup(address) const adminKey = deriveAdminKeyForGroup(address)
const requests = groupRequests.derived(requests => const requests = groupRequests.derived(requests =>
@ -56,7 +55,7 @@
let tabs let tabs
$: key = $group && $isGroupMember $: key = $group && $userIsGroupMember(address)
$: { $: {
if (key) { if (key) {

View File

@ -15,7 +15,7 @@
groups, groups,
loadGiftWraps, loadGiftWraps,
loadGroupMessages, loadGroupMessages,
deriveIsGroupMember, userIsGroupMember,
updateCurrentSession, updateCurrentSession,
communityListsByAddress, communityListsByAddress,
searchGroupMeta, searchGroupMeta,
@ -26,7 +26,7 @@
limit += 20 limit += 20
} }
const userIsMember = meta => deriveIsGroupMember(getAddress(meta.event), true).get() const userIsMember = meta => $userIsGroupMember(getAddress(meta.event), true)
const userGroupMeta = derived(groupMeta, filter(userIsMember)) const userGroupMeta = derived(groupMeta, filter(userIsMember))

View File

@ -15,7 +15,7 @@
import PersonSelect from "src/app/shared/PersonSelect.svelte" import PersonSelect from "src/app/shared/PersonSelect.svelte"
import { import {
mention, mention,
getSettings, settings,
publishSettings, publishSettings,
searchTopics, searchTopics,
userMutes, userMutes,
@ -23,12 +23,12 @@
updateSingleton, updateSingleton,
} from "src/engine" } from "src/engine"
const settings = getSettings() const values = {...$settings}
const searchWords = q => pluck("name", $searchTopics(q)) const searchWords = q => pluck("name", $searchTopics(q))
const submit = () => { const submit = () => {
publishSettings(settings) publishSettings(values)
updateSingleton(MUTES, () => mutedPubkeys.map(mention)) updateSingleton(MUTES, () => mutedPubkeys.map(mention))
showInfo("Your preferences have been saved!") showInfo("Your preferences have been saved!")
@ -50,20 +50,20 @@
</div> </div>
<div class="flex w-full flex-col gap-8"> <div class="flex w-full flex-col gap-8">
<FieldInline label="Show likes on notes"> <FieldInline label="Show likes on notes">
<Toggle bind:value={settings.enable_reactions} /> <Toggle bind:value={values.enable_reactions} />
<p slot="info"> <p slot="info">
Show how many likes and reactions a note received. Disabling this can reduce how much data {appName} Show how many likes and reactions a note received. Disabling this can reduce how much data {appName}
uses. uses.
</p> </p>
</FieldInline> </FieldInline>
<FieldInline label="Show images and link previews"> <FieldInline label="Show images and link previews">
<Toggle bind:value={settings.show_media} /> <Toggle bind:value={values.show_media} />
<p slot="info"> <p slot="info">
If enabled, {appName} will automatically show images and previews for embedded links. If enabled, {appName} will automatically show images and previews for embedded links.
</p> </p>
</FieldInline> </FieldInline>
<FieldInline label="Hide sensitive content"> <FieldInline label="Hide sensitive content">
<Toggle bind:value={settings.hide_sensitive} /> <Toggle bind:value={values.hide_sensitive} />
<p slot="info"> <p slot="info">
If enabled, content flagged by the author as potentially sensitive will be hidden. If enabled, content flagged by the author as potentially sensitive will be hidden.
</p> </p>
@ -71,9 +71,9 @@
<Field> <Field>
<div slot="label" class="flex justify-between"> <div slot="label" class="flex justify-between">
<strong>Minimum WoT score</strong> <strong>Minimum WoT score</strong>
<div>{settings.min_wot_score}</div> <div>{values.min_wot_score}</div>
</div> </div>
<Input type="range" bind:value={settings.min_wot_score} min={-10} max={10} /> <Input type="range" bind:value={values.min_wot_score} min={-10} max={10} />
<p slot="info"> <p slot="info">
Select a minimum <Anchor underline modal href="/help/web-of-trust">web-of-trust</Anchor> Select a minimum <Anchor underline modal href="/help/web-of-trust">web-of-trust</Anchor>
score. Notes from accounts with a lower score will be automatically hidden. score. Notes from accounts with a lower score will be automatically hidden.
@ -86,7 +86,7 @@
<Field label="Muted words and topics"> <Field label="Muted words and topics">
<SearchSelect <SearchSelect
multiple multiple
bind:value={settings.muted_words} bind:value={values.muted_words}
search={searchWords} search={searchWords}
termToItem={identity} /> termToItem={identity} />
<p slot="info">Notes containing these words will be hidden by default.</p> <p slot="info">Notes containing these words will be hidden by default.</p>

View File

@ -21,7 +21,7 @@
hints, hints,
groupSharedKeys, groupSharedKeys,
relaySearch, relaySearch,
deriveIsGroupMember, userIsGroupMember,
groupAdminKeys, groupAdminKeys,
subscribe, subscribe,
LOAD_OPTS, LOAD_OPTS,
@ -104,7 +104,7 @@
prop("group"), prop("group"),
sortBy( sortBy(
k => -k.created_at, k => -k.created_at,
$groupSharedKeys.filter(k => deriveIsGroupMember(k.group).get()), $groupSharedKeys.filter(k => $userIsGroupMember(k.group)),
), ),
), ),
) )

View File

@ -9,14 +9,14 @@
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import {env, getSettings, publishSettings} from "src/engine" import {env, settings, publishSettings} from "src/engine"
import SearchSelect from "src/partials/SearchSelect.svelte" import SearchSelect from "src/partials/SearchSelect.svelte"
import {fuzzy} from "src/util/misc" import {fuzzy} from "src/util/misc"
const settings = getSettings() const values = {...$settings}
const submit = () => { const submit = () => {
publishSettings(settings) publishSettings(values)
showInfo("Your settings have been saved!") showInfo("Your settings have been saved!")
} }
@ -26,7 +26,7 @@
const formatPercent = d => String(Math.round(d * 100)) const formatPercent = d => String(Math.round(d * 100))
const parsePercent = p => parseInt(p) / 100 const parsePercent = p => parseInt(p) / 100
$: settings.relay_redundancy = Math.round(Math.log10(settings.relay_limit) * 4) $: values.relay_redundancy = Math.round(Math.log10(values.relay_limit) * 4)
document.title = "Settings" document.title = "Settings"
</script> </script>
@ -38,7 +38,7 @@
</div> </div>
<div class="flex w-full flex-col gap-8"> <div class="flex w-full flex-col gap-8">
<Field label="Default zap amount"> <Field label="Default zap amount">
<Input bind:value={settings.default_zap}> <Input bind:value={values.default_zap}>
<i slot="before" class="fa fa-bolt" /> <i slot="before" class="fa fa-bolt" />
</Input> </Input>
<p slot="info">The default amount of sats to use when sending a lightning tip.</p> <p slot="info">The default amount of sats to use when sending a lightning tip.</p>
@ -46,7 +46,7 @@
<Field label="Platform zap split"> <Field label="Platform zap split">
<Input <Input
type="number" type="number"
bind:value={settings.platform_zap_split} bind:value={values.platform_zap_split}
format={formatPercent} format={formatPercent}
parse={parsePercent}> parse={parsePercent}>
<i slot="before" class="fa fa-percent" /> <i slot="before" class="fa fa-percent" />
@ -58,9 +58,9 @@
<Field> <Field>
<div slot="label" class="flex justify-between"> <div slot="label" class="flex justify-between">
<strong>Max relays per request</strong> <strong>Max relays per request</strong>
<div>{settings.relay_limit} relays</div> <div>{values.relay_limit} relays</div>
</div> </div>
<Input type="range" bind:value={settings.relay_limit} min={1} max={30} parse={parseInt} /> <Input type="range" bind:value={values.relay_limit} min={1} max={30} parse={parseInt} />
<p slot="info"> <p slot="info">
This controls how many relays to max out at when loading feeds and event context. More is This controls how many relays to max out at when loading feeds and event context. More is
faster, but will require more bandwidth and processing power. faster, but will require more bandwidth and processing power.
@ -68,7 +68,7 @@
</Field> </Field>
{#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0} {#if !$env.FORCE_GROUP && $env.PLATFORM_RELAYS.length === 0}
<FieldInline label="Authenticate with relays"> <FieldInline label="Authenticate with relays">
<Toggle bind:value={settings.auto_authenticate} /> <Toggle bind:value={values.auto_authenticate} />
<p slot="info"> <p slot="info">
Allows {appName} to authenticate with relays that have access controls automatically. Allows {appName} to authenticate with relays that have access controls automatically.
</p> </p>
@ -84,7 +84,7 @@
<SearchSelect <SearchSelect
multiple multiple
search={searchUploadProviders} search={searchUploadProviders}
bind:value={settings.nip96_urls} bind:value={values.nip96_urls}
termToItem={identity}> termToItem={identity}>
<div slot="item" let:item> <div slot="item" let:item>
<strong>{item}</strong> <strong>{item}</strong>
@ -92,7 +92,7 @@
</SearchSelect> </SearchSelect>
</Field> </Field>
<Field label="Dufflepud URL"> <Field label="Dufflepud URL">
<Input bind:value={settings.dufflepud_url}> <Input bind:value={values.dufflepud_url}>
<i slot="before" class="fa-solid fa-server" /> <i slot="before" class="fa-solid fa-server" />
</Input> </Input>
<p slot="info"> <p slot="info">
@ -104,7 +104,7 @@
</p> </p>
</Field> </Field>
<Field label="Imgproxy URL"> <Field label="Imgproxy URL">
<Input bind:value={settings.imgproxy_url}> <Input bind:value={values.imgproxy_url}>
<i slot="before" class="fa-solid fa-image" /> <i slot="before" class="fa-solid fa-image" />
</Input> </Input>
<p slot="info"> <p slot="info">
@ -115,7 +115,7 @@
</p> </p>
</Field> </Field>
<Field label="Multiplextr URL"> <Field label="Multiplextr URL">
<Input bind:value={settings.multiplextr_url}> <Input bind:value={values.multiplextr_url}>
<i slot="before" class="fa-solid fa-code-merge" /> <i slot="before" class="fa-solid fa-code-merge" />
</Input> </Input>
<p slot="info"> <p slot="info">
@ -128,14 +128,14 @@
</p> </p>
</Field> </Field>
<FieldInline label="Report errors and analytics"> <FieldInline label="Report errors and analytics">
<Toggle bind:value={settings.report_analytics} /> <Toggle bind:value={values.report_analytics} />
<p slot="info"> <p slot="info">
Keep this enabled if you would like developers to be able to know what features are used, Keep this enabled if you would like developers to be able to know what features are used,
and to diagnose and fix bugs. and to diagnose and fix bugs.
</p> </p>
</FieldInline> </FieldInline>
<FieldInline label="Enable client fingerprinting"> <FieldInline label="Enable client fingerprinting">
<Toggle bind:value={settings.enable_client_tag} /> <Toggle bind:value={values.enable_client_tag} />
<p slot="info"> <p slot="info">
If this is turned on, public notes you create will have a "client" tag added. This helps If this is turned on, public notes you create will have a "client" tag added. This helps
with troubleshooting, and allows other people to find out about {appName}. with troubleshooting, and allows other people to find out about {appName}.

View File

@ -54,10 +54,9 @@ import {
loadOne, loadOne,
createAndPublish, createAndPublish,
deriveAdminKeyForGroup, deriveAdminKeyForGroup,
deriveIsGroupMember, userIsGroupMember,
deriveSharedKeyForGroup, deriveSharedKeyForGroup,
displayProfileByPubkey, displayProfileByPubkey,
getSettings,
env, env,
getClientTags, getClientTags,
groupAdminKeys, groupAdminKeys,
@ -388,6 +387,8 @@ export const publishToGroupsPublicly = async (addresses, template, {anonymous =
} }
export const publishToGroupsPrivately = async (addresses, template, {anonymous = false} = {}) => { export const publishToGroupsPrivately = async (addresses, template, {anonymous = false} = {}) => {
const $userIsGroupMember = userIsGroupMember.get()
const events = [] const events = []
const pubs = [] const pubs = []
for (const address of addresses) { for (const address of addresses) {
@ -399,7 +400,7 @@ export const publishToGroupsPrivately = async (addresses, template, {anonymous =
throw new Error("Attempted to publish privately to an invalid address", address) throw new Error("Attempted to publish privately to an invalid address", address)
} }
if (!deriveIsGroupMember(address).get()) { if (!$userIsGroupMember(address)) {
throw new Error("Attempted to publish privately to a group the user is not a member of") throw new Error("Attempted to publish privately to a group the user is not a member of")
} }
@ -1034,8 +1035,8 @@ export const setAppData = async (d: string, data: any) => {
} }
} }
export const publishSettings = (updates: Record<string, any>) => export const publishSettings = ($settings: Record<string, any>) =>
setAppData(appDataKeys.USER_SETTINGS, {...getSettings(), ...updates}) setAppData(appDataKeys.USER_SETTINGS, $settings)
export const setSession = (k, data) => sessions.update($s => ($s[k] ? {...$s, [k]: data} : $s)) export const setSession = (k, data) => sessions.update($s => ($s[k] ? {...$s, [k]: data} : $s))

View File

@ -22,7 +22,7 @@ import {
topics, topics,
relays, relays,
deriveAdminKeyForGroup, deriveAdminKeyForGroup,
deriveGroupStatus, getGroupStatus,
getChannelId, getChannelId,
getSession, getSession,
groupAdminKeys, groupAdminKeys,
@ -71,7 +71,7 @@ projections.addHandler(24, (e: TrustedEvent) => {
return return
} }
const status = deriveGroupStatus(address).get() const status = getGroupStatus(getSession(recipient), address)
if (privkey) { if (privkey) {
const pubkey = getPublicKey(privkey) const pubkey = getPublicKey(privkey)

View File

@ -36,7 +36,7 @@ import {LIST_KINDS} from "src/domain"
import type {Zapper} from "src/engine/model" import type {Zapper} from "src/engine/model"
import {repository} from "src/engine/repository" import {repository} from "src/engine/repository"
import { import {
deriveUserCircles, getUserCircles,
getGroupReqInfo, getGroupReqInfo,
getCommunityReqInfo, getCommunityReqInfo,
dvmRequest, dvmRequest,
@ -175,7 +175,7 @@ export const loadGroups = async (rawAddrs: string[], explicitRelays: string[] =
export const loadGroupMessages = (addresses?: string[]) => { export const loadGroupMessages = (addresses?: string[]) => {
const promises = [] const promises = []
const addrs = addresses || deriveUserCircles().get() const addrs = addresses || getUserCircles(session.get())
const [groupAddrs, communityAddrs] = partition(isGroupAddress, addrs) const [groupAddrs, communityAddrs] = partition(isGroupAddress, addrs)
for (const address of groupAddrs) { for (const address of groupAddrs) {

View File

@ -75,6 +75,8 @@ import {
getFilterResultCardinality, getFilterResultCardinality,
isShareableRelayUrl, isShareableRelayUrl,
isReplaceable, isReplaceable,
isGroupAddress,
isCommunityAddress,
} from "@welshman/util" } from "@welshman/util"
import type {Filter, RouterScenario, TrustedEvent, SignedEvent} from "@welshman/util" import type {Filter, RouterScenario, TrustedEvent, SignedEvent} from "@welshman/util"
import { import {
@ -215,13 +217,22 @@ export const getFreshness = (key: string, value: any) =>
export const setFreshness = (key: string, value: any, ts: number) => export const setFreshness = (key: string, value: any, ts: number) =>
freshness.set(getFreshnessKey(key, value), ts) freshness.set(getFreshnessKey(key, value), ts)
// Session and settings // Session, signing, encryption
export const getSession = pubkey => sessions.get()[pubkey] export const getSession = pubkey => sessions.get()[pubkey]
export const getCurrentSession = () => sessions.get()[pubkey.get()] export const session = withGetter(derived([pubkey, sessions], ([$pk, $sessions]) => $sessions[$pk]))
export const getDefaultSettings = () => ({ export const connect = withGetter(derived(session, getConnect))
export const signer = withGetter(derived(session, getSigner))
export const nip04 = withGetter(derived(session, getNip04))
export const nip44 = withGetter(derived(session, getNip44))
export const nip59 = withGetter(derived(session, getNip59))
export const canSign = withGetter(derived(signer, $signer => $signer.isEnabled()))
// Settings
export const defaultSettings = {
relay_limit: 10, relay_limit: 10,
relay_redundancy: 3, relay_redundancy: 3,
default_zap: 21, default_zap: 21,
@ -238,11 +249,13 @@ export const getDefaultSettings = () => ({
dufflepud_url: env.get().DUFFLEPUD_URL, dufflepud_url: env.get().DUFFLEPUD_URL,
multiplextr_url: env.get().MULTIPLEXTR_URL, multiplextr_url: env.get().MULTIPLEXTR_URL,
platform_zap_split: env.get().PLATFORM_ZAP_SPLIT, platform_zap_split: env.get().PLATFORM_ZAP_SPLIT,
}) }
export const getSettings = () => ({...getDefaultSettings(), ...getSession(pubkey.get())?.settings}) export const settings = withGetter(
derived(session, $session => ({...defaultSettings, ...$session.settings})),
)
export const getSetting = k => prop(k, getSettings()) export const getSetting = k => prop(k, settings.get())
export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => { export const imgproxy = (url: string, {w = 640, h = 1024} = {}) => {
const base = getSetting("imgproxy_url") const base = getSetting("imgproxy_url")
@ -270,19 +283,6 @@ export const dufflepud = (path: string) => {
return `${base}/${path}` return `${base}/${path}`
} }
export const session = new Derived(
[pubkey, sessions],
([$pk, $sessions]: [string, Record<string, Session>]) => ($pk ? $sessions[$pk] : null),
)
export const connect = session.derived(getConnect)
export const signer = session.derived(getSigner)
export const nip04 = session.derived(getNip04)
export const nip44 = session.derived(getNip44)
export const nip59 = session.derived(getNip59)
export const canSign = signer.derived($signer => $signer.isEnabled())
export const settings = derived(pubkey, getSettings)
// Plaintext // Plaintext
export const getPlaintext = (e: TrustedEvent) => plaintext.get()[e.id] export const getPlaintext = (e: TrustedEvent) => plaintext.get()[e.id]
@ -617,65 +617,6 @@ export const userMuteList = derived([muteListsByPubkey, pubkey], ([$m, $pk]) =>
export const userMutes = derived(userMuteList, l => getSingletonValues("p", l)) export const userMutes = derived(userMuteList, l => getSingletonValues("p", l))
// Events
export const isEventMuted = withGetter(
derived(
[userMutes, userFollows, settings, pubkey],
([$userMutes, $userFollows, $settings, $pubkey]) => {
const words = $settings.muted_words
const minWot = $settings.min_wot_score
const regex =
words.length > 0 ? new RegExp(`\\b(${words.map(w => w.toLowerCase()).join("|")})\\b`) : null
return (e: Partial<TrustedEvent>, strict = false) => {
if (!$pubkey || e.pubkey === $pubkey) {
return false
}
const tags = Tags.wrap(e.tags || [])
const {roots, replies} = tags.ancestors()
if (
find(
t => $userMutes.has(t),
[e.id, e.pubkey, ...roots.values().valueOf(), ...replies.values().valueOf()],
)
) {
return true
}
if (regex && e.content?.toLowerCase().match(regex)) {
return true
}
if (!strict) {
return false
}
const isGroupMember = tags
.groups()
.values()
.some(a => deriveIsGroupMember(a).get())
const isCommunityMember = tags
.communities()
.values()
.some(a => false)
const wotAdjustment = isCommunityMember || isGroupMember ? 1 : 0
if (
!$userFollows.has(e.pubkey) &&
getWotScore($pubkey, e.pubkey) < minWot - wotAdjustment
) {
return true
}
return false
}
},
),
)
// Channels // Channels
export const sortChannels = $channels => export const sortChannels = $channels =>
@ -853,37 +794,36 @@ export const getGroupStatus = (session, address) =>
(session?.groups?.[address] || {}) as GroupStatus (session?.groups?.[address] || {}) as GroupStatus
export const deriveGroupStatus = address => export const deriveGroupStatus = address =>
session.derived($session => getGroupStatus($session, address)) derived(session, $session => getGroupStatus($session, address))
export const getIsGroupMember = (session, address, includeRequests = false) => { export const userIsGroupMember = withGetter(
const status = getGroupStatus(session, address) derived(session, $session => (address, includeRequests = false) => {
const status = getGroupStatus($session, address)
if (address.startsWith("34550:")) { if (isCommunityAddress(address)) {
return status.joined return status.joined
}
if (address.startsWith("35834:")) {
if (includeRequests && status.access === GroupAccess.Requested) {
return true
} }
return status.access === GroupAccess.Granted if (isGroupAddress(address)) {
} if (includeRequests && status.access === GroupAccess.Requested) {
return true
}
return false return status.access === GroupAccess.Granted
} }
export const deriveIsGroupMember = (address, includeRequests = false) => return false
session.derived($session => getIsGroupMember($session, address, includeRequests)) }),
)
export const deriveGroupOptions = (defaultGroups = []) => export const deriveGroupOptions = (defaultGroups = []) =>
session.derived($session => { derived([session, userIsGroupMember], ([$session, $userIsGroupMember]) => {
const options = [] const options = []
for (const address of Object.keys($session?.groups || {})) { for (const address of Object.keys($session?.groups || {})) {
const group = groups.key(address).get() const group = groups.key(address).get()
if (group && deriveIsGroupMember(address).get()) { if (group && $userIsGroupMember(address)) {
options.push(group) options.push(group)
} }
} }
@ -895,22 +835,73 @@ export const deriveGroupOptions = (defaultGroups = []) =>
return uniqBy(prop("address"), options) return uniqBy(prop("address"), options)
}) })
export const getUserCircles = (session: Session) => export const getUserCircles = (session: Session) => {
Object.entries(session?.groups || {}) const $userIsGroupMember = userIsGroupMember.get()
.filter(([a, s]) => deriveIsGroupMember(a).get())
return Object.entries(session?.groups || {})
.filter(([a, s]) => $userIsGroupMember(a))
.map(([a, s]) => a) .map(([a, s]) => a)
}
export const deriveUserCircles = () => session.derived(getUserCircles) export const getUserGroups = (session: Session) => getUserCircles(session).filter(isGroupAddress)
export const getUserGroups = (session: Session) =>
getUserCircles(session).filter(a => a.startsWith("35834:"))
export const deriveUserGroups = () => session.derived(getUserGroups)
export const getUserCommunities = (session: Session) => export const getUserCommunities = (session: Session) =>
getUserCircles(session).filter(a => a.startsWith("34550:")) getUserCircles(session).filter(isCommunityAddress)
export const deriveUserCommunities = () => session.derived(getUserCommunities) // Events
export const isEventMuted = withGetter(
derived(
[userMutes, userFollows, settings, pubkey, userIsGroupMember],
([$userMutes, $userFollows, $settings, $pubkey, $userIsGroupMember]) => {
const words = $settings.muted_words
const minWot = $settings.min_wot_score
const regex =
words.length > 0 ? new RegExp(`\\b(${words.map(w => w.toLowerCase()).join("|")})\\b`) : null
return (e: Partial<TrustedEvent>, strict = false) => {
if (!$pubkey || e.pubkey === $pubkey) {
return false
}
const tags = Tags.wrap(e.tags || [])
const {roots, replies} = tags.ancestors()
if (
find(
t => $userMutes.has(t),
[e.id, e.pubkey, ...roots.values().valueOf(), ...replies.values().valueOf()],
)
) {
return true
}
if (regex && e.content?.toLowerCase().match(regex)) {
return true
}
if (!strict) {
return false
}
const isInGroup = tags.groups().values().some($userIsGroupMember)
const isInCommunity = tags
.communities()
.values()
.some(a => false)
const wotAdjustment = isInCommunity || isInGroup ? 1 : 0
if (
!$userFollows.has(e.pubkey) &&
getWotScore($pubkey, e.pubkey) < minWot - wotAdjustment
) {
return true
}
return false
}
},
),
)
// Read receipts // Read receipts
@ -935,19 +926,20 @@ export const isSeen = derived(allReadReceipts, $m => e => $m.has(e.id))
// Notifications // Notifications
export const notifications = derived(events, $events => { export const notifications = derived(
const $pubkey = pubkey.get() [pubkey, events, isEventMuted],
const $isEventMuted = isEventMuted.get() ([$pubkey, $events, $isEventMuted]) => {
const kinds = [...noteKinds, ...reactionKinds] const kinds = [...noteKinds, ...reactionKinds]
return Array.from(repository.query([{"#p": [$pubkey]}])).filter( return Array.from(repository.query([{"#p": [$pubkey]}])).filter(
e => e =>
kinds.includes(e.kind) && kinds.includes(e.kind) &&
e.pubkey !== $pubkey && e.pubkey !== $pubkey &&
!$isEventMuted(e) && !$isEventMuted(e) &&
(e.kind !== 7 || isLike(e)), (e.kind !== 7 || isLike(e)),
) )
}) },
)
export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $notifications]) => { export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $notifications]) => {
const since = now() - seconds(30, "day") const since = now() - seconds(30, "day")
@ -958,14 +950,13 @@ export const unreadNotifications = derived([isSeen, notifications], ([$isSeen, $
}) })
export const groupNotifications = new Derived( export const groupNotifications = new Derived(
[session, events, groupRequests, groupAlerts, groupAdminKeys], [session, events, groupRequests, groupAlerts, groupAdminKeys, isEventMuted],
x => x, x => x,
) )
.throttle(3000) .throttle(3000)
.derived(([$session, $events, $requests, $alerts, $adminKeys, $addresses]) => { .derived(([$session, $events, $requests, $alerts, $adminKeys, $addresses, $isEventMuted]) => {
const addresses = new Set(getUserCircles($session)) const addresses = new Set(getUserCircles($session))
const adminPubkeys = new Set($adminKeys.map(k => k.pubkey)) const adminPubkeys = new Set($adminKeys.map(k => k.pubkey))
const $isEventMuted = isEventMuted.get()
const shouldSkip = e => { const shouldSkip = e => {
const context = e.tags.filter(t => t[0] === "a") const context = e.tags.filter(t => t[0] === "a")