From f001f52e39483ad4e045cbd2a4b448550b07d54e Mon Sep 17 00:00:00 2001
From: Michael <heluecht@pirati.ca>
Date: Sat, 15 May 2021 15:02:15 +0000
Subject: [PATCH] API: Fix relationships

---
 doc/API-Mastodon.md                           | 18 +++----
 src/Factory/Api/Mastodon/Relationship.php     | 41 +++-----------
 src/Module/Api/Mastodon/Accounts/Block.php    |  2 +-
 src/Module/Api/Mastodon/Accounts/Mute.php     |  2 +-
 src/Module/Api/Mastodon/Accounts/Note.php     | 53 +++++++++++++++++++
 .../{Relationsships.php => Relationships.php} |  8 +--
 src/Module/Api/Mastodon/Accounts/Statuses.php | 11 ++++
 src/Module/Api/Mastodon/Accounts/Unblock.php  |  2 +-
 src/Module/Api/Mastodon/Accounts/Unfollow.php |  2 +-
 src/Module/Api/Mastodon/Accounts/Unmute.php   |  2 +-
 src/Module/Api/Mastodon/FollowRequests.php    | 25 ++++-----
 src/Object/Api/Mastodon/Relationship.php      | 36 ++++++++-----
 static/routes.config.php                      |  4 +-
 13 files changed, 121 insertions(+), 85 deletions(-)
 create mode 100644 src/Module/Api/Mastodon/Accounts/Note.php
 rename src/Module/Api/Mastodon/Accounts/{Relationsships.php => Relationships.php} (87%)

diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md
index 278f5540e..d7ce5402d 100644
--- a/doc/API-Mastodon.md
+++ b/doc/API-Mastodon.md
@@ -33,20 +33,17 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
 ## Implemented endpoints
 
 - [`GET /api/v1/accounts/:id`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
-- [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/)
-- [`GET /api/v1/accounts/:id/followers`](https://docs.joinmastodon.org/methods/accounts/)
-- [`GET /api/v1/accounts/:id/following`](https://docs.joinmastodon.org/methods/accounts/)
-- [`GET /api/v1/accounts/:id/lists`](https://docs.joinmastodon.org/methods/accounts/)
-- [`POST /api/v1/accounts/:id/follow`](https://docs.joinmastodon.org/methods/accounts/)
-- [`POST /api/v1/accounts/:id/unfollow`](https://docs.joinmastodon.org/methods/accounts/)
 - [`POST /api/v1/accounts/:id/block`](https://docs.joinmastodon.org/methods/accounts/)
-- [`POST /api/v1/accounts/:id/unblock`](https://docs.joinmastodon.org/methods/accounts/)
-- [`POST /api/v1/accounts/:id/mute`](https://docs.joinmastodon.org/methods/accounts/)
-- [`POST /api/v1/accounts/:id/unmute`](https://docs.joinmastodon.org/methods/accounts/)
-- [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
+- [`POST /api/v1/accounts/:id/follow`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/accounts/:id/followers`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/accounts/:id/following`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/accounts/:id/lists`](https://docs.joinmastodon.org/methods/accounts/)
+- [`POST /api/v1/accounts/:id/mute`](https://docs.joinmastodon.org/methods/accounts/)
+- [`POST /api/v1/accounts/:id/note`](https://docs.joinmastodon.org/methods/accounts/)
+- [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/)
+- [`POST /api/v1/accounts/:id/unfollow`](https://docs.joinmastodon.org/methods/accounts/)
+- [`POST /api/v1/accounts/:id/unblock`](https://docs.joinmastodon.org/methods/accounts/)
+- [`POST /api/v1/accounts/:id/unmute`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/accounts/relationships`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/accounts/search`](https://docs.joinmastodon.org/methods/accounts)
 - [`GET /api/v1/accounts/verify_credentials`](https://docs.joinmastodon.org/methods/accounts)
@@ -115,7 +112,6 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
 
 These emdpoints are planned to be implemented
 
-- [`POST /api/v1/accounts/:id/note`](https://docs.joinmastodon.org/methods/accounts/)
 - [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/)
 - [`GET /api/v1/apps/verify_credentials`](https://docs.joinmastodon.org/methods/apps/)
 - [`GET /api/v1/conversations`](https://docs.joinmastodon.org/methods/timelines/conversations/)
diff --git a/src/Factory/Api/Mastodon/Relationship.php b/src/Factory/Api/Mastodon/Relationship.php
index a1d94a332..711114834 100644
--- a/src/Factory/Api/Mastodon/Relationship.php
+++ b/src/Factory/Api/Mastodon/Relationship.php
@@ -28,50 +28,21 @@ use Friendica\Model\Contact;
 class Relationship extends BaseFactory
 {
 	/**
-	 * @param int $userContactId Contact row id with uid != 0
-	 * @return RelationshipEntity
-	 * @throws \Exception
-	 */
-	public function createFromContactId(int $userContactId)
-	{
-		return $this->createFromContact(Contact::getById($userContactId));
-	}
-
-	/**
-	 * @param int $publicContactId Contact row id with uid = 0
+	 * @param int $contactId Contact ID (public or user contact)
 	 * @param int $uid User ID
 	 * @return RelationshipEntity
 	 * @throws \Exception
 	 */
-	public function createFromPublicContactId(int $publicContactId, int $uid)
+	public function createFromContactId(int $contactId, int $uid)
 	{
-		$cdata = Contact::getPublicAndUserContacID($publicContactId, $uid);
+		$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
 		if (!empty($cdata)) {
 			$cid = $cdata['user'];
 		} else {
-			$cid = $publicContactId;
+			$cid = $contactId;
 		}
 
-		return $this->createFromContact(Contact::getById($cid));
-	}
-
-	/**
-	 * @param array $userContact Full contact row record with uid != 0
-	 * @return RelationshipEntity
-	 */
-	public function createFromContact(array $userContact)
-	{
-		return new RelationshipEntity($userContact['id'], $userContact,
-			Contact\User::isBlocked($userContact['id'], $userContact['uid']),
-			Contact\User::isIgnored($userContact['id'], $userContact['uid']));
-	}
-
-	/**
-	 * @param int $userContactId Contact row id with uid != 0
-	 * @return RelationshipEntity
-	 */
-	public function createDefaultFromContactId(int $userContactId)
-	{
-		return new RelationshipEntity($userContactId);
+		return new RelationshipEntity($cdata['public'], Contact::getById($cid),
+			Contact\User::isBlocked($cid, $uid), Contact\User::isIgnored($cid, $uid));
 	}
 }
diff --git a/src/Module/Api/Mastodon/Accounts/Block.php b/src/Module/Api/Mastodon/Accounts/Block.php
index 504345244..edbae8a1d 100644
--- a/src/Module/Api/Mastodon/Accounts/Block.php
+++ b/src/Module/Api/Mastodon/Accounts/Block.php
@@ -42,6 +42,6 @@ class Block extends BaseApi
 
 		Contact\User::setBlocked($parameters['id'], $uid, true);
 
-		System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
 	}
 }
diff --git a/src/Module/Api/Mastodon/Accounts/Mute.php b/src/Module/Api/Mastodon/Accounts/Mute.php
index 6dda625f0..e3975771f 100644
--- a/src/Module/Api/Mastodon/Accounts/Mute.php
+++ b/src/Module/Api/Mastodon/Accounts/Mute.php
@@ -42,6 +42,6 @@ class Mute extends BaseApi
 
 		Contact\User::setIgnored($parameters['id'], $uid, true);
 
-		System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
 	}
 }
diff --git a/src/Module/Api/Mastodon/Accounts/Note.php b/src/Module/Api/Mastodon/Accounts/Note.php
new file mode 100644
index 000000000..bbb53d93a
--- /dev/null
+++ b/src/Module/Api/Mastodon/Accounts/Note.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2021, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Module\Api\Mastodon\Accounts;
+
+use Friendica\Core\System;
+use Friendica\Database\DBA;
+use Friendica\DI;
+use Friendica\Model\Contact;
+use Friendica\Module\BaseApi;
+
+/**
+ * @see https://docs.joinmastodon.org/methods/accounts/
+ */
+class Note extends BaseApi
+{
+	public static function post(array $parameters = [])
+	{
+		self::login();
+		$uid = self::getCurrentUserID();
+
+		if (empty($parameters['id'])) {
+			DI::mstdnError()->UnprocessableEntity();
+		}
+
+		$cdata = Contact::getPublicAndUserContacID($parameters['id'], $uid);
+		if (empty($cdata['user'])) {
+			DI::mstdnError()->RecordNotFound();
+		}
+
+		DBA::update('contact', ['info' => $_REQUEST['comment'] ?? ''], ['id' => $cdata['user']]);
+
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
+	}
+}
diff --git a/src/Module/Api/Mastodon/Accounts/Relationsships.php b/src/Module/Api/Mastodon/Accounts/Relationships.php
similarity index 87%
rename from src/Module/Api/Mastodon/Accounts/Relationsships.php
rename to src/Module/Api/Mastodon/Accounts/Relationships.php
index d485a06a3..a989460a3 100644
--- a/src/Module/Api/Mastodon/Accounts/Relationsships.php
+++ b/src/Module/Api/Mastodon/Accounts/Relationships.php
@@ -21,6 +21,7 @@
 
 namespace Friendica\Module\Api\Mastodon\Accounts;
 
+use Friendica\Core\Logger;
 use Friendica\Core\System;
 use Friendica\DI;
 use Friendica\Module\BaseApi;
@@ -39,15 +40,14 @@ class Relationships extends BaseApi
 		self::login();
 		$uid = self::getCurrentUserID();
 
-
-		if (empty($parameters['id'])) {
+		if (empty($_REQUEST['id']) || !is_array($_REQUEST['id'])) {
 			DI::mstdnError()->UnprocessableEntity();
 		}
 
 		$relationsships = [];
 
-		foreach ($parameters['id'] as $id) {
-			$relationsships[] = DI::mstdnRelationship()->createFromPublicContactId($id, $uid);
+		foreach ($_REQUEST['id'] as $id) {
+			$relationsships[] = DI::mstdnRelationship()->createFromContactId($id, $uid);
 		}
 
 		System::jsonExit($relationsships);
diff --git a/src/Module/Api/Mastodon/Accounts/Statuses.php b/src/Module/Api/Mastodon/Accounts/Statuses.php
index e234b7ee1..f824abc9a 100644
--- a/src/Module/Api/Mastodon/Accounts/Statuses.php
+++ b/src/Module/Api/Mastodon/Accounts/Statuses.php
@@ -62,6 +62,9 @@ class Statuses extends BaseApi
 		// Maximum number of results to return. Defaults to 20.
 		$limit = (int)!isset($_REQUEST['limit']) ? 20 : $_REQUEST['limit'];
 
+		$pinned = (bool)!isset($_REQUEST['pinned']) ? false : ($_REQUEST['pinned'] == 'true');
+		$exclude_replies = (bool)!isset($_REQUEST['exclude_replies']) ? false : ($_REQUEST['exclude_replies'] == 'true');
+
 		$params = ['order' => ['uri-id' => true], 'limit' => $limit];
 
 		$uid = self::getCurrentUserID();
@@ -94,6 +97,14 @@ class Statuses extends BaseApi
 			$params['order'] = ['uri-id'];
 		}
 
+		if ($pinned) {
+			$condition = DBA::mergeConditions($condition, ['pinned' => true]);
+		}
+
+		if ($exclude_replies) {
+			$condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
+		}
+
 		$items = Post::selectForUser($uid, ['uri-id'], $condition, $params);
 
 		$statuses = [];
diff --git a/src/Module/Api/Mastodon/Accounts/Unblock.php b/src/Module/Api/Mastodon/Accounts/Unblock.php
index 51997453f..7de5a4cfb 100644
--- a/src/Module/Api/Mastodon/Accounts/Unblock.php
+++ b/src/Module/Api/Mastodon/Accounts/Unblock.php
@@ -42,6 +42,6 @@ class Unblock extends BaseApi
 
 		Contact\User::setBlocked($parameters['id'], $uid, false);
 
-		System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
 	}
 }
diff --git a/src/Module/Api/Mastodon/Accounts/Unfollow.php b/src/Module/Api/Mastodon/Accounts/Unfollow.php
index ef681e67f..b2efde7b0 100644
--- a/src/Module/Api/Mastodon/Accounts/Unfollow.php
+++ b/src/Module/Api/Mastodon/Accounts/Unfollow.php
@@ -42,6 +42,6 @@ class Unfollow extends BaseApi
 
 		Contact::unfollow($parameters['id'], $uid);
 
-		System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
 	}
 }
diff --git a/src/Module/Api/Mastodon/Accounts/Unmute.php b/src/Module/Api/Mastodon/Accounts/Unmute.php
index c870da0aa..15892cff4 100644
--- a/src/Module/Api/Mastodon/Accounts/Unmute.php
+++ b/src/Module/Api/Mastodon/Accounts/Unmute.php
@@ -42,6 +42,6 @@ class Unmute extends BaseApi
 
 		Contact\User::setIgnored($parameters['id'], $uid, false);
 
-		System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
+		System::jsonExit(DI::mstdnRelationship()->createFromContactId($parameters['id'], $uid)->toArray());
 	}
 }
diff --git a/src/Module/Api/Mastodon/FollowRequests.php b/src/Module/Api/Mastodon/FollowRequests.php
index 009094057..8a59120d3 100644
--- a/src/Module/Api/Mastodon/FollowRequests.php
+++ b/src/Module/Api/Mastodon/FollowRequests.php
@@ -31,15 +31,6 @@ use Friendica\Network\HTTPException;
  */
 class FollowRequests extends BaseApi
 {
-	public static function init(array $parameters = [])
-	{
-		parent::init($parameters);
-
-		if (!self::login()) {
-			throw new HTTPException\UnauthorizedException();
-		}
-	}
-
 	/**
 	 * @param array $parameters
 	 * @throws HTTPException\BadRequestException
@@ -54,9 +45,10 @@ class FollowRequests extends BaseApi
 	 */
 	public static function post(array $parameters = [])
 	{
-		parent::post($parameters);
+		self::login();
+		$uid = self::getCurrentUserID();
 
-		$introduction = DI::intro()->selectFirst(['id' => $parameters['id'], 'uid' => self::$current_user_id]);
+		$introduction = DI::intro()->selectFirst(['id' => $parameters['id'], 'uid' => $uid]);
 
 		$contactId = $introduction->{'contact-id'};
 
@@ -64,17 +56,17 @@ class FollowRequests extends BaseApi
 			case 'authorize':
 				$introduction->confirm();
 
-				$relationship = DI::mstdnRelationship()->createFromContactId($contactId);
+				$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
 				break;
 			case 'ignore':
 				$introduction->ignore();
 
-				$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
+				$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
 				break;
 			case 'reject':
 				$introduction->discard();
 
-				$relationship = DI::mstdnRelationship()->createDefaultFromContactId($contactId);
+				$relationship = DI::mstdnRelationship()->createFromContactId($contactId, $uid);
 				break;
 			default:
 				throw new HTTPException\BadRequestException('Unexpected action parameter, expecting "authorize", "ignore" or "reject"');
@@ -91,6 +83,9 @@ class FollowRequests extends BaseApi
 	 */
 	public static function rawContent(array $parameters = [])
 	{
+		self::login();
+		$uid = self::getCurrentUserID();
+
 		$min_id = $_GET['min_id'] ?? null;
 		$max_id = $_GET['max_id'] ?? null;
 		$limit = intval($_GET['limit'] ?? 40);
@@ -98,7 +93,7 @@ class FollowRequests extends BaseApi
 		$baseUrl = DI::baseUrl();
 
 		$introductions = DI::intro()->selectByBoundaries(
-			['`uid` = ? AND NOT `ignore`', self::$current_user_id],
+			['`uid` = ? AND NOT `ignore`', $uid],
 			['order' => ['id' => 'DESC']],
 			$min_id,
 			$max_id,
diff --git a/src/Object/Api/Mastodon/Relationship.php b/src/Object/Api/Mastodon/Relationship.php
index 463eb97e0..870acb544 100644
--- a/src/Object/Api/Mastodon/Relationship.php
+++ b/src/Object/Api/Mastodon/Relationship.php
@@ -28,7 +28,7 @@ use Friendica\Util\Network;
 /**
  * Class Relationship
  *
- * @see https://docs.joinmastodon.org/api/entities/#relationship
+ * @see https://docs.joinmastodon.org/entities/relationship/
  */
 class Relationship extends BaseDataTransferObject
 {
@@ -72,27 +72,37 @@ class Relationship extends BaseDataTransferObject
 	protected $note = '';
 
 	/**
-	 * @param int   $userContactId Contact row Id with uid != 0
-	 * @param array $userContact   Full Contact table record with uid != 0
+	 * @param int   $contactId Contact row Id with uid != 0
+	 * @param array $contactRecord   Full Contact table record with uid != 0
 	 * @param bool  $blocked "true" if user is blocked
 	 * @param bool  $muted "true" if user is muted
 	 */
-	public function __construct(int $userContactId, array $userContact = [], bool $blocked = false, bool $muted = false)
+	public function __construct(int $contactId, array $contactRecord = [], bool $blocked = false, bool $muted = false)
 	{
-		$this->id                   = $userContactId;
-		$this->following            = in_array($userContact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
-		$this->requested            = (bool)$userContact['pending'] ?? false;
+		$this->id                   = $contactId;
+		$this->following            = false;
+		$this->requested            = false;
 		$this->endorsed             = false;
-		$this->followed_by          = in_array($userContact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
-		$this->muting               = (bool)($userContact['readonly'] ?? false) || $muted;
-		$this->muting_notifications = $this->muting;
+		$this->followed_by          = false;
+		$this->muting               = $muted;
+		$this->muting_notifications = false;
 		$this->showing_reblogs      = true;
-		$this->notifying            = (bool)$userContact['notify_new_posts'] ?? false;
-		$this->blocking             = (bool)($userContact['blocked'] ?? false) || $blocked;
-		$this->domain_blocking      = Network::isUrlBlocked($userContact['url'] ?? '');
+		$this->notifying            = false;
+		$this->blocking             = $blocked;
+		$this->domain_blocking      = Network::isUrlBlocked($contactRecord['url'] ?? '');
 		$this->blocked_by           = false;
 		$this->note                 = '';
 
+		if ($contactRecord['uid'] != 0) {
+			$this->following   = in_array($contactRecord['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
+			$this->requested   = (bool)($contactRecord['pending'] ?? false);
+			$this->followed_by = in_array($contactRecord['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
+			$this->muting      = (bool)($contactRecord['readonly'] ?? false) || $muted;
+			$this->notifying   = (bool)$contactRecord['notify_new_posts'] ?? false;
+			$this->blocking    = (bool)($contactRecord['blocked'] ?? false) || $blocked;
+			$this->note        = $contactRecord['info'];
+		}
+
 		return $this;
 	}
 }
diff --git a/static/routes.config.php b/static/routes.config.php
index ada9de8a5..6dee2164c 100644
--- a/static/routes.config.php
+++ b/static/routes.config.php
@@ -72,11 +72,11 @@ return [
 			'/accounts/{id:\d+}/unmute'          => [Module\Api\Mastodon\Accounts\Unmute::class,          [        R::POST]],
 			'/accounts/{id:\d+}/pin'             => [Module\Api\Mastodon\Unimplemented::class,            [        R::POST]], // not supported
 			'/accounts/{id:\d+}/unpin'           => [Module\Api\Mastodon\Unimplemented::class,            [        R::POST]], // not supported
-			'/accounts/{id:\d+}/note'            => [Module\Api\Mastodon\Unimplemented::class,            [        R::POST]], // @todo
+			'/accounts/{id:\d+}/note'            => [Module\Api\Mastodon\Accounts\Note::class,            [        R::POST]],
 			'/accounts/relationships'            => [Module\Api\Mastodon\Accounts\Relationships::class,   [R::GET         ]],
 			'/accounts/search'                   => [Module\Api\Mastodon\Accounts\Search::class,          [R::GET         ]],
-			'/accounts/verify_credentials'       => [Module\Api\Mastodon\Accounts\VerifyCredentials::class, [R::GET       ]],
 			'/accounts/update_credentials'       => [Module\Api\Mastodon\Accounts\UpdateCredentials::class, [R::PATCH     ]],
+			'/accounts/verify_credentials'       => [Module\Api\Mastodon\Accounts\VerifyCredentials::class, [R::GET       ]],
 			'/admin/accounts'                    => [Module\Api\Mastodon\Unimplemented::class,            [R::GET         ]], // not supported
 			'/admin/accounts/{id:\d+}'           => [Module\Api\Mastodon\Unimplemented::class,            [R::GET         ]], // not supported
 			'/admin/accounts/{id:\d+}/{action}'  => [Module\Api\Mastodon\Unimplemented::class,            [        R::POST]], // not supported