Clean up wot
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
|||||||
removeFromList,
|
removeFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import {Router} from "./router.js"
|
import {Router} from "./router.js"
|
||||||
import {User} from "./user.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
|
* so it depends on the relay-list collection. Feeds `RelayStats.getQuality` so
|
||||||
* blocked relays are never selected.
|
* blocked relays are never selected.
|
||||||
*/
|
*/
|
||||||
export class BlockedRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
export class BlockedRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||||
constructor(ctx: IClient) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [BLOCKED_RELAYS]}],
|
filters: [{kinds: [BLOCKED_RELAYS]}],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util"
|
import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import type {IClient} from "./client.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
|
* 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.
|
* 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) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [BLOSSOM_SERVERS]}],
|
filters: [{kinds: [BLOSSOM_SERVERS]}],
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export class ClientData<T> {
|
|||||||
protected index = writable(new Map<string, T>())
|
protected index = writable(new Map<string, T>())
|
||||||
protected getIndex = getter(this.index)
|
protected getIndex = getter(this.index)
|
||||||
protected itemSubscribers: ((key: string, value: Maybe<T>) => void)[] = []
|
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) {
|
constructor(protected readonly ctx: IClient) {
|
||||||
this.derive = makeDeriveItem(this.index)
|
this.derived = makeDeriveItem(this.index)
|
||||||
}
|
}
|
||||||
|
|
||||||
subscribe = this.index.subscribe
|
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
|
* 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`).
|
* from it (with per-key caching and backoff via `makeLoadItem`).
|
||||||
*/
|
*/
|
||||||
export abstract class LoadableData<T> extends ClientData<T> {
|
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.load = makeLoadItem(fetch, this.get, options)
|
||||||
this.forceLoad = makeForceLoadItem(fetch, this.get)
|
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 type {IClient} from "./client.js"
|
||||||
import {Stores} from "./stores.js"
|
import {Stores} from "./stores.js"
|
||||||
|
|
||||||
export type RepositoryCollectionOptions<T> = {
|
export type CollectionOptions<T> = {
|
||||||
filters: Filter[]
|
filters: Filter[]
|
||||||
eventToItem: EventToItem<T>
|
eventToItem: EventToItem<T>
|
||||||
getKey: (item: T) => string
|
getKey: (item: T) => string
|
||||||
@@ -22,7 +22,7 @@ export type RepositoryCollectionOptions<T> = {
|
|||||||
*
|
*
|
||||||
* Like `ClientData`, subclasses depend only on the `IClient` seam.
|
* Like `ClientData`, subclasses depend only on the `IClient` seam.
|
||||||
*/
|
*/
|
||||||
export abstract class RepositoryCollection<T> {
|
export abstract class Collection<T> {
|
||||||
byKey: Readable<ItemsByKey<T>>
|
byKey: Readable<ItemsByKey<T>>
|
||||||
all: Readable<T[]>
|
all: Readable<T[]>
|
||||||
subscribe: Readable<ItemsByKey<T>>["subscribe"]
|
subscribe: Readable<ItemsByKey<T>>["subscribe"]
|
||||||
@@ -32,9 +32,6 @@ export abstract class RepositoryCollection<T> {
|
|||||||
values: () => IterableIterator<T>
|
values: () => IterableIterator<T>
|
||||||
load: (key: string, ...args: any[]) => Promise<Maybe<T>>
|
load: (key: string, ...args: any[]) => Promise<Maybe<T>>
|
||||||
forceLoad: (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>>
|
derived: (key?: string, ...args: any[]) => Readable<Maybe<T>>
|
||||||
private getByKey: () => ItemsByKey<T>
|
private getByKey: () => ItemsByKey<T>
|
||||||
|
|
||||||
@@ -42,7 +39,7 @@ export abstract class RepositoryCollection<T> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly ctx: IClient,
|
protected readonly ctx: IClient,
|
||||||
options: RepositoryCollectionOptions<T>,
|
options: CollectionOptions<T>,
|
||||||
) {
|
) {
|
||||||
const fetch = (key: string, ...args: any[]) => this.fetch(key, ...args)
|
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.values = () => this.getByKey().values()
|
||||||
this.load = makeLoadItem(fetch, this.get, options.loadOptions)
|
this.load = makeLoadItem(fetch, this.get, options.loadOptions)
|
||||||
this.forceLoad = makeForceLoadItem(fetch, this.get)
|
this.forceLoad = makeForceLoadItem(fetch, this.get)
|
||||||
this.derive = makeDeriveItem(this.byKey, this.load)
|
this.derived = makeDeriveItem(this.byKey, this.load)
|
||||||
this.derived = makeDeriveItem(this.byKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convenience views of the current user's own item (replaces the old
|
// 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
|
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[]) => {
|
loadForUser = (...args: any[]) => {
|
||||||
const pubkey = this.ctx.user?.pubkey
|
const pubkey = this.ctx.user?.pubkey
|
||||||
@@ -26,11 +26,11 @@ export class Feeds {
|
|||||||
case Scope.Self:
|
case Scope.Self:
|
||||||
return [$pubkey]
|
return [$pubkey]
|
||||||
case Scope.Follows:
|
case Scope.Follows:
|
||||||
return this.ctx.use(Wot).getFollows($pubkey)
|
return this.ctx.use(Wot).deriveFollows($pubkey).get()
|
||||||
case Scope.Network:
|
case Scope.Network:
|
||||||
return this.ctx.use(Wot).getNetwork($pubkey)
|
return this.ctx.use(Wot).deriveNetwork($pubkey).get()
|
||||||
case Scope.Followers:
|
case Scope.Followers:
|
||||||
return this.ctx.use(Wot).getFollowers($pubkey)
|
return this.ctx.use(Wot).deriveFollowers($pubkey).get()
|
||||||
default:
|
default:
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
@@ -38,11 +38,11 @@ export class Feeds {
|
|||||||
|
|
||||||
getPubkeysForWOTRange = (min: number, max: number): string[] => {
|
getPubkeysForWOTRange = (min: number, max: number): string[] => {
|
||||||
const pubkeys = []
|
const pubkeys = []
|
||||||
const $maxWot = this.ctx.use(Wot).getMaxWot() ?? 0
|
const $maxWot = this.ctx.use(Wot).max.get() ?? 0
|
||||||
const thresholdMin = $maxWot * min
|
const thresholdMin = $maxWot * min
|
||||||
const thresholdMax = $maxWot * max
|
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) {
|
if (score >= thresholdMin && score <= thresholdMax) {
|
||||||
pubkeys.push(tpk)
|
pubkeys.push(tpk)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
removeFromList,
|
removeFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import {Thunks} from "./thunk.js"
|
import {Thunks} from "./thunk.js"
|
||||||
import {User} from "./user.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
|
* 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.
|
* 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) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [FOLLOWS]}],
|
filters: [{kinds: [FOLLOWS]}],
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export class Handles extends LoadableData<Handle> {
|
|||||||
this.loadForPubkey(pubkey, relays)
|
this.loadForPubkey(pubkey, relays)
|
||||||
|
|
||||||
return deriveDeduplicated(
|
return deriveDeduplicated(
|
||||||
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
|
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
|
||||||
([$handlesByNip05, $profile]) => {
|
([$handlesByNip05, $profile]) => {
|
||||||
if (!$profile?.nip05) return undefined
|
if (!$profile?.nip05) return undefined
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export * from "./policies.js"
|
|||||||
export * from "./network.js"
|
export * from "./network.js"
|
||||||
export * from "./stores.js"
|
export * from "./stores.js"
|
||||||
export * from "./clientData.js"
|
export * from "./clientData.js"
|
||||||
export * from "./repositoryCollection.js"
|
export * from "./collection.js"
|
||||||
export * from "./user.js"
|
export * from "./user.js"
|
||||||
export * from "./router.js"
|
export * from "./router.js"
|
||||||
export * from "./relays.js"
|
export * from "./relays.js"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
removeFromList,
|
removeFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import {Router} from "./router.js"
|
import {Router} from "./router.js"
|
||||||
import {User} from "./user.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
|
* outbox model (the author's write relays), so it depends on the relay-list
|
||||||
* collection.
|
* collection.
|
||||||
*/
|
*/
|
||||||
export class MessagingRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
export class MessagingRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||||
constructor(ctx: IClient) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [MESSAGING_RELAYS]}],
|
filters: [{kinds: [MESSAGING_RELAYS]}],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
updateList,
|
updateList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, PublishedList} 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 type {IClient} from "./client.js"
|
||||||
import {Network} from "./network.js"
|
import {Network} from "./network.js"
|
||||||
import {Thunks} from "./thunk.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
|
* Kind-10000 mute lists, keyed by pubkey. Mute lists carry private entries in
|
||||||
* encrypted content, so decoding goes through the plaintext cache.
|
* encrypted content, so decoding goes through the plaintext cache.
|
||||||
*/
|
*/
|
||||||
export class MuteLists extends RepositoryCollection<PublishedList> {
|
export class MuteLists extends Collection<PublishedList> {
|
||||||
constructor(ctx: IClient) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [MUTES]}],
|
filters: [{kinds: [MUTES]}],
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
removeFromList,
|
removeFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import {Thunks} from "./thunk.js"
|
import {Thunks} from "./thunk.js"
|
||||||
import {User} from "./user.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
|
* 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.
|
* (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) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [PINS]}],
|
filters: [{kinds: [PINS]}],
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
PROFILE,
|
PROFILE,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {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 {Network} from "./network.js"
|
||||||
import {Router} from "./router.js"
|
import {Router} from "./router.js"
|
||||||
import {Thunks} from "./thunk.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
|
* 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.
|
* 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) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [PROFILE]}],
|
filters: [{kinds: [PROFILE]}],
|
||||||
@@ -45,7 +45,7 @@ export class Profiles extends RepositoryCollection<ReturnType<typeof readProfile
|
|||||||
|
|
||||||
deriveDisplay = (pubkey: string | undefined, ...args: any[]) =>
|
deriveDisplay = (pubkey: string | undefined, ...args: any[]) =>
|
||||||
pubkey
|
pubkey
|
||||||
? derived(this.derive(pubkey, ...args), $profile =>
|
? derived(this.derived(pubkey, ...args), $profile =>
|
||||||
displayProfile($profile, displayPubkey(pubkey)),
|
displayProfile($profile, displayPubkey(pubkey)),
|
||||||
)
|
)
|
||||||
: readable("")
|
: readable("")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
makeEvent,
|
makeEvent,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent, PublishedList} 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 {Router, addMinimalFallbacks} from "./router.js"
|
||||||
import {Network} from "./network.js"
|
import {Network} from "./network.js"
|
||||||
import {User} from "./user.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
|
* NIP-65 relay lists, keyed by pubkey. This is the routing substrate every other
|
||||||
* outbox-model load depends on (see `Network.loadUsingOutbox`).
|
* outbox-model load depends on (see `Network.loadUsingOutbox`).
|
||||||
*/
|
*/
|
||||||
export class RelayLists extends RepositoryCollection<PublishedList> {
|
export class RelayLists extends Collection<PublishedList> {
|
||||||
constructor(ctx: IClient) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [RELAYS]}],
|
filters: [{kinds: [RELAYS]}],
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ export class Searches {
|
|||||||
onSearch: this.searchProfiles,
|
onSearch: this.searchProfiles,
|
||||||
getValue: (profile: PublishedProfile) => profile.event.pubkey,
|
getValue: (profile: PublishedProfile) => profile.event.pubkey,
|
||||||
sortFn: ({score = 1, item}) => {
|
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: {
|
fuseOptions: {
|
||||||
keys: [
|
keys: [
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
removeFromList,
|
removeFromList,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {TrustedEvent} 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 {Network} from "./network.js"
|
||||||
import {Router} from "./router.js"
|
import {Router} from "./router.js"
|
||||||
import {User} from "./user.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
|
* outbox model (the author's write relays), so it depends on the relay-list
|
||||||
* collection.
|
* collection.
|
||||||
*/
|
*/
|
||||||
export class SearchRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
|
export class SearchRelayLists extends Collection<ReturnType<typeof readList>> {
|
||||||
constructor(ctx: IClient) {
|
constructor(ctx: IClient) {
|
||||||
super(ctx, {
|
super(ctx, {
|
||||||
filters: [{kinds: [SEARCH_RELAYS]}],
|
filters: [{kinds: [SEARCH_RELAYS]}],
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {readable} from "svelte/store"
|
|||||||
import type {Readable} from "svelte/store"
|
import type {Readable} from "svelte/store"
|
||||||
import {on} from "@welshman/lib"
|
import {on} from "@welshman/lib"
|
||||||
import {getTopicTagValues} from "@welshman/util"
|
import {getTopicTagValues} from "@welshman/util"
|
||||||
|
import type {RepositoryUpdate} from "@welshman/net"
|
||||||
import {deriveItems} from "@welshman/store"
|
import {deriveItems} from "@welshman/store"
|
||||||
import type {IClient} from "./client.js"
|
import type {IClient} from "./client.js"
|
||||||
|
|
||||||
@@ -38,7 +39,7 @@ export class Topics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.byName = readable(topicsByName, set =>
|
this.byName = readable(topicsByName, set =>
|
||||||
on(ctx.repository, "update", ({added}: {added: {tags: string[][]}[]}) => {
|
on(ctx.repository, "update", ({added}: RepositoryUpdate) => {
|
||||||
let dirty = false
|
let dirty = false
|
||||||
|
|
||||||
for (const event of added) {
|
for (const event of added) {
|
||||||
|
|||||||
+137
-92
@@ -1,132 +1,177 @@
|
|||||||
import {derived, writable} from "svelte/store"
|
import {readable, derived} from "svelte/store"
|
||||||
import type {Readable, Writable} from "svelte/store"
|
|
||||||
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
|
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
|
||||||
import {getListTags, getPubkeyTagValues} from "@welshman/util"
|
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 type {IClient} from "./client.js"
|
||||||
import {FollowLists} from "./follows.js"
|
import {FollowLists} from "./follows.js"
|
||||||
import {MuteLists} from "./mutes.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
|
* 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
|
* 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.
|
* 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 {
|
export class Wot {
|
||||||
followersByPubkey: Readable<Map<string, Set<string>>>
|
followersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
|
||||||
mutersByPubkey: Readable<Map<string, Set<string>>>
|
mutersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
|
||||||
wotGraph: Writable<Map<string, number>>
|
graph: ReadableWithGetter<Map<string, number>>
|
||||||
maxWot: Readable<number | undefined>
|
max: ReadableWithGetter<number | undefined>
|
||||||
|
|
||||||
private getFollowersByPubkeyStore: () => Map<string, Set<string>>
|
|
||||||
private getMutersByPubkeyStore: () => Map<string, Set<string>>
|
|
||||||
private getWotGraphStore: () => Map<string, number>
|
|
||||||
private getMaxWotStore: () => number | undefined
|
|
||||||
|
|
||||||
constructor(readonly ctx: IClient) {
|
constructor(readonly ctx: IClient) {
|
||||||
const followLists = this.ctx.use(FollowLists)
|
this.followersByPubkey = withGetter(
|
||||||
const muteLists = this.ctx.use(MuteLists)
|
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 => {
|
for (const list of lists.values()) {
|
||||||
const $followersByPubkey = new Map<string, Set<string>>()
|
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||||
|
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const list of lists) {
|
set($followersByPubkey)
|
||||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
}),
|
||||||
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
|
),
|
||||||
}
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
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 => {
|
for (const list of lists.values()) {
|
||||||
const $mutersByPubkey = new Map<string, Set<string>>()
|
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
||||||
|
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const list of lists) {
|
set($mutersByPubkey)
|
||||||
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
|
}),
|
||||||
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
|
),
|
||||||
}
|
),
|
||||||
}
|
)
|
||||||
|
|
||||||
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)
|
set($graph)
|
||||||
this.getMutersByPubkeyStore = getter(this.mutersByPubkey)
|
})
|
||||||
this.getWotGraphStore = getter(this.wotGraph)
|
|
||||||
this.getMaxWotStore = getter(this.maxWot)
|
|
||||||
|
|
||||||
followLists.subscribe(this.buildGraph)
|
const unsubscribers = [
|
||||||
muteLists.subscribe(this.buildGraph)
|
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) =>
|
deriveFollows = (pubkey: string) =>
|
||||||
getPubkeyTagValues(getListTags(this.ctx.use(FollowLists).get(pubkey)))
|
withGetter(derived(this.ctx.use(FollowLists), $lists => listPubkeys($lists.get(pubkey))))
|
||||||
|
|
||||||
getMutes = (pubkey: string) =>
|
deriveMutes = (pubkey: string) =>
|
||||||
getPubkeyTagValues(getListTags(this.ctx.use(MuteLists).get(pubkey)))
|
withGetter(derived(this.ctx.use(MuteLists), $lists => listPubkeys($lists.get(pubkey))))
|
||||||
|
|
||||||
getNetwork = (pubkey: string) => {
|
deriveNetwork = (pubkey: string) =>
|
||||||
const pubkeys = new Set(this.getFollows(pubkey))
|
withGetter(
|
||||||
const network = new Set<string>()
|
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 follow of pubkeys) {
|
||||||
for (const tpk of this.getFollows(follow)) {
|
for (const tpk of listPubkeys($lists.get(follow))) {
|
||||||
if (!pubkeys.has(tpk)) {
|
if (!pubkeys.has(tpk)) {
|
||||||
network.add(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) =>
|
deriveWotScore = (pubkey: string, target: string) =>
|
||||||
this.getFollows(pubkey).filter(other => this.getFollows(other).includes(target))
|
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) =>
|
if (pubkey) {
|
||||||
this.getFollows(pubkey).filter(other => this.getMutes(other).includes(target))
|
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()
|
return follows.length - mutes.length
|
||||||
|
},
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export class Zappers extends LoadableData<Zapper> {
|
|||||||
this.loadForPubkey(pubkey, relays)
|
this.loadForPubkey(pubkey, relays)
|
||||||
|
|
||||||
return deriveDeduplicated(
|
return deriveDeduplicated(
|
||||||
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
|
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
|
||||||
([$zappersByLnurl, $profile]) =>
|
([$zappersByLnurl, $profile]) =>
|
||||||
$profile?.lnurl ? $zappersByLnurl.get($profile.lnurl) : undefined,
|
$profile?.lnurl ? $zappersByLnurl.get($profile.lnurl) : undefined,
|
||||||
)
|
)
|
||||||
@@ -116,7 +116,7 @@ export class Zappers extends LoadableData<Zapper> {
|
|||||||
|
|
||||||
const stores: Readable<any>[] = [
|
const stores: Readable<any>[] = [
|
||||||
this.index,
|
this.index,
|
||||||
...splits.map(split => profiles.derive(split.pubkey)),
|
...splits.map(split => profiles.derived(split.pubkey)),
|
||||||
]
|
]
|
||||||
|
|
||||||
return deriveDeduplicatedByValue(stores, (values: any[]) => {
|
return deriveDeduplicatedByValue(stores, (values: any[]) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user