From d7df0825db598012871d8555de04575d0c30247a Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 8 Apr 2022 21:25:31 +0000 Subject: [PATCH] We now offer an endpoint for featured posts --- src/Model/Contact.php | 3 +- src/Module/ActivityPub/Featured.php | 51 ++++++++++++++++++ src/Protocol/ActivityPub.php | 4 ++ src/Protocol/ActivityPub/Transmitter.php | 68 +++++++++++++++++++++++- static/routes.config.php | 4 +- 5 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 src/Module/ActivityPub/Featured.php diff --git a/src/Model/Contact.php b/src/Model/Contact.php index e23fa31699..acb4bc10ed 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -1474,7 +1474,8 @@ class Contact if ($pager->getStart() == 0) { $cdata = Contact::getPublicAndUserContactID($cid, local_user()); if (!empty($cdata['public'])) { - $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ?)", $cdata['public']]; + $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", + $cdata['public'], Post\Collection::FEATURED]; $pinned = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params)); $items = array_merge($pinned, $items); } diff --git a/src/Module/ActivityPub/Featured.php b/src/Module/ActivityPub/Featured.php new file mode 100644 index 0000000000..33aabf3a85 --- /dev/null +++ b/src/Module/ActivityPub/Featured.php @@ -0,0 +1,51 @@ +. + * + */ + +namespace Friendica\Module\ActivityPub; + +use Friendica\BaseModule; +use Friendica\Model\User; +use Friendica\Protocol\ActivityPub; + +/** + * ActivityPub featured posts + */ +class Featured extends BaseModule +{ + protected function rawContent(array $request = []) + { + if (empty($this->parameters['nickname'])) { + throw new \Friendica\Network\HTTPException\NotFoundException(); + } + + $owner = User::getOwnerDataByNick($this->parameters['nickname']); + if (empty($owner)) { + throw new \Friendica\Network\HTTPException\NotFoundException(); + } + + $page = $_REQUEST['page'] ?? null; + + $outbox = ActivityPub\Transmitter::getFeatured($owner, $page); + header('Content-Type: application/activity+json'); + echo json_encode($outbox); + exit(); + } +} diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 9ab2a3c885..78496e2433 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -65,6 +65,10 @@ class ActivityPub 'diaspora' => 'https://diasporafoundation.org/ns/', 'litepub' => 'http://litepub.social/ns#', 'toot' => 'http://joinmastodon.org/ns#', + 'featured' => [ + "@id" => "toot:featured", + "@type" => "@id", + ], 'schema' => 'http://schema.org#', 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag', diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 553993edca..a39a4c502a 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -288,6 +288,69 @@ class Transmitter return $data; } + /** + * Public posts for the given owner + * + * @param array $owner Owner array + * @param integer $page Page number + * + * @return array of posts + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + public static function getFeatured($owner, $page = null) + { + $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", + Contact::getIdForURL($owner['url'], 0, false), Post\Collection::FEATURED]; + + $condition = DBA::mergeConditions($condition, + ['uid' => $owner['uid'], + 'author-id' => Contact::getIdForURL($owner['url'], 0, false), + 'private' => [Item::PUBLIC, Item::UNLISTED], + 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], + 'network' => Protocol::FEDERATED, + 'parent-network' => Protocol::FEDERATED, + 'origin' => true, + 'deleted' => false, + 'visible' => true]); + + $count = Post::count($condition); + + $data = ['@context' => ActivityPub::CONTEXT]; + $data['id'] = DI::baseUrl() . '/featured/' . $owner['nickname']; + $data['type'] = 'OrderedCollection'; + $data['totalItems'] = $count; + + if (empty($page)) { + $data['first'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=1'; + } else { + $data['type'] = 'OrderedCollectionPage'; + $list = []; + + $items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]); + while ($item = Post::fetch($items)) { + $activity = self::createActivityFromItem($item['id'], true); + $activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type']; + + // Only list "Create" activity objects here, no reshares + if (!empty($activity['object']) && ($activity['type'] == 'Create')) { + $list[] = $activity['object']; + } + } + DBA::close($items); + + if (!empty($list)) { + $data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1); + } + + $data['partOf'] = DI::baseUrl() . '/featured/' . $owner['nickname']; + + $data['orderedItems'] = $list; + } + + return $data; + } + /** * Return the service array containing information the used software and it's url * @@ -328,8 +391,9 @@ class Transmitter if ($uid != 0) { $data['following'] = DI::baseUrl() . '/following/' . $owner['nick']; $data['followers'] = DI::baseUrl() . '/followers/' . $owner['nick']; - $data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick']; - $data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick']; + $data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick']; + $data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick']; + $data['featured'] = DI::baseUrl() . '/featured/' . $owner['nick']; } else { $data['inbox'] = DI::baseUrl() . '/friendica/inbox'; } diff --git a/static/routes.config.php b/static/routes.config.php index 21afd2307b..ddd678d277 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -361,7 +361,9 @@ return [ '/dirfind' => [Module\Search\Directory::class, [R::GET]], '/directory' => [Module\Directory::class, [R::GET]], - '/events/json' => [Module\Events\Json::class, [R::GET]], + '/events/json' => [Module\Events\Json::class, [R::GET]], + + '/featured/{nickname}' => [Module\ActivityPub\Featured::class, [R::GET]], '/feed' => [ '/{nickname}' => [Module\Feed::class, [R::GET]],