From 0ebc496d81b41326d5e7574e1f70e0e5f3635c3b Mon Sep 17 00:00:00 2001 From: Maxence Lange Date: Sat, 3 Dec 2022 15:55:34 -0100 Subject: [PATCH] favourites and notifications Signed-off-by: Maxence Lange --- appinfo/routes.php | 3 +- lib/AppInfo/Application.php | 9 -- lib/Command/ExtendedBase.php | 1 + lib/Command/Timeline.php | 53 ++----- lib/Controller/ActivityPubController.php | 2 +- lib/Controller/ApiController.php | 144 ++++++++++++++++--- lib/Controller/OAuthController.php | 1 - lib/Db/ActorsRequestBuilder.php | 7 +- lib/Db/CoreRequestBuilder.php | 14 +- lib/Db/HashtagsRequestBuilder.php | 7 +- lib/Db/RequestQueueRequestBuilder.php | 7 +- lib/Db/SocialLimitsQueryBuilder.php | 6 +- lib/Db/StreamActionsRequestBuilder.php | 7 +- lib/Db/StreamQueueRequestBuilder.php | 7 +- lib/Db/StreamRequest.php | 85 ++++++++++- lib/Model/ActivityPub/ACore.php | 26 +++- lib/Model/ActivityPub/Stream.php | 40 +++++- lib/Model/Client/Options/TimelineOptions.php | 116 +++++++++++++-- lib/Service/CurlService.php | 1 - lib/Service/StreamService.php | 2 +- tests/psalm-baseline.xml | 10 ++ 21 files changed, 415 insertions(+), 133 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index bec44f7a..b029b16c 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -79,8 +79,9 @@ return [ ['name' => 'Api#instance', 'url' => '/api/v1/instance/', 'verb' => 'GET'], ['name' => 'Api#customEmojis', 'url' => '/api/v1/custom_emojis', 'verb' => 'GET'], ['name' => 'Api#savedSearches', 'url' => '/api/saved_searches/list.json', 'verb' => 'GET'], - ['name' => 'Api#timelines', 'url' => '/api/v1/timelines/{timeline}/', 'verb' => 'GET'], + ['name' => 'Api#favourites', 'url' => '/api/v1/favourites/', 'verb' => 'GET'], ['name' => 'Api#notifications', 'url' => '/api/v1/notifications', 'verb' => 'GET'], + ['name' => 'Api#tag', 'url' => '/api/v1/timelines/tag/{hashtag}', 'verb' => 'GET'], ['name' => 'Api#statusNew', 'url' => '/api/v1/statuses', 'verb' => 'POST'], // Api for local front-end diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index e2a70004..7ef42a73 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,11 +31,8 @@ declare(strict_types=1); namespace OCA\Social\AppInfo; -use Closure; use OCA\Social\Notification\Notifier; use OCA\Social\Search\UnifiedSearchProvider; -use OCA\Social\Service\ConfigService; -use OCA\Social\Service\UpdateService; use OCA\Social\WellKnown\WebfingerHandler; use OCA\Social\Listeners\ProfileSectionListener; use OCA\Social\Dashboard\SocialWidget; @@ -43,13 +40,7 @@ use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; -use OCP\AppFramework\QueryException; use OCP\Profile\BeforeTemplateRenderedEvent; -use OCP\IDBConnection; -use OCP\IServerContainer; -use OC\DB\SchemaWrapper; -use OCP\DB\ISchemaWrapper; -use Throwable; require_once __DIR__ . '/../../vendor/autoload.php'; diff --git a/lib/Command/ExtendedBase.php b/lib/Command/ExtendedBase.php index feca6ff9..0755beea 100644 --- a/lib/Command/ExtendedBase.php +++ b/lib/Command/ExtendedBase.php @@ -62,6 +62,7 @@ class ExtendedBase extends Base { protected function outputStreams(array $streams) { if ($this->asJson) { $this->output->writeln(json_encode($streams, JSON_PRETTY_PRINT)); + return; } $table = new Table($this->output); diff --git a/lib/Command/Timeline.php b/lib/Command/Timeline.php index 433118c2..6ab9988d 100644 --- a/lib/Command/Timeline.php +++ b/lib/Command/Timeline.php @@ -33,12 +33,10 @@ namespace OCA\Social\Command; use Exception; use OCA\Social\Db\StreamRequest; -use OCA\Social\Exceptions\UnknownTimelineException; use OCA\Social\Model\ActivityPub\Stream; use OCA\Social\Model\Client\Options\TimelineOptions; use OCA\Social\Service\AccountService; use OCA\Social\Service\ConfigService; -use OCA\Social\Tools\Exceptions\DateTimeException; use OCP\IUserManager; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -88,15 +86,15 @@ class Timeline extends ExtendedBase { */ protected function configure() { parent::configure(); - $this->setName('social:stream') + $this->setName('social:timeline') ->addArgument('userId', InputArgument::REQUIRED, 'viewer') ->addArgument('timeline', InputArgument::REQUIRED, 'timeline') ->addOption('local', '', InputOption::VALUE_NONE, 'public') - ->addOption('count', '', InputOption::VALUE_REQUIRED, 'number of elements', '5') ->addOption('min_id', '', InputOption::VALUE_REQUIRED, 'min_id', 0) ->addOption('max_id', '', InputOption::VALUE_REQUIRED, 'max_id', 0) + ->addOption('since', '', InputOption::VALUE_REQUIRED, 'since', 0) + ->addOption('limit', '', InputOption::VALUE_REQUIRED, 'limit', 5) ->addOption('crop', '', InputOption::VALUE_REQUIRED, 'crop', 0) - ->addOption('json', '', InputOption::VALUE_NONE, 'return JSON format') ->setDescription('Get stream by timeline and viewer'); } @@ -111,7 +109,7 @@ class Timeline extends ExtendedBase { $output = new ConsoleOutput(); $this->output = $output->section(); - $this->asJson = $input->getOption('json'); + $this->asJson = (strtolower($input->getOption('output')) === 'json'); $this->crop = intval($input->getOption('crop')); $userId = $input->getArgument('userId'); @@ -129,46 +127,17 @@ class Timeline extends ExtendedBase { $options = new TimelineOptions(); $options->setFormat(Stream::FORMAT_LOCAL); - $options->setLimit(intval($input->getOption('count'))) + $options->setLimit(intval($input->getOption('limit'))) ->setMinId(intval($input->getOption('min_id'))) - ->setMaxId(intval($input->getOption('max_id'))); + ->setMaxId(intval($input->getOption('max_id'))) + ->setSince(intval($input->getOption('since'))); - try { - if ($input->getOption('local')) { - $options->setLocal(true); - } - $options->setTimeline($timeline = $input->getArgument('timeline')); - $this->outputStreams($this->streamRequest->getTimeline($options)); - } catch (UnknownTimelineException $e) { - $this->displayUnsupportedStream($options); + if ($input->getOption('local')) { + $options->setLocal(true); } + $options->setTimeline($input->getArgument('timeline')); + $this->outputStreams($this->streamRequest->getTimeline($options)); return 0; } - - - /** - * @param TimelineOptions $options - * - * @throws DateTimeException - */ - private function displayUnsupportedStream(TimelineOptions $options) { - switch ($options->getTimeline()) { - case 'notifications': - $stream = $this->streamRequest->getTimelineNotifications(0, $options->getLimit()); - $this->outputStreams($stream); - break; - - case 'liked': - $stream = $this->streamRequest->getTimelineLiked(0, $options->getLimit()); - $this->outputStreams($stream); - break; - - default: - throw new Exception( - 'Unknown timeline. Try ' . implode(', ', TimelineOptions::$availableTimelines) - . ', direct, notifications, liked' - ); - } - } } diff --git a/lib/Controller/ActivityPubController.php b/lib/Controller/ActivityPubController.php index 05258855..b2302232 100644 --- a/lib/Controller/ActivityPubController.php +++ b/lib/Controller/ActivityPubController.php @@ -39,7 +39,6 @@ use OCA\Social\Exceptions\SignatureIsGoneException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\StreamNotFoundException; use OCA\Social\Exceptions\UrlCloudException; -use OCA\Social\Model\ActivityPub\Activity\Delete; use OCA\Social\Service\AccountService; use OCA\Social\Service\CacheActorService; use OCA\Social\Service\ConfigService; @@ -76,6 +75,7 @@ class ActivityPubController extends Controller { private FollowService $followService; private StreamService $streamService; private ConfigService $configService; + private LoggerInterface $logger; public function __construct( IRequest $request, diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index c089b003..7fbd5006 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -193,23 +193,6 @@ class ApiController extends Controller { } - /** - * @NoCSRFRequired - * @PublicPage - * - * @return DataResponse - */ - public function notifications(): DataResponse { - try { - $this->initViewer(true); - - return new DataResponse([], Http::STATUS_OK); - } catch (Exception $e) { - return $this->error($e->getMessage()); - } - } - - /** * @NoCSRFRequired * @PublicPage @@ -273,18 +256,133 @@ class ApiController extends Controller { bool $local = false, int $limit = 20, int $max_id = 0, - int $min_id = 0 + int $min_id = 0, + int $since = 0 ): DataResponse { try { $this->initViewer(true); $options = new TimelineOptions($this->request); $options->setFormat(ACore::FORMAT_LOCAL); - $options->setTimeline($timeline); - $options->setLocal($local); - $options->setLimit($limit); - $options->setMaxId($max_id); - $options->setMinId($min_id); + $options->setTimeline($timeline) + ->setLocal($local) + ->setLimit($limit) + ->setMaxId($max_id) + ->setMinId($min_id) + ->setSince($since); + + $posts = $this->streamService->getTimeline($options); + + return new DataResponse($posts, Http::STATUS_OK); + } catch (Exception $e) { + return $this->error($e->getMessage()); + } + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @param int $limit + * @param int $max_id + * @param int $min_id + * @param int $since + * + * @return DataResponse + */ + public function favourites( + int $limit = 20, + int $max_id = 0, + int $min_id = 0, + int $since = 0 + ): DataResponse { + try { + $this->initViewer(true); + + $options = new TimelineOptions($this->request); + $options->setFormat(ACore::FORMAT_LOCAL); + $options->setTimeline(TimelineOptions::TIMELINE_FAVOURITES) + ->setLimit($limit) + ->setMaxId($max_id) + ->setMinId($min_id) + ->setSince($since); + + $posts = $this->streamService->getTimeline($options); + + return new DataResponse($posts, Http::STATUS_OK); + } catch (Exception $e) { + return $this->error($e->getMessage()); + } + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return DataResponse + */ + public function notifications( + int $limit = 20, + int $max_id = 0, + int $min_id = 0, + int $since = 0, + array $types = [], + array $exclude_types = [], + string $accountId = '' + ): DataResponse { + try { + $this->initViewer(true); + + $options = new TimelineOptions($this->request); + $options->setFormat(ACore::FORMAT_LOCAL); + $options->setTimeline(TimelineOptions::TIMELINE_NOTIFICATIONS) + ->setLimit($limit) + ->setMaxId($max_id) + ->setMinId($min_id) + ->setSince($since) + ->setTypes($types) + ->setExcludeTypes($exclude_types) + ->setAccountId($accountId); + + $posts = $this->streamService->getTimeline($options); + + return new DataResponse($posts, Http::STATUS_OK); + } catch (Exception $e) { + return $this->error($e->getMessage()); + } + } + + + /** + * @NoCSRFRequired + * @PublicPage + * + * @return DataResponse + */ + public function tag( + string $hashtag, + int $limit = 20, + int $max_id = 0, + int $min_id = 0, + int $since = 0, + bool $local = false, + bool $only_media = false + ): DataResponse { + try { + $this->initViewer(true); + + $options = new TimelineOptions($this->request); + $options->setFormat(ACore::FORMAT_LOCAL); + $options->setTimeline('hashtag') + ->setLimit($limit) + ->setMaxId($max_id) + ->setMinId($min_id) + ->setSince($since) + ->setLocal($local) + ->setOnlyMedia($only_media) + ->setArgument($hashtag); $posts = $this->streamService->getTimeline($options); diff --git a/lib/Controller/OAuthController.php b/lib/Controller/OAuthController.php index ec36c308..680d4f08 100644 --- a/lib/Controller/OAuthController.php +++ b/lib/Controller/OAuthController.php @@ -36,7 +36,6 @@ use OCA\Social\Exceptions\ClientNotFoundException; use OCA\Social\Exceptions\InstanceDoesNotExistException; use OCA\Social\Model\Client\SocialClient; use OCA\Social\Service\AccountService; -use OCA\Social\Service\CacheActorService; use OCA\Social\Service\ClientService; use OCA\Social\Service\ConfigService; use OCA\Social\Service\InstanceService; diff --git a/lib/Db/ActorsRequestBuilder.php b/lib/Db/ActorsRequestBuilder.php index d9247f86..98b9ec82 100644 --- a/lib/Db/ActorsRequestBuilder.php +++ b/lib/Db/ActorsRequestBuilder.php @@ -33,7 +33,6 @@ namespace OCA\Social\Db; use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Model\ActivityPub\Actor\Person; -use OCP\DB\QueryBuilder\IQueryBuilder; class ActorsRequestBuilder extends CoreRequestBuilder { use TArrayTools; @@ -42,7 +41,7 @@ class ActorsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Insert request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getActorsInsertSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -55,7 +54,7 @@ class ActorsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Update request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getActorsUpdateSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -90,7 +89,7 @@ class ActorsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Delete request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getActorsDeleteSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index c2ff47d5..a7c93c11 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -36,7 +36,6 @@ use DateTime; use Doctrine\DBAL\Query\QueryBuilder; use Exception; use OC; -use OC\DB\Connection; use OC\DB\SchemaWrapper; use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Model\ActivityPub\Actor\Person; @@ -65,6 +64,7 @@ class CoreRequestBuilder { public const TABLE_FOLLOWS = 'social_follow'; public const TABLE_HASHTAGS = 'social_hashtag'; public const TABLE_INSTANCE = 'social_instance'; + public const TABLE_NOTIFICATION = 'social_notif'; public const TABLE_REQUEST_QUEUE = 'social_req_queue'; public const TABLE_STREAM = 'social_stream'; public const TABLE_STREAM_ACTIONS = 'social_stream_act'; @@ -1013,11 +1013,11 @@ class CoreRequestBuilder { /** - * @param IQueryBuilder $qb + * @param SocialQueryBuilder $qb * * @deprecated */ - protected function leftJoinStreamAction(IQueryBuilder &$qb) { + protected function leftJoinStreamAction(SocialQueryBuilder &$qb) { if ($qb->getType() !== QueryBuilder::SELECT || $this->viewer === null) { return; } @@ -1187,8 +1187,8 @@ class CoreRequestBuilder { ->selectAlias($prefix . '_f.follow_id', $prefix . '_follow_id') ->selectAlias($prefix . '_f.creation', $prefix . '_creation') ->leftJoin( - $this->defaultSelectAlias, CoreRequestBuilder::TABLE_FOLLOWS, $prefix . '_f', - $andX + $this->defaultSelectAlias, CoreRequestBuilder::TABLE_FOLLOWS, $prefix . '_f', + $andX ); } @@ -1263,7 +1263,7 @@ class CoreRequestBuilder { * this just empty all tables from the app. */ public function emptyAll() { - $schema = new SchemaWrapper(Server::get(Connection::class)); + $schema = new SchemaWrapper(Server::get(IDBConnection::class)); foreach (array_keys(self::$tables) as $table) { if ($schema->hasTable($table)) { $qb = $this->dbConnection->getQueryBuilder(); @@ -1278,7 +1278,7 @@ class CoreRequestBuilder { * this just empty all tables from the app. */ public function uninstallSocialTables() { - $schema = new SchemaWrapper(Server::get(Connection::class)); + $schema = new SchemaWrapper(Server::get(IDBConnection::class)); foreach (array_keys(self::$tables) as $table) { if ($schema->hasTable($table)) { $schema->dropTable($table); diff --git a/lib/Db/HashtagsRequestBuilder.php b/lib/Db/HashtagsRequestBuilder.php index b2cc075b..cc3598aa 100644 --- a/lib/Db/HashtagsRequestBuilder.php +++ b/lib/Db/HashtagsRequestBuilder.php @@ -32,7 +32,6 @@ declare(strict_types=1); namespace OCA\Social\Db; use OCA\Social\Tools\Traits\TArrayTools; -use OCP\DB\QueryBuilder\IQueryBuilder; /** * Class HashtagsRequestBuilder @@ -46,7 +45,7 @@ class HashtagsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Insert request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getHashtagsInsertSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -59,7 +58,7 @@ class HashtagsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Update request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getHashtagsUpdateSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -91,7 +90,7 @@ class HashtagsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Delete request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getHashtagsDeleteSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); diff --git a/lib/Db/RequestQueueRequestBuilder.php b/lib/Db/RequestQueueRequestBuilder.php index a0ae7b33..a0731ada 100644 --- a/lib/Db/RequestQueueRequestBuilder.php +++ b/lib/Db/RequestQueueRequestBuilder.php @@ -32,7 +32,6 @@ namespace OCA\Social\Db; use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\Model\RequestQueue; -use OCP\DB\QueryBuilder\IQueryBuilder; class RequestQueueRequestBuilder extends CoreRequestBuilder { use TArrayTools; @@ -41,7 +40,7 @@ class RequestQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Insert request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getRequestQueueInsertSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -54,7 +53,7 @@ class RequestQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Update request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getRequestQueueUpdateSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -89,7 +88,7 @@ class RequestQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Delete request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getRequestQueueDeleteSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); diff --git a/lib/Db/SocialLimitsQueryBuilder.php b/lib/Db/SocialLimitsQueryBuilder.php index 68c5ae82..e025c740 100644 --- a/lib/Db/SocialLimitsQueryBuilder.php +++ b/lib/Db/SocialLimitsQueryBuilder.php @@ -335,8 +335,9 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder { $expr = $this->expr(); $pf = $this->getDefaultSelectAlias(); - if ($options->getSinceId() > 0) { - $this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getSinceId()))); + if ($options->getSince() > 0) { + $options->setInverted(true); + $this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getSince()))); } if ($options->getMaxId() > 0) { @@ -344,7 +345,6 @@ class SocialLimitsQueryBuilder extends SocialCrossQueryBuilder { } if ($options->getMinId() > 0) { - $options->setInverted(true); $this->andWhere($expr->gt($pf . '.nid', $this->createNamedParameter($options->getMinId()))); } diff --git a/lib/Db/StreamActionsRequestBuilder.php b/lib/Db/StreamActionsRequestBuilder.php index 5f61506a..ae5186ea 100644 --- a/lib/Db/StreamActionsRequestBuilder.php +++ b/lib/Db/StreamActionsRequestBuilder.php @@ -32,7 +32,6 @@ namespace OCA\Social\Db; use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\Model\StreamAction; -use OCP\DB\QueryBuilder\IQueryBuilder; /** * Class StreamActionsRequestBuilder @@ -46,7 +45,7 @@ class StreamActionsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Insert request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamActionInsertSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -59,7 +58,7 @@ class StreamActionsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Update request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamActionUpdateSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -91,7 +90,7 @@ class StreamActionsRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Delete request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamActionDeleteSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); diff --git a/lib/Db/StreamQueueRequestBuilder.php b/lib/Db/StreamQueueRequestBuilder.php index 37c5f8fd..626c4466 100644 --- a/lib/Db/StreamQueueRequestBuilder.php +++ b/lib/Db/StreamQueueRequestBuilder.php @@ -32,7 +32,6 @@ namespace OCA\Social\Db; use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\Model\StreamQueue; -use OCP\DB\QueryBuilder\IQueryBuilder; class StreamQueueRequestBuilder extends CoreRequestBuilder { use TArrayTools; @@ -41,7 +40,7 @@ class StreamQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Insert request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamQueueInsertSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -54,7 +53,7 @@ class StreamQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Update request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamQueueUpdateSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); @@ -88,7 +87,7 @@ class StreamQueueRequestBuilder extends CoreRequestBuilder { /** * Base of the Sql Delete request * - * @return IQueryBuilder + * @return SocialQueryBuilder */ protected function getStreamQueueDeleteSql(): SocialQueryBuilder { $qb = $this->getQueryBuilder(); diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php index 813b3e87..156dfa2d 100644 --- a/lib/Db/StreamRequest.php +++ b/lib/Db/StreamRequest.php @@ -338,24 +338,38 @@ class StreamRequest extends StreamRequestBuilder { return $this->getStreamFromRequest($qb); } - + /** + * @param TimelineOptions $options + * + * @return Stream[] + */ public function getTimeline(TimelineOptions $options): array { switch (strtolower($options->getTimeline())) { - case 'home': + case TimelineOptions::TIMELINE_HOME: $result = $this->getTimelineHome($options); break; - case 'direct': + case TimelineOptions::TIMELINE_DIRECT: $result = $this->getTimelineDirect($options); break; - case 'public': + case TimelineOptions::TIMELINE_FAVOURITES: + $result = $this->getTimelineFavourites($options); + break; + case TimelineOptions::TIMELINE_HASHTAG: + $result = $this->getTimelineHashtag($options, $options->getArgument()); + break; + case TimelineOptions::TIMELINE_NOTIFICATIONS: + $options->setFormat(ACore::FORMAT_NOTIFICATION); + $result = $this->getTimelineNotifications($options); + break; + case TimelineOptions::TIMELINE_PUBLIC: $result = $this->getTimelinePublic($options); break; - default: return []; } if ($options->isInverted()) { + // in cae we inverted the order during the request, we revert the results $result = array_reverse($result); } @@ -412,6 +426,64 @@ class StreamRequest extends StreamRequestBuilder { } + /** + * @param TimelineOptions $options + * + * @return Stream[] + */ + private function getTimelineFavourites(TimelineOptions $options): array { + $qb = $this->getStreamSelectSql($options->getFormat()); + $actor = $qb->getViewer(); + $expr = $qb->expr(); + + $qb->limitToType(Note::TYPE); + $qb->paginate($options); + $qb->linkToCacheActors('ca', 's.attributed_to_prim'); + + $qb->selectStreamActions('sa'); + $qb->andWhere($expr->eq('sa.stream_id_prim', 's.id_prim')); + $qb->andWhere($expr->eq('sa.actor_id_prim', $qb->createNamedParameter($qb->prim($actor->getId())))); + $qb->andWhere($expr->eq('sa.liked', $qb->createNamedParameter(1))); + + return $this->getStreamsFromRequest($qb); + } + + + /** + * @param TimelineOptions $options + * + * @return Stream[] + */ + private function getTimelineHashtag(TimelineOptions $options, string $hashtag): array { + $qb = $this->getStreamSelectSql($options->getFormat()); + + return []; + + return $this->getStreamsFromRequest($qb); + } + + + /** + * @param TimelineOptions $options + * + * @return Stream[] + */ + private function getTimelineNotifications(TimelineOptions $options): array { + $qb = $this->getStreamSelectSql($options->getFormat()); + $actor = $qb->getViewer(); + + $qb->limitToType(SocialAppNotification::TYPE); + $qb->paginate($options); + + $qb->selectDestFollowing('sd', ''); + $qb->limitToDest($actor->getId(), 'notif', '', 'sd'); + $qb->linkToCacheActors('ca', 's.attributed_to_prim'); + $qb->leftJoinStreamAction(); + + return $this->getStreamsFromRequest($qb); + } + + /** * Should return: * * Own posts, @@ -456,8 +528,9 @@ class StreamRequest extends StreamRequestBuilder { * * @return Stream[] * @throws DateTimeException + * @deprecated */ - public function getTimelineNotifications(int $since = 0, int $limit = 5): array { + public function getTimelineNotifications_dep(int $since = 0, int $limit = 5): array { $qb = $this->getStreamSelectSql(); $actor = $qb->getViewer(); diff --git a/lib/Model/ActivityPub/ACore.php b/lib/Model/ActivityPub/ACore.php index 00feb456..1451cb2d 100644 --- a/lib/Model/ActivityPub/ACore.php +++ b/lib/Model/ActivityPub/ACore.php @@ -30,9 +30,6 @@ declare(strict_types=1); namespace OCA\Social\Model\ActivityPub; -use OCA\Social\Tools\Traits\TArrayTools; -use OCA\Social\Tools\Traits\TPathTools; -use OCA\Social\Tools\Traits\TStringTools; use JsonSerializable; use OCA\Social\Exceptions\ActivityCantBeVerifiedException; use OCA\Social\Exceptions\InvalidOriginException; @@ -40,6 +37,9 @@ use OCA\Social\Exceptions\InvalidResourceEntryException; use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Model\ActivityPub\Object\Document; use OCA\Social\Model\LinkedDataSignature; +use OCA\Social\Tools\Traits\TArrayTools; +use OCA\Social\Tools\Traits\TPathTools; +use OCA\Social\Tools\Traits\TStringTools; class ACore extends Item implements JsonSerializable { use TArrayTools; @@ -63,6 +63,7 @@ class ACore extends Item implements JsonSerializable { public const FORMAT_ACTIVITYPUB = 1; public const FORMAT_LOCAL = 2; + public const FORMAT_NOTIFICATION = 3; /** @var null Item */ @@ -681,6 +682,10 @@ class ACore extends Item implements JsonSerializable { return $this->exportAsLocal(); } + if ($this->getExportFormat() === self::FORMAT_NOTIFICATION) { + return $this->exportAsNotification(); + } + return $this->exportAsActivityPub(); } @@ -767,4 +772,19 @@ class ACore extends Item implements JsonSerializable { return $result; } + + /** + * @return array + */ + public function exportAsNotification(): array { + $result = [ + 'id' => $this->getId() + ]; + + if ($this->getNid() > 0) { + $result['id'] = (string)$this->getNid(); + } + + return $result; + } } diff --git a/lib/Model/ActivityPub/Stream.php b/lib/Model/ActivityPub/Stream.php index 61ca03b3..03421f34 100644 --- a/lib/Model/ActivityPub/Stream.php +++ b/lib/Model/ActivityPub/Stream.php @@ -30,14 +30,17 @@ declare(strict_types=1); namespace OCA\Social\Model\ActivityPub; -use OCA\Social\Tools\IQueryRow; -use OCA\Social\Tools\Model\Cache; -use OCA\Social\Tools\Model\CacheItem; use DateTime; use Exception; use JsonSerializable; use OCA\Social\Model\ActivityPub\Actor\Person; +use OCA\Social\Model\ActivityPub\Object\Announce; +use OCA\Social\Model\ActivityPub\Object\Follow; +use OCA\Social\Model\ActivityPub\Object\Like; use OCA\Social\Model\StreamAction; +use OCA\Social\Tools\IQueryRow; +use OCA\Social\Tools\Model\Cache; +use OCA\Social\Tools\Model\CacheItem; use OCA\Social\Traits\TDetails; /** @@ -496,4 +499,35 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { return array_merge(parent::exportAsLocal(), $result); } + + + public function exportAsNotification(): array { + switch ($this->getSubType()) { + case Like::TYPE: + $type = 'favourites'; + break; + case Announce::TYPE: + $type = 'mention'; + break; + case Follow::TYPE: + $type = 'follow'; + break; + default: + $type = ''; + } + + $result = [ + 'id' => $this->getId(), + 'type' => $type, + 'created_at' => $this->getOriginCreationTime(), + 'status' => $this->getDetails('post') + ]; + + if ($this->hasActor()) { + $actor = $this->getActor(); + $result['account'] = $actor->exportAsLocal(); + } + + return array_merge(parent::exportAsNotification(), $result); + } } diff --git a/lib/Model/Client/Options/TimelineOptions.php b/lib/Model/Client/Options/TimelineOptions.php index c009dd40..ae844d41 100644 --- a/lib/Model/Client/Options/TimelineOptions.php +++ b/lib/Model/Client/Options/TimelineOptions.php @@ -44,22 +44,35 @@ use OCP\IRequest; class TimelineOptions extends CoreOptions implements JsonSerializable { use TArrayTools; + public const TIMELINE_HOME = 'home'; + public const TIMELINE_PUBLIC = 'public'; + public const TIMELINE_DIRECT = 'direct'; + public const TIMELINE_FAVOURITES = 'favourites'; + public const TIMELINE_HASHTAG = 'hashtag'; + public const TIMELINE_NOTIFICATIONS = 'notifications'; + private string $timeline = ''; private bool $local = false; private bool $remote = false; private bool $onlyMedia = false; private int $minId = 0; private int $maxId = 0; - private int $sinceId = 0; + private int $since = 0; private int $limit = 20; private bool $inverted = false; + private string $argument = ''; + private array $types = []; + private array $excludeTypes = []; + private string $accountId = ''; public static array $availableTimelines = [ - 'home', - 'public' + self::TIMELINE_HOME, + self::TIMELINE_PUBLIC, + self::TIMELINE_DIRECT, + self::TIMELINE_FAVOURITES, + self::TIMELINE_NOTIFICATIONS ]; - /** * TimelineOptions constructor. * @@ -197,17 +210,17 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { /** * @return int */ - public function getSinceId(): int { - return $this->sinceId; + public function getSince(): int { + return $this->since; } /** - * @param int $sinceId + * @param int $since * * @return TimelineOptions */ - public function setSinceId(int $sinceId): self { - $this->sinceId = $sinceId; + public function setSince(int $since): self { + $this->since = $since; return $this; } @@ -251,6 +264,83 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { } + /** + * @param string $argument + * + * @return TimelineOptions + */ + public function setArgument(string $argument): self { + $this->argument = $argument; + + return $this; + } + + /** + * @return string + */ + public function getArgument(): string { + return $this->argument; + } + + + /** + * @param array $types + * + * @return TimelineOptions + */ + public function setTypes(array $types): self { + $this->types = $types; + + return $this; + } + + /** + * @return array + */ + public function getTypes(): array { + return $this->types; + } + + + /** + * @param array $excludeTypes + * + * @return TimelineOptions + */ + public function setExcludeTypes(array $excludeTypes): self { + $this->excludeTypes = $excludeTypes; + + return $this; + } + + /** + * @return array + */ + public function getExcludeTypes(): array { + return $this->excludeTypes; + } + + + /** + * @param string $accountId + * + * @return TimelineOptions + */ + public function setAccountId(string $accountId): self { + $this->accountId = $accountId; + + return $this; + } + + + /** + * @return string + */ + public function getAccountId(): string { + return $this->accountId; + } + + /** * @param array $arr * @@ -262,8 +352,9 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { $this->setRemote($this->getBool('only_media', $arr, $this->isOnlyMedia())); $this->setMinId($this->getInt('min_id', $arr, $this->getMinId())); $this->setMaxId($this->getInt('max_id', $arr, $this->getMaxId())); - $this->setSinceId($this->getInt('since_id', $arr, $this->getSinceId())); + $this->setSince($this->getInt('since', $arr, $this->getSince())); $this->setLimit($this->getInt('limit', $arr, $this->getLimit())); + $this->setArgument($this->get('argument', $arr, $this->getArgument())); return $this; } @@ -281,8 +372,9 @@ class TimelineOptions extends CoreOptions implements JsonSerializable { 'only_media' => $this->isOnlyMedia(), 'min_id' => $this->getMinId(), 'max_id' => $this->getMaxId(), - 'since_id' => $this->getSinceId(), - 'limit' => $this->getLimit() + 'since' => $this->getSince(), + 'limit' => $this->getLimit(), + 'argument' => $this->getArgument() ]; } } diff --git a/lib/Service/CurlService.php b/lib/Service/CurlService.php index e21b2857..82b79996 100644 --- a/lib/Service/CurlService.php +++ b/lib/Service/CurlService.php @@ -52,7 +52,6 @@ use OCA\Social\Tools\Model\NCRequest; use OCA\Social\Tools\Model\Request; use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\Tools\Traits\TPathTools; -use OCP\AppFramework\Http; use Psr\Log\LoggerInterface; class CurlService { diff --git a/lib/Service/StreamService.php b/lib/Service/StreamService.php index 62ca49d0..473f049c 100644 --- a/lib/Service/StreamService.php +++ b/lib/Service/StreamService.php @@ -431,7 +431,7 @@ class StreamService { * @deprecated */ public function getStreamNotifications(int $since = 0, int $limit = 5): array { - return $this->streamRequest->getTimelineNotifications($since, $limit); + return $this->streamRequest->getTimelineNotifications_dep($since, $limit); } diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index ab16b035..46d5c462 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -56,6 +56,13 @@ $this->instance + + + dropTable + hasTable + hasTable + + (bool)($this->cache->get(self::CACHE_PREFIX . 'wellknown') === 'true') @@ -71,6 +78,9 @@ is_array($result) + + $this->maxDownloadSizeReached === true +