Browse Source

Streamline nostr scheme extension

imwald
Nuša Pukšič 8 months ago
parent
commit
3d82e3ea81
  1. 12
      src/Util/CommonMark/Converter.php
  2. 11
      src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php
  3. 15
      src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php
  4. 11
      src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php
  5. 10
      src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php
  6. 74
      src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php

12
src/Util/CommonMark/Converter.php

@ -2,7 +2,7 @@ @@ -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; @@ -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 @@ -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']));

11
src/Util/CommonMark/NostrSchemeExtension/NostrEventRenderer.php

@ -22,15 +22,22 @@ class NostrEventRenderer implements NodeRendererInterface @@ -22,15 +22,22 @@ class NostrEventRenderer implements NodeRendererInterface
} 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;
}
}

15
src/Util/CommonMark/NostrSchemeExtension/NostrMentionParser.php

@ -2,6 +2,7 @@ @@ -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; @@ -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 @@ -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));

11
src/Util/CommonMark/NostrSchemeExtension/NostrRawNpubParser.php

@ -2,19 +2,19 @@ @@ -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 @@ -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));

10
src/Util/CommonMark/NostrSchemeExtension/NostrSchemeExtension.php

@ -2,23 +2,23 @@ @@ -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)

74
src/Util/CommonMark/NostrSchemeExtension/NostrSchemeParser.php

@ -2,18 +2,18 @@ @@ -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 @@ -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

Loading…
Cancel
Save