remove group related utilities
This commit is contained in:
+110
-107
@@ -1,24 +1,53 @@
|
|||||||
import {
|
import {
|
||||||
intersection, first, switcher, throttleWithValue, clamp, last, splitAt, identity, sortBy, uniq, shuffle,
|
intersection,
|
||||||
pushToMapKey, now, assoc, ctx, sample,
|
first,
|
||||||
} from '@welshman/lib'
|
switcher,
|
||||||
|
throttleWithValue,
|
||||||
|
clamp,
|
||||||
|
last,
|
||||||
|
splitAt,
|
||||||
|
identity,
|
||||||
|
sortBy,
|
||||||
|
uniq,
|
||||||
|
shuffle,
|
||||||
|
pushToMapKey,
|
||||||
|
now,
|
||||||
|
assoc,
|
||||||
|
ctx,
|
||||||
|
sample,
|
||||||
|
} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
Tags, getFilterId, unionFilters, isShareableRelayUrl, isCommunityAddress, isGroupAddress, isContextAddress,
|
Tags,
|
||||||
PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS, LOCAL_RELAY_URL, WRAP,
|
getFilterId,
|
||||||
} from '@welshman/util'
|
unionFilters,
|
||||||
import type {TrustedEvent, Filter} from '@welshman/util'
|
isShareableRelayUrl,
|
||||||
import {ConnectionStatus, AuthStatus} from '@welshman/net'
|
isContextAddress,
|
||||||
import type {RelaysAndFilters} from '@welshman/net'
|
PROFILE,
|
||||||
import {pubkey} from './session'
|
RELAYS,
|
||||||
import {relaySelectionsByPubkey, inboxRelaySelectionsByPubkey, getReadRelayUrls, getWriteRelayUrls, getRelayUrls} from './relaySelections'
|
INBOX_RELAYS,
|
||||||
import {relays, relaysByUrl} from './relays'
|
FOLLOWS,
|
||||||
|
LOCAL_RELAY_URL,
|
||||||
|
WRAP,
|
||||||
|
} from "@welshman/util"
|
||||||
|
import type {TrustedEvent, Filter} from "@welshman/util"
|
||||||
|
import {ConnectionStatus, AuthStatus} from "@welshman/net"
|
||||||
|
import type {RelaysAndFilters} from "@welshman/net"
|
||||||
|
import {pubkey} from "./session"
|
||||||
|
import {
|
||||||
|
relaySelectionsByPubkey,
|
||||||
|
inboxRelaySelectionsByPubkey,
|
||||||
|
getReadRelayUrls,
|
||||||
|
getWriteRelayUrls,
|
||||||
|
getRelayUrls,
|
||||||
|
} from "./relaySelections"
|
||||||
|
import {relays, relaysByUrl} from "./relays"
|
||||||
|
|
||||||
export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS]
|
export const INDEXED_KINDS = [PROFILE, RELAYS, INBOX_RELAYS, FOLLOWS]
|
||||||
|
|
||||||
export enum RelayMode {
|
export enum RelayMode {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
Write = "write",
|
Write = "write",
|
||||||
Inbox = "inbox"
|
Inbox = "inbox",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RouterOptions = {
|
export type RouterOptions = {
|
||||||
@@ -28,20 +57,6 @@ export type RouterOptions = {
|
|||||||
*/
|
*/
|
||||||
getUserPubkey?: () => string | null
|
getUserPubkey?: () => string | null
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves group relays for the specified community.
|
|
||||||
* @param address - The address to retrieve group relays for.
|
|
||||||
* @returns An array of group relay URLs as strings.
|
|
||||||
*/
|
|
||||||
getGroupRelays?: (address: string) => string[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves relays for the specified community.
|
|
||||||
* @param address - The address to retrieve community relays for.
|
|
||||||
* @returns An array of community relay URLs as strings.
|
|
||||||
*/
|
|
||||||
getCommunityRelays?: (address: string) => string[]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves relays for the specified public key and mode.
|
* Retrieves relays for the specified public key and mode.
|
||||||
* @param pubkey - The public key to retrieve relays for.
|
* @param pubkey - The public key to retrieve relays for.
|
||||||
@@ -107,7 +122,7 @@ export type FallbackPolicy = (count: number, limit: number) => number
|
|||||||
|
|
||||||
export const addNoFallbacks = (count: number, redundancy: number) => 0
|
export const addNoFallbacks = (count: number, redundancy: number) => 0
|
||||||
|
|
||||||
export const addMinimalFallbacks = (count: number, redundancy: number) => count > 0 ? 0 : 1
|
export const addMinimalFallbacks = (count: number, redundancy: number) => (count > 0 ? 0 : 1)
|
||||||
|
|
||||||
export const addMaximalFallbacks = (count: number, redundancy: number) => redundancy - count
|
export const addMaximalFallbacks = (count: number, redundancy: number) => redundancy - count
|
||||||
|
|
||||||
@@ -125,13 +140,6 @@ export class Router {
|
|||||||
getUserSelections = (mode?: RelayMode) =>
|
getUserSelections = (mode?: RelayMode) =>
|
||||||
this.getPubkeySelections([this.options.getUserPubkey?.()].filter(identity) as string[], mode)
|
this.getPubkeySelections([this.options.getUserPubkey?.()].filter(identity) as string[], mode)
|
||||||
|
|
||||||
getContextSelections = (tags: Tags) => {
|
|
||||||
return [
|
|
||||||
...tags.communities().mapTo(t => this.selection(t.value(), this.options.getCommunityRelays?.(t.value()) || [])).valueOf(),
|
|
||||||
...tags.groups().mapTo(t => this.selection(t.value(), this.options.getGroupRelays?.(t.value()) || [])).valueOf(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utilities for creating ValueRelays
|
// Utilities for creating ValueRelays
|
||||||
|
|
||||||
selection = (value: string, relays: Iterable<string>) => ({value, relays: Array.from(relays)})
|
selection = (value: string, relays: Iterable<string>) => ({value, relays: Array.from(relays)})
|
||||||
@@ -147,8 +155,10 @@ export class Router {
|
|||||||
relaySelectionsFromMap = (valuesByRelay: ValuesByRelay) =>
|
relaySelectionsFromMap = (valuesByRelay: ValuesByRelay) =>
|
||||||
sortBy(
|
sortBy(
|
||||||
({values}) => -values.length,
|
({values}) => -values.length,
|
||||||
Array.from(valuesByRelay)
|
Array.from(valuesByRelay).map(([relay, values]: [string, string[]]) => ({
|
||||||
.map(([relay, values]: [string, string[]]) => ({relay, values: uniq(values)}))
|
relay,
|
||||||
|
values: uniq(values),
|
||||||
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
scoreRelaySelection = ({values, relay}: RelayValues) =>
|
scoreRelaySelection = ({values, relay}: RelayValues) =>
|
||||||
@@ -172,8 +182,7 @@ export class Router {
|
|||||||
merge = (scenarios: RouterScenario[]) =>
|
merge = (scenarios: RouterScenario[]) =>
|
||||||
this.scenario(scenarios.flatMap((scenario: RouterScenario) => scenario.selections))
|
this.scenario(scenarios.flatMap((scenario: RouterScenario) => scenario.selections))
|
||||||
|
|
||||||
product = (values: string[], relays: string[]) =>
|
product = (values: string[], relays: string[]) => this.scenario(this.selections(values, relays))
|
||||||
this.scenario(this.selections(values, relays))
|
|
||||||
|
|
||||||
fromRelays = (relays: string[]) => this.scenario([this.selection("", relays)])
|
fromRelays = (relays: string[]) => this.scenario([this.selection("", relays)])
|
||||||
|
|
||||||
@@ -200,28 +209,22 @@ export class Router {
|
|||||||
]).policy(addMinimalFallbacks)
|
]).policy(addMinimalFallbacks)
|
||||||
|
|
||||||
Event = (event: TrustedEvent) =>
|
Event = (event: TrustedEvent) =>
|
||||||
this.scenario(this.forceValue(event.id, [
|
this.scenario(
|
||||||
this.getPubkeySelection(event.pubkey, RelayMode.Write),
|
this.forceValue(event.id, [this.getPubkeySelection(event.pubkey, RelayMode.Write)])
|
||||||
...this.getContextSelections(Tags.fromEvent(event).context()),
|
)
|
||||||
]))
|
|
||||||
|
|
||||||
EventChildren = (event: TrustedEvent) =>
|
EventChildren = (event: TrustedEvent) =>
|
||||||
this.scenario(this.forceValue(event.id, [
|
this.scenario(
|
||||||
this.getPubkeySelection(event.pubkey, RelayMode.Read),
|
this.forceValue(event.id, [this.getPubkeySelection(event.pubkey, RelayMode.Read)])
|
||||||
...this.getContextSelections(Tags.fromEvent(event).context()),
|
)
|
||||||
]))
|
|
||||||
|
|
||||||
EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => {
|
EventAncestors = (event: TrustedEvent, type: "mentions" | "replies" | "roots") => {
|
||||||
const tags = Tags.fromEvent(event)
|
const tags = Tags.fromEvent(event)
|
||||||
const ancestors = tags.ancestors()[type]
|
const ancestors = tags.ancestors()[type]
|
||||||
const pubkeys = tags.values("p").valueOf()
|
const pubkeys = tags.values("p").valueOf()
|
||||||
const communities = tags.communities().values().valueOf()
|
|
||||||
const groups = tags.groups().values().valueOf()
|
|
||||||
const relays = uniq([
|
const relays = uniq([
|
||||||
...this.options.getPubkeyRelays?.(event.pubkey, RelayMode.Read) || [],
|
...(this.options.getPubkeyRelays?.(event.pubkey, RelayMode.Read) || []),
|
||||||
...pubkeys.flatMap((k: string) => this.options.getPubkeyRelays?.(k, RelayMode.Write) || []),
|
...pubkeys.flatMap((k: string) => this.options.getPubkeyRelays?.(k, RelayMode.Write) || []),
|
||||||
...communities.flatMap((a: string) => this.options.getCommunityRelays?.(a) || []),
|
|
||||||
...groups.flatMap((a: string) => this.options.getGroupRelays?.(a) || []),
|
|
||||||
...ancestors.relays().valueOf(),
|
...ancestors.relays().valueOf(),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -238,18 +241,12 @@ export class Router {
|
|||||||
const tags = Tags.fromEvent(event)
|
const tags = Tags.fromEvent(event)
|
||||||
const mentions = tags.values("p").valueOf()
|
const mentions = tags.values("p").valueOf()
|
||||||
|
|
||||||
// If we're publishing to private groups, only publish to those groups' relays
|
return this.scenario(
|
||||||
if (tags.groups().exists()) {
|
this.forceValue(event.id, [
|
||||||
return this
|
this.getPubkeySelection(event.pubkey, RelayMode.Write),
|
||||||
.scenario(this.getContextSelections(tags.groups()))
|
...this.getPubkeySelections(mentions, RelayMode.Read),
|
||||||
.policy(addNoFallbacks)
|
])
|
||||||
}
|
)
|
||||||
|
|
||||||
return this.scenario(this.forceValue(event.id, [
|
|
||||||
this.getPubkeySelection(event.pubkey, RelayMode.Write),
|
|
||||||
...this.getContextSelections(tags.context()),
|
|
||||||
...this.getPubkeySelections(mentions, RelayMode.Read),
|
|
||||||
]))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FromPubkeys = (pubkeys: string[]) =>
|
FromPubkeys = (pubkeys: string[]) =>
|
||||||
@@ -258,29 +255,6 @@ export class Router {
|
|||||||
ForPubkeys = (pubkeys: string[]) =>
|
ForPubkeys = (pubkeys: string[]) =>
|
||||||
this.scenario(this.getPubkeySelections(pubkeys, RelayMode.Read))
|
this.scenario(this.getPubkeySelections(pubkeys, RelayMode.Read))
|
||||||
|
|
||||||
WithinGroup = (address: string, relays?: string) =>
|
|
||||||
this
|
|
||||||
.scenario(this.getContextSelections(Tags.wrap([["a", address]])))
|
|
||||||
.policy(addNoFallbacks)
|
|
||||||
|
|
||||||
WithinCommunity = (address: string) =>
|
|
||||||
this.scenario(this.getContextSelections(Tags.wrap([["a", address]])))
|
|
||||||
|
|
||||||
WithinContext = (address: string) => {
|
|
||||||
if (isGroupAddress(address)) {
|
|
||||||
return this.WithinGroup(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCommunityAddress(address)) {
|
|
||||||
return this.WithinCommunity(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Unknown context ${address}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
WithinMultipleContexts = (addresses: string[]) =>
|
|
||||||
this.merge(addresses.map(this.WithinContext))
|
|
||||||
|
|
||||||
Search = (term: string, relays: string[] = []) =>
|
Search = (term: string, relays: string[] = []) =>
|
||||||
this.product([term], uniq(relays.concat(this.options.getSearchRelays?.() || [])))
|
this.product([term], uniq(relays.concat(this.options.getSearchRelays?.() || [])))
|
||||||
}
|
}
|
||||||
@@ -294,16 +268,28 @@ export type RouterScenarioOptions = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RouterScenario {
|
export class RouterScenario {
|
||||||
constructor(readonly router: Router, readonly selections: ValueRelays[], readonly options: RouterScenarioOptions = {}) {}
|
constructor(
|
||||||
|
readonly router: Router,
|
||||||
|
readonly selections: ValueRelays[],
|
||||||
|
readonly options: RouterScenarioOptions = {}
|
||||||
|
) {}
|
||||||
|
|
||||||
clone = (options: RouterScenarioOptions) =>
|
clone = (options: RouterScenarioOptions) =>
|
||||||
new RouterScenario(this.router, this.selections, {...this.options, ...options})
|
new RouterScenario(this.router, this.selections, {...this.options, ...options})
|
||||||
|
|
||||||
filter = (f: (selection: ValueRelays) => boolean) =>
|
filter = (f: (selection: ValueRelays) => boolean) =>
|
||||||
new RouterScenario(this.router, this.selections.filter(selection => f(selection)), this.options)
|
new RouterScenario(
|
||||||
|
this.router,
|
||||||
|
this.selections.filter(selection => f(selection)),
|
||||||
|
this.options
|
||||||
|
)
|
||||||
|
|
||||||
update = (f: (selection: ValueRelays) => ValueRelays) =>
|
update = (f: (selection: ValueRelays) => ValueRelays) =>
|
||||||
new RouterScenario(this.router, this.selections.map(selection => f(selection)), this.options)
|
new RouterScenario(
|
||||||
|
this.router,
|
||||||
|
this.selections.map(selection => f(selection)),
|
||||||
|
this.options
|
||||||
|
)
|
||||||
|
|
||||||
redundancy = (redundancy: number) => this.clone({redundancy})
|
redundancy = (redundancy: number) => this.clone({redundancy})
|
||||||
|
|
||||||
@@ -334,7 +320,10 @@ export class RouterScenario {
|
|||||||
// are we're less tolerant of failure. Add more redundancy to fill our relay limit.
|
// are we're less tolerant of failure. Add more redundancy to fill our relay limit.
|
||||||
const limit = this.getLimit()
|
const limit = this.getLimit()
|
||||||
const redundancy = this.getRedundancy()
|
const redundancy = this.getRedundancy()
|
||||||
const adjustedRedundancy = Math.max(redundancy, redundancy * (limit / (allValues.size * redundancy)))
|
const adjustedRedundancy = Math.max(
|
||||||
|
redundancy,
|
||||||
|
redundancy * (limit / (allValues.size * redundancy))
|
||||||
|
)
|
||||||
|
|
||||||
const seen = new Map<string, number>()
|
const seen = new Map<string, number>()
|
||||||
const result: ValuesByRelay = new Map()
|
const result: ValuesByRelay = new Map()
|
||||||
@@ -371,7 +360,9 @@ export class RouterScenario {
|
|||||||
const [keep, discard] = splitAt(limit, this.router.relaySelectionsFromMap(result))
|
const [keep, discard] = splitAt(limit, this.router.relaySelectionsFromMap(result))
|
||||||
|
|
||||||
for (const target of keep.slice(0, redundancy)) {
|
for (const target of keep.slice(0, redundancy)) {
|
||||||
target.values = uniq(discard.concat(target).flatMap((selection: RelayValues) => selection.values))
|
target.values = uniq(
|
||||||
|
discard.concat(target).flatMap((selection: RelayValues) => selection.values)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return keep
|
return keep
|
||||||
@@ -437,23 +428,30 @@ export const getPubkeyRelays = (pubkey: string, mode?: string) => {
|
|||||||
const $inboxSelections = inboxRelaySelectionsByPubkey.get()
|
const $inboxSelections = inboxRelaySelectionsByPubkey.get()
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case RelayMode.Read: return getReadRelayUrls($relaySelections.get(pubkey))
|
case RelayMode.Read:
|
||||||
case RelayMode.Write: return getWriteRelayUrls($relaySelections.get(pubkey))
|
return getReadRelayUrls($relaySelections.get(pubkey))
|
||||||
case RelayMode.Inbox: return getRelayUrls($inboxSelections.get(pubkey))
|
case RelayMode.Write:
|
||||||
default: return getRelayUrls($relaySelections.get(pubkey))
|
return getWriteRelayUrls($relaySelections.get(pubkey))
|
||||||
|
case RelayMode.Inbox:
|
||||||
|
return getRelayUrls($inboxSelections.get(pubkey))
|
||||||
|
default:
|
||||||
|
return getRelayUrls($relaySelections.get(pubkey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIndexerRelays = () => ctx.app.indexerRelays || getFallbackRelays()
|
export const getIndexerRelays = () => ctx.app.indexerRelays || getFallbackRelays()
|
||||||
|
|
||||||
export const getFallbackRelays = throttleWithValue(300, () =>
|
export const getFallbackRelays = throttleWithValue(300, () =>
|
||||||
sortBy(r => -getRelayQuality(r.url), relays.get()).slice(0, 30).map(r => r.url)
|
sortBy(r => -getRelayQuality(r.url), relays.get())
|
||||||
|
.slice(0, 30)
|
||||||
|
.map(r => r.url)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const getSearchRelays = throttleWithValue(300, () =>
|
export const getSearchRelays = throttleWithValue(300, () =>
|
||||||
sortBy(r => -getRelayQuality(r.url), relays.get())
|
sortBy(r => -getRelayQuality(r.url), relays.get())
|
||||||
.filter(r => r.profile?.supported_nips?.includes(50))
|
.filter(r => r.profile?.supported_nips?.includes(50))
|
||||||
.slice(0, 30).map(r => r.url)
|
.slice(0, 30)
|
||||||
|
.map(r => r.url)
|
||||||
)
|
)
|
||||||
|
|
||||||
export const makeRouter = (options: Partial<RouterOptions> = {}) =>
|
export const makeRouter = (options: Partial<RouterOptions> = {}) =>
|
||||||
@@ -472,20 +470,23 @@ export const makeRouter = (options: Partial<RouterOptions> = {}) =>
|
|||||||
// Infer relay selections from filters
|
// Infer relay selections from filters
|
||||||
|
|
||||||
export type FilterSelection = {
|
export type FilterSelection = {
|
||||||
id: string,
|
id: string
|
||||||
filter: Filter,
|
filter: Filter
|
||||||
scenario: RouterScenario
|
scenario: RouterScenario
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterSelectionRuleState = {
|
type FilterSelectionRuleState = {
|
||||||
filter: Filter,
|
filter: Filter
|
||||||
selections: FilterSelection[]
|
selections: FilterSelection[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterSelectionRule = (state: FilterSelectionRuleState) => boolean
|
type FilterSelectionRule = (state: FilterSelectionRuleState) => boolean
|
||||||
|
|
||||||
export const makeFilterSelection = (id: string, filter: Filter, scenario: RouterScenario) =>
|
export const makeFilterSelection = (id: string, filter: Filter, scenario: RouterScenario) => ({
|
||||||
({id, filter, scenario})
|
id,
|
||||||
|
filter,
|
||||||
|
scenario,
|
||||||
|
})
|
||||||
|
|
||||||
export const getFilterSelectionsForLocalRelay = (state: FilterSelectionRuleState) => {
|
export const getFilterSelectionsForLocalRelay = (state: FilterSelectionRuleState) => {
|
||||||
const id = getFilterId(state.filter)
|
const id = getFilterId(state.filter)
|
||||||
@@ -530,7 +531,7 @@ export const getFilterSelectionsForWraps = (state: FilterSelectionRuleState) =>
|
|||||||
if (!state.filter.kinds?.includes(WRAP) || state.filter.authors) return false
|
if (!state.filter.kinds?.includes(WRAP) || state.filter.authors) return false
|
||||||
|
|
||||||
const id = getFilterId({...state.filter, kinds: [WRAP]})
|
const id = getFilterId({...state.filter, kinds: [WRAP]})
|
||||||
const scenario = ctx.app.router.InboxRelays().update(assoc('value', id))
|
const scenario = ctx.app.router.InboxRelays().update(assoc("value", id))
|
||||||
|
|
||||||
state.selections.push(makeFilterSelection(id, state.filter, scenario))
|
state.selections.push(makeFilterSelection(id, state.filter, scenario))
|
||||||
|
|
||||||
@@ -557,7 +558,7 @@ export const getFilterSelectionsForAuthors = (state: FilterSelectionRuleState) =
|
|||||||
|
|
||||||
const id = getFilterId(state.filter)
|
const id = getFilterId(state.filter)
|
||||||
const pubkeys = sample(50, state.filter.authors!)
|
const pubkeys = sample(50, state.filter.authors!)
|
||||||
const scenario = ctx.app.router.FromPubkeys(pubkeys).update(assoc('value', id))
|
const scenario = ctx.app.router.FromPubkeys(pubkeys).update(assoc("value", id))
|
||||||
|
|
||||||
state.selections.push(makeFilterSelection(id, state.filter, scenario))
|
state.selections.push(makeFilterSelection(id, state.filter, scenario))
|
||||||
|
|
||||||
@@ -584,7 +585,10 @@ export const defaultFilterSelectionRules = [
|
|||||||
getFilterSelectionsForUser,
|
getFilterSelectionsForUser,
|
||||||
]
|
]
|
||||||
|
|
||||||
export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRule[] = defaultFilterSelectionRules): RelaysAndFilters[] => {
|
export const getFilterSelections = (
|
||||||
|
filters: Filter[],
|
||||||
|
rules: FilterSelectionRule[] = defaultFilterSelectionRules
|
||||||
|
): RelaysAndFilters[] => {
|
||||||
const scenarios: RouterScenario[] = []
|
const scenarios: RouterScenario[] = []
|
||||||
const filtersById = new Map<string, Filter>()
|
const filtersById = new Map<string, Filter>()
|
||||||
|
|
||||||
@@ -605,7 +609,6 @@ export const getFilterSelections = (filters: Filter[], rules: FilterSelectionRul
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Use low redundancy because filters will be very low cardinality
|
// Use low redundancy because filters will be very low cardinality
|
||||||
const selections = ctx.app.router
|
const selections = ctx.app.router
|
||||||
.merge(scenarios)
|
.merge(scenarios)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from "nostr-tools"
|
||||||
import {GROUP, COMMUNITY} from './Kinds'
|
|
||||||
|
|
||||||
// Define this locally to avoid circular dependencies
|
// Define this locally to avoid circular dependencies
|
||||||
type AddressableEvent = {
|
type AddressableEvent = {
|
||||||
@@ -30,7 +29,7 @@ export class Address {
|
|||||||
let type
|
let type
|
||||||
let data = {} as any
|
let data = {} as any
|
||||||
try {
|
try {
|
||||||
({type, data} = nip19.decode(naddr) as {
|
;({type, data} = nip19.decode(naddr) as {
|
||||||
type: "naddr"
|
type: "naddr"
|
||||||
data: any
|
data: any
|
||||||
})
|
})
|
||||||
@@ -59,9 +58,3 @@ export class Address {
|
|||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
export const getAddress = (e: AddressableEvent) => Address.fromEvent(e).toString()
|
export const getAddress = (e: AddressableEvent) => Address.fromEvent(e).toString()
|
||||||
|
|
||||||
export const isGroupAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && Address.from(a).kind === GROUP
|
|
||||||
|
|
||||||
export const isCommunityAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && Address.from(a).kind === COMMUNITY
|
|
||||||
|
|
||||||
export const isContextAddress = (a: string, ...args: unknown[]) => Address.isAddress(a) && [GROUP, COMMUNITY].includes(Address.from(a).kind)
|
|
||||||
|
|||||||
+23
-28
@@ -1,10 +1,9 @@
|
|||||||
import type {OmitStatics} from '@welshman/lib'
|
import type {OmitStatics} from "@welshman/lib"
|
||||||
import {Fluent, uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from '@welshman/lib'
|
import {Fluent, uniq, uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib"
|
||||||
import {isRelayUrl, isShareableRelayUrl, normalizeRelayUrl} from './Relay'
|
import {isRelayUrl, isShareableRelayUrl, normalizeRelayUrl} from "./Relay"
|
||||||
import {Address, isContextAddress} from './Address'
|
import {Address} from "./Address"
|
||||||
import {GROUP, COMMUNITY} from './Kinds'
|
|
||||||
|
|
||||||
export class Tag extends (Fluent<string> as OmitStatics<typeof Fluent<string>, 'from'>) {
|
export class Tag extends (Fluent<string> as OmitStatics<typeof Fluent<string>, "from">) {
|
||||||
static from = (xs: Iterable<string>) => new Tag(Array.from(xs))
|
static from = (xs: Iterable<string>) => new Tag(Array.from(xs))
|
||||||
|
|
||||||
static fromId = (id: string) => new Tag(["e", id])
|
static fromId = (id: string) => new Tag(["e", id])
|
||||||
@@ -28,15 +27,9 @@ export class Tag extends (Fluent<string> as OmitStatics<typeof Fluent<string>, '
|
|||||||
setValue = (v: string) => this.set(1, v)
|
setValue = (v: string) => this.set(1, v)
|
||||||
|
|
||||||
isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`)
|
isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`)
|
||||||
|
|
||||||
isGroup = () => this.isAddress(GROUP)
|
|
||||||
|
|
||||||
isCommunity = () => this.isAddress(COMMUNITY)
|
|
||||||
|
|
||||||
isContext = () => this.isAddress(GROUP) || this.isAddress(COMMUNITY)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'>) {
|
export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, "from">) {
|
||||||
static from = (p: Iterable<Tag>) => new Tags(Array.from(p))
|
static from = (p: Iterable<Tag>) => new Tags(Array.from(p))
|
||||||
|
|
||||||
static wrap = (p: Iterable<string[]>) => new Tags(Array.from(p).map(Tag.from))
|
static wrap = (p: Iterable<string[]>) => new Tags(Array.from(p).map(Tag.from))
|
||||||
@@ -70,9 +63,18 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
|
|
||||||
entries = () => this.mapTo(t => t.entry())
|
entries = () => this.mapTo(t => t.entry())
|
||||||
|
|
||||||
relays = () => this.flatMap((t: Tag) => t.valueOf().filter(isRelayUrl).map(url => normalizeRelayUrl(url))).uniq()
|
relays = () =>
|
||||||
|
this.flatMap((t: Tag) =>
|
||||||
|
t
|
||||||
|
.valueOf()
|
||||||
|
.filter(isRelayUrl)
|
||||||
|
.map(url => normalizeRelayUrl(url))
|
||||||
|
).uniq()
|
||||||
|
|
||||||
topics = () => this.whereKey("t").values().map((t: string) => t.replace(/^#/, ""))
|
topics = () =>
|
||||||
|
this.whereKey("t")
|
||||||
|
.values()
|
||||||
|
.map((t: string) => t.replace(/^#/, ""))
|
||||||
|
|
||||||
ancestors = (x?: boolean) => {
|
ancestors = (x?: boolean) => {
|
||||||
const {roots, replies, mentions} = getAncestorTags(this.unwrap())
|
const {roots, replies, mentions} = getAncestorTags(this.unwrap())
|
||||||
@@ -114,10 +116,6 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
return parents.get("e") || parents.get("a")
|
return parents.get("e") || parents.get("a")
|
||||||
}
|
}
|
||||||
|
|
||||||
groups = () => this.whereKey("a").filter(t => t.isGroup())
|
|
||||||
|
|
||||||
communities = () => this.whereKey("a").filter(t => t.isCommunity())
|
|
||||||
|
|
||||||
context = () => this.whereKey("a").filter(t => t.isContext())
|
context = () => this.whereKey("a").filter(t => t.isContext())
|
||||||
|
|
||||||
asObject = () => {
|
asObject = () => {
|
||||||
@@ -161,7 +159,7 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
addImages = (imeta: Tags[]) =>
|
addImages = (imeta: Tags[]) =>
|
||||||
this.concat(imeta.map(tags => Tag.from(["image", tags.get("url").value()])))
|
this.concat(imeta.map(tags => Tag.from(["image", tags.get("url").value()])))
|
||||||
|
|
||||||
removeImages = () => this.rejectByKey(['image'])
|
removeImages = () => this.rejectByKey(["image"])
|
||||||
|
|
||||||
setImages = (imeta: Tags[]) => this.removeImages().addImages(imeta)
|
setImages = (imeta: Tags[]) => this.removeImages().addImages(imeta)
|
||||||
|
|
||||||
@@ -170,7 +168,7 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
addIMeta = (imeta: Tags[]) =>
|
addIMeta = (imeta: Tags[]) =>
|
||||||
this.concat(imeta.map(tags => Tag.from(["imeta", ...tags.valueOf().map(xs => xs.join(" "))])))
|
this.concat(imeta.map(tags => Tag.from(["imeta", ...tags.valueOf().map(xs => xs.join(" "))])))
|
||||||
|
|
||||||
removeIMeta = () => this.rejectByKey(['imeta'])
|
removeIMeta = () => this.rejectByKey(["imeta"])
|
||||||
|
|
||||||
setIMeta = (imeta: Tags[]) => this.removeIMeta().addIMeta(imeta)
|
setIMeta = (imeta: Tags[]) => this.removeIMeta().addIMeta(imeta)
|
||||||
}
|
}
|
||||||
@@ -196,9 +194,6 @@ export const getAddressTags = (tags: string[][]) =>
|
|||||||
|
|
||||||
export const getAddressTagValues = (tags: string[][]) => getAddressTags(tags).map(nth(1))
|
export const getAddressTagValues = (tags: string[][]) => getAddressTags(tags).map(nth(1))
|
||||||
|
|
||||||
export const getContextTagValues = (tags: string[][]) =>
|
|
||||||
getAddressTagValues(tags).filter(isContextAddress)
|
|
||||||
|
|
||||||
export const getPubkeyTags = (tags: string[][]) =>
|
export const getPubkeyTags = (tags: string[][]) =>
|
||||||
tags.filter(t => ["p"].includes(t[0]) && t[1].length === 64)
|
tags.filter(t => ["p"].includes(t[0]) && t[1].length === 64)
|
||||||
|
|
||||||
@@ -224,7 +219,7 @@ export const getKindTags = (tags: string[][]) =>
|
|||||||
export const getKindTagValues = (tags: string[][]) => getKindTags(tags).map(t => parseInt(t[1]))
|
export const getKindTagValues = (tags: string[][]) => getKindTags(tags).map(t => parseInt(t[1]))
|
||||||
|
|
||||||
export const getAncestorTags = (tags: string[][]) => {
|
export const getAncestorTags = (tags: string[][]) => {
|
||||||
const validTags = tags.filter(t => ["a", "e", "q"].includes(t[0]) && !isContextAddress(t[1]))
|
const validTags = tags.filter(t => ["a", "e", "q"].includes(t[0]))
|
||||||
const mentionTags = validTags.filter(nthEq(0, "q"))
|
const mentionTags = validTags.filter(nthEq(0, "q"))
|
||||||
const roots: string[][] = []
|
const roots: string[][] = []
|
||||||
const replies: string[][] = []
|
const replies: string[][] = []
|
||||||
@@ -232,15 +227,15 @@ export const getAncestorTags = (tags: string[][]) => {
|
|||||||
|
|
||||||
const dispatchTags = (thisTags: string[][]) =>
|
const dispatchTags = (thisTags: string[][]) =>
|
||||||
thisTags.forEach((t: string[], i: number) => {
|
thisTags.forEach((t: string[], i: number) => {
|
||||||
if (t[3] === 'root') {
|
if (t[3] === "root") {
|
||||||
if (validTags.filter(nthEq(3, "reply")).length === 0) {
|
if (validTags.filter(nthEq(3, "reply")).length === 0) {
|
||||||
replies.push(t)
|
replies.push(t)
|
||||||
} else {
|
} else {
|
||||||
roots.push(t)
|
roots.push(t)
|
||||||
}
|
}
|
||||||
} else if (t[3] === 'reply') {
|
} else if (t[3] === "reply") {
|
||||||
replies.push(t)
|
replies.push(t)
|
||||||
} else if (t[3] === 'mention') {
|
} else if (t[3] === "mention") {
|
||||||
mentions.push(t)
|
mentions.push(t)
|
||||||
} else if (i === thisTags.length - 1) {
|
} else if (i === thisTags.length - 1) {
|
||||||
replies.push(t)
|
replies.push(t)
|
||||||
|
|||||||
Reference in New Issue
Block a user