Clean up wot
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
removeFromList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Router} from "./router.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -21,7 +21,7 @@ import type {IClient} from "./client.js"
|
||||
* so it depends on the relay-list collection. Feeds `RelayStats.getQuality` so
|
||||
* blocked relays are never selected.
|
||||
*/
|
||||
export class BlockedRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class BlockedRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [BLOCKED_RELAYS]}],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import type {IClient} from "./client.js"
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {IClient} from "./client.js"
|
||||
* Blossom server lists (kind 10063), keyed by pubkey. Loaded via the outbox
|
||||
* model (the author's write relays), so it depends on the relay-list collection.
|
||||
*/
|
||||
export class BlossomServerLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class BlossomServerLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [BLOSSOM_SERVERS]}],
|
||||
|
||||
@@ -18,10 +18,10 @@ export class ClientData<T> {
|
||||
protected index = writable(new Map<string, T>())
|
||||
protected getIndex = getter(this.index)
|
||||
protected itemSubscribers: ((key: string, value: Maybe<T>) => void)[] = []
|
||||
public derive: (key?: string, ...args: any[]) => Readable<Maybe<T>>
|
||||
public derived: (key?: string, ...args: any[]) => Readable<Maybe<T>>
|
||||
|
||||
constructor(protected readonly ctx: IClient) {
|
||||
this.derive = makeDeriveItem(this.index)
|
||||
this.derived = makeDeriveItem(this.index)
|
||||
}
|
||||
|
||||
subscribe = this.index.subscribe
|
||||
@@ -83,7 +83,7 @@ export class ClientData<T> {
|
||||
|
||||
/**
|
||||
* A `ClientData` collection that knows how to lazily load items by key from the
|
||||
* network. Subclasses implement `fetch`; `load`/`forceLoad`/`derive` are derived
|
||||
* network. Subclasses implement `fetch`; `load`/`forceLoad`/`derived` are derived
|
||||
* from it (with per-key caching and backoff via `makeLoadItem`).
|
||||
*/
|
||||
export abstract class LoadableData<T> extends ClientData<T> {
|
||||
@@ -102,6 +102,6 @@ export abstract class LoadableData<T> extends ClientData<T> {
|
||||
|
||||
this.load = makeLoadItem(fetch, this.get, options)
|
||||
this.forceLoad = makeForceLoadItem(fetch, this.get)
|
||||
this.derive = makeDeriveItem(this.index, this.load)
|
||||
this.derived = makeDeriveItem(this.index, this.load)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {EventToItem, ItemsByKey, MakeLoadItemOptions} from "@welshman/store
|
||||
import type {IClient} from "./client.js"
|
||||
import {Stores} from "./stores.js"
|
||||
|
||||
export type RepositoryCollectionOptions<T> = {
|
||||
export type CollectionOptions<T> = {
|
||||
filters: Filter[]
|
||||
eventToItem: EventToItem<T>
|
||||
getKey: (item: T) => string
|
||||
@@ -22,7 +22,7 @@ export type RepositoryCollectionOptions<T> = {
|
||||
*
|
||||
* Like `ClientData`, subclasses depend only on the `IClient` seam.
|
||||
*/
|
||||
export abstract class RepositoryCollection<T> {
|
||||
export abstract class Collection<T> {
|
||||
byKey: Readable<ItemsByKey<T>>
|
||||
all: Readable<T[]>
|
||||
subscribe: Readable<ItemsByKey<T>>["subscribe"]
|
||||
@@ -32,9 +32,6 @@ export abstract class RepositoryCollection<T> {
|
||||
values: () => IterableIterator<T>
|
||||
load: (key: string, ...args: any[]) => Promise<Maybe<T>>
|
||||
forceLoad: (key: string, ...args: any[]) => Promise<Maybe<T>>
|
||||
// Reactive view of a single key that also triggers a load
|
||||
derive: (key?: string, ...args: any[]) => Readable<Maybe<T>>
|
||||
// Reactive view of a single key that does not trigger a load
|
||||
derived: (key?: string, ...args: any[]) => Readable<Maybe<T>>
|
||||
private getByKey: () => ItemsByKey<T>
|
||||
|
||||
@@ -42,7 +39,7 @@ export abstract class RepositoryCollection<T> {
|
||||
|
||||
constructor(
|
||||
protected readonly ctx: IClient,
|
||||
options: RepositoryCollectionOptions<T>,
|
||||
options: CollectionOptions<T>,
|
||||
) {
|
||||
const fetch = (key: string, ...args: any[]) => this.fetch(key, ...args)
|
||||
|
||||
@@ -60,8 +57,7 @@ export abstract class RepositoryCollection<T> {
|
||||
this.values = () => this.getByKey().values()
|
||||
this.load = makeLoadItem(fetch, this.get, options.loadOptions)
|
||||
this.forceLoad = makeForceLoadItem(fetch, this.get)
|
||||
this.derive = makeDeriveItem(this.byKey, this.load)
|
||||
this.derived = makeDeriveItem(this.byKey)
|
||||
this.derived = makeDeriveItem(this.byKey, this.load)
|
||||
}
|
||||
|
||||
// Convenience views of the current user's own item (replaces the old
|
||||
@@ -73,7 +69,7 @@ export abstract class RepositoryCollection<T> {
|
||||
return pubkey ? this.get(pubkey) : undefined
|
||||
}
|
||||
|
||||
deriveForUser = (...args: any[]) => this.derive(this.ctx.user?.pubkey, ...args)
|
||||
deriveForUser = (...args: any[]) => this.derived(this.ctx.user?.pubkey, ...args)
|
||||
|
||||
loadForUser = (...args: any[]) => {
|
||||
const pubkey = this.ctx.user?.pubkey
|
||||
@@ -26,11 +26,11 @@ export class Feeds {
|
||||
case Scope.Self:
|
||||
return [$pubkey]
|
||||
case Scope.Follows:
|
||||
return this.ctx.use(Wot).getFollows($pubkey)
|
||||
return this.ctx.use(Wot).deriveFollows($pubkey).get()
|
||||
case Scope.Network:
|
||||
return this.ctx.use(Wot).getNetwork($pubkey)
|
||||
return this.ctx.use(Wot).deriveNetwork($pubkey).get()
|
||||
case Scope.Followers:
|
||||
return this.ctx.use(Wot).getFollowers($pubkey)
|
||||
return this.ctx.use(Wot).deriveFollowers($pubkey).get()
|
||||
default:
|
||||
return []
|
||||
}
|
||||
@@ -38,11 +38,11 @@ export class Feeds {
|
||||
|
||||
getPubkeysForWOTRange = (min: number, max: number): string[] => {
|
||||
const pubkeys = []
|
||||
const $maxWot = this.ctx.use(Wot).getMaxWot() ?? 0
|
||||
const $maxWot = this.ctx.use(Wot).max.get() ?? 0
|
||||
const thresholdMin = $maxWot * min
|
||||
const thresholdMax = $maxWot * max
|
||||
|
||||
for (const [tpk, score] of this.ctx.use(Wot).getWotGraph().entries()) {
|
||||
for (const [tpk, score] of this.ctx.use(Wot).graph.get().entries()) {
|
||||
if (score >= thresholdMin && score <= thresholdMax) {
|
||||
pubkeys.push(tpk)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
removeFromList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Thunks} from "./thunk.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -17,7 +17,7 @@ import type {IClient} from "./client.js"
|
||||
* Kind-3 follow lists, keyed by pubkey. Loaded via the outbox model (the
|
||||
* author's write relays), so it depends on the relay-list collection.
|
||||
*/
|
||||
export class FollowLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class FollowLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [FOLLOWS]}],
|
||||
|
||||
@@ -65,7 +65,7 @@ export class Handles extends LoadableData<Handle> {
|
||||
this.loadForPubkey(pubkey, relays)
|
||||
|
||||
return deriveDeduplicated(
|
||||
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
|
||||
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
|
||||
([$handlesByNip05, $profile]) => {
|
||||
if (!$profile?.nip05) return undefined
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export * from "./policies.js"
|
||||
export * from "./network.js"
|
||||
export * from "./stores.js"
|
||||
export * from "./clientData.js"
|
||||
export * from "./repositoryCollection.js"
|
||||
export * from "./collection.js"
|
||||
export * from "./user.js"
|
||||
export * from "./router.js"
|
||||
export * from "./relays.js"
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
removeFromList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Router} from "./router.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -20,7 +20,7 @@ import type {IClient} from "./client.js"
|
||||
* outbox model (the author's write relays), so it depends on the relay-list
|
||||
* collection.
|
||||
*/
|
||||
export class MessagingRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class MessagingRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [MESSAGING_RELAYS]}],
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
updateList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, PublishedList} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import type {IClient} from "./client.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Thunks} from "./thunk.js"
|
||||
@@ -20,7 +20,7 @@ import {User} from "./user.js"
|
||||
* Kind-10000 mute lists, keyed by pubkey. Mute lists carry private entries in
|
||||
* encrypted content, so decoding goes through the plaintext cache.
|
||||
*/
|
||||
export class MuteLists extends RepositoryCollection<PublishedList> {
|
||||
export class MuteLists extends Collection<PublishedList> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [MUTES]}],
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
removeFromList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Thunks} from "./thunk.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -17,7 +17,7 @@ import type {IClient} from "./client.js"
|
||||
* NIP-51 pin lists (kind 10001), keyed by pubkey. Loaded via the outbox model
|
||||
* (the author's write relays), so it depends on the relay-list collection.
|
||||
*/
|
||||
export class PinLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class PinLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [PINS]}],
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
PROFILE,
|
||||
} from "@welshman/util"
|
||||
import type {Profile} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Router} from "./router.js"
|
||||
import {Thunks} from "./thunk.js"
|
||||
@@ -19,7 +19,7 @@ import type {IClient} from "./client.js"
|
||||
* Kind-0 profiles, keyed by pubkey. Loaded via the outbox model (the author's
|
||||
* write relays), resolved through the relay-list collection at fetch time.
|
||||
*/
|
||||
export class Profiles extends RepositoryCollection<ReturnType<typeof readProfile>> {
|
||||
export class Profiles extends Collection<ReturnType<typeof readProfile>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [PROFILE]}],
|
||||
@@ -45,7 +45,7 @@ export class Profiles extends RepositoryCollection<ReturnType<typeof readProfile
|
||||
|
||||
deriveDisplay = (pubkey: string | undefined, ...args: any[]) =>
|
||||
pubkey
|
||||
? derived(this.derive(pubkey, ...args), $profile =>
|
||||
? derived(this.derived(pubkey, ...args), $profile =>
|
||||
displayProfile($profile, displayPubkey(pubkey)),
|
||||
)
|
||||
: readable("")
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
makeEvent,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent, PublishedList} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Router, addMinimalFallbacks} from "./router.js"
|
||||
import {Network} from "./network.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -23,7 +23,7 @@ import type {IClient} from "./client.js"
|
||||
* NIP-65 relay lists, keyed by pubkey. This is the routing substrate every other
|
||||
* outbox-model load depends on (see `Network.loadUsingOutbox`).
|
||||
*/
|
||||
export class RelayLists extends RepositoryCollection<PublishedList> {
|
||||
export class RelayLists extends Collection<PublishedList> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [RELAYS]}],
|
||||
|
||||
@@ -82,9 +82,9 @@ export class Searches {
|
||||
onSearch: this.searchProfiles,
|
||||
getValue: (profile: PublishedProfile) => profile.event.pubkey,
|
||||
sortFn: ({score = 1, item}) => {
|
||||
const wotScore = this.ctx.use(Wot).getWotGraph().get(item.event.pubkey) || 0
|
||||
const wotScore = this.ctx.use(Wot).graph.get().get(item.event.pubkey) || 0
|
||||
|
||||
return dec(score) * inc(wotScore / (this.ctx.use(Wot).getMaxWot() || 1))
|
||||
return dec(score) * inc(wotScore / (this.ctx.use(Wot).max.get() || 1))
|
||||
},
|
||||
fuseOptions: {
|
||||
keys: [
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
removeFromList,
|
||||
} from "@welshman/util"
|
||||
import type {TrustedEvent} from "@welshman/util"
|
||||
import {RepositoryCollection} from "./repositoryCollection.js"
|
||||
import {Collection} from "./collection.js"
|
||||
import {Network} from "./network.js"
|
||||
import {Router} from "./router.js"
|
||||
import {User} from "./user.js"
|
||||
@@ -20,7 +20,7 @@ import type {IClient} from "./client.js"
|
||||
* outbox model (the author's write relays), so it depends on the relay-list
|
||||
* collection.
|
||||
*/
|
||||
export class SearchRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
||||
export class SearchRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||
constructor(ctx: IClient) {
|
||||
super(ctx, {
|
||||
filters: [{kinds: [SEARCH_RELAYS]}],
|
||||
|
||||
@@ -2,6 +2,7 @@ import {readable} from "svelte/store"
|
||||
import type {Readable} from "svelte/store"
|
||||
import {on} from "@welshman/lib"
|
||||
import {getTopicTagValues} from "@welshman/util"
|
||||
import type {RepositoryUpdate} from "@welshman/net"
|
||||
import {deriveItems} from "@welshman/store"
|
||||
import type {IClient} from "./client.js"
|
||||
|
||||
@@ -38,7 +39,7 @@ export class Topics {
|
||||
}
|
||||
|
||||
this.byName = readable(topicsByName, set =>
|
||||
on(ctx.repository, "update", ({added}: {added: {tags: string[][]}[]}) => {
|
||||
on(ctx.repository, "update", ({added}: RepositoryUpdate) => {
|
||||
let dirty = false
|
||||
|
||||
for (const event of added) {
|
||||
|
||||
+137
-92
@@ -1,132 +1,177 @@
|
||||
import {derived, writable} from "svelte/store"
|
||||
import type {Readable, Writable} from "svelte/store"
|
||||
import {readable, derived} from "svelte/store"
|
||||
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
|
||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
||||
import {throttled, getter} from "@welshman/store"
|
||||
import type {List} from "@welshman/util"
|
||||
import {withGetter} from "@welshman/store"
|
||||
import type {ReadableWithGetter} from "@welshman/store"
|
||||
import type {IClient} from "./client.js"
|
||||
import {FollowLists} from "./follows.js"
|
||||
import {MuteLists} from "./mutes.js"
|
||||
|
||||
const listPubkeys = (list: List | undefined) => getPubkeyTagValues(getListTags(list))
|
||||
|
||||
/**
|
||||
* Web-of-trust scoring derived from follow and mute lists. The trust graph is
|
||||
* built from the perspective of the client's user (or, with no user, the union
|
||||
* of every known follow list) and updated reactively as lists change.
|
||||
*
|
||||
* Every query is exposed as a reactive store — the aggregate `*ByPubkey`/`graph`/
|
||||
* `max` fields and the parameterized `derive*` methods. All of them are wrapped
|
||||
* in `withGetter`, so a synchronous snapshot is just `<store>.get()` /
|
||||
* `derive*(...).get()`.
|
||||
*/
|
||||
export class Wot {
|
||||
followersByPubkey: Readable<Map<string, Set<string>>>
|
||||
mutersByPubkey: Readable<Map<string, Set<string>>>
|
||||
wotGraph: Writable<Map<string, number>>
|
||||
maxWot: Readable<number | undefined>
|
||||
|
||||
private getFollowersByPubkeyStore: () => Map<string, Set<string>>
|
||||
private getMutersByPubkeyStore: () => Map<string, Set<string>>
|
||||
private getWotGraphStore: () => Map<string, number>
|
||||
private getMaxWotStore: () => number | undefined
|
||||
followersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
|
||||
mutersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
|
||||
graph: ReadableWithGetter<Map<string, number>>
|
||||
max: ReadableWithGetter<number | undefined>
|
||||
|
||||
constructor(readonly ctx: IClient) {
|
||||
const followLists = this.ctx.use(FollowLists)
|
||||
const muteLists = this.ctx.use(MuteLists)
|
||||
this.followersByPubkey = withGetter(
|
||||
readable(new Map<string, Set<string>>(), set =>
|
||||
this.ctx.use(FollowLists).subscribe(
|
||||
throttle(1000, lists => {
|
||||
const $followersByPubkey = new Map<string, Set<string>>()
|
||||
|
||||
this.followersByPubkey = derived(throttled(1000, followLists.all), lists => {
|
||||
const $followersByPubkey = new Map<string, Set<string>>()
|
||||
for (const list of lists.values()) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
set($followersByPubkey)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return $followersByPubkey
|
||||
})
|
||||
this.mutersByPubkey = withGetter(
|
||||
readable(new Map<string, Set<string>>(), set =>
|
||||
this.ctx.use(MuteLists).subscribe(
|
||||
throttle(1000, lists => {
|
||||
const $mutersByPubkey = new Map<string, Set<string>>()
|
||||
|
||||
this.mutersByPubkey = derived(throttled(1000, muteLists.all), lists => {
|
||||
const $mutersByPubkey = new Map<string, Set<string>>()
|
||||
for (const list of lists.values()) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
|
||||
for (const list of lists) {
|
||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
||||
}
|
||||
}
|
||||
set($mutersByPubkey)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return $mutersByPubkey
|
||||
})
|
||||
this.graph = withGetter(
|
||||
readable(new Map<string, number>(), set => {
|
||||
const rebuild = throttle(1000, () => {
|
||||
const followLists = this.ctx.use(FollowLists)
|
||||
const muteLists = this.ctx.use(MuteLists)
|
||||
const $pubkey = this.ctx.user?.pubkey
|
||||
const $graph = new Map<string, number>()
|
||||
const $follows = $pubkey
|
||||
? listPubkeys(followLists.get($pubkey))
|
||||
: Array.from(followLists.keys())
|
||||
|
||||
this.wotGraph = writable(new Map<string, number>())
|
||||
for (const follow of $follows) {
|
||||
for (const pubkey of listPubkeys(followLists.get(follow))) {
|
||||
$graph.set(pubkey, inc($graph.get(pubkey)))
|
||||
}
|
||||
|
||||
this.maxWot = derived(this.wotGraph, $g => max(Array.from($g.values())))
|
||||
for (const pubkey of listPubkeys(muteLists.get(follow))) {
|
||||
$graph.set(pubkey, dec($graph.get(pubkey)))
|
||||
}
|
||||
}
|
||||
|
||||
this.getFollowersByPubkeyStore = getter(this.followersByPubkey)
|
||||
this.getMutersByPubkeyStore = getter(this.mutersByPubkey)
|
||||
this.getWotGraphStore = getter(this.wotGraph)
|
||||
this.getMaxWotStore = getter(this.maxWot)
|
||||
set($graph)
|
||||
})
|
||||
|
||||
followLists.subscribe(this.buildGraph)
|
||||
muteLists.subscribe(this.buildGraph)
|
||||
const unsubscribers = [
|
||||
this.ctx.use(FollowLists).subscribe(rebuild),
|
||||
this.ctx.use(MuteLists).subscribe(rebuild),
|
||||
]
|
||||
|
||||
return () => unsubscribers.forEach(unsubscribe => unsubscribe())
|
||||
}),
|
||||
)
|
||||
|
||||
this.max = withGetter(derived(this.graph, $g => max(Array.from($g.values()))))
|
||||
}
|
||||
|
||||
getFollows = (pubkey: string) =>
|
||||
getPubkeyTagValues(getListTags(this.ctx.use(FollowLists).get(pubkey)))
|
||||
deriveFollows = (pubkey: string) =>
|
||||
withGetter(derived(this.ctx.use(FollowLists), $lists => listPubkeys($lists.get(pubkey))))
|
||||
|
||||
getMutes = (pubkey: string) =>
|
||||
getPubkeyTagValues(getListTags(this.ctx.use(MuteLists).get(pubkey)))
|
||||
deriveMutes = (pubkey: string) =>
|
||||
withGetter(derived(this.ctx.use(MuteLists), $lists => listPubkeys($lists.get(pubkey))))
|
||||
|
||||
getNetwork = (pubkey: string) => {
|
||||
const pubkeys = new Set(this.getFollows(pubkey))
|
||||
const network = new Set<string>()
|
||||
deriveNetwork = (pubkey: string) =>
|
||||
withGetter(
|
||||
derived(this.ctx.use(FollowLists), $lists => {
|
||||
const pubkeys = new Set(listPubkeys($lists.get(pubkey)))
|
||||
const network = new Set<string>()
|
||||
|
||||
for (const follow of pubkeys) {
|
||||
for (const tpk of this.getFollows(follow)) {
|
||||
if (!pubkeys.has(tpk)) {
|
||||
network.add(tpk)
|
||||
for (const follow of pubkeys) {
|
||||
for (const tpk of listPubkeys($lists.get(follow))) {
|
||||
if (!pubkeys.has(tpk)) {
|
||||
network.add(tpk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(network)
|
||||
}
|
||||
return Array.from(network)
|
||||
}),
|
||||
)
|
||||
|
||||
getFollowersByPubkey = () => this.getFollowersByPubkeyStore()
|
||||
deriveFollowers = (pubkey: string) =>
|
||||
withGetter(derived(this.followersByPubkey, $followers => Array.from($followers.get(pubkey) || [])))
|
||||
|
||||
getMutersByPubkey = () => this.getMutersByPubkeyStore()
|
||||
deriveMuters = (pubkey: string) =>
|
||||
withGetter(derived(this.mutersByPubkey, $muters => Array.from($muters.get(pubkey) || [])))
|
||||
|
||||
getFollowers = (pubkey: string) => Array.from(this.getFollowersByPubkey().get(pubkey) || [])
|
||||
deriveFollowsWhoFollow = (pubkey: string, target: string) =>
|
||||
withGetter(
|
||||
derived(this.ctx.use(FollowLists), $lists =>
|
||||
listPubkeys($lists.get(pubkey)).filter(other =>
|
||||
listPubkeys($lists.get(other)).includes(target),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
getMuters = (pubkey: string) => Array.from(this.getMutersByPubkey().get(pubkey) || [])
|
||||
deriveFollowsWhoMute = (pubkey: string, target: string) =>
|
||||
withGetter(
|
||||
derived([this.ctx.use(FollowLists), this.ctx.use(MuteLists)], ([$follows, $mutes]) =>
|
||||
listPubkeys($follows.get(pubkey)).filter(other =>
|
||||
listPubkeys($mutes.get(other)).includes(target),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
getFollowsWhoFollow = (pubkey: string, target: string) =>
|
||||
this.getFollows(pubkey).filter(other => this.getFollows(other).includes(target))
|
||||
deriveWotScore = (pubkey: string, target: string) =>
|
||||
withGetter(
|
||||
derived(
|
||||
[
|
||||
this.ctx.use(FollowLists),
|
||||
this.ctx.use(MuteLists),
|
||||
this.followersByPubkey,
|
||||
this.mutersByPubkey,
|
||||
],
|
||||
([$follows, $mutes, $followers, $muters]) => {
|
||||
let follows: string[]
|
||||
let mutes: string[]
|
||||
|
||||
getFollowsWhoMute = (pubkey: string, target: string) =>
|
||||
this.getFollows(pubkey).filter(other => this.getMutes(other).includes(target))
|
||||
if (pubkey) {
|
||||
const theirFollows = listPubkeys($follows.get(pubkey))
|
||||
|
||||
getWotGraph = () => this.getWotGraphStore()
|
||||
follows = theirFollows.filter(other => listPubkeys($follows.get(other)).includes(target))
|
||||
mutes = theirFollows.filter(other => listPubkeys($mutes.get(other)).includes(target))
|
||||
} else {
|
||||
follows = Array.from($followers.get(target) || [])
|
||||
mutes = Array.from($muters.get(target) || [])
|
||||
}
|
||||
|
||||
getMaxWot = () => this.getMaxWotStore()
|
||||
|
||||
buildGraph = throttle(1000, () => {
|
||||
const $pubkey = this.ctx.user?.pubkey
|
||||
const $graph = new Map<string, number>()
|
||||
const $follows = $pubkey
|
||||
? this.getFollows($pubkey)
|
||||
: Array.from(this.ctx.use(FollowLists).keys())
|
||||
|
||||
for (const follow of $follows) {
|
||||
for (const pubkey of this.getFollows(follow)) {
|
||||
$graph.set(pubkey, inc($graph.get(pubkey)))
|
||||
}
|
||||
|
||||
for (const pubkey of this.getMutes(follow)) {
|
||||
$graph.set(pubkey, dec($graph.get(pubkey)))
|
||||
}
|
||||
}
|
||||
|
||||
this.wotGraph.set($graph)
|
||||
})
|
||||
|
||||
getWotScore = (pubkey: string, target: string) => {
|
||||
const follows = pubkey ? this.getFollowsWhoFollow(pubkey, target) : this.getFollowers(target)
|
||||
const mutes = pubkey ? this.getFollowsWhoMute(pubkey, target) : this.getMuters(target)
|
||||
|
||||
return follows.length - mutes.length
|
||||
}
|
||||
return follows.length - mutes.length
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export class Zappers extends LoadableData<Zapper> {
|
||||
this.loadForPubkey(pubkey, relays)
|
||||
|
||||
return deriveDeduplicated(
|
||||
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
|
||||
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
|
||||
([$zappersByLnurl, $profile]) =>
|
||||
$profile?.lnurl ? $zappersByLnurl.get($profile.lnurl) : undefined,
|
||||
)
|
||||
@@ -116,7 +116,7 @@ export class Zappers extends LoadableData<Zapper> {
|
||||
|
||||
const stores: Readable<any>[] = [
|
||||
this.index,
|
||||
...splits.map(split => profiles.derive(split.pubkey)),
|
||||
...splits.map(split => profiles.derived(split.pubkey)),
|
||||
]
|
||||
|
||||
return deriveDeduplicatedByValue(stores, (values: any[]) => {
|
||||
|
||||
Reference in New Issue
Block a user