From 3950a657831a7b7322d65e56216d2a2e53d4bd48 Mon Sep 17 00:00:00 2001 From: James Griffin Date: Fri, 27 Mar 2026 12:16:02 -0300 Subject: [PATCH] Filter thread replies to original author only Only replies from the root post's author are rendered; replies from other users are skipped. Adds fixture and test for a real-world post with an other-author reply (3mhypinzezo2l). Co-Authored-By: Claude Sonnet 4.6 --- extension.php | 21 ++++-- tests/BlueskyThreadsTest.php | 19 +++++ tests/fixtures/thread_3mhypinzezo2l.json | 93 ++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 tests/fixtures/thread_3mhypinzezo2l.json diff --git a/extension.php b/extension.php index b10ef2c..8184632 100644 --- a/extension.php +++ b/extension.php @@ -123,7 +123,8 @@ final class BlueskyThreadsExtension extends Minz_Extension { return $fallback; } - $html = $this->renderThread($data['thread'], true); + $rootAuthorDid = $data['thread']['post']['author']['did'] ?? null; + $html = $this->renderThread($data['thread'], true, $rootAuthorDid); $this->writeCache($url, $html); return $html; } @@ -221,7 +222,7 @@ final class BlueskyThreadsExtension extends Minz_Extension { // Thread rendering // ------------------------------------------------------------------------- - private function renderThread(array $node, bool $isRoot): string { + private function renderThread(array $node, bool $isRoot, ?string $rootAuthorDid = null): string { if (!isset($node['post'])) { return ''; } @@ -229,13 +230,21 @@ final class BlueskyThreadsExtension extends Minz_Extension { $html = $this->renderPost($node['post'], $isRoot); if (!empty($node['replies'])) { - $html .= '
'; + $repliesHtml = ''; foreach ($node['replies'] as $reply) { - if (isset($reply['post'])) { - $html .= $this->renderThread($reply, false); + if (!isset($reply['post'])) { + continue; } + if ($rootAuthorDid !== null && ($reply['post']['author']['did'] ?? null) !== $rootAuthorDid) { + continue; + } + $repliesHtml .= $this->renderThread($reply, false, $rootAuthorDid); + } + if ($repliesHtml !== '') { + $html .= '
'; + $html .= $repliesHtml; + $html .= '
'; } - $html .= '
'; } return $html; diff --git a/tests/BlueskyThreadsTest.php b/tests/BlueskyThreadsTest.php index df77d77..9cd4648 100644 --- a/tests/BlueskyThreadsTest.php +++ b/tests/BlueskyThreadsTest.php @@ -174,6 +174,25 @@ class BlueskyThreadsTest extends TestCase { $this->assertStringContainsString('View on Bluesky', $html); } + /** + * A post with only a reply from another user (3mhypinzezo2l fixture) must + * render just the root post — the other-author reply is filtered out. + */ + public function testOtherAuthorRepliesAreExcluded(): void { + $data = json_decode(file_get_contents(__DIR__ . '/fixtures/thread_3mhypinzezo2l.json'), true); + $thread = $data['thread']; + $rootAuthorDid = $thread['post']['author']['did']; + + $html = $this->call('renderThread', $thread, true, $rootAuthorDid); + + $this->assertSame(1, substr_count($html, 'bsky-post'), + 'Only the root post should be rendered; other-author reply must be excluded'); + $this->assertSame(0, substr_count($html, 'bsky-replies'), + 'No replies wrapper should be rendered when all replies are from other authors'); + $this->assertStringNotContainsString('lasagnazero.bsky.social', $html, + 'Reply author handle must not appear in output'); + } + // ------------------------------------------------------------------------- // applyFacets — rich-text rendering // ------------------------------------------------------------------------- diff --git a/tests/fixtures/thread_3mhypinzezo2l.json b/tests/fixtures/thread_3mhypinzezo2l.json new file mode 100644 index 0000000..9307483 --- /dev/null +++ b/tests/fixtures/thread_3mhypinzezo2l.json @@ -0,0 +1,93 @@ +{ + "thread": { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": { + "uri": "at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhypinzezo2l", + "cid": "bafyreialfdaqkwykiinxzy3xb6qhcenuwsaxi7d6qwzx4zfdyhprzo4s2e", + "author": { + "did": "did:plc:d4324t32vfi5xzydqbh2qdj3", + "handle": "hockeyviz.com", + "displayName": "Micah McCurdy ", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:d4324t32vfi5xzydqbh2qdj3/bafkreiddyjdi3hty4z73vdoyxja3hj5n7izrsdmeoif2ppkqidbt4nsluq", + "associated": { + "chat": { + "allowIncoming": "all" + }, + "activitySubscription": { + "allowSubscriptions": "followers" + } + }, + "labels": [], + "createdAt": "2023-05-28T17:07:32.578Z" + }, + "record": { + "$type": "app.bsky.feed.post", + "createdAt": "2026-03-26T23:01:26.959Z", + "facets": [], + "langs": [ + "en" + ], + "tags": [], + "text": "Any good musician can rerecord somebody else's song but what really makes a cover worth doing is that little note of jealousy nestled in the blanket of admiration. It's the same with reproving somebody else's result or redoing a viz. Shoulda been me, the grudgingist yet in fact highest respect." + }, + "bookmarkCount": 0, + "replyCount": 1, + "repostCount": 0, + "likeCount": 21, + "quoteCount": 0, + "indexedAt": "2026-03-26T23:01:27.160Z", + "labels": [] + }, + "replies": [ + { + "$type": "app.bsky.feed.defs#threadViewPost", + "post": { + "uri": "at://did:plc:vfi77nom5cwpnhurnxhy7ltj/app.bsky.feed.post/3mhyw2ijjc22a", + "cid": "bafyreif6evbxbqcf5t3t6i4apm6pertxy4mylleet6a2d2fgzwqnz6inly", + "author": { + "did": "did:plc:vfi77nom5cwpnhurnxhy7ltj", + "handle": "lasagnazero.bsky.social", + "displayName": "L0", + "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:vfi77nom5cwpnhurnxhy7ltj/bafkreigjurbghqtq3iogsqarnxldhl54zqdmi657pflwrrqwa2wisteq6i", + "associated": { + "activitySubscription": { + "allowSubscriptions": "followers" + } + }, + "labels": [], + "createdAt": "2024-11-07T14:26:50.985Z" + }, + "record": { + "$type": "app.bsky.feed.post", + "createdAt": "2026-03-27T00:58:47.713Z", + "langs": [ + "en", + "ru" + ], + "reply": { + "parent": { + "cid": "bafyreialfdaqkwykiinxzy3xb6qhcenuwsaxi7d6qwzx4zfdyhprzo4s2e", + "uri": "at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhypinzezo2l" + }, + "root": { + "cid": "bafyreialfdaqkwykiinxzy3xb6qhcenuwsaxi7d6qwzx4zfdyhprzo4s2e", + "uri": "at://did:plc:d4324t32vfi5xzydqbh2qdj3/app.bsky.feed.post/3mhypinzezo2l" + } + }, + "text": "Something about imitation and flattery\u2026" + }, + "bookmarkCount": 0, + "replyCount": 0, + "repostCount": 0, + "likeCount": 0, + "quoteCount": 0, + "indexedAt": "2026-03-27T00:58:48.952Z", + "labels": [] + }, + "replies": [], + "threadContext": {} + } + ], + "threadContext": {} + } +}