From d34052b3321c675fa2086797489e2798f43580ac Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 4 Dec 2019 07:02:39 +0000 Subject: [PATCH 1/5] Make quoted announces look better / more announce improvements --- src/Content/Text/BBCode.php | 5 +- src/Module/Objects.php | 9 +++- src/Protocol/ActivityPub/Transmitter.php | 62 ++++++++++++++++++++---- 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index c2b54821aa..38719e046c 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -1052,9 +1052,12 @@ class BBCode extends BaseObject $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 7: // statusnet/GNU Social - case 9: // ActivityPub $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '

' . "\n"; break; + case 9: // ActivityPub + $author = '@' . $author_contact['addr'] . ':'; + $text = '
' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author . '
' . $content . '
' . "\n"; + break; default: // Transforms quoted tweets in rich attachments to avoid nested tweets if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) { diff --git a/src/Module/Objects.php b/src/Module/Objects.php index 5538be1888..df57636926 100644 --- a/src/Module/Objects.php +++ b/src/Module/Objects.php @@ -42,7 +42,14 @@ class Objects extends BaseModule } } - $data = ActivityPub\Transmitter::createObjectFromItemID($item['id']); + $activity = ActivityPub\Transmitter::createActivityFromItem($item['id'], true); + // Only display "Create" activity objects here, no reshares or anything else + if (!is_array($activity['object']) || ($activity['type'] != 'Create')) { + throw new \Friendica\Network\HTTPException\NotFoundException(); + } + + $data = ['@context' => ActivityPub::CONTEXT]; + $data = array_merge($data, $activity['object']); header('Content-Type: application/activity+json'); echo json_encode($data); diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index fa8e4bf46f..720fa09d7f 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -176,9 +176,11 @@ class Transmitter $items = Item::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]); while ($item = Item::fetch($items)) { - $object = self::createObjectFromItemID($item['id']); - unset($object['@context']); - $list[] = $object; + $activity = self::createActivityFromItem($item['id'], true); + // Only list "Create" activity objects here, no reshares + if (is_array($activity['object']) && ($activity['type'] == 'Create')) { + $list[] = $activity['object']; + } } if (!empty($list)) { @@ -379,6 +381,15 @@ class Transmitter $terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]); + // Directly mention the original author upon a quoted reshare. + // Else just ensure that the original author receives the reshare. + $announce = self::getAnnounceObject($item); + if (!empty($announce['comment'])) { + $data['to'][] = $announce['actor']['url']; + } elseif (!empty($announce)) { + $data['cc'][] = $announce['actor']['url']; + } + if (!$item['private']) { $data = array_merge($data, self::fetchPermissionBlockFromConversation($item)); @@ -757,8 +768,8 @@ class Transmitter // Only check for a reshare, if it is a real reshare and no quoted reshare if (strpos($item['body'], "[share") === 0) { - $announce = api_share_as_retweet($item); - $reshared = !empty($announce['plink']); + $announce = self::getAnnounceObject($item); + $reshared = !empty($announce); } if ($reshared) { @@ -1004,6 +1015,13 @@ class Transmitter $tags[] = ['type' => 'Mention', 'href' => $term['url'], 'name' => $mention]; } } + + $announce = self::getAnnounceObject($item); + // Mention the original author upon commented reshares + if (!empty($announce['comment'])) { + $tags[] = ['type' => 'Mention', 'href' => $announce['actor']['url'], 'name' => '@' . $announce['actor']['addr']]; + } + return $tags; } @@ -1371,21 +1389,21 @@ class Transmitter private static function createAnnounce($item, $data) { $orig_body = $item['body']; - $announce = api_share_as_retweet($item); - if (empty($announce['plink'])) { + $announce = self::getAnnounceObject($item); + if (empty($announce)) { $data['type'] = 'Create'; $data['object'] = self::createNote($item); return $data; } // Fetch the original id of the object - $activity = ActivityPub::fetchContent($announce['plink'], $item['uid']); + $activity = ActivityPub::fetchContent($announce['id'], $item['uid']); if (!empty($activity)) { $ldactivity = JsonLD::compact($activity); $id = JsonLD::fetchElement($ldactivity, '@id'); $type = str_replace('as:', '', JsonLD::fetchElement($ldactivity, '@type')); if (!empty($id)) { - if (empty($announce['share-pre-body'])) { + if (empty($announce['comment'])) { // Pure announce, without a quote $data['type'] = 'Announce'; $data['object'] = $id; @@ -1394,7 +1412,7 @@ class Transmitter // Quote $data['type'] = 'Create'; - $item['body'] = trim($announce['share-pre-body']) . "\n" . $id; + $item['body'] = $announce['comment'] . "\n" . $id; $data['object'] = self::createNote($item); /// @todo Finally descide how to implement this in AP. This is a possible way: @@ -1411,6 +1429,30 @@ class Transmitter return $data; } + /** + * Return announce related data if the item is an annunce + * + * @param array $item + * + * @return array + */ + public static function getAnnounceObject($item) + { + $announce = api_share_as_retweet($item); + if (empty($announce['plink'])) { + return []; + } + + /// @ToDo Check if the announced item is an AP object + + $profile = APContact::getByURL($announce['author-link'], false); + if (empty($profile)) { + return []; + } + + return ['id' => $announce['plink'], 'actor' => $profile, 'comment' => trim($announce['share-pre-body'])]; + } + /** * Creates an activity id for a given contact id * From abed3ba90607392d0e84abf08e247bf7bea437dd Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 4 Dec 2019 07:13:29 +0000 Subject: [PATCH 2/5] Remove unused function --- src/Protocol/ActivityPub/Transmitter.php | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 720fa09d7f..2be621cb93 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -930,29 +930,6 @@ class Transmitter /// @todo Create "conversation" entry } - /** - * Creates an object array for a given item id - * - * @param integer $item_id - * - * @return array with the object data - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - * @throws \ImagickException - */ - public static function createObjectFromItemID($item_id) - { - $item = Item::selectFirst([], ['id' => $item_id, 'parent-network' => Protocol::NATIVE_SUPPORT]); - - if (!DBA::isResult($item)) { - return false; - } - - $data = ['@context' => ActivityPub::CONTEXT]; - $data = array_merge($data, self::createNote($item)); - - return $data; - } - /** * Creates a location entry for a given item array * From ef3664e6d4583d8f0a8272065530a2a440effebf Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 4 Dec 2019 08:08:48 +0000 Subject: [PATCH 3/5] Check if reshared item is an AP item --- src/Protocol/ActivityPub/Transmitter.php | 66 +++++++++++++----------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 2be621cb93..6b4d7a9960 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -1373,36 +1373,22 @@ class Transmitter return $data; } - // Fetch the original id of the object - $activity = ActivityPub::fetchContent($announce['id'], $item['uid']); - if (!empty($activity)) { - $ldactivity = JsonLD::compact($activity); - $id = JsonLD::fetchElement($ldactivity, '@id'); - $type = str_replace('as:', '', JsonLD::fetchElement($ldactivity, '@type')); - if (!empty($id)) { - if (empty($announce['comment'])) { - // Pure announce, without a quote - $data['type'] = 'Announce'; - $data['object'] = $id; - return $data; - } - - // Quote - $data['type'] = 'Create'; - $item['body'] = $announce['comment'] . "\n" . $id; - $data['object'] = self::createNote($item); - - /// @todo Finally descide how to implement this in AP. This is a possible way: - $data['object']['attachment'][] = ['type' => $type, 'id' => $id]; - - $data['object']['source']['content'] = $orig_body; - return $data; - } + if (empty($announce['comment'])) { + // Pure announce, without a quote + $data['type'] = 'Announce'; + $data['object'] = $announce['id']; + return $data; } - $item['body'] = $orig_body; + // Quote $data['type'] = 'Create'; + $item['body'] = $announce['comment'] . "\n" . $id; $data['object'] = self::createNote($item); + + /// @todo Finally descide how to implement this in AP. This is a possible way: + $data['object']['attachment'][] = ['type' => $type, 'id' => $id]; + + $data['object']['source']['content'] = $orig_body; return $data; } @@ -1415,19 +1401,37 @@ class Transmitter */ public static function getAnnounceObject($item) { - $announce = api_share_as_retweet($item); - if (empty($announce['plink'])) { + if (!preg_match("/(.*?)\[share(.*?)\]\s?.*?\s?\[\/share\]\s?/ism", $item['body'], $matches)) { return []; } - /// @ToDo Check if the announced item is an AP object + $attributes = $matches[2]; + $comment = $matches[1]; - $profile = APContact::getByURL($announce['author-link'], false); + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if (empty($matches[1])) { + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + } + + if (empty($matches[1])) { + return []; + } + + $reshared_item = Item::selectFirst(['author-link', 'uri', 'network'], ['guid' => $matches[1]]); + if (!DBA::isResult($reshared_item)) { + return []; + } + + if (!in_array($reshared_item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) { + return []; + } + + $profile = APContact::getByURL($reshared_item['author-link'], false); if (empty($profile)) { return []; } - return ['id' => $announce['plink'], 'actor' => $profile, 'comment' => trim($announce['share-pre-body'])]; + return ['id' => $reshared_item['uri'], 'actor' => $profile, 'comment' => trim($comment)]; } /** From 276d6fddd116d4afdb0fdc6bf73082623a38f20a Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 4 Dec 2019 09:15:03 +0000 Subject: [PATCH 4/5] Function renamed - we don't return an object --- src/Protocol/ActivityPub/Transmitter.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 6b4d7a9960..1686f68dea 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -383,7 +383,7 @@ class Transmitter // Directly mention the original author upon a quoted reshare. // Else just ensure that the original author receives the reshare. - $announce = self::getAnnounceObject($item); + $announce = self::getAnnounceArray($item); if (!empty($announce['comment'])) { $data['to'][] = $announce['actor']['url']; } elseif (!empty($announce)) { @@ -768,7 +768,7 @@ class Transmitter // Only check for a reshare, if it is a real reshare and no quoted reshare if (strpos($item['body'], "[share") === 0) { - $announce = self::getAnnounceObject($item); + $announce = self::getAnnounceArray($item); $reshared = !empty($announce); } @@ -993,7 +993,7 @@ class Transmitter } } - $announce = self::getAnnounceObject($item); + $announce = self::getAnnounceArray($item); // Mention the original author upon commented reshares if (!empty($announce['comment'])) { $tags[] = ['type' => 'Mention', 'href' => $announce['actor']['url'], 'name' => '@' . $announce['actor']['addr']]; @@ -1366,7 +1366,7 @@ class Transmitter private static function createAnnounce($item, $data) { $orig_body = $item['body']; - $announce = self::getAnnounceObject($item); + $announce = self::getAnnounceArray($item); if (empty($announce)) { $data['type'] = 'Create'; $data['object'] = self::createNote($item); @@ -1399,7 +1399,7 @@ class Transmitter * * @return array */ - public static function getAnnounceObject($item) + public static function getAnnounceArray($item) { if (!preg_match("/(.*?)\[share(.*?)\]\s?.*?\s?\[\/share\]\s?/ism", $item['body'], $matches)) { return []; From 8f4f3e00e2ea1cb71cca251551058a8515c1743e Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 4 Dec 2019 09:36:46 +0000 Subject: [PATCH 5/5] Return more data in the announce object --- src/Protocol/ActivityPub/Transmitter.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 1686f68dea..a9f5ed20a1 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -1376,17 +1376,17 @@ class Transmitter if (empty($announce['comment'])) { // Pure announce, without a quote $data['type'] = 'Announce'; - $data['object'] = $announce['id']; + $data['object'] = $announce['object']['uri']; return $data; } // Quote $data['type'] = 'Create'; - $item['body'] = $announce['comment'] . "\n" . $id; + $item['body'] = $announce['comment'] . "\n" . $announce['object']['plink']; $data['object'] = self::createNote($item); /// @todo Finally descide how to implement this in AP. This is a possible way: - $data['object']['attachment'][] = ['type' => $type, 'id' => $id]; + $data['object']['attachment'][] = self::createNote($announce['object']); $data['object']['source']['content'] = $orig_body; return $data; @@ -1417,7 +1417,7 @@ class Transmitter return []; } - $reshared_item = Item::selectFirst(['author-link', 'uri', 'network'], ['guid' => $matches[1]]); + $reshared_item = Item::selectFirst([], ['guid' => $matches[1]]); if (!DBA::isResult($reshared_item)) { return []; } @@ -1431,7 +1431,7 @@ class Transmitter return []; } - return ['id' => $reshared_item['uri'], 'actor' => $profile, 'comment' => trim($comment)]; + return ['object' => $reshared_item, 'actor' => $profile, 'comment' => trim($comment)]; } /**