Re-work address
This commit is contained in:
+56
-80
@@ -1,85 +1,61 @@
|
|||||||
import type {UnsignedEvent} from 'nostr-tools'
|
import type {UnsignedEvent} from 'nostr-tools'
|
||||||
import {nip19} from 'nostr-tools'
|
import {nip19} from 'nostr-tools'
|
||||||
import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds'
|
import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds'
|
||||||
import {Tags} from './Tags'
|
|
||||||
|
|
||||||
export const isGroupAddress = (a: string) => a.startsWith(`${GROUP_DEFINITION}:`)
|
export type Address = {
|
||||||
|
kind: number,
|
||||||
export const isCommunityAddress = (a: string) => a.startsWith(`${COMMUNITY_DEFINITION}:`)
|
pubkey: string
|
||||||
|
identifier: string
|
||||||
export const isContextAddress = (a: string) => isCommunityAddress(a) || isGroupAddress(a)
|
relays: string[]
|
||||||
|
|
||||||
export class Address {
|
|
||||||
readonly kind: number
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
kind: string | number,
|
|
||||||
readonly pubkey: string,
|
|
||||||
readonly identifier: string,
|
|
||||||
readonly relays: string[],
|
|
||||||
) {
|
|
||||||
this.kind = parseInt(kind as string)
|
|
||||||
this.identifier = identifier || ""
|
|
||||||
}
|
|
||||||
|
|
||||||
static getKind = (a: string) => Address.fromRaw(a, []).kind
|
|
||||||
|
|
||||||
static getPubkey = (a: string) => Address.fromRaw(a, []).pubkey
|
|
||||||
|
|
||||||
static getIdentifier = (a: string) => Address.fromRaw(a, []).identifier
|
|
||||||
|
|
||||||
static fromEvent = (e: UnsignedEvent, relays: string[]) =>
|
|
||||||
new Address(e.kind, e.pubkey, Tags.fromEvent(e).get("d")?.value() || "", relays)
|
|
||||||
|
|
||||||
static fromRaw = (a: string, relays: string[]) => {
|
|
||||||
const [kind, pubkey, identifier] = a.split(":")
|
|
||||||
|
|
||||||
return new Address(kind, pubkey, identifier, relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
static fromTag = (tag: string[]) => {
|
|
||||||
const [a, hint] = tag.slice(1)
|
|
||||||
const relays = hint ? [hint] : []
|
|
||||||
|
|
||||||
return this.fromRaw(a, relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
asRaw = () => [this.kind, this.pubkey, this.identifier].join(":")
|
|
||||||
|
|
||||||
asTag = (mark?: string) => {
|
|
||||||
const tag = ["a", this.asRaw(), this.relays[0] || ""]
|
|
||||||
|
|
||||||
if (mark) {
|
|
||||||
tag.push(mark)
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag
|
|
||||||
}
|
|
||||||
|
|
||||||
asNaddr = () => nip19.naddrEncode(this)
|
|
||||||
|
|
||||||
asFilter = () => ({
|
|
||||||
kinds: [this.kind],
|
|
||||||
authors: [this.pubkey],
|
|
||||||
"#d": [this.identifier],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plain text format
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type !== "naddr") {
|
||||||
|
throw new Error(`Invalid naddr ${naddr}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
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})
|
||||||
|
|
||||||
|
export const addressToFilter = (a: Address) =>
|
||||||
|
({kinds: [a.kind], authors: [a.pubkey], "#d": [a.identifier]})
|
||||||
|
|
||||||
|
// Utils
|
||||||
|
|
||||||
|
export const isGroupAddress = (a: Address) => a.kind === GROUP_DEFINITION
|
||||||
|
|
||||||
|
export const isCommunityAddress = (a: Address) => a.kind === COMMUNITY_DEFINITION
|
||||||
|
|
||||||
|
export const isContextAddress = (a: Address) => [GROUP_DEFINITION, COMMUNITY_DEFINITION].includes(a.kind)
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ import type {Event, EventTemplate, UnsignedEvent} from 'nostr-tools'
|
|||||||
import {verifyEvent, getEventHash} from 'nostr-tools'
|
import {verifyEvent, getEventHash} from 'nostr-tools'
|
||||||
import {cached} from "./LRUCache"
|
import {cached} from "./LRUCache"
|
||||||
import {now} from './Tools'
|
import {now} from './Tools'
|
||||||
import {Address} from './Address'
|
import {addressFromEvent, encodeAddress} from './Address'
|
||||||
import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds'
|
import {isEphemeralKind, isReplaceableKind, isPlainReplaceableKind, isParameterizedReplaceableKind} from './Kinds'
|
||||||
|
|
||||||
export type Rumor = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey' | 'id'>
|
export type Rumor = Pick<Event, 'kind' | 'tags' | 'content' | 'created_at' | 'pubkey' | 'id'>
|
||||||
@@ -43,7 +43,7 @@ export const hasValidSignature = cached<string, boolean, [Event]>({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getAddress = (e: UnsignedEvent) => Address.fromEvent(e, []).asRaw()
|
export const getAddress = (e: UnsignedEvent) => encodeAddress(addressFromEvent(e))
|
||||||
|
|
||||||
export const getIdOrAddress = (e: Rumor) => isReplaceable(e) ? getAddress(e) : e.id
|
export const getIdOrAddress = (e: Rumor) => isReplaceable(e) ? getAddress(e) : e.id
|
||||||
|
|
||||||
|
|||||||
+7
-2
@@ -3,7 +3,12 @@ import type {Rumor} from './Events'
|
|||||||
import {getAddress, isReplaceable} from './Events'
|
import {getAddress, isReplaceable} from './Events'
|
||||||
import {Tag, Tags} from './Tags'
|
import {Tag, Tags} from './Tags'
|
||||||
import {first, uniq, shuffle} from './Tools'
|
import {first, uniq, shuffle} from './Tools'
|
||||||
import {Address, isGroupAddress, isCommunityAddress} from './Address'
|
import {GROUP_DEFINITION, COMMUNITY_DEFINITION} from './Kinds'
|
||||||
|
import {addressFromEvent, decodeAddress} from './Address'
|
||||||
|
|
||||||
|
const isGroupAddress = (a: string) => decodeAddress(a).kind === GROUP_DEFINITION
|
||||||
|
|
||||||
|
const isCommunityAddress = (a: string) => decodeAddress(a).kind === COMMUNITY_DEFINITION
|
||||||
|
|
||||||
export enum RelayMode {
|
export enum RelayMode {
|
||||||
Inbox = "inbox",
|
Inbox = "inbox",
|
||||||
@@ -205,7 +210,7 @@ export class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
address = (event: UnsignedEvent) =>
|
address = (event: UnsignedEvent) =>
|
||||||
Address.fromEvent(event, this.Event(event).limit(3).getUrls())
|
addressFromEvent(event, this.Event(event).limit(3).getUrls())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Router Scenario
|
// Router Scenario
|
||||||
|
|||||||
+20
-6
@@ -3,13 +3,17 @@ import {Fluent} from './Fluent'
|
|||||||
import type {OmitStatics} from './Tools'
|
import type {OmitStatics} from './Tools'
|
||||||
import {last} from './Tools'
|
import {last} from './Tools'
|
||||||
import {isShareableRelayUrl} from './Relays'
|
import {isShareableRelayUrl} from './Relays'
|
||||||
import {isCommunityAddress, isGroupAddress, isContextAddress} from './Address'
|
import type {Address} from './Address'
|
||||||
|
import {encodeAddress, decodeAddress} from './Address'
|
||||||
|
import {GROUP_DEFINITION, COMMUNITY_DEFINITION} 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>) {
|
static from(xs: Iterable<string>) {
|
||||||
return new Tag(Array.from(xs))
|
return new Tag(Array.from(xs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static fromAddress = (a: Address) => new Tag(["a", encodeAddress(a), a.relays[0] || ""])
|
||||||
|
|
||||||
valueOf = () => this.xs
|
valueOf = () => this.xs
|
||||||
|
|
||||||
key = () => this.xs[0]
|
key = () => this.xs[0]
|
||||||
@@ -25,6 +29,16 @@ 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)
|
||||||
|
|
||||||
setMark = (m: string) => this.xs.length > 2 ? this.set(this.xs.length - 2, m) : this.append(m)
|
setMark = (m: string) => this.xs.length > 2 ? this.set(this.xs.length - 2, m) : this.append(m)
|
||||||
|
|
||||||
|
asAddress = () => decodeAddress(this.value())
|
||||||
|
|
||||||
|
isAddress = (kind?: number) => this.key() === "a" && this.value()?.startsWith(`${kind}:`)
|
||||||
|
|
||||||
|
isGroup = () => this.isAddress(GROUP_DEFINITION)
|
||||||
|
|
||||||
|
isCommunity = () => this.isAddress(COMMUNITY_DEFINITION)
|
||||||
|
|
||||||
|
isContext = () => this.isAddress(GROUP_DEFINITION) || this.isAddress(COMMUNITY_DEFINITION)
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'>) {
|
export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'>) {
|
||||||
@@ -70,7 +84,7 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
topics = () => this.whereKey("t").values().map((t: string) => t.replace(/^#/, ""))
|
topics = () => this.whereKey("t").values().map((t: string) => t.replace(/^#/, ""))
|
||||||
|
|
||||||
ancestors = () => {
|
ancestors = () => {
|
||||||
const tags = this.filter(t => ["a", "e", "q"].includes(t.key()) && !isContextAddress(t.value()))
|
const tags = this.filter(t => ["a", "e", "q"].includes(t.key()) && !t.isContext())
|
||||||
const roots: string[][] = []
|
const roots: string[][] = []
|
||||||
const replies: string[][] = []
|
const replies: string[][] = []
|
||||||
const mentions: string[][] = []
|
const mentions: string[][] = []
|
||||||
@@ -129,11 +143,11 @@ 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 => isGroupAddress(t.value()))
|
groups = () => this.whereKey("a").filter(t => t.isGroup())
|
||||||
|
|
||||||
communities = () => this.whereKey("a").filter(t => isCommunityAddress(t.value()))
|
communities = () => this.whereKey("a").filter(t => t.isCommunity())
|
||||||
|
|
||||||
context = () => this.whereKey("a").filter(t => isContextAddress(t.value()))
|
context = () => this.whereKey("a").filter(t => t.isContext())
|
||||||
|
|
||||||
asObject = () => {
|
asObject = () => {
|
||||||
const result: Record<string, string> = {}
|
const result: Record<string, string> = {}
|
||||||
@@ -167,7 +181,7 @@ export class Tags extends (Fluent<Tag> as OmitStatics<typeof Fluent<Tag>, 'from'
|
|||||||
|
|
||||||
addContext = (addresses: string[]) => this.concat(addresses.map(a => Tag.from(["a", a])))
|
addContext = (addresses: string[]) => this.concat(addresses.map(a => Tag.from(["a", a])))
|
||||||
|
|
||||||
removeContext = () => this.reject(t => t.key() === "a" && isContextAddress(t.value()))
|
removeContext = () => this.reject(t => t.isContext())
|
||||||
|
|
||||||
setContext = (addresses: string[]) => this.removeContext().addContext(addresses)
|
setContext = (addresses: string[]) => this.removeContext().addContext(addresses)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user