From 0b74b00d850b9beb486c0c90e10857cc335fc4ed Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Tue, 4 Jun 2024 17:38:22 -0700 Subject: [PATCH] Fix parser bugs, fix repository delete --- package-lock.json | 8 ++-- packages/content/index.ts | 81 +++++++++++++++-------------------- packages/content/package.json | 2 +- packages/feeds/package.json | 2 +- packages/net/package.json | 2 +- packages/util/Repository.ts | 12 +++--- packages/util/package.json | 2 +- 7 files changed, 49 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f2322e..fa8f274 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3092,7 +3092,7 @@ }, "packages/content": { "name": "@welshman/content", - "version": "0.0.1", + "version": "0.0.3", "license": "MIT", "dependencies": { "insane": "^2.6.2", @@ -3110,7 +3110,7 @@ "version": "0.0.9", "license": "MIT", "dependencies": { - "@welshman/util": "0.0.11" + "@welshman/util": "0.0.12" }, "devDependencies": { "gts": "^5.0.1", @@ -3148,7 +3148,7 @@ "license": "MIT", "dependencies": { "@welshman/lib": "0.0.8", - "@welshman/util": "0.0.11", + "@welshman/util": "0.0.12", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" }, @@ -3160,7 +3160,7 @@ }, "packages/util": { "name": "@welshman/util", - "version": "0.0.11", + "version": "0.0.12", "license": "MIT", "dependencies": { "@welshman/lib": "0.0.8", diff --git a/packages/content/index.ts b/packages/content/index.ts index 8d944cc..7d0045e 100644 --- a/packages/content/index.ts +++ b/packages/content/index.ts @@ -5,6 +5,9 @@ const last = (xs: T[], ...args: unknown[]) => xs[xs.length - 1] const fromNostrURI = (s: string) => s.replace(/^nostr:\/?\/?/, "") +export const urlIsMedia = (url: string) => + Boolean(url.match(/\.(jpe?g|png|wav|mp3|mp4|mov|avi|webm|webp|gif|bmp|svg)$/)) + // Copy some types from nostr-tools because I can't import them type AddressPointer = { @@ -37,8 +40,7 @@ export type ParseContext = { export enum ParsedType { Address = "address", Cashu = "cashu", - CodeBlock = "code_block", - CodeInline = "code_inline", + Code = "code", Ellipsis = "ellipsis", Event = "event", Invoice = "invoice", @@ -55,14 +57,8 @@ export type ParsedCashu = { raw: string } -export type ParsedCodeBlock = { - type: ParsedType.CodeBlock - value: string - raw: string -} - -export type ParsedCodeInline = { - type: ParsedType.CodeInline +export type ParsedCode = { + type: ParsedType.Code value: string raw: string } @@ -130,8 +126,7 @@ export type ParsedAddress = { export type Parsed = ParsedAddress | ParsedCashu | - ParsedCodeBlock | - ParsedCodeInline | + ParsedCode | ParsedEllipsis | ParsedEvent | ParsedInvoice | @@ -141,6 +136,20 @@ export type Parsed = ParsedText | ParsedTopic +// Matchers + +export const isAddress = (parsed: Parsed): parsed is ParsedAddress => parsed.type === ParsedType.Address +export const isCashu = (parsed: Parsed): parsed is ParsedCashu => parsed.type === ParsedType.Cashu +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 isEvent = (parsed: Parsed): parsed is ParsedEvent => parsed.type === ParsedType.Event +export const isInvoice = (parsed: Parsed): parsed is ParsedInvoice => parsed.type === ParsedType.Invoice +export const isLink = (parsed: Parsed): parsed is ParsedLink => parsed.type === ParsedType.Link +export const isNewline = (parsed: Parsed): parsed is ParsedNewline => parsed.type === ParsedType.Newline +export const isProfile = (parsed: Parsed): parsed is ParsedProfile => parsed.type === ParsedType.Profile +export const isText = (parsed: Parsed): parsed is ParsedText => parsed.type === ParsedType.Text +export const isTopic = (parsed: Parsed): parsed is ParsedTopic => parsed.type === ParsedType.Topic + // Parsers for known formats export const parseAddress = (text: string, context: ParseContext): ParsedAddress | void => { @@ -165,19 +174,19 @@ export const parseCashu = (text: string, context: ParseContext): ParsedCashu | v } } -export const parseCodeBlock = (text: string, context: ParseContext): ParsedCodeBlock | void => { +export const parseCodeBlock = (text: string, context: ParseContext): ParsedCode | void => { const [code, value] = text.match(/^```([^]*?)```/i) || [] if (code) { - return {type: ParsedType.CodeBlock, value, raw: code} + return {type: ParsedType.Code, value, raw: code} } } -export const parseCodeInline = (text: string, context: ParseContext): ParsedCodeInline | void => { +export const parseCodeInline = (text: string, context: ParseContext): ParsedCode | void => { const [code, value] = text.match(/^`(.*?)`/i) || [] if (code) { - return {type: ParsedType.CodeInline, value, raw: code} + return {type: ParsedType.Code, value, raw: code} } } @@ -207,28 +216,18 @@ export const parseInvoice = (text: string, context: ParseContext): ParsedInvoice } export const parseLink = (text: string, context: ParseContext): ParsedLink | void => { - let [link] = text.match(/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]/gi) || [] - - if (!link) { - return - } - const prev = last(context.results) + const [link] = text.match(/^([a-z\+:]{2,30}:\/\/)?[^<>\(\)\s]+\.[a-z]{2,6}[^\s]*[^<>"'\.!?,:\s\)\(]/gi) || [] // Skip url if it's just the end of a filepath or an ellipse - if (prev?.type === ParsedType.Text && prev.value.endsWith("/") || link.match(/\.\./)) { + if (!link || prev?.type === ParsedType.Text && prev.value.endsWith("/") || link.match(/\.\./)) { return } - // Make sure there's a protocol - if (!link.match(/^\w+:\/\//)) { - link = "https://" + link - } - - // Parse using URL + // Parse using URL, make sure there's a protocol let url try { - url = new URL(link) + url = new URL(link.match(/^\w+:\/\//) ? link : "https://" + link) } catch (e) { return } @@ -241,13 +240,7 @@ export const parseLink = (text: string, context: ParseContext): ParsedLink | voi } } - const isMedia = Boolean( - url.pathname.match(/\.(jpe?g|png|wav|mp3|mp4|mov|avi|webm|webp|gif|bmp|svg)$/) - ) - - const value = {url, meta, isMedia} - - return {type: ParsedType.Link, value, raw: link} + return {type: ParsedType.Link, value: {url, meta, isMedia: urlIsMedia(url.pathname)}, raw: link} } export const parseNewline = (text: string, context: ParseContext): ParsedNewline | void => { @@ -280,7 +273,7 @@ export const parseTopic = (text: string, context: ParseContext): ParsedTopic | v // Skip numeric topics if (value && !value.match(/^#\d+$/)) { - return {type: ParsedType.Topic, value, raw: value} + return {type: ParsedType.Topic, value: value.slice(1), raw: value} } } @@ -448,19 +441,16 @@ export class HTML { HTML.useSafely(`${display}`) static buildEntityLink = (entity: string, options: RenderOptions) => - HTML.buildLink(options.entityBaseUrl + entity, entity.slice(0, 16)) + HTML.buildLink(options.entityBaseUrl + entity, entity.slice(0, 16) + '…') } export const renderCashu = (parsed: ParsedCashu, options: RenderOptions) => HTML.useSafely(parsed.value) -export const renderCodeBlock = (parsed: ParsedCodeBlock, options: RenderOptions) => +export const renderCode = (parsed: ParsedCode, options: RenderOptions) => HTML.useSafely(parsed.value) -export const renderCodeInline = (parsed: ParsedCodeInline, options: RenderOptions) => - HTML.useSafely(parsed.value) - -export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "..." +export const renderEllipsis = (parsed: ParsedEllipsis, options: RenderOptions) => "…" export const renderInvoice = (parsed: ParsedInvoice, options: RenderOptions) => HTML.useSafely(parsed.value) @@ -496,8 +486,7 @@ export const render = (parsed: Parsed, options: RenderOptions = {}) => { switch (parsed.type) { case ParsedType.Address: return renderAddress(parsed as ParsedAddress, options) case ParsedType.Cashu: return renderCashu(parsed as ParsedCashu, options) - case ParsedType.CodeBlock: return renderCodeBlock(parsed as ParsedCodeBlock, options) - case ParsedType.CodeInline: return renderCodeInline(parsed as ParsedCodeInline, options) + case ParsedType.Code: return renderCode(parsed as ParsedCode, options) case ParsedType.Ellipsis: return renderEllipsis(parsed as ParsedEllipsis, options) case ParsedType.Event: return renderEvent(parsed as ParsedEvent, options) case ParsedType.Invoice: return renderInvoice(parsed as ParsedInvoice, options) diff --git a/packages/content/package.json b/packages/content/package.json index de5945f..0a4d038 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/content", - "version": "0.0.2", + "version": "0.0.3", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities for parsing nostr note content.", diff --git a/packages/feeds/package.json b/packages/feeds/package.json index 6c8e638..930fe8b 100644 --- a/packages/feeds/package.json +++ b/packages/feeds/package.json @@ -31,6 +31,6 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/util": "0.0.11" + "@welshman/util": "0.0.12" } } diff --git a/packages/net/package.json b/packages/net/package.json index 7632d69..4d0f6cd 100644 --- a/packages/net/package.json +++ b/packages/net/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@welshman/lib": "0.0.8", - "@welshman/util": "0.0.11", + "@welshman/util": "0.0.12", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" } diff --git a/packages/util/Repository.ts b/packages/util/Repository.ts index 214ee4e..db1cb3e 100644 --- a/packages/util/Repository.ts +++ b/packages/util/Repository.ts @@ -1,8 +1,8 @@ import {throttle} from 'throttle-debounce' -import {flatten, Emitter, sortBy, inc, chunk, sleep, uniq, omit, now, range, identity} from '@welshman/lib' +import {flatten, nth, Emitter, sortBy, inc, chunk, sleep, uniq, omit, now, range, identity} from '@welshman/lib' import {DELETE} from './Kinds' import {EPOCH, matchFilter} from './Filters' -import {isReplaceable, isTrustedEvent} from './Events' +import {getIdAndAddress, isReplaceable, isTrustedEvent} from './Events' import {getAddress} from './Address' import type {Filter} from './Filters' import type {TrustedEvent} from './Events' @@ -68,8 +68,8 @@ export class Repository extends Emitter { this.emit('event', event) } - notifyDelete = (event: TrustedEvent) => { - this.emit('delete', event) + notifyDelete = (values: string[]) => { + this.emit('delete', new Set(values)) } // API @@ -162,7 +162,7 @@ export class Repository extends Emitter { // Delete our duplicate first if (duplicate) { - this.notifyDelete(duplicate) + this.notifyDelete(getIdAndAddress(duplicate)) this.eventsById.delete(duplicate.id) if (isReplaceable(duplicate)) { @@ -200,7 +200,7 @@ export class Repository extends Emitter { // Deletes are tricky, re-evaluate all subscriptions if that's what we're dealing with if (event.kind === DELETE) { - this.notifyDelete(event) + this.notifyDelete(event.tags.map(nth(1))) } } } diff --git a/packages/util/package.json b/packages/util/package.json index b99a439..f7b581d 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/util", - "version": "0.0.11", + "version": "0.0.12", "author": "hodlbod", "license": "MIT", "description": "A collection of nostr-related utilities.",