* * @license GNU AGPL version 3 or any later version * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ require_once 'Mastodon_api.php'; require_once '../../mastodon.feed/config.php'; class CollectMastodonData { /** @var \Mastodon_api */ private $api; /** @var string url of the mastodon instance */ private $mastodonUrl = 'https://mastodon.social'; /** @var string token to authenticate at the mastodon instance */ private $bearerToken; /** @var int keep cache at least 600 seconds = 10 minutes */ private $threshold = 600; /** @var string uid on the mastodon instance */ private $uid; /** @var array cached comments from previous searches */ private $commentCache = []; private $cacheFile = 'myCommentsCache.json'; public function __construct($config) { $this->mastodonUrl = $config['mastodon-instance']; $this->bearerToken = $config['token']; $this->uid = $config['user-id']; $this->api = new Mastodon_api(); $this->api->set_url($this->mastodonUrl); $this->api->set_token($this->bearerToken, 'bearer'); } private function filterComments($descendants, $root, &$result) { 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; } private function filterStats($stats) { $result = [ 'reblogs' => (int)$stats['reblogs_count'], 'favs' => (int)$stats['favourites_count'], 'replies' => (int)$stats['replies_count'], 'url' => $stats['url'] ]; return $result; } private function filterSearchResults($searchResult) { $result = []; if (isset($searchResult['html']['statuses'])) { foreach ($searchResult['html']['statuses'] as $status) { if ($status['in_reply_to_id'] === null) { $result[] = $status['id']; } } } sort($result); return $result; } /** * find all toots for a given blog post and return the corresponding IDs * * @param string $search * @return array */ public function findToots($search) { $result = $this->api->search(['q' => $search]); return $this->filterSearchResults($result); } public function getComments($id, &$result) { $raw = file_get_contents("https://mastodon.social/api/v1/statuses/$id/context"); $json = json_decode($raw, true); $this->filterComments($json['descendants'], $id, $result); } public function getStatistics($id, &$result) { $raw = file_get_contents("https://mastodon.social/api/v1/statuses/$id"); $json = json_decode($raw, true); $newStats = $this->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']; } } public function storeCollection($id, $comments) { $timestamp = time(); $comments['timestamp'] = $timestamp; $this->commentCache[$id] = $comments; file_put_contents($this->cacheFile, json_encode($this->commentCache)); } public function getCachedCollection($search) { if (file_exists($this->cacheFile)) { $cachedComments = file_get_contents($this->cacheFile); $cachedCommentsArray = json_decode($cachedComments, true); if (is_array($cachedCommentsArray)) { $this->commentCache = $cachedCommentsArray; $currentTimestamp = time(); if (isset($cachedCommentsArray[$search])) { if ((int)$cachedCommentsArray[$search]['timestamp'] + $this->threshold > $currentTimestamp) { unset($cachedCommentsArray[$search]['timestamp']); return $cachedCommentsArray[$search]; } } } } return []; } } $result = ['comments' => [], 'stats' => ['reblogs' => 0, 'favs' => 0, 'replies' => 0, 'url' => '', 'root' => 0]]; $search = isset($_GET['search']) ? $_GET['search'] : ''; $collector = new CollectMastodonData($config); $ids = []; if (!empty($search)) { $oldCollection = $collector->getCachedCollection($search); if (empty($oldCollection)) { $ids = $collector->findToots($search); $result['stats']['root'] = isset($ids[0]) ? $ids[0] : 0; foreach ($ids as $id) { // get comments $newComments = $collector->getComments($id, $result); // get statistics (likes, replies, boosts,...) $collector->getStatistics($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']); } $collector->storeCollection($search, $result); } else { $result = $oldCollection; } } // 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'); // send the result now echo json_encode($result);