Use paravel Tags

This commit is contained in:
Jon Staab 2023-11-09 14:44:14 -08:00
parent c28f3fdd56
commit 54fe302509
47 changed files with 152 additions and 272 deletions

View File

@ -3,7 +3,7 @@
import {dec, inc} from "ramda" import {dec, inc} from "ramda"
import {switcherFn} from "hurdak" import {switcherFn} from "hurdak"
import {throttle} from "throttle-debounce" import {throttle} from "throttle-debounce"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {AudioController} from "src/util/audio" import {AudioController} from "src/util/audio"
import Audio from "src/partials/Audio.svelte" import Audio from "src/partials/Audio.svelte"
import Modal from "src/partials/Modal.svelte" import Modal from "src/partials/Modal.svelte"
@ -61,7 +61,7 @@
const getStreamUrl = e => const getStreamUrl = e =>
switcherFn(e.kind, { switcherFn(e.kind, {
1808: () => Tags.from(e).asMeta().stream_url, 1808: () => Tags.from(e).getValue("stream_url"),
32123: () => JSON.parse(e.content).enclosure, 32123: () => JSON.parse(e.content).enclosure,
}) })

View File

@ -126,8 +126,9 @@
) )
$: { $: {
if (term) { if (term.length < 30) {
loadPeople(term) loadPeople(term)
} else if (term) {
tryParseEntity(term) tryParseEntity(term)
} }
} }

View File

