diff --git a/appinfo/database.xml b/appinfo/database.xml
index 4f980f25..c4c3184f 100644
--- a/appinfo/database.xml
+++ b/appinfo/database.xml
@@ -77,6 +77,13 @@
true
+
+ type
+ text
+ 31
+ false
+
+
actor_id
text
@@ -380,6 +387,13 @@
true
+
+ details
+ text
+ 3000
+ false
+
+
creation
timestamp
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 88ffacd0..c6c624be 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -5,7 +5,7 @@
Social
🎉 Nextcloud becomes part of the federated social networks!
- 0.0.52
+ 0.0.60
agpl
Maxence Lange
Julius Härtl
diff --git a/composer.lock b/composer.lock
index 0a63850a..37c1c411 100644
--- a/composer.lock
+++ b/composer.lock
@@ -12,12 +12,12 @@
"source": {
"type": "git",
"url": "https://github.com/daita/my-small-php-tools.git",
- "reference": "39ea0ce3f9442cb507c0bfaa4be36c3e90d6903e"
+ "reference": "36ea85a58ceb57a521c8f5a43effb4a7330e7b4c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/39ea0ce3f9442cb507c0bfaa4be36c3e90d6903e",
- "reference": "39ea0ce3f9442cb507c0bfaa4be36c3e90d6903e",
+ "url": "https://api.github.com/repos/daita/my-small-php-tools/zipball/36ea85a58ceb57a521c8f5a43effb4a7330e7b4c",
+ "reference": "36ea85a58ceb57a521c8f5a43effb4a7330e7b4c",
"shasum": ""
},
"require": {
@@ -40,7 +40,7 @@
}
],
"description": "My small PHP Tools",
- "time": "2018-11-29T12:56:09+00:00"
+ "time": "2018-11-29T16:46:38+00:00"
}
],
"packages-dev": [],
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
index 243217e2..d20ff7f1 100644
--- a/lib/AppInfo/Application.php
+++ b/lib/AppInfo/Application.php
@@ -32,7 +32,6 @@ namespace OCA\Social\AppInfo;
use OCP\AppFramework\App;
-use OCP\AppFramework\IAppContainer;
class Application extends App {
diff --git a/lib/Command/CacheRefresh.php b/lib/Command/CacheRefresh.php
index fae76806..26a688fe 100644
--- a/lib/Command/CacheRefresh.php
+++ b/lib/Command/CacheRefresh.php
@@ -105,6 +105,9 @@ class CacheRefresh extends Base {
$result = $this->actorService->manageCacheLocalActors();
$output->writeLn($result . ' local accounts regenerated');
+ $result = $this->personService->missingCacheRemoteActors();
+ $output->writeLn($result . ' remote accounts created');
+
$result = $this->personService->manageCacheRemoteActors();
$output->writeLn($result . ' remote accounts updated');
diff --git a/lib/Command/QueueProcess.php b/lib/Command/QueueProcess.php
index 780a87da..d9991b9d 100644
--- a/lib/Command/QueueProcess.php
+++ b/lib/Command/QueueProcess.php
@@ -31,9 +31,7 @@ declare(strict_types=1);
namespace OCA\Social\Command;
-use Exception;
use OC\Core\Command\Base;
-use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Service\ActivityService;
diff --git a/lib/Controller/ActivityPubController.php b/lib/Controller/ActivityPubController.php
index acd79fa3..83646e36 100644
--- a/lib/Controller/ActivityPubController.php
+++ b/lib/Controller/ActivityPubController.php
@@ -74,9 +74,6 @@ class ActivityPubController extends Controller {
/** @var MiscService */
private $miscService;
- /** @var NavigationController */
- private $navigationController;
-
/**
* ActivityPubController constructor.
@@ -94,13 +91,11 @@ class ActivityPubController extends Controller {
IRequest $request, SocialPubController $socialPubController,
ActivityService $activityService, ImportService $importService,
FollowService $followService, ActorService $actorService, NotesRequest $notesRequest,
- NavigationController $navigationController,
MiscService $miscService
) {
parent::__construct(Application::APP_NAME, $request);
$this->socialPubController = $socialPubController;
- $this->navigationController = $navigationController;
$this->activityService = $activityService;
$this->importService = $importService;
@@ -128,7 +123,7 @@ class ActivityPubController extends Controller {
*/
public function actor(string $username): Response {
if (!$this->checkSourceActivityStreams()) {
- return $this->navigationController->public($username);
+ return $this->socialPubController->actor($username);
}
try {
@@ -258,7 +253,7 @@ class ActivityPubController extends Controller {
*
* @return Response
*/
- public function followers(string $username, $data): Response {
+ public function followers(string $username): Response {
if (!$this->checkSourceActivityStreams()) {
return $this->socialPubController->followers($username);
diff --git a/lib/Controller/ConfigController.php b/lib/Controller/ConfigController.php
index dc2ee989..3059ec14 100644
--- a/lib/Controller/ConfigController.php
+++ b/lib/Controller/ConfigController.php
@@ -25,11 +25,8 @@ declare(strict_types=1);
namespace OCA\Social\Controller;
-use OCA\Activity\Data;
-use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Service\ConfigService;
use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http;
use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest;
@@ -46,16 +43,12 @@ class ConfigController extends Controller {
/**
* @param string $cloudAddress
+ *
* @return DataResponse
*/
public function setCloudAddress(string $cloudAddress): DataResponse {
- try {
- $this->configService->setCloudAddress($cloudAddress);
- return new DataResponse([]);
- } catch (SocialAppConfigException $e) {
- return new DataResponse([
- 'message' => $e->getMessage()
- ], Http::STATUS_BAD_REQUEST);
- }
+ $this->configService->setCloudAddress($cloudAddress);
+
+ return new DataResponse([]);
}
-}
\ No newline at end of file
+}
diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php
index eab56caa..03dba180 100644
--- a/lib/Controller/LocalController.php
+++ b/lib/Controller/LocalController.php
@@ -219,6 +219,9 @@ class LocalController extends Controller {
* @NoAdminRequired
* @NoSubAdminRequired
*
+ * @param int $since
+ * @param int $limit
+ *
* @return DataResponse
*/
public function streamDirect(int $since = 0, int $limit = 5): DataResponse {
@@ -294,12 +297,22 @@ class LocalController extends Controller {
* @param string $search
*
* @return DataResponse
+ * @throws Exception
*/
public function accountsSearch(string $search): DataResponse {
+ try {
+ $viewer = $this->actorService->getActorFromUserId($this->userId, true);
+ } catch (Exception $e) {
+ throw new Exception();
+ }
+
+ $this->personService->setViewerId($viewer->getId());
+
/* Look for an exactly matching account */
$match = null;
try {
$match = $this->personService->getFromAccount($search, false);
+ $match->setCompleteDetails(true);
} catch (Exception $e) {
}
@@ -410,6 +423,7 @@ class LocalController extends Controller {
* @NoCSRFRequired
* @NoAdminRequired
* @NoSubAdminRequired
+ *
* @param string $id
* @return DataResponse
*/
@@ -419,8 +433,10 @@ class LocalController extends Controller {
if ($actor->gotIcon()) {
$avatar = $actor->getIcon();
$document = $this->documentService->getFromCache($avatar->getId());
+
return new FileDisplayResponse($document);
}
+
return new NotFoundResponse();
} catch (Exception $e) {
return $this->fail($e);
diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php
index 3b76ea91..0911b12f 100644
--- a/lib/Controller/NavigationController.php
+++ b/lib/Controller/NavigationController.php
@@ -30,11 +30,9 @@ declare(strict_types=1);
namespace OCA\Social\Controller;
-use daita\MySmallPhpTools\Traits\TArrayTools;
use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
+use daita\MySmallPhpTools\Traits\TArrayTools;
use Exception;
-use OC\Files\Node\File;
-use OC\Files\SimpleFS\SimpleFile;
use OC\User\NoUserException;
use OCA\Social\AppInfo\Application;
use OCA\Social\Exceptions\AccountAlreadyExistsException;
@@ -48,9 +46,7 @@ use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\FileDisplayResponse;
-use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
-use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N;
@@ -132,9 +128,11 @@ class NavigationController extends Controller {
* @NoAdminRequired
* @NoSubAdminRequired
*
+ * @param string $path
+ *
* @return TemplateResponse
*/
- public function navigate($path = ''): TemplateResponse {
+ public function navigate(string $path = ''): TemplateResponse {
$data = [
'serverData' => [
'public' => false,
@@ -230,10 +228,11 @@ class NavigationController extends Controller {
* @NoAdminRequired
* @NoSubAdminRequired
*
+ * @param string $path
+ *
* @return TemplateResponse
- * @throws NoUserException
*/
- public function timeline($path = ''): TemplateResponse {
+ public function timeline(string $path = ''): TemplateResponse {
return $this->navigate();
}
@@ -244,43 +243,14 @@ class NavigationController extends Controller {
* @NoAdminRequired
* @NoSubAdminRequired
*
+ * @param string $path
+ *
* @return TemplateResponse
- * @throws NoUserException
*/
- public function account($path = ''): TemplateResponse {
+ public function account(string $path = ''): TemplateResponse {
return $this->navigate();
}
- /**
- * @NoCSRFRequired
- * @PublicPage
- *
- * @param $username
- *
- * @return RedirectResponse|PublicTemplateResponse
- */
- public function public($username) {
- // Redirect to external instances
- if (preg_match('/@[\w._-]+@[\w._-]+/', $username) === 1) {
- $actor = $this->personService->getFromAccount(substr($username, 1));
- return new RedirectResponse($actor->getUrl());
- }
- if (\OC::$server->getUserSession()
- ->isLoggedIn()) {
- return $this->navigate();
- }
-
- $data = [
- 'serverData' => [
- 'public' => true,
- ]
- ];
- $page = new PublicTemplateResponse(Application::APP_NAME, 'main', $data);
- $page->setHeaderTitle($this->l10n->t('Social') . ' ' . $username);
-
- return $page;
- }
-
/**
*
diff --git a/lib/Controller/QueueController.php b/lib/Controller/QueueController.php
index 26cdddfa..c85dee2b 100644
--- a/lib/Controller/QueueController.php
+++ b/lib/Controller/QueueController.php
@@ -32,12 +32,10 @@ namespace OCA\Social\Controller;
use daita\MySmallPhpTools\Traits\TAsync;
use OCA\Social\AppInfo\Application;
-use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Model\RequestQueue;
use OCA\Social\Service\ActivityService;
-use OCA\Social\Service\CurlService;
use OCA\Social\Service\MiscService;
use OCA\Social\Service\QueueService;
use OCP\AppFramework\Controller;
diff --git a/lib/Controller/SocialPubController.php b/lib/Controller/SocialPubController.php
index 4783c90e..efd35049 100644
--- a/lib/Controller/SocialPubController.php
+++ b/lib/Controller/SocialPubController.php
@@ -31,13 +31,19 @@ namespace OCA\Social\Controller;
use daita\MySmallPhpTools\Traits\Nextcloud\TNCDataResponse;
+use Exception;
use OCA\Social\AppInfo\Application;
-use OCA\Social\Service\ActivityService;
+use OCA\Social\Exceptions\CacheActorDoesNotExistException;
+use OCA\Social\Model\ActivityPub\Person;
+use OCA\Social\Service\ActivityPub\PersonService;
use OCA\Social\Service\ActorService;
use OCA\Social\Service\MiscService;
use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IL10N;
use OCP\IRequest;
class SocialPubController extends Controller {
@@ -45,12 +51,18 @@ class SocialPubController extends Controller {
use TNCDataResponse;
- /** @var ActivityService */
- private $activityService;
+ /** @var string */
+ private $userId;
+
+ /** @var IL10N */
+ private $l10n;
/** @var ActorService */
private $actorService;
+ /** @var PersonService */
+ private $personService;
+
/** @var MiscService */
private $miscService;
@@ -58,19 +70,23 @@ class SocialPubController extends Controller {
/**
* SocialPubController constructor.
*
- * @param ActivityService $activityService
- * @param ActorService $actorService
+ * @param $userId
* @param IRequest $request
+ * @param IL10N $l10n
+ * @param ActorService $actorService
+ * @param PersonService $personService
* @param MiscService $miscService
*/
public function __construct(
- ActivityService $activityService, ActorService $actorService, IRequest $request,
- MiscService $miscService
+ $userId, IRequest $request, IL10N $l10n, ActorService $actorService,
+ PersonService $personService, MiscService $miscService
) {
parent::__construct(Application::APP_NAME, $request);
- $this->activityService = $activityService;
+ $this->userId = $userId;
+ $this->l10n = $l10n;
$this->actorService = $actorService;
+ $this->personService = $personService;
$this->miscService = $miscService;
}
@@ -81,14 +97,48 @@ class SocialPubController extends Controller {
*
* @NoCSRFRequired
* @PublicPage
- * e*
*
* @param string $username
*
- * @return TemplateResponse
+ * @return Response
*/
- public function actor(string $username): TemplateResponse {
- return new TemplateResponse(Application::APP_NAME, 'actor', [], 'blank');
+ public function actor(string $username): Response {
+
+ try {
+ $actor = $this->personService->getFromLocalAccount($username);
+ $actor->setCompleteDetails(true);
+
+ $logged = false;
+ $ownAccount = false;
+ if ($this->userId !== null) {
+ $logged = true;
+ $local = $this->actorService->getActorFromUserId($this->userId, true);
+ if ($local->getId() === $actor->getId()) {
+ $ownAccount = true;
+ } else {
+ $this->fillActorWithLinks($actor, $local);
+ }
+ }
+
+ $data = [
+ 'serverData' => [
+ 'public' => true,
+ ],
+ 'actor' => $actor,
+ 'logged' => $logged,
+ 'ownAccount' => $ownAccount
+ ];
+
+
+ $page = new PublicTemplateResponse(Application::APP_NAME, 'main', $data);
+ $page->setHeaderTitle($this->l10n->t('Social') . ' ' . $username);
+
+ return $page;
+ } catch (CacheActorDoesNotExistException $e) {
+ return new NotFoundResponse();
+ } catch (Exception $e) {
+ return $this->fail($e);
+ }
}
@@ -139,6 +189,15 @@ class SocialPubController extends Controller {
return $this->success([$username, $postId]);
}
+
+ /**
+ * @param Person $actor
+ * @param Person $local
+ */
+ private function fillActorWithLinks(Person $actor, Person $local) {
+ $links = $this->actorService->getLinksBetweenPersons($local, $actor);
+ $actor->addDetailArray('link', $links);
+ }
+
}
-
diff --git a/lib/Cron/Queue.php b/lib/Cron/Queue.php
index 58e759a4..1dd1b8c4 100644
--- a/lib/Cron/Queue.php
+++ b/lib/Cron/Queue.php
@@ -31,18 +31,11 @@ declare(strict_types=1);
namespace OCA\Social\Cron;
-use Exception;
use OC\BackgroundJob\TimedJob;
use OCA\Social\AppInfo\Application;
-use OCA\Social\Exceptions\ActorDoesNotExistException;
use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
-use OCA\Social\Service\ActivityPub\DocumentService;
-use OCA\Social\Service\ActivityPub\PersonService;
use OCA\Social\Service\ActivityService;
-use OCA\Social\Service\ActorService;
-use OCA\Social\Service\CacheService;
-use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
use OCA\Social\Service\QueueService;
use OCP\AppFramework\QueryException;
diff --git a/lib/Db/CacheActorsRequest.php b/lib/Db/CacheActorsRequest.php
index f5b4454e..3a9a07b0 100644
--- a/lib/Db/CacheActorsRequest.php
+++ b/lib/Db/CacheActorsRequest.php
@@ -33,6 +33,7 @@ namespace OCA\Social\Db;
use DateTime;
use Exception;
use OCA\Social\Exceptions\CacheActorDoesNotExistException;
+use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Model\ActivityPub\Person;
use OCA\Social\Service\ConfigService;
use OCA\Social\Service\MiscService;
@@ -85,6 +86,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder {
->setValue('summary', $qb->createNamedParameter($actor->getSummary()))
->setValue('public_key', $qb->createNamedParameter($actor->getPublicKey()))
->setValue('source', $qb->createNamedParameter($actor->getSource()))
+ ->setValue('details', $qb->createNamedParameter(json_encode($actor->getDetails())))
->setValue(
'creation',
$qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE)
@@ -132,14 +134,61 @@ class CacheActorsRequest extends CacheActorsRequestBuilder {
*
* @param string $account
*
+ * @param string $viewerId
+ *
* @return Person
* @throws CacheActorDoesNotExistException
*/
- public function getFromAccount(string $account): Person {
+ public function getFromAccount(string $account, string $viewerId = ''): Person {
$qb = $this->getCacheActorsSelectSql();
$this->limitToAccount($qb, $account);
$this->leftJoinCacheDocuments($qb, 'icon_id');
+ if ($viewerId !== '') {
+ $this->leftJoinFollowAsViewer($qb, 'id', $viewerId, true, 'as_follower');
+ $this->leftJoinFollowAsViewer($qb, 'id', $viewerId, false, 'as_followed');
+ }
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ if ($data === false) {
+ throw new CacheActorDoesNotExistException();
+ }
+
+ $account = $this->parseCacheActorsSelectSql($data);
+
+ try {
+ $this->parseFollowLeftJoin($data, 'as_follower');
+ $account->addDetailBool('follower', true);
+ } catch (InvalidResourceException $e) {
+ }
+
+ try {
+ $this->parseFollowLeftJoin($data, 'as_followed');
+ $account->addDetailBool('following', true);
+ } catch (InvalidResourceException $e) {
+ }
+
+ return $account;
+ }
+
+
+ /**
+ * get Cached version of a local Actor, based on the preferred username
+ *
+ * @param string $account
+ *
+ * @return Person
+ * @throws CacheActorDoesNotExistException
+ */
+ public function getFromLocalAccount(string $account): Person {
+ $qb = $this->getCacheActorsSelectSql();
+ $this->limitToPreferredUsername($qb, $account);
+ $this->limitToLocal($qb, true);
+ $this->leftJoinCacheDocuments($qb, 'icon_id');
+
$cursor = $qb->execute();
$data = $cursor->fetch();
$cursor->closeCursor();
@@ -154,18 +203,42 @@ class CacheActorsRequest extends CacheActorsRequestBuilder {
/**
* @param string $search
+ * @param string $viewerId
*
* @return Person[]
*/
- public function searchAccounts(string $search): array {
+ public function searchAccounts(string $search, string $viewerId = ''): array {
$qb = $this->getCacheActorsSelectSql();
$this->searchInAccount($qb, $search);
$this->leftJoinCacheDocuments($qb, 'icon_id');
+ if ($viewerId !== '') {
+ $this->leftJoinFollowAsViewer($qb, 'id', $viewerId, true, 'as_follower');
+ $this->leftJoinFollowAsViewer($qb, 'id', $viewerId, false, 'as_followed');
+ }
+
$accounts = [];
$cursor = $qb->execute();
while ($data = $cursor->fetch()) {
- $accounts[] = $this->parseCacheActorsSelectSql($data);
+ $account = $this->parseCacheActorsSelectSql($data);
+
+ if ($viewerId !== '') {
+ try {
+ $this->parseFollowLeftJoin($data, 'as_follower');
+ $account->addDetailBool('following', true);
+ } catch (InvalidResourceException $e) {
+ }
+
+ try {
+ $this->parseFollowLeftJoin($data, 'as_followed');
+ $account->addDetailBool('follower', true);
+ } catch (InvalidResourceException $e) {
+ }
+
+ $account->setCompleteDetails(true);
+ }
+
+ $accounts[] = $account;
}
$cursor->closeCursor();
diff --git a/lib/Db/CacheActorsRequestBuilder.php b/lib/Db/CacheActorsRequestBuilder.php
index 1d6e9668..cb9ce58a 100644
--- a/lib/Db/CacheActorsRequestBuilder.php
+++ b/lib/Db/CacheActorsRequestBuilder.php
@@ -80,7 +80,7 @@ class CacheActorsRequestBuilder extends CoreRequestBuilder {
'ca.id', 'ca.account', 'ca.following', 'ca.followers', 'ca.inbox',
'ca.shared_inbox', 'ca.outbox', 'ca.featured', 'ca.url', 'ca.type',
'ca.preferred_username', 'ca.name', 'ca.summary',
- 'ca.public_key', 'ca.local', 'ca.source', 'ca.creation'
+ 'ca.public_key', 'ca.local', 'ca.details', 'ca.source', 'ca.creation'
)
->from(self::TABLE_CACHE_ACTORS, 'ca');
diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php
index 8d4179e6..114dc671 100644
--- a/lib/Db/CoreRequestBuilder.php
+++ b/lib/Db/CoreRequestBuilder.php
@@ -37,6 +37,7 @@ use Doctrine\DBAL\Query\QueryBuilder;
use Exception;
use OCA\Social\Exceptions\InvalidResourceException;
use OCA\Social\Model\ActivityPub\Document;
+use OCA\Social\Model\ActivityPub\Follow;
use OCA\Social\Model\ActivityPub\Image;
use OCA\Social\Model\ActivityPub\Person;
use OCA\Social\Service\ConfigService;
@@ -134,7 +135,7 @@ class CoreRequestBuilder {
* @param string $username
*/
protected function limitToPreferredUsername(IQueryBuilder &$qb, string $username) {
- $this->limitToDBField($qb, 'preferred_username', $username);
+ $this->limitToDBField($qb, 'preferred_username', $username, false);
}
/**
@@ -195,6 +196,18 @@ class CoreRequestBuilder {
}
+ /**
+ * Limit the request to the FollowId
+ *
+ * @param IQueryBuilder $qb
+ * @param bool $accepted
+ */
+ protected function limitToAccepted(IQueryBuilder &$qb, bool $accepted) {
+ $this->limitToDBField($qb, 'accepted', ($accepted) ? '1' : '0');
+
+ }
+
+
/**
* Limit the request to the ServiceId
*
@@ -272,6 +285,17 @@ class CoreRequestBuilder {
}
+ /**
+ * Limit the request to the url
+ *
+ * @param IQueryBuilder $qb
+ * @param string $actorId
+ */
+ protected function limitToAttributedTo(IQueryBuilder &$qb, string $actorId) {
+ $this->limitToDBField($qb, 'attributed_to', $actorId, false);
+ }
+
+
/**
* Limit the request to the status
*
@@ -507,7 +531,6 @@ class CoreRequestBuilder {
$expr = $qb->expr();
$pf = $this->defaultSelectAlias;
-// /** @noinspection PhpMethodParametersCountMismatchInspection */
$qb->selectAlias('ca.id', 'cacheactor_id')
->selectAlias('ca.type', 'cacheactor_type')
->selectAlias('ca.account', 'cacheactor_account')
@@ -569,7 +592,6 @@ class CoreRequestBuilder {
$expr = $qb->expr();
$pf = $this->defaultSelectAlias;
-// /** @noinspection PhpMethodParametersCountMismatchInspection */
$qb->selectAlias('cd.id', 'cachedocument_id')
->selectAlias('cd.type', 'cachedocument_type')
->selectAlias('cd.mime_type', 'cachedocument_mime_type')
@@ -612,6 +634,75 @@ class CoreRequestBuilder {
return $document;
}
+
+ /**
+ * @param IQueryBuilder $qb
+ * @param string $fieldActorId
+ * @param string $viewerId
+ * @param bool $asFollower
+ * @param string $prefix
+ */
+ protected function leftJoinFollowAsViewer(
+ IQueryBuilder &$qb, string $fieldActorId, string $viewerId, bool $asFollower = true,
+ string $prefix = 'follow'
+ ) {
+ if ($qb->getType() !== QueryBuilder::SELECT) {
+ return;
+ }
+
+ $expr = $qb->expr();
+ $pf = $this->defaultSelectAlias;
+
+ $andX = $expr->andX();
+ if ($asFollower === true) {
+ $andX->add($expr->eq($pf . '.' . $fieldActorId, $prefix . '_f.object_id'));
+ $andX->add($expr->eq($prefix . '_f.actor_id', $qb->createNamedParameter($viewerId)));
+ } else {
+ $andX->add($expr->eq($pf . '.' . $fieldActorId, $prefix . '_f.actor_id'));
+ $andX->add($expr->eq($prefix . '_f.object_id', $qb->createNamedParameter($viewerId)));
+ }
+
+ $qb->selectAlias($prefix . '_f.id', $prefix . '_id')
+ ->selectAlias($prefix . '_f.type', $prefix . '_type')
+ ->selectAlias($prefix . '_f.actor_id', $prefix . '_actor_id')
+ ->selectAlias($prefix . '_f.object_id', $prefix . '_object_id')
+ ->selectAlias($prefix . '_f.follow_id', $prefix . '_follow_id')
+ ->selectAlias($prefix . '_f.creation', $prefix . '_creation')
+ ->leftJoin(
+ $this->defaultSelectAlias, CoreRequestBuilder::TABLE_SERVER_FOLLOWS, $prefix . '_f',
+ $andX
+ );
+ }
+
+
+ /**
+ * @param array $data
+ * @param string $prefix
+ *
+ * @return Follow
+ * @throws InvalidResourceException
+ */
+ protected function parseFollowLeftJoin(array $data, string $prefix): Follow {
+ $new = [];
+
+ $length = strlen($prefix) + 1;
+ foreach ($data as $k => $v) {
+ if (substr($k, 0, $length) === $prefix . '_') {
+ $new[substr($k, $length)] = $v;
+ }
+ }
+
+ $follow = new Follow();
+ $follow->importFromDatabase($new);
+
+ if ($follow->getType() !== Follow::TYPE) {
+ throw new InvalidResourceException();
+ }
+
+ return $follow;
+ }
+
+
}
diff --git a/lib/Db/FollowsRequest.php b/lib/Db/FollowsRequest.php
index b4197b17..fdcb04ff 100644
--- a/lib/Db/FollowsRequest.php
+++ b/lib/Db/FollowsRequest.php
@@ -31,6 +31,7 @@ declare(strict_types=1);
namespace OCA\Social\Db;
+use daita\MySmallPhpTools\Traits\TArrayTools;
use OCA\Social\Exceptions\FollowDoesNotExistException;
use OCA\Social\Model\ActivityPub\Follow;
@@ -43,6 +44,9 @@ use OCA\Social\Model\ActivityPub\Follow;
class FollowsRequest extends FollowsRequestBuilder {
+ use TArrayTools;
+
+
/**
* Insert a new Note in the database.
*
@@ -52,6 +56,7 @@ class FollowsRequest extends FollowsRequestBuilder {
$qb = $this->getFollowsInsertSql();
$qb->setValue('id', $qb->createNamedParameter($follow->getId()))
->setValue('actor_id', $qb->createNamedParameter($follow->getActorId()))
+ ->setValue('type', $qb->createNamedParameter($follow->getType()))
->setValue('object_id', $qb->createNamedParameter($follow->getObjectId()))
->setValue('follow_id', $qb->createNamedParameter($follow->getFollowId()));
@@ -96,6 +101,42 @@ class FollowsRequest extends FollowsRequestBuilder {
}
+ /**
+ * @param string $actorId
+ *
+ * @return int
+ */
+ public function countFollowers(string $actorId): int {
+ $qb = $this->countFollowsSelectSql();
+ $this->limitToObjectId($qb, $actorId);
+ $this->limitToAccepted($qb, true);
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ return $this->getInt('count', $data, 0);
+ }
+
+
+ /**
+ * @param string $actorId
+ *
+ * @return int
+ */
+ public function countFollowing(string $actorId): int {
+ $qb = $this->countFollowsSelectSql();
+ $this->limitToActorId($qb, $actorId);
+ $this->limitToAccepted($qb, true);
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ return $this->getInt('count', $data, 0);
+ }
+
+
/**
* @param string $followId
*
diff --git a/lib/Db/FollowsRequestBuilder.php b/lib/Db/FollowsRequestBuilder.php
index 1947dd4c..0f50b37e 100644
--- a/lib/Db/FollowsRequestBuilder.php
+++ b/lib/Db/FollowsRequestBuilder.php
@@ -83,7 +83,23 @@ class FollowsRequestBuilder extends CoreRequestBuilder {
$qb = $this->dbConnection->getQueryBuilder();
/** @noinspection PhpMethodParametersCountMismatchInspection */
- $qb->select('f.id', 'f.actor_id', 'f.object_id', 'f.follow_id', 'f.creation')
+ $qb->select('f.id', 'f.type', 'f.actor_id', 'f.object_id', 'f.follow_id', 'f.creation')
+ ->from(self::TABLE_SERVER_FOLLOWS, 'f');
+
+ $this->defaultSelectAlias = 'f';
+
+ return $qb;
+ }
+
+
+ /**
+ * Base of the Sql Select request for Shares
+ *
+ * @return IQueryBuilder
+ */
+ protected function countFollowsSelectSql(): IQueryBuilder {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count')
->from(self::TABLE_SERVER_FOLLOWS, 'f');
$this->defaultSelectAlias = 'f';
@@ -112,10 +128,7 @@ class FollowsRequestBuilder extends CoreRequestBuilder {
*/
protected function parseFollowsSelectSql($data): Follow {
$follow = new Follow();
- $follow->setId($data['id'])
- ->setActorId($data['actor_id'])
- ->setObjectId($data['object_id']);
- $follow->setFollowId($data['follow_id']);
+ $follow->importFromDatabase($data);
try {
$actor = $this->parseCacheActorsLeftJoin($data);
diff --git a/lib/Db/NotesRequest.php b/lib/Db/NotesRequest.php
index e2c7d9c8..f4bf6842 100644
--- a/lib/Db/NotesRequest.php
+++ b/lib/Db/NotesRequest.php
@@ -137,6 +137,23 @@ class NotesRequest extends NotesRequestBuilder {
}
+ /**
+ * @param string $actorId
+ *
+ * @return int
+ */
+ public function countNotesFromActorId(string $actorId): int {
+ $qb = $this->countNotesSelectSql();
+ $this->limitToAttributedTo($qb, $actorId);
+
+ $cursor = $qb->execute();
+ $data = $cursor->fetch();
+ $cursor->closeCursor();
+
+ return $this->getInt('count', $data, 0);
+ }
+
+
/**
* @param string $actorId
* @param int $since
diff --git a/lib/Db/NotesRequestBuilder.php b/lib/Db/NotesRequestBuilder.php
index 90ff24e1..e6f2cb18 100644
--- a/lib/Db/NotesRequestBuilder.php
+++ b/lib/Db/NotesRequestBuilder.php
@@ -92,6 +92,22 @@ class NotesRequestBuilder extends CoreRequestBuilder {
}
+ /**
+ * Base of the Sql Select request for Shares
+ *
+ * @return IQueryBuilder
+ */
+ protected function countNotesSelectSql(): IQueryBuilder {
+ $qb = $this->dbConnection->getQueryBuilder();
+ $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count')
+ ->from(self::TABLE_SERVER_NOTES, 'sn');
+
+ $this->defaultSelectAlias = 'sn';
+
+ return $qb;
+ }
+
+
/**
* Base of the Sql Delete request
*
diff --git a/lib/Db/RequestQueueRequest.php b/lib/Db/RequestQueueRequest.php
index 4e07ffba..941c1913 100644
--- a/lib/Db/RequestQueueRequest.php
+++ b/lib/Db/RequestQueueRequest.php
@@ -35,7 +35,6 @@ use DateTime;
use Exception;
use OCA\Social\Exceptions\QueueStatusException;
use OCA\Social\Model\RequestQueue;
-use OCA\Social\Service\QueueService;
use OCP\DB\QueryBuilder\IQueryBuilder;
diff --git a/lib/Model/ActivityPub/ACore.php b/lib/Model/ActivityPub/ACore.php
index 6cc36c17..80f134a4 100644
--- a/lib/Model/ActivityPub/ACore.php
+++ b/lib/Model/ActivityPub/ACore.php
@@ -872,7 +872,6 @@ abstract class ACore implements JsonSerializable {
$this->setPublished($this->get('published', $data, ''));
$this->setActorId($this->get('actor', $data, ''));
$this->setObjectId($this->get('object', $data, ''));
- $this->setLocal(($this->getInt('local', $data, 0) === 1));
}
diff --git a/lib/Model/ActivityPub/Follow.php b/lib/Model/ActivityPub/Follow.php
index c7818826..26546265 100644
--- a/lib/Model/ActivityPub/Follow.php
+++ b/lib/Model/ActivityPub/Follow.php
@@ -88,6 +88,16 @@ class Follow extends ACore implements JsonSerializable {
}
+ /**
+ * @param array $data
+ */
+ public function importFromDatabase(array $data) {
+ parent::importFromDatabase($data);
+
+ $this->setFollowId($this->get('follow_id', $data, ''));
+ }
+
+
/**
* @return array
*/
diff --git a/lib/Model/ActivityPub/Person.php b/lib/Model/ActivityPub/Person.php
index 41f3b375..bfa25a16 100644
--- a/lib/Model/ActivityPub/Person.php
+++ b/lib/Model/ActivityPub/Person.php
@@ -85,6 +85,10 @@ class Person extends ACore implements JsonSerializable {
/** @var string */
private $featured = '';
+ /** @var array */
+ private $details = [];
+
+
/**
* Person constructor.
*
@@ -339,6 +343,73 @@ class Person extends ACore implements JsonSerializable {
}
+ /**
+ * @return array
+ */
+ public function getDetails(): array {
+ return $this->details;
+ }
+
+ /**
+ * @param string $detail
+ * @param string $value
+ *
+ * @return Person
+ */
+ public function addDetail(string $detail, string $value): Person {
+ $this->details[$detail] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string $detail
+ * @param int $value
+ *
+ * @return Person
+ */
+ public function addDetailInt(string $detail, int $value): Person {
+ $this->details[$detail] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string $detail
+ * @param array $value
+ *
+ * @return Person
+ */
+ public function addDetailArray(string $detail, array $value): Person {
+ $this->details[$detail] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param string $detail
+ * @param bool $value
+ *
+ * @return Person
+ */
+ public function addDetailBool(string $detail, bool $value): Person {
+ $this->details[$detail] = $value;
+
+ return $this;
+ }
+
+ /**
+ * @param array $details
+ *
+ * @return Person
+ */
+ public function setDetails(array $details): Person {
+ $this->details = $details;
+
+ return $this;
+ }
+
+
/**
* @param array $data
*
@@ -365,12 +436,6 @@ class Person extends ACore implements JsonSerializable {
$this->setIcon($icon);
}
-
-// ->setCreation($this->getInt('creation', $data, 0));
-
-// if ($this->getPreferredUsername() === '') {
-// $this->setType('Invalid');
-// }
}
@@ -390,11 +455,8 @@ class Person extends ACore implements JsonSerializable {
->setFollowing($this->get('following', $data, ''))
->setSharedInbox($this->get('shared_inbox', $data, ''))
->setFeatured($this->get('featured', $data, ''))
+ ->setDetails($this->getArray('details', $data, []))
->setCreation($this->getInt('creation', $data, 0));
-
-// if ($this->getPreferredUsername() === '') {
-// $this->setType('Invalid');
-// }
}
@@ -402,7 +464,7 @@ class Person extends ACore implements JsonSerializable {
* @return array
*/
public function jsonSerialize(): array {
- return array_merge(
+ $result = array_merge(
parent::jsonSerialize(),
[
'aliases' => [
@@ -425,8 +487,12 @@ class Person extends ACore implements JsonSerializable {
]
]
);
+
+ if ($this->isCompleteDetails()) {
+ $result['details'] = $this->getDetails();
+ }
+
+ return $result;
}
-
}
-
diff --git a/lib/Service/ActivityPub/NoteService.php b/lib/Service/ActivityPub/NoteService.php
index e457e135..ebce852b 100644
--- a/lib/Service/ActivityPub/NoteService.php
+++ b/lib/Service/ActivityPub/NoteService.php
@@ -336,9 +336,12 @@ class NoteService implements ICoreService {
/**
* @param Person $actor
*
+ * @param int $since
+ * @param int $limit
+ *
* @return Note[]
*/
- public function getDirectNotesForActor(Person $actor, $since, $limit): array {
+ public function getDirectNotesForActor(Person $actor, int $since = 0, int $limit = 5): array {
return $this->notesRequest->getDirectNotesForActorId($actor->getId(), $since, $limit);
}
diff --git a/lib/Service/ActivityPub/PersonService.php b/lib/Service/ActivityPub/PersonService.php
index c170f988..c6d9a074 100644
--- a/lib/Service/ActivityPub/PersonService.php
+++ b/lib/Service/ActivityPub/PersonService.php
@@ -76,6 +76,10 @@ class PersonService implements ICoreService {
private $miscService;
+ /** @var string */
+ private $viewerId = '';
+
+
/**
* UndoService constructor.
*
@@ -97,6 +101,18 @@ class PersonService implements ICoreService {
}
+ /**
+ * @param string $viewerId
+ */
+ public function setViewerId(string $viewerId) {
+ $this->viewerId = $viewerId;
+ }
+
+ public function getViewerId(): string {
+ return $this->viewerId;
+ }
+
+
/**
* @param Person $actor
* @param bool $refresh
@@ -169,7 +185,7 @@ class PersonService implements ICoreService {
public function getFromAccount(string $account, bool $retrieve = true): Person {
try {
- $actor = $this->cacheActorsRequest->getFromAccount($account);
+ $actor = $this->cacheActorsRequest->getFromAccount($account, $this->getViewerId());
} catch (CacheActorDoesNotExistException $e) {
if (!$retrieve) {
throw new CacheActorDoesNotExistException();
@@ -189,6 +205,17 @@ class PersonService implements ICoreService {
}
+ /**
+ * @param string $account
+ *
+ * @return Person
+ * @throws CacheActorDoesNotExistException
+ */
+ public function getFromLocalAccount(string $account): Person {
+ return $this->cacheActorsRequest->getFromLocalAccount($account);
+ }
+
+
/**
* @param array $object
*
@@ -208,18 +235,7 @@ class PersonService implements ICoreService {
}
$actor->setSource(json_encode($object, JSON_UNESCAPED_SLASHES));
-// $actor->setPreferredUsername($this->get('preferredUsername', $object, ''));
-// $actor->setPublicKey($this->get('publicKey.publicKeyPem', $object));
-// $actor->setSharedInbox($this->get('endpoints.sharedInbox', $object));
-// $actor->setAccount($actor->getPreferredUsername() . '@' . $this->get('_host', $object));
-//
-// $icon = new Image($actor);
-// $icon->setUrlCloud($this->configService->getCloudAddress());
-// $icon->import($this->getArray('icon', $object, []));
-// if ($icon->getType() === Image::TYPE) {
-// $actor->setIcon($icon);
-// }
-//
+
return $actor;
}
@@ -229,7 +245,7 @@ class PersonService implements ICoreService {
* @return Person[]
*/
public function searchCachedAccounts(string $search): array {
- return $this->cacheActorsRequest->searchAccounts($search);
+ return $this->cacheActorsRequest->searchAccounts($search, $this->getViewerId());
}
@@ -260,6 +276,25 @@ class PersonService implements ICoreService {
}
+ /**
+ * @throws Exception
+ * @return int
+ */
+ public function missingCacheRemoteActors(): int {
+ // TODO - looking for missing cache remote actors...
+ $missing = [];
+
+ foreach ($missing as $item) {
+ try {
+ $this->getFromId($item->getId());
+ } catch (Exception $e) {
+ }
+ }
+
+ return sizeof($missing);
+ }
+
+
/**
* @throws Exception
* @return int
diff --git a/lib/Service/ActivityService.php b/lib/Service/ActivityService.php
index 37bf5606..57ba561c 100644
--- a/lib/Service/ActivityService.php
+++ b/lib/Service/ActivityService.php
@@ -284,8 +284,12 @@ class ActivityService {
);
} catch (ActorDoesNotExistException $e) {
$this->queueService->deleteRequest($queue);
+
+ return;
} catch (Request410Exception $e) {
$this->queueService->deleteRequest($queue);
+
+ return;
}
try {
diff --git a/lib/Service/ActorService.php b/lib/Service/ActorService.php
index 70fb824e..33ee2cfd 100644
--- a/lib/Service/ActorService.php
+++ b/lib/Service/ActorService.php
@@ -34,8 +34,11 @@ use daita\MySmallPhpTools\Traits\TArrayTools;
use Exception;
use OC\User\NoUserException;
use OCA\Social\Db\ActorsRequest;
+use OCA\Social\Db\FollowsRequest;
+use OCA\Social\Db\NotesRequest;
use OCA\Social\Exceptions\AccountAlreadyExistsException;
use OCA\Social\Exceptions\ActorDoesNotExistException;
+use OCA\Social\Exceptions\FollowDoesNotExistException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Model\ActivityPub\Person;
use OCA\Social\Service\ActivityPub\PersonService;
@@ -55,6 +58,12 @@ class ActorService {
/** @var ActorsRequest */
private $actorsRequest;
+ /** @var FollowsRequest */
+ private $followsRequest;
+
+ /** @var NotesRequest */
+ private $notesRequest;
+
/** @var PersonService */
private $personService;
@@ -69,15 +78,19 @@ class ActorService {
* ActorService constructor.
*
* @param ActorsRequest $actorsRequest
+ * @param FollowsRequest $followsRequest
+ * @param NotesRequest $notesRequest
* @param PersonService $personService
* @param ConfigService $configService
* @param MiscService $miscService
*/
public function __construct(
- ActorsRequest $actorsRequest, PersonService $personService, ConfigService $configService,
- MiscService $miscService
+ ActorsRequest $actorsRequest, FollowsRequest $followsRequest, NotesRequest $notesRequest,
+ PersonService $personService, ConfigService $configService, MiscService $miscService
) {
$this->actorsRequest = $actorsRequest;
+ $this->followsRequest = $followsRequest;
+ $this->notesRequest = $notesRequest;
$this->personService = $personService;
$this->configService = $configService;
$this->miscService = $miscService;
@@ -114,15 +127,26 @@ class ActorService {
/**
* @param string $userId
+ * @param bool $create
*
* @return Person
+ * @throws AccountAlreadyExistsException
* @throws ActorDoesNotExistException
* @throws NoUserException
* @throws SocialAppConfigException
*/
- public function getActorFromUserId(string $userId): Person {
+ public function getActorFromUserId(string $userId, bool $create = false): Person {
$this->miscService->confirmUserId($userId);
- $actor = $this->actorsRequest->getFromUserId($userId);
+ try {
+ $actor = $this->actorsRequest->getFromUserId($userId);
+ } catch (ActorDoesNotExistException $e) {
+ if ($create) {
+ $this->createActor($userId, $userId);
+ $actor = $this->actorsRequest->getFromUserId($userId);
+ } else {
+ throw new ActorDoesNotExistException();
+ }
+ }
return $actor;
}
@@ -177,6 +201,35 @@ class ActorService {
}
+ /**
+ * @param Person $local
+ * @param Person $actor
+ *
+ * @return array
+ */
+ public function getLinksBetweenPersons(Person $local, Person $actor): array {
+
+ $links = [
+ 'follower' => false,
+ 'following' => false
+ ];
+
+ try {
+ $this->followsRequest->getByPersons($local->getId(), $actor->getId());
+ $links['following'] = true;
+ } catch (FollowDoesNotExistException $e) {
+ }
+
+ try {
+ $this->followsRequest->getByPersons($actor->getId(), $local->getId());
+ $links['follower'] = true;
+ } catch (FollowDoesNotExistException $e) {
+ }
+
+ return $links;
+ }
+
+
/**
* @param string $username
* @param bool $refresh
@@ -185,7 +238,14 @@ class ActorService {
*/
public function cacheLocalActorByUsername(string $username, bool $refresh = false) {
try {
- $actor = $this->getActor($username);
+ $actor = $this->getActor($username);;
+ $count = [
+ 'followers', $this->followsRequest->countFollowers($actor->getId()),
+ 'following', $this->followsRequest->countFollowing($actor->getId()),
+ 'post', $this->notesRequest->countNotesFromActorId($actor->getId())
+ ];
+ $actor->addDetailArray('count', $count);
+
$this->personService->cacheLocalActor($actor, $refresh);
} catch (ActorDoesNotExistException $e) {
}
diff --git a/lib/Service/ConfigService.php b/lib/Service/ConfigService.php
index 968859dd..83741f16 100644
--- a/lib/Service/ConfigService.php
+++ b/lib/Service/ConfigService.php
@@ -237,8 +237,6 @@ class ConfigService {
/**
* @param string $cloudAddress
- *
- * @return string
*/
public function setCloudAddress(string $cloudAddress) {
$this->setAppValue(self::SOCIAL_ADDRESS, $cloudAddress);
diff --git a/lib/Service/CurlService.php b/lib/Service/CurlService.php
index 2218a52d..e073cdf7 100644
--- a/lib/Service/CurlService.php
+++ b/lib/Service/CurlService.php
@@ -249,8 +249,6 @@ class CurlService {
/**
* @param int $code
*
- * @param Request $request
- *
* @throws Request410Exception
*/
private function parseRequestResultCode410(int $code) {
diff --git a/lib/Service/MiscService.php b/lib/Service/MiscService.php
index 0feb57fc..6be52d60 100644
--- a/lib/Service/MiscService.php
+++ b/lib/Service/MiscService.php
@@ -31,7 +31,6 @@ declare(strict_types=1);
namespace OCA\Social\Service;
-use Exception;
use OC\User\NoUserException;
use OCA\Social\AppInfo\Application;
use OCP\ILogger;
diff --git a/lib/Service/PostService.php b/lib/Service/PostService.php
index 980b6089..0ea482be 100644
--- a/lib/Service/PostService.php
+++ b/lib/Service/PostService.php
@@ -33,7 +33,6 @@ namespace OCA\Social\Service;
use Exception;
use OC\User\NoUserException;
use OCA\Social\Exceptions\ActorDoesNotExistException;
-use OCA\Social\Exceptions\RequestException;
use OCA\Social\Exceptions\SocialAppConfigException;
use OCA\Social\Model\ActivityPub\ACore;
use OCA\Social\Model\Post;
diff --git a/lib/Service/QueueService.php b/lib/Service/QueueService.php
index 36cc46dd..d7228acb 100644
--- a/lib/Service/QueueService.php
+++ b/lib/Service/QueueService.php
@@ -78,6 +78,7 @@ class QueueService {
/**
* @param array $instancePaths
* @param ACore $item
+ * @param string $author
*
* @return string
* @throws Exception
diff --git a/whenanaccountisdeleter.json b/whenanaccountisdeleter.json
new file mode 100644
index 00000000..af8c879e
--- /dev/null
+++ b/whenanaccountisdeleter.json
@@ -0,0 +1,45 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "movedTo": {
+ "@id": "as:movedTo",
+ "@type": "@id"
+ },
+ "Hashtag": "as:Hashtag",
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "focalPoint": {
+ "@container": "@list",
+ "@id": "toot:focalPoint"
+ },
+ "featured": {
+ "@id": "toot:featured",
+ "@type": "@id"
+ },
+ "schema": "http://schema.org#",
+ "PropertyValue": "schema:PropertyValue",
+ "value": "schema:value"
+ }
+ ],
+ "id": "https://mastodon.social/users/twospirit#delete",
+ "type": "Delete",
+ "actor": "https://mastodon.social/users/twospirit",
+ "to": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "object": "https://mastodon.social/users/twospirit",
+ "signature": {
+ "type": "RsaSignature2017",
+ "creator": "https://mastodon.social/users/twospirit#main-key",
+ "created": "2018-11-26T14:33:30Z",
+ "signatureValue": "e20O0AYwAGLgHyb/xD1fO+v7gnE8aONFR3iMb/4ULd+Qjz4fV1/ay0IRFdatWGRP4uG/XzjdW6hJTR69wjQUag9k0JOvHkOssUHIXkmBEsKZURyEVWrjT1+Dyx1ZFwsbhjSXCkvbz70mq+1JPx2zDK+fTsG8TPIKBpvj9LYmQbF3Z4n7wRRxibCCL8oGlrHlwWwABYkZKoruLYJya9a6eVvbCD1P5TO+M1CUdMHhqez8k3ll50ZsGRlHqnojR0V9AjMixuMNMx4qvcC+wwJb4QBorfPJDh843Pw4lybwbs5/bXwErxR+Infc61w/dkyw1MvZdPYD2dy+uOHdndRRdQ=="
+ }
+}