From 818075d039330d89569ba189f1b415e3e9e6391d Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 21 Feb 2023 15:37:32 -0500 Subject: [PATCH 01/19] Implement time based paging for Mastodon Home Timeline Endpoint --- src/Module/Api/Mastodon/Timelines/Home.php | 35 ++++----- src/Module/BaseApi.php | 71 +++++++++++++++++-- src/Object/Api/Mastodon/Status.php | 15 ++++ .../Api/Mastodon/TimelineOrderByTypes.php | 32 +++++++++ 4 files changed, 131 insertions(+), 22 deletions(-) create mode 100644 src/Object/Api/Mastodon/TimelineOrderByTypes.php diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 5efaf4930e..379e175d45 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -29,6 +29,7 @@ use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\BaseApi; use Friendica\Network\HTTPException; +use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; /** * @see https://docs.joinmastodon.org/methods/timelines/ @@ -53,30 +54,21 @@ class Home extends BaseApi 'only_media' => false, // Show only statuses with media attached? Defaults to false. 'remote' => false, // Show only remote statuses? Defaults to false. 'exclude_replies' => false, // Don't show comments + 'min_time' => '', + 'max_time' => '', + 'friendica_order' => TimelineOrderByTypes::ID, ], $request); $params = ['order' => ['uri-id' => true], 'limit' => $request['limit']]; $condition = ['gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'uid' => $uid]; + $condition = $this->addPagingConditions($request, $condition); + if ($request['local']) { $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]); } - if (!empty($request['max_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); - } - - if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); - } - - if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); - - $params['order'] = ['uri-id']; - } - if ($request['only_media']) { $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))", Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]); @@ -96,9 +88,17 @@ class Home extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { - self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); + switch ($request['friendica_order']) { + case TimelineOrderByTypes::CREATED_AT: + self::setBoundaries($status->createdAtTimestamp()); + break; + case TimelineOrderByTypes::ID: + default: + self::setBoundaries($item['uri-id']); + } + $statuses[] = $status; } catch (\Throwable $th) { Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]); } @@ -109,7 +109,8 @@ class Home extends BaseApi $statuses = array_reverse($statuses); } - self::setLinkHeader(); + + self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID); System::jsonExit($statuses); } } diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index f8524b524e..1deb81735a 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -21,12 +21,14 @@ namespace Friendica\Module; +use DateTime; use Friendica\App; use Friendica\App\Router; use Friendica\BaseModule; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\System; +use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; @@ -35,6 +37,7 @@ use Friendica\Model\User; use Friendica\Module\Api\ApiResponse; use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Network\HTTPException; +use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; use Friendica\Security\BasicAuth; use Friendica\Security\OAuth; use Friendica\Util\DateTimeFormat; @@ -101,6 +104,53 @@ class BaseApi extends BaseModule return parent::run($httpException, $request); } + /** + * Processes data from GET requests and sets defaults + * + * @param array $defaults Associative array of expected request keys and their default typed value. A null + * value will remove the request key from the resulting value array. + * @param array $request Custom REQUEST array, superglobal instead + * @return array request data + * @throws \Exception + */ + public function addPagingConditions(array $request, array $condition): array + { + $requested_order = $request['friendica_order']; + if ($requested_order == TimelineOrderByTypes::ID) { + if (!empty($request['max_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); + } + + if (!empty($request['since_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); + } + + if (!empty($request['min_id'])) { + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); + } + } else { + switch ($requested_order) { + case TimelineOrderByTypes::CREATED_AT: + $order_field = 'created'; + break; + default: + $order_field = 'uri-id'; + } + if (!empty($request['max_time'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_time'], DateTimeFormat::MYSQL)]); + } + + + if (!empty($request['min_time'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['min_time'], DateTimeFormat::MYSQL)]); + + $params['order'] = [$order_field]; + } + } + + return $condition; + } + /** * Processes data from GET requests and sets defaults * @@ -143,7 +193,7 @@ class BaseApi extends BaseModule * Get the "link" header with "next" and "prev" links * @return string */ - protected static function getLinkHeader(): string + protected static function getLinkHeader(bool $asDate): string { if (empty(self::$boundaries)) { return ''; @@ -154,11 +204,22 @@ class BaseApi extends BaseModule unset($request['min_id']); unset($request['max_id']); unset($request['since_id']); + unset($request['min_time']); + unset($request['max_time']); $prev_request = $next_request = $request; - $prev_request['min_id'] = self::$boundaries['max']; - $next_request['max_id'] = self::$boundaries['min']; + if ($asDate) { + $max_date = new DateTime(); + $max_date->setTimestamp(self::$boundaries['max']); + $min_date = new DateTime(); + $min_date->setTimestamp(self::$boundaries['min']); + $prev_request['min_time'] = $max_date->format(DateTimeFormat::JSON); + $next_request['max_time'] = $min_date->format(DateTimeFormat::JSON); + } else { + $prev_request['min_id'] = self::$boundaries['max']; + $next_request['max_id'] = self::$boundaries['min']; + } $command = DI::baseUrl() . '/' . DI::args()->getCommand(); @@ -200,9 +261,9 @@ class BaseApi extends BaseModule * Set the "link" header with "next" and "prev" links * @return void */ - protected static function setLinkHeader() + protected static function setLinkHeader(bool $asDate = false) { - $header = self::getLinkHeader(); + $header = self::getLinkHeader($asDate); if (!empty($header)) { header($header); } diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index 30f554bdf5..3fbd009a6e 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -154,6 +154,21 @@ class Status extends BaseDataTransferObject $this->friendica = new FriendicaExtension($item['title'], $counts->dislikes); } + /** + * Returns the current created_at DateTime as an integer timestamp + * @return int + * @throws \Exception + */ + public function createdAtTimestamp(): int + { + $result = strtotime($this->created_at); + if (!$result) { + throw new \Exception('Unknown date-time format'); + } + + return $result; + } + /** * Returns the current entity as an array * diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php new file mode 100644 index 0000000000..a42d2c0605 --- /dev/null +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -0,0 +1,32 @@ +. + * + */ + + +namespace Friendica\Object\Api\Mastodon; + +/** + * Enumeration of order types that can be requested + */ +abstract class TimelineOrderByTypes +{ + const ID = 'id'; + const CREATED_AT = 'created_at'; +} From 9c5f3ae58f0d77fe3fe1f85f9e1f3f97209337be Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Wed, 22 Feb 2023 11:02:52 -0500 Subject: [PATCH 02/19] Simply Timeline Home temporal endpoint to reuse min_id and max_id --- src/Module/Api/Mastodon/Timelines/Home.php | 8 +++---- src/Module/BaseApi.php | 22 +++++++++---------- .../Api/Mastodon/TimelineOrderByTypes.php | 2 +- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 379e175d45..2d646e428a 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -45,17 +45,15 @@ class Home extends BaseApi $uid = self::getCurrentUserID(); $request = $this->getRequest([ - 'max_id' => 0, // Return results older than id + 'max_id' => '0', // Return results older than id 'since_id' => 0, // Return results newer than id - 'min_id' => 0, // Return results immediately newer than id + 'min_id' => '0', // Return results immediately newer than id 'limit' => 20, // Maximum number of results to return. Defaults to 20. 'local' => false, // Return only local statuses? 'with_muted' => false, // Pleroma extension: return activities by muted (not by blocked!) users. 'only_media' => false, // Show only statuses with media attached? Defaults to false. 'remote' => false, // Show only remote statuses? Defaults to false. 'exclude_replies' => false, // Don't show comments - 'min_time' => '', - 'max_time' => '', 'friendica_order' => TimelineOrderByTypes::ID, ], $request); @@ -91,7 +89,7 @@ class Home extends BaseApi try { $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); switch ($request['friendica_order']) { - case TimelineOrderByTypes::CREATED_AT: + case TimelineOrderByTypes::CREATED: self::setBoundaries($status->createdAtTimestamp()); break; case TimelineOrderByTypes::ID: diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 1deb81735a..bdcced59f4 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -118,31 +118,31 @@ class BaseApi extends BaseModule $requested_order = $request['friendica_order']; if ($requested_order == TimelineOrderByTypes::ID) { if (!empty($request['max_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); + $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", intval($request['max_id'])]); } if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", intval($request['since_id'])]); } if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", intval($request['min_id'])]); } } else { switch ($requested_order) { - case TimelineOrderByTypes::CREATED_AT: + case TimelineOrderByTypes::CREATED: $order_field = 'created'; break; default: $order_field = 'uri-id'; } - if (!empty($request['max_time'])) { - $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_time'], DateTimeFormat::MYSQL)]); + if (!empty($request['max_id'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_id'], DateTimeFormat::MYSQL)]); } - if (!empty($request['min_time'])) { - $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['min_time'], DateTimeFormat::MYSQL)]); + if (!empty($request['min_id'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['min_id'], DateTimeFormat::MYSQL)]); $params['order'] = [$order_field]; } @@ -204,8 +204,6 @@ class BaseApi extends BaseModule unset($request['min_id']); unset($request['max_id']); unset($request['since_id']); - unset($request['min_time']); - unset($request['max_time']); $prev_request = $next_request = $request; @@ -214,8 +212,8 @@ class BaseApi extends BaseModule $max_date->setTimestamp(self::$boundaries['max']); $min_date = new DateTime(); $min_date->setTimestamp(self::$boundaries['min']); - $prev_request['min_time'] = $max_date->format(DateTimeFormat::JSON); - $next_request['max_time'] = $min_date->format(DateTimeFormat::JSON); + $prev_request['min_id'] = $max_date->format(DateTimeFormat::JSON); + $next_request['max_id'] = $min_date->format(DateTimeFormat::JSON); } else { $prev_request['min_id'] = self::$boundaries['max']; $next_request['max_id'] = self::$boundaries['min']; diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php index a42d2c0605..196933c1fe 100644 --- a/src/Object/Api/Mastodon/TimelineOrderByTypes.php +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -28,5 +28,5 @@ namespace Friendica\Object\Api\Mastodon; abstract class TimelineOrderByTypes { const ID = 'id'; - const CREATED_AT = 'created_at'; + const CREATED = 'created'; } From a67fe069e88a497b9d07c6b147b1279f21c7dad5 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Wed, 22 Feb 2023 11:27:54 -0500 Subject: [PATCH 03/19] Add order/limit building func and fix reverse order requirement on min_id --- src/Module/Api/Mastodon/Timelines/Home.php | 3 +- src/Module/BaseApi.php | 40 +++++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 2d646e428a..f219f0a2c7 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -57,11 +57,12 @@ class Home extends BaseApi 'friendica_order' => TimelineOrderByTypes::ID, ], $request); - $params = ['order' => ['uri-id' => true], 'limit' => $request['limit']]; + $condition = ['gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'uid' => $uid]; $condition = $this->addPagingConditions($request, $condition); + $params = $this->buildOrderAndLimitParams($request); if ($request['local']) { $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]); diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index bdcced59f4..5f1156d771 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -105,12 +105,11 @@ class BaseApi extends BaseModule } /** - * Processes data from GET requests and sets defaults + * Processes data from GET requests and sets paging conditions * - * @param array $defaults Associative array of expected request keys and their default typed value. A null - * value will remove the request key from the resulting value array. - * @param array $request Custom REQUEST array, superglobal instead - * @return array request data + * @param array $request Custom REQUEST array + * @param array $condition Existing conditions to merge + * @return array paging data condition parameters data * @throws \Exception */ public function addPagingConditions(array $request, array $condition): array @@ -151,6 +150,37 @@ class BaseApi extends BaseModule return $condition; } + /** + * Processes data from GET requests and sets paging conditions + * + * @param array $request Custom REQUEST array + * @param array $params Existing $params element to build on + * @return array ordering data added to the params blocks that was passed in + * @throws \Exception + */ + public function buildOrderAndLimitParams(array $request, array $params = []): array + { + $requested_order = $request['friendica_order']; + switch ($requested_order) { + case TimelineOrderByTypes::CREATED: + $order_field = 'created'; + break; + case TimelineOrderByTypes::ID: + default: + $order_field = 'uri-id'; + } + + if(!empty($request['min_id'])) { + $params['order'] = [$order_field]; + } else { + $params['order'] = [$order_field => true]; + } + + $params['limit']= $request['limit']; + + return $params; + } + /** * Processes data from GET requests and sets defaults * From cbf55241a3fc74165a669f3097eac20a678e6750 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Thu, 23 Feb 2023 07:03:58 -0500 Subject: [PATCH 04/19] Null out min/max/since ID fields by default in Mastodon Timeline API --- src/Module/Api/Mastodon/Timelines/Home.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index f219f0a2c7..797b192407 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -45,9 +45,9 @@ class Home extends BaseApi $uid = self::getCurrentUserID(); $request = $this->getRequest([ - 'max_id' => '0', // Return results older than id - 'since_id' => 0, // Return results newer than id - 'min_id' => '0', // Return results immediately newer than id + 'max_id' => null, // Return results older than id + 'since_id' => null, // Return results newer than id + 'min_id' => null, // Return results immediately newer than id 'limit' => 20, // Maximum number of results to return. Defaults to 20. 'local' => false, // Return only local statuses? 'with_muted' => false, // Pleroma extension: return activities by muted (not by blocked!) users. From 9a6ebc94c48d1b42cb5db9a8349bfefc34d34c3f Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Thu, 23 Feb 2023 07:04:12 -0500 Subject: [PATCH 05/19] Add support for since_id field into the timeline API --- src/Module/BaseApi.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 5f1156d771..895cff2b82 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -139,11 +139,12 @@ class BaseApi extends BaseModule $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_id'], DateTimeFormat::MYSQL)]); } + if (!empty($request['since_id'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['since_id'], DateTimeFormat::MYSQL)]); + } if (!empty($request['min_id'])) { $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['min_id'], DateTimeFormat::MYSQL)]); - - $params['order'] = [$order_field]; } } From 1adb23d8fd115df594246dd58aed257a1c229f9f Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 08:20:08 -0500 Subject: [PATCH 06/19] Change timestamp to be DateTime object not integers in Mastodon Timeline API --- src/Module/BaseApi.php | 22 ++++++++++------------ src/Object/Api/Mastodon/Status.php | 6 +++--- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 895cff2b82..82f0ad0596 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -112,7 +112,7 @@ class BaseApi extends BaseModule * @return array paging data condition parameters data * @throws \Exception */ - public function addPagingConditions(array $request, array $condition): array + protected function addPagingConditions(array $request, array $condition): array { $requested_order = $request['friendica_order']; if ($requested_order == TimelineOrderByTypes::ID) { @@ -139,9 +139,9 @@ class BaseApi extends BaseModule $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_id'], DateTimeFormat::MYSQL)]); } - if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['since_id'], DateTimeFormat::MYSQL)]); - } + if (!empty($request['since_id'])) { + $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['since_id'], DateTimeFormat::MYSQL)]); + } if (!empty($request['min_id'])) { $condition = DBA::mergeConditions($condition, ["`$order_field` > ?", DateTimeFormat::convert($request['min_id'], DateTimeFormat::MYSQL)]); @@ -159,7 +159,7 @@ class BaseApi extends BaseModule * @return array ordering data added to the params blocks that was passed in * @throws \Exception */ - public function buildOrderAndLimitParams(array $request, array $params = []): array + protected function buildOrderAndLimitParams(array $request, array $params = []): array { $requested_order = $request['friendica_order']; switch ($requested_order) { @@ -171,7 +171,7 @@ class BaseApi extends BaseModule $order_field = 'uri-id'; } - if(!empty($request['min_id'])) { + if (!empty($request['min_id'])) { $params['order'] = [$order_field]; } else { $params['order'] = [$order_field => true]; @@ -204,9 +204,9 @@ class BaseApi extends BaseModule /** * Set boundaries for the "link" header * @param array $boundaries - * @param int $id + * @param int|\DateTime $id */ - protected static function setBoundaries(int $id) + protected static function setBoundaries($id) { if (!isset(self::$boundaries['min'])) { self::$boundaries['min'] = $id; @@ -239,10 +239,8 @@ class BaseApi extends BaseModule $prev_request = $next_request = $request; if ($asDate) { - $max_date = new DateTime(); - $max_date->setTimestamp(self::$boundaries['max']); - $min_date = new DateTime(); - $min_date->setTimestamp(self::$boundaries['min']); + $max_date = self::$boundaries['max']; + $min_date = self::$boundaries['min']; $prev_request['min_id'] = $max_date->format(DateTimeFormat::JSON); $next_request['max_id'] = $min_date->format(DateTimeFormat::JSON); } else { diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index cefae5d212..e157d44d03 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -156,12 +156,12 @@ class Status extends BaseDataTransferObject /** * Returns the current created_at DateTime as an integer timestamp - * @return int + * @return \DateTime * @throws \Exception */ - public function createdAtTimestamp(): int + public function createdAtTimestamp(): \DateTime { - $result = strtotime($this->created_at); + $result = new \DateTime($this->created_at); if (!$result) { throw new \Exception('Unknown date-time format'); } From 1f6578229e55689138c0a59b08dc2a5d2774233f Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 10:36:11 -0500 Subject: [PATCH 07/19] Add additional Mastodon timeline sorting date types and data to output --- src/Factory/Api/Mastodon/Status.php | 6 +- src/Module/Api/Mastodon/Timelines/Home.php | 9 +-- src/Module/BaseApi.php | 59 ++++++++++++++++--- src/Object/Api/Mastodon/Status.php | 27 +++++---- .../Mastodon/Status/FriendicaExtension.php | 56 +++++++++++++++++- .../Api/Mastodon/TimelineOrderByTypes.php | 14 ++++- 6 files changed, 138 insertions(+), 33 deletions(-) diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 19f3866cd4..642c9efe83 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -99,7 +99,7 @@ class Status extends BaseFactory public function createFromUriId(int $uriId, int $uid = 0, bool $display_quote = false, bool $reblog = true, bool $in_reply_status = true): \Friendica\Object\Api\Mastodon\Status { $fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id', - 'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id', + 'created', 'edited', 'commented', 'received', 'changed', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id', 'delivery_queue_count', 'delivery_queue_done','delivery_queue_failed']; $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]); if (!$item) { @@ -289,7 +289,7 @@ class Status extends BaseFactory } $delivery_data = new FriendicaDeliveryData($item['delivery_queue_count'], $item['delivery_queue_done'], $item['delivery_queue_failed']); - $friendica = new FriendicaExtension($item['title'], $counts->dislikes, $delivery_data); + $friendica = new FriendicaExtension($item['title'], $item['changed'], $item['commented'], $item['edited'], $item['received'], $counts->dislikes, $delivery_data); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll); } @@ -355,7 +355,7 @@ class Status extends BaseFactory $attachments = []; $in_reply = []; $reshare = []; - $friendica = new FriendicaExtension('', 0, new FriendicaDeliveryData(0, 0, 0)); + $friendica = new FriendicaExtension('', null, null, null, null, 0, new FriendicaDeliveryData(0, 0, 0)); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica); } diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 797b192407..53ab3d629b 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -89,14 +89,7 @@ class Home extends BaseApi while ($item = Post::fetch($items)) { try { $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); - switch ($request['friendica_order']) { - case TimelineOrderByTypes::CREATED: - self::setBoundaries($status->createdAtTimestamp()); - break; - case TimelineOrderByTypes::ID: - default: - self::setBoundaries($item['uri-id']); - } + $this->updateBoundaries($status, $item, $request['friendica_order']); $statuses[] = $status; } catch (\Throwable $th) { Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]); diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 82f0ad0596..4c16ab20e2 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -37,6 +37,7 @@ use Friendica\Model\User; use Friendica\Module\Api\ApiResponse; use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Network\HTTPException; +use Friendica\Object\Api\Mastodon\Status; use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; use Friendica\Security\BasicAuth; use Friendica\Security\OAuth; @@ -128,13 +129,7 @@ class BaseApi extends BaseModule $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", intval($request['min_id'])]); } } else { - switch ($requested_order) { - case TimelineOrderByTypes::CREATED: - $order_field = 'created'; - break; - default: - $order_field = 'uri-id'; - } + $order_field = $requested_order; if (!empty($request['max_id'])) { $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_id'], DateTimeFormat::MYSQL)]); } @@ -163,9 +158,21 @@ class BaseApi extends BaseModule { $requested_order = $request['friendica_order']; switch ($requested_order) { + case TimelineOrderByTypes::CHANGED: + $order_field = 'changed'; + break; case TimelineOrderByTypes::CREATED: $order_field = 'created'; break; + case TimelineOrderByTypes::COMMENTED: + $order_field = 'commented'; + break; + case TimelineOrderByTypes::EDITED: + $order_field = 'edited'; + break; + case TimelineOrderByTypes::RECEIVED: + $order_field = 'received'; + break; case TimelineOrderByTypes::ID: default: $order_field = 'uri-id'; @@ -182,6 +189,44 @@ class BaseApi extends BaseModule return $params; } + protected function updateBoundaries(Status $status, array $post_item, string $order) { + ; + try { + switch ($order) { + case TimelineOrderByTypes::CHANGED: + if (!empty($status->friendicaExtension()->changedAt())){ + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->changedAt(), DateTimeFormat::JSON))); + } + break; + case TimelineOrderByTypes::CREATED: + if (!empty($status->createdAt())){ + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->createdAt(), DateTimeFormat::JSON))); + } + break; + case TimelineOrderByTypes::COMMENTED: + if (!empty($status->friendicaExtension()->commentedAt())){ + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->commentedAt(), DateTimeFormat::JSON))); + } + break; + case TimelineOrderByTypes::EDITED: + if (!empty($status->friendicaExtension()->editedAt())){ + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->editedAt(), DateTimeFormat::JSON))); + } + break; + case TimelineOrderByTypes::RECEIVED: + if (!empty($status->friendicaExtension()->receivedAt())){ + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->receivedAt(), DateTimeFormat::JSON))); + } + break; + case TimelineOrderByTypes::ID: + default: + self::setBoundaries($post_item['uri-id']); + } + } catch (\Exception $e) { + Logger::debug('Error processing page boundary calculation, skipping', ['error' => $e]); + } + } + /** * Processes data from GET requests and sets defaults * diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index e157d44d03..5563981f13 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -38,7 +38,7 @@ class Status extends BaseDataTransferObject { /** @var string */ protected $id; - /** @var string (Datetime) */ + /** @var string|null (Datetime) */ protected $created_at; /** @var string|null */ protected $in_reply_to_id = null; @@ -107,8 +107,8 @@ class Status extends BaseDataTransferObject */ public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card, array $attachments, array $in_reply, array $reblog, FriendicaExtension $friendica, array $quote = null, array $poll = null) { - $this->id = (string)$item['uri-id']; - $this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::JSON); + $this->id = (string)$item['uri-id']; + $this->created_at = $item['created']; if ($item['gravity'] == Item::GRAVITY_COMMENT) { $this->in_reply_to_id = (string)$item['thr-parent-id']; @@ -155,18 +155,21 @@ class Status extends BaseDataTransferObject } /** - * Returns the current created_at DateTime as an integer timestamp - * @return \DateTime - * @throws \Exception + * Returns the current created_at string or null if not set + * @return \DateTime|null */ - public function createdAtTimestamp(): \DateTime + public function createdAt(): ?string { - $result = new \DateTime($this->created_at); - if (!$result) { - throw new \Exception('Unknown date-time format'); - } + return $this->created_at; + } - return $result; + /** + * Returns the Friendica Extension properties + * @return FriendicaExtension + */ + public function friendicaExtension(): FriendicaExtension + { + return $this->friendica; } /** diff --git a/src/Object/Api/Mastodon/Status/FriendicaExtension.php b/src/Object/Api/Mastodon/Status/FriendicaExtension.php index b2853894ad..3e39c2baa6 100644 --- a/src/Object/Api/Mastodon/Status/FriendicaExtension.php +++ b/src/Object/Api/Mastodon/Status/FriendicaExtension.php @@ -35,6 +35,18 @@ class FriendicaExtension extends BaseDataTransferObject /** @var string */ protected $title; + /** @var string|null (Datetime) */ + protected $changed_at; + + /** @var string|null (Datetime) */ + protected $commented_at; + + /** @var string|null (Datetime) */ + protected $edited_at; + + /** @var string|null (Datetime) */ + protected $received_at; + /** @var FriendicaDeliveryData */ protected $delivery_data; /** @var int */ @@ -47,10 +59,52 @@ class FriendicaExtension extends BaseDataTransferObject * @param int $dislikes_count * @param FriendicaDeliveryData $delivery_data */ - public function __construct(string $title, int $dislikes_count, FriendicaDeliveryData $delivery_data) + public function __construct(string $title, ?string $changed_at, ?string $commented_at, ?string $edited_at, ?string $received_at, + int $dislikes_count, FriendicaDeliveryData $delivery_data) { $this->title = $title; + $this->changed_at = $changed_at; + $this->commented_at = $commented_at; + $this->edited_at = $edited_at; + $this->received_at = $received_at; $this->delivery_data = $delivery_data; $this->dislikes_count = $dislikes_count; } + + /** + * Returns the current changed_at string or null if not set + * @return ?string + */ + public function changedAt(): ?string + { + return $this->changed_at; + } + + /** + * Returns the current commented_at string or null if not set + * @return ?string + */ + public function commentedAt(): ?string + { + return $this->commented_at; + } + + /** + * Returns the current edited_at string or null if not set + * @return ?string + */ + public function editedAt(): ?string + { + return $this->edited_at; + } + + /** + * Returns the current received_at string or null if not set + * @return ?string + */ + public function receivedAt(): ?string + { + return $this->received_at; + } + } diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php index 196933c1fe..04fb7cc3b8 100644 --- a/src/Object/Api/Mastodon/TimelineOrderByTypes.php +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -27,6 +27,16 @@ namespace Friendica\Object\Api\Mastodon; */ abstract class TimelineOrderByTypes { - const ID = 'id'; - const CREATED = 'created'; + const CHANGED = 'changed'; + + const CREATED = 'created'; + + const COMMENTED = 'commented'; + + const EDITED = 'edited'; + + const ID = 'id'; + + const RECEIVED = 'received'; + } From 099ef1118385d36d6fa6b0482a7aad15d835a3fe Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:35:21 -0500 Subject: [PATCH 08/19] Fix tag filtering on block/mute list --- src/Module/Api/Mastodon/Timelines/Tag.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Api/Mastodon/Timelines/Tag.php b/src/Module/Api/Mastodon/Timelines/Tag.php index 476c9c2b94..4943b51b07 100644 --- a/src/Module/Api/Mastodon/Timelines/Tag.php +++ b/src/Module/Api/Mastodon/Timelines/Tag.php @@ -107,7 +107,7 @@ class Tag extends BaseApi if (!empty($uid)) { $condition = DBA::mergeConditions( $condition, - ["NOT `parent-author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored`) AND `cid` = `parent-author-id`)", $uid] + ["NOT `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`blocked` OR `ignored`) AND `cid` = `author-id`)", $uid] ); } From 4f86cd09231917be718a6baee1ae6242c8b67ddc Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:35:54 -0500 Subject: [PATCH 09/19] Add comment to friendica_order query parameter in Mastodon Timeline Home --- src/Module/Api/Mastodon/Timelines/Home.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 53ab3d629b..6d0b7a3340 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -54,11 +54,9 @@ class Home extends BaseApi 'only_media' => false, // Show only statuses with media attached? Defaults to false. 'remote' => false, // Show only remote statuses? Defaults to false. 'exclude_replies' => false, // Don't show comments - 'friendica_order' => TimelineOrderByTypes::ID, + 'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID) ], $request); - - $condition = ['gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'uid' => $uid]; $condition = $this->addPagingConditions($request, $condition); From c33c624f83839477e918b8336f23bc65b2a71da4 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:36:24 -0500 Subject: [PATCH 10/19] Add new temporal paging to Mastodon Timeline Public and List endpoints --- .../Api/Mastodon/Timelines/ListTimeline.php | 34 ++++++----------- .../Api/Mastodon/Timelines/PublicTimeline.php | 38 ++++++++----------- 2 files changed, 27 insertions(+), 45 deletions(-) diff --git a/src/Module/Api/Mastodon/Timelines/ListTimeline.php b/src/Module/Api/Mastodon/Timelines/ListTimeline.php index 1e9a5810c1..eeffeba885 100644 --- a/src/Module/Api/Mastodon/Timelines/ListTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/ListTimeline.php @@ -29,6 +29,7 @@ use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\BaseApi; use Friendica\Network\HTTPException; +use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; /** * @see https://docs.joinmastodon.org/methods/timelines/ @@ -48,35 +49,23 @@ class ListTimeline extends BaseApi } $request = $this->getRequest([ - 'max_id' => 0, // Return results older than id - 'since_id' => 0, // Return results newer than id - 'min_id' => 0, // Return results immediately newer than id + 'max_id' => null, // Return results older than id + 'since_id' => null, // Return results newer than id + 'min_id' => null, // Return results immediately newer than id 'limit' => 20, // Maximum number of results to return. Defaults to 20.Return results older than this ID. 'with_muted' => false, // Pleroma extension: return activities by muted (not by blocked!) users. 'only_media' => false, // Show only statuses with media attached? Defaults to false. 'local' => false, // Show only local statuses? Defaults to false. 'remote' => false, // Show only remote statuses? Defaults to false. 'exclude_replies' => false, // Don't show comments + 'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID) ], $request); - $params = ['order' => ['uri-id' => true], 'limit' => $request['limit']]; - $condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", $uid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, $this->parameters['id']]; - if (!empty($request['max_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); - } - - if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); - } - - if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); - - $params['order'] = ['uri-id']; - } + $condition = $this->addPagingConditions($request, $condition); + $params = $this->buildOrderAndLimitParams($request); if ($request['only_media']) { $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))", @@ -101,11 +90,12 @@ class ListTimeline extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { - self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); - } catch (\Exception $exception) { - Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); + $this->updateBoundaries($status, $item, $request['friendica_order']); + $statuses[] = $status; + } catch (\Throwable $th) { + Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]); } } DBA::close($items); diff --git a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php index 31750d4a5c..1f58a6a69f 100644 --- a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php @@ -30,6 +30,7 @@ use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\BaseApi; use Friendica\Network\HTTPException; +use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; /** * @see https://docs.joinmastodon.org/methods/timelines/ @@ -44,22 +45,24 @@ class PublicTimeline extends BaseApi $uid = self::getCurrentUserID(); $request = $this->getRequest([ + 'max_id' => null, // Return results older than id + 'since_id' => null, // Return results newer than id + 'min_id' => null, // Return results immediately newer than id + 'limit' => 20, // Maximum number of results to return. Defaults to 20. 'local' => false, // Show only local statuses? Defaults to false. 'remote' => false, // Show only remote statuses? Defaults to false. 'only_media' => false, // Show only statuses with media attached? Defaults to false. - 'max_id' => 0, // Return results older than this id - 'since_id' => 0, // Return results newer than this id - 'min_id' => 0, // Return results immediately newer than this id - 'limit' => 20, // Maximum number of results to return. Defaults to 20. 'with_muted' => false, // Pleroma extension: return activities by muted (not by blocked!) users. 'exclude_replies' => false, // Don't show comments + 'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID) ], $request); - $params = ['order' => ['uri-id' => true], 'limit' => $request['limit']]; - $condition = ['gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'private' => Item::PUBLIC, 'network' => Protocol::FEDERATED, 'author-blocked' => false, 'author-hidden' => false]; + $condition = $this->addPagingConditions($request, $condition); + $params = $this->buildOrderAndLimitParams($request); + if ($request['local']) { $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)"]); } @@ -73,19 +76,6 @@ class PublicTimeline extends BaseApi Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]); } - if (!empty($request['max_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $request['max_id']]); - } - - if (!empty($request['since_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['since_id']]); - } - - if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); - $params['order'] = ['uri-id']; - } - if ($request['exclude_replies']) { $condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]); } @@ -96,13 +86,15 @@ class PublicTimeline extends BaseApi $statuses = []; while ($item = Post::fetch($items)) { - self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); - } catch (\Exception $exception) { - Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); + $this->updateBoundaries($status, $item, $request['friendica_order']); + $statuses[] = $status; + } catch (\Throwable $th) { + Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]); } } + DBA::close($items); if (!empty($request['min_id'])) { From 32bb0976046ef9fa2296d0aeb39a01b0b916dc1e Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:41:53 -0500 Subject: [PATCH 11/19] Correct format/style errors --- src/Module/BaseApi.php | 23 +++++++++++++------ .../Mastodon/Status/FriendicaExtension.php | 18 +++++++++++---- .../Api/Mastodon/TimelineOrderByTypes.php | 1 - 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 4c16ab20e2..8b79aff491 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -189,32 +189,41 @@ class BaseApi extends BaseModule return $params; } - protected function updateBoundaries(Status $status, array $post_item, string $order) { - ; + /** + * Update the ID/time boundaries for this result set. Used for building Link Headers + * + * @param Status $status + * @param array $post_item + * @param string $order + * @return void + * @throws \Exception + */ + protected function updateBoundaries(Status $status, array $post_item, string $order) + { try { switch ($order) { case TimelineOrderByTypes::CHANGED: - if (!empty($status->friendicaExtension()->changedAt())){ + if (!empty($status->friendicaExtension()->changedAt())) { self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->changedAt(), DateTimeFormat::JSON))); } break; case TimelineOrderByTypes::CREATED: - if (!empty($status->createdAt())){ + if (!empty($status->createdAt())) { self::setBoundaries(new DateTime(DateTimeFormat::utc($status->createdAt(), DateTimeFormat::JSON))); } break; case TimelineOrderByTypes::COMMENTED: - if (!empty($status->friendicaExtension()->commentedAt())){ + if (!empty($status->friendicaExtension()->commentedAt())) { self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->commentedAt(), DateTimeFormat::JSON))); } break; case TimelineOrderByTypes::EDITED: - if (!empty($status->friendicaExtension()->editedAt())){ + if (!empty($status->friendicaExtension()->editedAt())) { self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->editedAt(), DateTimeFormat::JSON))); } break; case TimelineOrderByTypes::RECEIVED: - if (!empty($status->friendicaExtension()->receivedAt())){ + if (!empty($status->friendicaExtension()->receivedAt())) { self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->receivedAt(), DateTimeFormat::JSON))); } break; diff --git a/src/Object/Api/Mastodon/Status/FriendicaExtension.php b/src/Object/Api/Mastodon/Status/FriendicaExtension.php index 3e39c2baa6..b6dcd5df0c 100644 --- a/src/Object/Api/Mastodon/Status/FriendicaExtension.php +++ b/src/Object/Api/Mastodon/Status/FriendicaExtension.php @@ -52,16 +52,27 @@ class FriendicaExtension extends BaseDataTransferObject /** @var int */ protected $dislikes_count; + /** * Creates a status count object * * @param string $title + * @param string|null $changed_at + * @param string|null $commented_at + * @param string|null $edited_at + * @param string|null $received_at * @param int $dislikes_count * @param FriendicaDeliveryData $delivery_data */ - public function __construct(string $title, ?string $changed_at, ?string $commented_at, ?string $edited_at, ?string $received_at, - int $dislikes_count, FriendicaDeliveryData $delivery_data) - { + public function __construct( + string $title, + ?string $changed_at, + ?string $commented_at, + ?string $edited_at, + ?string $received_at, + int $dislikes_count, + FriendicaDeliveryData $delivery_data + ) { $this->title = $title; $this->changed_at = $changed_at; $this->commented_at = $commented_at; @@ -106,5 +117,4 @@ class FriendicaExtension extends BaseDataTransferObject { return $this->received_at; } - } diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php index 04fb7cc3b8..d50f389bf0 100644 --- a/src/Object/Api/Mastodon/TimelineOrderByTypes.php +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -38,5 +38,4 @@ abstract class TimelineOrderByTypes const ID = 'id'; const RECEIVED = 'received'; - } From 260f4e46abb1a70f008428e598be01e9025cf6e5 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:43:12 -0500 Subject: [PATCH 12/19] Correct format/style errors --- src/Module/BaseApi.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 8b79aff491..3b940e3ae3 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -184,7 +184,7 @@ class BaseApi extends BaseModule $params['order'] = [$order_field => true]; } - $params['limit']= $request['limit']; + $params['limit'] = $request['limit']; return $params; } From fae67b6d88ad20248b61340c74add0016784bd22 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 11:44:37 -0500 Subject: [PATCH 13/19] Remove extra line between copyright and namespace --- src/Object/Api/Mastodon/TimelineOrderByTypes.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php index d50f389bf0..9ab5a3ccf9 100644 --- a/src/Object/Api/Mastodon/TimelineOrderByTypes.php +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -19,7 +19,6 @@ * */ - namespace Friendica\Object\Api\Mastodon; /** From f0b3864c7a0a245a65b78afb6c3a38613d1f88aa Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 12:48:25 -0500 Subject: [PATCH 14/19] Add default getLinkHeader asDate to be false (the previous ID-only way) --- src/Module/BaseApi.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 3b940e3ae3..bfcb95eb2e 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -278,7 +278,7 @@ class BaseApi extends BaseModule * Get the "link" header with "next" and "prev" links * @return string */ - protected static function getLinkHeader(bool $asDate): string + protected static function getLinkHeader(bool $asDate = false): string { if (empty(self::$boundaries)) { return ''; From 13672bccf4022edfdfdbef8ff490345e8e73c3d8 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 13:10:45 -0500 Subject: [PATCH 15/19] Only allow explicitly known order types through --- src/Module/BaseApi.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index bfcb95eb2e..2be5c246a2 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -129,7 +129,18 @@ class BaseApi extends BaseModule $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", intval($request['min_id'])]); } } else { - $order_field = $requested_order; + switch ($requested_order) { + case TimelineOrderByTypes::RECEIVED: + case TimelineOrderByTypes::CHANGED: + case TimelineOrderByTypes::EDITED: + case TimelineOrderByTypes::CREATED: + case TimelineOrderByTypes::COMMENTED: + $order_field = $requested_order; + break; + default: + throw new \Exception("Unrecognized request order: $requested_order"); + } + if (!empty($request['max_id'])) { $condition = DBA::mergeConditions($condition, ["`$order_field` < ?", DateTimeFormat::convert($request['max_id'], DateTimeFormat::MYSQL)]); } From 80896936b22cdcb061f7d4f6137517e37c047c77 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 13:12:11 -0500 Subject: [PATCH 16/19] Remove extra lines from TimelineOrderByTypes.php to get rid of server side only formatting error --- src/Object/Api/Mastodon/TimelineOrderByTypes.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Object/Api/Mastodon/TimelineOrderByTypes.php b/src/Object/Api/Mastodon/TimelineOrderByTypes.php index 9ab5a3ccf9..a58f892733 100644 --- a/src/Object/Api/Mastodon/TimelineOrderByTypes.php +++ b/src/Object/Api/Mastodon/TimelineOrderByTypes.php @@ -27,14 +27,9 @@ namespace Friendica\Object\Api\Mastodon; abstract class TimelineOrderByTypes { const CHANGED = 'changed'; - const CREATED = 'created'; - const COMMENTED = 'commented'; - const EDITED = 'edited'; - const ID = 'id'; - const RECEIVED = 'received'; } From c7457c7a72d77152fcf378661e153dd51ae5abf5 Mon Sep 17 00:00:00 2001 From: Hank G Date: Tue, 28 Feb 2023 13:49:23 -0500 Subject: [PATCH 17/19] Update src/Module/BaseApi.php Co-authored-by: Hypolite Petovan --- src/Module/BaseApi.php | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 2be5c246a2..10e64044f3 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -170,19 +170,11 @@ class BaseApi extends BaseModule $requested_order = $request['friendica_order']; switch ($requested_order) { case TimelineOrderByTypes::CHANGED: - $order_field = 'changed'; - break; case TimelineOrderByTypes::CREATED: - $order_field = 'created'; - break; case TimelineOrderByTypes::COMMENTED: - $order_field = 'commented'; - break; case TimelineOrderByTypes::EDITED: - $order_field = 'edited'; - break; case TimelineOrderByTypes::RECEIVED: - $order_field = 'received'; + $order_field = $requested_order; break; case TimelineOrderByTypes::ID: default: From 1e4897866435c803ff6e6bbdbe456d8d3038ce98 Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 15:02:02 -0500 Subject: [PATCH 18/19] Use Mastodon standard edited_at field for tracking edit time --- src/Factory/Api/Mastodon/Status.php | 4 ++-- src/Module/BaseApi.php | 4 ++-- src/Object/Api/Mastodon/Status.php | 12 ++++++++++++ .../Api/Mastodon/Status/FriendicaExtension.php | 14 -------------- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 642c9efe83..9c1bce0268 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -289,7 +289,7 @@ class Status extends BaseFactory } $delivery_data = new FriendicaDeliveryData($item['delivery_queue_count'], $item['delivery_queue_done'], $item['delivery_queue_failed']); - $friendica = new FriendicaExtension($item['title'], $item['changed'], $item['commented'], $item['edited'], $item['received'], $counts->dislikes, $delivery_data); + $friendica = new FriendicaExtension($item['title'], $item['commented'], $item['edited'], $item['received'], $counts->dislikes, $delivery_data); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll); } @@ -355,7 +355,7 @@ class Status extends BaseFactory $attachments = []; $in_reply = []; $reshare = []; - $friendica = new FriendicaExtension('', null, null, null, null, 0, new FriendicaDeliveryData(0, 0, 0)); + $friendica = new FriendicaExtension('', null, null, null, 0, new FriendicaDeliveryData(0, 0, 0)); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica); } diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 10e64044f3..b5fc8c8499 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -221,8 +221,8 @@ class BaseApi extends BaseModule } break; case TimelineOrderByTypes::EDITED: - if (!empty($status->friendicaExtension()->editedAt())) { - self::setBoundaries(new DateTime(DateTimeFormat::utc($status->friendicaExtension()->editedAt(), DateTimeFormat::JSON))); + if (!empty($status->editedAt())) { + self::setBoundaries(new DateTime(DateTimeFormat::utc($status->editedAt(), DateTimeFormat::JSON))); } break; case TimelineOrderByTypes::RECEIVED: diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index 5563981f13..1ce996c5d8 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -40,6 +40,8 @@ class Status extends BaseDataTransferObject protected $id; /** @var string|null (Datetime) */ protected $created_at; + /** @var string|null (Datetime) */ + protected $edited_at; /** @var string|null */ protected $in_reply_to_id = null; /** @var Status|null - Fedilab extension, see issue https://github.com/friendica/friendica/issues/12672 */ @@ -109,6 +111,7 @@ class Status extends BaseDataTransferObject { $this->id = (string)$item['uri-id']; $this->created_at = $item['created']; + $this->edited_at = $item['edited']; if ($item['gravity'] == Item::GRAVITY_COMMENT) { $this->in_reply_to_id = (string)$item['thr-parent-id']; @@ -163,6 +166,15 @@ class Status extends BaseDataTransferObject return $this->created_at; } + /** + * Returns the current edited_at string or null if not set + * @return ?string + */ + public function editedAt(): ?string + { + return $this->edited_at; + } + /** * Returns the Friendica Extension properties * @return FriendicaExtension diff --git a/src/Object/Api/Mastodon/Status/FriendicaExtension.php b/src/Object/Api/Mastodon/Status/FriendicaExtension.php index b6dcd5df0c..8e68f3014e 100644 --- a/src/Object/Api/Mastodon/Status/FriendicaExtension.php +++ b/src/Object/Api/Mastodon/Status/FriendicaExtension.php @@ -41,9 +41,6 @@ class FriendicaExtension extends BaseDataTransferObject /** @var string|null (Datetime) */ protected $commented_at; - /** @var string|null (Datetime) */ - protected $edited_at; - /** @var string|null (Datetime) */ protected $received_at; @@ -68,7 +65,6 @@ class FriendicaExtension extends BaseDataTransferObject string $title, ?string $changed_at, ?string $commented_at, - ?string $edited_at, ?string $received_at, int $dislikes_count, FriendicaDeliveryData $delivery_data @@ -76,7 +72,6 @@ class FriendicaExtension extends BaseDataTransferObject $this->title = $title; $this->changed_at = $changed_at; $this->commented_at = $commented_at; - $this->edited_at = $edited_at; $this->received_at = $received_at; $this->delivery_data = $delivery_data; $this->dislikes_count = $dislikes_count; @@ -100,15 +95,6 @@ class FriendicaExtension extends BaseDataTransferObject return $this->commented_at; } - /** - * Returns the current edited_at string or null if not set - * @return ?string - */ - public function editedAt(): ?string - { - return $this->edited_at; - } - /** * Returns the current received_at string or null if not set * @return ?string From 63a65b6d02fe8a5bada485c5d80044a8ec5fdc6b Mon Sep 17 00:00:00 2001 From: Hank Grabowski Date: Tue, 28 Feb 2023 16:10:13 -0500 Subject: [PATCH 19/19] Fix passing in correct change fields to Mastodon Status FriendicaExtension --- src/Factory/Api/Mastodon/Status.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 9c1bce0268..d794247cb1 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -289,7 +289,7 @@ class Status extends BaseFactory } $delivery_data = new FriendicaDeliveryData($item['delivery_queue_count'], $item['delivery_queue_done'], $item['delivery_queue_failed']); - $friendica = new FriendicaExtension($item['title'], $item['commented'], $item['edited'], $item['received'], $counts->dislikes, $delivery_data); + $friendica = new FriendicaExtension($item['title'], $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $delivery_data); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll); }