2 Commits

Author SHA1 Message Date
3d778f7ec7 fix: handle invalid client tag 2025-04-30 12:22:18 +01:00
992d7f19be feat: application handler note render 2025-04-30 12:15:18 +01:00
9 changed files with 84 additions and 5 deletions

View File

@ -0,0 +1,39 @@
import { mapEventToProfile, TaggedNostrEvent } from "@snort/system";
import { FormattedMessage } from "react-intl";
import KindName from "../kind-name";
import Avatar from "../User/Avatar";
import DisplayName from "../User/DisplayName";
import ProfileImage from "../User/ProfileImage";
export function ApplicationHandler({ ev }: { ev: TaggedNostrEvent }) {
const profile = mapEventToProfile(ev);
const kinds = ev.tags
.filter(a => a[0] === "k")
.map(a => Number(a[1]))
.sort((a, b) => a - b);
return (
<div className="p flex gap-2 flex-col">
<div className="flex items-center gap-2 text-xl">
<Avatar user={profile} pubkey={""} size={120} />
<div className="flex flex-col gap-2">
<DisplayName user={profile} pubkey={""} />
<div className="text-sm flex items-center gap-2">
<div className="text-gray-light">
<FormattedMessage defaultMessage="Published by" />
</div>
<ProfileImage className="inline" pubkey={ev.pubkey} size={30} link="" />
</div>
</div>
</div>
<FormattedMessage defaultMessage="Supported Kinds:" />
<div className="flex flex-wrap">
{kinds.map(a => (
<div key={a} className="pill">
<KindName kind={a} />
</div>
))}
</div>
</div>
);
}

View File

