Use reshare with Diaspora like with ActivityPub

This commit is contained in:
Michael 2022-12-13 23:19:19 +00:00
parent ed805d1af2
commit ca8a89ed1c
11 changed files with 108 additions and 84 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2022.12-rc (Giant Rhubarb) -- Friendica 2022.12-rc (Giant Rhubarb)
-- DB_UPDATE_VERSION 1501 -- DB_UPDATE_VERSION 1502
-- ------------------------------------------ -- ------------------------------------------
@ -1439,7 +1439,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
`event-id` int unsigned COMMENT 'Used to link to the event.id', `event-id` int unsigned COMMENT 'Used to link to the event.id',
`unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen', `unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen',
`hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user', `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user',
`notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', `notification-type` smallint unsigned NOT NULL DEFAULT 0 COMMENT '',
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid', `wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`origin` boolean NOT NULL DEFAULT '0' COMMENT 'item originated at this site', `origin` boolean NOT NULL DEFAULT '0' COMMENT 'item originated at this site',
`psid` int unsigned COMMENT 'ID of the permission set of this post', `psid` int unsigned COMMENT 'ID of the permission set of this post',

View file

@ -34,7 +34,7 @@ Fields
| event-id | Used to link to the event.id | int unsigned | YES | | NULL | | | event-id | Used to link to the event.id | int unsigned | YES | | NULL | |
| unseen | post has not been seen | boolean | NO | | 1 | | | unseen | post has not been seen | boolean | NO | | 1 | |
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | | | hidden | Marker to hide the post from the user | boolean | NO | | 0 | |
| notification-type | | tinyint unsigned | NO | | 0 | | | notification-type | | smallint unsigned | NO | | 0 | |
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | | | wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
| origin | item originated at this site | boolean | NO | | 0 | | | origin | item originated at this site | boolean | NO | | 0 | |
| psid | ID of the permission set of this post | int unsigned | YES | | NULL | | | psid | ID of the permission set of this post | int unsigned | YES | | NULL | |

View file

@ -985,7 +985,8 @@ class Conversation
$thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params); $thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
$items = []; $items = [];
$quoteuriids = [];
while ($row = Post::fetch($thread_items)) { while ($row = Post::fetch($thread_items)) {
if (!empty($items[$row['uri-id']]) && ($row['uid'] == 0)) { if (!empty($items[$row['uri-id']]) && ($row['uid'] == 0)) {
@ -1005,11 +1006,37 @@ class Conversation
} }
} }
if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT])) {
$quoteuriids[$row['uri-id']] = [
'uri-id' => $row['uri-id'],
'uri' => $row['uri'],
'parent-uri-id' => $row['parent-uri-id'],
'parent-uri' => $row['parent-uri'],
];
}
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []); $items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []);
} }
DBA::close($thread_items); DBA::close($thread_items);
$quotes = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), ['quote-uri-id' => array_column($quoteuriids, 'uri-id'), 'uid' => 0]);
while ($quote = Post::fetch($quotes)) {
$row = $quote;
$row['uid'] = $uid;
$row['verb'] = $row['body'] = $row['raw-body'] = Activity::ANNOUNCE;
$row['gravity'] = ItemModel::GRAVITY_ACTIVITY;
$row['object-type'] = Activity\ObjectType::NOTE;
$row['parent-uri'] = $quoteuriids[$quote['quote-uri-id']]['parent-uri'];
$row['parent-uri-id'] = $quoteuriids[$quote['quote-uri-id']]['parent-uri-id'];
$row['thr-parent'] = $quoteuriids[$quote['quote-uri-id']]['uri'];
$row['thr-parent-id'] = $quoteuriids[$quote['quote-uri-id']]['uri-id'];
$items[$row['uri-id']] = $this->addRowInformation($row, [], []);
}
DBA::close($quotes);
$items = $this->convSort($items, $order); $items = $this->convSort($items, $order);
$this->profiler->stopRecording(); $this->profiler->stopRecording();

View file

