Browse Source

make hugo-mastodon-comments a submodule

new-gallery
max.mehl 1 year ago
parent
commit
98475f605b
Signed by: mxmehl GPG Key ID: 2704E4AB371E2E92
9 changed files with 4 additions and 421 deletions
  1. +3
    -0
      .gitmodules
  2. +1
    -0
      themes/hugo-mastodon-comments
  3. +0
    -2
      themes/hugo-mastodon-comments/.gitignore
  4. +0
    -26
      themes/hugo-mastodon-comments/layouts/partials/comments.html
  5. +0
    -11
      themes/hugo-mastodon-comments/static/comments/config.sample.php
  6. +0
    -51
      themes/hugo-mastodon-comments/static/comments/getcomments.js
  7. +0
    -249
      themes/hugo-mastodon-comments/static/comments/getcomments.php
  8. +0
    -66
      themes/hugo-mastodon-comments/static/comments/mastodon-comments.css
  9. +0
    -16
      themes/hugo-mastodon-comments/theme.yaml

+ 3
- 0
.gitmodules View File

@@ -4,3 +4,6 @@
[submodule "themes/hugo-snap-gallery"]
path = themes/hugo-snap-gallery
url = https://src.mehl.mx/mxmehl/hugo-snap-gallery
[submodule "themes/hugo-mastodon-comments"]
path = themes/hugo-mastodon-comments
url = https://src.mehl.mx/mxmehl/hugo-mastodon-comments

+ 1
- 0
themes/hugo-mastodon-comments

@@ -0,0 +1 @@
Subproject commit b9090e34553c731b287b2752a5c8fe628ad4b741

+ 0
- 2
themes/hugo-mastodon-comments/.gitignore View File

@@ -1,2 +0,0 @@
static/comments/config.php
static/comments/cache-*.json

+ 0
- 26
themes/hugo-mastodon-comments/layouts/partials/comments.html View File

@@ -1,26 +0,0 @@
{{ if ne .Params.page true }}
<div class="comments-container">
<h5>Comments</h5>
<noscript>
<p class="bg-info" style="text-align: center; padding: 5px;">
Comments are only visible with JavaScript enabled. They are
dynamically loaded from Mastodon. The code to display the
comments is Free Software and <a
href="https://src.mehl.mx/mxmehl/hugo-mastodon-comments">completely
transparent</a>.
</p>
</noscript>
<div id="statistics">
<div id="like-count-container"></div><div id="reblog-count-container"></div><div id="reply-count-container"></div>
</div>
<div class="clear"></div>
<div id="comments"></div>
<div class="clear"></div>
<div id="reference"></div>
</div>
{{ end }}

<script>var RelPermalink="{{ .RelPermalink }}"</script>
<script>var MastodonUser="{{ .Site.Params.mastodoncomments.user }}"</script>
<script>var BlogRegex="{{ .Site.Params.mastodoncomments.regex }}"</script>
<script>var CommentsContact="{{ .Site.Params.mastodoncomments.contact }}"</script>

+ 0
- 11
themes/hugo-mastodon-comments/static/comments/config.sample.php View File

@@ -1,11 +0,0 @@
<?php
$config = [
'mastodon-instance' => 'https://mastodon.social', // URL of your Mastodon instance
'user-id' => 379833, // Your Mastodon-ID. A bit tricky to find out, the API might help
// the URL of your blog. All toots are searched for this string
// please use https?:// as schema
'search-url' => 'https?://fsfe.org',
'cache_toots' => 300, // seconds to cache toots
'cache_comments' => 300, // seconds to cache comments (per ID)
'debug' => false // writes some debug messages in error_log
];

+ 0
- 51
themes/hugo-mastodon-comments/static/comments/getcomments.js View File

