From 67d92eba017e394e1ab9f2932e6fd2a7b7f75190 Mon Sep 17 00:00:00 2001 From: Max Mehl Date: Tue, 24 Feb 2026 11:55:42 +0100 Subject: [PATCH] feat: support defining Mastodon toot URL --- README.md | 25 ++++++++++ layouts/partials/comments.html | 1 + static/comments/getcomments.js | 12 +++-- static/comments/getcomments.php | 84 +++++++++++++++++++++++++-------- 4 files changed, 100 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 46b8aa5..b0c4963 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,31 @@ Comments are loaded automatically via JavaScript when visitors view a blog post. - Queries the PHP backend to find matching Mastodon toots - Displays replies as comments with full formatting +### Optional: Direct Toot URL (Recommended for Performance) + +For better performance, you can specify the Mastodon toot URL directly in your post's front matter: + +```yaml +--- +title: "My Post Title" +mastodon_toot_url: "https://mastodon.social/@username/113456789012345678" +--- +``` + +**Benefits:** +- Skips the entire toot search phase +- Reduces API calls significantly +- Faster page loads +- Works across different Mastodon instances +- Ensures the correct toot is always used + +**When to use:** +- For any post where you know the Mastodon toot URL +- Especially recommended for popular or archived posts +- When you've shared the post on a different Mastodon instance + +If no `mastodon_toot_url` is provided, the system automatically falls back to searching for toots containing the post URL. + ### Force Cache Refresh To manually clear and rebuild the cache: diff --git a/layouts/partials/comments.html b/layouts/partials/comments.html index 8e931b7..1f6368c 100644 --- a/layouts/partials/comments.html +++ b/layouts/partials/comments.html @@ -30,3 +30,4 @@ + diff --git a/static/comments/getcomments.js b/static/comments/getcomments.js index ac8953e..a2449f9 100644 --- a/static/comments/getcomments.js +++ b/static/comments/getcomments.js @@ -28,12 +28,18 @@ $(document).ready(function() { debugLog('Searching for comments for: ' + RelPermalink); + // Check if a direct Mastodon toot URL is provided + var ajaxData = { search: RelPermalink }; + if (MastodonTootUrl && MastodonTootUrl !== "") { + // Pass the full URL to PHP so it can extract instance and ID + ajaxData.toot_url = MastodonTootUrl; + debugLog('Using predefined Mastodon toot URL: ' + MastodonTootUrl); + } + $.ajax({ url: "/comments/getcomments.php", type: "get", - data: { - search : RelPermalink - }, + data: ajaxData, success: function(data) { // Enable debug logging if backend has debug enabled if (data.debug === true) { diff --git a/static/comments/getcomments.php b/static/comments/getcomments.php index 89305b1..f32ffa2 100644 --- a/static/comments/getcomments.php +++ b/static/comments/getcomments.php @@ -10,8 +10,11 @@ $instance = $config['mastodon-instance']; $uid = $config['user-id']; $searchurl = $config['search-url']; $search = isset($_GET['search']) ? strtolower($_GET['search']) : ''; +$toot_url = isset($_GET['toot_url']) ? $_GET['toot_url'] : null; $force_refresh = isset($_GET['force_refresh']) && $_GET['force_refresh'] == '1'; $debug_on = $config['debug']; + +debug("Request parameters - search: $search, toot_url: " . ($toot_url ? $toot_url : "not provided") . ", force_refresh: " . ($force_refresh ? "yes" : "no")); /* cache files */ $ctt = $config['cache_toots']; $dbt = "cache-toots.json"; @@ -45,6 +48,19 @@ function debug($data) { } } +/* Parse Mastodon toot URL to extract instance and toot ID */ +function parseTootUrl($url) { + // Expected format: https://instance.example/@username/1234567890 + // or: https://instance.example/users/username/statuses/1234567890 + if (preg_match('|^(https?://[^/]+)/.*/(\d+)$|i', $url, $matches)) { + return [ + 'instance' => $matches[1], + 'id' => $matches[2] + ]; + } + return null; +} + /* CACHE FUNCTIONS */ /* write data to file */ function write_db($db, $data, $id) { @@ -170,12 +186,41 @@ function tootContextAndStats($instance, $id, &$result) { * START PROGRAM ***************/ -/* check whether the cached file containing all toots is older than max. cache time */ -// this at the same time loads the cached DB, either way -$cachebreak = $force_refresh; // Force cache break if requested -read_db($dbt, $toots, $ctt, $cachebreak, false); +// create empty $result template +$result_empty = ['comments' => [], 'stats' => ['reblogs' => 0, 'favs' => 0, 'replies' => 0, 'url' => '', 'root' => 0]]; +$result = $result_empty; -if ($cachebreak) { +/* If toot_url is provided, skip the toot search entirely */ +$toot_instance = $instance; // Default to config instance +$id = null; // Initialize + +if ($toot_url) { + $parsed = parseTootUrl($toot_url); + if ($parsed) { + $id = $parsed['id']; + $toot_instance = $parsed['instance']; + debug("Toot URL provided: $toot_url"); + debug("Extracted - Instance: $toot_instance, ID: $id"); + + // When using a custom instance, include instance hash in cache filename to avoid collisions + if ($toot_instance !== $instance) { + $instance_hash = md5($toot_instance); + $dbc = "cache-comments_%id-{$instance_hash}.json"; + debug("Using instance-specific cache file for non-default instance"); + } + } else { + debug("Failed to parse toot URL: $toot_url"); + $toot_url = null; // Fall back to search + } +} + +if (!$toot_url) { + /* check whether the cached file containing all toots is older than max. cache time */ + // this at the same time loads the cached DB, either way + $cachebreak = $force_refresh; // Force cache break if requested + read_db($dbt, $toots, $ctt, $cachebreak, false); + + if ($cachebreak) { /* Collect all the toots */ /* get id of latest cached toot, and set as $min_id */ debug("Toots cache outdated. Checking for new toots"); @@ -208,24 +253,25 @@ if ($cachebreak) { } } else { debug("Toots cache is up-to-date"); -} + } -// create empty $result -$result_empty = ['comments' => [], 'stats' => ['reblogs' => 0, 'favs' => 0, 'replies' => 0, 'url' => '', 'root' => 0]]; -$result = $result_empty; + /* check if URL from $search exists in $toots */ + $found_id = null; + foreach ($toots as $toot) { + if (!empty($toot['url']) && strpos($toot['url'], $search) !== false) { + $found_id = $toot['id']; // will keep the oldest (last in array) + } + } -/* check if URL from $search exists in $toots */ -$found_id = null; -foreach ($toots as $toot) { - if (!empty($toot['url']) && strpos($toot['url'], $search) !== false) { - $found_id = $toot['id']; // will keep the oldest (last in array) + if ($found_id === null) { + debug("Blog URL \"$search\" has not been found"); + } else { + $id = $found_id; } } -if ($found_id === null) { - debug("Blog URL \"$search\" has not been found"); -} else { - $id = $found_id; +/* Process comments if we have a toot ID (either from URL or search) */ +if ($id) { /* read cached comments, or reload new comments if cached data too old */ $cachebreak = false; @@ -239,7 +285,7 @@ if ($found_id === null) { $result = $result_empty; read_db($dbc, $result, $ctc, $cachebreak, $id); /* Extract comments and stats from toot */ - tootContextAndStats($instance, $id, $result); + tootContextAndStats($toot_instance, $id, $result); // Always count replies manually for accuracy $result['stats']['replies'] = count($result['comments']); $result['stats']['root'] = $id;