]> git.mxchange.org Git - friendica.git/commitdiff
Merge pull request #7930 from MrPetovan/task/7887-api-followers-request
authorPhilipp <admin+Github@philipp.info>
Wed, 18 Dec 2019 21:12:52 +0000 (22:12 +0100)
committerGitHub <noreply@github.com>
Wed, 18 Dec 2019 21:12:52 +0000 (22:12 +0100)
Add POST follow request Mastodon API endpoint

12 files changed:
doc/API-Mastodon.md
mod/notifications.php
src/Api/Mastodon/Account.php
src/Api/Mastodon/Relationship.php [new file with mode: 0644]
src/App/Module.php
src/BaseModel.php [new file with mode: 0644]
src/Model/APContact.php
src/Model/Introduction.php [new file with mode: 0644]
src/Module/Api/Mastodon/FollowRequests.php
src/Module/Base/Api.php
src/Module/FollowConfirm.php
static/routes.config.php

index a711e367fb8db0480bed4fdeeb0a3ad78f24206f..546aca2431117ddf40b96296e8d447f714ec2a4c 100644 (file)
@@ -16,11 +16,18 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/ap
 ## Implemented endpoints
 
 - [GET /api/v1/follow_requests](https://docs.joinmastodon.org/api/rest/follow-requests/#get-api-v1-follow-requests)
+- [POST /api/v1/follow_requests/:id/authorize](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-authorize)
+    - Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
+- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
+    - Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
+- POST /api/v1/follow_requests/:id/ignore
+    - Friendica-specific, hides the follow request from the list and prevents the remote contact from retrying.
+    - Returns a [Relationship](https://docs.joinmastodon.org/api/entities/#relationship) object.
+    
+
 - [GET /api/v1/instance](https://docs.joinmastodon.org/api/rest/instances)
 - GET /api/v1/instance/peers - undocumented, but implemented by Mastodon and Pleroma
 
-## Non-implemented endpoints
 
-- [POST /api/v1/follow_requests/:id/authorize](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-authorize)
-- [POST /api/v1/follow_requests/:id/reject](https://docs.joinmastodon.org/api/rest/follow-requests/#post-api-v1-follow-requests-id-reject)
 
+## Non-implemented endpoints
index 88972728cb7ca63b213700997efc2adc9542b3ab..90036705bc9d1eb33bf5f307ed888753ffdaa032 100644 (file)
@@ -14,7 +14,7 @@ use Friendica\Core\Renderer;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\Module\Login;
-use Friendica\Model\Contact;
+use Friendica\Model\Introduction;
 use Friendica\Model\Notify;
 
 function notifications_post(App $a)
@@ -30,43 +30,20 @@ function notifications_post(App $a)
        }
 
        if ($request_id) {
-               $intro = DBA::selectFirst('intro', ['id', 'contact-id', 'fid'], ['id' => $request_id, 'uid' => local_user()]);
-
-               if (DBA::isResult($intro)) {
-                       $intro_id = $intro['id'];
-                       $contact_id = $intro['contact-id'];
-               } else {
-                       notice(L10n::t('Invalid request identifier.') . EOL);
-                       return;
+               /** @var Introduction $Intro */
+               $Intro = \Friendica\BaseObject::getClass(Introduction::class);
+               $Intro->fetch(['id' => $request_id, 'uid' => local_user()]);
+
+               switch ($_POST['submit']) {
+                       case L10n::t('Discard'):
+                               $Intro->discard();
+                               break;
+                       case L10n::t('Ignore'):
+                               $Intro->ignore();
+                               break;
                }
 
-               // If it is a friend suggestion, the contact is not a new friend but an existing friend
-               // that should not be deleted.
-
-               $fid = $intro['fid'];
-
-               if ($_POST['submit'] == L10n::t('Discard')) {
-                       DBA::delete('intro', ['id' => $intro_id]);
-                       if (!$fid) {
-                               // When the contact entry had been created just for that intro, we want to get rid of it now
-                               $condition = ['id' => $contact_id, 'uid' => local_user(),
-                                       'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
-                               $contact_pending = DBA::exists('contact', $condition);
-
-                               // Remove the "pending" to stop the reappearing in any case
-                               DBA::update('contact', ['pending' => false], ['id' => $contact_id]);
-
-                               if ($contact_pending) {
-                                       Contact::remove($contact_id);
-                               }
-                       }
-                       $a->internalRedirect('notifications/intros');
-               }
-
-               if ($_POST['submit'] == L10n::t('Ignore')) {
-                       DBA::update('intro', ['ignore' => true], ['id' => $intro_id]);
-                       $a->internalRedirect('notifications/intros');
-               }
+               $a->internalRedirect('notifications/intros');
        }
 }
 
index 5d4f369155eae1e37e1cf9407ef73c79fef97acf..18ab93be0c5a387f08bbe3bf74e0b060a8431d99 100644 (file)
@@ -4,6 +4,7 @@ namespace Friendica\Api\Mastodon;
 
 use Friendica\Content\Text\BBCode;
 use Friendica\Database\DBA;
+use Friendica\Model\Contact;
 use Friendica\Util\DateTimeFormat;
 
 /**
@@ -55,31 +56,33 @@ class Account
        /**
         * Creates an account record from a contact record. Expects all contact table fields to be set
         *
-        * @param array $contact
+        * @param array $contact   Full contact table record
+        * @param array $apcontact Full apcontact table record
         * @return Account
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function createFromContact(array $contact) {
+       public static function createFromContact(array $contact, array $apcontact = [])
+       {
                $account = new Account();
-               $account->id = $contact['id'];
-               $account->username = $contact['nick'];
-               $account->acct = $contact['nick'];
-               $account->display_name = $contact['name'];
-               $account->locked = $contact['blocked'];
-               $account->created_at = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
-               // No data is available from contact
-               $account->followers_count = 0;
-               $account->following_count = 0;
-               $account->statuses_count = 0;
-               $account->note = BBCode::convert($contact['about']);
-               $account->url = $contact['url'];
-               $account->avatar = $contact['avatar'];
-               $account->avatar_static = $contact['avatar'];
+               $account->id              = $contact['id'];
+               $account->username        = $contact['nick'];
+               $account->acct            = $contact['nick'];
+               $account->display_name    = $contact['name'];
+               $account->locked          = !empty($apcontact['manually-approve']);
+               $account->created_at      = DateTimeFormat::utc($contact['created'], DateTimeFormat::ATOM);
+               $account->followers_count = $apcontact['followers_count'] ?? 0;
+               $account->following_count = $apcontact['following_count'] ?? 0;
+               $account->statuses_count  = $apcontact['statuses_count'] ?? 0;
+               $account->note            = BBCode::convert($contact['about'], false);
+               $account->url             = $contact['url'];
+               $account->avatar          = $contact['avatar'];
+               $account->avatar_static   = $contact['avatar'];
                // No header picture in Friendica
-               $account->header = '';
-               $account->header_static = '';
+               $account->header          = '';
+               $account->header_static   = '';
                // No custom emojis per account in Friendica
-               $account->emojis = [];
+               $account->emojis          = [];
+               $account->bot             = ($contact['contact-type'] == Contact::TYPE_NEWS);
 
                return $account;
        }
diff --git a/src/Api/Mastodon/Relationship.php b/src/Api/Mastodon/Relationship.php
new file mode 100644 (file)
index 0000000..18e249b
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+namespace Friendica\Api\Mastodon;
+
+use Friendica\Model\Contact;
+use Friendica\Util\Network;
+
+/**
+ * Class Relationship
+ *
+ * @see https://docs.joinmastodon.org/api/entities/#relationship
+ */
+class Relationship
+{
+       /** @var int */
+       var $id;
+       /** @var bool */
+       var $following = false;
+       /** @var bool */
+       var $followed_by = false;
+       /** @var bool */
+       var $blocking = false;
+       /** @var bool */
+       var $muting = false;
+       /** @var bool */
+       var $muting_notifications = false;
+       /** @var bool */
+       var $requested = false;
+       /** @var bool */
+       var $domain_blocking = false;
+       /** @var bool */
+       var $showing_reblogs = false;
+       /** @var bool */
+       var $endorsed = false;
+
+       /**
+        * @param array $contact Full Contact table record
+        * @return Relationship
+        */
+       public static function createFromContact(array $contact)
+       {
+               $relationship = new self();
+
+               $relationship->id                   = $contact['id'];
+               $relationship->following            = in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]);
+               $relationship->followed_by          = in_array($contact['rel'], [Contact::FOLLOWER, Contact::FRIEND]);
+               $relationship->blocking             = (bool)$contact['blocked'];
+               $relationship->muting               = (bool)$contact['readonly'];
+               $relationship->muting_notifications = (bool)$contact['readonly'];
+               $relationship->requested            = (bool)$contact['pending'];
+               $relationship->domain_blocking      = Network::isUrlBlocked($contact['url']);
+               // Unsupported
+               $relationship->showing_reblogs      = true;
+               // Unsupported
+               $relationship->endorsed             = false;
+
+               return $relationship;
+       }
+}
index 868520c0257b23e5b77c6fa6c7ec7a24f52d1fae..a8648d0aa0cc54778b689f4bf457626750d77663 100644 (file)
@@ -251,10 +251,6 @@ class Module
 
                call_user_func([$this->module_class, 'init'], $this->module_parameters);
 
-               // "rawContent" is especially meant for technical endpoints.
-               // This endpoint doesn't need any theme initialization or other comparable stuff.
-               call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
-
                if ($server['REQUEST_METHOD'] === 'POST') {
                        Core\Hook::callAll($this->module . '_mod_post', $post);
                        call_user_func([$this->module_class, 'post'], $this->module_parameters);
@@ -262,5 +258,9 @@ class Module
 
                Core\Hook::callAll($this->module . '_mod_afterpost', $placeholder);
                call_user_func([$this->module_class, 'afterpost'], $this->module_parameters);
+
+               // "rawContent" is especially meant for technical endpoints.
+               // This endpoint doesn't need any theme initialization or other comparable stuff.
+               call_user_func([$this->module_class, 'rawContent'], $this->module_parameters);
        }
 }
diff --git a/src/BaseModel.php b/src/BaseModel.php
new file mode 100644 (file)
index 0000000..32011c7
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+
+namespace Friendica;
+
+use Friendica\Database\Database;
+use Friendica\Network\HTTPException;
+use Psr\Log\LoggerInterface;
+
+/**
+ * Class BaseModel
+ *
+ * The Model classes inheriting from this abstract class are meant to represent a single database record.
+ * The associated table name has to be provided in the child class, and the table is expected to have a unique `id` field.
+ *
+ * @property int id
+ */
+abstract class BaseModel
+{
+       protected static $table_name;
+
+       /** @var Database */
+       protected $dba;
+       /** @var LoggerInterface */
+       protected $logger;
+
+       /**
+        * Model record abstraction.
+        * Child classes never have to interact directly with it.
+        * Please use the magic getter instead.
+        *
+        * @var array
+        */
+       private $data = [];
+
+       public function __construct(Database $dba, LoggerInterface $logger)
+       {
+               $this->dba = $dba;
+               $this->logger = $logger;
+       }
+
+       /**
+        * Magic getter. This allows to retrieve model fields with the following syntax:
+        * - $model->field (outside of class)
+        * - $this->field (inside of class)
+        *
+        * @param $name
+        * @return mixed
+        * @throws HTTPException\InternalServerErrorException
+        */
+       public function __get($name)
+       {
+               if (empty($this->data['id'])) {
+                       throw new HTTPException\InternalServerErrorException(static::class . ' record uninitialized');
+               }
+
+               if (!array_key_exists($name, $this->data)) {
+                       throw new HTTPException\InternalServerErrorException('Field ' . $name . ' not found in ' . static::class);
+               }
+
+               return $this->data[$name];
+       }
+
+       /**
+        * Fetches a single model record. The condition array is expected to contain a unique index (primary or otherwise).
+        *
+        * Chainable.
+        *
+        * @param array $condition
+        * @return BaseModel
+        * @throws HTTPException\NotFoundException
+        */
+       public function fetch(array $condition)
+       {
+               $intro = $this->dba->selectFirst(static::$table_name, [], $condition);
+
+               if (!$intro) {
+                       throw new HTTPException\NotFoundException(static::class . ' record not found.');
+               }
+
+               $this->data = $intro;
+
+               return $this;
+       }
+
+       /**
+        * Deletes the model record from the database.
+        * Prevents further methods from being called by wiping the internal model data.
+        */
+       public function delete()
+       {
+               if ($this->dba->delete(static::$table_name, ['id' => $this->id])) {
+                       $this->data = [];
+               }
+       }
+}
index 346c4ec2f74450757e5863a94a0a8c1fa74dc60c..e3c3a0a12b2f09fc51c7ca403e6f2c4ac91a0c65 100644 (file)
@@ -84,7 +84,7 @@ class APContact extends BaseObject
        public static function getByURL($url, $update = null)
        {
                if (empty($url)) {
-                       return false;
+                       return [];
                }
 
                $fetched_contact = false;
@@ -110,7 +110,7 @@ class APContact extends BaseObject
                        }
 
                        if (!is_null($update)) {
-                               return DBA::isResult($apcontact) ? $apcontact : false;
+                               return DBA::isResult($apcontact) ? $apcontact : [];
                        }
 
                        if (DBA::isResult($apcontact)) {
diff --git a/src/Model/Introduction.php b/src/Model/Introduction.php
new file mode 100644 (file)
index 0000000..127765c
--- /dev/null
@@ -0,0 +1,156 @@
+<?php
+
+namespace Friendica\Model;
+
+use Friendica\BaseModel;
+use Friendica\Core\Protocol;
+use Friendica\Network\HTTPException;
+use Friendica\Protocol\ActivityPub;
+use Friendica\Protocol\Diaspora;
+use Friendica\Util\DateTimeFormat;
+
+/**
+ * @property int    uid
+ * @property int    fid
+ * @property int    contact-id
+ * @property bool   knowyou
+ * @property bool   duplex
+ * @property string note
+ * @property string hash
+ * @property string datetime
+ * @property bool   blocked
+ * @property bool   ignored
+ *
+ * @package Friendica\Model
+ */
+final class Introduction extends BaseModel
+{
+       static $table_name = 'intro';
+
+       /**
+        * Confirms a follow request and sends a notic to the remote contact.
+        *
+        * @param bool      $duplex Is it a follow back?
+        * @param bool|null $hidden Should this contact be hidden? null = no change
+        * @throws HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        * @throws HTTPException\NotFoundException
+        */
+       public function confirm(bool $duplex = false, bool $hidden = null)
+       {
+               $this->logger->info('Confirming follower', ['cid' => $this->{'contact-id'}]);
+
+               $contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
+
+               if (!$contact) {
+                       throw new HTTPException\NotFoundException('Contact record not found.');
+               }
+
+               $new_relation = $contact['rel'];
+               $writable = $contact['writable'];
+
+               if (!empty($contact['protocol'])) {
+                       $protocol = $contact['protocol'];
+               } else {
+                       $protocol = $contact['network'];
+               }
+
+               if ($protocol == Protocol::ACTIVITYPUB) {
+                       ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $contact['uid']);
+               }
+
+               if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
+                       if ($duplex) {
+                               $new_relation = Contact::FRIEND;
+                       } else {
+                               $new_relation = Contact::FOLLOWER;
+                       }
+
+                       if ($new_relation != Contact::FOLLOWER) {
+                               $writable = 1;
+                       }
+               }
+
+               $fields = [
+                       'name-date' => DateTimeFormat::utcNow(),
+                       'uri-date'  => DateTimeFormat::utcNow(),
+                       'blocked'   => false,
+                       'pending'   => false,
+                       'protocol'  => $protocol,
+                       'writable'  => $writable,
+                       'hidden'    => $hidden ?? $contact['hidden'],
+                       'rel'       => $new_relation,
+               ];
+               $this->dba->update('contact', $fields, ['id' => $contact['id']]);
+
+               array_merge($contact, $fields);
+
+               if ($new_relation == Contact::FRIEND) {
+                       if ($protocol == Protocol::DIASPORA) {
+                               $ret = Diaspora::sendShare(User::getById($contact['uid']), $contact);
+                               $this->logger->info('share returns', ['return' => $ret]);
+                       } elseif ($protocol == Protocol::ACTIVITYPUB) {
+                               ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $contact['uid']);
+                       }
+               }
+
+               $this->delete();
+       }
+
+       /**
+        * Silently ignores the introduction, hides it from notifications and prevents the remote contact from submitting
+        * additional follow requests.
+        *
+        * Chainable
+        *
+        * @return Introduction
+        * @throws \Exception
+        */
+       public function ignore()
+       {
+               $this->dba->update('intro', ['ignore' => true], ['id' => $this->id]);
+
+               return $this;
+       }
+
+       /**
+        * Discards the introduction and sends a rejection message to AP contacts.
+        *
+        * @throws HTTPException\InternalServerErrorException
+        * @throws HTTPException\NotFoundException
+        * @throws \ImagickException
+        */
+       public function discard()
+       {
+               // If it is a friend suggestion, the contact is not a new friend but an existing friend
+               // that should not be deleted.
+               if (!$this->fid) {
+                       // When the contact entry had been created just for that intro, we want to get rid of it now
+                       $condition = ['id' => $this->{'contact-id'}, 'uid' => $this->uid,
+                               'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
+                       if ($this->dba->exists('contact', $condition)) {
+                               Contact::remove($this->{'contact-id'});
+                       } else {
+                               $this->dba->update('contact', ['pending' => false], ['id' => $this->{'contact-id'}]);
+                       }
+               }
+
+               $contact = Contact::selectFirst([], ['id' => $this->{'contact-id'}, 'uid' => $this->uid]);
+
+               if (!$contact) {
+                       throw new HTTPException\NotFoundException('Contact record not found.');
+               }
+
+               if (!empty($contact['protocol'])) {
+                       $protocol = $contact['protocol'];
+               } else {
+                       $protocol = $contact['network'];
+               }
+
+               if ($protocol == Protocol::ACTIVITYPUB) {
+                       ActivityPub\Transmitter::sendContactReject($contact['url'], $contact['hub-verify'], $contact['uid']);
+               }
+
+               $this->delete();
+       }
+}
index 515dc451cdcb947d412d67931684155016d54197..739400eb77c54634414af4f1e3c5caefbec6776d 100644 (file)
@@ -2,11 +2,13 @@
 
 namespace Friendica\Module\Api\Mastodon;
 
