You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
2.2 KiB
83 lines
2.2 KiB
import type { Event } from 'nostr-tools' |
|
|
|
const HEX_64_RE = /^[0-9a-f]{64}$/i |
|
const HEX_SIG_RE = /^[0-9a-f]{128}$/i |
|
|
|
export type StringifiedNostrEventMatch = { |
|
event: Event |
|
textBefore: string |
|
jsonText: string |
|
} |
|
|
|
function isStringArrayArray(value: unknown): value is string[][] { |
|
return ( |
|
Array.isArray(value) && |
|
value.every((tag) => Array.isArray(tag) && tag.every((part) => typeof part === 'string')) |
|
) |
|
} |
|
|
|
export function isNostrEventJson(value: unknown): value is Event { |
|
if (!value || typeof value !== 'object') return false |
|
const event = value as Partial<Event> |
|
return ( |
|
typeof event.id === 'string' && |
|
HEX_64_RE.test(event.id) && |
|
typeof event.pubkey === 'string' && |
|
HEX_64_RE.test(event.pubkey) && |
|
typeof event.created_at === 'number' && |
|
Number.isFinite(event.created_at) && |
|
typeof event.kind === 'number' && |
|
Number.isFinite(event.kind) && |
|
isStringArrayArray(event.tags) && |
|
typeof event.content === 'string' && |
|
typeof event.sig === 'string' && |
|
HEX_SIG_RE.test(event.sig) |
|
) |
|
} |
|
|
|
function parseNostrEventJson(raw: string): Event | null { |
|
try { |
|
const parsed = JSON.parse(raw) |
|
return isNostrEventJson(parsed) ? parsed : null |
|
} catch { |
|
return null |
|
} |
|
} |
|
|
|
/** |
|
* Some clients append a full serialized event after quote/repost text. Treat a trailing event JSON |
|
* object as structured data instead of showing it as prose. |
|
*/ |
|
export function findTrailingStringifiedNostrEvent(content: string): StringifiedNostrEventMatch | null { |
|
const trimmed = content.trimEnd() |
|
if (!trimmed) return null |
|
|
|
const whole = parseNostrEventJson(trimmed) |
|
if (whole) { |
|
return { |
|
event: whole, |
|
textBefore: '', |
|
jsonText: trimmed |
|
} |
|
} |
|
|
|
let start = trimmed.lastIndexOf('{') |
|
while (start >= 0) { |
|
const jsonText = trimmed.slice(start) |
|
const event = parseNostrEventJson(jsonText) |
|
if (event) { |
|
return { |
|
event, |
|
textBefore: trimmed.slice(0, start).trimEnd(), |
|
jsonText |
|
} |
|
} |
|
start = trimmed.lastIndexOf('{', start - 1) |
|
} |
|
|
|
return null |
|
} |
|
|
|
export function stripTrailingStringifiedNostrEvent(content: string): string { |
|
return findTrailingStringifiedNostrEvent(content)?.textBefore ?? content |
|
}
|
|
|