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 @@
       <code>$this-&gt;instance</code>
     </NullableReturnStatement>
   </file>
+  <file src="lib/Db/CoreRequestBuilder.php">
+    <UndefinedMethod occurrences="3">
+      <code>dropTable</code>
+      <code>hasTable</code>
+      <code>hasTable</code>
+    </UndefinedMethod>
+  </file>
   <file src="lib/Service/CheckService.php">
     <RedundantCast occurrences="1">
       <code>(bool)($this-&gt;cache-&gt;get(self::CACHE_PREFIX . 'wellknown') === 'true')</code>
@@ -71,6 +78,9 @@
     <RedundantCondition occurrences="1">
       <code>is_array($result)</code>
     </RedundantCondition>
+    <TypeDoesNotContainType occurrences="1">
+      <code>$this-&gt;maxDownloadSizeReached === true</code>
+    </TypeDoesNotContainType>
   </file>
   <file src="lib/Service/FollowService.php">
     <InvalidReturnStatement occurrences="2">