diff --git a/packages/feeds/compiler.ts b/packages/feeds/compiler.ts index 087721e..fc114c9 100644 --- a/packages/feeds/compiler.ts +++ b/packages/feeds/compiler.ts @@ -2,22 +2,12 @@ import {uniq, identity, flatten, pushToMapKey, intersection, tryCatch, now} from import type {TrustedEvent, Filter} from '@welshman/util' import {Tags, intersectFilters, getAddress, getIdFilters, unionFilters} from '@welshman/util' import type {CreatedAtItem, RequestItem, ListItem, WOTItem, DVMItem, Scope, Feed, FeedOptions} from './core' -import {hasSubFeeds, getFeedArgs, feedsFromTags} from './utils' +import {getFeedArgs, feedsFromTags} from './utils' import {FeedType} from './core' export class FeedCompiler { constructor(readonly options: FeedOptions) {} - walk(feed: Feed, visit: (feed: Feed) => void) { - visit(feed) - - if (hasSubFeeds(feed)) { - for (const subFeed of getFeedArgs(feed)) { - this.walk(subFeed, visit) - } - } - } - canCompile(feed: Feed): boolean { switch(feed[0]) { case FeedType.Union: @@ -240,6 +230,7 @@ export class FeedCompiler { if (event) { for (const feed of feedsFromTags(Tags.fromEvent(event), mappings)) { + feeds.push(feed) } } diff --git a/packages/feeds/utils.ts b/packages/feeds/utils.ts index 7d0ca04..6f1cc9a 100644 --- a/packages/feeds/utils.ts +++ b/packages/feeds/utils.ts @@ -155,3 +155,14 @@ export const feedsFromFilter = ({since, until, ...filter}: Filter) => { export const feedFromFilter = (filter: Filter) => makeIntersectionFeed(...feedsFromFilter(filter)) + + +export const walkFeed = (feed: Feed, visit: (feed: Feed) => void) => { + visit(feed) + + if (hasSubFeeds(feed)) { + for (const subFeed of getFeedArgs(feed)) { + walkFeed(subFeed, visit) + } + } +} diff --git a/packages/lib/Tools.ts b/packages/lib/Tools.ts index 532888c..9440ea1 100644 --- a/packages/lib/Tools.ts +++ b/packages/lib/Tools.ts @@ -89,6 +89,8 @@ export const difference = (a: T[], b: T[]) => { return a.filter(x => !s.has(x)) } +export const remove = (a: T, b: T[]) => b.filter(x => x !== a) + export const clamp = ([min, max]: [number, number], n: number) => Math.min(max, Math.max(min, n)) export const tryCatch = async (f: () => Promise | T | void, onError?: (e: Error) => void): Promise => { diff --git a/packages/util/Address.ts b/packages/util/Address.ts index c22ad5d..d1d7206 100644 --- a/packages/util/Address.ts +++ b/packages/util/Address.ts @@ -1,58 +1,67 @@ -import type {UnsignedEvent} from 'nostr-tools' import {nip19} from 'nostr-tools' import {GROUP, COMMUNITY} from './Kinds' -export type Address = { - kind: number, +// Define this locally to avoid circular dependencies +type AddressableEvent = { + kind: number pubkey: string - identifier: string - relays: string[] + tags: string[][] } -// Plain text format +export class Address { + constructor( + readonly kind: number, + readonly pubkey: string, + readonly identifier: string, + readonly relays: string[] = [] + ) {} -export const decodeAddress = (a: string, relays: string[] = []): Address => { - const [kind, pubkey, identifier = ""] = a.split(":") - - return {kind: parseInt(kind), pubkey, identifier, relays} -} - -export const encodeAddress = (a: Address) => [a.kind, a.pubkey, a.identifier].join(":") - -// Naddr encoding - -export const addressFromNaddr = (naddr: string): Address => { - let type - let data = {} as any - try { - ({type, data} = nip19.decode(naddr) as { - type: "naddr" - data: any - }) - } catch (e) { - // pass + static isAddress(address: string) { + return Boolean(address.match(/^\d+:\w+:.*$/)) } - if (type !== "naddr") { - throw new Error(`Invalid naddr ${naddr}`) + static from(address: string, relays: string[] = []) { + const [kind, pubkey, identifier = ""] = address.match(/^(\d+):(\w+):(.*)$/)!.slice(1) + + return new Address(parseInt(kind), pubkey, identifier, relays) } - return data + static fromNaddr(naddr: string) { + let type + let data = {} as any + try { + ({type, data} = nip19.decode(naddr) as { + type: "naddr" + data: any + }) + } catch (e) { + // pass + } + + if (type !== "naddr") { + throw new Error(`Invalid naddr ${naddr}`) + } + + return new Address(data.kind, data.pubkey, data.identifier, data.relays) + } + + static fromEvent(event: AddressableEvent, relays: string[] = []) { + const identifier = event.tags.find(t => t[0] === "d")?.[1] || "" + + return new Address(event.kind, event.pubkey, identifier, relays) + } + + toString = () => [this.kind, this.pubkey, this.identifier].join(":") + + toNaddr = () => nip19.naddrEncode(this) } -export const addressToNaddr = (a: Address): string => nip19.naddrEncode(a) - -// Get from event, encode to filter - -export const getIdentifier = (e: UnsignedEvent) => e.tags.find(t => t[0] === "d")?.[1] || "" - -export const addressFromEvent = (e: UnsignedEvent, relays: string[] = []) => - ({kind: e.kind, pubkey: e.pubkey, identifier: getIdentifier(e), relays}) - // Utils -export const isGroupAddress = (a: Address) => a.kind === GROUP +export const getAddress = (e: AddressableEvent) => Address.fromEvent(e).toString() -export const isCommunityAddress = (a: Address) => a.kind === COMMUNITY +export const isGroupAddress = (a: string, ...args: unknown[]) => Address.from(a).kind === GROUP -export const isContextAddress = (a: Address) => [GROUP, COMMUNITY].includes(a.kind) +export const isCommunityAddress = (a: string, ...args: unknown[]) => Address.from(a).kind === COMMUNITY + +export const isContextAddress = (a: string, ...args: unknown[]) => [GROUP, COMMUNITY].includes(Address.from(a).kind) diff --git a/packages/util/Events.ts b/packages/util/Events.ts index 2167e05..d3d485a 100644 --- a/packages/util/Events.ts +++ b/packages/util/Events.ts @@ -2,7 +2,7 @@ import {verifiedSymbol} from 'nostr-tools' import {verifyEvent, getEventHash} from 'nostr-tools' import {cached, pick, now} from '@welshman/lib' import {Tags} from './Tags' -import {addressFromEvent, encodeAddress} from './Address' +import {getAddress} from './Address' import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds' export type EventTemplate = { @@ -98,8 +98,6 @@ export const hasValidSignature = cached({ }, }) -export const getAddress = (e: HashedEvent) => encodeAddress(addressFromEvent(e)) - export const getIdOrAddress = (e: HashedEvent) => isReplaceable(e) ? getAddress(e) : e.id export const getIdAndAddress = (e: HashedEvent) => isReplaceable(e) ? [e.id, getAddress(e)] : [e.id] diff --git a/packages/util/Filters.ts b/packages/util/Filters.ts index 5a5cab7..feba1ea 100644 --- a/packages/util/Filters.ts +++ b/packages/util/Filters.ts @@ -2,8 +2,8 @@ import {Event} from 'nostr-tools' import {matchFilter as nostrToolsMatchFilter} from 'nostr-tools' import {prop, avg, hash, groupBy, randomId, uniq} from '@welshman/lib' import type {HashedEvent, TrustedEvent} from './Events' -import {decodeAddress, addressFromEvent, encodeAddress} from './Address' import {isReplaceableKind} from './Kinds' +import {Address, getAddress} from './Address' export const EPOCH = 1609459200 @@ -133,8 +133,8 @@ export const getIdFilters = (idsOrAddresses: string[]) => { const aFilters = [] for (const idOrAddress of idsOrAddresses) { - if (idOrAddress.includes(":")) { - const {kind, pubkey, identifier} = decodeAddress(idOrAddress) + if (Address.isAddress(idOrAddress)) { + const {kind, pubkey, identifier} = Address.from(idOrAddress) if (identifier) { aFilters.push({kinds: [kind], authors: [pubkey], "#d": [identifier]}) @@ -163,7 +163,7 @@ export const getReplyFilters = (events: TrustedEvent[], filter: Filter) => { e.push(event.id) if (isReplaceableKind(event.kind)) { - a.push(encodeAddress(addressFromEvent(event))) + a.push(getAddress(event)) } if (event.wrap) { diff --git a/packages/util/Repository.ts b/packages/util/Repository.ts index dae92e7..521a96c 100644 --- a/packages/util/Repository.ts +++ b/packages/util/Repository.ts @@ -3,7 +3,8 @@ import type {IReadable, Subscriber, Invalidator} from '@welshman/lib' import {Derived, Emitter, sortBy, customStore, inc, first, chunk, sleep, uniq, omit, now, range, identity} from '@welshman/lib' import {DELETE} from './Kinds' import {EPOCH, matchFilter, getIdFilters, matchFilters} from './Filters' -import {isReplaceable, isTrustedEvent, getAddress} from './Events' +import {isReplaceable, isTrustedEvent} from './Events' +import {getAddress} from './Address' import type {Filter} from './Filters' import type {TrustedEvent} from './Events' diff --git a/packages/util/Router.ts b/packages/util/Router.ts index eef4213..3aaac3a 100644 --- a/packages/util/Router.ts +++ b/packages/util/Router.ts @@ -1,9 +1,9 @@ import {first, splitAt, identity, sortBy, uniq, shuffle, pushToMapKey} from '@welshman/lib' import {Tags, Tag} from '@welshman/util' import type {TrustedEvent} from './Events' -import {getAddress, isReplaceable} from './Events' +import {isReplaceable} from './Events' import {isShareableRelayUrl} from './Relay' -import {addressFromEvent, decodeAddress, isCommunityAddress, isGroupAddress} from './Address' +import {getAddress, isCommunityAddress, isGroupAddress} from './Address' export enum RelayMode { Read = "read", @@ -246,11 +246,11 @@ export class Router { this.scenario(this.getContextSelections(Tags.wrap([["a", address]]))) WithinContext = (address: string) => { - if (isGroupAddress(decodeAddress(address))) { + if (isGroupAddress(address)) { return this.WithinGroup(address) } - if (isCommunityAddress(decodeAddress(address))) { + if (isCommunityAddress(address)) { return this.WithinCommunity(address) } @@ -294,9 +294,6 @@ export class Router { return new Tags(tags) } - - address = (event: TrustedEvent) => - addressFromEvent(event, this.Event(event).redundancy(3).getUrls()) } // Router Scenario @@ -352,7 +349,7 @@ export class RouterScenario { const relaySelections = this.router.relaySelectionsFromMap(valuesByRelay) for (const {relay} of this.router.sortRelaySelections(relaySelections)) { const values = new Set() - for (const value of valuesByRelay.get(relay) || []) { + for (const value of uniq(valuesByRelay.get(relay) || [])) { const timesSeen = seen.get(value) || 0 if (timesSeen < adjustedRedundancy) { diff --git a/packages/util/Tags.ts b/packages/util/Tags.ts index 78ab87d..c596ffa 100644 --- a/packages/util/Tags.ts +++ b/packages/util/Tags.ts @@ -2,8 +2,6 @@ import {EventTemplate} from 'nostr-tools' import type {OmitStatics} from '@welshman/lib' import {Fluent, ensurePlural} from '@welshman/lib' import {isShareableRelayUrl, normalizeRelayUrl} from './Relay' -import type {Address} from './Address' -import {encodeAddress, decodeAddress} from './Address' import {GROUP, COMMUNITY} from './Kinds' export class Tag extends (Fluent as OmitStatics, 'from'>) { @@ -17,7 +15,7 @@ export class Tag extends (Fluent as OmitStatics, ' static fromPubkey = (pubkey: string) => new Tag(["p", pubkey]) - static fromAddress = (address: Address) => new Tag(["a", encodeAddress(address), address.relays[0] || ""]) + static fromAddress = (address: string, relay = "") => new Tag(["a", address, relay]) key = () => this.xs[0] @@ -29,8 +27,6 @@ export class Tag extends (Fluent as OmitStatics, ' setValue = (v: string) => this.set(1, v) - asAddress = () => decodeAddress(this.value()) - isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`) isGroup = () => this.isAddress(GROUP)