@@ -1,51 +0,0 @@
$(document).ready(function() {

// check if we show a blog post or not. Regex is defined in site-wide config
var patt = new RegExp(BlogRegex);
var isArticle = patt.test(RelPermalink);
if (isArticle === false) {
console.log("Not a blog post, no need to search for comments");
return;
}

$.ajax({
url: "/comments/getcomments.php",
type: "get",
data: {
search : RelPermalink
},
success: function(data) {
var stats = data.stats;
var root = data.stats.root;
$("#like-count-container").append('<div id="mastodon-like-count"><a href="' + stats.url + '"><span title="Likes"><i class="fa fa-star"></i>' + stats.favs + '</span></a></div></div>');
$("#reblog-count-container").append('<div id="mastodon-reblog-count"><a href="' + stats.url + '"><span title="Reblogs"><i class="fa fa-retweet"></i>' + stats.reblogs + '</span></a></div></div>');
$("#reply-count-container").append('<div id="mastodon-reply-count"><a href="' + stats.url + '"><span title="Comments"><i class="fa fa-comments"></i>' + stats.replies + '</span></a></div></div>');
var comments = data.comments;
$.each(comments, function(key, value) {
var timestamp = Date.parse(value.date);
var date = new Date(timestamp);
var comment = "<div class='comment' id='" + key + "'>";
comment += "<img class='avatar' src='" + value.author.avatar + "' />";
comment += "<div class='author'><a class='displayName' href='" + value.author.url + "'>" + value.author.display_name + "</a> wrote at ";
comment += "<a class='date' href='" + value.url + "'>" + date.toDateString() + ', ' + date.toLocaleTimeString() + "</a></div>";
comment += "<div class='toot'>" + value.toot + "</div>";
comment += "</div>";
var parentComment = document.getElementById(value.reply_to);
if (value.reply_to === root || parentComment === null) {
$("#comments").append(comment);
} else {
var selector = '#'+value.reply_to;
$(selector).append(comment);
}
});
if (parseInt(root) > 0) {
$("#reference").append("<a href='" + MastodonUser + "/statuses/" + root + "'>Join the discussion on Mastodon!</a>");
} else {
$("#comments").empty();
$("#statistics").empty();
$("#reference").append("Comments are handled by my <a href='" + MastodonUser + "'>Mastodon account</a>. Sadly this article wasn't published at Mastodon. Feel free to <a href='" + CommentsContact + "'>send me a mail</a> if you want to share your thoughts regarding this topic.");
}

}
});
});

+ 0
- 249
themes/hugo-mastodon-comments/static/comments/getcomments.php View File

@@ -1,249 +0,0 @@
<?php

/* load config. You normally don't want to edit something here */
require_once 'config.php';
$instance = $config['mastodon-instance'];
$uid = $config['user-id'];
$searchurl = $config['search-url'];
$search = isset($_GET['search']) ? strtolower($_GET['search']) : '';
$debug_on = $config['debug'];
/* cache files */
$ctt = $config['cache_toots'];
$dbt = "cache-toots.json";
$ctc = $config['cache_comments'];
$dbc = "cache-comments_%id.json";

/* Exit if search empty */
if (empty($search)) {
debug("No proper search given");
die();
}

/* MISC FUNCTIONS */
function debug($data) {
global $debug_on;
if ($debug_on === true) {
error_log("[getcomments.php] " . print_r($data, TRUE));
}
}

