kopia lustrzana https://github.com/nextcloud/social
rodzic
9f49b14657
commit
a2fca565a1
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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');
|
||||
}
|
||||
}
|
|
@ -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
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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 {
|
||||
|
|
Ładowanie…
Reference in New Issue