From 7ab746c30d7346d346bade5bcb4a73f516ae0bfc Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Mon, 12 May 2025 15:09:00 -0700 Subject: [PATCH] Add emoji parsing and utils --- packages/content/package.json | 2 +- packages/content/src/index.ts | 44 ++++++++++++++++++++++++++++++----- packages/util/package.json | 2 +- packages/util/src/Tags.ts | 11 ++++++++- 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/packages/content/package.json b/packages/content/package.json index d2da9ea..0b596db 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/content", - "version": "0.2.1", + "version": "0.2.2", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities for parsing nostr note content.", diff --git a/packages/content/src/index.ts b/packages/content/src/index.ts index 502eb2b..b9955aa 100644 --- a/packages/content/src/index.ts +++ b/packages/content/src/index.ts @@ -42,6 +42,7 @@ export enum ParsedType { Cashu = "cashu", Code = "code", Ellipsis = "ellipsis", + Emoji = "emoji", Event = "event", Invoice = "invoice", Link = "link", @@ -70,6 +71,17 @@ export type ParsedEllipsis = { raw: string } +export type ParsedEmojiValue = { + name: string + url?: string +} + +export type ParsedEmoji = { + type: ParsedType.Emoji + value: ParsedEmojiValue + raw: string +} + export type ParsedInvoice = { type: ParsedType.Invoice value: string @@ -138,6 +150,7 @@ export type Parsed = | ParsedCashu | ParsedCode | ParsedEllipsis + | ParsedEmoji | ParsedEvent | ParsedInvoice | ParsedLink @@ -155,6 +168,7 @@ export const isCashu = (parsed: Parsed): parsed is ParsedCashu => parsed.type == export const isCode = (parsed: Parsed): parsed is ParsedCode => parsed.type === ParsedType.Code export const isEllipsis = (parsed: Parsed): parsed is ParsedEllipsis => parsed.type === ParsedType.Ellipsis +export const isEmoji = (parsed: Parsed): parsed is ParsedEmoji => parsed.type === ParsedType.Emoji export const isEvent = (parsed: Parsed): parsed is ParsedEvent => parsed.type === ParsedType.Event export const isInvoice = (parsed: Parsed): parsed is ParsedInvoice => parsed.type === ParsedType.Invoice @@ -195,18 +209,28 @@ export const parseCashu = (text: string, context: ParseContext): ParsedCashu | v } export const parseCodeBlock = (text: string, context: ParseContext): ParsedCode | void => { - const [code, value] = text.match(/^```([^]*?)```/i) || [] + const [raw, value] = text.match(/^```([^]*?)```/i) || [] - if (code) { - return {type: ParsedType.Code, value, raw: code} + if (raw) { + return {type: ParsedType.Code, value, raw} } } export const parseCodeInline = (text: string, context: ParseContext): ParsedCode | void => { - const [code, value] = text.match(/^`(.*?)`/i) || [] + const [raw, value] = text.match(/^`(.*?)`/i) || [] - if (code) { - return {type: ParsedType.Code, value, raw: code} + if (raw) { + return {type: ParsedType.Code, value, raw} + } +} + +export const parseEmoji = (text: string, context: ParseContext): ParsedEmoji | void => { + const [raw, name] = text.match(/^:(\w+):/i) || [] + + if (raw) { + const url = context.tags.find(t => t[0] === "emoji" && t[1] === name)?.[2] + + return {type: ParsedType.Emoji, value: {name, url}, raw} } } @@ -331,6 +355,7 @@ export const parsers = [ parseCodeInline, parseAddress, parseProfile, + parseEmoji, parseEvent, parseCashu, parseInvoice, @@ -407,6 +432,8 @@ export const truncate = ( case ParsedType.Address: case ParsedType.Profile: return entityLength + case ParsedType.Emoji: + return parsed.value.name.length default: return parsed.value.length } @@ -549,6 +576,8 @@ export const renderCode = (p: ParsedCode, r: Renderer) => r.addText(p.value) export const renderEllipsis = (p: ParsedEllipsis, r: Renderer) => r.addText("…") +export const renderEmoji = (p: ParsedEmoji, r: Renderer) => r.addText(p.raw) + export const renderInvoice = (p: ParsedInvoice, r: Renderer) => r.addLink("lightning:" + p.value, p.value.slice(0, 16) + "…") @@ -584,6 +613,9 @@ export const renderOne = (parsed: Parsed, renderer: Renderer) => { case ParsedType.Ellipsis: renderEllipsis(parsed as ParsedEllipsis, renderer) break + case ParsedType.Emoji: + renderEmoji(parsed as ParsedEmoji, renderer) + break case ParsedType.Event: renderEvent(parsed as ParsedEvent, renderer) break diff --git a/packages/util/package.json b/packages/util/package.json index f178b99..4d63fb8 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/util", - "version": "0.2.2", + "version": "0.2.3", "author": "hodlbod", "license": "MIT", "description": "A collection of nostr-related utilities.", diff --git a/packages/util/src/Tags.ts b/packages/util/src/Tags.ts index e6a46ac..a7fd54f 100644 --- a/packages/util/src/Tags.ts +++ b/packages/util/src/Tags.ts @@ -1,4 +1,4 @@ -import {uniqBy, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib" +import {uniqBy, first, spec, mapVals, nth, nthEq, ensurePlural} from "@welshman/lib" import {isRelayUrl} from "./Relay.js" import {Address} from "./Address.js" @@ -106,6 +106,15 @@ export const getReplyTags = (tags: string[][]) => { export const getReplyTagValues = (tags: string[][]) => mapVals(tags => tags.map(nth(1)), getReplyTags(tags)) +export const getEmojiTags = (name: string, tags: string[][]) => tags.filter(spec(["emoji", name])) + +export const getEmojiTag = (name: string, tags: string[][]) => first(getEmojiTags(name, tags)) + +export const getEmojiTagUrls = (name: string, tags: string[][]) => + getEmojiTags(name, tags).map(nth(2)) + +export const getEmojiTagUrl = (name: string, tags: string[][]) => first(getEmojiTagUrls(name, tags)) + export const uniqTags = (tags: string[][]) => uniqBy(t => t.slice(0, 2).join(":"), tags) export const tagsFromIMeta = (imeta: string[]) => imeta.map((m: string) => m.split(" "))