Add remove group, format

This commit is contained in:
Jon Staab
2024-08-16 10:50:38 -07:00
parent 437cfa7bc4
commit bd8fcd3264
51 changed files with 800 additions and 435 deletions
+164 -80
View File
@@ -1,17 +1,67 @@
import type {Readable} from "svelte/store"
import type {FuseResult} from 'fuse.js'
import type {FuseResult} from "fuse.js"
import {get, writable, readable, derived} from "svelte/store"
import type {Maybe} from "@welshman/lib"
import {max, uniq, between, uniqBy, groupBy, pushToMapKey, nthEq, batcher, postJson, stripProtocol, assoc, indexBy, now} from "@welshman/lib"
import {getIdentifier, getRelayTags, getRelayTagValues, normalizeRelayUrl, getPubkeyTagValues, GROUP_META, PROFILE, RELAYS, FOLLOWS, MUTES, GROUPS, getGroupTags, readProfile, readList, asDecryptedEvent, editList, makeList, createList, GROUP_JOIN, GROUP_ADD_USER} from "@welshman/util"
import type {Filter, SignedEvent, CustomEvent, PublishedProfile, PublishedList} from '@welshman/util'
import type {SubscribeRequest, PublishRequest} from '@welshman/net'
import {publish as basePublish, subscribe} from '@welshman/net'
import {decrypt} from '@welshman/signer'
import {
max,
uniq,
between,
uniqBy,
groupBy,
pushToMapKey,
nthEq,
batcher,
postJson,
stripProtocol,
assoc,
indexBy,
now,
} from "@welshman/lib"
import {
getIdFilters,
getIdentifier,
getRelayTags,
getRelayTagValues,
normalizeRelayUrl,
getPubkeyTagValues,
GROUP_META,
PROFILE,
RELAYS,
FOLLOWS,
MUTES,
GROUPS,
getGroupTags,
readProfile,
readList,
asDecryptedEvent,
editList,
makeList,
createList,
GROUP_JOIN,
GROUP_ADD_USER,
} from "@welshman/util"
import type {
Filter,
SignedEvent,
CustomEvent,
PublishedProfile,
PublishedList,
} from "@welshman/util"
import type {SubscribeRequest, PublishRequest} from "@welshman/net"
import {publish as basePublish, subscribe} from "@welshman/net"
import {decrypt} from "@welshman/signer"
import {deriveEvents, deriveEventsMapped, getter, withGetter} from "@welshman/store"
import {parseJson, createSearch} from '@lib/util'
import type {Session, Handle, Relay} from '@app/types'
import {INDEXER_RELAYS, DUFFLEPUD_URL, repository, pk, getSession, getSigner, signer} from "@app/base"
import {parseJson, createSearch} from "@lib/util"
import type {Session, Handle, Relay} from "@app/types"
import {
INDEXER_RELAYS,
DUFFLEPUD_URL,
repository,
pk,
getSession,
getSigner,
signer,
} from "@app/base"
// Utils
@@ -21,15 +71,15 @@ export const createCollection = <T>({
getKey,
load,
}: {
name: string,
store: Readable<T[]>,
getKey: (item: T) => string,
name: string
store: Readable<T[]>
getKey: (item: T) => string
load: (key: string, ...args: any) => Promise<any>
}) => {
const indexStore = derived(store, $items => indexBy(getKey, $items))
const getIndex = getter(indexStore)
const getItem = (key: string) => getIndex().get(key)
const pending = new Map<string, Promise<Maybe<T>>>
const pending = new Map<string, Promise<Maybe<T>>>()
const loadItem = async (key: string, ...args: any[]) => {
if (getFreshness(name, key) > now() - 3600) {
@@ -68,6 +118,25 @@ export const createCollection = <T>({
return {indexStore, getIndex, deriveItem, loadItem, getItem}
}
export const deriveEvent = (idOrAddress: string, hints: string[] = []) => {
let attempted = false
const filters = getIdFilters([idOrAddress])
const relays = [...hints, ...INDEXER_RELAYS]
return derived(
deriveEvents(repository, {filters, includeDeleted: true}),
(events: CustomEvent[]) => {
if (!attempted && events.length === 0) {
load({relays, filters})
attempted = true
}
return events[0]
},
)
}
export const publish = (request: PublishRequest) => {
repository.publish(request.event)
@@ -79,12 +148,12 @@ export const load = (request: SubscribeRequest) =>
const sub = subscribe({closeOnEose: true, timeout: 3000, delay: 50, ...request})
const events: CustomEvent[] = []
sub.emitter.on('event', (url: string, e: SignedEvent) => {
sub.emitter.on("event", (url: string, e: SignedEvent) => {
repository.publish(e)
events.push(e)
})
sub.emitter.on('complete', () => resolve(events))
sub.emitter.on("complete", () => resolve(events))
})
// Freshness
@@ -93,9 +162,11 @@ export const freshness = withGetter(writable<Record<string, number>>({}))
export const getFreshnessKey = (ns: string, key: string) => `${ns}:${key}`
export const getFreshness = (ns: string, key: string) => freshness.get()[getFreshnessKey(ns, key)] || 0
export const getFreshness = (ns: string, key: string) =>
freshness.get()[getFreshnessKey(ns, key)] || 0
export const setFreshness = (ns: string, key: string, ts: number) => freshness.update(assoc(getFreshnessKey(ns, key), ts))
export const setFreshness = (ns: string, key: string, ts: number) =>
freshness.update(assoc(getFreshnessKey(ns, key), ts))
export const setFreshnessBulk = (ns: string, updates: Record<string, number>) =>
freshness.update($freshness => {
@@ -131,7 +202,9 @@ export const ensurePlaintext = async (e: CustomEvent) => {
export const relays = writable<Relay[]>([])
export const relaysByPubkey = derived(relays, $relays => groupBy(($relay: Relay) => $relay.pubkey, $relays))
export const relaysByPubkey = derived(relays, $relays =>
groupBy(($relay: Relay) => $relay.pubkey, $relays),
)
export const {
indexStore: relaysByUrl,
@@ -139,7 +212,7 @@ export const {
deriveItem: deriveRelay,
loadItem: loadRelay,
} = createCollection({
name: 'relays',
name: "relays",
store: relays,
getKey: (relay: Relay) => relay.url,
load: batcher(800, async (urls: string[]) => {
@@ -168,7 +241,7 @@ export const {
deriveItem: deriveHandle,
loadItem: loadHandle,
} = createCollection({
name: 'handles',
name: "handles",
store: handles,
getKey: (handle: Handle) => handle.nip05,
load: batcher(800, async (nip05s: string[]) => {
@@ -201,7 +274,7 @@ export const {
deriveItem: deriveProfile,
loadItem: loadProfile,
} = createCollection({
name: 'profiles',
name: "profiles",
store: profiles,
getKey: profile => profile.event.pubkey,
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
@@ -215,10 +288,14 @@ export const {
// Relay selections
export const getReadRelayUrls = (event?: CustomEvent): string[] =>
getRelayTags(event?.tags || []).filter((t: string[]) => !t[2] || t[2] === 'read').map((t: string[]) => normalizeRelayUrl(t[1]))
getRelayTags(event?.tags || [])
.filter((t: string[]) => !t[2] || t[2] === "read")
.map((t: string[]) => normalizeRelayUrl(t[1]))
export const getWriteRelayUrls = (event?: CustomEvent): string[] =>
getRelayTags(event?.tags || []).filter((t: string[]) => !t[2] || t[2] === 'write').map((t: string[]) => normalizeRelayUrl(t[1]))
getRelayTags(event?.tags || [])
.filter((t: string[]) => !t[2] || t[2] === "write")
.map((t: string[]) => normalizeRelayUrl(t[1]))
export const relaySelections = deriveEvents(repository, {filters: [{kinds: [RELAYS]}]})
@@ -228,7 +305,7 @@ export const {
deriveItem: deriveRelaySelections,
loadItem: loadRelaySelections,
} = createCollection({
name: 'relaySelections',
name: "relaySelections",
store: relaySelections,
getKey: relaySelections => relaySelections.pubkey,
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
@@ -236,7 +313,7 @@ export const {
...request,
relays: [...relays, ...INDEXER_RELAYS],
filters: [{kinds: [RELAYS], authors: [pubkey]}],
})
}),
})
// Follows
@@ -258,7 +335,7 @@ export const {
deriveItem: deriveFollows,
loadItem: loadFollows,
} = createCollection({
name: 'follows',
name: "follows",
store: follows,
getKey: follows => follows.event.pubkey,
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
@@ -266,7 +343,7 @@ export const {
...request,
relays: [...relays, ...INDEXER_RELAYS],
filters: [{kinds: [FOLLOWS], authors: [pubkey]}],
})
}),
})
// Mutes
@@ -288,7 +365,7 @@ export const {
deriveItem: deriveMutes,
loadItem: loadMutes,
} = createCollection({
name: 'mutes',
name: "mutes",
store: mutes,
getKey: mute => mute.event.pubkey,
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
@@ -296,7 +373,7 @@ export const {
...request,
relays: [...relays, ...INDEXER_RELAYS],
filters: [{kinds: [MUTES], authors: [pubkey]}],
})
}),
})
// Groups
@@ -304,7 +381,7 @@ export const {
export const GROUP_DELIMITER = `'`
export const makeGroupId = (url: string, nom: string) =>
[stripProtocol(url).replace(/\/$/, ''), nom].join(GROUP_DELIMITER)
[stripProtocol(url).replace(/\/$/, ""), nom].join(GROUP_DELIMITER)
export const splitGroupId = (groupId: string) => {
const [url, nom] = groupId.split(GROUP_DELIMITER)
@@ -321,10 +398,10 @@ export const getGroupName = (e?: CustomEvent) => e?.tags.find(nthEq(0, "name"))?
export const getGroupPicture = (e?: CustomEvent) => e?.tags.find(nthEq(0, "picture"))?.[1]
export type Group = {
nom: string,
name?: string,
about?: string,
picture?: string,
nom: string
name?: string
about?: string
picture?: string
event?: CustomEvent
}
@@ -353,7 +430,7 @@ export const {
deriveItem: deriveGroup,
loadItem: loadGroup,
} = createCollection({
name: 'groups',
name: "groups",
store: groups,
getKey: (group: PublishedGroup) => group.nom,
load: (nom: string, relays: string[] = [], request: Partial<SubscribeRequest> = {}) =>
@@ -362,25 +439,23 @@ export const {
load({
...request,
relays,
filters: [{kinds: [GROUP_META], '#d': [nom]}],
filters: [{kinds: [GROUP_META], "#d": [nom]}],
}),
])
]),
})
export const searchGroups = derived(
groups,
$groups =>
createSearch($groups, {
getValue: (group: PublishedGroup) => group.nom,
sortFn: (result: FuseResult<PublishedGroup>) => {
const scale = result.item.picture ? 0.5 : 1
export const searchGroups = derived(groups, $groups =>
createSearch($groups, {
getValue: (group: PublishedGroup) => group.nom,
sortFn: (result: FuseResult<PublishedGroup>) => {
const scale = result.item.picture ? 0.5 : 1
return result.score! * scale
},
fuseOptions: {
keys: ["name", {name: "about", weight: 0.3}],
},
})
return result.score! * scale
},
fuseOptions: {
keys: ["name", {name: "about", weight: 0.3}],
},
}),
)
// Qualified groups
@@ -396,12 +471,16 @@ export const qualifiedGroups = derived([relaysByPubkey, groups], ([$relaysByPubk
const relays = $relaysByPubkey.get(group.event.pubkey) || []
return relays.map(relay => ({id: makeGroupId(relay.url, group.nom), relay, group}))
})
}),
)
export const qualifiedGroupsById = derived(qualifiedGroups, $qualifiedGroups => indexBy($qg => $qg.id, $qualifiedGroups))
export const qualifiedGroupsById = derived(qualifiedGroups, $qualifiedGroups =>
indexBy($qg => $qg.id, $qualifiedGroups),
)
export const qualifiedGroupsByNom = derived(qualifiedGroups, $qualifiedGroups => groupBy($qg => $qg.group.nom, $qualifiedGroups))
export const qualifiedGroupsByNom = derived(qualifiedGroups, $qualifiedGroups =>
groupBy($qg => $qg.group.nom, $qualifiedGroups),
)
export const relayUrlsByNom = derived(qualifiedGroups, $qualifiedGroups => {
const $relayUrlsByNom = new Map()
@@ -452,7 +531,7 @@ export const {
deriveItem: deriveGroupMembership,
loadItem: loadGroupMembership,
} = createCollection({
name: 'groupMemberships',
name: "groupMemberships",
store: groupMemberships,
getKey: groupMembership => groupMembership.event.pubkey,
load: (pubkey: string, relays = [], request: Partial<SubscribeRequest> = {}) =>
@@ -460,7 +539,7 @@ export const {
...request,
relays: [...relays, ...INDEXER_RELAYS],
filters: [{kinds: [GROUPS], authors: [pubkey]}],
})
}),
})
// Group Messages
@@ -471,7 +550,7 @@ export type GroupMessage = {
}
export const readGroupMessage = (event: CustomEvent): Maybe<GroupMessage> => {
const nom = event.tags.find(nthEq(0, 'h'))?.[1]
const nom = event.tags.find(nthEq(0, "h"))?.[1]
if (!nom || between(GROUP_ADD_USER - 1, GROUP_JOIN + 1, event.kind)) {
return undefined
@@ -505,20 +584,20 @@ export const {
deriveItem: deriveGroupConversation,
loadItem: loadGroupConversation,
} = createCollection({
name: 'groupConversations',
name: "groupConversations",
store: groupConversations,
getKey: groupConversation => groupConversation.nom,
load: (nom: string, hints = [], request: Partial<SubscribeRequest> = {}) => {
const relays = [...hints, ...get(relayUrlsByNom).get(nom) || []]
const relays = [...hints, ...(get(relayUrlsByNom).get(nom) || [])]
const conversation = get(groupConversations).find(c => c.nom === nom)
const timestamps = conversation?.messages.map(m => m.event.created_at) || []
const since = Math.min(0, max(timestamps) - 3600)
const since = Math.max(0, max(timestamps) - 3600)
if (relays.length === 0) {
console.warn(`Attempted to load conversation for ${nom} with no qualified groups`)
}
return load({...request, relays, filters: [{'#h': [nom], since}]})
return load({...request, relays, filters: [{"#h": [nom], since}]})
},
})
@@ -532,30 +611,35 @@ export const userProfile = derived([pk, profilesByPubkey], ([$pk, $profilesByPub
return $profilesByPubkey.get($pk)
})
export const userMembership = derived([pk, groupMembershipByPubkey], ([$pk, $groupMembershipByPubkey]) => {
if (!$pk) return null
export const userMembership = derived(
[pk, groupMembershipByPubkey],
([$pk, $groupMembershipByPubkey]) => {
if (!$pk) return null
loadGroupMembership($pk)
loadGroupMembership($pk)
return $groupMembershipByPubkey.get($pk)
})
return $groupMembershipByPubkey.get($pk)
},
)
export const userGroupsByNom = withGetter(derived([userMembership, qualifiedGroupsById], ([$userMembership, $qualifiedGroupsById]) => {
const $userGroupsByNom = new Map()
export const userGroupsByNom = withGetter(
derived([userMembership, qualifiedGroupsById], ([$userMembership, $qualifiedGroupsById]) => {
const $userGroupsByNom = new Map()
for (const id of $userMembership?.ids || []) {
const [url, nom] = splitGroupId(id)
const group = $qualifiedGroupsById.get(id)
const groups = $userGroupsByNom.get(nom) || []
for (const id of $userMembership?.ids || []) {
const [url, nom] = splitGroupId(id)
const group = $qualifiedGroupsById.get(id)
const groups = $userGroupsByNom.get(nom) || []
loadGroup(nom, [url])
loadGroup(nom, [url])
if (group) {
groups.push(group)
if (group) {
groups.push(group)
}
$userGroupsByNom.set(nom, groups)
}
$userGroupsByNom.set(nom, groups)
}
return $userGroupsByNom
}))
return $userGroupsByNom
}),
)