summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCarl Schwan <carl@carlschwan.eu>2022-07-10 18:42:57 +0200
committerCarl Schwan <carl@carlschwan.eu>2022-08-09 12:23:47 +0200
commitf21a5a70d8a94bb9c447dbf59d6de5653c8a95a9 (patch)
tree6454b7261a6a59f79a0c9866a3ae7c8458200070
parentb023f93deb57693eb1d11131adfe388644d77a1e (diff)
More stuff around timeline handling and following new account
Signed-off-by: Carl Schwan <carl@carlschwan.eu>
-rw-r--r--.gitignore2
-rw-r--r--composer.json3
-rw-r--r--lib/AppInfo/Application.php11
-rw-r--r--lib/Command/AccountFollowing.php23
-rw-r--r--lib/Entity/Account.php69
-rw-r--r--lib/Entity/FollowRequest.php129
-rw-r--r--lib/InstanceUtils.php29
-rw-r--r--lib/Model/ActivityPub/ACore.php2
-rw-r--r--lib/Model/ActivityPub/Object/Follow.php51
-rw-r--r--lib/Serializer/AccountSerializer.php9
-rw-r--r--lib/Service/AccountFinder.php6
-rw-r--r--lib/Service/Feed/FeedManager.php10
-rw-r--r--lib/Service/Feed/IFeedProvider.php18
-rw-r--r--lib/Service/Feed/RedisFeedProvider.php12
-rw-r--r--lib/Service/FollowService.php157
-rw-r--r--tests/Serializer/AccountSerializerTest.php15
-rw-r--r--tests/Service/AccountFinderTest.php2
-rw-r--r--tests/Service/InstanceUtilsTest.php33
18 files changed, 416 insertions, 165 deletions
diff --git a/.gitignore b/.gitignore
index 58498d6a..203714a8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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();
+ }
+ private function directFollow(Account $