/* CACHE FUNCTIONS */
/* write data to file */
function write_db($db, $data, $id) {
// if $id is given, it's a comments file. Replace placeholder in filename
if ($id) {
$db = str_replace('%id', $id, $db);
}
$file['toots'] = $data;
$file['timestamp'] = time();
// encode and write file
$encoded = json_encode($file, JSON_PRETTY_PRINT);
file_put_contents($db, $encoded, LOCK_EX);
}
/* delete file */
function delete_db($db, $id) {
// if $id is given, it's a comments file. Replace placeholder in filename
if ($id) {
$db = str_replace('%id', $id, $db);
}
unlink($db);
}
/* access data from file */
function read_db($db, &$data, $cachetime, &$cachebreak, $id) {
// if $id is given, it's a comments file. Replace placeholder in filename
if ($id) {
$db = str_replace('%id', $id, $db);
}
// if DB does not exist, create it with empty array
if (! file_exists($db)) {
// if $data empty (usually with $toots, not with comment's $result), populate with empty array
if (empty($data)) {
$data = array();
}
touch($db);
write_db($db, $data, $id);
$cachebreak = true;
}
$file = file_get_contents($db, true);
$data = json_decode($file, true);

// check if timestamp in cache file too old
if (empty($data['timestamp']) || ($data['timestamp'] + $cachetime < time())) {
$cachebreak = true;
}

$data = $data['toots'];
}

/* TOOT FUNCTIONS */
function collectToots($instance, $uid, $min_id, $searchurl) {
$raw = file_get_contents("$instance/api/v1/accounts/$uid/statuses?exclude_reblogs=true&exclude_replies=true&limit=50&min_id=$min_id");
$json_complete = json_decode($raw, true);
$json = array();
foreach ($json_complete as $toot) {
$json[] = array('id' => $toot['id'], 'date' => $toot['created_at'] ,'url' => analyzeToot($instance, $toot['id'], $searchurl));
}
return($json);
}
/* Find out if a toot contains the searched URL */
function analyzeToot($instance, $id, $searchurl) {
debug("Searching for $searchurl in $id");
$raw = file_get_contents("$instance/api/v1/statuses/$id");
$json = json_decode($raw, true);

// search for $searchurl inside of <a> tags, until (and excluding) a "
preg_match("|$searchurl.+?(?=\")|i", $json['content'], $matches);

if(!empty($matches)) {
return(strtolower($matches[0])); // take first match inside toot
} else {
return("");
}
}
/* of context, extract the interesting bits */
function filterComments($descendants, $root, &$result) {
// go through each comment
foreach ($descendants as $d) {
$result['comments'][$d['id']] = [
'author' => [
'display_name' => $d['account']['display_name'] ? $d['account']['display_name'] : $d['account']['username'],
'avatar' => $d['account']['avatar_static'],
'url' => $d['account']['url']
],
'toot' => $d['content'],
'date' => $d['created_at'],
'url' => $d['uri'],
'reply_to' => $d['in_reply_to_id'],
'root' => $root,
];
}
return $result;
}
/* get /context of toot */
function tootContext($instance, $id, &$result) {
$raw = file_get_contents("$instance/api/v1/statuses/$id/context");
$json = json_decode($raw, true);
filterComments($json['descendants'], $id, $result);
}
/* extract stats info from toot */
function filterStats($stats) {
$result = [
'reblogs' => (int)$stats['reblogs_count'],
'favs' => (int)$stats['favourites_count'],
'replies' => (int)$stats['replies_count'],
'url' => $stats['url']
];
return $result;
}
/* for toot, extract interesting statistics */
function tootStats($instance, $id, &$result) {
debug("Checking ID $id");
$raw = file_get_contents("$instance/api/v1/statuses/$id");
$json = json_decode($raw, true);
$newStats = filterStats($json);
$result['stats']['reblogs'] += $newStats['reblogs'];
$result['stats']['favs'] += $newStats['favs'];
$result['stats']['replies'] += $newStats['replies'];
if (empty($result['stats']['url'])) {
$result['stats']['url'] = $newStats['url'];
}
}

/***************
* 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 = false;
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");
if (!empty($toots['0']['id'])) {
$min_id_cached = $toots['0']['id'];
$min_id = $min_id_cached;
} else {
/* if cached toots do not exist, start from oldest toot */
$min_id = "0";
$min_id_cached = "0";
}