@ -115,13 +115,17 @@ class Status extends BaseFactory
'gravity' => Item::GRAVITY_ACTIVITY, 'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::ANNOUNCE), 'vid' => Verb::getID(Activity::ANNOUNCE),
'deleted' => false 'deleted' => false
], []); ]) + Post::countPosts([
'quote-uri-id' => $uriId,
'deleted' => false
]);
$count_like = Post::countPosts([ $count_like = Post::countPosts([
'thr-parent-id' => $uriId, 'thr-parent-id' => $uriId,
'gravity' => Item::GRAVITY_ACTIVITY, 'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::LIKE), 'vid' => Verb::getID(Activity::LIKE),
'deleted' => false 'deleted' => false
], []); ]);
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts( $counts = new \Friendica\Object\Api\Mastodon\Status\Counts(
Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false], []), Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false], []),
@ -144,6 +148,11 @@ class Status extends BaseFactory
'gravity' => Item::GRAVITY_ACTIVITY, 'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::ANNOUNCE), 'vid' => Verb::getID(Activity::ANNOUNCE),
'deleted' => false 'deleted' => false
]) || Post::exists([
'quote-uri-id' => $uriId,
'uid' => $uid,
'origin' => true,
'deleted' => false
]); ]);
$userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes( $userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(
$origin_like, $origin_like,

View file

@ -27,7 +27,6 @@ use Friendica\Core\Hook;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Item; use Friendica\Model\Item;
@ -52,6 +51,7 @@ class UserNotification
const TYPE_DIRECT_THREAD_COMMENT = 64; const TYPE_DIRECT_THREAD_COMMENT = 64;
const TYPE_SHARED = 128; const TYPE_SHARED = 128;
const TYPE_FOLLOW = 256; const TYPE_FOLLOW = 256;
const TYPE_QUOTED = 512;
/** /**
* Insert a new user notification entry * Insert a new user notification entry
@ -273,6 +273,14 @@ class UserNotification
} }
} }
if (($item['verb'] != Activity::ANNOUNCE) && self::checkQuoted($item, $contacts)) {
$notification_type = $notification_type | self::TYPE_QUOTED;
if (!$notified) {
self::insertNotificationByItem(self::TYPE_QUOTED, $uid, $item);
$notified = true;
}
}
if (($item['verb'] != Activity::ANNOUNCE) && self::checkFollowParticipation($item, $contacts)) { if (($item['verb'] != Activity::ANNOUNCE) && self::checkFollowParticipation($item, $contacts)) {
$notification_type = $notification_type | self::TYPE_FOLLOW; $notification_type = $notification_type | self::TYPE_FOLLOW;
if (!$notified) { if (!$notified) {
@ -581,4 +589,23 @@ class UserNotification
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY]; $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY];
return Post::exists($condition); return Post::exists($condition);
} }
/**
* Check for a quoted post of a post of the given user
*
* @param array $item
* @param array $contacts Array of contact IDs
* @return bool The item is a quoted post of a user's post or comment
* @throws Exception
*/
private static function checkQuoted(array $item, array $contacts): bool
{
if (empty($item['quote-uri-id'])) {
return false;
}
$condition = ['uri-id' => $item['quote-uri-id'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => [item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]];
return Post::exists($condition);
}
} }

View file

@ -29,6 +29,7 @@ use Friendica\DI;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Protocol\Diaspora;
/** /**
* @see https://docs.joinmastodon.org/methods/statuses/ * @see https://docs.joinmastodon.org/methods/statuses/
@ -49,12 +50,14 @@ class Reblog extends BaseApi
DI::mstdnError()->RecordNotFound(); DI::mstdnError()->RecordNotFound();
} }
if (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { if ($item['network'] == Protocol::DIASPORA) {
Diaspora::performReshare($this->parameters['id'], $uid);
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) {
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network']))); DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network'])));
} else {
Item::performActivity($item['id'], 'announce', $uid);
} }
Item::performActivity($item['id'], 'announce', $uid);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
} }
} }

View file

@ -49,12 +49,21 @@ class Unreblog extends BaseApi
DI::mstdnError()->RecordNotFound(); DI::mstdnError()->RecordNotFound();
} }
if (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { if ($item['network'] == Protocol::DIASPORA) {
$item = Post::selectFirstForUser($uid, ['id'], ['quote-uri-id' => $this->parameters['id'], 'origin' => true, 'uid' => $uid]);
if (empty($item['id'])) {
DI::mstdnError()->RecordNotFound();
}
if (!Item::markForDeletionById($item['id'])) {
DI::mstdnError()->RecordNotFound();
}
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) {
DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network']))); DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network'])));
} else {
Item::performActivity($item['id'], 'unannounce', $uid);
} }
Item::performActivity($item['id'], 'unannounce', $uid);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
} }
} }

View file

@ -46,17 +46,26 @@ class Activity extends BaseModule
throw new HTTPException\BadRequestException(); throw new HTTPException\BadRequestException();
} }
$verb = $this->parameters['verb']; $verb = $this->parameters['verb'];
$itemId = $this->parameters['id']; $itemId = $this->parameters['id'];
$handled = false;
if (in_array($verb, ['announce', 'unannounce'])) { if (in_array($verb, ['announce', 'unannounce'])) {
$item = Post::selectFirst(['network', 'uri-id'], ['id' => $itemId, 'uid' => [DI::userSession()->getLocalUserId(), 0]]); $item = Post::selectFirst(['network', 'uri-id'], ['id' => $itemId, 'uid' => [DI::userSession()->getLocalUserId(), 0]]);
if ($item['network'] == Protocol::DIASPORA) { if ($item['network'] == Protocol::DIASPORA) {
Diaspora::performReshare($item['uri-id'], DI::userSession()->getLocalUserId()); $quote = Post::selectFirst(['id'], ['quote-uri-id' => $item['uri-id'], 'origin' => true, 'uid' => DI::userSession()->getLocalUserId()]);
if (!empty($quote['id'])) {
if (!Item::markForDeletionById($quote['id'])) {
throw new HTTPException\BadRequestException();
}
} else {
Diaspora::performReshare($item['uri-id'], DI::userSession()->getLocalUserId());
}
$handled = true;
} }
} }
if (!Item::performActivity($itemId, $verb, DI::userSession()->getLocalUserId())) { if (!$handled && !Item::performActivity($itemId, $verb, DI::userSession()->getLocalUserId())) {
throw new HTTPException\BadRequestException(); throw new HTTPException\BadRequestException();
} }

View file

@ -311,7 +311,11 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
$msg = $l10n->t('%1$s shared a post'); $msg = $l10n->t('%1$s shared a post');
} }
break; break;
}
case Post\UserNotification::TYPE_QUOTED:
$msg = $l10n->t('%1$s shared your post %2$s');
break;
}
break; break;
} }
} }

View file

@ -2285,65 +2285,6 @@ class Diaspora
return true; return true;
} }
/**
* Stores a reshare activity
*
* @param array $item Array of reshare post
* @param integer $parent_message_id Id of the parent post
* @param string $guid GUID string of reshare action
* @param WebFingerUri $author Author handle
* @return false|void
* @throws InternalServerErrorException
* @throws \ImagickException
*/
private static function addReshareActivity(array $item, int $parent_message_id, string $guid, WebFingerUri $author)
{
$parent = Post::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]);
$datarray = [];
$datarray['uid'] = $item['uid'];
$datarray['contact-id'] = $item['contact-id'];
$datarray['network'] = $item['network'];
$datarray['author-link'] = $item['author-link'];
$datarray['author-id'] = $item['author-id'];
$datarray['owner-link'] = $datarray['author-link'];
$datarray['owner-id'] = $datarray['author-id'];
$datarray['guid'] = $parent['guid'] . '-' . $guid;
$datarray['uri'] = self::getUriFromGuid($datarray['guid'], $author);
$datarray['thr-parent'] = $parent['uri'];
$datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE;
$datarray['gravity'] = Item::GRAVITY_ACTIVITY;
$datarray['object-type'] = Activity\ObjectType::NOTE;
$datarray['protocol'] = $item['protocol'];
$datarray['source'] = $item['source'];
$datarray['direction'] = $item['direction'];
$datarray['post-reason'] = $item['post-reason'];
$datarray['plink'] = self::plink($author, $datarray['guid']);
$datarray['private'] = $item['private'];
$datarray['changed'] = $datarray['created'] = $datarray['edited'] = $item['created'];
if (Item::isTooOld($datarray)) {
Logger::info('Reshare activity is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]);
return false;
}
$message_id = Item::insert($datarray);
if ($message_id) {
Logger::info('Stored reshare activity.', ['guid' => $guid, 'id' => $message_id]);
if ($datarray['uid'] == 0) {
Item::distribute($message_id);
}
}
}
/** /**
* Processes a reshare message * Processes a reshare message
* *
@ -2436,11 +2377,6 @@ class Diaspora
self::sendParticipation($contact, $datarray); self::sendParticipation($contact, $datarray);
$root_message_id = self::messageExists($importer['uid'], $root_guid);
if ($root_message_id) {
self::addReshareActivity($datarray, $root_message_id, $guid, $author);
}
if ($message_id) { if ($message_id) {
Logger::info('Stored reshare ' . $datarray['guid'] . ' with message id ' . $message_id); Logger::info('Stored reshare ' . $datarray['guid'] . ' with message id ' . $message_id);
if ($datarray['uid'] == 0) { if ($datarray['uid'] == 0) {

View file

@ -55,7 +55,7 @@
use Friendica\Database\DBA; use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1501); define('DB_UPDATE_VERSION', 1502);
} }
return [ return [
@ -1458,7 +1458,7 @@ return [
"event-id" => ["type" => "int unsigned", "foreign" => ["event" => "id"], "comment" => "Used to link to the event.id"], "event-id" => ["type" => "int unsigned", "foreign" => ["event" => "id"], "comment" => "Used to link to the event.id"],
"unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "post has not been seen"], "unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "post has not been seen"],
"hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide the post from the user"], "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide the post from the user"],
"notification-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], "notification-type" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
"wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"], "wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"],
"origin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item originated at this site"], "origin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item originated at this site"],
"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"], "psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"],