Skip to content
Snippets Groups Projects
ActorController.php 13.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /**
     * @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?
                            extract_text_from_html($payload->object->content);
    
                                '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('activitypub_follows', 'activitypub_follows.actor_id = id', 'inner')
    
                ->where('activitypub_follows.target_actor_id', $this->actor->id)
                ->orderBy('activitypub_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
            // parse activityPub id to get actor and domain
            // check if actor and domain exist
    
    
                ! ($parts = split_handle($this->request->getPost('handle'))) ||
                ! ($data = get_webfinger_data($parts['username'], $parts['domain']))
    
                return redirect()
                    ->back()
                    ->withInput()
                    ->with('error', lang('ActivityPub.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));