Skip to content
Snippets Groups Projects
ActorController.php 13.6 KiB
Newer Older
/**
 * @copyright  2021 Podlibre
 * @license    https://www.gnu.org/licenses/agpl-3.0.en.html AGPL3
 * @link       https://castopod.org/
 */

use CodeIgniter\Exceptions\PageNotFoundException;
use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use Modules\Fediverse\Config\Fediverse;
use Modules\Fediverse\Entities\Actor;
use Modules\Fediverse\Entities\Post;
use Modules\Fediverse\Objects\OrderedCollectionObject;
use Modules\Fediverse\Objects\OrderedCollectionPage;
    public function _remap(string $method, string ...$params): mixed
        if (count($params) < 1) {
            throw PageNotFoundException::forPageNotFound();
        }

            ($actor = model('ActorModel')->getActorByUsername($params[0])) === null
    /**
     * @noRector ReturnTypeDeclarationRector
     */
    public function index(): ResponseInterface
    {
        $actorObjectClass = $this->config->actorObject;
        $actorObject = new $actorObjectClass($this->actor);

        return $this->response
            ->setContentType('application/activity+json')
            ->setBody($actorObject->toJSON());
    }

    /**
     * Handles incoming requests from fediverse servers
     *
     * @noRector ReturnTypeDeclarationRector
    {
        // get json body and parse it
        $payload = $this->request->getJSON();

        // retrieve payload actor from database or create it if it doesn't exist
        $payloadActor = get_or_create_actor_from_uri($payload->actor);

        // store activity to database
        $activityId = model('ActivityModel')
            ->newActivity(
                $payload->type,
                $payloadActor->id,
                $this->actor->id,
                null,
                json_encode($payload, JSON_THROW_ON_ERROR),
            );
        switch ($payload->type) {
            case 'Create':
                if ($payload->object->type === 'Note') {
                    if (! $payload->object->inReplyTo) {
                        return $this->response->setStatusCode(501)
                            ->setJSON([]);
                    $replyToPost = model('PostModel')
                        ->getPostByUri($payload->object->inReplyTo);
                        // TODO: strip content from html to retrieve message
                        // remove all html tags and reconstruct message with mentions?
                            'uri' => $payload->object->id,
                            'actor_id' => $payloadActor->id,
                            'published_at' => Time::parse($payload->object->published),
                            ->addReply($reply, true, false);

                        model('ActivityModel')
                            ->update($activityId, [
                    return $this->response->setStatusCode(200)
                        ->setJSON([]);
                // return not handled undo error (501 = not implemented)
                return $this->response->setStatusCode(501)
                    ->setJSON([]);
                $postToDelete = model('PostModel')
                    ->getPostByUri($payload->object->id);
                if ($postToDelete !== null) {
                    model('PostModel')
                        ->removePost($postToDelete, false);
                return $this->response->setStatusCode(200)
                    ->setJSON([]);
            case 'Follow':
                // add to followers table
                    ->addFollower($payloadActor, $this->actor, false);

                // Automatically accept follow by returning accept activity
                accept_follow($this->actor, $payloadActor, $payload->id);

                // TODO: return 202 (Accepted) followed!
                return $this->response->setStatusCode(202)
                    ->setJSON([]);
                // get favourited post
                $post = model('PostModel')
                    ->getPostByUri($payload->object);
                        ->addFavourite($payloadActor, $post, false);
                return $this->response->setStatusCode(200)
                    ->setJSON([]);
                $post = model('PostModel')
                    ->getPostByUri($payload->object);
                    model('PostModel')
                        ->reblog($payloadActor, $post, false);
                return $this->response->setStatusCode(200)
                    ->setJSON([]);
            case 'Undo':
                // switch/case on the type of activity to undo
                switch ($payload->object->type) {
                    case 'Follow':
                        // revert side-effect by removing follow from database
                            ->removeFollower($payloadActor, $this->actor, false);

                        // TODO: undo has been accepted! (202 - Accepted)
                        return $this->response->setStatusCode(202)
                            ->setJSON([]);
                        $post = model('PostModel')
                            ->getPostByUri($payload->object->object);
                            // revert side-effect by removing favourite from database
                            model('FavouriteModel')
                                ->removeFavourite($payloadActor, $post, false);
                        return $this->response->setStatusCode(200)
                            ->setJSON([]);
                        $post = model('PostModel')
                            ->getPostByUri($payload->object->object);
                        $reblogPost = null;
                        if ($post !== null) {
                            $reblogPost = model('PostModel')
                                ->where([
                                    'actor_id' => $payloadActor->id,
                                    'reblog_of_id' => service('uuid')
                        if ($reblogPost !== null) {
                            model('PostModel')
                                ->undoReblog($reblogPost, false);
                        return $this->response->setStatusCode(200)
                            ->setJSON([]);
                    default:
                        // return not handled undo error (501 = not implemented)
                        return $this->response->setStatusCode(501)
                            ->setJSON([]);
            default:
                // return not handled activity error (501 = not implemented)
                return $this->response->setStatusCode(501)
                    ->setJSON([]);
    /**
     * @noRector ReturnTypeDeclarationRector
     */
    public function outbox(): ResponseInterface
    {
        // get published activities by publication date
        $actorActivity = model('ActivityModel')
            ->where('actor_id', $this->actor->id)
            ->where('`created_at` <= NOW()', null, false)
            ->orderBy('created_at', 'DESC');

        $pageNumber = (int) $this->request->getGet('page');
            $actorActivity->paginate(12);
            $pager = $actorActivity->pager;
            $collection = new OrderedCollectionObject(null, $pager);
        } else {
            $paginatedActivity = $actorActivity->paginate(12, 'default', $pageNumber);
            $pager = $actorActivity->pager;
            $orderedItems = [];
            foreach ($paginatedActivity as $activity) {
            }
            $collection = new OrderedCollectionPage($pager, $orderedItems);
        }

        return $this->response
            ->setContentType('application/activity+json')
            ->setBody($collection->toJSON());
    }

    /**
     * @noRector ReturnTypeDeclarationRector
     */
    public function followers(): ResponseInterface
        // get followers for a specific actor
        $followers = model('ActorModel')
            ->join($tablesPrefix . 'follows', $tablesPrefix . 'follows.actor_id = id', 'inner')
            ->where($tablesPrefix . 'follows.target_actor_id', $this->actor->id)
            ->orderBy($tablesPrefix . 'follows.created_at', 'DESC');
        $pageNumber = (int) $this->request->getGet('page');
            $followers->paginate(12);
            $pager = $followers->pager;
            $followersCollection = new OrderedCollectionObject(null, $pager);
        } else {
            $paginatedFollowers = $followers->paginate(12, 'default', $pageNumber);
            $pager = $followers->pager;

            $orderedItems = [];
            foreach ($paginatedFollowers as $follower) {
            $followersCollection = new OrderedCollectionPage($pager, $orderedItems);
        }

        return $this->response
            ->setContentType('application/activity+json')
            ->setBody($followersCollection->toJSON());
    }

    public function attemptFollow(): RedirectResponse | ResponseInterface
    {
        $rules = [
            'handle' =>
                'regex_match[/^@?(?P<username>[\w\.\-]+)@(?P<host>[\w\.\-]+)(?P<port>:[\d]+)?$/]',
        ];

            return redirect()
                ->back()
                ->withInput()
                ->with('errors', $this->validator->getErrors());
        }

        helper('text');

        // get webfinger data from actor
            ! ($parts = split_handle($this->request->getPost('handle'))) ||
            ! ($data = get_webfinger_data($parts['username'], $parts['domain']))
                ->with('error', lang('Fediverse.follow.accountNotFound'));
        }

        $ostatusKey = array_search(
            'http://ostatus.org/schema/1.0/subscribe',
            array_column($data->links, 'rel'),
            // TODO: error, couldn't subscribe to activitypub account
            // The instance doesn't allow its users to follow others
            return $this->response->setJSON([]);
        }

        return redirect()->to(
            str_replace('{uri}', urlencode($this->actor->uri), $data->links[$ostatusKey]->template),
    /**
     * @noRector ReturnTypeDeclarationRector
     */
    public function activity(string $activityId): ResponseInterface
            ! ($activity = model('ActivityModel')->getActivityById($activityId))
        }

        return $this->response
            ->setContentType('application/activity+json')
            ->setBody(json_encode($activity->payload, JSON_THROW_ON_ERROR));