diff --git a/src/Util/CommonMark/Converter.php b/src/Util/CommonMark/Converter.php index e4faae9..b037b6c 100644 --- a/src/Util/CommonMark/Converter.php +++ b/src/Util/CommonMark/Converter.php @@ -2,7 +2,7 @@ namespace App\Util\CommonMark; -use App\Util\Bech32\Bech32Decoder; +use App\Service\RedisCacheService; use App\Util\CommonMark\ImagesExtension\RawImageLinkExtension; use App\Util\CommonMark\NostrSchemeExtension\NostrSchemeExtension; use League\CommonMark\Environment\Environment; @@ -22,11 +22,11 @@ use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension; use League\CommonMark\MarkdownConverter; use League\CommonMark\Renderer\HtmlDecorator; -class Converter +readonly class Converter { - public function __construct(private readonly Bech32Decoder $bech32Decoder) - { - } + public function __construct( + private RedisCacheService $redisCacheService + ){} /** * @throws CommonMarkException @@ -64,7 +64,7 @@ class Converter $environment->addExtension(new TableExtension()); $environment->addExtension(new StrikethroughExtension()); // create a custom extension, that handles nostr mentions - $environment->addExtension(new NostrSchemeExtension($this->bech32Decoder)); + $environment->addExtension(new NostrSchemeExtension($this->redisCacheService)); $environment->addExtension(new SmartPunctExtension()); $environment->addExtension(new EmbedExtension()); $environment->addRenderer(Embed::class, new HtmlDecorator(new EmbedRenderer(), 'div', ['class' => 'embedded-content'])); diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php b/src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php index a699600..020df24 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php @@ -18,19 +18,26 @@ class NostrEventRenderer implements NodeRendererInterface if ($node->getType() === 'nevent') { // Construct the local link URL from the special part - $url = '/e/' . $node->getSpecial(); + $url = '/e/' . $node->getSpecial(); } else if ($node->getType() === 'naddr') { // dump($node); // Construct the local link URL from the special part - $url = '/' . $node->getSpecial(); + $url = '/article/' . $node->getSpecial(); } if (isset($url)) { // Create the anchor element - return new HtmlElement('a', ['href' => $url], '@' . $node->getSpecial()); + return new HtmlElement('a', ['href' => $url], '@' . $this->labelFromKey($node->getSpecial())); } return false; } + + private function labelFromKey($key): string + { + $start = substr($key, 0, 8); + $end = substr($key, -8); + return $start . '…' . $end; + } } diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php b/src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php index 694f1da..2267b9b 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php @@ -2,6 +2,7 @@ namespace App\Util\CommonMark\NostrSchemeExtension; +use App\Service\RedisCacheService; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; @@ -13,8 +14,11 @@ use swentel\nostr\Key\Key; * but have npub1XXXX instead of a URL * @package App\Util\CommonMark */ -class NostrMentionParser implements InlineParserInterface +readonly class NostrMentionParser implements InlineParserInterface { + public function __construct( + private RedisCacheService $redisCacheService + ){} public function getMatchDefinition(): InlineParserMatch { @@ -34,13 +38,14 @@ class NostrMentionParser implements InlineParserInterface // Extract "npub" part from fullMatch $npubLink = substr($fullMatch, strpos($fullMatch, 'npub1'), -1); // e.g., "npubXXXX" - $npubPart = substr($npubLink, 5); // Extract the part after "npub1", i.e., "XXXX" - $key = new Key(); - $hex = $key->convertToHex($npubLink); + if (empty($label)) { + $metadata = $this->redisCacheService->getMetadata($npubLink); + $label = $metadata->display_name ?? $metadata->name; + } // Create a new inline node for the custom link - $inlineContext->getContainer()->appendChild(new NostrMentionLink($label, $hex)); + $inlineContext->getContainer()->appendChild(new NostrMentionLink($label, $npubLink)); // Advance the cursor to consume the matched part (important!) $cursor->advanceBy(strlen($fullMatch)); diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php b/src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php index 03a71cd..b641585 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php @@ -2,19 +2,19 @@ namespace App\Util\CommonMark\NostrSchemeExtension; +use App\Service\RedisCacheService; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; -use swentel\nostr\Key\Key; /** * Class NostrRawNpubParser * Looks for raw nostr mentions formatted as npub1XXXX */ -class NostrRawNpubParser implements InlineParserInterface +readonly class NostrRawNpubParser implements InlineParserInterface { - public function __construct() + public function __construct(private RedisCacheService $redisCacheService) { } @@ -28,11 +28,10 @@ class NostrRawNpubParser implements InlineParserInterface $cursor = $inlineContext->getCursor(); // Get the match and extract relevant parts $fullMatch = $inlineContext->getFullMatch(); - $key = new Key(); - $hex = $key->convertToHex($fullMatch); + $meta = $this->redisCacheService->getMetadata($fullMatch); // Create a new inline node for the custom link - $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $hex)); + $inlineContext->getContainer()->appendChild(new NostrMentionLink($meta->name, $fullMatch)); // Advance the cursor to consume the matched part (important!) $cursor->advanceBy(strlen($fullMatch)); diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php index 9fe4548..ea5cec9 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php @@ -2,23 +2,23 @@ namespace App\Util\CommonMark\NostrSchemeExtension; -use App\Util\Bech32\Bech32Decoder; +use App\Service\RedisCacheService; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ExtensionInterface; class NostrSchemeExtension implements ExtensionInterface { - public function __construct(private readonly Bech32Decoder $bech32Decoder) + public function __construct(private readonly RedisCacheService $redisCacheService) { } public function register(EnvironmentBuilderInterface $environment): void { $environment - ->addInlineParser(new NostrMentionParser(), 200) - ->addInlineParser(new NostrSchemeParser($this->bech32Decoder), 199) - ->addInlineParser(new NostrRawNpubParser(), 198) + ->addInlineParser(new NostrMentionParser($this->redisCacheService), 200) + ->addInlineParser(new NostrSchemeParser(), 199) + ->addInlineParser(new NostrRawNpubParser($this->redisCacheService), 198) ->addRenderer(NostrSchemeData::class, new NostrEventRenderer(), 2) ->addRenderer(NostrMentionLink::class, new NostrMentionRenderer(), 1) diff --git a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php index ff03514..3e199f7 100644 --- a/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php +++ b/src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php @@ -2,18 +2,18 @@ namespace App\Util\CommonMark\NostrSchemeExtension; -use App\Util\Bech32\Bech32Decoder; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; -use function BitWasp\Bech32\convertBits; -use function BitWasp\Bech32\decode; - +use nostriphant\NIP19\Bech32; +use nostriphant\NIP19\Data\NAddr; +use nostriphant\NIP19\Data\NEvent; +use nostriphant\NIP19\Data\NProfile; class NostrSchemeParser implements InlineParserInterface { - public function __construct(private Bech32Decoder $bech32Decoder) + public function __construct() { } @@ -30,58 +30,36 @@ class NostrSchemeParser implements InlineParserInterface // The match is a Bech32 encoded string // decode it to get the parts $bechEncoded = substr($fullMatch, 6); // Extract the part after "nostr:", i.e., "XXXX" - // dump($bechEncoded); try { - list($hrp, $tlv) = $this->bech32Decoder->decodeAndParseNostrBech32($bechEncoded); - // dump($hrp); - // dump($tlv); - switch ($hrp) { + $decoded = new Bech32($bechEncoded); + + switch ($decoded->type) { case 'npub': - $str = ''; - list($hrp, $data) = decode($bechEncoded); - $bytes = convertBits($data, count($data), 5, 8, false); - foreach ($bytes as $item) { - $str .= str_pad(dechex($item), 2, '0', STR_PAD_LEFT); - } - $npubPart = $str; - $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $npubPart)); + $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $decoded->data)); break; case 'nprofile': - $type = 0; // npub - foreach ($tlv as $item) { - if ($item['type'] === $type) { - // from array of integers to string - $str = ''; - foreach ($item['value'] as $byte) { - $str .= str_pad(dechex($byte), 2, '0', STR_PAD_LEFT); - } - $npubPart = $str; - break; - } - } - if (isset($npubPart)) { - $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $npubPart)); - } + /** @var NProfile $decodedProfile */ + $decodedProfile = $decoded->data; + $inlineContext->getContainer()->appendChild(new NostrMentionLink(null, $decodedProfile->getPubkey())); break; case 'nevent': - foreach ($tlv as $item) { - // event id - if ($item['type'] === 0) { - $eventId = implode('', array_map(fn($byte) => sprintf('%02x', $byte), $item['value'])); - break; - } - // relays - if ($item['type'] === 1) { - $relays[] = implode('', array_map('chr', $item['value'])); - } - } - // dump($relays ?? null); - // TODO also potentially contains relays, author, and kind - $inlineContext->getContainer()->appendChild(new NostrSchemeData('nevent', $eventId, $relays ?? null, null, null)); + /** @var NEvent $decodedNpub */ + $decodedEvent = $decoded->data; + $eventId = $decodedEvent->getId(); + $relays = $decodedEvent->getRelays(); + $author = $decodedEvent->getAuthor(); + $kind = $decodedEvent->getKind(); + $inlineContext->getContainer()->appendChild(new NostrSchemeData('nevent', $eventId, $relays, $author, $kind)); break; case 'naddr': - $inlineContext->getContainer()->appendChild(new NostrSchemeData('naddr', $bechEncoded, null, null, null)); + /** @var NAddr $decodedNpub */ + $decodedEvent = $decoded->data; + $identifier = $decodedEvent->getIdentifier(); + $pubkey = $decodedEvent->getPubkey(); + $kind = $decodedEvent->getKind(); + $relays = $decodedEvent->getRelays(); + $inlineContext->getContainer()->appendChild(new NostrSchemeData('naddr', $identifier, $relays, $pubkey, $kind)); break; case 'nrelay': // deprecated