Signed-off-by: Carl Schwan <carl@carlschwan.eu>
pull/1439/head
Carl Schwan 2022-09-12 13:42:43 +02:00
rodzic 9f49b14657
commit a2fca565a1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: C3AA6B3A5EFA7AC5
9 zmienionych plików z 378 dodań i 18 usunięć

Wyświetl plik

@ -36,6 +36,7 @@ class MediaApiController extends Controller {
private AccountFinder $accountFinder;
private IEntityManager $entityManager;
private IURLGenerator $generator;
private LoggerInterface $logger;
public const IMAGE_MIME_TYPES = [
'image/png',

Wyświetl plik

@ -0,0 +1,174 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\Social\Controller;
use OCA\Social\Entity\MediaAttachment;
use OCA\Social\Service\AccountFinder;
use OCA\Social\Service\PostServiceStatus;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\DB\ORM\IEntityManager;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\AppFramework\Controller;
use OCP\Files\IMimeTypeDetector;
use OCP\Image;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Util;
use Psr\Log\LoggerInterface;
class StatusApiController extends Controller {
private IL10N $l10n;
private IMimeTypeDetector $mimeTypeDetector;
private IAppData $appData;
private IUserSession $userSession;
private AccountFinder $accountFinder;
private IEntityManager $entityManager;
private IURLGenerator $generator;
private LoggerInterface $logger;
private PostServiceStatus $postServiceStatus;
public function __construct(
string $appName,
IRequest $request,
IL10N $l10n,
IUserSession $userSession,
AccountFinder $accountFinder,
IEntityManager $entityManager,
IURLGenerator $generator,
LoggerInterface $logger,
PostServiceStatus $postServiceStatus
) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->userSession = $userSession;
$this->accountFinder = $accountFinder;
$this->entityManager = $entityManager;
$this->generator = $generator;
$this->logger = $logger;
$this->postServiceStatus = $postServiceStatus;
}
/**
* Publish new status
* @NoAdminRequired
*/
public function publishStatus(
?string $status,
array $media_ids,
?bool $sensitive,
?string $spoiler_text
): DataResponse {
if ($sensitive === null) {
$sensitive = false;
}
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
$status = $this->postServiceStatus->create($account, [
'text' => $status,
'spoilerText' => $spoiler_text,
'sensitive' => $sensitive,
]);
return new DataResponse($status->toMastodonApi());
}
/**
* View specific status
* @NoAdminRequired
*/
public function getStatus(string $id): DataResponse {
$statusRepository = $this->entityManager->getRepository(Status::class);
$status = $statusRepository->findOneBy([
'id' => $id,
]);
if ($status === null) {
return new DataResponse(["error" => "Record not found"]);
}
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
if (!$this->canRead($account, $status)) {
return new DataResponse(["error" => "Record not found"]);
}
return new DataResponse($status->toMastodonApi());
}
/**
* Delete specific status
* @NoAdminRequired
*/
public function deleteStatus(string $id): DataResponse {
$statusRepository = $this->entityManager->getRepository(Status::class);
$status = $statusRepository->findOneBy([
'id' => $id,
]);
if ($status === null) {
return new DataResponse(["error" => "Record not found"]);
}
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
if ($status->getAccount()->getId() !== $account->getId()) {
return new DataResponse(["error" => "Record not found"]);
}
$this->entityManager->delete($status);
$this->entityManager->flush();
return new DataResponse($status->toMastodonApi());
}
/**
* Context of a specific status
* @NoAdminRequired
*/
public function contextStatus(string $id): DataResponse {
$statusRepository = $this->entityManager->getRepository(Status::class);
$status = $statusRepository->findOneBy([
'id' => $id,
]);
if ($status === null) {
return new DataResponse(["error" => "Record not found"]);
}
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
if (!$this->canRead($account, $status)) {
return new DataResponse(["error" => "Record not found"]);
}
return new DataResponse([
'ancestors' => [],
'descendants' => [],
]);
}
public function reblogedBy(string $id): DataResponse {
$statusRepository = $this->entityManager->getRepository(Status::class);
$status = $statusRepository->findOneBy([
'id' => $id,
]);
if ($status === null) {
return new DataResponse(["error" => "Record not found"]);
}
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
if (!$this->canRead($account, $status)) {
return new DataResponse(["error" => "Record not found"]);
}
return new DataResponse([]);
}
private function canRead(Account $accout, Status $status): bool {
return true;
}
}

Wyświetl plik

@ -0,0 +1,98 @@
<?php
declare(strict_types=1);
// SPDX-FileCopyrightText: Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace OCA\Social\Controller;
use OCA\Social\Entity\MediaAttachment;
use OCA\Social\Service\AccountFinder;
use OCA\Social\Service\PostServiceStatus;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\NotFoundResponse;
use OCP\DB\ORM\IEntityManager;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\IL10N;
use OCP\AppFramework\Controller;
use OCP\Files\IMimeTypeDetector;
use OCP\Image;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Util;
use Psr\Log\LoggerInterface;
class TimelineApiController extends Controller {
private IL10N $l10n;
private IMimeTypeDetector $mimeTypeDetector;
private IAppData $appData;
private IUserSession $userSession;
private AccountFinder $accountFinder;
private IEntityManager $entityManager;
private IURLGenerator $generator;
private LoggerInterface $logger;
private PostServiceStatus $postServiceStatus;
public function __construct(
string $appName,
IRequest $request,
IL10N $l10n,
IUserSession $userSession,
AccountFinder $accountFinder,
IEntityManager $entityManager,
IURLGenerator $generator,
LoggerInterface $logger,
) {
parent::__construct($appName, $request);
$this->l10n = $l10n;
$this->userSession = $userSession;
$this->accountFinder = $accountFinder;
$this->entityManager = $entityManager;
$this->generator = $generator;
$this->logger = $logger;
}
/**
* Public timeline
*
* @params bool $local Show only local statuses? Defaults to false.
* @params bool $remote Show only remote statuses? Defaults to false.
* @params bool $only_media Show only statuses with media attached? Defaults to false.
* @params string $max_id Return results older than this id
* @params string $since_id Return results newer than this id
* @params string $min_id Return results immediately newer than this id
* @params int $limit Maximum number of results to return. Defaults to 20.
*/
public function publicTimeline(
bool $local = null,
bool $remote = null,
bool $only_media = null,
string $max_id = null,
string $since_id = null,
string $min_id = null,
int $limit = null,
): DataResponse {
if ($local === null) {
$local = false;
}
if ($remote === null) {
$remote = false;
}
if ($only_media === null) {
$only_media = false;
}
if ($limit === null || $limit > 100) {
$limit = 20;
}
$statusRepository = $this->entityManager->getRepository(Status::class);
$statusRepository->createQuery('SELECT s FROM \OCA\Social\Entity\Status s WHERE s.visibility = :visibility');
}
}

Wyświetl plik

@ -567,4 +567,14 @@ class Account {
$this->followRequest->add($followRequest);
return $followRequest;
}
public function toMastodonApi(): array {
return [
'id' => $this->id,
'username' => $this->userName,
'acct' => $this->userName,
'display_name' => $this->name ?? $this->userName,
// TODO more
];
}
}

