refactor parseContent (#458)

Co-authored-by: BlowaterNostr <blowater.nostr@proton.me>
This commit is contained in:
Bob 2024-04-22 14:23:56 +08:00 committed by GitHub
parent 8169955d38
commit 2237b7bbb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 399 additions and 318 deletions

View File

@ -666,9 +666,9 @@ export function generateTags(
const parsedTextItems = parseContent(args.content); const parsedTextItems = parseContent(args.content);
for (const item of parsedTextItems) { for (const item of parsedTextItems) {
if (item.type === "nevent") { if (item.type === "nevent") {
eTags.set(item.event.pointer.id, [item.event.pointer.relays?.[0] || "", "mention"]); eTags.set(item.nevent.pointer.id, [item.nevent.pointer.relays?.[0] || "", "mention"]);
if (item.event.pointer.pubkey) { if (item.nevent.pointer.pubkey) {
pTags.add(item.event.pointer.pubkey.hex); pTags.add(item.nevent.pointer.pubkey.hex);
} }
} else if (item.type === "npub") { } else if (item.type === "npub") {
pTags.add(item.pubkey.hex); pTags.add(item.pubkey.hex);

View File

@ -204,36 +204,36 @@ export function ParseMessageContent(
const parsedContentItems = parseContent(message.content); const parsedContentItems = parseContent(message.content);
const vnode = []; const vnode = [];
let start = 0;
for (const item of parsedContentItems) { for (const item of parsedContentItems) {
vnode.push(message.content.slice(start, item.start)); if (item.type === "raw" || item.type === "tag") {
const itemStr = message.content.slice(item.start, item.end + 1); vnode.push(item.text);
if (item.type == "url") { } else if (item.type === "url") {
if (urlIsImage(itemStr)) { if (urlIsImage(item.text)) {
vnode.push( vnode.push(
<img <img
class={`w-96 p-1 rounded-lg border-2 border-[${DividerBackgroundColor}]`} class={`w-96 p-1 rounded-lg border-2 border-[${DividerBackgroundColor}]`}
src={itemStr} src={item.text}
/>, />,
); );
} else if (urlIsVideo(itemStr)) { } else if (urlIsVideo(item.text)) {
vnode.push( vnode.push(
<video <video
class={`w-96 p-1 rounded-lg border-2 border-[${DividerBackgroundColor}]`} class={`w-96 p-1 rounded-lg border-2 border-[${DividerBackgroundColor}]`}
controls controls
src={itemStr} src={item.text}
> >
</video>, </video>,
); );
} else { } else {
vnode.push( vnode.push(
<a target="_blank" class={`hover:underline text-[${LinkColor}]`} href={itemStr}> <a target="_blank" class={`hover:underline text-[${LinkColor}]`} href={item.text}>
{itemStr} {item.text}
</a>, </a>,
); );
} }
} else if (item.type == "npub") { } else if (item.type === "npub" || item.type === "nprofile") {
const profile = getters.profileGetter.getProfileByPublicKey(item.pubkey); const pubkey = item.type == "npub" ? item.pubkey : item.nprofile.pubkey;
const profile = getters.profileGetter.getProfileByPublicKey(pubkey);
const name = profile?.profile.name || profile?.profile.display_name; const name = profile?.profile.name || profile?.profile.display_name;
vnode.push( vnode.push(
<span <span
@ -241,16 +241,16 @@ export function ParseMessageContent(
onClick={() => onClick={() =>
emit({ emit({
type: "ViewUserDetail", type: "ViewUserDetail",
pubkey: item.pubkey, pubkey: pubkey,
})} })}
> >
{name ? `@${name}` : item.pubkey.bech32()} {name ? `@${name}` : pubkey.bech32()}
</span>, </span>,
); );
} else if (item.type == "note") { } else if (item.type === "note") {
const event = getters.getEventByID(item.noteID); const event = getters.getEventByID(item.noteID);
if (event == undefined || event.kind == NostrKind.DIRECT_MESSAGE) { if (event == undefined || event.kind == NostrKind.DIRECT_MESSAGE) {
vnode.push(itemStr); vnode.push(item.text);
emit({ emit({
type: "SyncEvent", type: "SyncEvent",
eventID: item.noteID.hex, eventID: item.noteID.hex,
@ -259,26 +259,22 @@ export function ParseMessageContent(
const profile = getters.profileGetter.getProfileByPublicKey(event.publicKey); const profile = getters.profileGetter.getProfileByPublicKey(event.publicKey);
vnode.push(Card(event, profile?.profile, emit, event.publicKey)); vnode.push(Card(event, profile?.profile, emit, event.publicKey));
} }
} else if (item.type == "nevent") { } else if (item.type === "nevent") {
const event = getters.getEventByID(NoteID.FromString(item.event.pointer.id)); const event = getters.getEventByID(NoteID.FromString(item.nevent.pointer.id));
if ( if (
event == undefined || event.kind == NostrKind.DIRECT_MESSAGE event == undefined || event.kind == NostrKind.DIRECT_MESSAGE
) { ) {
vnode.push(itemStr); vnode.push(item.text);
emit({ emit({
type: "SyncEvent", type: "SyncEvent",
eventID: item.event.pointer.id, eventID: item.nevent.pointer.id,
}); });
} else { } else {
const profile = getters.profileGetter.getProfileByPublicKey(event.publicKey); const profile = getters.profileGetter.getProfileByPublicKey(event.publicKey);
vnode.push(Card(event, profile ? profile.profile : undefined, emit, event.publicKey)); vnode.push(Card(event, profile ? profile.profile : undefined, emit, event.publicKey));
} }
} }
start = item.end + 1;
} }
vnode.push(message.content.slice(start));
return vnode; return vnode;
} }

View File

@ -1,175 +1,195 @@
import { assertEquals } from "https://deno.land/std@0.202.0/testing/asserts.ts"; import { assertEquals } from "https://deno.land/std@0.202.0/assert/assert_equals.ts";
import { ChatMessage, findUrlInString, groupContinuousMessages, parseContent } from "./message.ts"; import {
ChatMessage,
ContentItem,
findUrlInString,
groupContinuousMessages,
parseContent,
} from "./message.ts";
import { PrivateKey, PublicKey } from "../../libs/nostr.ts/key.ts"; import { PrivateKey, PublicKey } from "../../libs/nostr.ts/key.ts";
import { NostrKind } from "../../libs/nostr.ts/nostr.ts"; import { NostrKind } from "../../libs/nostr.ts/nostr.ts";
import { Nevent, NostrAddress } from "../../libs/nostr.ts/nip19.ts"; import { Nevent, NostrAddress, NostrProfile } from "../../libs/nostr.ts/nip19.ts";
Deno.test("inline parse", async (t) => { Deno.test("inline parse", async (t) => {
const data = [ const data: {
input: string;
output: ContentItem[];
}[] = [
{
input: "",
output: [],
},
{ {
input: `nothing`, input: `nothing`,
output: [], output: [{
text: "nothing",
type: "raw",
}],
}, },
{ {
input: input:
`https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg`, `https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg`,
output: [{ output: [{
text:
"https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg",
type: "url", type: "url",
start: 0,
end: 89,
}], }],
}, },
{ {
input: input:
` https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg`, ` https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg`,
output: [{ output: [
type: "url", {
start: 1, text: " ",
end: 90, type: "raw",
}], },
{
text:
"https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg",
type: "url",
},
],
}, },
{ {
input: input:
`https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg `, `https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg `,
output: [{ output: [
type: "url", {
start: 0, text:
end: 89, "https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg",
}], type: "url",
},
{
text: " ",
type: "raw",
},
],
}, },
{ {
input: input:
` https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg `, ` https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg `,
output: [{ output: [{
text: " ",
type: "raw",
}, {
text:
"https://nostr.build/i/f91187675750791b652f7e129b374c2b682d7cc0e9dbc28def58ffdf66508867.jpg",
type: "url", type: "url",
start: 1, }, {
end: 90, text: " ",
type: "raw",
}], }],
}, },
{ {
input: `Hi https://some.jpg`, input: `Hi https://some.jpg`,
output: [{ output: [
type: "url", {
start: 3, text: "Hi ",
end: 18, type: "raw",
}], },
{
text: "https://some.jpg",
type: "url",
},
],
}, },
{ {
input: `Hi https://some.jpg http://some.jpg`, input: `Hi https://some.jpg http://some.jpg`,
output: [{ output: [{
type: "url", text: "Hi ",
start: 3, type: "raw",
end: 18,
}, { }, {
text: "https://some.jpg",
type: "url",
}, {
text: " ",
type: "raw",
}, {
text: "http://some.jpg",
type: "url", type: "url",
start: 20,
end: 34,
}],
},
{
input: `nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4ylログボ`,
output: [{
type: "npub",
pubkey: PublicKey.FromHex("f34d34b94c1dd0bb552803761e00cc7d3851f7bc8b9f0bf49edc3637b450aefd"),
start: 0,
end: 68,
}],
},
{
input: `sherryiscutenpub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4ylログボ`,
output: [{
type: "npub",
pubkey: PublicKey.FromHex("f34d34b94c1dd0bb552803761e00cc7d3851f7bc8b9f0bf49edc3637b450aefd"),
start: 12,
end: 74,
}], }],
}, },
{ {
input: `npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl`, input: `npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl`,
output: [{ output: [{
text: "npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
type: "npub", type: "npub",
pubkey: PublicKey.FromHex("f34d34b94c1dd0bb552803761e00cc7d3851f7bc8b9f0bf49edc3637b450aefd"), pubkey: PublicKey.FromBech32(
start: 0, "npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
end: 62, ) as PublicKey,
}], }],
}, },
{
input: `nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4ylログボ`,
output: [{
text: "nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
type: "npub",
pubkey: PublicKey.FromBech32(
"npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
) as PublicKey,
}, {
text: "ログボ",
type: "raw",
}],
},
{
input: `sherryiscutenpub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4ylログボ`,
output: [{
text: "sherryiscute",
type: "raw",
}, {
text: "npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
type: "npub",
pubkey: PublicKey.FromBech32(
"npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
) as PublicKey,
}, {
text: "ログボ",
type: "raw",
}],
},
{ {
input: input:
`nostr:nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4`, `nostr:nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4`,
output: [{ output: [{
type: "npub", type: "nprofile",
pubkey: PublicKey.FromHex("98fb85057818fb2b6d5d54a1a9b199c19adeaa31b824b9ebd1e6209d825b2c93"), text:
start: 0, "nostr:nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
end: 598, nprofile: NostrProfile.decode(
relays: [ "nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
"wss://nos.lol", ) as NostrProfile,
"wss://relay.damus.io",
"wss://brb.io",
"wss://nostr.orangepill.dev",
"wss://relay.current.fyi",
"wss://nostr.wine",
"wss://relay.snort.social",
"wss://eden.nostr.land",
"wss://relay.nostrgraph.net",
"wss://puravida.nostr.land",
"wss://nostr.zebedee.cloud",
"wss://purplepag.es",
"wss://atlas.nostr.land",
"wss://nostr.mutinywallet.com",
],
}], }],
}, },
{ {
input: input:
`sherryiscutenprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4 123`, `sherryiscutenprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4 123`,
output: [{ output: [{
type: "npub", text: "sherryiscute",
pubkey: PublicKey.FromHex("98fb85057818fb2b6d5d54a1a9b199c19adeaa31b824b9ebd1e6209d825b2c93"), type: "raw",
start: 12, }, {
end: 604, type: "nprofile",
relays: [ text:
"wss://nos.lol", "nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
"wss://relay.damus.io", nprofile: NostrProfile.decode(
"wss://brb.io", "nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
"wss://nostr.orangepill.dev", ) as NostrProfile,
"wss://relay.current.fyi", }, {
"wss://nostr.wine", text: " 123",
"wss://relay.snort.social", type: "raw",
"wss://eden.nostr.land",
"wss://relay.nostrgraph.net",
"wss://puravida.nostr.land",
"wss://nostr.zebedee.cloud",
"wss://purplepag.es",
"wss://atlas.nostr.land",
"wss://nostr.mutinywallet.com",
],
}], }],
}, },
{ {
input: input:
`nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4`, `nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4`,
output: [{ output: [{
type: "npub", type: "nprofile",
pubkey: PublicKey.FromHex("98fb85057818fb2b6d5d54a1a9b199c19adeaa31b824b9ebd1e6209d825b2c93"), text:
start: 0, "nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
end: 592, nprofile: NostrProfile.decode(
relays: [ "nprofile1qqsf37u9q4up37etd4w4fgdfkxvurxk74gcmsf9ea0g7vgyasfdjeycpp4mhxue69uhkummn9ekx7mqpz3mhxue69uhhyetvv9ujuerpd46hxtnfduqscamnwvaz7tmzwf3zu6t0qyd8wumn8ghj7mn0wd68ytn0wfskuem9wp5kcmpwv3jhvqghwaehxw309aex2mrp0yhxxatjwfjkuapwveukjqgswaehxw309ahx7um5wgh8w6twv5q3samnwvaz7tmjv4kxz7fwwdhx7un59eek7cmfv9kqz9thwden5te0v4jx2m3wdehhxarj9ekxzmnyqyd8wumn8ghj7un9d3shjtnwdaehgun8wfshq6pwdejhgqgewaehxw309ac82unpwe5kgcfwdehhxarj9ekxzmnyqyvhwumn8ghj7mn0wd68ytn6v43x2er9v5hxxmr0w4jqzynhwden5te0wp6hyurvv4cxzeewv4esz9nhwden5te0v96xcctn9ehx7um5wghxcctwvsq3camnwvaz7tmwdaehgu3wd46hg6tw09mkzmrvv46zucm0d5lxp0l4",
"wss://nos.lol", ) as NostrProfile,
"wss://relay.damus.io",
"wss://brb.io",
"wss://nostr.orangepill.dev",
"wss://relay.current.fyi",
"wss://nostr.wine",
"wss://relay.snort.social",
"wss://eden.nostr.land",
"wss://relay.nostrgraph.net",
"wss://puravida.nostr.land",
"wss://nostr.zebedee.cloud",
"wss://purplepag.es",
"wss://atlas.nostr.land",
"wss://nostr.mutinywallet.com",
],
}], }],
}, },
{ {
@ -177,8 +197,8 @@ Deno.test("inline parse", async (t) => {
`naddr1qqxnzd3exsmnjvphxqunqv33qgsp7hwmlh5zccs55shzpfued50pznvypj0wwzn00dtyjzlqkr04w4grqsqqqa28vct2px`, `naddr1qqxnzd3exsmnjvphxqunqv33qgsp7hwmlh5zccs55shzpfued50pznvypj0wwzn00dtyjzlqkr04w4grqsqqqa28vct2px`,
output: [{ output: [{
type: "naddr", type: "naddr",
start: 0, text:
end: 99, "naddr1qqxnzd3exsmnjvphxqunqv33qgsp7hwmlh5zccs55shzpfued50pznvypj0wwzn00dtyjzlqkr04w4grqsqqqa28vct2px",
addr: new NostrAddress({ addr: new NostrAddress({
pubkey: PublicKey.FromHex( pubkey: PublicKey.FromHex(
"1f5ddbfde82c6214a42e20a7996d1e114d840c9ee70a6f7b56490be0b0df5755", "1f5ddbfde82c6214a42e20a7996d1e114d840c9ee70a6f7b56490be0b0df5755",
@ -193,8 +213,10 @@ Deno.test("inline parse", async (t) => {
input: input:
`nostr:nevent1qqsz25j8nrppstgmyry8hgsg4fggtfa6xnym2n4c2xth7usxtydtgpcpp4mhxue69uhhjctzw5hx6egzyze7g05vclndlu36x0vjzw37jykcjkcu8ep9qfqwpjvahmlrq6947qcyqqqqqqgj5mjek`, `nostr:nevent1qqsz25j8nrppstgmyry8hgsg4fggtfa6xnym2n4c2xth7usxtydtgpcpp4mhxue69uhhjctzw5hx6egzyze7g05vclndlu36x0vjzw37jykcjkcu8ep9qfqwpjvahmlrq6947qcyqqqqqqgj5mjek`,
output: [{ output: [{
end: 161, text:
event: new Nevent( "nostr:nevent1qqsz25j8nrppstgmyry8hgsg4fggtfa6xnym2n4c2xth7usxtydtgpcpp4mhxue69uhhjctzw5hx6egzyze7g05vclndlu36x0vjzw37jykcjkcu8ep9qfqwpjvahmlrq6947qcyqqqqqqgj5mjek",
type: "nevent",
nevent: new Nevent(
{ {
id: "25524798c2182d1b20c87ba208aa5085a7ba34c9b54eb851977f7206591ab407", id: "25524798c2182d1b20c87ba208aa5085a7ba34c9b54eb851977f7206591ab407",
kind: 1, kind: 1,
@ -206,42 +228,90 @@ Deno.test("inline parse", async (t) => {
], ],
}, },
), ),
start: 0,
type: "nevent",
}], }],
}, },
{ {
input: `Thank you #[0] #[2]#[3]`, input: `Thank you #[0] #[2]#[3]`,
output: [{ output: [
type: "tag", {
start: 10, type: "raw",
end: 13, text: "Thank you ",
}, { },
type: "tag", {
start: 16, type: "tag",
end: 19, text: "#[0]",
}, { },
type: "tag", {
start: 20, type: "raw",
end: 23, text: " ",
}], },
{
type: "tag",
text: "#[2]",
},
{
type: "tag",
text: "#[3]",
},
],
}, },
{ {
input: input:
"You have been invited to group npub1k9p03z0gqsz2dqvjrkp6337lq5tl9nzj4wx0sfrpjmje2ze8nyls424ds3", `nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl https://example.com`,
output: [
{
type: "npub",
text: "nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
pubkey: PublicKey.FromBech32(
"npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
) as PublicKey,
},
{
type: "raw",
text: " ",
},
{
type: "url",
text: "https://example.com",
},
],
},
{
input:
`hi https://example.com nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl`,
output: [
{
type: "raw",
text: "hi ",
},
{
type: "url",
text: "https://example.com",
},
{
type: "raw",
text: " ",
},
{
type: "npub",
text: "nostr:npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
pubkey: PublicKey.FromBech32(
"npub17dxnfw2vrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7s6ng4yl",
) as PublicKey,
},
],
},
{
input: `pubkey error: nostr:npub1xxxxxxxxrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7sxxxxxx`,
output: [{ output: [{
type: "npub", type: "raw",
pubkey: PublicKey.FromBech32( text: "pubkey error: nostr:npub1xxxxxxxxrhgtk4fgqdmpuqxv05u9raau3w0shay7msmr0dzs4m7sxxxxxx",
"npub1k9p03z0gqsz2dqvjrkp6337lq5tl9nzj4wx0sfrpjmje2ze8nyls424ds3",
),
start: 31,
end: 93,
}], }],
}, },
]; ];
for (const [i, test] of data.entries()) { for (const test of data) {
await t.step(test.input, () => { await t.step(`t-${test.input}`, () => {
assertEquals(test.output, Array.from(parseContent(test.input))); assertEquals(Array.from(parseContent(test.input)), test.output);
}); });
} }
}); });
@ -258,7 +328,6 @@ Deno.test("message group", () => {
pubkey: "", pubkey: "",
sig: "", sig: "",
tags: [], tags: [],
parsedContentItems: [],
parsedTags: { parsedTags: {
e: [], e: [],
p: [], p: [],

View File

@ -1,168 +1,187 @@
import { PublicKey } from "../../libs/nostr.ts/key.ts"; import { PublicKey } from "../../libs/nostr.ts/key.ts";
import { DirectedMessage_Event, Parsed_Event } from "../nostr.ts"; import { DirectedMessage_Event, Parsed_Event } from "../nostr.ts";
import { Nevent, NostrAddress, NostrProfile, NoteID } from "../../libs/nostr.ts/nip19.ts";
import { NostrKind } from "../../libs/nostr.ts/nostr.ts"; import { NostrKind } from "../../libs/nostr.ts/nostr.ts";
import { Nevent, NostrAddress, NostrProfile, NoteID } from "../../libs/nostr.ts/nip19.ts";
export function* parseContent(content: string) { type ItemType = "url" | "tag" | "note" | "npub" | "nprofile" | "naddr" | "nevent";
// URLs
yield* match(/https?:\/\/[^\s]+/g, content, "url");
// npubs
yield* match(/(nostr:)?npub[0-9a-z]{59}/g, content, "npub");
//nprofile
yield* match(/(nostr:)?nprofile[0-9a-z]+/g, content, "nprofile");
//naddr
yield* match(/(nostr:)?naddr[0-9a-z]+/g, content, "naddr");
// notes
yield* match(/note[0-9a-z]{59}/g, content, "note");
// nevent
yield* match(/(nostr:)?nevent[0-9a-z]+/g, content, "nevent");
// tags
yield* match(/#\[[0-9]+\]/g, content, "tag");
}
function* match(regex: RegExp, content: string, type: ItemType): Generator<ContentItem, void, unknown> {
let match;
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#return_value
// If the match succeeds, the exec() method returns an array and
// updates the lastIndex property of the regular expression object.
while ((match = regex.exec(content)) !== null) {
const urlStartPosition = match.index;
if (urlStartPosition == undefined) {
return;
}
const urlEndPosition = urlStartPosition + match[0].length - 1;
if (type == "note") {
const noteID = NoteID.FromBech32(content.slice(urlStartPosition, urlEndPosition + 1));
if (noteID instanceof Error) {
// ignore
} else {
yield {
type: type,
noteID: noteID,
start: urlStartPosition,
end: urlEndPosition,
};
}
} else if (type == "npub") {
let bech32: string;
if (match[0].startsWith("nostr:")) {
bech32 = content.slice(urlStartPosition + 6, urlEndPosition + 1);
} else {
bech32 = content.slice(urlStartPosition, urlEndPosition + 1);
}
const pubkey = PublicKey.FromBech32(bech32);
if (pubkey instanceof Error) {
// ignore
} else {
yield {
type: type,
pubkey: pubkey,
start: urlStartPosition,
end: urlEndPosition,
};
}
} else if (type == "nprofile") {
let bech32: string;
if (match[0].startsWith("nostr:")) {
bech32 = content.slice(urlStartPosition + 6, urlEndPosition + 1);
} else {
bech32 = content.slice(urlStartPosition, urlEndPosition + 1);
}
const decoded_nProfile = NostrProfile.decode(bech32);
if (decoded_nProfile instanceof Error) {
// ignore
} else {
const pubkey = decoded_nProfile.pubkey;
yield {
type: "npub",
pubkey: pubkey,
start: urlStartPosition,
end: urlEndPosition,
relays: decoded_nProfile.relays,
};
}
} else if (type == "naddr") {
let bech32: string;
if (match[0].startsWith("nostr:")) {
bech32 = content.slice(urlStartPosition + 6, urlEndPosition + 1);
} else {
bech32 = content.slice(urlStartPosition, urlEndPosition + 1);
}
const decoded_nAddr = NostrAddress.decode(bech32);
if (decoded_nAddr instanceof Error) {
// ignore
} else {
yield {
type: "naddr",
start: urlStartPosition,
end: urlEndPosition,
addr: decoded_nAddr,
};
}
} else if (type == "nevent") {
let bech32: string;
if (match[0].startsWith("nostr:")) {
bech32 = content.slice(urlStartPosition + 6, urlEndPosition + 1);
} else {
bech32 = content.slice(urlStartPosition, urlEndPosition + 1);
}
const decoded_nEvent = Nevent.decode(bech32);
if (decoded_nEvent instanceof Error) {
// ignore
} else {
yield {
type: "nevent",
start: urlStartPosition,
end: urlEndPosition,
event: decoded_nEvent,
};
}
} else {
yield {
type: type,
start: urlStartPosition,
end: urlEndPosition,
};
}
}
}
type otherItemType = "url" | "tag";
type ItemType = otherItemType | "note" | "npub" | "nprofile" | "naddr" | "nevent";
export type ContentItem = { export type ContentItem = {
type: otherItemType; type: "raw" | "url" | "tag";
start: number; text: string;
end: number;
} | { } | {
type: "npub"; type: "npub";
text: string;
pubkey: PublicKey; pubkey: PublicKey;
start: number; } | {
end: number; type: "nprofile";
relays?: string[]; text: string;
nprofile: NostrProfile;
} | { } | {
type: "note"; type: "note";
text: string;
noteID: NoteID; noteID: NoteID;
start: number;
end: number;
} | { } | {
type: "naddr"; type: "naddr";
start: number; text: string;
end: number;
addr: NostrAddress; addr: NostrAddress;
} | { } | {
type: "nevent"; type: "nevent";
start: number; text: string;
end: number; nevent: Nevent;
event: Nevent;
}; };
export function* parseContent(content: string): Iterable<ContentItem> {
if (content.length === 0) {
return;
}
const first_match = match_first(content);
if (!first_match) {
yield { text: content, type: "raw" };
return;
}
const text = content.substring(first_match.start, first_match.end);
const bech32 = text.startsWith("nostr:") ? text.slice(6) : text;
const raw_string_before = content.substring(0, first_match.start);
if (first_match.name === "npub") {
const pubkey = PublicKey.FromBech32(bech32);
if (pubkey instanceof Error) {
yield {
type: "raw",
text: content.slice(0, first_match.end),
};
yield* parseContent(content.slice(first_match.end));
return;
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name, pubkey };
yield* parseContent(content.slice(first_match.end));
return;
}
} else if (first_match.name === "nprofile") {
const decoded_nProfile = NostrProfile.decode(bech32);
if (decoded_nProfile instanceof Error) {
yield {
type: "raw",
text: content.slice(0, first_match.end),
};
yield* parseContent(content.slice(first_match.end));
return;
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name, nprofile: decoded_nProfile };
yield* parseContent(content.slice(first_match.end));
return;
}
} else if (first_match.name === "note") {
const noteID = NoteID.FromBech32(bech32);
if (noteID instanceof Error) {
yield {
type: "raw",
text: content.slice(0, first_match.end),
};
yield* parseContent(content.slice(first_match.end));
return;
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name, noteID };
yield* parseContent(content.slice(first_match.end));
return;
}
} else if (first_match.name === "naddr") {
const addr = NostrAddress.decode(bech32);
if (addr instanceof Error) {
yield {
type: "raw",
text: content.slice(0, first_match.end),
};
yield* parseContent(content.slice(first_match.end));
return;
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name, addr };
yield* parseContent(content.slice(first_match.end));
return;
}
} else if (first_match.name === "nevent") {
const nevent = Nevent.decode(bech32);
if (nevent instanceof Error) {
yield {
type: "raw",
text: content.slice(0, first_match.end),
};
yield* parseContent(content.slice(first_match.end));
return;
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name, nevent };
yield* parseContent(content.slice(first_match.end));
return;
}
} else {
if (raw_string_before) {
yield { text: raw_string_before, type: "raw" };
}
yield { text, type: first_match.name };
yield* parseContent(content.slice(first_match.end));
}
}
function match_first(content: string) {
if (content.length === 0) {
return;
}
const regexs: { name: ItemType; regex: RegExp }[] = [
{ name: "url", regex: /https?:\/\/[^\s]+/ },
{ name: "npub", regex: /(nostr:)?npub[0-9a-z]{59}/ },
{ name: "nprofile", regex: /(nostr:)?nprofile[0-9a-z]+/ },
{ name: "naddr", regex: /(nostr:)?naddr[0-9a-z]+/ },
{ name: "note", regex: /(nostr:)?note[0-9a-z]{59}/ },
{ name: "nevent", regex: /(nostr:)?nevent[0-9a-z]+/ },
{ name: "tag", regex: /#\[[0-9]+\]/ },
];
let first_match: {
name: ItemType;
start: number;
end: number;
} | undefined;
for (const r of regexs) {
const matched = r.regex.exec(content);
if (matched == null) {
continue;
}
const start = matched.index;
const end = matched.index + matched[0].length;
// Return the matching string with the maximum length
if (first_match == undefined) {
first_match = { name: r.name, start, end };
continue;
}
if (start < first_match.start) {
first_match = { name: r.name, start, end };
continue;
}
if (first_match.start == start && end > first_match.end) {
first_match = { name: r.name, start, end };
continue;
}
}
return first_match;
}
// Think of ChatMessage as an materialized view of NostrEvent // Think of ChatMessage as an materialized view of NostrEvent
export type ChatMessage = { export type ChatMessage = {
readonly type: "image" | "text"; readonly type: "image" | "text";

View File

@ -3,7 +3,7 @@ import { InvalidKey, PublicKey } from "../../libs/nostr.ts/key.ts";
import { NostrAccountContext, NostrEvent, NostrKind } from "../../libs/nostr.ts/nostr.ts"; import { NostrAccountContext, NostrEvent, NostrKind } from "../../libs/nostr.ts/nostr.ts";
import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts"; import { ConnectionPool } from "../../libs/nostr.ts/relay-pool.ts";
import { DirectMessageGetter } from "../UI/app_update.tsx"; import { DirectMessageGetter } from "../UI/app_update.tsx";
import { ChatMessage, parseContent } from "../UI/message.ts"; import { ChatMessage } from "../UI/message.ts";
import { import {
compare, compare,
DirectedMessage_Event, DirectedMessage_Event,
@ -297,7 +297,6 @@ async function parseDM(
parsedTags, parsedTags,
publicKey, publicKey,
decryptedContent: decrypted, decryptedContent: decrypted,
parsedContentItems: Array.from(parseContent(decrypted)),
}, },
}; };
} }

View File

@ -5,7 +5,6 @@ import { PublicKey } from "../libs/nostr.ts/key.ts";
import * as nostr from "../libs/nostr.ts/nostr.ts"; import * as nostr from "../libs/nostr.ts/nostr.ts";
import { NostrKind, TagPubKey } from "../libs/nostr.ts/nostr.ts"; import { NostrKind, TagPubKey } from "../libs/nostr.ts/nostr.ts";
import { ProfileData } from "./features/profile.ts"; import { ProfileData } from "./features/profile.ts";
import { ContentItem } from "./UI/message.ts";
import { prepareEncryptedNostrEvent, prepareNormalNostrEvent } from "../libs/nostr.ts/event.ts"; import { prepareEncryptedNostrEvent, prepareNormalNostrEvent } from "../libs/nostr.ts/event.ts";
type TotolChunks = string; type TotolChunks = string;
@ -47,7 +46,6 @@ export type Profile_Nostr_Event = Parsed_Event<NostrKind.META_DATA> & {
export type DirectedMessage_Event = Parsed_Event<NostrKind.DIRECT_MESSAGE> & { export type DirectedMessage_Event = Parsed_Event<NostrKind.DIRECT_MESSAGE> & {
decryptedContent: string; decryptedContent: string;
parsedContentItems: ContentItem[];
}; };
export type Encrypted_Event = DirectedMessage_Event; export type Encrypted_Event = DirectedMessage_Event;