summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--appinfo/routes.php2
-rw-r--r--lib/Command/Timeline.php11
-rw-r--r--lib/Controller/ApiController.php58
-rw-r--r--lib/Db/StreamRequest.php42
-rw-r--r--lib/Model/Client/Options/TimelineOptions.php5
-rw-r--r--lib/Service/StreamService.php33
-rw-r--r--src/store/timeline.js82
7 files changed, 167 insertions, 66 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php
index b029b16c..a05daff8 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -83,6 +83,8 @@ return [
['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'],
+ ['name' => 'Api#timelines', 'url' => '/api/v1/timelines/{timeline}/', 'verb' => 'GET'],
+ ['name' => 'Api#accountStatuses', 'url' => '/api/v1/accounts/{account}/statuses', 'verb' => 'GET'],
// Api for local front-end
// TODO: front-end should be using the new ApiController
diff --git a/lib/Command/Timeline.php b/lib/Command/Timeline.php
index 6ab9988d..55932954 100644
--- a/lib/Command/Timeline.php
+++ b/lib/Command/Timeline.php
@@ -36,6 +36,7 @@ use OCA\Social\Db\StreamRequest;
use OCA\Social\Model\ActivityPub\Stream;
use OCA\Social\Model\Client\Options\TimelineOptions;
use OCA\Social\Service\AccountService;
+use OCA\Social\Service\CacheActorService;
use OCA\Social\Service\ConfigService;
use OCP\IUserManager;
use Symfony\Component\Console\Input\InputArgument;
@@ -53,6 +54,7 @@ class Timeline extends ExtendedBase {
private IUserManager $userManager;
private StreamRequest $streamRequest;
private AccountService $accountService;
+ private CacheActorService $cacheActorService;
private ConfigService $configService;
private ?int $count = null;
@@ -70,6 +72,7 @@ class Timeline extends ExtendedBase {
IUserManager $userManager,
StreamRequest $streamRequest,
AccountService $accountService,
+ CacheActorService $cacheActorService,
ConfigService $configService
) {
parent::__construct();
@@ -77,6 +80,7 @@ class Timeline extends ExtendedBase {
$this->userManager = $userManager;
$this->streamRequest = $streamRequest;
$this->accountService = $accountService;
+ $this->cacheActorService = $cacheActorService;
$this->configService = $configService;
}
@@ -94,6 +98,7 @@ class Timeline extends ExtendedBase {
->addOption('max_id', '', InputOption::VALUE_REQUIRED, 'max_id', 0)
->addOption('since', '', InputOption::VALUE_REQUIRED, 'since', 0)
->addOption('limit', '', InputOption::VALUE_REQUIRED, 'limit', 5)
+ ->addOption('account', '', InputOption::VALUE_REQUIRED, 'account', '')
->addOption('crop', '', InputOption::VALUE_REQUIRED, 'crop', 0)
->setDescription('Get stream by timeline and viewer');
}
@@ -136,6 +141,12 @@ class Timeline extends ExtendedBase {
$options->setLocal(true);
}
$options->setTimeline($input->getArgument('timeline'));
+
+ if ($input->getOption('account') !== '') {
+ $local = $this->cacheActorService->getFromLocalAccount($input->getOption('account'));
+ $options->setAccountId($local->getId());
+ }
+
$this->outputStreams($this->streamRequest->getTimeline($options));
return 0;
diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
index 7fbd5006..a31894bd 100644
--- a/lib/Controller/ApiController.php
+++ b/lib/Controller/ApiController.php
@@ -248,6 +248,7 @@ class ApiController extends Controller {
* @param int $limit
* @param int $max_id
* @param int $min_id
+ * @param int $since_id
*
* @return DataResponse
*/
@@ -257,7 +258,7 @@ class ApiController extends Controller {
int $limit = 20,
int $max_id = 0,
int $min_id = 0,
- int $since = 0
+ int $since_id = 0
): DataResponse {
try {
$this->initViewer(true);
@@ -269,6 +270,47 @@ class ApiController extends Controller {
->setLimit($limit)
->setMaxId($max_id)
->setMinId($min_id)
+ ->setSince($since_id);
+
+ $posts = $this->streamService->getTimeline($options);
+
+ return new DataResponse($posts, Http::STATUS_OK);
+ } catch (Exception $e) {
+ return $this->error($e->getMessage());
+ }
+ }
+
+
+ /**
+ * @NoCSRFRequired
+ * @PublicPage
+ *
+ * @param string $account
+ * @param int $limit
+ * @param int $max_id
+ * @param int $min_id
+ *
+ * @return DataResponse
+ */
+ public function accountStatuses(
+ string $account,
+ int $limit = 20,
+ int $max_id = 0,
+ int $min_id = 0,
+ int $since = 0
+ ): DataResponse {
+ try {
+ $this->initViewer(true);
+
+ $local = $this->cacheActorService->getFromLocalAccount($account);
+
+ $options = new TimelineOptions($this->request);
+ $options->setFormat(ACore::FORMAT_LOCAL);
+ $options->setTimeline(TimelineOptions::TIMELINE_ACCOUNT)
+ ->setAccountId($local->getId())
+ ->setLimit($limit)
+ ->setMaxId($max_id)
+ ->setMinId($min_id)
->setSince($since);
$posts = $this->streamService->getTimeline($options);
@@ -287,7 +329,7 @@ class ApiController extends Controller {
* @param int $limit
* @param int $max_id
* @param int $min_id
- * @param int $since
+ * @param int $since_id
*
* @return DataResponse
*/
@@ -295,7 +337,7 @@ class ApiController extends Controller {
int $limit = 20,
int $max_id = 0,
int $min_id = 0,
- int $since = 0
+ int $since_id = 0
): DataResponse {
try {
$this->initViewer(true);
@@ -306,7 +348,7 @@ class ApiController extends Controller {
->setLimit($limit)
->setMaxId($max_id)
->setMinId($min_id)
- ->setSince($since);
+ ->setSince($since_id);
$posts = $this->streamService->getTimeline($options);
@@ -327,7 +369,7 @@ class ApiController extends Controller {
int $limit = 20,
int $max_id = 0,
int $min_id = 0,
- int $since = 0,
+ int $since_id = 0,
array $types = [],
array $exclude_types = [],
string $accountId = ''
@@ -341,7 +383,7 @@ class ApiController extends Controller {
->setLimit($limit)
->setMaxId($max_id)
->setMinId($min_id)
- ->setSince($since)
+ ->setSince($since_id)
->setTypes($types)
->setExcludeTypes($exclude_types)
->setAccountId($accountId);
@@ -366,7 +408,7 @@ class ApiController extends Controller {
int $limit = 20,
int $max_id = 0,
int $min_id = 0,
- int $since = 0,
+ int $since_id = 0,
bool $local = false,
bool $only_media = false
): DataResponse {
@@ -379,7 +421,7 @@ class ApiController extends Controller {
->setLimit($limit)
->setMaxId($max_id)
->setMinId($min_id)
- ->setSince($since)
+ ->setSince($since_id)
->setLocal($local)
->setOnlyMedia($only_media)
->setArgument($hashtag);
diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php
index e143fdd7..88488a19 100644
--- a/lib/Db/StreamRequest.php
+++ b/lib/Db/StreamRequest.php
@@ -362,6 +362,9 @@ class StreamRequest extends StreamRequestBuilder {
*/
public function getTimeline(TimelineOptions $options): array {
switch (strtolower($options->getTimeline())) {
+ case TimelineOptions::TIMELINE_ACCOUNT:
+ $result = $this->getTimelineAccount($options);
+ break;
case TimelineOptions::TIMELINE_HOME:
$result = $this->getTimelineHome($options);
break;
@@ -443,6 +446,43 @@ class StreamRequest extends StreamRequestBuilder {
}
+
+
+ /**
+ * Should returns:
+ * - public message from actorId.
+ * - followers-only if logged and follower.
+ *
+ * @param TimelineOptions $options
+ *
+ * @return Stream[]
+ */
+ private function getTimelineAccount(TimelineOptions $options): array {
+ $qb = $this->getStreamSelectSql();
+
+ $qb->filterType(SocialAppNotification::TYPE);
+ $qb->paginate($options);
+
+ $actorId = $options->getAccountId();
+ if ($actorId === '') {
+ return [];
+ }
+
+ $qb->limitToAttributedTo($actorId, true);
+
+ $qb->selectDestFollowing('sd', '');
+ $qb->innerJoinStreamDest('recipient', 'id_prim', 'sd', 's');
+ $accountIsViewer = ($qb->hasViewer() && $qb->getViewer()->getId() === $actorId);
+ $qb->limitToDest($accountIsViewer ? '' : ACore::CONTEXT_PUBLIC, 'recipient', '', 'sd');
+
+ $qb->linkToCacheActors('ca', 's.attributed_to_prim');
+ $qb->leftJoinStreamAction();
+
+ return $this->getStreamsFromRequest($qb);
+ }
+
+
+
/**
* @param TimelineOptions $options
*
@@ -577,7 +617,7 @@ class StreamRequest extends StreamRequestBuilder {
* @return Stream[]
* @throws DateTimeException
*/
- public function getTimelineAccount(string $actorId, int $since = 0, int $limit = 5): array {
+ public function getTimelineAccount_dep(string $actorId, int $since = 0, int $limit = 5): array {
$qb = $this->getStreamSelectSql();
$qb->limitPaginate($since, $limit);
diff --git a/lib/Model/Client/Options/TimelineOptions.php b/lib/Model/Client/Options/TimelineOptions.php
index ae844d41..fabb45a3 100644
--- a/lib/Model/Client/Options/TimelineOptions.php
+++ b/lib/Model/Client/Options/TimelineOptions.php
@@ -47,6 +47,7 @@ class TimelineOptions extends CoreOptions implements JsonSerializable {
public const TIMELINE_HOME = 'home';
public const TIMELINE_PUBLIC = 'public';
public const TIMELINE_DIRECT = 'direct';
+ public const TIMELINE_ACCOUNT = 'account';
public const TIMELINE_FAVOURITES = 'favourites';
public const TIMELINE_HASHTAG = 'hashtag';
public const TIMELINE_NOTIFICATIONS = 'notifications';
@@ -67,6 +68,7 @@ class TimelineOptions extends CoreOptions implements JsonSerializable {
public static array $availableTimelines = [
self::TIMELINE_HOME,
+ self::TIMELINE_ACCOUNT,
self::TIMELINE_PUBLIC,
self::TIMELINE_DIRECT,
self::TIMELINE_FAVOURITES,
@@ -349,7 +351,7 @@ class TimelineOptions extends CoreOptions implements JsonSerializable {
public function fromArray(array $arr): self {
$this->setLocal($this->getBool('local', $arr, $this->isLocal()));
$this->setRemote($this->getBool('remote', $arr, $this->isRemote()));
- $this->setRemote($this->getBool('only_media', $arr, $this->isOnlyMedia()));
+ $this->setOnlyMedia($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->setSince($this->getInt('since', $arr, $this->getSince()));
@@ -367,6 +369,7 @@ class TimelineOptions extends CoreOptions implements JsonSerializable {
return
[
'timeline' => $this->getTimeline(),
+ 'accountId' => $this->getAccountId(),
'local' => $this->isLocal(),
'remote' => $this->isRemote(),
'only_media' => $this->isOnlyMedia(),
diff --git a/lib/Service/StreamService.php b/lib/Service/StreamService.php
index 46fa3904..501c6e4a 100644
--- a/lib/Service/StreamService.php
+++ b/lib/Service/StreamService.php
@@ -56,48 +56,29 @@ use OCA\Social\Tools\Exceptions\RequestServerException;
class StreamService {
private StreamRequest $streamRequest;
-
private ActivityService $activityService;
-
- private AccountService $accountService;
-
- private SignatureService $signatureService;
-
- private StreamQueueService $streamQueueService;
-
private CacheActorService $cacheActorService;
-
private ConfigService $configService;
- private MiscService $miscService;
-
/**
* NoteService constructor.
*
* @param StreamRequest $streamRequest
* @param ActivityService $activityService
- * @param AccountService $accountService
- * @param SignatureService $signatureService
- * @param StreamQueueService $streamQueueService
* @param CacheActorService $cacheActorService
* @param ConfigService $configService
- * @param MiscService $miscService
*/
public function __construct(
- StreamRequest $streamRequest, ActivityService $activityService,
- AccountService $accountService, SignatureService $signatureService,
- StreamQueueService $streamQueueService, CacheActorService $cacheActorService,
- ConfigService $configService, MiscService $miscService
+ StreamRequest $streamRequest,
+ ActivityService $activityService,
+ CacheActorService $cacheActorService,
+ ConfigService $configService
) {
$this->streamRequest = $streamRequest;
$this->activityService = $activityService;
- $this->accountService = $accountService;
- $this->signatureService = $signatureService;
- $this->streamQueueService = $streamQueueService;
$this->cacheActorService = $cacheActorService;
$this->configService = $configService;
- $this->miscService = $miscService;
}
@@ -268,9 +249,7 @@ class StreamService {
$note->addTag(
[
'type' => 'Hashtag',
- 'href' => $this->configService->getSocialUrl() . 'tag/' . strtolower(
- $hashtag
- ),
+ 'href' => $this->configService->getSocialUrl() . 'tag/' . strtolower($hashtag),
'name' => '#' . $hashtag
]
);
@@ -445,7 +424,7 @@ class StreamService {
* @deprecated
*/
public function getStreamAccount(string $actorId, int $since = 0, int $limit = 5): array {
- return $this->streamRequest->getTimelineAccount($actorId, $since, $limit);
+ return $this->streamRequest->getTimelineAccount_dep($actorId, $since, $limit);
}
diff --git a/src/store/timeline.js b/src/store/timeline.js
index 0a14fe98..bd86e7eb 100644
--- a/src/store/timeline.js
+++ b/src/store/timeline.js
@@ -23,10 +23,13 @@
*
*/
-import logger from '../services/logger.js'
-import axios from '@nextcloud/axios'
import Vue from 'vue'
+
+import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
+import { showError } from '@nextcloud/dialogs'
+
+import logger from '../services/logger.js'
/**
* @property {object} timeline - The posts' collection
@@ -48,8 +51,10 @@ const state = {
*/
params: {},
account: '',
- /* Tells whether the composer should be displayed or not.
+ /**
+ * Tells whether the composer should be displayed or not.
* It's up to the view to honor this status or not.
+ *
* @member {boolean}
*/
composerDisplayStatus: false,
@@ -153,7 +158,7 @@ const actions = {
})
logger.info('Post created with token ' + data.result.token)
} catch (error) {
- OC.Notification.showTemporary('Failed to create a post')
+ showError('Failed to create a post')
logger.error('Failed to create a post', { error: error.response })
}
},
@@ -162,7 +167,7 @@ const actions = {
context.commit('removePost', post)
logger.info('Post deleted with token ' + response.data.result.token)
}).catch((error) => {
- OC.Notification.showTemporary('Failed to delete the post')
+ showError('Failed to delete the post')
logger.error('Failed to delete the post', { error })
})
},
@@ -172,7 +177,7 @@ const actions = {
context.commit('likePost', { post, parentAnnounce })
resolve(response)
}).catch((error) => {
- OC.Notification.showTemporary('Failed to like post')
+ showError('Failed to like post')
logger.error('Failed to like post', { error: error.response })
reject(error)
})
@@ -186,7 +191,7 @@ const actions = {
context.commit('removePost', post)
}
}).catch((error) => {
- OC.Notification.showTemporary('Failed to unlike post')
+ showError('Failed to unlike post')
logger.error('Failed to unlike post', { error })
})
},
@@ -197,7 +202,7 @@ const actions = {
logger.info('Post boosted with token ' + response.data.result.token)
resolve(response)
}).catch((error) => {
- OC.Notification.showTemporary('Failed to create a boost post')
+ showError('Failed to create a boost post')
logger.error('Failed to create a boost post', { error: error.response })
reject(error)
})
@@ -208,43 +213,62 @@ const actions = {
context.commit('unboostPost', { post, parentAnnounce })
logger.info('Boost deleted with token ' + response.data.result.token)
}).catch((error) => {
- OC.Notification.showTemporary('Failed to delete the boost')
+ showError('Failed to delete the boost')
logger.error('Failed to delete the boost', { error })
})
},
refreshTimeline(context) {
return this.dispatch('fetchTimeline', { sinceTimestamp: Math.floor(Date.now() / 1000) + 1 })
},
- fetchTimeline(context, { sinceTimestamp }) {
+ /**
+ *
+ * @param {object} context
+ * @param {object} params - see https://docs.joinmastodon.org/methods/timelines
+ * @param {number} [params.since_id] - Fetch results newer than ID
+ * @param {number} [params.max_id] - Fetch results older than ID
+ * @param {number} [params.min_id] - Fetch results immediately newer than ID
+ * @param {number} [params.limit] - Maximum number of results to return. Defaults to 20 statuses. Max 40 statuses
+ * @param {number} [params.local] - Show only local statuses? Defaults to false.
+ * @return {Promise<object>}
+ */
+ async fetchTimeline(context, params = {}) {
+ if (params.since_id === undefined) {
+ params.since_id = state.since_id - 1
+ }
- if (typeof sinceTimestamp === 'undefined') {
- sinceTimestamp = state.since - 1
+ if (params.limit === undefined) {
+ params.limit = 15
}
- // Compute URl to get the data
+ // Compute URL to get the data
let url = ''
- if (state.type === 'account') {
- url = generateUrl(`apps/social/api/v1/account/${state.account}/stream?limit=25&since=` + sinceTimestamp)
- } else if (state.type === 'tags') {
- url = generateUrl(`apps/social/api/v1/stream/tag/${state.params.tag}?limit=25&since=` + sinceTimestamp)
- } else if (state.type === 'single-post') {
- url = generateUrl(`apps/social/local/v1/post/replies?id=${state.params.id}&limit=5&since=` + sinceTimestamp)
- } else {
- url = generateUrl(`apps/social/api/v1/stream/${state.type}?limit=25&since=` + sinceTimestamp)
+ switch (state.type) {
+ case 'account':
+ // TODO: wait for maxence
+ url = generateUrl(`apps/social/api/v1/timelines/${state.account}`, params)
+ break
+ case 'tags':
+ url = generateUrl(`apps/social/api/v1/timelines/tag/${state.params.tag}`, params)
+ break
+ case 'single-post':
+ // TODO: wait for maxence
+ url = generateUrl(`apps/social/local/v1/post/replies?id=${state.params.id}`, params)
+ break
+ default:
+ url = generateUrl(`apps/social/api/v1/timelines/${state.type}`, params)
}
// Get the data and add them to the timeline
- return axios.get(url).then((response) => {
+ const response = await axios.get(url)
- if (response.status === -1) {
- throw response.message
- }
+ if (response.status === -1) {
+ throw response.message
+ }
- // Add results to timeline
- context.commit('addToTimeline', response.data.result)
+ // Add results to timeline
+ context.commit('addToTimeline', response.data.result)
- return response.data
- })
+ return response.data
},
addToTimeline(context, data) {
context.commit('addToTimeline', data)