/* test whether there are new toots available */
// Search for toots older than the cached latest toot ID ($min_id)
$uptodate = false;
while ($uptodate === false) {
$toots = array_merge(collectToots($instance, $uid, $min_id, $searchurl), $toots);
$min_id_new = $toots['0']['id']; // the latest ID of the recent search

if ($min_id_new === $min_id) {
// min_id is the latest, let's write the new DB and end this loop
$uptodate = true;
debug("Toots up-to-date. Rewrite cache DB.");
write_db($dbt, $toots, false);
} else {
// next round looks for toots newer than the newly found ID
debug("Newer toots than in cache found. Starting another search for new toots");
$min_id = $min_id_new;
}
}
} 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 */
$id = array_keys(
array_filter(
array_column($toots, 'url'),
function ($value) use ($search) {
return (strpos($value, $search) !== false);
}
)
);
if (empty($id)) {
debug("Blog URL \"$search\" has not been found");
} else {
// if multiple toots with the searched URL exist, take the oldest one (largest array index)
$id = $toots[end($id)]['id'];

/* read cached comments, or reload new comments if cached data too old */
$cachebreak = false;
read_db($dbc, $result, $ctc, $cachebreak, $id);

if ($cachebreak) {
debug("Comments cache for $id outdated. Checking for new comments");
// delete old cache file, otherwise the stats would add up
delete_db($dbc, $id);
// re-create empty $result and new cache file
$result = $result_empty;
read_db($dbc, $result, $ctc, $cachebreak, $id);
/* Extract comments and stats from toot */
tootContext($instance, $id, $result);
tootStats($instance, $id, $result);
// FIXME: At the moment the API doesn't return the correct replies count so I count it manually
$result['stats']['replies'] = count($result['comments']);
$result['stats']['root'] = $id;

write_db($dbc, $result, $id);
} else {
debug("Comments cache for $id up-to-date. Returning cached comments");
}
}

// headers for not caching the results
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');

// headers to tell that result is JSON
header('Content-type: application/json');

// actually output result as JSON, to be digested by getcomments.js
echo json_encode($result);

?>

+ 0
- 66
themes/hugo-mastodon-comments/static/comments/mastodon-comments.css View File

@@ -1,66 +0,0 @@
.comments-container {
text-align: left;
padding-bottom: 15px;
}

.comments-container .comment .avatar {
float: left;
width: 50px;
height: 50px;
margin-left: 16px;
margin-right: 16px;
border-radius: 50%;
padding-top: 0;
}

.comments-container #reference {
text-align: center;
font-size: 16px;
}

.comments-container .comment {
margin-top: 50px;
margin-bottom: 50px;
font-size: 16px;
padding-left: 20px;
}

.comments-container .toot {
padding-left: 82px;
}

.comments-container .author {
padding-top: 10px;
padding-bottom: 10px;
}

.comments-container #mastodon-like-count,
.comments-container #mastodon-reblog-count,
.comments-container #mastodon-reply-count
{
float: right;
font-size: 16px;
background: #eee;
padding: 3px 10px;
margin: 30px 5px;
border-radius: 5px;
}

.comments-container #mastodon-like-count a,
.comments-container #mastodon-reblog-count a,
.comments-container #mastodon-reply-count a
{
color: #333333;
}

.comments-container #mastodon-like-count a:hover,
.comments-container #mastodon-reblog-count a:hover,
.comments-container #mastodon-reply-count a:hover
{
color: black;
text-decoration: none;
}

.comments-container .fa {
padding-right: 10px;
}

+ 0
- 16
themes/hugo-mastodon-comments/theme.yaml View File

@@ -1,16 +0,0 @@
# theme.yaml configuration file

name: Mastodon Comments
license: AGPL-3.0-or-later
licenselink:
description: Hugo theme component for scraping comments on a Mastodon post containing a site's address
homepage:
tags:
- component
features:
- comments
min_version: 0.40.0

author:
name: Björn Schießle, Max Mehl
homepage:

Loading…
Cancel
Save