Browse Source

bug-fixes

imwald
Silberengel 5 days ago
parent
commit
20e8a6f4ef
  1. 4
      importmap.php
  2. 5
      src/Controller/AuthorController.php
  3. 35
      src/Service/ArticleCommentThreadLoader.php
  4. 50
      src/Service/NostrClient.php
  5. 28
      templates/components/Organisms/Comments.html.twig

4
importmap.php

@ -59,6 +59,10 @@ return [ @@ -59,6 +59,10 @@ return [
'@noble/hashes' => [
'version' => '1.3.1',
],
// Required by nostr-tools/nip19 (bytesToHex / hexToBytes / concatBytes); bare @noble/hashes is not enough.
'@noble/hashes/utils' => [
'version' => '1.3.1',
],
'@scure/base' => [
'version' => '1.1.1',
],

5
src/Controller/AuthorController.php

@ -33,6 +33,11 @@ class AuthorController extends AbstractController @@ -33,6 +33,11 @@ class AuthorController extends AbstractController
ProfilePaymentLinksBuilder $profilePaymentLinks,
ProfileIdentityLinksBuilder $profileIdentityLinks,
): Response {
// Profile pages chain several sequential Nostr REQ runs; match article pages so a slow relay
// set does not hit PHP’s default 30s max_execution_time during Twig render.
@set_time_limit(300);
@ini_set('max_execution_time', '300');
$keys = new Key();
$pubkey = $keys->convertToHex($npub);

35
src/Service/ArticleCommentThreadLoader.php

@ -4,6 +4,7 @@ declare(strict_types=1); @@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Service;
use App\Enum\KindsEnum;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
@ -162,6 +163,7 @@ final readonly class ArticleCommentThreadLoader @@ -162,6 +163,7 @@ final readonly class ArticleCommentThreadLoader
]);
$this->enrichThreadListForDisplay($list, $articleEventHexId);
$this->stripRepostEventBodies($list, $quotes);
$commentLinks = [];
$quoteLinks = [];
@ -194,6 +196,39 @@ final readonly class ArticleCommentThreadLoader @@ -194,6 +196,39 @@ final readonly class ArticleCommentThreadLoader
];
}
/**
* NIP-18 reposts (kinds 6 and 16) carry a JSON-wrapped copy of the original; we only show who reposted, not the body.
*
* @param array<int, object> $list
* @param array<int, object> $quotes
*/
private function stripRepostEventBodies(array $list, array $quotes): void
{
$strip = static function (object $ev): void {
$k = (int) ($ev->kind ?? 0);
if ($k !== KindsEnum::REPOST->value && $k !== KindsEnum::GENERIC_REPOST->value) {
return;
}
$ev->content = '';
if (isset($ev->unfold_reply_blurb)) {
$ev->unfold_reply_blurb = null;
}
if (isset($ev->unfold_body)) {
$ev->unfold_body = '';
}
};
foreach ($list as $ev) {
if (\is_object($ev)) {
$strip($ev);
}
}
foreach ($quotes as $ev) {
if (\is_object($ev)) {
$strip($ev);
}
}
}
/**
* @param array<string, array<int, mixed>> $linkBucket
* @param array<string, string> $processedContent

50
src/Service/NostrClient.php

@ -39,6 +39,12 @@ class NostrClient @@ -39,6 +39,12 @@ class NostrClient
*/
private const MAX_DISCUSSION_RELAY_URLS = 10;
/**
* {@see Request::send()} hits relays sequentially; profile pages (metadata, long-form list, 10133) used
* the full default+article+profile list (~8–9 wss) → 2 slow relays can exceed PHP’s 30s default max_execution_time.
*/
private const MAX_PROFILE_SEQUENTIAL_RELAY_URLS = 3;
/**
* {@see sendArticleDiscussionToRelaysSequential} visits relays one after another (~RELAY_REQUEST_TIMEOUT_SEC
* each). Keep this low so HTTP /fragment/comments and browsers do not hit 60–90s proxy cuts.
@ -327,6 +333,25 @@ class NostrClient @@ -327,6 +333,25 @@ class NostrClient
return $relaySet;
}
/**
* @param list<string> $urls
*
* @return list<string>
*/
private function capSequentialRelaysForProfileFetches(array $urls): array
{
if (\count($urls) <= self::MAX_PROFILE_SEQUENTIAL_RELAY_URLS) {
return $urls;
}
$this->logger->notice('nostr.relay_list_capped', [
'context' => 'profile_sequential',
'max' => self::MAX_PROFILE_SEQUENTIAL_RELAY_URLS,
'had' => \count($urls),
]);
return \array_values(\array_slice($urls, 0, self::MAX_PROFILE_SEQUENTIAL_RELAY_URLS));
}
/**
* Full NIP-65 (kind-10002) wss:// list for a hex pubkey, cached. Used for comment fetches; prefer
* {@see getTopReputableRelaysForAuthor} when you only need a few relays.
@ -589,9 +614,9 @@ class NostrClient @@ -589,9 +614,9 @@ class NostrClient
*/
public function getNpubMetadata($npub): \stdClass
{
$relaysTried = $this->profileMetadataQueryRelayUrlList();
$relaysTried = $this->capSequentialRelaysForProfileFetches($this->profileMetadataQueryRelayUrlList());
$relaysTriedStr = implode(', ', array_map(self::relayLogLabel(...), $relaysTried));
$relaySet = $this->relaySetForProfileMetadataFetch();
$relaySet = $this->relaySetFromDistinctUrlList($relaysTried);
$this->logger->info(sprintf('Getting metadata for npub (relays: %s)', $relaysTriedStr), ['npub' => $npub, 'relays' => $relaysTried]);
$request = $this->createNostrRequest(
kinds: [KindsEnum::METADATA],
@ -624,9 +649,9 @@ class NostrClient @@ -624,9 +649,9 @@ class NostrClient
*/
public function getKind10133PaymentTargetEventsForNpub(string $npub, int $limit = 20): array
{
$relaysTried = $this->profileMetadataQueryRelayUrlList();
$relaysTried = $this->capSequentialRelaysForProfileFetches($this->profileMetadataQueryRelayUrlList());
$relaysTriedStr = implode(', ', array_map(self::relayLogLabel(...), $relaysTried));
$relaySet = $this->relaySetForProfileMetadataFetch();
$relaySet = $this->relaySetFromDistinctUrlList($relaysTried);
try {
$request = $this->createNostrRequest(
kinds: [KindsEnum::PAYMENT_TARGETS],
@ -1527,13 +1552,20 @@ class NostrClient @@ -1527,13 +1552,20 @@ class NostrClient
*/
public function getLongFormContentForPubkey(string $ident): array
{
// Add user relays to the default set
$authorRelays = $this->getTopReputableRelaysForAuthor($ident);
// Create a RelaySet from the author's relays
$relaySet = $this->defaultRelaySet;
if (!empty($authorRelays)) {
$relaySet = $this->createRelaySet($authorRelays);
$base = $this->configuredArticleRelayUrlList();
$merged = $authorRelays !== [] ? array_merge($base, $authorRelays) : $base;
$seen = [];
$deduped = [];
foreach ($merged as $url) {
if (!\is_string($url) || $url === '' || isset($seen[$url])) {
continue;
}
$seen[$url] = true;
$deduped[] = $url;
}
$capped = $this->capSequentialRelaysForProfileFetches($deduped);
$relaySet = $this->relaySetFromDistinctUrlList($capped);
// Create request using the helper method
$request = $this->createNostrRequest(

28
templates/components/Organisms/Comments.html.twig

@ -63,6 +63,7 @@ @@ -63,6 +63,7 @@
{% set cpk = item.pubkey|default('') %}
{% set cts = item.created_at|default(null) %}
{% set cdepth = item.unfold_depth|default(0) %}
{% set is_nip18_repost = item.kind is defined and (item.kind == 6 or item.kind == 16) %}
<div class="card comment comment--depth-{{ cdepth }}">
<div class="metadata">
<p>
@ -70,20 +71,26 @@ @@ -70,20 +71,26 @@
<span class="ui-badge ui-badge--neutral" title="Legacy text-note reply (pre–NIP-22)">kind 1</span>
{% elseif item.kind is defined and item.kind == 1111 %}
<span class="ui-badge ui-badge--secondary" title="NIP-22 comment">1111</span>
{% elseif is_nip18_repost %}
<span class="ui-badge ui-badge--neutral" title="NIP-18 repost (body omitted)">repost</span>
{% endif %}
{% if cpk != '' %}<twig:Molecules:UserFromNpub ident="{{ cpk }}" />{% else %}<span class="text-subtle">Unknown</span>{% endif %}
</p>
<small>{% if cts is not null and cts != '' %}{{ cts|date('F j Y') }}{% endif %}</small>
</div>
{% if item.unfold_reply_blurb|default('')|trim != '' %}
{% if not is_nip18_repost and item.unfold_reply_blurb|default('')|trim != '' %}
<div class="comment__reply-blurb" role="note" aria-label="Reply context">
<twig:Atoms:Content content="{{ item.unfold_reply_blurb }}" />
</div>
{% endif %}
<div class="card-body">
<twig:Atoms:Content content="{{ item.unfold_body|default(item.content|default('')) }}" />
{% if is_nip18_repost %}
<p class="text-subtle">Repost</p>
{% else %}
<twig:Atoms:Content content="{{ item.unfold_body|default(item.content|default('')) }}" />
{% endif %}
</div>
{% if cid != '' and commentLinks[cid] is defined and commentLinks[cid]|length > 0 %}
{% if not is_nip18_repost and cid != '' and commentLinks[cid] is defined and commentLinks[cid]|length > 0 %}
<div class="card-footer nostr-previews mt-3">
<div class="preview-container">
{% for link in commentLinks[cid] %}
@ -161,10 +168,15 @@ @@ -161,10 +168,15 @@
{% set cid = item.id|default('') %}
{% set cpk = item.pubkey|default('') %}
{% set cts = item.created_at|default(null) %}
{% set q_repost = item.kind is defined and (item.kind == 6 or item.kind == 16) %}
<div class="card comment comment--quote">
<div class="metadata">
<p>
<span class="ui-badge ui-badge--neutral">kind {{ item.kind|default('?') }}</span>
{% if q_repost %}
<span class="ui-badge ui-badge--neutral" title="NIP-18 repost (body omitted)">repost (kind {{ item.kind }})</span>
{% else %}
<span class="ui-badge ui-badge--neutral">kind {{ item.kind|default('?') }}</span>
{% endif %}
{% if cpk != '' %}<twig:Molecules:UserFromNpub ident="{{ cpk }}" />{% else %}<span class="text-subtle">Unknown</span>{% endif %}
</p>
<small>
@ -176,9 +188,13 @@ @@ -176,9 +188,13 @@
</small>
</div>
<div class="card-body">
<twig:Atoms:Content content="{{ item.content|default('') }}" />
{% if q_repost %}
<p class="text-subtle">Repost</p>
{% else %}
<twig:Atoms:Content content="{{ item.content|default('') }}" />
{% endif %}
</div>
{% if cid != '' and quoteLinks[cid] is defined and quoteLinks[cid]|length > 0 %}
{% if not q_repost and cid != '' and quoteLinks[cid] is defined and quoteLinks[cid]|length > 0 %}
<div class="card-footer nostr-previews mt-3">
<div class="preview-container">
{% for link in quoteLinks[cid] %}

Loading…
Cancel
Save