diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | composer.json | 3 | ||||
-rw-r--r-- | lib/AppInfo/Application.php | 11 | ||||
-rw-r--r-- | lib/Command/AccountFollowing.php | 23 | ||||
-rw-r--r-- | lib/Entity/Account.php | 69 | ||||
-rw-r--r-- | lib/Entity/FollowRequest.php | 129 | ||||
-rw-r--r-- | lib/InstanceUtils.php | 29 | ||||
-rw-r--r-- | lib/Model/ActivityPub/ACore.php | 2 | ||||
-rw-r--r-- | lib/Model/ActivityPub/Object/Follow.php | 51 | ||||
-rw-r--r-- | lib/Serializer/AccountSerializer.php | 9 | ||||
-rw-r--r-- | lib/Service/AccountFinder.php | 6 | ||||
-rw-r--r-- | lib/Service/Feed/FeedManager.php | 10 | ||||
-rw-r--r-- | lib/Service/Feed/IFeedProvider.php | 18 | ||||
-rw-r--r-- | lib/Service/Feed/RedisFeedProvider.php | 12 | ||||
-rw-r--r-- | lib/Service/FollowService.php | 157 | ||||
-rw-r--r-- | tests/Serializer/AccountSerializerTest.php | 15 | ||||
-rw-r--r-- | tests/Service/AccountFinderTest.php | 2 | ||||
-rw-r--r-- | tests/Service/InstanceUtilsTest.php | 33 |
18 files changed, 416 insertions, 165 deletions
@@ -8,3 +8,5 @@ cypress/screenshots cypress/snapshots .php-cs-fixer.cache + +tests/.phpunit.result.cache diff --git a/composer.json b/composer.json index b2e48943..b029b7a2 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,8 @@ }, "require": { "gumlet/php-image-resize": "2.0.*", - "friendica/json-ld": "^1.0" + "friendica/json-ld": "^1.0", + "landrok/activitypub": "^0.5.8" }, "require-dev": { "phpunit/phpunit": "^9.5", diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 33dd3ba0..ed8c904c 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -31,28 +31,19 @@ declare(strict_types=1); namespace OCA\Social\AppInfo; -use Closure; use OCA\Social\Entity\Account; use OCA\Social\Notification\Notifier; use OCA\Social\Search\UnifiedSearchProvider; use OCA\Social\Serializer\AccountSerializer; use OCA\Social\Serializer\SerializerFactory; -use OCA\Social\Service\ConfigService; use OCA\Social\Service\Feed\RedisFeedProvider; -use OCA\Social\Service\IFeedProvider; -use OCA\Social\Service\UpdateService; +use OCA\Social\Service\Feed\IFeedProvider; use OCA\Social\WellKnown\WebfingerHandler; 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\IDBConnection; -use OCP\IServerContainer; -use OC\DB\SchemaWrapper; -use OCP\DB\ISchemaWrapper; use Psr\Container\ContainerInterface; -use Throwable; require_once __DIR__ . '/../../vendor/autoload.php'; diff --git a/lib/Command/AccountFollowing.php b/lib/Command/AccountFollowing.php index 66fbb4cf..b95cf25e 100644 --- a/lib/Command/AccountFollowing.php +++ b/lib/Command/AccountFollowing.php @@ -33,9 +33,11 @@ namespace OCA\Social\Command; use Exception; use OC\Core\Command\Base; +use OCA\Social\Service\AccountFinder; use OCA\Social\Service\AccountService; use OCA\Social\Service\CacheActorService; use OCA\Social\Service\ConfigService; +use OCA\Social\Service\FollowOption; use OCA\Social\Service\FollowService; use OCA\Social\Service\MiscService; use Symfony\Component\Console\Input\InputArgument; @@ -44,23 +46,18 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; class AccountFollowing extends Base { - private AccountService $accountService; + private AccountFinder $accountFinder; private CacheActorService $cacheActorService; private FollowService $followService; - private ConfigService $configService; - private MiscService $miscService; public function __construct( - AccountService $accountService, CacheActorService $cacheActorService, - FollowService $followService, ConfigService $configService, MiscService $miscService + AccountFinder $accountFinder, FollowService $followService, ConfigService $configService ) { parent::__construct(); - $this->accountService = $accountService; - $this->cacheActorService = $cacheActorService; + $this->accountFinder = $accountFinder; $this->followService = $followService; $this->configService = $configService; - $this->miscService = $miscService; } protected function configure() { @@ -80,16 +77,16 @@ class AccountFollowing extends Base { $userId = $input->getArgument('userId'); $account = $input->getArgument('account'); - $actor = $this->accountService->getActor($userId); + $sourceAccount = $this->accountFinder->getAccountByNextcloudId($userId); + if ($input->getOption('local')) { - $local = $this->cacheActorService->getFromLocalAccount($account); - $account = $local->getAccount(); + $targetAccount = $this->accountFinder->getAccountByNextcloudId($account); } if ($input->getOption('unfollow')) { - $this->followService->unfollowAccount($actor, $account); + $this->followService->unfollow($sourceAccount, $targetAccount, FollowOption::default()); } else { - $this->followService->followAccount($actor, $account); + $this->followService->follow($sourceAccount, $targetAccount, FollowOption::default()); } } } diff --git a/lib/Entity/Account.php b/lib/Entity/Account.php index dc4fa9ab..1194fbf3 100644 --- a/lib/Entity/Account.php +++ b/lib/Entity/Account.php @@ -11,6 +11,9 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; +use OCA\Social\Service\FollowOption; +use OCA\Social\InstanceUtils; +use OCP\IRequest; /** * @ORM\Entity @@ -168,6 +171,18 @@ class Account { private Collection $followedBy; /** + * @ORM\OneToMany(targetEntity="FollowRequest", mappedBy="account", fetch="EXTRA_LAZY", cascade={"persist", "remove"}) + * @var Collection<FollowRequest> + */ + private Collection $followRequest; + + /** + * @ORM\OneToMany(targetEntity="FollowRequest", mappedBy="targetAccount", fetch="EXTRA_LAZY", cascade={"persist", "remove"}) + * @var Collection<FollowRequest> + */ + private Collection $followRequestFrom; + + /** * @ORM\OneToMany(targetEntity="Follow", mappedBy="account", fetch="EXTRA_LAZY", cascade={"persist", "remove"}) * @var Collection<Block> */ @@ -179,17 +194,12 @@ class Account { */ private Collection $blockedBy; - private Collection $activeMentions; - - /** - * @var ?array{private: string, public: string} - */ - private ?array $keyPair = null; - public function __construct() { $this->block = new ArrayCollection(); $this->blockedBy = new ArrayCollection(); $this->follow = new ArrayCollection(); + $this->followRequest = new ArrayCollection(); + $this->followRequestFrom = new ArrayCollection(); $this->followedBy = new ArrayCollection(); $this->updatedAt = new \DateTime(); $this->createdAt = new \DateTime(); @@ -501,31 +511,50 @@ class Account { } /** - * @return Collection<Follow> + * Check whether this account follow the $targetAccount */ - public function getFollowersForLocalDistribution(): Collection { + public function following(Account $targetAccount): bool { $criteria = Criteria::create(); - $criteria->where(Criteria::expr()->eq('', false)); - return $this->followedBy->matching($criteria); + $criteria->where(Criteria::expr()->eq('account', $targetAccount)); + return !$this->follow->matching($criteria)->isEmpty(); } /** - * Add a new follower to this account + * Check whether this account created a follow request to $targetAccount */ - public function addFollower(Account $account): void { - $follow = new Follow(); - $follow->setTargetAccount($this); - $follow->setAccount($account); - $this->followedBy->add($follow); + public function followRequested(Account $targetAccount): bool { + $criteria = Criteria::create(); + $criteria->where(Criteria::expr()->eq('account', $targetAccount)); + return !$this->followRequest->matching($criteria)->isEmpty(); } /** - * Follow a new account + * Add a new follower to this account */ - public function follow(Account $account): void { + public function follow(Account $account, bool $notify = false, bool $showReblogs = true): Follow { $follow = new Follow(); - $follow->setAccount($this); $follow->setTargetAccount($account); + $follow->setAccount($this); + $follow->setNotify($notify); + $follow->setShowReblogs($showReblogs); $this->followedBy->add($follow); + return $follow; + } + + public function getFollowRequest() { + return $this->followRequest; + } + + public function setFollowRequest($followRequest): void { + $this->followRequest = $followRequest; + } + + public function getFollowRequestFrom(): Collection { + return $this->followRequestFrom; + } + + public function setFollowRequestFrom(Collection $followRequestFrom): self { + $this->followRequestFrom = $followRequestFrom; + return $this; } } diff --git a/lib/Entity/FollowRequest.php b/lib/Entity/FollowRequest.php new file mode 100644 index 00000000..fd54395f --- /dev/null +++ b/lib/Entity/FollowRequest.php @@ -0,0 +1,129 @@ +<?php + +declare(strict_types=1); + +// Nextcloud - Social Support +// SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu> +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OCA\Social\Entity; + +use DateTimeInterface; +use Doctrine\ORM\Mapping as ORM; + +/** + * @ORM\Entity + * @ORM\Table(name="social_request") + */ +class FollowRequest { + /** + * @ORM\Id + * @ORM\Column(type="bigint") + * @ORM\GeneratedValue + */ + private ?string $id = null; + + /** + * @ORM\Column(name="created_at", type="datetime", nullable=false) + */ + private \DateTime $createdAt; + + /** + * @ORM\Column(name="updated_at", type="datetime", nullable=false) + */ + private \DateTime $updatedAt; + + /** + * @ORM\ManyToOne(cascade={"persist", "remove"}) + * @ORM\JoinColumn(nullable=false) + */ + private ?Account $account = null; + + /** + * @ORM\ManyToOne(cascade={"persist", "remove"}) + * @ORM\JoinColumn(nullable=false) + */ + private ?Account $targetAccount = null; + + /** + * @ORM\Column + */ + private bool $showReblogs = true; + + /** + * @ORM\Column + */ + private string $uri = ""; + + /** + * @ORM\Column + */ + private bool $notify = false; + + public function __construct() { + $this->updatedAt = new \DateTime(); + $this->createdAt = new \DateTime(); + $this->account = new Account(); + $this->targetAccount = new Account(); + } + + public function getId(): string { + return $this->id; + } + + public function getCreatedAt():\DateTimeInterface { + return $this->createdAt; + } + + public function setCreatedAt(\DateTime $createdAt): void { + $this->createdAt = $createdAt; + } + + public function getUpdatedAt(): \DateTimeInterface { + return $this->updatedAt; + } + + public function setUpdatedAt(\DateTime $updatedAt): void { + $this->updatedAt = $updatedAt; + } + + public function getAccount(): Account { + return $this->account; + } + + public function setAccount(Account $account): void { + $this->account = $account; + } + + public function getTargetAccount(): Account { + return $this->targetAccount; + } + + public function setTargetAccount(Account $targetAccount): void { + $this->targetAccount = $targetAccount; + } + + public function isShowReblogs(): bool { + return $this->showReblogs; + } + + public function setShowReblogs(bool $showReblogs): void { + $this->showReblogs = $showReblogs; + } + + public function getUri(): string { + return $this->uri; + } + + public function setUri(string $uri): void { + $this->uri = $uri; + } + + public function isNotify(): bool { + return $this->notify; + } + + public function setNotify(bool $notify): void { + $this->notify = $notify; + } +} diff --git a/lib/InstanceUtils.php b/lib/InstanceUtils.php new file mode 100644 index 00000000..975180b6 --- /dev/null +++ b/lib/InstanceUtils.php @@ -0,0 +1,29 @@ +<?php + +namespace OCA\Social; + +use OCP\IURLGenerator; + +class InstanceUtils { + private IURLGenerator $generator; + + public function __construct(IURLGenerator $generator) { + $this->generator = $generator; + } + /** + * Return the url of the instance: e.g. https://hello.social + */ + public function getLocalInstanceUrl(): string { + $url = $this->generator->getAbsoluteURL('/'); + return rtrim($url, '/'); + } + + /** + * Return the name of the instance: e.g. hello.social + */ + public function getLocalInstanceName(): string { + $url = $this->generator->getAbsoluteURL('/'); + $url = rtrim($url, '/'); + return substr($url, 8); + } +} diff --git a/lib/Model/ActivityPub/ACore.php b/lib/Model/ActivityPub/ACore.php index 00feb456..6f980bac 100644 --- a/lib/Model/ActivityPub/ACore.php +++ b/lib/Model/ActivityPub/ACore.php @@ -283,7 +283,7 @@ class ACore extends Item implements JsonSerializable { * * @throws UrlCloudException */ - public function generateUniqueId(string $base = '', bool $root = true) { + public function generateUniqueId(string $base = '', bool $root = true): void { $url = ''; if ($root) { $url = $this->getUrlCloud(); diff --git a/lib/Model/ActivityPub/Object/Follow.php b/lib/Model/ActivityPub/Object/Follow.php index 3b779311..1728c51d 100644 --- a/lib/Model/ActivityPub/Object/Follow.php +++ b/lib/Model/ActivityPub/Object/Follow.php @@ -31,34 +31,27 @@ declare(strict_types=1); namespace OCA\Social\Model\ActivityPub\Object; -use OCA\Social\Tools\IQueryRow; +use OCA\Social\Entity\Follow as FollowEntitiy; use JsonSerializable; use OCA\Social\Model\ActivityPub\ACore; /** + * Virtual rep * Class Follow * - * @package OCA\Social\Model\ActivityPub\Object */ -class Follow extends ACore implements JsonSerializable, IQueryRow { +class Follow extends ACore implements JsonSerializable { public const TYPE = 'Follow'; - - private string $followId = ''; - - private string $followIdPrim = ''; - - private bool $accepted = false; - - - /** - * Follow constructor. - * - * @param ACore $parent - */ + static public function create(FollowEntitiy $follow): self { + $followActivity = new Follow(); + $followActivity->setId($follow->getUri() ?: $follow->getAccount()->getUri() . '#follows/' . $follow->getId()); + $followActivity->setActor($follow->getAccount()); + $followActivity->setVirtualObject() + return $followActivity + } public function __construct($parent = null) { parent::__construct($parent); - $this->setType(self::TYPE); } @@ -119,27 +112,6 @@ class Follow extends ACore implements JsonSerializable, IQueryRow { return $this; } - - /** - * @param array $data - */ - public function import(array $data) { - parent::import($data); - } - - - /** - * @param array $data - */ - public function importFromDatabase(array $data) { - parent::importFromDatabase($data); - - $this->setAccepted(($this->getInt('accepted', $data, 0) === 1) ? true : false); - $this->setFollowId($this->get('follow_id', $data, '')); - $this->setFollowIdPrim($this->get('follow_id_prim', $data, '')); - } - - /** * @return array */ @@ -150,9 +122,6 @@ class Follow extends ACore implements JsonSerializable, IQueryRow { $result = array_merge( $result, [ - 'follow_id' => $this->getFollowId(), - 'follow_id_prim' => $this->getFollowIdPrim(), - 'accepted' => $this->isAccepted() ] ); } diff --git a/lib/Serializer/AccountSerializer.php b/lib/Serializer/AccountSerializer.php index 555cb314..ad73c159 100644 --- a/lib/Serializer/AccountSerializer.php +++ b/lib/Serializer/AccountSerializer.php @@ -8,16 +8,17 @@ declare(strict_types=1); namespace OCA\Social\Serializer; use OCA\Social\Entity\Account; +use OCA\Social\InstanceUtils; use OCP\IRequest; use OCP\IUserManager; class AccountSerializer extends ActivityPubSerializer { - private IRequest $request; private IUserManager $userManager; + private InstanceUtils $instanceUtils; - public function __construct(IRequest $request, IUserManager $userManager) { - $this->request = $request; + public function __construct(IUserManager $userManager, InstanceUtils $instanceUtils) { $this->userManager = $userManager; + $this->instanceUtils = $instanceUtils; } public function toJsonLd(object $account): array { @@ -25,7 +26,7 @@ class AccountSerializer extends ActivityPubSerializer { $user = $this->userManager->get($account->getUserId()); - $baseUrl = "https://" . $this->request->getServerHost() . '/'; + $baseUrl = $this->instanceUtils->getLocalInstanceUrl() . '/'; $baseUserUrl = $baseUrl . "/users/" . $account->getUserName() . '/'; return array_merge($this->getContext(), [ diff --git a/lib/Service/AccountFinder.php b/lib/Service/AccountFinder.php index 49556808..384f64cc 100644 --- a/lib/Service/AccountFinder.php +++ b/lib/Service/AccountFinder.php @@ -99,7 +99,9 @@ class AccountFinder { * @return array<Follow> */ public function getLocalFollowersOf(Account $account): array { - return $this->entityManager->createQuery('SELECT f,a FROM \OCA\Social\Entity\Follow f LEFT JOIN f.account a WHERE f.targetAccount = :target') - ->setParameters(['target' => $account])->getResult(); + return $this->entityManager + ->createQuery('SELECT f,a FROM \OCA\Social\Entity\Follow f LEFT JOIN f.account a WHERE f.targetAccount = :target') + ->setParameters(['target' => $account]) + ->getResult(); } } diff --git a/lib/Service/Feed/FeedManager.php b/lib/Service/Feed/FeedManager.php index 57c1499d..0b644779 100644 --- a/lib/Service/Feed/FeedManager.php +++ b/lib/Service/Feed/FeedManager.php @@ -8,6 +8,7 @@ declare(strict_types=1); namespace OCA\Social\Service\Feed; +use OCA\Social\Entity\Account; use OCA\Social\Entity\Status; /** @@ -22,6 +23,7 @@ class FeedManager { const REBLOG_FALLOFF = 40; const HOME_FEED = "home"; + const MAX_ITEM = 400; private IFeedProvider $feedProvider; @@ -33,6 +35,10 @@ class FeedManager { return $this->addToFeed(self::HOME_FEED, $accountId, $status); } + public function removeFromHome(string $accountId, Status $status): bool { + return $this->removeFromFeed(self::HOME_FEED, $accountId, $status); + } + public function addToFeed(string $timelineType, string $accountId, Status $status, bool $aggregateReblog = true): bool { return $this->feedProvider->addToFeed($timelineType, $accountId, $status, $aggregateReblog); } @@ -40,4 +46,8 @@ class FeedManager { public function removeFromFeed(string $timelineType, string $accountId, Status $status, bool $aggregateReblog = true): bool { return $this->feedProvider->removeFromFeed($timelineType, $accountId, $status, $aggregateReblog); } + + public function mergeIntoHome(Account $fromAccount, Account $toAccount): void { + $this->feedProvider->mergeIntoHome($fromAccount, $toAccount); + } } diff --git a/lib/Service/Feed/IFeedProvider.php b/lib/Service/Feed/IFeedProvider.php index 446e7806..3145adc5 100644 --- a/lib/Service/Feed/IFeedProvider.php +++ b/lib/Service/Feed/IFeedProvider.php @@ -6,12 +6,28 @@ declare(strict_types=1); // SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu> // SPDX-License-Identifier: AGPL-3.0-or-later -namespace OCA\Social\Service; +namespace OCA\Social\Service\Feed; +use OCA\Social\Entity\Account; use OCA\Social\Entity\Status; +/** + * Interface abstracting the feed. Currently, there is only one implementation + * relying on Redis. + */ interface IFeedProvider { + /** + * Add a status from a feed + */ public function addToFeed(string $timelineType, string $accountId, Status $status, bool $aggregateReblog = true): bool; + /** + * Remove a status from a feed + */ public function removeFromFeed(string $timelineType, string $accountId, Status $status, bool $aggregateReblog = true): bool; + + /** + * Fill a home feed with an account's status + */ + public function mergeIntoHome(Account $fromAccount, Account $toAccount): void; } diff --git a/lib/Service/Feed/RedisFeedProvider.php b/lib/Service/Feed/RedisFeedProvider.php index ede6af45..480c7031 100644 --- a/lib/Service/Feed/RedisFeedProvider.php +++ b/lib/Service/Feed/RedisFeedProvider.php @@ -8,9 +8,8 @@ declare(strict_types=1); namespace OCA\Social\Service\Feed; use OC\RedisFactory; +use OCA\Social\Entity\Account; use OCA\Social\Entity\Status; -use OCA\Social\Service\FeedManager; -use OCA\Social\Service\IFeedProvider; class RedisFeedProvider implements IFeedProvider { private \Redis $redis; @@ -56,4 +55,13 @@ class RedisFeedProvider implements IFeedProvider { public function removeFromFeed(string $timelineType, string $accountId, Status $status, bool $aggregateReblog = true): bool { return false; } + + public function mergeIntoHome(Account $fromAccount, Account $intoAccount): void { + $timelineKey = $this->key(FeedManager::HOME_FEED, $intoAccount->getId()); + $aggregate = true; // TODO make configurable + + if ($this->redis->zCard($timelineKey) > (FeedManager::MAX_ITEM / 4)) { + $oldestHomeScore = $this->redis->zRange($timelineKey, 0, 0, true); + } + } } diff --git a/lib/Service/FollowService.php b/lib/Service/FollowService.php index 47e1ce59..ee295e45 100644 --- a/lib/Service/FollowService.php +++ b/lib/Service/FollowService.php @@ -30,79 +30,118 @@ declare(strict_types=1); namespace OCA\Social\Service; -use OCA\Social\Tools\Exceptions\MalformedArrayException; -use OCA\Social\Tools\Traits\TArrayTools; +use ActivityPhp\Type; use OCA\Social\AP; -use OCA\Social\Db\FollowsRequest; -use OCA\Social\Exceptions\CacheActorDoesNotExistException; -use OCA\Social\Exceptions\FollowNotFoundException; -use OCA\Social\Exceptions\FollowSameAccountException; -use OCA\Social\Exceptions\InvalidOriginException; -use OCA\Social\Exceptions\InvalidResourceException; -use OCA\Social\Exceptions\ItemUnknownException; -use OCA\Social\Exceptions\RedundancyLimitException; -use OCA\Social\Tools\Exceptions\RequestContentException; -use OCA\Social\Tools\Exceptions\RequestNetworkException; -use OCA\Social\Tools\Exceptions\RequestResultNotJsonException; -use OCA\Social\Tools\Exceptions\RequestResultSizeException; -use OCA\Social\Tools\Exceptions\RequestServerException; -use OCA\Social\Exceptions\RetrieveAccountFormatException; -use OCA\Social\Exceptions\SocialAppConfigException; -use OCA\Social\Exceptions\UnauthorizedFediverseException; -use OCA\Social\Exceptions\UrlCloudException; -use OCA\Social\Model\ActivityPub\Activity\Undo; -use OCA\Social\Model\ActivityPub\Actor\Person; -use OCA\Social\Model\ActivityPub\Object\Follow; -use OCA\Social\Model\ActivityPub\OrderedCollection; -use OCA\Social\Model\InstancePath; +use OCA\Social\Entity\Account; +use OCA\Social\Entity\Follow; +use OCA\Social\Entity\FollowRequest; +use OCA\Social\Service\Feed\FeedManager; +use OCP\DB\ORM\IEntityManager; +use OCP\DB\ORM\IEntityRepository; + +class FollowOption { + /** + * Show reblog of the account + */ + public bool $showReblogs = true; -class FollowService { - use TArrayTools; + /** + * Notify about new posts + */ + public bool $notify = false; + static public function default(): self { + return new FollowOption(); + } +} - private FollowsRequest $followsRequest; +class FollowService { + private IEntityManager $entityManager; + /** @var IEntityRepository<Follow> $followRepository */ + private IEntityRepository $followRepository; + /** @var IEntityRepository<FollowRequest> $followRepository */ + private IEntityRepository $followRequestRepository; + private FeedManager $feedManager; + + public function __construct(IEntityManager $entityManager, FeedManager $feedManager) { + $this->entityManager = $entityManager; + $this->followRepository = $entityManager->getRepository(Follow::class); + $this->followRepository = $entityManager->getRepository(FollowRequest::class); + $this->feedManager = $feedManager; + } - private ActivityService $activityService; + public function follow(Account $sourceAccount, Account $targetAccount, FollowOption $option): void { + if ($sourceAccount->following($targetAccount)) { + $this->updateFollow($sourceAccount, $targetAccount, $option->notify, $option->showReblogs); + return; + } elseif ($sourceAccount->followRequested($targetAccount)) { + $this->updateFollowRequest($sourceAccount, $targetAccount, $option->notify, $option->showReblogs); + return; + } - private CacheActorService $cacheActorService; + if ($targetAccount->isLocked() || !$targetAccount->isLocal()) { + $this->requestFollow($sourceAccount, $targetAccount); + } else { + $this->directFollow($sourceAccount, $targetAccount); + } + } - private ConfigService $configService; + private function updateFollow(Account $sourceAccount, Account $targetAccount, bool $notify, bool $showReblogs): void { + /** @var Follow $follow */ + $follow = $this->followRepository->findOneBy([ + 'account' => $sourceAccount, + 'targetAccount' => $targetAccount, + ]); + assert($follow); + + $follow->setNotify($notify); + $follow->setShowReblogs($showReblogs); + $this->entityManager->persist($follow); + $this->entityManager->flush(); + } - private MiscService $miscService; + private function updateFollowRequest(Account $sourceAccount, Account $targetAccount, bool $notify, bool $showReblogs): void { + /** @var Follow $follow */ + $followRequest = $this->followRequestRepository->findOneBy([ + 'account' => $sourceAccount, + 'targetAccount' => $targetAccount, + ]); + assert($followRequest); + + $followRequest->setNotify($notify); + $followRequest->setShowReblogs($showReblogs); + $this->entityManager->persist($followRequest); + $this->entityManager->flush(); + } |