@ -6,15 +6,15 @@ import { memo, ReactNode } from "react";
import PubkeyList from "@/Components/Embed/PubkeyList";
import ZapstrEmbed from "@/Components/Embed/ZapstrEmbed";
import ErrorBoundary from "@/Components/ErrorBoundary";
import { ApplicationHandler } from "@/Components/Event/Application";
import { LongFormText } from "@/Components/Event/LongFormText";
import { NostrFileElement } from "@/Components/Event/NostrFileHeader";
import { Note } from "@/Components/Event/Note/Note";
import NoteReaction from "@/Components/Event/NoteReaction";
import { ZapGoal } from "@/Components/Event/ZapGoal";
import { LiveEvent } from "@/Components/LiveStream/LiveEvent";
import ProfilePreview from "@/Components/User/ProfilePreview";
import { LongFormText } from "./LongFormText";
import { Note } from "./Note/Note";
export interface NotePropsOptions {
isRoot?: boolean;
showHeader?: boolean;
@ -75,6 +75,10 @@ export default memo(function EventComponent(props: NoteProps) {
case 9041: // Assuming 9041 is a valid EventKind
content = <ZapGoal ev={ev} />;
break;
case EventKind.ApplicationHandler: {
content = <ApplicationHandler ev={ev} />;
break;
}
case EventKind.Photo:
case EventKind.Video:
case EventKind.ShortVideo: {

View File

@ -5,7 +5,7 @@ import { Link } from "react-router-dom";
export function ClientTag({ ev }: { ev: TaggedNostrEvent }) {
const tag = ev.tags.find(a => a[0] === "client");
if (!tag) return;
const link = tag[2] && tag[2].includes(":") ? NostrLink.fromTag(["a", tag[2]]) : undefined;
const link = tag[2] && tag[2].includes(":") ? NostrLink.tryFromTag(["a", tag[2]]) : undefined;
return (
<span className="text-xs text-gray-light">
{" "}

View File

@ -1,3 +1,4 @@
/* eslint-disable max-lines */
import { FormattedMessage } from "react-intl";
export default function KindName({ kind }: { kind: number }) {
@ -222,5 +223,9 @@ export default function KindName({ kind }: { kind: number }) {
return <FormattedMessage defaultMessage="Video View Event" />;
case 34550:
return <FormattedMessage defaultMessage="Community Definition" />;
case 31337:
return <FormattedMessage defaultMessage="Zapstr Track" />;
default:
return kind;
}
}

View File

@ -64,7 +64,11 @@ export function Header() {
</>
);
} else if (nostrLink) {
if (nostrLink.type === NostrPrefix.Event || nostrLink.type === NostrPrefix.Note) {
if (
nostrLink.type === NostrPrefix.Event ||
nostrLink.type === NostrPrefix.Note ||
nostrLink.type === NostrPrefix.Address
) {
title = <NoteTitle link={nostrLink} />;
} else if (nostrLink.type === NostrPrefix.PublicKey || nostrLink.type === NostrPrefix.Profile) {
try {

View File

@ -270,6 +270,9 @@
"4emo2p": {
"defaultMessage": "Missing Relays"
},
"4oPRxH": {
"defaultMessage": "Supported Kinds:"
},
"4rYCjn": {
"defaultMessage": "Note to Self"
},
@ -1148,6 +1151,9 @@
"QWhotP": {
"defaultMessage": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)"
},
"QaGyuN": {
"defaultMessage": "Zapstr Track"
},
"QpaLA3": {
"defaultMessage": "Channel Message"
},
@ -1302,6 +1308,9 @@
"ULXFfP": {
"defaultMessage": "Receive"
},
"ULsJTk": {
"defaultMessage": "Published by"
},
"UNjfWJ": {
"defaultMessage": "Check all event signatures received from relays"
},

View File

@ -89,6 +89,7 @@
"4Vmpt4": "Nostr Plebs is one of the first NIP-05 providers in the space and offers a good collection of domains at reasonable prices",
"4Z3t5i": "Use imgproxy to compress images",
"4emo2p": "Missing Relays",
"4oPRxH": "Supported Kinds:",
"4rYCjn": "Note to Self",
"4wgYpI": "Recommended Application Handlers",
"5BVs2e": "zap",
@ -380,6 +381,7 @@
"QDFTjG": "{n} Relays",
"QJfhKt": "The private key is like a password, but it cannot be reset. Guard it carefully and never show it to anyone. Once someone has your private key, they will have access to your account forever.",
"QWhotP": "Zap Pool only works if you use one of the supported wallet connections (WebLN, LNC, LNDHub or Nostr Wallet Connect)",
"QaGyuN": "Zapstr Track",
"QpaLA3": "Channel Message",
"Qxv0B2": "You currently have {number} sats in your zap pool.",
"Qy6/Ft": "Private Direct Messages",
@ -431,6 +433,7 @@
"U30H69": "Community Definition",
"UJTWqI": "Remove from my relays",
"ULXFfP": "Receive",
"ULsJTk": "Published by",
"UNjfWJ": "Check all event signatures received from relays",
"UT7Nkj": "New Chat",
"UUPFlt": "Users must accept the content warning to show the content of your note.",

View File

@ -55,6 +55,7 @@ const enum EventKind {
LiveEvent = 30311, // NIP-102
UserStatus = 30315, // NIP-38
ZapstrTrack = 31337,
ApplicationHandler = 31_990,
SimpleChatMetadata = 39_000, // NIP-29
ZapRequest = 9734, // NIP 57
ZapReceipt = 9735, // NIP 57

View File

@ -235,16 +235,30 @@ export class NostrLink implements ToNostrEventTag {
}
case "A": {
const [kind, author, dTag] = tag[1].split(":");
if (!isHex(author)) {
throw new Error(`Invalid author in A tag: ${tag[1]}`);
}
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, "root");
}
case "a": {
const [kind, author, dTag] = tag[1].split(":");
if (!isHex(author)) {
throw new Error(`Invalid author in a tag: ${tag[1]}`);
}
return new NostrLink(NostrPrefix.Address, dTag, Number(kind), author, relays, tag[3]);
}
}
throw new Error("Unknown tag!");
}
static tryFromTag(tag: Array<string>, author?: string, kind?: number) {
try {
return NostrLink.fromTag(tag, author, kind);
} catch (e) {
// ignored
}
}
static fromTags(tags: ReadonlyArray<Array<string>>) {
return removeUndefined(
tags.map(a => {