From 0e2c2cd0e0fc8c2ed7a94ef7b4b22c98f562656d Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 27 Nov 2022 19:22:41 +0000 Subject: [PATCH 01/17] API: Support new tag endpoints --- .htaccess-dist | 2 +- src/Module/Api/Mastodon/Tags.php | 48 +++++++++++++++++++++++ src/Module/Api/Mastodon/Tags/Follow.php | 44 +++++++++++++++++++++ src/Module/Api/Mastodon/Tags/Unfollow.php | 44 +++++++++++++++++++++ static/routes.config.php | 6 ++- 5 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/Module/Api/Mastodon/Tags.php create mode 100644 src/Module/Api/Mastodon/Tags/Follow.php create mode 100644 src/Module/Api/Mastodon/Tags/Unfollow.php diff --git a/.htaccess-dist b/.htaccess-dist index 404137168a..c5c1b5b716 100644 --- a/.htaccess-dist +++ b/.htaccess-dist @@ -51,6 +51,6 @@ AddType audio/ogg .oga RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^(.*)$ index.php?pagename=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA] + RewriteRule ^(.*)$ index.php?pagename=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA,B] diff --git a/src/Module/Api/Mastodon/Tags.php b/src/Module/Api/Mastodon/Tags.php new file mode 100644 index 0000000000..97631b506a --- /dev/null +++ b/src/Module/Api/Mastodon/Tags.php @@ -0,0 +1,48 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\App\Router; +use Friendica\Core\Logger; +use Friendica\DI; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/tags/ + */ +class Tags extends BaseApi +{ + /** + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + protected function rawContent(array $request = []) + { + self::checkAllowedScope(self::SCOPE_READ); + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['hashtag'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $this->response->unsupported(Router::GET, $request); + } +} diff --git a/src/Module/Api/Mastodon/Tags/Follow.php b/src/Module/Api/Mastodon/Tags/Follow.php new file mode 100644 index 0000000000..9a813f7257 --- /dev/null +++ b/src/Module/Api/Mastodon/Tags/Follow.php @@ -0,0 +1,44 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Tags; + +use Friendica\App\Router; +use Friendica\DI; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/tags/#follow + */ +class Follow extends BaseApi +{ + protected function post(array $request = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['hashtag'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $this->response->unsupported(Router::POST, $request); + } +} diff --git a/src/Module/Api/Mastodon/Tags/Unfollow.php b/src/Module/Api/Mastodon/Tags/Unfollow.php new file mode 100644 index 0000000000..a20b824b8d --- /dev/null +++ b/src/Module/Api/Mastodon/Tags/Unfollow.php @@ -0,0 +1,44 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Tags; + +use Friendica\App\Router; +use Friendica\DI; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/tags/#unfollow + */ +class Unfollow extends BaseApi +{ + protected function post(array $request = []) + { + self::checkAllowedScope(self::SCOPE_WRITE); + $uid = self::getCurrentUserID(); + + if (empty($this->parameters['hashtag'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + $this->response->unsupported(Router::POST, $request); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index ae57557117..522e5ff168 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -235,10 +235,10 @@ return [ '/featured_tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST]], // not supported '/featured_tags/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not supported '/featured_tags/suggestions' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported - '/filters' => [Module\Api\Mastodon\Filters::class, [R::GET ]], // Dummy, not supported '/filters/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::PUT, R::DELETE]], // not supported '/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]], '/follow_requests/{id:\d+}/{action}' => [Module\Api\Mastodon\FollowRequests::class, [ R::POST]], + '/followed_tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/instance' => [Module\Api\Mastodon\Instance::class, [R::GET ]], '/instance/activity' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/instance/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET ]], @@ -289,6 +289,9 @@ return [ '/streaming/user' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/streaming/user/notification' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/suggestions/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented + '/tags/{hashtag}' => [Module\Api\Mastodon\Tags::class, [R::GET ]], + '/tags/{hashtag}/follow' => [Module\Api\Mastodon\Tags\Follow::class, [ R::POST]], + '/tags/{hashtag}/unfollow' => [Module\Api\Mastodon\Tags\Unfollow::class, [ R::POST]], '/timelines/direct' => [Module\Api\Mastodon\Timelines\Direct::class, [R::GET ]], '/timelines/home' => [Module\Api\Mastodon\Timelines\Home::class, [R::GET ]], '/timelines/list/{id:\d+}' => [Module\Api\Mastodon\Timelines\ListTimeline::class, [R::GET ]], @@ -301,6 +304,7 @@ return [ ], '/v{version:\d+}' => [ '/admin/accounts' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/filters' => [Module\Api\Mastodon\Filters::class, [R::GET ]], // Dummy, not supported '/media' => [Module\Api\Mastodon\Media::class, [ R::POST]], '/search' => [Module\Api\Mastodon\Search::class, [R::GET ]], '/suggestions' => [Module\Api\Mastodon\Suggestions::class, [R::GET ]], From 85f57e69faaca3645c596afd12e4b024194ae349 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 03:29:43 +0000 Subject: [PATCH 02/17] Issue 12191: We can now follow and unfollow tags via API --- src/Content/Widget/SavedSearches.php | 2 +- src/Module/Api/Mastodon/FollowedTags.php | 83 +++++++++++++++++++++++ src/Module/Api/Mastodon/Tags.php | 11 ++- src/Module/Api/Mastodon/Tags/Follow.php | 11 ++- src/Module/Api/Mastodon/Tags/Unfollow.php | 10 ++- src/Object/Api/Mastodon/Tag.php | 15 ++-- static/routes.config.php | 2 +- 7 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 src/Module/Api/Mastodon/FollowedTags.php diff --git a/src/Content/Widget/SavedSearches.php b/src/Content/Widget/SavedSearches.php index 6b6202ba32..1bf9e76a26 100644 --- a/src/Content/Widget/SavedSearches.php +++ b/src/Content/Widget/SavedSearches.php @@ -37,7 +37,7 @@ class SavedSearches public static function getHTML(string $return_url, string $search = ''): string { $saved = []; - $saved_searches = DBA::select('search', ['id', 'term'], ['uid' => DI::userSession()->getLocalUserId()]); + $saved_searches = DBA::select('search', ['id', 'term'], ['uid' => DI::userSession()->getLocalUserId()], ['order' => ['term']]); while ($saved_search = DBA::fetch($saved_searches)) { $saved[] = [ 'id' => $saved_search['id'], diff --git a/src/Module/Api/Mastodon/FollowedTags.php b/src/Module/Api/Mastodon/FollowedTags.php new file mode 100644 index 0000000000..6e95783c9f --- /dev/null +++ b/src/Module/Api/Mastodon/FollowedTags.php @@ -0,0 +1,83 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/followed_tags/ + */ +class FollowedTags extends BaseApi +{ + protected function rawContent(array $request = []) + { + self::checkAllowedScope(self::SCOPE_READ); + $uid = self::getCurrentUserID(); + + $request = $this->getRequest([ + 'max_id' => 0, + 'since_id' => 0, + 'min_id' => 0, + 'limit' => 100, // Maximum number of results to return. Defaults to 100. Paginate using the HTTP Link header. + ], $request); + + $params = ['order' => ['id' => true], 'limit' => $request['limit']]; + + $condition = ["`uid` = ? AND `term` LIKE ?", $uid, '#%']; + + if (!empty($request['max_id'])) { + $condition = DBA::mergeConditions($condition, ["`id` < ?", $request['max_id']]); + } + + if (!empty($request['since_id'])) { + $condition = DBA::mergeConditions($condition, ["`id` > ?", $request['since_id']]); + } + + if (!empty($request['min_id'])) { + $condition = DBA::mergeConditions($condition, ["`id` > ?", $request['min_id']]); + + $params['order'] = ['id']; + } + + $return = []; + + $saved_searches = DBA::select('search', ['id', 'term'], $condition); + while ($saved_search = DBA::fetch($saved_searches)) { + self::setBoundaries($saved_search['id']); + $tag = ['name' => substr($saved_search['term'], 1)]; + + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); + $return[] = $hashtag->toArray(); + } + + DBA::close($saved_searches); + + if (!empty($request['min_id'])) { + $return = array_reverse($return); + } + + self::setLinkHeader(); + System::jsonExit($return); + } +} diff --git a/src/Module/Api/Mastodon/Tags.php b/src/Module/Api/Mastodon/Tags.php index 97631b506a..442f0f6576 100644 --- a/src/Module/Api/Mastodon/Tags.php +++ b/src/Module/Api/Mastodon/Tags.php @@ -21,8 +21,8 @@ namespace Friendica\Module\Api\Mastodon; -use Friendica\App\Router; -use Friendica\Core\Logger; +use Friendica\Core\System; +use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\BaseApi; @@ -43,6 +43,11 @@ class Tags extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - $this->response->unsupported(Router::GET, $request); + $tag = ltrim($this->parameters['hashtag'], '#'); + $following = DBA::exists('search', ['uid' => $uid, 'term' => '#' . $tag]); + $term = ['term' => $tag]; + + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], $following); + System::jsonExit($hashtag->toArray()); } } diff --git a/src/Module/Api/Mastodon/Tags/Follow.php b/src/Module/Api/Mastodon/Tags/Follow.php index 9a813f7257..e2261f3fd5 100644 --- a/src/Module/Api/Mastodon/Tags/Follow.php +++ b/src/Module/Api/Mastodon/Tags/Follow.php @@ -21,7 +21,8 @@ namespace Friendica\Module\Api\Mastodon\Tags; -use Friendica\App\Router; +use Friendica\Core\System; +use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\BaseApi; @@ -39,6 +40,12 @@ class Follow extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - $this->response->unsupported(Router::POST, $request); + $fields = ['uid' => $uid, 'term' => '#' . $this->parameters['hashtag']]; + if (!DBA::exists('search', $fields)) { + DBA::insert('search', $fields); + } + + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $fields, [], true); + System::jsonExit($hashtag->toArray()); } } diff --git a/src/Module/Api/Mastodon/Tags/Unfollow.php b/src/Module/Api/Mastodon/Tags/Unfollow.php index a20b824b8d..465c1974b5 100644 --- a/src/Module/Api/Mastodon/Tags/Unfollow.php +++ b/src/Module/Api/Mastodon/Tags/Unfollow.php @@ -21,7 +21,8 @@ namespace Friendica\Module\Api\Mastodon\Tags; -use Friendica\App\Router; +use Friendica\Core\System; +use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\BaseApi; @@ -39,6 +40,11 @@ class Unfollow extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - $this->response->unsupported(Router::POST, $request); + $term = ['uid' => $uid, 'term' => '#' . $this->parameters['hashtag']]; + + DBA::delete('search', $term); + + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], false); + System::jsonExit($hashtag->toArray()); } } diff --git a/src/Object/Api/Mastodon/Tag.php b/src/Object/Api/Mastodon/Tag.php index 340e30f703..e40b793e26 100644 --- a/src/Object/Api/Mastodon/Tag.php +++ b/src/Object/Api/Mastodon/Tag.php @@ -37,18 +37,23 @@ class Tag extends BaseDataTransferObject protected $url = null; /** @var array */ protected $history = []; + /** @var bool */ + protected $following = false; /** * Creates a hashtag record from an tag-view record. * * @param BaseURL $baseUrl - * @param array $tag tag-view record + * @param array $tag tag-view record + * @param array $history + * @param array $following "true" if the user is following this tag * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(BaseURL $baseUrl, array $tag, array $history = []) + public function __construct(BaseURL $baseUrl, array $tag, array $history = [], bool $following = false) { - $this->name = strtolower($tag['name']); - $this->url = $baseUrl . '/search?tag=' . urlencode($this->name); - $this->history = $history; + $this->name = strtolower($tag['name']); + $this->url = $baseUrl . '/search?tag=' . urlencode($this->name); + $this->history = $history; + $this->following = $following; } } diff --git a/static/routes.config.php b/static/routes.config.php index 07c2b45508..42333e65fa 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -236,7 +236,7 @@ return [ '/filters/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::PUT, R::DELETE]], // not supported '/follow_requests' => [Module\Api\Mastodon\FollowRequests::class, [R::GET ]], '/follow_requests/{id:\d+}/{action}' => [Module\Api\Mastodon\FollowRequests::class, [ R::POST]], - '/followed_tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo + '/followed_tags' => [Module\Api\Mastodon\FollowedTags::class, [R::GET ]], '/instance' => [Module\Api\Mastodon\Instance::class, [R::GET ]], '/instance/activity' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/instance/peers' => [Module\Api\Mastodon\Instance\Peers::class, [R::GET ]], From 4e6b6fd76b7b76fe577e3dfe19daf38a5a32aa90 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 28 Nov 2022 05:00:08 +0100 Subject: [PATCH 03/17] Update src/Module/Api/Mastodon/FollowedTags.php Co-authored-by: Hypolite Petovan --- src/Module/Api/Mastodon/FollowedTags.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/FollowedTags.php b/src/Module/Api/Mastodon/FollowedTags.php index 6e95783c9f..eb5e32fdc1 100644 --- a/src/Module/Api/Mastodon/FollowedTags.php +++ b/src/Module/Api/Mastodon/FollowedTags.php @@ -65,7 +65,7 @@ class FollowedTags extends BaseApi $saved_searches = DBA::select('search', ['id', 'term'], $condition); while ($saved_search = DBA::fetch($saved_searches)) { self::setBoundaries($saved_search['id']); - $tag = ['name' => substr($saved_search['term'], 1)]; + $tag = ['name' => ltrim($saved_search['term'], '#')]; $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); $return[] = $hashtag->toArray(); From 934766c36406b1797741ae67b9b3840598f2cccb Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 04:04:27 +0000 Subject: [PATCH 04/17] Trim the tag --- src/Module/Api/Mastodon/Tags/Follow.php | 2 +- src/Module/Api/Mastodon/Tags/Unfollow.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Module/Api/Mastodon/Tags/Follow.php b/src/Module/Api/Mastodon/Tags/Follow.php index e2261f3fd5..767245b60f 100644 --- a/src/Module/Api/Mastodon/Tags/Follow.php +++ b/src/Module/Api/Mastodon/Tags/Follow.php @@ -40,7 +40,7 @@ class Follow extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - $fields = ['uid' => $uid, 'term' => '#' . $this->parameters['hashtag']]; + $fields = ['uid' => $uid, 'term' => '#' . ltrim($this->parameters['hashtag'], '#')]; if (!DBA::exists('search', $fields)) { DBA::insert('search', $fields); } diff --git a/src/Module/Api/Mastodon/Tags/Unfollow.php b/src/Module/Api/Mastodon/Tags/Unfollow.php index 465c1974b5..163b65003b 100644 --- a/src/Module/Api/Mastodon/Tags/Unfollow.php +++ b/src/Module/Api/Mastodon/Tags/Unfollow.php @@ -40,7 +40,7 @@ class Unfollow extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - $term = ['uid' => $uid, 'term' => '#' . $this->parameters['hashtag']]; + $term = ['uid' => $uid, 'term' => '#' . ltrim($this->parameters['hashtag'], '#')]; DBA::delete('search', $term); From f7167acc74db0ac31a9ccc83da6087424e261e18 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 04:07:25 +0000 Subject: [PATCH 05/17] Standards --- src/Module/Api/Mastodon/FollowedTags.php | 2 +- src/Module/Api/Mastodon/Tags.php | 2 +- src/Module/Api/Mastodon/Tags/Follow.php | 2 +- src/Module/Api/Mastodon/Tags/Unfollow.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Module/Api/Mastodon/FollowedTags.php b/src/Module/Api/Mastodon/FollowedTags.php index eb5e32fdc1..bfa7db7935 100644 --- a/src/Module/Api/Mastodon/FollowedTags.php +++ b/src/Module/Api/Mastodon/FollowedTags.php @@ -67,7 +67,7 @@ class FollowedTags extends BaseApi self::setBoundaries($saved_search['id']); $tag = ['name' => ltrim($saved_search['term'], '#')]; - $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); $return[] = $hashtag->toArray(); } diff --git a/src/Module/Api/Mastodon/Tags.php b/src/Module/Api/Mastodon/Tags.php index 442f0f6576..d38899ac0f 100644 --- a/src/Module/Api/Mastodon/Tags.php +++ b/src/Module/Api/Mastodon/Tags.php @@ -47,7 +47,7 @@ class Tags extends BaseApi $following = DBA::exists('search', ['uid' => $uid, 'term' => '#' . $tag]); $term = ['term' => $tag]; - $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], $following); + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], $following); System::jsonExit($hashtag->toArray()); } } diff --git a/src/Module/Api/Mastodon/Tags/Follow.php b/src/Module/Api/Mastodon/Tags/Follow.php index 767245b60f..22f8fa3f27 100644 --- a/src/Module/Api/Mastodon/Tags/Follow.php +++ b/src/Module/Api/Mastodon/Tags/Follow.php @@ -45,7 +45,7 @@ class Follow extends BaseApi DBA::insert('search', $fields); } - $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $fields, [], true); + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $fields, [], true); System::jsonExit($hashtag->toArray()); } } diff --git a/src/Module/Api/Mastodon/Tags/Unfollow.php b/src/Module/Api/Mastodon/Tags/Unfollow.php index 163b65003b..f3fbad2e5a 100644 --- a/src/Module/Api/Mastodon/Tags/Unfollow.php +++ b/src/Module/Api/Mastodon/Tags/Unfollow.php @@ -44,7 +44,7 @@ class Unfollow extends BaseApi DBA::delete('search', $term); - $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], false); + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $term, [], false); System::jsonExit($hashtag->toArray()); } } From a7639a2341bcd181d8730b220cab0f9ec18f965e Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 06:46:18 +0000 Subject: [PATCH 06/17] Updated documentation --- doc/API-Mastodon.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 63c487caf3..6c6a1691a2 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -73,7 +73,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - `:id` is a follow request ID, not a regular account id - Returns a [Relationship](https://docs.joinmastodon.org/entities/relationship) object. - +- [`GET /api/v1/followed_tags'](https://docs.joinmastodon.org/methods/followed_tags/) - [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance) - `GET /api/v1/instance/rules` Undocumented, returns Terms of Service - [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains) @@ -126,6 +126,9 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`GET /api/v1/statuses/:id/source`](https://docs.joinmastodon.org/methods/statuses/#source) - [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/#card) - [`GET /api/v1/suggestions`](https://docs.joinmastodon.org/methods/accounts/suggestions/) +- [`GET /api/v1/tags/:id'](https://docs.joinmastodon.org/methods/tags/#get) +- [`GET /api/v1/tags/:id/follow'](https://docs.joinmastodon.org/methods/tags/#follow) +- [`GET /api/v1/tags/:id/unfollow'](https://docs.joinmastodon.org/methods/tags/#unfollow) - [`GET /api/v1/timelines/direct`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/home`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/list/:id`](https://docs.joinmastodon.org/methods/timelines/) From 73ba7d800540f6106f1f2bb344e3f33a3bf12714 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 06:52:55 +0000 Subject: [PATCH 07/17] Coding styles --- src/Module/Api/Mastodon/FollowedTags.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/FollowedTags.php b/src/Module/Api/Mastodon/FollowedTags.php index bfa7db7935..eb5e32fdc1 100644 --- a/src/Module/Api/Mastodon/FollowedTags.php +++ b/src/Module/Api/Mastodon/FollowedTags.php @@ -67,7 +67,7 @@ class FollowedTags extends BaseApi self::setBoundaries($saved_search['id']); $tag = ['name' => ltrim($saved_search['term'], '#')]; - $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); + $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag, [], true); $return[] = $hashtag->toArray(); } From 6b36bd893bd8b1b44d578f19acbaa4d4bb670c36 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 07:08:01 +0000 Subject: [PATCH 08/17] API: Trending tags are added --- doc/API-Mastodon.md | 1 + static/routes.config.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 6c6a1691a2..d77218f670 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -135,6 +135,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/tag/:hashtag`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/) +- [`GET /api/v1/trends/tags`](https://docs.joinmastodon.org/methods/trends/#tags) - [`GET /api/v2/search`](https://docs.joinmastodon.org/methods/search/) diff --git a/static/routes.config.php b/static/routes.config.php index 42333e65fa..9b2d3d9365 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -298,7 +298,7 @@ return [ '/trends' => [Module\Api\Mastodon\Trends::class, [R::GET ]], '/trends/links' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/trends/statuses' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/trends/tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented + '/trends/tags' => [Module\Api\Mastodon\Trends::class, [R::GET ]], ], '/v{version:\d+}' => [ '/admin/accounts' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported From c00004f6ea05646310904c97c1289cc1c5ac1d2d Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 09:30:41 +0000 Subject: [PATCH 09/17] API: Tredning statuses added --- database.sql | 3 +- doc/API-Mastodon.md | 9 ++- src/Model/Post.php | 15 +++++ src/Module/Api/Mastodon/Trends/Statuses.php | 57 +++++++++++++++++++ .../Mastodon/{Trends.php => Trends/Tags.php} | 4 +- static/dbstructure.config.php | 2 +- static/dbview.config.php | 1 + static/routes.config.php | 6 +- 8 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 src/Module/Api/Mastodon/Trends/Statuses.php rename src/Module/Api/Mastodon/{Trends.php => Trends/Tags.php} (95%) diff --git a/database.sql b/database.sql index 8fa20be52c..cccb233d37 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.12-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1491 +-- DB_UPDATE_VERSION 1492 -- ------------------------------------------ @@ -2437,6 +2437,7 @@ CREATE VIEW `post-thread-view` AS SELECT `post-question`.`end-time` AS `question-end-time`, 0 AS `has-categories`, EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`) AS `has-media`, + (SELECT COUNT(*) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6) AS `total-comments`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index d77218f670..2850f0ebf0 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -126,15 +126,16 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`GET /api/v1/statuses/:id/source`](https://docs.joinmastodon.org/methods/statuses/#source) - [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/#card) - [`GET /api/v1/suggestions`](https://docs.joinmastodon.org/methods/accounts/suggestions/) -- [`GET /api/v1/tags/:id'](https://docs.joinmastodon.org/methods/tags/#get) -- [`GET /api/v1/tags/:id/follow'](https://docs.joinmastodon.org/methods/tags/#follow) -- [`GET /api/v1/tags/:id/unfollow'](https://docs.joinmastodon.org/methods/tags/#unfollow) +- [`GET /api/v1/tags/:id`](https://docs.joinmastodon.org/methods/tags/#get) +- [`GET /api/v1/tags/:id/follow`](https://docs.joinmastodon.org/methods/tags/#follow) +- [`GET /api/v1/tags/:id/unfollow`](https://docs.joinmastodon.org/methods/tags/#unfollow) - [`GET /api/v1/timelines/direct`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/home`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/list/:id`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/tag/:hashtag`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/) +- [`GET /api/v1/trends/statuses`](https://docs.joinmastodon.org/methods/trends/#statuses) - [`GET /api/v1/trends/tags`](https://docs.joinmastodon.org/methods/trends/#tags) - [`GET /api/v2/search`](https://docs.joinmastodon.org/methods/search/) @@ -147,8 +148,6 @@ These emdpoints are planned to be implemented somewhere in the future. - [`GET /api/v1/accounts/familiar_followers`](https://github.com/mastodon/mastodon/pull/17700) - [`GET /api/v1/accounts/lookup`](https://github.com/mastodon/mastodon/pull/15740) - [`GET /api/v1/trends/links`](https://github.com/mastodon/mastodon/pull/16917) -- [`GET /api/v1/trends/statuses`](https://github.com/mastodon/mastodon/pull/17431) -- [`GET /api/v1/trends/tags`](https://github.com/mastodon/mastodon/pull/16917) - [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/) - [`GET /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/) - [`POST /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/) diff --git a/src/Model/Post.php b/src/Model/Post.php index 11e5c2c985..2baa76b5be 100644 --- a/src/Model/Post.php +++ b/src/Model/Post.php @@ -374,6 +374,21 @@ class Post return self::selectView('post-thread-user-view', $selected, $condition, $params); } + /** + * Select rows from the post-thread-view view + * + * @param array $selected Array of selected fields, empty for all + * @param array $condition Array of fields for condition + * @param array $params Array of several parameters + * + * @return boolean|object + * @throws \Exception + */ + public static function selectPostThread(array $selected = [], array $condition = [], array $params = []) + { + return self::selectView('post-thread-view', $selected, $condition, $params); + } + /** * Select rows from the given view for a given user * diff --git a/src/Module/Api/Mastodon/Trends/Statuses.php b/src/Module/Api/Mastodon/Trends/Statuses.php new file mode 100644 index 0000000000..70b23f5cc1 --- /dev/null +++ b/src/Module/Api/Mastodon/Trends/Statuses.php @@ -0,0 +1,57 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Trends; + +use Friendica\Core\Protocol; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Post; +use Friendica\Module\BaseApi; +use Friendica\Util\DateTimeFormat; + +/** + * @see https://docs.joinmastodon.org/methods/trends/#statuses + */ +class Statuses extends BaseApi +{ + /** + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + protected function rawContent(array $request = []) + { + $request = $this->getRequest([ + 'limit' => 10, // Maximum number of results to return. Defaults to 10. + ], $request); + + $trending = []; + $condition = ["NOT `private` AND `commented` > ?", DateTimeFormat::utc('now -1 day')]; + $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); + $statuses = Post::selectPostThread(['uri-id'], $condition, ['limit' => $request['limit'], 'order' => ['total-comments' => true]]); + while ($status = Post::fetch($statuses)) { + $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id']); + } + DBA::close($statuses); + + System::jsonExit($trending); + } +} diff --git a/src/Module/Api/Mastodon/Trends.php b/src/Module/Api/Mastodon/Trends/Tags.php similarity index 95% rename from src/Module/Api/Mastodon/Trends.php rename to src/Module/Api/Mastodon/Trends/Tags.php index c42a27641b..2014b7a384 100644 --- a/src/Module/Api/Mastodon/Trends.php +++ b/src/Module/Api/Mastodon/Trends/Tags.php @@ -19,7 +19,7 @@ * */ -namespace Friendica\Module\Api\Mastodon; +namespace Friendica\Module\Api\Mastodon\Trends; use Friendica\Core\System; use Friendica\DI; @@ -29,7 +29,7 @@ use Friendica\Module\BaseApi; /** * @see https://docs.joinmastodon.org/methods/instance/trends/ */ -class Trends extends BaseApi +class Tags extends BaseApi { /** * @throws \Friendica\Network\HTTPException\InternalServerErrorException diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 942f2237d5..2f613f9749 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1491); + define('DB_UPDATE_VERSION', 1492); } return [ diff --git a/static/dbview.config.php b/static/dbview.config.php index 2452dc341b..6c4f00a986 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -664,6 +664,7 @@ "question-end-time" => ["post-question", "end-time"], "has-categories" => "0", "has-media" => "EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`)", + "total-comments" => "(SELECT COUNT(*) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6)", "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], diff --git a/static/routes.config.php b/static/routes.config.php index 9b2d3d9365..81a9d2e2fd 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -295,10 +295,10 @@ return [ '/timelines/list/{id:\d+}' => [Module\Api\Mastodon\Timelines\ListTimeline::class, [R::GET ]], '/timelines/public' => [Module\Api\Mastodon\Timelines\PublicTimeline::class, [R::GET ]], '/timelines/tag/{hashtag}' => [Module\Api\Mastodon\Timelines\Tag::class, [R::GET ]], - '/trends' => [Module\Api\Mastodon\Trends::class, [R::GET ]], + '/trends' => [Module\Api\Mastodon\Trends\Tags::class, [R::GET ]], '/trends/links' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/trends/statuses' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/trends/tags' => [Module\Api\Mastodon\Trends::class, [R::GET ]], + '/trends/statuses' => [Module\Api\Mastodon\Trends\Statuses::class, [R::GET ]], + '/trends/tags' => [Module\Api\Mastodon\Trends\Tags::class, [R::GET ]], ], '/v{version:\d+}' => [ '/admin/accounts' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported From 0e160040d49fdd82935a2d48530cdec552212b81 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 10:54:26 +0000 Subject: [PATCH 10/17] Code standards --- src/Module/Api/Mastodon/Trends/Statuses.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/Trends/Statuses.php b/src/Module/Api/Mastodon/Trends/Statuses.php index 70b23f5cc1..cb69be5ed7 100644 --- a/src/Module/Api/Mastodon/Trends/Statuses.php +++ b/src/Module/Api/Mastodon/Trends/Statuses.php @@ -43,9 +43,10 @@ class Statuses extends BaseApi 'limit' => 10, // Maximum number of results to return. Defaults to 10. ], $request); - $trending = []; $condition = ["NOT `private` AND `commented` > ?", DateTimeFormat::utc('now -1 day')]; $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); + + $trending = []; $statuses = Post::selectPostThread(['uri-id'], $condition, ['limit' => $request['limit'], 'order' => ['total-comments' => true]]); while ($status = Post::fetch($statuses)) { $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id']); From bded1c3989854bbf8a23fd39592cd42161813165 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 14:51:37 +0000 Subject: [PATCH 11/17] Only use items that had originally been posted in the last week --- src/Module/Api/Mastodon/Trends/Statuses.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/Trends/Statuses.php b/src/Module/Api/Mastodon/Trends/Statuses.php index cb69be5ed7..8974403d77 100644 --- a/src/Module/Api/Mastodon/Trends/Statuses.php +++ b/src/Module/Api/Mastodon/Trends/Statuses.php @@ -43,7 +43,7 @@ class Statuses extends BaseApi 'limit' => 10, // Maximum number of results to return. Defaults to 10. ], $request); - $condition = ["NOT `private` AND `commented` > ?", DateTimeFormat::utc('now -1 day')]; + $condition = ["NOT `private` AND `commented` > ? AND `created` > ?", DateTimeFormat::utc('now -1 day'), DateTimeFormat::utc('now -1 week')]; $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); $trending = []; From de25d3a7eebd0c14c1b6449a45fd47d0ac93fbd6 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 28 Nov 2022 20:13:38 +0100 Subject: [PATCH 12/17] Fix Legacy Router class routing --- src/App/Router.php | 76 ++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/src/App/Router.php b/src/App/Router.php index 35ea9ada8b..5b915c0e6a 100644 --- a/src/App/Router.php +++ b/src/App/Router.php @@ -80,7 +80,7 @@ class Router /** * @var array Module parameters */ - private $parameters = []; + protected $parameters = []; /** @var L10n */ private $l10n; @@ -268,7 +268,6 @@ class Router * * @throws InternalServerErrorException * @throws MethodNotAllowedException - * @throws NotFoundException */ public function getModuleClass(): string { @@ -284,9 +283,8 @@ class Router * * @return void * - * @throws HTTPException\InternalServerErrorException - * @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't - * @throws HTTPException\NotFoundException If no rule matched + * @throws HTTPException\InternalServerErrorException Unexpected exceptions + * @throws HTTPException\MethodNotAllowedException If a rule is private only */ private function determineModuleClass(): void { @@ -295,39 +293,26 @@ class Router $dispatcher = new FriendicaGroupCountBased($this->getCachedDispatchData()); - $this->parameters = []; + $this->parameters = [$this->server]; - // Check if the HTTP method is OPTIONS and return the special Options Module with the possible HTTP methods - if ($this->args->getMethod() === static::OPTIONS) { - $this->moduleClass = Options::class; - $this->parameters = ['allowedMethods' => $dispatcher->getOptions($cmd)]; - } else { - $routeInfo = $dispatcher->dispatch($this->args->getMethod(), $cmd); - if ($routeInfo[0] === Dispatcher::FOUND) { - $this->moduleClass = $routeInfo[1]; - $this->parameters = $routeInfo[2]; - } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { - throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1]))); - } else { - throw new HTTPException\NotFoundException($this->l10n->t('Page not found.')); - } - } - } - - public function getModule(?string $module_class = null): ICanHandleRequests - { - $module_parameters = [$this->server]; - /** - * ROUTING - * - * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the - * post() and/or content() static methods can be respectively called to produce a data change or an output. - **/ try { - $module_class = $module_class ?? $this->getModuleClass(); - $module_parameters[] = $this->parameters; + // Check if the HTTP method is OPTIONS and return the special Options Module with the possible HTTP methods + if ($this->args->getMethod() === static::OPTIONS) { + $this->moduleClass = Options::class; + $this->parameters = ['allowedMethods' => $dispatcher->getOptions($cmd)]; + } else { + $routeInfo = $dispatcher->dispatch($this->args->getMethod(), $cmd); + if ($routeInfo[0] === Dispatcher::FOUND) { + $this->moduleClass = $routeInfo[1]; + $this->parameters[] = $routeInfo[2]; + } else if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1]))); + } else { + throw new HTTPException\NotFoundException($this->l10n->t('Page not found.')); + } + } } catch (MethodNotAllowedException $e) { - $module_class = MethodNotAllowed::class; + $this->moduleClass = MethodNotAllowed::class; } catch (NotFoundException $e) { $moduleName = $this->args->getModuleName(); // Then we try addon-provided modules that we wrap in the LegacyModule class @@ -339,8 +324,8 @@ class Router } else { include_once "addon/{$moduleName}/{$moduleName}.php"; if (function_exists($moduleName . '_module')) { - $module_parameters[] = "addon/{$moduleName}/{$moduleName}.php"; - $module_class = LegacyModule::class; + $this->parameters[] = "addon/{$moduleName}/{$moduleName}.php"; + $this->moduleClass = LegacyModule::class; } } } @@ -348,24 +333,29 @@ class Router /* Finally, we look for a 'standard' program module in the 'mod' directory * We emulate a Module class through the LegacyModule class */ - if (!$module_class && file_exists("mod/{$moduleName}.php")) { - $module_parameters[] = "mod/{$moduleName}.php"; - $module_class = LegacyModule::class; + if (!$this->moduleClass && file_exists("mod/{$moduleName}.php")) { + $this->parameters[] = "mod/{$moduleName}.php"; + $this->moduleClass = LegacyModule::class; } - $module_class = $module_class ?: PageNotFound::class; + $this->moduleClass = $this->moduleClass ?: PageNotFound::class; } + } + + public function getModule(?string $module_class = null): ICanHandleRequests + { + $moduleClass = $module_class ?? $this->getModuleClass(); $stamp = microtime(true); try { /** @var ICanHandleRequests $module */ - return $this->dice->create($module_class, $module_parameters); + return $this->dice->create($moduleClass, $this->parameters); } finally { if ($this->dice_profiler_threshold > 0) { $dur = floatval(microtime(true) - $stamp); if ($dur >= $this->dice_profiler_threshold) { - $this->logger->notice('Dice module creation lasts too long.', ['duration' => round($dur, 3), 'module' => $module_class, 'parameters' => $module_parameters]); + $this->logger->notice('Dice module creation lasts too long.', ['duration' => round($dur, 3), 'module' => $moduleClass, 'parameters' => $this->parameters]); } } } From e391328cbf7edf05b6d40fd15c538f2f9260346a Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 19:31:04 +0000 Subject: [PATCH 13/17] Added changelog entry --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index b2e549610f..b9a1dd966b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,6 @@ Version 2022.12 (unreleased) Friendica Core + The rewrite rule in .htaccess-dist has been changed. The change has to be applied manually to the existing .htaccess Friendica Addons BREAKING: The functions from the boot.php file have been moved into better fitting classes From de76e860ad024a7cc0b5f3366cdabefd4915c3e9 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 20:19:57 +0000 Subject: [PATCH 14/17] API: Added trending links --- database.sql | 1 + doc/API-Mastodon.md | 2 +- src/Factory/Api/Mastodon/Card.php | 7 +-- src/Module/Api/Mastodon/Trends/Links.php | 60 +++++++++++++++++++++ src/Module/Api/Mastodon/Trends/Statuses.php | 6 ++- src/Object/Api/Mastodon/Card.php | 3 +- static/dbview.config.php | 1 + static/routes.config.php | 2 +- 8 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 src/Module/Api/Mastodon/Trends/Links.php diff --git a/database.sql b/database.sql index cccb233d37..c68ae094c1 100644 --- a/database.sql +++ b/database.sql @@ -2438,6 +2438,7 @@ CREATE VIEW `post-thread-view` AS SELECT 0 AS `has-categories`, EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`) AS `has-media`, (SELECT COUNT(*) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6) AS `total-comments`, + (SELECT COUNT(DISTINCT(`author-id`)) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6) AS `total-actors`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, `parent-post`.`network` AS `parent-network`, diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 2850f0ebf0..0e2f574303 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -135,6 +135,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en - [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/timelines/tag/:hashtag`](https://docs.joinmastodon.org/methods/timelines/) - [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/) +- [`GET /api/v1/trends/links`](https://github.com/mastodon/mastodon/pull/16917) - [`GET /api/v1/trends/statuses`](https://docs.joinmastodon.org/methods/trends/#statuses) - [`GET /api/v1/trends/tags`](https://docs.joinmastodon.org/methods/trends/#tags) - [`GET /api/v2/search`](https://docs.joinmastodon.org/methods/search/) @@ -147,7 +148,6 @@ These emdpoints are planned to be implemented somewhere in the future. - [`POST /api/v1/accounts/:id/remove_from_followers`](https://github.com/mastodon/mastodon/pull/16864) - [`GET /api/v1/accounts/familiar_followers`](https://github.com/mastodon/mastodon/pull/17700) - [`GET /api/v1/accounts/lookup`](https://github.com/mastodon/mastodon/pull/15740) -- [`GET /api/v1/trends/links`](https://github.com/mastodon/mastodon/pull/16917) - [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/) - [`GET /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/) - [`POST /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/) diff --git a/src/Factory/Api/Mastodon/Card.php b/src/Factory/Api/Mastodon/Card.php index 659ab3a3de..ac50841847 100644 --- a/src/Factory/Api/Mastodon/Card.php +++ b/src/Factory/Api/Mastodon/Card.php @@ -30,13 +30,14 @@ use Friendica\Util\Strings; class Card extends BaseFactory { /** - * @param int $uriId Uri-ID of the item + * @param int $uriId Uri-ID of the item + * @param array $history Link request history * * @return \Friendica\Object\Api\Mastodon\Card * @throws HTTPException\InternalServerErrorException * @throws \ImagickException*@throws \Exception */ - public function createFromUriId(int $uriId): \Friendica\Object\Api\Mastodon\Card + public function createFromUriId(int $uriId, array $history = []): \Friendica\Object\Api\Mastodon\Card { $item = Post::selectFirst(['body'], ['uri-id' => $uriId]); if (!empty($item['body'])) { @@ -76,6 +77,6 @@ class Card extends BaseFactory } } - return new \Friendica\Object\Api\Mastodon\Card($data); + return new \Friendica\Object\Api\Mastodon\Card($data, $history); } } diff --git a/src/Module/Api/Mastodon/Trends/Links.php b/src/Module/Api/Mastodon/Trends/Links.php new file mode 100644 index 0000000000..ee535c7f3c --- /dev/null +++ b/src/Module/Api/Mastodon/Trends/Links.php @@ -0,0 +1,60 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Trends; + +use Friendica\Core\Protocol; +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\DI; +use Friendica\Model\Post; +use Friendica\Module\BaseApi; +use Friendica\Util\DateTimeFormat; + +/** + * @see https://docs.joinmastodon.org/methods/trends/#links + */ +class Links extends BaseApi +{ + /** + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + protected function rawContent(array $request = []) + { + $request = $this->getRequest([ + 'limit' => 10, // Maximum number of results to return. Defaults to 10. + ], $request); + + $condition = ["EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread-view`.`uri-id` AND `type` = ? AND NOT `name` IS NULL AND NOT `description` IS NULL) AND NOT `private` AND `commented` > ? AND `created` > ?", + Post\Media::HTML, DateTimeFormat::utc('now -1 day'), DateTimeFormat::utc('now -1 week')]; + $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); + + $trending = []; + $statuses = Post::selectPostThread(['uri-id', 'total-comments', 'total-actors'], $condition, ['limit' => $request['limit'], 'order' => ['total-actors' => true]]); + while ($status = Post::fetch($statuses)) { + $history = [['day' => (string)time(), 'uses' => (string)$status['total-comments'], 'accounts' => (string)$status['total-actors']]]; + $trending[] = DI::mstdnCard()->createFromUriId($status['uri-id'], $history)->toArray(); + } + DBA::close($statuses); + + System::jsonExit($trending); + } +} diff --git a/src/Module/Api/Mastodon/Trends/Statuses.php b/src/Module/Api/Mastodon/Trends/Statuses.php index 8974403d77..26eb52d7ae 100644 --- a/src/Module/Api/Mastodon/Trends/Statuses.php +++ b/src/Module/Api/Mastodon/Trends/Statuses.php @@ -39,6 +39,8 @@ class Statuses extends BaseApi */ protected function rawContent(array $request = []) { + $uid = self::getCurrentUserID(); + $request = $this->getRequest([ 'limit' => 10, // Maximum number of results to return. Defaults to 10. ], $request); @@ -47,9 +49,9 @@ class Statuses extends BaseApi $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); $trending = []; - $statuses = Post::selectPostThread(['uri-id'], $condition, ['limit' => $request['limit'], 'order' => ['total-comments' => true]]); + $statuses = Post::selectPostThread(['uri-id'], $condition, ['limit' => $request['limit'], 'order' => ['total-actors' => true]]); while ($status = Post::fetch($statuses)) { - $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id']); + $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id'], $uid); } DBA::close($statuses); diff --git a/src/Object/Api/Mastodon/Card.php b/src/Object/Api/Mastodon/Card.php index 4bd6c6e769..bf87617190 100644 --- a/src/Object/Api/Mastodon/Card.php +++ b/src/Object/Api/Mastodon/Card.php @@ -59,7 +59,7 @@ class Card extends BaseDataTransferObject * @param array $attachment Attachment record * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function __construct(array $attachment) + public function __construct(array $attachment, array $history = []) { $this->url = $attachment['url'] ?? ''; $this->title = $attachment['title'] ?? ''; @@ -72,6 +72,7 @@ class Card extends BaseDataTransferObject $this->width = $attachment['width'] ?? 0; $this->height = $attachment['height'] ?? 0; $this->image = $attachment['image'] ?? ''; + $this->history = $history; } /** diff --git a/static/dbview.config.php b/static/dbview.config.php index 6c4f00a986..d06904a91f 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -665,6 +665,7 @@ "has-categories" => "0", "has-media" => "EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`)", "total-comments" => "(SELECT COUNT(*) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6)", + "total-actors" => "(SELECT COUNT(DISTINCT(`author-id`)) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6)", "signed_text" => ["diaspora-interaction", "interaction"], "parent-guid" => ["parent-item-uri", "guid"], "parent-network" => ["parent-post", "network"], diff --git a/static/routes.config.php b/static/routes.config.php index 81a9d2e2fd..da5dda0278 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -296,7 +296,7 @@ return [ '/timelines/public' => [Module\Api\Mastodon\Timelines\PublicTimeline::class, [R::GET ]], '/timelines/tag/{hashtag}' => [Module\Api\Mastodon\Timelines\Tag::class, [R::GET ]], '/trends' => [Module\Api\Mastodon\Trends\Tags::class, [R::GET ]], - '/trends/links' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented + '/trends/links' => [Module\Api\Mastodon\Trends\Links::class, [R::GET ]], '/trends/statuses' => [Module\Api\Mastodon\Trends\Statuses::class, [R::GET ]], '/trends/tags' => [Module\Api\Mastodon\Trends\Tags::class, [R::GET ]], ], From 437419bbd95879904876e2e218778460625b40fa Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 20:22:08 +0000 Subject: [PATCH 15/17] Updated database.sql --- database.sql | 2 +- static/dbstructure.config.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database.sql b/database.sql index c68ae094c1..04401f4c0d 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.12-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1492 +-- DB_UPDATE_VERSION 1493 -- ------------------------------------------ diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 2f613f9749..4f231b4c32 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1492); + define('DB_UPDATE_VERSION', 1493); } return [ From 7898f9cccb9edd62cf6e2f2f20aa3df827067418 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 28 Nov 2022 21:42:34 +0100 Subject: [PATCH 16/17] Update src/Module/Api/Mastodon/Trends/Links.php Co-authored-by: Hypolite Petovan --- src/Module/Api/Mastodon/Trends/Links.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/Trends/Links.php b/src/Module/Api/Mastodon/Trends/Links.php index ee535c7f3c..b41b1d6eaa 100644 --- a/src/Module/Api/Mastodon/Trends/Links.php +++ b/src/Module/Api/Mastodon/Trends/Links.php @@ -50,7 +50,7 @@ class Links extends BaseApi $trending = []; $statuses = Post::selectPostThread(['uri-id', 'total-comments', 'total-actors'], $condition, ['limit' => $request['limit'], 'order' => ['total-actors' => true]]); while ($status = Post::fetch($statuses)) { - $history = [['day' => (string)time(), 'uses' => (string)$status['total-comments'], 'accounts' => (string)$status['total-actors']]]; + $history = [['day' => (string)time(), 'uses' => (string)$status['total-comments'], 'accounts' => (string)$status['total-actors']]]; $trending[] = DI::mstdnCard()->createFromUriId($status['uri-id'], $history)->toArray(); } DBA::close($statuses); From 3288efb323eae6ee98d3520f0b7ba48a17baea5d Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 28 Nov 2022 21:22:21 +0000 Subject: [PATCH 17/17] Fix fatal errors because of unknown function/variable names --- src/Object/Post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Object/Post.php b/src/Object/Post.php index 8b8463f858..c81b49580f 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -622,7 +622,7 @@ class Post public function addChild(Post $item) { if (!$item->getId()) { - Logger::fatal('Post object has no id', ['post' => $item]); + Logger::error('Post object has no id', ['post' => $item]); return false; } elseif ($this->getChild($item->getId())) { Logger::warning('Post object already exists', ['post' => $item]); @@ -633,7 +633,7 @@ class Post * Only add what will be displayed */ if ($item->getDataValue('network') === Protocol::MAIL && DI::userSession()->getLocalUserId() != $item->getDataValue('uid')) { - Logger::warning('Post object does not belong to local user', ['post' => $item, 'local_user' => $local_user]); + Logger::warning('Post object does not belong to local user', ['post' => $item, 'local_user' => DI::userSession()->getLocalUserId()]); return false; } elseif (DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) || DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE)) {