Wyświetl plik

@ -11,10 +11,12 @@ 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\ActivityPub;
/**
* @ORM\Entity
* @ORM\Table(name="social_status")
* @ORM\HasLifecycleCallbacks
*/
class Status {
const STATUS_PUBLIC = "public";
@ -149,6 +151,17 @@ class Status {
$this->updatedAt = new \DateTime();
}
/**
* @ORM\PostPersist
*/
public function generateUri(): void {
if ($this->uri !== null) {
return;
}
$this->uri = ActivityPub\TagManager::getInstance()->uriFor($this);
}
public function getId(): string {
return $this->id;
}
@ -359,4 +372,30 @@ class Status {
public function isReblog(): bool {
return $this->reblogOf !== null;
}
public function toMastodonApi(): array {
return [
'id' => $this->id,
'created_at' => $this->createdAt->format(\DateTimeInterface::ISO8601),
'in_reply_to_id' => $this->inReplyTo ? $this->inReplyTo->getId() : null,
'in_reply_to_account_id' => $this->inReplyTo ? $this->inReplyTo->getAccount()->getId() : null,
'sensitive' => $this->sensitive,
'spoiler_text' => $this->spoilerText,
'visibility' => $this->visibility,
'language' => $this->language,
'uri' => $this->uri,
'url' => $this->url,
'replies_count' => 0,
'reblogs_count' => 0,
'favourites_count' => 0,
'favourited' => false,
'reblogged' => false,
'muted' => false,
'bookmarked' => false,
'content' => $this->text,
'reblog' => $this->reblogOf,
'application' => $this->application ? $this->application->toMastodonApi() : null,
'account' => $this->account->toMastodonApi(),
];
}
}

Wyświetl plik

@ -24,9 +24,9 @@ class RemoteAccountFetcher {
private IRequest $request;
private TagManager $tagManager;
public function __construct(IRequest $request, TagManager $tagManager) {
public function __construct(IRequest $request) {
$this->request = $request;
$this->tagManager = $tagManager;
$this->tagManager = TagManager::getInstance();
}
public function fetch(?string $uri, RemoteAccountFetchOption $fetchOption): ?Account {

Wyświetl plik

@ -4,15 +4,28 @@ namespace OCA\Social\Service\ActivityPub;
use OCA\Social\Entity\Account;
use OCA\Social\Entity\Status;
use OCA\Social\InstanceUtils;
use OCP\IRequest;
class TagManager {
final class TagManager {
private IRequest $request;
static private ?TagManager $instance = null;
public function __construct(IRequest $request) {
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new TagManager(\OCP\Server::get(IRequest::class));
}
return self::$instance;
}
private function __construct(IRequest $request) {
$this->request = $request;
}
private function __clone() {
}
/**
* @template T
* @param class-string<T> $className
@ -50,4 +63,38 @@ class TagManager {
}
return $host === $this->request->getServerHost();
}
public function uriFor(object $target): string {
if ($target->getUri()) {
return $target->getUri();
}
$instanceUtils = \OCP\Server::get(InstanceUtils::class);
if ($target instanceof Status) {
if ($target->isReblog()) {
// todo
}
return $instanceUtils->getLocalInstanceUrl() . '/users/' . $target->getAccount()->getUserName() . '/statues/' . $target->getId();
}
}
public function urlFor(object $target): string {
if ($target->getUrl()) {
return $target->getUrl();
}
$instanceUtils = \OCP\Server::get(InstanceUtils::class);
if ($target instanceof Status) {
if ($target->isReblog()) {
// todo
}
return $instanceUtils->getLocalInstanceUrl() . '/@' . $target->account->getUserName() . '/' . $target->getId();
}
}
public function __wakeup() {
throw new \Exception("Cannot unserialize singleton");
}
}

Wyświetl plik

@ -307,19 +307,8 @@ class ConfigService {
* @throws SocialAppConfigException
*/
public function getCloudUrl(bool $noPhp = false) {
$address = $this->getAppValue(self::CLOUD_URL);
if ($address === '') {
throw new SocialAppConfigException();
}
if ($noPhp) {
$pos = strpos($address, '/index.php');
if ($pos) {
$address = substr($address, 0, $pos);
}
}
return $this->withoutEndSlash($address, false, false);
$url = $this->urlGenerator->getAbsoluteURL('/');
return rtrim($url, '/');
}
/**

Wyświetl plik

@ -38,7 +38,7 @@ class PostServiceStatus {
/**
* @psalm-param array{?text: string, ?spoilerText: string, ?sensitive: bool, ?visibility: Status::STATUS_*} $options
*/
public function create(Account $account, array $options): void {
public function create(Account $account, array $options): Status {
$this->checkIdempotenceDuplicate($account, $options);
$status = new Status();
@ -61,12 +61,14 @@ class PostServiceStatus {
$this->mentionsService->run($status);
// Save status
$this->entityManager->persist($status);
$this->entityManager->persist($account);
$this->entityManager->flush();
$this->deliveryService->run($status);
$this->updateIdempotency($account, $status);
return $status;
}
private function idempotencyKey(Account $account, string $idempotency): string {