diff --git a/appinfo/info.xml b/appinfo/info.xml index 6a07ee14..24087c6d 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -18,7 +18,7 @@ **🕸 Open standards:** We use the established ActivityPub standard! ]]> - 0.6.0-beta2 + 0.6.0-beta3 agpl Maxence Lange Julius Härtl diff --git a/appinfo/routes.php b/appinfo/routes.php index 13e467be..dca8dc8d 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -90,6 +90,8 @@ return [ ['name' => 'Api#statusNew', 'url' => '/api/v1/statuses', 'verb' => 'POST'], ['name' => 'Api#statusGet', 'url' => '/api/v1/statuses/{nid}', 'verb' => 'GET'], ['name' => 'Api#statusContext', 'url' => '/api/v1/statuses/{nid}/context', 'verb' => 'GET'], + ['name' => 'Api#statusAction', 'url' => '/api/v1/statuses/{nid}/{act}', 'verb' => 'POST'], + ['name' => 'Api#accountStatuses', 'url' => '/api/v1/accounts/{account}/statuses', 'verb' => 'GET'], // Api for local front-end diff --git a/lib/Command/NoteBoost.php b/lib/Command/NoteBoost.php index 67a5c0cc..9b6cf21f 100644 --- a/lib/Command/NoteBoost.php +++ b/lib/Command/NoteBoost.php @@ -35,7 +35,6 @@ use Exception; use OC\Core\Command\Base; use OCA\Social\Service\AccountService; use OCA\Social\Service\BoostService; -use OCA\Social\Service\MiscService; use OCA\Social\Service\StreamService; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -51,18 +50,17 @@ class NoteBoost extends Base { private StreamService $streamService; private AccountService $accountService; private BoostService $boostService; - private MiscService $miscService; public function __construct( - AccountService $accountService, StreamService $streamService, BoostService $boostService, - MiscService $miscService + AccountService $accountService, + StreamService $streamService, + BoostService $boostService ) { parent::__construct(); $this->streamService = $streamService; $this->boostService = $boostService; $this->accountService = $accountService; - $this->miscService = $miscService; } diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php index 005f9785..1f775500 100644 --- a/lib/Controller/ApiController.php +++ b/lib/Controller/ApiController.php @@ -46,6 +46,7 @@ use OCA\Social\Model\Client\SocialClient; use OCA\Social\Model\Client\Status; use OCA\Social\Model\Post; use OCA\Social\Service\AccountService; +use OCA\Social\Service\ActionService; use OCA\Social\Service\CacheActorService; use OCA\Social\Service\CacheDocumentService; use OCA\Social\Service\ClientService; @@ -85,6 +86,7 @@ class ApiController extends Controller { private DocumentService $documentService; private FollowService $followService; private StreamService $streamService; + private ActionService $actionService; private PostService $postService; private ConfigService $configService; @@ -105,6 +107,7 @@ class ApiController extends Controller { DocumentService $documentService, FollowService $followService, StreamService $streamService, + ActionService $actionService, PostService $postService, ConfigService $configService ) { @@ -121,6 +124,7 @@ class ApiController extends Controller { $this->documentService = $documentService; $this->followService = $followService; $this->streamService = $streamService; + $this->actionService = $actionService; $this->postService = $postService; $this->configService = $configService; @@ -256,9 +260,14 @@ class ApiController extends Controller { } $activity = $this->postService->createPost($post); - $activity->setExportFormat(ACore::FORMAT_LOCAL); - return new DataResponse($activity, Http::STATUS_OK); + $item = $this->streamService->getStreamById( + $activity->getObjectId(), + true, + ACore::FORMAT_LOCAL + ); + + return new DataResponse($item, Http::STATUS_OK); } catch (Exception $e) { $this->logger->warning('issues while statusNew', ['exception' => $e]); @@ -461,6 +470,31 @@ class ApiController extends Controller { } } + /** + * @NoCSRFRequired + * @PublicPage + * + * @param int $nid + * @param string $action + * + * @return DataResponse + */ + public function statusAction(int $nid, string $act): DataResponse { + try { + $this->initViewer(true); + $item = $this->actionService->action($this->viewer->getId(), $nid, $act); + + if ($item === null) { + $item = $this->streamService->getStreamByNid($nid); + } + + return new DataResponse($item, Http::STATUS_OK); + } catch (Exception $e) { + return $this->error($e->getMessage()); + } + } + + /** * @NoCSRFRequired * @PublicPage @@ -469,6 +503,7 @@ class ApiController extends Controller { * @param int $limit * @param int $max_id * @param int $min_id + * @param int $since * * @return DataResponse */ diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index b7aa1a60..8bac6d18 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -202,6 +202,7 @@ class CoreRequestBuilder { 'nid', 'id', 'id_prim', + 'visibility', 'type', 'subtype', 'to', diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php index 9e0c18ff..3f22dab6 100644 --- a/lib/Db/StreamRequest.php +++ b/lib/Db/StreamRequest.php @@ -891,6 +891,7 @@ class StreamRequest extends StreamRequestBuilder { $qb = $this->getStreamInsertSql(); $qb->setValue('nid', $qb->createNamedParameter($stream->getNid())) ->setValue('id', $qb->createNamedParameter($stream->getId())) + ->setValue('visibility', $qb->createNamedParameter($stream->getVisibility())) ->setValue('type', $qb->createNamedParameter($stream->getType())) ->setValue('subtype', $qb->createNamedParameter($stream->getSubType())) ->setValue('to', $qb->createNamedParameter($stream->getTo())) diff --git a/lib/Db/StreamRequestBuilder.php b/lib/Db/StreamRequestBuilder.php index e2b421d1..9096f9cb 100644 --- a/lib/Db/StreamRequestBuilder.php +++ b/lib/Db/StreamRequestBuilder.php @@ -31,9 +31,6 @@ declare(strict_types=1); namespace OCA\Social\Db; -use OCA\Social\Tools\Exceptions\CacheItemNotFoundException; -use OCA\Social\Tools\Exceptions\RowNotFoundException; -use OCA\Social\Tools\Traits\TArrayTools; use OCA\Social\AP; use OCA\Social\Exceptions\InvalidResourceException; use OCA\Social\Exceptions\ItemUnknownException; @@ -42,6 +39,9 @@ use OCA\Social\Exceptions\StreamNotFoundException; use OCA\Social\Model\ActivityPub\Object\Announce; use OCA\Social\Model\ActivityPub\Stream; use OCA\Social\Model\InstancePath; +use OCA\Social\Tools\Exceptions\CacheItemNotFoundException; +use OCA\Social\Tools\Exceptions\RowNotFoundException; +use OCA\Social\Tools\Traits\TArrayTools; /** * Class StreamRequestBuilder @@ -92,9 +92,9 @@ class StreamRequestBuilder extends CoreRequestBuilder { /** @noinspection PhpMethodParametersCountMismatchInspection */ $qb->selectDistinct('s.id') ->addSelect( - 's.nid', 's.type', 's.subtype', 's.to', 's.to_array', 's.cc', 's.bcc', 's.content', - 's.summary', 's.attachments', 's.published', 's.published_time', 's.cache', - 's.object_id', 's.attributed_to', 's.in_reply_to', 's.source', 's.local', + 's.nid', 's.type', 's.subtype', 's.visibility', 's.to', 's.to_array', 's.cc', + 's.bcc', 's.content', 's.summary', 's.attachments', 's.published', 's.published_time', + 's.cache', 's.object_id', 's.attributed_to', 's.in_reply_to', 's.source', 's.local', 's.instances', 's.creation', 's.filter_duplicate', 's.details', 's.hashtags' ) ->from(self::TABLE_STREAM, 's'); diff --git a/lib/Exceptions/InvalidActionException.php b/lib/Exceptions/InvalidActionException.php new file mode 100644 index 00000000..3011108a --- /dev/null +++ b/lib/Exceptions/InvalidActionException.php @@ -0,0 +1,35 @@ + + * @copyright 2023, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Exceptions; + +use Exception; + +class InvalidActionException extends Exception { +} diff --git a/lib/Migration/Version1000Date20221118000001.php b/lib/Migration/Version1000Date20221118000001.php index 89ddb82c..cb0a791e 100644 --- a/lib/Migration/Version1000Date20221118000001.php +++ b/lib/Migration/Version1000Date20221118000001.php @@ -537,6 +537,14 @@ class Version1000Date20221118000001 extends SimpleMigrationStep { 'default' => '' ] ); + $table->addColumn( + 'visibility', Types::STRING, + [ + 'notnull' => false, + 'length' => 31, + 'default' => '' + ] + ); $table->addColumn( 'to', Types::TEXT, [ diff --git a/lib/Migration/Version1000Date20230217000002.php b/lib/Migration/Version1000Date20230217000002.php index 8f63cc49..e2969158 100644 --- a/lib/Migration/Version1000Date20230217000002.php +++ b/lib/Migration/Version1000Date20230217000002.php @@ -40,6 +40,21 @@ class Version1000Date20230217000002 extends SimpleMigrationStep { /** @var ISchemaWrapper $schema */ $schema = $schemaClosure(); + if ($schema->hasTable('social_stream')) { + $table = $schema->getTable('social_stream'); + + if (!$table->hasColumn('visibility')) { + $table->addColumn( + 'visibility', Types::STRING, + [ + 'notnull' => false, + 'length' => 31, + 'default' => '' + ] + ); + } + } + if ($schema->hasTable('social_cache_doc')) { $table = $schema->getTable('social_cache_doc'); diff --git a/lib/Model/ActivityPub/Stream.php b/lib/Model/ActivityPub/Stream.php index 83cfeb3b..b540cd42 100644 --- a/lib/Model/ActivityPub/Stream.php +++ b/lib/Model/ActivityPub/Stream.php @@ -33,15 +33,10 @@ namespace OCA\Social\Model\ActivityPub; use DateTime; use Exception; use JsonSerializable; -use OCA\Social\AP; -use OCA\Social\Exceptions\InvalidResourceEntryException; use OCA\Social\Exceptions\ItemAlreadyExistsException; -use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Model\ActivityPub\Actor\Person; use OCA\Social\Model\ActivityPub\Object\Announce; -use OCA\Social\Model\ActivityPub\Object\Document; use OCA\Social\Model\ActivityPub\Object\Follow; -use OCA\Social\Model\ActivityPub\Object\Image; use OCA\Social\Model\ActivityPub\Object\Like; use OCA\Social\Model\Client\MediaAttachment; use OCA\Social\Model\StreamAction; @@ -70,6 +65,7 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { private string $activityId = ''; private string $content = ''; + private string $visibility = ''; private string $spoilerText = ''; private string $language = 'en'; private string $attributedTo = ''; @@ -130,6 +126,24 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { return $this; } + /** + * @param string $visibility + * + * @return Stream + */ + public function setVisibility(string $visibility): self { + $this->visibility = $visibility; + + return $this; + } + + /** + * @return string + */ + public function getVisibility(): string { + return $this->visibility; + } + /** * @return string @@ -470,6 +484,7 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { $this->setDetailsAll($this->getArray('details', $data, [])); $this->setFilterDuplicate($this->getBool('filter_duplicate', $data, false)); $this->setAttachments($this->getArray('attachments', $data, [])); + $this->setVisibility($this->get('visibility', $data)); $cache = new Cache(); $cache->import($this->getArray('cache', $data, [])); @@ -545,7 +560,7 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { "content" => $this->getContent(), "sensitive" => $this->isSensitive(), "spoiler_text" => $this->getSpoilerText(), - "visibility" => 'unlisted', + "visibility" => $this->getVisibility(), "language" => $this->getLanguage(), "in_reply_to_id" => null, "in_reply_to_account_id" => null, @@ -560,7 +575,8 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { 'url' => $this->getId(), "reblog" => null, 'media_attachments' => $this->getAttachments(), - "created_at" => date('Y-m-d\TH:i:s', $this->getPublishedTime()) . '.000Z' + "created_at" => date('Y-m-d\TH:i:s', $this->getPublishedTime()) . '.000Z', + 'noindex' => false ]; // TODO - store created_at full string with milliseconds ? @@ -592,7 +608,7 @@ class Stream extends ACore implements IQueryRow, JsonSerializable { 'id' => $this->getId(), 'type' => $type, 'created_at' => $this->getOriginCreationTime(), - 'status' => $this->getDetails('post') + 'status' => $this->getDetails('post'), ]; if ($this->hasActor()) { diff --git a/lib/Service/ActionService.php b/lib/Service/ActionService.php new file mode 100644 index 00000000..38c36a01 --- /dev/null +++ b/lib/Service/ActionService.php @@ -0,0 +1,140 @@ + + * @copyright 2023, Maxence Lange + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OCA\Social\Service; + +use OCA\Social\Exceptions\InvalidActionException; +use OCA\Social\Exceptions\StreamNotFoundException; +use OCA\Social\Model\ActivityPub\Stream; +use OCA\Social\Model\StreamAction; +use OCA\Social\Tools\Traits\TStringTools; + +class ActionService { + use TStringTools; + + private StreamService $streamService; + private StreamActionService $streamActionService; + + private const TRANSLATE = 'translate'; + private const FAVOURITE = 'favourite'; + private const UNFAVOURITE = 'unfavourite'; + private const REBLOG = 'reblog'; + private const UNREBLOG = 'unreblog'; + private const BOOKMARK = 'bookmark'; + private const UNBOOKMARK = 'unbookmark'; + private const MUTE = 'mute'; + private const UNMUTE = 'unmute'; + private const PIN = 'pin'; + private const UNPIN = 'unpin'; + + private static array $availableStatusAction = [ + self::TRANSLATE, + self::FAVOURITE, + self::UNFAVOURITE, + self::REBLOG, + self::UNREBLOG, + self::BOOKMARK, + self::UNBOOKMARK, + self::MUTE, + self::UNMUTE, + self::PIN, + self::UNPIN + ]; + + public function __construct( + StreamService $streamService, + StreamActionService $streamActionService + ) { + $this->streamService = $streamService; + $this->streamActionService = $streamActionService; + } + + + /** + * should return null + * will return Stream only with translate action + * + * @param int $nid + * @param string $action + * + * @return Stream|null + * @throws InvalidActionException + */ + public function action(string $actorId, int $nid, string $action): ?Stream { + if (!in_array($action, self::$availableStatusAction)) { + throw new InvalidActionException(); + } + + $post = $this->streamService->getStreamByNid($nid); + + switch ($action) { + case self::TRANSLATE: + return $this->translate($nid); + + case self::FAVOURITE: + $this->favourite($actorId, $post->getId()); + break; + + case self::UNFAVOURITE: + $this->favourite($actorId, $post->getId(), false); + break; + + case self::REBLOG: + $this->reblog($actorId, $post->getId()); + break; + + case self::UNREBLOG: + $this->reblog($actorId, $post->getId(), false); + break; + } + + return null; + } + + + /** + * TODO: returns a translated version of the Status + * + * @param int $nid + * + * @return Stream + * @throws StreamNotFoundException + */ + private function translate(int $nid): Stream { + return $this->streamService->getStreamByNid($nid); + } + + private function favourite(string $actorId, string $postId, bool $enabled = true): void { + $this->streamActionService->setActionBool($actorId, $postId, StreamAction::LIKED, $enabled); + } + + private function reblog(string $actorId, string $postId, bool $enabled = true): void { + $this->streamActionService->setActionBool($actorId, $postId, StreamAction::BOOSTED, $enabled); + } +} diff --git a/lib/Service/PostService.php b/lib/Service/PostService.php index e420bef6..cd10fce9 100644 --- a/lib/Service/PostService.php +++ b/lib/Service/PostService.php @@ -107,6 +107,7 @@ class PostService { $note->setAttributedTo($actor->getId()); $note->setContent(htmlentities($post->getContent(), ENT_QUOTES)); $note->setAttachments($post->getMedias()); + $note->setVisibility($post->getType()); // $this->generateDocumentsFromAttachments($note, $post); diff --git a/lib/Service/StreamActionService.php b/lib/Service/StreamActionService.php index 2bb025ee..456056df 100644 --- a/lib/Service/StreamActionService.php +++ b/lib/Service/StreamActionService.php @@ -90,7 +90,7 @@ class StreamActionService { * @param string $key * @param bool $value */ - public function setActionBool(string $actorId, string $streamId, string $key, bool $value) { + public function setActionBool(string $actorId, string $streamId, string $key, bool $value): void { $action = $this->loadAction($actorId, $streamId); $action->updateValueBool($key, $value); $this->saveAction($action);