@ -1,13 +1,13 @@
<script lang="ts"> <script lang="ts">
import {identity} from "ramda" import {identity} from "ramda"
import {quantify} from "hurdak" import {quantify} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
export let list export let list
const topics = Tags.from(list).topics() const topics = Tags.from(list).topics().all()
const authors = Tags.from(list).pubkeys() const authors = Tags.from(list).pubkeys().all()
const relays = Tags.from(list).urls() const relays = Tags.from(list).urls().all()
const summary = [ const summary = [
topics.length > 0 && quantify(topics.length, "topic"), topics.length > 0 && quantify(topics.length, "topic"),

View File

@ -3,8 +3,9 @@
import {reject, propEq, uniqBy, prop} from "ramda" import {reject, propEq, uniqBy, prop} from "ramda"
import {onMount, onDestroy} from "svelte" import {onMount, onDestroy} from "svelte"
import {quantify, batch} from "hurdak" import {quantify, batch} from "hurdak"
import {Tags} from "paravel"
import {fly} from "src/util/transition" import {fly} from "src/util/transition"
import {LOCAL_RELAY_URL, Tags, findRootId, findReplyId, isLike} from "src/util/nostr" import {LOCAL_RELAY_URL, isLike} from "src/util/nostr"
import {formatTimestamp} from "src/util/misc" import {formatTimestamp} from "src/util/misc"
import Popover from "src/partials/Popover.svelte" import Popover from "src/partials/Popover.svelte"
import Spinner from "src/partials/Spinner.svelte" import Spinner from "src/partials/Spinner.svelte"
@ -26,7 +27,6 @@
processZaps, processZaps,
getReplyHints, getReplyHints,
isEventMuted, isEventMuted,
getParentHints,
getEventHints, getEventHints,
getIdFilters, getIdFilters,
getReplyFilters, getReplyFilters,
@ -78,7 +78,7 @@
const goToParent = () => const goToParent = () =>
router router
.at("notes") .at("notes")
.of(findReplyId(event), {relays: getParentHints(event)}) .of(tags.getReply(), {relays: tags.mark("reply").relays().all()})
.cx({context: ctx.concat(event)}) .cx({context: ctx.concat(event)})
.open() .open()
@ -94,6 +94,8 @@
ctx = reject(propEq("id", e.id), ctx) ctx = reject(propEq("id", e.id), ctx)
} }
$: tags = Tags.from(event).normalize()
$: muted = !showMuted && $isEventMuted(event, true) $: muted = !showMuted && $isEventMuted(event, true)
// Find children in our context // Find children in our context
@ -146,7 +148,7 @@
) )
onMount(async () => { onMount(async () => {
const zapAddress = Tags.from(note).getMeta("zap") const zapAddress = Tags.from(note).getValue("zap")
if (zapAddress && getLnUrl(zapAddress)) { if (zapAddress && getLnUrl(zapAddress)) {
zapper = await getZapper(getLnUrl(zapAddress)) zapper = await getZapper(getLnUrl(zapAddress))
@ -221,13 +223,13 @@
</div> </div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
{#if findReplyId(event) && showParent} {#if tags.getReply() && showParent}
<small class="text-gray-1"> <small class="text-gray-1">
<i class="fa fa-code-merge" /> <i class="fa fa-code-merge" />
<Anchor class="underline" on:click={goToParent}>View Parent</Anchor> <Anchor class="underline" on:click={goToParent}>View Parent</Anchor>
</small> </small>
{/if} {/if}
{#if findRootId(event) && findRootId(event) !== findReplyId(event) && showParent} {#if tags.getRoot() && tags.getRoot() !== tags.getReply() && showParent}
<small class="text-gray-1"> <small class="text-gray-1">
<i class="fa fa-code-pull-request" /> <i class="fa fa-code-pull-request" />
<Anchor class="underline" on:click={goToThread}>View Thread</Anchor> <Anchor class="underline" on:click={goToThread}>View Thread</Anchor>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import RelayCard from "src/app/shared/RelayCard.svelte" import RelayCard from "src/app/shared/RelayCard.svelte"

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {urlIsMedia} from "src/util/notes" import {urlIsMedia} from "src/util/notes"
import NoteContentLink from "src/app/shared/NoteContentLink.svelte" import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
export let note, showMedia export let note, showMedia
const url = Tags.from(note).getMeta("url") const url = Tags.from(note).getValue("url")
</script> </script>
{#if url} {#if url}

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {annotateMedia} from "src/util/misc" import {annotateMedia} from "src/util/misc"
import Media from "src/partials/Media.svelte" import Media from "src/partials/Media.svelte"
import NoteContentLabel from "src/app/shared/NoteContentLabel.svelte" import NoteContentLabel from "src/app/shared/NoteContentLabel.svelte"
@ -7,7 +7,7 @@
export let note, anchorId, maxLength, showEntire export let note, anchorId, maxLength, showEntire
const {stream_url} = Tags.from(note).asMeta() as {stream_url: string} const {stream_url} = Tags.from(note).getDict() as {stream_url: string}
</script> </script>
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis"> <div class="flex flex-col gap-2 overflow-hidden text-ellipsis">

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte" import PersonBadgeSmall from "src/app/shared/PersonBadgeSmall.svelte"
import NoteContentEllipsis from "src/app/shared/NoteContentEllipsis.svelte" import NoteContentEllipsis from "src/app/shared/NoteContentEllipsis.svelte"

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import NoteContentLink from "src/app/shared/NoteContentLink.svelte" import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
export let note, showMedia export let note, showMedia
const {name, description, image} = Tags.from(note).asMeta() const {name, description, image} = Tags.from(note).getDict()
</script> </script>
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis"> <div class="flex flex-col gap-2 overflow-hidden text-ellipsis">

View File

@ -4,9 +4,8 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {switcherFn} from "hurdak" import {switcherFn} from "hurdak"
import {fromNostrURI} from "paravel" import {fromNostrURI, Tags} from "paravel"
import {warn} from "src/util/logger" import {warn} from "src/util/logger"
import {Tags} from "src/util/nostr"
import {urlIsMedia} from "src/util/notes" import {urlIsMedia} from "src/util/notes"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
@ -26,7 +25,7 @@
let content let content
const tags = Tags.from(note) const tags = Tags.from(note)
const regex = /(nostr:)?n(event|ote|pub|profile|addr)\w{10,1000}/g const regex = /(nostr:)?n(event|ote|pub|profile|addr)\w{10,1000}/g
const {title, summary, image} = tags.asMeta() as {[k: string]: string} const {title, summary, image} = tags.getDict() as {[k: string]: string}
const convertEntities = markdown => { const convertEntities = markdown => {
for (const uri of markdown.match(regex) || []) { for (const uri of markdown.match(regex) || []) {
@ -78,7 +77,7 @@
<NoteContentLink value={{url: image, isMedia: true}} showMedia /> <NoteContentLink value={{url: image, isMedia: true}} showMedia />
{/if} {/if}
<div> <div>
{#each tags.topics() as topic} {#each tags.topics().all() as topic}
<Anchor modal href={router.at("topics").of(topic).toString()}> <Anchor modal href={router.at("topics").of(topic).toString()}>
<Chip class="mb-2 mr-2 inline-block cursor-pointer">#{topic}</Chip> <Chip class="mb-2 mr-2 inline-block cursor-pointer">#{topic}</Chip>
</Anchor> </Anchor>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {urlIsMedia} from "src/util/notes" import {urlIsMedia} from "src/util/notes"
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
@ -15,7 +15,7 @@
const tags = Tags.from(note) const tags = Tags.from(note)
const naddr = Naddr.fromEvent(note).encode() const naddr = Naddr.fromEvent(note).encode()
const {title, summary, image, status, p} = tags.asMeta() as Record<string, string> const {title, summary, image, status, p} = tags.getDict() as Record<string, string>
</script> </script>
<Anchor external href={`https://zap.stream/${naddr}`}> <Anchor external href={`https://zap.stream/${naddr}`}>

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
import NoteContentLink from "src/app/shared/NoteContentLink.svelte" import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
export let note, showMedia export let note, showMedia
const {c, cover, media, subject} = Tags.from(note).asMeta() const {c, cover, media, subject} = Tags.from(note).getDict()
</script> </script>
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis"> <div class="flex flex-col gap-2 overflow-hidden text-ellipsis">

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "paravel"
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
import {Tags} from "src/util/nostr"
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
import ImageCircle from "src/partials/ImageCircle.svelte" import ImageCircle from "src/partials/ImageCircle.svelte"
@ -9,7 +9,7 @@
export let note export let note
const {name, picture, about} = tryJson(() => JSON.parse(note.content)) const {name, picture, about} = tryJson(() => JSON.parse(note.content))
const noteId = nip19.noteEncode(note.kind === 40 ? note.id : Tags.from(note).getMeta("e")) const noteId = nip19.noteEncode(note.kind === 40 ? note.id : Tags.from(note).getValue("e"))
const goToChat = () => window.open(`https://chat.coracle.social/chat/${noteId}`) const goToChat = () => window.open(`https://chat.coracle.social/chat/${noteId}`)
</script> </script>

View File

@ -1,12 +1,12 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {urlIsMedia} from "src/util/notes" import {urlIsMedia} from "src/util/notes"
import NoteContentKind1 from "src/app/shared/NoteContentKind1.svelte" import NoteContentKind1 from "src/app/shared/NoteContentKind1.svelte"
import NoteContentLink from "src/app/shared/NoteContentLink.svelte" import NoteContentLink from "src/app/shared/NoteContentLink.svelte"
export let note, anchorId, maxLength, showEntire, showMedia export let note, anchorId, maxLength, showEntire, showMedia
const ref = Tags.from(note).getMeta("r") const ref = Tags.from(note).getValue("r")
</script> </script>
<div class="flex flex-col gap-2 overflow-hidden text-ellipsis"> <div class="flex flex-col gap-2 overflow-hidden text-ellipsis">

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
export let note export let note

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {switcherFn} from "hurdak" import {switcherFn} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Rating from "src/partials/Rating.svelte" import Rating from "src/partials/Rating.svelte"
import {router} from "src/app/router" import {router} from "src/app/router"
@ -8,10 +8,9 @@
export let note, rating export let note, rating
const tag = const tag = Tags.from(note)
Tags.from(note) .reject(t => ["l", "L"].includes(t[0]))
.reject(t => ["l", "L"].includes(t[0])) .first()
.first() || []
let href let href
let display let display

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
import {router} from "src/app/router" import {router} from "src/app/router"
@ -8,7 +8,7 @@
</script> </script>
<div> <div>
{#each Tags.from(note).topics() as topic} {#each Tags.from(note).topics().all() as topic}
<Anchor modal href={router.at("topics").of(topic).toString()}> <Anchor modal href={router.at("topics").of(topic).toString()}>
<Chip class="mb-2 mr-2 inline-block cursor-pointer">#{topic}</Chip> <Chip class="mb-2 mr-2 inline-block cursor-pointer">#{topic}</Chip>
</Anchor> </Anchor>

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "paravel"
import {createEventDispatcher} from "svelte" import {createEventDispatcher} from "svelte"
import {without, uniq} from "ramda" import {without, uniq} from "ramda"
import {slide} from "src/util/transition" import {slide} from "src/util/transition"
import {Tags} from "src/util/nostr"
import ImageInput from "src/partials/ImageInput.svelte" import ImageInput from "src/partials/ImageInput.svelte"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
import Media from "src/partials/Media.svelte" import Media from "src/partials/Media.svelte"

View File

@ -2,9 +2,9 @@
import {onDestroy} from "svelte" import {onDestroy} from "svelte"
import {groupBy, filter} from "ramda" import {groupBy, filter} from "ramda"
import {mapVals} from "hurdak" import {mapVals} from "hurdak"
import {isShareableRelay} from "paravel" import {isShareableRelay, Tags} from "paravel"
import {createScroller} from "src/util/misc" import {createScroller} from "src/util/misc"
import {Tags, getAvgQuality} from "src/util/nostr" import {getAvgQuality} from "src/util/nostr"
import {getModal} from "src/partials/state" import {getModal} from "src/partials/state"
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import RelayCard from "src/app/shared/RelayCard.svelte" import RelayCard from "src/app/shared/RelayCard.svelte"
@ -38,7 +38,7 @@
$: ratings = mapVals( $: ratings = mapVals(
events => getAvgQuality("review/relay", events), events => getAvgQuality("review/relay", events),
groupBy(e => Tags.from(e).getMeta("r"), reviews) groupBy(e => Tags.from(e).getValue("r"), reviews)
) )
load({ load({

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import {nip05} from "nostr-tools" import {nip05} from "nostr-tools"
import {Tags} from "paravel"
import {uniqBy, sortBy} from "ramda" import {uniqBy, sortBy} from "ramda"
import {batch, quantify} from "hurdak" import {batch, quantify} from "hurdak"
import {tryJson, displayDomain, pushToKey} from "src/util/misc" import {tryJson, displayDomain, pushToKey} from "src/util/misc"
import {copyToClipboard} from "src/util/html" import {copyToClipboard} from "src/util/html"
import {Tags} from "src/util/nostr"
import {toast} from "src/partials/state" import {toast} from "src/partials/state"
import Image from "src/partials/Image.svelte" import Image from "src/partials/Image.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
@ -58,7 +58,7 @@
pubkeys.push(e.pubkey) pubkeys.push(e.pubkey)
if (e.kind === 31990) { if (e.kind === 31990) {
;(e as any).address = [e.kind, e.pubkey, Tags.from(e).getMeta("d")].join(":") ;(e as any).address = [e.kind, e.pubkey, Tags.from(e).getValue("d")].join(":")
handlers = handlers.concat(e) handlers = handlers.concat(e)
} else { } else {

View File

@ -2,7 +2,7 @@
import {onMount} from "svelte" import {onMount} from "svelte"
import {identity, sortBy} from "ramda" import {identity, sortBy} from "ramda"
import {quantify} from "hurdak" import {quantify} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Card from "src/partials/Card.svelte" import Card from "src/partials/Card.svelte"
import Chip from "src/partials/Chip.svelte" import Chip from "src/partials/Chip.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"

View File

@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import cx from "classnames" import cx from "classnames"
import {Tags, noteKinds} from "src/util/nostr" import {Tags} from "paravel"
import {noteKinds} from "src/util/nostr"
import {theme} from "src/partials/state" import {theme} from "src/partials/state"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
@ -24,9 +25,9 @@
const loadListFeed = naddr => { const loadListFeed = naddr => {
const list = lists.key(naddr).get() const list = lists.key(naddr).get()
const authors = Tags.wrap(list.tags).pubkeys() const authors = Tags.from(list).pubkeys().all()
const topics = Tags.wrap(list.tags).topics() const topics = Tags.from(list).topics().all()
const urls = Tags.wrap(list.tags).urls() const urls = Tags.from(list).urls().all()
if (urls.length > 0) { if (urls.length > 0) {
relays = urls relays = urls

View File

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import {identity} from "ramda" import {identity} from "ramda"
import {Tags} from "paravel"
import {onMount} from "svelte" import {onMount} from "svelte"
import {fly} from "src/util/transition" import {fly} from "src/util/transition"
import {findReplyId, Tags} from "src/util/nostr"
import {createScroller} from "src/util/misc" import {createScroller} from "src/util/misc"
import {getModal} from "src/partials/state" import {getModal} from "src/partials/state"
import Content from "src/partials/Content.svelte" import Content from "src/partials/Content.svelte"
@ -31,9 +31,9 @@
limit += 5 limit += 5
} }
$: ids = sortEventsDesc($labels.filter(e => Tags.from(e).getMeta("l") === label)) $: ids = sortEventsDesc($labels.filter(e => Tags.from(e).getValue("l") === label))
.slice(0, limit) .slice(0, limit)
.map(findReplyId) .map(e => Tags.from(e).getReply())
onMount(() => { onMount(() => {
const scroller = createScroller(loadMore, {element: getModal()}) const scroller = createScroller(loadMore, {element: getModal()})

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {toast} from "src/partials/state" import {toast} from "src/partials/state"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import PersonBadge from "src/app/shared/PersonBadge.svelte" import PersonBadge from "src/app/shared/PersonBadge.svelte"
@ -28,7 +28,7 @@
const tags = list ? Tags.from(list) : Tags.from([]) const tags = list ? Tags.from(list) : Tags.from([])
let values = { let values = {
name: tags.getMeta("d") || "", name: tags.getValue("d") || "",
params: tags.type(["t", "p"]).all(), params: tags.type(["t", "p"]).all(),
relays: tags.type("r").all(), relays: tags.type("r").all(),
} }

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import {updateIn} from "hurdak" import {updateIn} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import Heading from "src/partials/Heading.svelte" import Heading from "src/partials/Heading.svelte"
import Anchor from "src/partials/Anchor.svelte" import Anchor from "src/partials/Anchor.svelte"
import BorderLeft from "src/partials/BorderLeft.svelte" import BorderLeft from "src/partials/BorderLeft.svelte"
@ -35,7 +35,7 @@
Select a list to modify. The selected {label} will be added to it as an additional filter. Select a list to modify. The selected {label} will be added to it as an additional filter.
</p> </p>
{#each $userLists as list (list.naddr)} {#each $userLists as list (list.naddr)}
{@const meta = Tags.wrap(list.tags).asMeta()} {@const meta = Tags.from(list).getDict()}
<BorderLeft on:click={() => selectlist(list)}> <BorderLeft on:click={() => selectlist(list)}>
<strong>{meta.d}</strong> <strong>{meta.d}</strong>
<ListSummary {list} /> <ListSummary {list} />

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {pluck} from "ramda" import {pluck} from "ramda"
import {Tags} from "paravel"
import {formatTimestamp} from "src/util/misc" import {formatTimestamp} from "src/util/misc"
import {findReplyId} from "src/util/nostr"
import Note from "src/app/shared/Note.svelte" import Note from "src/app/shared/Note.svelte"
import NotificationPeople from "src/app/shared/NotificationPeople.svelte" import NotificationPeople from "src/app/shared/NotificationPeople.svelte"
import type {Notification} from "src/engine" import type {Notification} from "src/engine"
@ -9,7 +9,7 @@
export let notification: Notification export let notification: Notification
const {timestamp, interactions} = notification const {timestamp, interactions} = notification
const parentId = findReplyId(interactions[0]) const parentId = Tags.from(interactions[0]).getReply()
const note = parentId ? {id: parentId} : interactions[0] const note = parentId ? {id: parentId} : interactions[0]
</script> </script>

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import {onMount} from "svelte" import {onMount} from "svelte"
import {pluck, identity} from "ramda" import {pluck, equals, identity} from "ramda"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {toast, appName} from "src/partials/state" import {toast, appName} from "src/partials/state"
import Input from "src/partials/Input.svelte" import Input from "src/partials/Input.svelte"
import Field from "src/partials/Field.svelte" import Field from "src/partials/Field.svelte"
@ -23,7 +23,7 @@
publishMutes, publishMutes,
} from "src/engine" } from "src/engine"
const muteTags = Tags.wrap($user.mutes || []) const muteTags = new Tags($user.mutes || [])
let settings = getSettings() let settings = getSettings()
let mutedPeople = muteTags.type("p").values().all().map(getPersonWithDefault) let mutedPeople = muteTags.type("p").values().all().map(getPersonWithDefault)
@ -32,7 +32,7 @@
const submit = () => { const submit = () => {
const pubkeyMutes = mutedPeople.map(p => ["p", p.pubkey]) const pubkeyMutes = mutedPeople.map(p => ["p", p.pubkey])
const otherMutes = muteTags.type("p", {reject: true}).all() const otherMutes = muteTags.reject(equals("p")).all()
const allMutes = [...pubkeyMutes, ...otherMutes] const allMutes = [...pubkeyMutes, ...otherMutes]
publishSettings(settings) publishSettings(settings)

View File

@ -1,7 +1,8 @@
import {prop, uniqBy, uniq} from "ramda" import {prop, uniqBy, uniq} from "ramda"
import {Tags} from "paravel"
import {tryFunc, sleep} from "hurdak" import {tryFunc, sleep} from "hurdak"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
import {Tags, appDataKeys} from "src/util/nostr" import {appDataKeys} from "src/util/nostr"
import {EventKind} from "src/engine/events/model" import {EventKind} from "src/engine/events/model"
import {sessions} from "src/engine/session/state" import {sessions} from "src/engine/session/state"
import {Signer, Nip04, getNdk} from "src/engine/session/utils" import {Signer, Nip04, getNdk} from "src/engine/session/utils"
@ -16,7 +17,7 @@ const getSigner = session => new Signer(session, getNdk(session))
const getNip04 = session => new Nip04(session, getNdk(session)) const getNip04 = session => new Nip04(session, getNdk(session))
projections.addHandler(EventKind.AppData, async e => { projections.addHandler(EventKind.AppData, async e => {
const d = Tags.from(e).getMeta("d") const d = Tags.from(e).getValue("d")
const session = getSession(e.pubkey) const session = getSession(e.pubkey)
if (!session) { if (!session) {

View File

@ -1,5 +1,5 @@
import {whereEq, find} from "ramda" import {whereEq, find} from "ramda"
import {findReplyAndRootIds} from "src/util/nostr" import {Tags} from "paravel"
import {derived, DerivedCollection} from "src/engine/core/utils" import {derived, DerivedCollection} from "src/engine/core/utils"
import {pubkey} from "src/engine/session/state" import {pubkey} from "src/engine/session/state"
import {settings} from "src/engine/session/derived" import {settings} from "src/engine/session/derived"
@ -28,7 +28,8 @@ export const isEventMuted = derived([mutes, settings, pubkey], ([$mutes, $settin
return false return false
} }
const {reply, root} = findReplyAndRootIds(e) const reply = Tags.from(e).getReply()
const root = Tags.from(e).getRoot()
if (find(t => $mutes.has(t), [e.id, e.pubkey, reply, root])) { if (find(t => $mutes.has(t), [e.id, e.pubkey, reply, root])) {
return true return true

View File

@ -1,5 +1,5 @@
import {batch} from "hurdak" import {batch} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {sessions} from "src/engine/session/state" import {sessions} from "src/engine/session/state"
@ -31,7 +31,7 @@ projections.addHandler(EventKind.Delete, e => {
}) })
projections.addHandler(EventKind.GiftWrap, e => { projections.addHandler(EventKind.GiftWrap, e => {
const session = sessions.get()[Tags.from(e).getMeta("p")] const session = sessions.get()[Tags.from(e).getValue("p")]
if (session?.method !== "privkey") { if (session?.method !== "privkey") {
return return

View File

@ -1,9 +1,8 @@
import type {AddressPointer} from "nostr-tools/lib/nip19" import type {AddressPointer} from "nostr-tools/lib/nip19"
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {sortBy} from "ramda" import {sortBy} from "ramda"
import {fromNostrURI} from "paravel" import {fromNostrURI, Tags} from "paravel"
import {tryFunc, switcherFn} from "hurdak" import {tryFunc, switcherFn} from "hurdak"
import {findReplyId, Tags} from "src/util/nostr"
import {getEventHints} from "src/engine/relays/utils" import {getEventHints} from "src/engine/relays/utils"
import type {Event} from "./model" import type {Event} from "./model"
@ -21,7 +20,7 @@ export const getIds = (e: Event) => {
return ids return ids
} }
export const isChildOf = (a, b) => getIds(b).includes(findReplyId(a)) export const isChildOf = (a, b) => getIds(b).includes(Tags.from(a).getReply())
const annotateEvent = eid => ({ const annotateEvent = eid => ({
eid, eid,
@ -65,7 +64,7 @@ export class Naddr {
} }
static fromEvent = (e: Event) => static fromEvent = (e: Event) =>
new Naddr(e.kind, e.pubkey, Tags.from(e).getMeta("d"), getEventHints(e)) new Naddr(e.kind, e.pubkey, Tags.from(e).getValue("d"), getEventHints(e))
static fromTagValue = (a, relays = []) => { static fromTagValue = (a, relays = []) => {
const [kind, pubkey, identifier] = a.split(":") const [kind, pubkey, identifier] = a.split(":")

View File

@ -1,5 +1,5 @@
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {updateRecord} from "src/engine/core/commands" import {updateRecord} from "src/engine/core/commands"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
@ -7,7 +7,7 @@ import {EventKind} from "src/engine/events/model"
import {_lists} from "./state" import {_lists} from "./state"
projections.addHandler(EventKind.BookmarkList, (e: Event) => { projections.addHandler(EventKind.BookmarkList, (e: Event) => {
const name = Tags.from(e).getMeta("d") const name = Tags.from(e).getValue("d")
const naddr = nip19.naddrEncode({ const naddr = nip19.naddrEncode({
identifier: name, identifier: name,
pubkey: e.pubkey, pubkey: e.pubkey,

View File

@ -13,9 +13,9 @@ import {
assoc, assoc,
} from "ramda" } from "ramda"
import {ensurePlural, doPipe, batch} from "hurdak" import {ensurePlural, doPipe, batch} from "hurdak"
import {now} from "paravel" import {now, Tags} from "paravel"
import {race, pushToKey} from "src/util/misc" import {race, pushToKey} from "src/util/misc"
import {findReplyId, noteKinds, reactionKinds, LOCAL_RELAY_URL} from "src/util/nostr" import {noteKinds, reactionKinds, LOCAL_RELAY_URL} from "src/util/nostr"
import type {DisplayEvent} from "src/engine/notes/model" import type {DisplayEvent} from "src/engine/notes/model"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {isEventMuted} from "src/engine/events/derived" import {isEventMuted} from "src/engine/events/derived"
@ -99,7 +99,7 @@ export class FeedLoader {
return false return false
} }
if (this.opts.shouldHideReplies && findReplyId(e)) { if (this.opts.shouldHideReplies && Tags.from(e).getReply()) {
return false return false
} }
@ -108,7 +108,9 @@ export class FeedLoader {
} }
loadParents = notes => { loadParents = notes => {
const parentIds = reject(this.isEventMuted, notes).map(findReplyId).filter(identity) const parentIds = reject(this.isEventMuted, notes)
.map(e => Tags.from(e).getReply())
.filter(identity)
load({ load({
relays: this.opts.relays.concat(LOCAL_RELAY_URL), relays: this.opts.relays.concat(LOCAL_RELAY_URL),
@ -156,7 +158,7 @@ export class FeedLoader {
.map(e => { .map(e => {
/* eslint no-constant-condition: 0 */ /* eslint no-constant-condition: 0 */
while (true) { while (true) {
const parentId = findReplyId(e) const parentId = Tags.from(e).getReply()
if (!parentId) { if (!parentId) {
break break
@ -236,7 +238,7 @@ export class FeedLoader {
// If something has a parent id but we haven't found the parent yet, skip it until we have it. // If something has a parent id but we haven't found the parent yet, skip it until we have it.
const [defer, ok] = partition(e => { const [defer, ok] = partition(e => {
const parentId = findReplyId(e) const parentId = Tags.from(e).getReply()
return parentId && !this.parents.get(parentId) return parentId && !this.parents.get(parentId)
}, notes) }, notes)

View File

@ -1,6 +1,6 @@
import {omit, find, prop, groupBy, uniq} from "ramda" import {omit, find, prop, groupBy, uniq} from "ramda"
import {shuffle, randomId, seconds, avg} from "hurdak" import {shuffle, randomId, seconds, avg} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {env, pubkey} from "src/engine/session/state" import {env, pubkey} from "src/engine/session/state"
import {follows, network} from "src/engine/people/derived" import {follows, network} from "src/engine/people/derived"
import {mergeHints, getPubkeyHints} from "src/engine/relays/utils" import {mergeHints, getPubkeyHints} from "src/engine/relays/utils"
@ -67,9 +67,9 @@ export const getReplyFilters = (events, filter) => {
e.push(event.id) e.push(event.id)
if (event.kind >= 10000) { if (event.kind >= 10000) {
const tags = Tags.from(event).asMeta() const d = Tags.from(event).getValue("d") || ""
a.push([event.kind, event.pubkey, tags.d || ""].join(":")) a.push([event.kind, event.pubkey, d].join(":"))
} }
} }

View File

@ -1,10 +1,9 @@
import EventEmitter from "events" import EventEmitter from "events"
import {createEvent} from "paravel" import {createEvent, Tags} from "paravel"
import {last, omit, uniqBy} from "ramda" import {omit, uniqBy} from "ramda"
import {doPipe, defer, union, difference} from "hurdak" import {defer, union, difference} from "hurdak"
import {info} from "src/util/logger" import {info} from "src/util/logger"
import {parseContent} from "src/util/notes" import {parseContent} from "src/util/notes"
import {Tags, findRoot, findReply} from "src/util/nostr"
import type {Event, NostrEvent} from "src/engine/events/model" import type {Event, NostrEvent} from "src/engine/events/model"
import {people} from "src/engine/people/state" import {people} from "src/engine/people/state"
import {displayPerson} from "src/engine/people/utils" import {displayPerson} from "src/engine/people/utils"
@ -179,19 +178,17 @@ export const tagsFromContent = (content: string) => {
} }
export const getReplyTags = (parent: Event, inherit = false) => { export const getReplyTags = (parent: Event, inherit = false) => {
const extra = inherit
? Tags.from(parent)
.type(["e", "a"])
.reject(t => last(t) === "mention")
.all()
.map(t => t.slice(0, 3))
: []
const hint = getEventHint(parent) const hint = getEventHint(parent)
const reply = ["e", parent.id, hint, "reply"] const tags = Tags.from(parent).normalize()
const root = doPipe(findRoot(parent) || findReply(parent) || reply, [ const reply = tags.mark("reply").first() || ["e", parent.id, hint, "reply"]
t => (t.length < 3 ? t.concat(hint) : t), const root = tags.mark("root").first() || reply.slice(0, 3).concat("root")
t => t.slice(0, 3).concat("root"), const extra = inherit
]) ? tags
.type(["a", "e"])
.reject(t => [reply[1], root[1]].includes(t[1]))
.take(3)
.all()
: []
if (isReplaceable(parent)) { if (isReplaceable(parent)) {
extra.push(Naddr.fromEvent(parent).asTag("reply")) extra.push(Naddr.fromEvent(parent).asTag("reply"))

View File

@ -1,6 +1,7 @@
import {uniqBy, identity, prop, sortBy} from "ramda" import {uniqBy, identity, prop, sortBy} from "ramda"
import {batch} from "hurdak" import {batch} from "hurdak"
import {LOCAL_RELAY_URL, findReplyId, findRootId} from "src/util/nostr" import {Tags} from "paravel"
import {LOCAL_RELAY_URL} from "src/util/nostr"
import type {DisplayEvent} from "src/engine/notes/model" import type {DisplayEvent} from "src/engine/notes/model"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {writable} from "src/engine/core/utils" import {writable} from "src/engine/core/utils"
@ -16,7 +17,7 @@ export class ThreadLoader {
root = writable<DisplayEvent>(null) root = writable<DisplayEvent>(null)
constructor(readonly note: Event, readonly relays: string[]) { constructor(readonly note: Event, readonly relays: string[]) {
this.loadNotes([findReplyId(note), findRootId(note)]) this.loadNotes(Tags.from(note).type(["e", "a"]).values().all())
} }
stop() { stop() {
@ -37,7 +38,7 @@ export class ThreadLoader {
filters: getIdFilters(filteredIds), filters: getIdFilters(filteredIds),
onEvent: batch(300, (events: Event[]) => { onEvent: batch(300, (events: Event[]) => {
this.addToThread(events) this.addToThread(events)
this.loadNotes(events.flatMap(e => [findReplyId(e), findRootId(e)])) this.loadNotes(events.flatMap(e => Tags.from(e).type(["e", "a"]).values().all()))
}), }),
}) })
} }
@ -52,12 +53,13 @@ export class ThreadLoader {
} }
addToThread(events) { addToThread(events) {
const tags = Tags.from(this.note).normalize()
const ancestors = [] const ancestors = []
for (const event of events) { for (const event of events) {
if (event.id === findReplyId(this.note)) { if (event.id === tags.getReply()) {
this.parent.set(event) this.parent.set(event)
} else if (event.id === findRootId(this.note)) { } else if (event.id === tags.getRoot()) {
this.root.set(event) this.root.set(event)
} else { } else {
ancestors.push(event) ancestors.push(event)

View File

@ -1,6 +1,6 @@
import {find, pluck, whereEq} from "ramda" import {find, pluck, whereEq} from "ramda"
import {batch, sleep} from "hurdak" import {batch, sleep} from "hurdak"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {env} from "src/engine/session/state" import {env} from "src/engine/session/state"
import {loadOne, getIdFilters, dvmRequest} from "src/engine/network/utils" import {loadOne, getIdFilters, dvmRequest} from "src/engine/network/utils"
import {selectHints, mergeHints} from "src/engine/relays/utils" import {selectHints, mergeHints} from "src/engine/relays/utils"
@ -32,7 +32,7 @@ export const dereferenceNote = async ({
return false return false
} }
return !identifier || Tags.from(e).getMeta("d") === identifier return !identifier || Tags.from(e).getValue("d") === identifier
}, context) }, context)
if (note) { if (note) {

View File

@ -1,7 +1,7 @@
import {prop, max, sortBy} from "ramda" import {prop, max, sortBy} from "ramda"
import {seconds} from "hurdak" import {seconds} from "hurdak"
import {now} from "paravel" import {now, Tags} from "paravel"
import {Tags, reactionKinds, findReplyId, findReplyAndRootIds} from "src/util/nostr" import {reactionKinds} from "src/util/nostr"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
import {events, isEventMuted} from "src/engine/events/derived" import {events, isEventMuted} from "src/engine/events/derived"
import {derived} from "src/engine/core/utils" import {derived} from "src/engine/core/utils"
@ -18,16 +18,16 @@ export const notifications = derived(
const $isEventMuted = isEventMuted.get() const $isEventMuted = isEventMuted.get()
return $events.filter(e => { return $events.filter(e => {
const {root, reply} = findReplyAndRootIds(e)
if (e.pubkey === $session.pubkey || $isEventMuted(e)) { if (e.pubkey === $session.pubkey || $isEventMuted(e)) {
return false return false
} }
const tags = Tags.from(e).normalize()
return ( return (
$userEvents.get(root) || $userEvents.get(tags.mark("root").getValue()) ||
$userEvents.get(reply) || $userEvents.get(tags.mark("reply").getValue()) ||
Tags.from(e).pubkeys().includes($session.pubkey) tags.pubkeys().has($session.pubkey)
) )
}) })
} }
@ -51,7 +51,7 @@ export const groupNotifications = ($notifications, kinds) => {
// Convert zaps to zap requests // Convert zaps to zap requests
const convertZap = e => { const convertZap = e => {
if (e.kind === 9735) { if (e.kind === 9735) {
return tryJson(() => JSON.parse(Tags.from(e).asMeta().description as string)) return tryJson(() => JSON.parse(Tags.from(e).getDict().description as string))
} }
return e return e
@ -61,7 +61,7 @@ export const groupNotifications = ($notifications, kinds) => {
// Group notifications by event // Group notifications by event
for (const ix of $notifications) { for (const ix of $notifications) {
const parentId = findReplyId(ix) const parentId = Tags.from(ix).getReply()
const event = $userEvents.get(parentId) const event = $userEvents.get(parentId)
if (!kinds.includes(ix.kind)) { if (!kinds.includes(ix.kind)) {

View File

@ -1,7 +1,7 @@
import {now} from "paravel" import {now, Tags} from "paravel"
import {seconds, batch, doPipe} from "hurdak" import {seconds, batch, doPipe} from "hurdak"
import {pluck, identity, max, slice, filter, without, sortBy} from "ramda" import {pluck, identity, max, slice, filter, without, sortBy} from "ramda"
import {noteKinds, findReplyId, reactionKinds} from "src/util/nostr" import {noteKinds, reactionKinds} from "src/util/nostr"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import type {Filter} from "src/engine/network/model" import type {Filter} from "src/engine/network/model"
import {EventKind} from "src/engine/events/model" import {EventKind} from "src/engine/events/model"
@ -21,13 +21,13 @@ const onNotificationEvent = batch(300, (chunk: Event[]) => {
const kinds = getNotificationKinds() const kinds = getNotificationKinds()
const $isEventMuted = isEventMuted.get() const $isEventMuted = isEventMuted.get()
const events = chunk.filter(e => kinds.includes(e.kind) && !$isEventMuted(e)) const events = chunk.filter(e => kinds.includes(e.kind) && !$isEventMuted(e))
const eventsWithParent = chunk.filter(e => findReplyId(e)) const eventsWithParent = chunk.filter(e => Tags.from(e).getReply())
loadPubkeys(pluck("pubkey", events)) loadPubkeys(pluck("pubkey", events))
load({ load({
relays: mergeHints(eventsWithParent.map(getParentHints)), relays: mergeHints(eventsWithParent.map(getParentHints)),
filters: getIdFilters(eventsWithParent.map(findReplyId)), filters: getIdFilters(eventsWithParent.map(e => Tags.from(e).getReply())),
onEvent: e => _events.update($events => $events.concat(e)), onEvent: e => _events.update($events => $events.concat(e)),
}) })

View File

@ -1,4 +1,4 @@
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
import {updateStore} from "src/engine/core/commands" import {updateStore} from "src/engine/core/commands"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"

View File

@ -1,8 +1,6 @@
import {drop} from "ramda" import {normalizeRelayUrl, isShareableRelay, Tags} from "paravel"
import {normalizeRelayUrl, isShareableRelay} from "paravel"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
import {warn} from "src/util/logger" import {warn} from "src/util/logger"
import {Tags} from "src/util/nostr"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"
import type {RelayPolicy} from "./model" import type {RelayPolicy} from "./model"
import {RelayMode} from "./model" import {RelayMode} from "./model"
@ -35,8 +33,8 @@ projections.addHandler(10002, e => {
e, e,
Tags.from(e) Tags.from(e)
.type(["r", "relay"]) .type(["r", "relay"])
.drop(1)
.all() .all()
.map(drop(1))
.filter(([url]: [string]) => isShareableRelay(url)) .filter(([url]: [string]) => isShareableRelay(url))
.map(([url, mode]: [string, string]) => { .map(([url, mode]: [string, string]) => {
const write = !mode || mode === RelayMode.Write const write = !mode || mode === RelayMode.Write

View File

@ -1,9 +1,14 @@
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import {isShareableRelay, normalizeRelayUrl as _normalizeRelayUrl, fromNostrURI} from "paravel" import {
Tags,
isShareableRelay,
normalizeRelayUrl as _normalizeRelayUrl,
fromNostrURI,
} from "paravel"
import {sortBy, pluck, uniq, nth, prop, last} from "ramda" import {sortBy, pluck, uniq, nth, prop, last} from "ramda"
import {chain, displayList, first} from "hurdak" import {chain, displayList, first} from "hurdak"
import {fuzzy} from "src/util/misc" import {fuzzy} from "src/util/misc"
import {LOCAL_RELAY_URL, findReplyId, findRootId, Tags} from "src/util/nostr" import {LOCAL_RELAY_URL} from "src/util/nostr"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {env} from "src/engine/session/state" import {env} from "src/engine/session/state"
import {stateKey} from "src/engine/session/derived" import {stateKey} from "src/engine/session/derived"
@ -161,16 +166,12 @@ export const getReplyHints = hintSelector(function* (event) {
// If we're looking for an event's parent, tags are the most reliable hint, // If we're looking for an event's parent, tags are the most reliable hint,
// but we can also look at where the author of the note reads from // but we can also look at where the author of the note reads from
export const getParentHints = hintSelector(function* (event) { export const getParentHints = hintSelector(function* (event) {
const parentId = findReplyId(event) yield* Tags.from(event).normalize().mark("reply").relays().all()
yield* Tags.from(event).equals(parentId).relays()
yield* getPubkeyRelayUrls(event.pubkey, RelayMode.Read) yield* getPubkeyRelayUrls(event.pubkey, RelayMode.Read)
}) })
export const getRootHints = hintSelector(function* (event) { export const getRootHints = hintSelector(function* (event) {
const rootId = findRootId(event) yield* Tags.from(event).normalize().mark("root").relays().all()
yield* Tags.from(event).equals(rootId).relays()
yield* getPubkeyRelayUrls(event.pubkey, RelayMode.Read) yield* getPubkeyRelayUrls(event.pubkey, RelayMode.Read)
}) })

View File

@ -1,5 +1,6 @@
import {tryFunc} from "hurdak" import {tryFunc} from "hurdak"
import {Tags, appDataKeys} from "src/util/nostr" import {Tags} from "paravel"
import {appDataKeys} from "src/util/nostr"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"
import {updateRecord} from "src/engine/core/commands" import {updateRecord} from "src/engine/core/commands"
import {EventKind} from "src/engine/events/model" import {EventKind} from "src/engine/events/model"
@ -7,7 +8,7 @@ import {sessions} from "./state"
import {nip04} from "./derived" import {nip04} from "./derived"
projections.addHandler(EventKind.AppData, e => { projections.addHandler(EventKind.AppData, e => {
if (Tags.from(e).getMeta("d") === appDataKeys.USER_SETTINGS) { if (Tags.from(e).getValue("d") === appDataKeys.USER_SETTINGS) {
sessions.updateAsync(async $sessions => { sessions.updateAsync(async $sessions => {
if ($sessions[e.pubkey]) { if ($sessions[e.pubkey]) {
await tryFunc(async () => { await tryFunc(async () => {

View File

@ -1,5 +1,5 @@
import {nth, inc} from "ramda" import {nth, inc} from "ramda"
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {topics} from "./state" import {topics} from "./state"
@ -15,7 +15,7 @@ export const addTopic = (e, name) => {
} }
export const processTopics = (e: Event) => { export const processTopics = (e: Event) => {
const tagTopics = Tags.from(e).topics() const tagTopics = Tags.from(e).topics().all()
const contentTopics = Array.from(e.content.toLowerCase().matchAll(/#(\w{2,100})/g)).map(nth(1)) const contentTopics = Array.from(e.content.toLowerCase().matchAll(/#(\w{2,100})/g)).map(nth(1))
for (const name of tagTopics.concat(contentTopics)) { for (const name of tagTopics.concat(contentTopics)) {

View File

@ -1,4 +1,4 @@
import {Tags} from "src/util/nostr" import {Tags} from "paravel"
import {projections} from "src/engine/core/projections" import {projections} from "src/engine/core/projections"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
import {EventKind} from "src/engine/events/model" import {EventKind} from "src/engine/events/model"

View File

@ -1,8 +1,7 @@
import {cached} from "paravel" import {cached, Tags} from "paravel"
import {identity, pick, uniq} from "ramda" import {identity, pick, uniq} from "ramda"
import {Fetch, tryFunc, createMapOf} from "hurdak" import {Fetch, tryFunc, createMapOf} from "hurdak"
import {tryJson, hexToBech32, bech32ToHex, createBatcher} from "src/util/misc" import {tryJson, hexToBech32, bech32ToHex, createBatcher} from "src/util/misc"
import {Tags} from "src/util/nostr"
import {people} from "src/engine/people/state" import {people} from "src/engine/people/state"
import {dufflepud} from "src/engine/session/utils" import {dufflepud} from "src/engine/session/utils"
import type {Event} from "src/engine/events/model" import type {Event} from "src/engine/events/model"
@ -118,7 +117,7 @@ export const processZap = (event, zapper) => {
return null return null
} }
const zapMeta = Tags.from(event).asMeta() as { const zapMeta = Tags.from(event).getDict() as {
bolt11: string bolt11: string
description: string description: string
} }
@ -139,7 +138,7 @@ export const processZap = (event, zapper) => {
} }
const {invoiceAmount, request} = zap const {invoiceAmount, request} = zap
const reqMeta = Tags.from(request).asMeta() as { const reqMeta = Tags.from(request).getDict() as {
amount?: string amount?: string
lnurl?: string lnurl?: string
} }

View File

@ -1,19 +1,7 @@
import {isShareableRelay} from "paravel" import {Tags} from "paravel"
import {nip19} from "nostr-tools" import {nip19} from "nostr-tools"
import { import {pick, is, mergeLeft, last, identity} from "ramda"
find, import {between, avg} from "hurdak"
complement,
pick,
is,
fromPairs,
mergeLeft,
last,
identity,
prop,
flatten,
uniq,
} from "ramda"
import {ensurePlural, between, mapVals, avg, first} from "hurdak"
import type {Filter, Event} from "src/engine" import type {Filter, Event} from "src/engine"
import {tryJson} from "src/util/misc" import {tryJson} from "src/util/misc"
@ -30,117 +18,6 @@ export const appDataKeys = {
NIP24_LAST_CHECKED: "nostr-engine/Nip24/last_checked/v1", NIP24_LAST_CHECKED: "nostr-engine/Nip24/last_checked/v1",
} }
export class Tags {
tags: any[]
constructor(tags: any[]) {
this.tags = tags.filter(identity)
}
static from(events: Event | Event[]) {
return new Tags(ensurePlural(events).flatMap(prop("tags")))
}
static wrap(tags: any[]) {
return new Tags(tags.filter(identity))
}
all() {
return this.tags
}
count() {
return this.tags.length
}
exists() {
return this.tags.length > 0
}
first() {
return first(this.tags)
}
nth(i: number) {
return this.tags[i]
}
last() {
return last(this.tags)
}
relays() {
return uniq(flatten(this.tags).filter(isShareableRelay))
}
topics() {
return this.type("t")
.values()
.all()
.map(t => t.replace(/^#/, ""))
}
pubkeys() {
return this.type("p").values().all()
}
urls() {
return this.type("r").values().all()
}
asMeta() {
return fromPairs(this.tags)
}
getMeta(k: string) {
return this.type(k).values().first()
}
drop(n) {
return new Tags(this.tags.map(t => t.slice(n)))
}
values() {
return new Tags(this.tags.map(t => t[1]))
}
filter(f: (t: any) => boolean) {
return new Tags(this.tags.filter(f))
}
reject(f: (t: any) => boolean) {
return new Tags(this.tags.filter(t => !f(t)))
}
any(f: (t: any) => boolean) {
return this.filter(f).exists()
}
type(type: string | string[], {reject = false} = {}) {
const types = ensurePlural(type)
const pred = t => types.includes(t[0])
return new Tags(this.tags.filter(reject ? complement(pred) : pred))
}
equals(value: string) {
return new Tags(this.tags.filter(t => t[1] === value))
}
mark(mark: string | string[]) {
const marks = ensurePlural(mark)
return new Tags(this.tags.filter(t => marks.includes(last(t))))
}
}
export const findReplyAndRoot = (e: Event) => {
const tags = Tags.from(e)
.type(["a", "e"])
.filter(t => last(t) !== "mention")
const legacy = tags.any(t => !["reply", "root"].includes(last(t)))
// Support the deprecated version where tags are not marked as replies
if (legacy) {
const reply = tags.last()
const root = tags.count() > 1 ? tags.first() : null
return {reply, root}
}
const reply = tags.mark("reply").first()
const root = tags.mark("root").first()
return {reply: reply || root, root}
}
export const findReplyAndRootIds = (e: Event) => mapVals(t => t?.[1], findReplyAndRoot(e))
export const findReply = (e: Event) => prop("reply", findReplyAndRoot(e))
export const findReplyId = (e: Event) => findReply(e)?.[1]
export const findRoot = (e: Event) => prop("root", findReplyAndRoot(e))
export const findRootId = (e: Event) => findRoot(e)?.[1]
export const isLike = (content: string) => export const isLike = (content: string) =>
["", "+", "🤙", "👍", "❤️", "😎", "🏅", "🫂", "🤣", "😂", "💜"].includes(content) ["", "+", "🤙", "👍", "❤️", "😎", "🏅", "🫂", "🤣", "😂", "💜"].includes(content)
@ -177,7 +54,7 @@ export const isHex = x => x.match(/^[a-f0-9]{64}$/)
export const getIdOrNaddr = e => { export const getIdOrNaddr = e => {
if (between(9999, 20000, e.kind) || between(39999, 40000, e.kind)) { if (between(9999, 20000, e.kind) || between(39999, 40000, e.kind)) {
return `${e.kind}:${e.pubkey}:${Tags.from(e).getMeta("d")}` return `${e.kind}:${e.pubkey}:${Tags.from(e).getValue("d")}`
} }
return e.id return e.id
@ -204,5 +81,5 @@ export const getContentWarning = e => {
return warning return warning
} }
return find(t => WARN_TAGS.has(t.toLowerCase()), tags.type("t").values().all()) return tags.topics().find(t => WARN_TAGS.has(t.toLowerCase()))
} }