From aa2a32d16f20ee71dfcf7ec0f999b8b3f636ad81 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 4 Apr 2022 16:03:53 +0000 Subject: [PATCH 1/4] Store the "EmojiReact" activity --- src/Protocol/Activity.php | 16 ++++++++++++---- src/Protocol/ActivityNamespace.php | 4 ++++ src/Protocol/ActivityPub/Processor.php | 4 ++++ src/Protocol/ActivityPub/Receiver.php | 20 ++++++++++++++++---- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Protocol/Activity.php b/src/Protocol/Activity.php index 4ebb02b8dd..637669a61e 100644 --- a/src/Protocol/Activity.php +++ b/src/Protocol/Activity.php @@ -176,6 +176,13 @@ final class Activity const O_UNFOLLOW = ActivityNamespace::OSTATUS . '/unfollow'; const O_UNFAVOURITE = ActivityNamespace::OSTATUS . '/unfavorite'; + /** + * React to a post via an emoji + * + * @var string + */ + const EMOJIREACT = ActivityNamespace::LITEPUB . '/emojireact'; + /** * likes (etc.) can apply to other things besides posts. Check if they are post children, * in which case we handle them specially @@ -183,10 +190,11 @@ final class Activity * Hidden activities, which doesn't need to be shown */ const HIDDEN_ACTIVITIES = [ - Activity::LIKE, Activity::DISLIKE, - Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, - Activity::FOLLOW, - Activity::ANNOUNCE, + self::LIKE, self::DISLIKE, + self::ATTEND, self::ATTENDNO, self::ATTENDMAYBE, + self::FOLLOW, + self::ANNOUNCE, + self::EMOJIREACT, ]; /** diff --git a/src/Protocol/ActivityNamespace.php b/src/Protocol/ActivityNamespace.php index 5506554609..5dbf88c86c 100644 --- a/src/Protocol/ActivityNamespace.php +++ b/src/Protocol/ActivityNamespace.php @@ -148,4 +148,8 @@ final class ActivityNamespace * @var string */ const MASTODON = 'http://mastodon.social/schema/1.0'; + /** + * @var string + */ + const LITEPUB = 'http://litepub.social'; } diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 3fc7dabe05..533d3a29b1 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -430,6 +430,10 @@ class Processor unset($item['post-type']); $item['object-type'] = Activity\ObjectType::NOTE; + if (!empty($activity['content'])) { + $item['body'] = HTML::toBBCode($activity['content']); + } + $item['diaspora_signed_text'] = $activity['diaspora:like'] ?? ''; self::postItem($activity, $item); diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index f53ce74193..13d1a921f8 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -384,7 +384,7 @@ class Receiver } else { $object_data['directmessage'] = JsonLD::fetchElement($activity, 'litepub:directMessage'); } - } elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Follow'])) && in_array($object_type, self::CONTENT_TYPES)) { + } elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Follow', 'litepub:EmojiReact'])) && in_array($object_type, self::CONTENT_TYPES)) { // Create a mostly empty array out of the activity data (instead of the object). // This way we later don't have to check for the existence of each individual array element. $object_data = self::processObject($activity); @@ -752,8 +752,10 @@ class Receiver break; case 'litepub:EmojiReact': - if (in_array($object_data['object_type'], array_merge([''], self::CONTENT_TYPES))) { - // Unhandled Pleroma activity to react to a post via an emoji + if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { + ActivityPub\Processor::createActivity($object_data, Activity::EMOJIREACT); + } elseif ($object_data['object_type'] == '') { + // The object type couldn't be determined. We don't have it and we can't fetch it. We ignore this activity. } else { self::storeUnhandledActivity(true, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); } @@ -786,7 +788,17 @@ class Receiver return; } - $tempfile = tempnam(System::getTempPath(), ($unknown ? 'unknown-' : 'unhandled-') . str_replace(':', '-', $type) . '-' . str_replace(':', '-', $object_data['object_type']) . '-' . str_replace(':', '-', $object_data['object_object_type'] ?? '') . '-'); + $file = ($unknown ? 'unknown-' : 'unhandled-') . str_replace(':', '-', $type) . '-'; + + if (!empty($object_data['object_type'])) { + $file .= str_replace(':', '-', $object_data['object_type']) . '-'; + } + + if (!empty($object_data['object_object_type'])) { + $file .= str_replace(':', '-', $object_data['object_object_type']) . '-'; + } + + $tempfile = tempnam(System::getTempPath(), $file); file_put_contents($tempfile, json_encode(['activity' => $activity, 'body' => $body, 'uid' => $uid, 'trust_source' => $trust_source, 'push' => $push, 'signer' => $signer, 'object_data' => $object_data], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); Logger::notice('Unknown activity stored', ['type' => $type, 'object_type' => $object_data['object_type'], $object_data['object_object_type'] ?? '', 'file' => $tempfile]); } From 61abc6377de478fdf4f7a71251ea90bbcff8d7b1 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 4 Apr 2022 23:07:44 +0000 Subject: [PATCH 2/4] Preparation for "Featured" collection added --- database.sql | 4 +- doc/database/db_apcontact.md | 2 + src/Model/APContact.php | 3 ++ src/Protocol/ActivityPub/Processor.php | 75 ++++++++++++++++++++++++++ src/Protocol/ActivityPub/Receiver.php | 6 +-- static/dbstructure.config.php | 4 +- 6 files changed, 88 insertions(+), 6 deletions(-) diff --git a/database.sql b/database.sql index ffe0202033..b986cc409f 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.05-dev (Siberian Iris) --- DB_UPDATE_VERSION 1453 +-- DB_UPDATE_VERSION 1454 -- ------------------------------------------ @@ -332,6 +332,8 @@ CREATE TABLE IF NOT EXISTS `apcontact` ( `inbox` varchar(255) NOT NULL COMMENT '', `outbox` varchar(255) COMMENT '', `sharedinbox` varchar(255) COMMENT '', + `featured` varchar(255) COMMENT 'Address for the collection of featured posts', + `featured-tags` varchar(255) COMMENT 'Address for the collection of featured tags', `manually-approve` boolean COMMENT '', `discoverable` boolean COMMENT 'Mastodon extension: true if profile is published in their directory', `nick` varchar(255) NOT NULL DEFAULT '' COMMENT '', diff --git a/doc/database/db_apcontact.md b/doc/database/db_apcontact.md index ee95d994f9..34cf4ec18b 100644 --- a/doc/database/db_apcontact.md +++ b/doc/database/db_apcontact.md @@ -17,6 +17,8 @@ Fields | inbox | | varchar(255) | NO | | NULL | | | outbox | | varchar(255) | YES | | NULL | | | sharedinbox | | varchar(255) | YES | | NULL | | +| featured | Address for the collection of featured posts | varchar(255) | YES | | NULL | | +| featured-tags | Address for the collection of featured tags | varchar(255) | YES | | NULL | | | manually-approve | | boolean | YES | | NULL | | | discoverable | Mastodon extension: true if profile is published in their directory | boolean | YES | | NULL | | | nick | | varchar(255) | NO | | | | diff --git a/src/Model/APContact.php b/src/Model/APContact.php index 9759876fab..deed6299de 100644 --- a/src/Model/APContact.php +++ b/src/Model/APContact.php @@ -232,6 +232,9 @@ class APContact self::unarchiveInbox($apcontact['sharedinbox'], true); } + $apcontact['featured'] = JsonLD::fetchElement($compacted, 'toot:featured', '@id'); + $apcontact['featured-tags'] = JsonLD::fetchElement($compacted, 'toot:featuredTags', '@id'); + $apcontact['nick'] = JsonLD::fetchElement($compacted, 'as:preferredUsername', '@value') ?? ''; $apcontact['name'] = JsonLD::fetchElement($compacted, 'as:name', '@value'); diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 533d3a29b1..647357811e 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -21,6 +21,7 @@ namespace Friendica\Protocol\ActivityPub; +use Exception; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Content\Text\Markdown; @@ -40,6 +41,7 @@ use Friendica\Model\Mail; use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Model\Post; +use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Relay; @@ -439,6 +441,79 @@ class Processor self::postItem($activity, $item); } + /** + * Fetch the Uri-Id of a post for the "featured" collection + * + * @param array $activity + * @return null|int + */ + private static function getUriIdForFeaturedCollection(array $activity) + { + $actor = APContact::getByURL($activity['actor']); + if (empty($actor)) { + return null; + } + + // Refetch the account when the "featured" collection is missing. + // This can be removed in a future version (end of 2022 should be good). + if (empty($actor['featured'])) { + $actor = APContact::getByURL($activity['actor'], true); + if (empty($actor)) { + return null; + } + } + + if ($activity['target_id'] != $actor['featured']) { + return null; + } + + $id = Contact::getIdForURL($activity['actor']); + if (empty($id)) { + return null; + } + + $parent = Post::selectFirst(['uri-id'], ['uri' => $activity['object_id'], 'author-id' => $id]); + if (!empty($parent['uri-id'])) { + return $parent['uri-id']; + } + + return null; + } + + /** + * Add a post to the "Featured" collection + * + * @param array $activity + */ + public static function addToFeaturedCollection(array $activity) + { + $uriid = self::getUriIdForFeaturedCollection($activity); + if (empty($uriid)) { + return; + } + + Logger::debug('Add post to featured collection', ['uri-id' => $uriid]); + + // @todo Add functionality + } + + /** + * Remove a post to the "Featured" collection + * + * @param array $activity + */ + public static function removeFromFeaturedCollection(array $activity) + { + $uriid = self::getUriIdForFeaturedCollection($activity); + if (empty($uriid)) { + return; + } + + Logger::debug('Remove post from featured collection', ['uri-id' => $uriid]); + + // @todo Add functionality + } + /** * Create an event * diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index b14780b196..0894ffc453 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -578,8 +578,7 @@ class Receiver if ($object_data['object_type'] == 'as:tag') { ActivityPub\Processor::addTag($object_data); } elseif (in_array($object_data['object_type'], self::CONTENT_TYPES)) { - // Seems to be used by Mastodon to announce that a post is pinned - self::storeUnhandledActivity(false, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); + ActivityPub\Processor::addToFeaturedCollection($object_data); } elseif ($object_data['object_type'] == '') { // The object type couldn't be determined. We don't have it and we can't fetch it. We ignore this activity. } else { @@ -680,8 +679,7 @@ class Receiver case 'as:Remove': if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { - // Seems to be used by Mastodon to remove the pinned status of a post - self::storeUnhandledActivity(false, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); + ActivityPub\Processor::removeFromFeaturedCollection($object_data); } elseif ($object_data['object_type'] == '') { // The object type couldn't be determined. We don't have it and we can't fetch it. We ignore this activity. } else { diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index fd1e39cc2e..36c9ca3e0a 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', 1453); + define('DB_UPDATE_VERSION', 1454); } return [ @@ -394,6 +394,8 @@ return [ "inbox" => ["type" => "varchar(255)", "not null" => "1", "comment" => ""], "outbox" => ["type" => "varchar(255)", "comment" => ""], "sharedinbox" => ["type" => "varchar(255)", "comment" => ""], + "featured" => ["type" => "varchar(255)", "comment" => "Address for the collection of featured posts"], + "featured-tags" => ["type" => "varchar(255)", "comment" => "Address for the collection of featured tags"], "manually-approve" => ["type" => "boolean", "comment" => ""], "discoverable" => ["type" => "boolean", "comment" => "Mastodon extension: true if profile is published in their directory"], "nick" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], From a5a5a4b60318eb8ec94ee25f38e8ef6e6516c4b9 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 5 Apr 2022 05:53:17 +0000 Subject: [PATCH 3/4] Cleaned code --- src/Protocol/ActivityPub/Processor.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 647357811e..5caef7745e 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -21,7 +21,6 @@ namespace Friendica\Protocol\ActivityPub; -use Exception; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; use Friendica\Content\Text\Markdown; @@ -41,7 +40,6 @@ use Friendica\Model\Mail; use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Model\Post; -use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Protocol\Activity; use Friendica\Protocol\ActivityPub; use Friendica\Protocol\Relay; From dcbca0c6a344df31721e4de56b34566a66cc4091 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 5 Apr 2022 07:48:38 +0000 Subject: [PATCH 4/4] Store "View" activity from Peertube --- src/Protocol/Activity.php | 8 ++++++++ src/Protocol/ActivityNamespace.php | 7 +++++++ src/Protocol/ActivityPub/Receiver.php | 8 +++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Protocol/Activity.php b/src/Protocol/Activity.php index 637669a61e..e75b1cf507 100644 --- a/src/Protocol/Activity.php +++ b/src/Protocol/Activity.php @@ -183,6 +183,13 @@ final class Activity */ const EMOJIREACT = ActivityNamespace::LITEPUB . '/emojireact'; + /** + * View notification from Peertube + * + * @var string + */ + const VIEW = ActivityNamespace::PEERTUBE . '/view'; + /** * likes (etc.) can apply to other things besides posts. Check if they are post children, * in which case we handle them specially @@ -195,6 +202,7 @@ final class Activity self::FOLLOW, self::ANNOUNCE, self::EMOJIREACT, + self::VIEW, ]; /** diff --git a/src/Protocol/ActivityNamespace.php b/src/Protocol/ActivityNamespace.php index 5dbf88c86c..ee6d80df14 100644 --- a/src/Protocol/ActivityNamespace.php +++ b/src/Protocol/ActivityNamespace.php @@ -144,12 +144,19 @@ final class ActivityNamespace * @var string */ const ATOM1 = 'http://www.w3.org/2005/Atom'; + /** * @var string */ const MASTODON = 'http://mastodon.social/schema/1.0'; + /** * @var string */ const LITEPUB = 'http://litepub.social'; + + /** + * @var string + */ + const PEERTUBE = 'https://joinpeertube.org'; } diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 0894ffc453..80bba0b0b9 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -385,7 +385,7 @@ class Receiver } else { $object_data['directmessage'] = JsonLD::fetchElement($activity, 'litepub:directMessage'); } - } elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Follow', 'litepub:EmojiReact'])) && in_array($object_type, self::CONTENT_TYPES)) { + } elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Follow', 'litepub:EmojiReact', 'as:View'])) && in_array($object_type, self::CONTENT_TYPES)) { // Create a mostly empty array out of the activity data (instead of the object). // This way we later don't have to check for the existence of each individual array element. $object_data = self::processObject($activity); @@ -743,8 +743,10 @@ class Receiver break; case 'as:View': - if (in_array($object_data['object_type'], ['as:Note', 'as:Video'])) { - // Unhandled Peertube activity + if (in_array($object_data['object_type'], self::CONTENT_TYPES)) { + ActivityPub\Processor::createActivity($object_data, Activity::VIEW); + } elseif ($object_data['object_type'] == '') { + // The object type couldn't be determined. Most likely we don't have it here. We ignore this activity. } else { self::storeUnhandledActivity(true, $type, $object_data, $activity, $body, $uid, $trust_source, $push, $signer); }