-use Friendica\Api\Mastodon\Account;
+use Friendica\Api\Mastodon;
 use Friendica\App\BaseURL;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
+use Friendica\Model\APContact;
 use Friendica\Model\Contact;
+use Friendica\Model\Introduction;
 use Friendica\Module\Base\Api;
 use Friendica\Network\HTTPException;
 
@@ -19,7 +21,40 @@ class FollowRequests extends Api
        {
                parent::init($parameters);
 
-               self::login();
+               if (!self::login()) {
+                       throw new HTTPException\UnauthorizedException();
+               }
+       }
+
+       public static function post(array $parameters = [])
+       {
+               parent::post($parameters);
+
+               /** @var Introduction $Intro */
+               $Intro = self::getClass(Introduction::class);
+               $Intro->fetch(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
+
+               $contactId = $Intro->{'contact-id'};
+
+               $relationship = new Mastodon\Relationship();
+               $relationship->id = $contactId;
+
+               switch ($parameters['action']) {
+                       case 'authorize':
+                               $Intro->confirm();
+                               $relationship = Mastodon\Relationship::createFromContact(Contact::getById($contactId));
+                               break;
+                       case 'ignore':
+                               $Intro->ignore();
+                               break;
+                       case 'reject':
+                               $Intro->discard();
+                               break;
+                       default:
+                               throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
+               }
+
+               System::jsonExit($relationship);
        }
 
        /**
@@ -34,26 +69,32 @@ class FollowRequests extends Api
                $limit = intval($_GET['limit'] ?? 40);
 
                if (isset($since_id) && isset($max_id)) {
-                       $condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
+                       $condition = ['`uid` = ? AND NOT `ignore` AND `id` > ? AND `id` < ?', self::$current_user_id, $since_id, $max_id];
                } elseif (isset($since_id)) {
-                       $condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` > ?', self::$current_user_id, $since_id];
+                       $condition = ['`uid` = ? AND NOT `ignore` AND `id` > ?', self::$current_user_id, $since_id];
                } elseif (isset($max_id)) {
-                       $condition = ['`uid` = ? AND NOT `self` AND `pending` AND `id` < ?', self::$current_user_id, $max_id];
+                       $condition = ['`uid` = ? AND NOT `ignore` AND `id` < ?', self::$current_user_id, $max_id];
                } else {
-                       $condition = ['`uid` = ? AND NOT `self` AND `pending`', self::$current_user_id];
+                       $condition = ['`uid` = ? AND NOT `ignore`', self::$current_user_id];
                }
 
-               $count = DBA::count('contact', $condition);
+               $count = DBA::count('intro', $condition);
 
-               $contacts = Contact::selectToArray(
+               $intros = DBA::selectToArray(
+                       'intro',
                        [],
                        $condition,
                        ['order' => ['id' => 'DESC'], 'limit' => $limit]
                );
 
                $return = [];
-               foreach ($contacts as $contact) {
-                       $account = Account::createFromContact($contact);
+               foreach ($intros as $intro) {
+                       $contact = Contact::getById($intro['contact-id']);
+                       $apcontact = APContact::getByURL($contact['url'], false);
+                       $account = Mastodon\Account::createFromContact($contact, $apcontact);
+
+                       // Not ideal, the same "account" can have multiple ids depending on the context
+                       $account->id = $intro['id'];
 
                        $return[] = $account;
                }
@@ -68,9 +109,9 @@ class FollowRequests extends Api
 
                $links = [];
                if ($count > $limit) {
-                       $links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $contacts[count($contacts) - 1]['id']]) . '>; rel="next"';
+                       $links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['max_id' => $intros[count($intros) - 1]['id']]) . '>; rel="next"';
                }
-               $links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $contacts[0]['id']]) . '>; rel="prev"';
+               $links[] = '<' . $BaseURL->get() . '/api/v1/follow_requests?' . http_build_query($base_query + ['since_id' => $intros[0]['id']]) . '>; rel="prev"';
 
                header('Link: ' . implode(', ', $links));
 
index f3453e0323fc0d22d2c1b74b2babbb8ef1e609aa..08cf96158d51d4147631e783e27e18d0c4ec8e15 100644 (file)
@@ -54,6 +54,7 @@ class Api extends BaseModule
         *
         * @brief Login API user
         *
+        * @return bool Was a user authenticated?
         * @throws HTTPException\ForbiddenException
         * @throws HTTPException\UnauthorizedException
         * @throws HTTPException\InternalServerErrorException
@@ -69,6 +70,8 @@ class Api extends BaseModule
                api_login(self::getApp());
 
                self::$current_user_id = api_user();
+
+               return (bool)self::$current_user_id;
        }
 
        /**
index d1a0a5dda573183aba2f5f944fe370f5d3140b79..5e9ab0481ce436ae5aa0409522504ff50f642475 100644 (file)
@@ -1,17 +1,9 @@
 <?php
 namespace Friendica\Module;
 
-use Friendica\App;
 use Friendica\BaseModule;
 use Friendica\Core\L10n;
-use Friendica\Core\Logger;
-use Friendica\Core\Protocol;
-use Friendica\Database\DBA;
-use Friendica\Model\Contact;
-use Friendica\Model\User;
-use Friendica\Protocol\Diaspora;
-use Friendica\Protocol\ActivityPub;
-use Friendica\Util\DateTimeFormat;
+use Friendica\Model\Introduction;
 
 /**
  * Process follow request confirmations
@@ -30,67 +22,15 @@ class FollowConfirm extends BaseModule
 
                $intro_id = intval($_POST['intro_id']   ?? 0);
                $duplex   = intval($_POST['duplex']     ?? 0);
-               $cid      = intval($_POST['contact_id'] ?? 0);
                $hidden   = intval($_POST['hidden']     ?? 0);
 
-               if (empty($cid)) {
-                       notice(L10n::t('No given contact.') . EOL);
-                       return;
-               }
-
-               Logger::info('Confirming follower', ['cid' => $cid]);
-
-               $contact = DBA::selectFirst('contact', [], ['id' => $cid, 'uid' => $uid]);
-               if (!DBA::isResult($contact)) {
-                       Logger::warning('Contact not found in DB.', ['cid' => $cid]);
-                       notice(L10n::t('Contact not found.') . EOL);
-                       return;
-               }
-
-               $relation = $contact['rel'];
-               $new_relation = $contact['rel'];
-               $writable = $contact['writable'];
-
-               if (!empty($contact['protocol'])) {
-                       $protocol = $contact['protocol'];
-               } else {
-                       $protocol = $contact['network'];
-               }
-
-               if ($protocol == Protocol::ACTIVITYPUB) {
-                       ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $uid);
-               }
-
-               if (in_array($protocol, [Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
-                       if ($duplex) {
-                               $new_relation = Contact::FRIEND;
-                       } else {
-                               $new_relation = Contact::FOLLOWER;
-                       }
+               /** @var Introduction $Intro */
+               $Intro = self::getClass(Introduction::class);
+               $Intro->fetch(['id' => $intro_id, 'uid' => local_user()]);
 
-                       if ($new_relation != Contact::FOLLOWER) {
-                               $writable = 1;
-                       }
-               }
-
-               $fields = ['name-date' => DateTimeFormat::utcNow(),
-                       'uri-date' => DateTimeFormat::utcNow(),
-                       'blocked' => false, 'pending' => false, 'protocol' => $protocol,
-                       'writable' => $writable, 'hidden' => $hidden, 'rel' => $new_relation];
-               DBA::update('contact', $fields, ['id' => $cid]);
-
-               if ($new_relation == Contact::FRIEND) {
-                       if ($protocol == Protocol::DIASPORA) {
-                               $user = User::getById($uid);
-                               $contact = Contact::getById($cid);
-                               $ret = Diaspora::sendShare($user, $contact);
-                               Logger::info('share returns', ['return' => $ret]);
-                       } elseif ($protocol == Protocol::ACTIVITYPUB) {
-                               ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid);
-                       }
-               }
+               $cid = $Intro->{'contact-id'};
 
-               DBA::delete('intro', ['id' => $intro_id]);
+               $Intro->confirm($duplex, $hidden);
 
                $a->internalRedirect('contact/' . intval($cid));
        }
index 824354690d5972675d846f328fd67cf77f705dbb..d23b092169448363f105692e04f0838557d10f79 100644 (file)
@@ -30,6 +30,7 @@ return [
        '/api' => [
                '/v1' => [
                        '/follow_requests'                   => [Module\Api\Mastodon\FollowRequests::class, [R::GET         ]],
+                       '/follow_requests/{id:\d+}/{action}' => [Module\Api\Mastodon\FollowRequests::class, [        R::POST]],
                        '/instance'                          => [Module\Api\Mastodon\Instance::class, [R::GET]],
                        '/instance/peers'                    => [Module\Api\Mastodon\Instance\Peers::class, [R::GET]],
                ],