API: Account actions

This commit is contained in:
Michael 2021-05-14 21:51:32 +00:00
parent 5ade228c63
commit 45c2cc3887
13 changed files with 399 additions and 32 deletions

View File

@ -13,17 +13,18 @@ Authentication is the same as described in [Using the APIs](help/api#Authenticat
Supported mobile apps: Supported mobile apps:
- [Tusky](https://tusky.app)
- [Husky](https://husky.fwgs.ru)
- [twitlatte](https://github.com/moko256/twitlatte)
- [AndStatus](http://andstatus.org) - [AndStatus](http://andstatus.org)
- [Twidere](https://github.com/TwidereProject/) - [Husky](https://husky.fwgs.ru)
- [Subway Tooter](https://github.com/tateisu/SubwayTooter) - [Subway Tooter](https://github.com/tateisu/SubwayTooter)
- [Tusky](https://tusky.app)
- [Twidere](https://github.com/TwidereProject/)
- [twitlatte](https://github.com/moko256/twitlatte)
- [Yuito](https://github.com/accelforce/Yuito)
Unsupported mobile apps: Unsupported mobile apps:
- [Mammut](https://github.com/jamiesanson/Mammut) There are problems with the token request, see issue https://github.com/jamiesanson/Mammut/issues/19
- [Fedilab](https://framagit.org/tom79/fedilab) Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520 - [Fedilab](https://framagit.org/tom79/fedilab) Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520
- [Mammut](https://github.com/jamiesanson/Mammut) There are problems with the token request, see issue https://github.com/jamiesanson/Mammut/issues/19
## Entities ## Entities

View File

@ -44,9 +44,9 @@ class Account extends BaseFactory
{ {
parent::__construct($logger); parent::__construct($logger);
$this->baseUrl = $baseURL; $this->baseUrl = $baseURL;
$this->profileField = $profileField; $this->profileField = $profileField;
$this->mstdnField = $mstdnField; $this->mstdnField = $mstdnField;
} }
/** /**
@ -61,7 +61,7 @@ class Account extends BaseFactory
$cdata = Contact::getPublicAndUserContacID($contactId, $uid); $cdata = Contact::getPublicAndUserContacID($contactId, $uid);
if (!empty($cdata)) { if (!empty($cdata)) {
$publicContact = Contact::getById($cdata['public']); $publicContact = Contact::getById($cdata['public']);
$userContact = Contact::getById($cdata['user']); $userContact = Contact::getById($cdata['user']);
} else { } else {
$publicContact = Contact::getById($contactId); $publicContact = Contact::getById($contactId);
$userContact = []; $userContact = [];

View File

@ -37,13 +37,33 @@ class Relationship extends BaseFactory
return $this->createFromContact(Contact::getById($userContactId)); return $this->createFromContact(Contact::getById($userContactId));
} }
/**
* @param int $publicContactId Contact row id with uid = 0
* @param int $uid User ID
* @return RelationshipEntity
* @throws \Exception
*/
public function createFromPublicContactId(int $publicContactId, int $uid)
{
$cdata = Contact::getPublicAndUserContacID($publicContactId, $uid);
if (!empty($cdata)) {
$cid = $cdata['user'];
} else {
$cid = $publicContactId;
}
return $this->createFromContact(Contact::getById($cid));
}
/** /**
* @param array $userContact Full contact row record with uid != 0 * @param array $userContact Full contact row record with uid != 0
* @return RelationshipEntity * @return RelationshipEntity
*/ */
public function createFromContact(array $userContact) public function createFromContact(array $userContact)
{ {
return new RelationshipEntity($userContact['id'], $userContact); return new RelationshipEntity($userContact['id'], $userContact,
Contact\User::isBlocked($userContact['id'], $userContact['uid']),
Contact\User::isIgnored($userContact['id'], $userContact['uid']));
} }
/** /**

View File

@ -2409,6 +2409,50 @@ class Contact
return $contact; return $contact;
} }
/**
* Follow a contact
*
* @param int $cid Public contact id
* @param int $uid User ID
*
* @return bool "true" if following had been successful
*/
public static function follow(int $cid, int $uid)
{
$user = User::getById($uid);
if (empty($user)) {
return false;
}
$contact = self::getById($cid, ['url']);
$result = self::createFromProbe($user, $contact['url'], false);
return $result['cid'];
}
/**
* Unfollow a contact
*
* @param int $cid Public contact id
* @param int $uid User ID
*
* @return bool "true" if unfollowing had been successful
*/
public static function unfollow(int $cid, int $uid)
{
$cdata = self::getPublicAndUserContacID($cid, $uid);
if (empty($cdata['user'])) {
return false;
}
$contact = self::getById($cdata['user']);
self::removeSharer([], $contact);
return true;
}
/** /**
* @param array $importer Owner (local user) data * @param array $importer Owner (local user) data
* @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional. * @param array $contact Existing owner-specific contact data we want to expand the relationship with. Optional.
@ -2554,7 +2598,7 @@ class Contact
return null; return null;
} }
public static function removeFollower($importer, $contact, array $datarray = [], $item = "") public static function removeFollower($importer, $contact)
{ {
if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::SHARING)) { if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::SHARING)) {
DBA::update('contact', ['rel' => self::SHARING], ['id' => $contact['id']]); DBA::update('contact', ['rel' => self::SHARING], ['id' => $contact['id']]);
@ -2563,7 +2607,7 @@ class Contact
} }
} }
public static function removeSharer($importer, $contact, array $datarray = [], $item = "") public static function removeSharer($importer, $contact)
{ {
if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) { if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) {
DBA::update('contact', ['rel' => self::FOLLOWER], ['id' => $contact['id']]); DBA::update('contact', ['rel' => self::FOLLOWER], ['id' => $contact['id']]);

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Block extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Contact\User::setBlocked($parameters['id'], $uid, true);
System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
}
}

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Follow extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
$cid = Contact::follow($parameters['id'], self::getCurrentUserID());
System::jsonExit(DI::mstdnRelationship()->createFromContactId($cid)->toArray());
}
}

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Mute extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Contact\User::setIgnored($parameters['id'], $uid, true);
System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
}
}

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Unblock extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Contact\User::setBlocked($parameters['id'], $uid, false);
System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
}
}

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Unfollow extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Contact::unfollow($parameters['id'], $uid);
System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
}
}

View File

@ -0,0 +1,47 @@
<?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\DI;
use Friendica\Model\Contact;
use Friendica\Module\BaseApi;
/**
* @see https://docs.joinmastodon.org/methods/accounts/
*/
class Unmute extends BaseApi
{
public static function post(array $parameters = [])
{
self::login();
$uid = self::getCurrentUserID();
if (empty($parameters['id'])) {
DI::mstdnError()->UnprocessableEntity();
}
Contact\User::setIgnored($parameters['id'], $uid, false);
System::jsonExit(DI::mstdnRelationship()->createFromPublicContactId($parameters['id'], $uid)->toArray());
}
}

View File

@ -52,7 +52,7 @@ class Favourited extends BaseApi
$params = ['order' => ['thr-parent-id' => true], 'limit' => $limit]; $params = ['order' => ['thr-parent-id' => true], 'limit' => $limit];
$condition = ['gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::LIKE, 'uid' => $uid]; $condition = ['gravity' => GRAVITY_ACTIVITY, 'origin' => true, 'verb' => Activity::LIKE, 'uid' => $uid];
if (!empty($max_id)) { if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`thr-parent-id` < ?", $max_id]); $condition = DBA::mergeConditions($condition, ["`thr-parent-id` < ?", $max_id]);

View File

@ -37,42 +37,61 @@ class Relationship extends BaseDataTransferObject
/** @var bool */ /** @var bool */
protected $following = false; protected $following = false;
/** @var bool */ /** @var bool */
protected $followed_by = false; protected $requested = false;
/**
* Unsupported
* @var bool
*/
protected $endorsed = false;
/** @var bool */ /** @var bool */
protected $blocking = false; protected $followed_by = false;
/** @var bool */ /** @var bool */
protected $muting = false; protected $muting = false;
/** @var bool */ /** @var bool */
protected $muting_notifications = false; protected $muting_notifications = false;
/**
* Unsupported
* @var bool
*/
protected $showing_reblogs = true;
/** @var bool */ /** @var bool */
protected $requested = false; protected $notifying = false;
/** @var bool */
protected $blocking = false;
/** @var bool */ /** @var bool */
protected $domain_blocking = false; protected $domain_blocking = false;
/** /**
* Unsupported * Unsupported
* @var bool * @var bool
*/ */
protected $showing_reblogs = true; protected $blocked_by = false;
/** /**
* Unsupported * Unsupported
* @var bool * @var string
*/ */
protected $endorsed = false; protected $note = '';
/** /**
* @param int $userContactId Contact row Id with uid != 0 * @param int $userContactId Contact row Id with uid != 0
* @param array $userContact Full Contact table record with uid != 0 * @param array $userContact 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 = []) public function __construct(int $userContactId, array $userContact = [], bool $blocked = false, bool $muted = false)
{ {
$this->id = $userContactId; $this->id = $userContactId;
$this->following = in_array($userContact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]); $this->following = in_array($userContact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
$this->followed_by = in_array($userContact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
$this->blocking = (bool)$userContact['blocked'] ?? false;
$this->muting = (bool)$userContact['readonly'] ?? false;
$this->muting_notifications = (bool)$userContact['readonly'] ?? false;
$this->requested = (bool)$userContact['pending'] ?? false; $this->requested = (bool)$userContact['pending'] ?? 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->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->domain_blocking = Network::isUrlBlocked($userContact['url'] ?? '');
$this->blocked_by = false;
$this->note = '';
return $this; return $this;
} }

View File

@ -64,12 +64,12 @@ return [
'/accounts/{id:\d+}/following' => [Module\Api\Mastodon\Accounts\Following::class, [R::GET ]], '/accounts/{id:\d+}/following' => [Module\Api\Mastodon\Accounts\Following::class, [R::GET ]],
'/accounts/{id:\d+}/lists' => [Module\Api\Mastodon\Accounts\Lists::class, [R::GET ]], '/accounts/{id:\d+}/lists' => [Module\Api\Mastodon\Accounts\Lists::class, [R::GET ]],
'/accounts/{id:\d+}/identity_proofs' => [Module\Api\Mastodon\Accounts\IdentityProofs::class, [R::GET ]], // Dummy, not supported '/accounts/{id:\d+}/identity_proofs' => [Module\Api\Mastodon\Accounts\IdentityProofs::class, [R::GET ]], // Dummy, not supported
'/accounts/{id:\d+}/follow' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/accounts/{id:\d+}/follow' => [Module\Api\Mastodon\Accounts\Follow::class, [ R::POST]],
'/accounts/{id:\d+}/unfollow' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/accounts/{id:\d+}/unfollow' => [Module\Api\Mastodon\Accounts\Unfollow::class, [ R::POST]],
'/accounts/{id:\d+}/block' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/accounts/{id:\d+}/block' => [Module\Api\Mastodon\Accounts\Block::class, [ R::POST]],
'/accounts/{id:\d+}/unblock' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/accounts/{id:\d+}/unblock' => [Module\Api\Mastodon\Accounts\Unblock::class, [ R::POST]],
'/accounts/{id:\d+}/mute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/accounts/{id:\d+}/mute' => [Module\Api\Mastodon\Accounts\Mute::class, [ R::POST]],
'/accounts/{id:\d+}/unmute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/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+}/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+}/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\Unimplemented::class, [ R::POST]], // @todo
@ -90,9 +90,9 @@ return [
'/apps/verify_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/apps/verify_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo
'/blocks' => [Module\Api\Mastodon\Blocks::class, [R::GET ]], '/blocks' => [Module\Api\Mastodon\Blocks::class, [R::GET ]],
'/bookmarks' => [Module\Api\Mastodon\Bookmarks::class, [R::GET ]], '/bookmarks' => [Module\Api\Mastodon\Bookmarks::class, [R::GET ]],
'/conversations' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/conversations' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo
'/conversations/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented '/conversations/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // @todo
'/conversations/{id:\d+}/read' => [Module\Api\Mastodon\Unimplemented::class, [R::POST ]], // not implemented '/conversations/{id:\d+}/read' => [Module\Api\Mastodon\Unimplemented::class, [R::POST ]], // @todo
'/custom_emojis' => [Module\Api\Mastodon\CustomEmojis::class, [R::GET ]], '/custom_emojis' => [Module\Api\Mastodon\CustomEmojis::class, [R::GET ]],
'/domain_blocks' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::DELETE]], // not supported '/domain_blocks' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::DELETE]], // not supported
'/directory' => [Module\Api\Mastodon\Directory::class, [R::GET ]], '/directory' => [Module\Api\Mastodon\Directory::class, [R::GET ]],
@ -142,6 +142,7 @@ return [
'/statuses/{id:\d+}/unpin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo '/statuses/{id:\d+}/unpin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo
'/suggestions' => [Module\Api\Mastodon\Suggestions::class, [R::GET ]], '/suggestions' => [Module\Api\Mastodon\Suggestions::class, [R::GET ]],
'/suggestions/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented '/suggestions/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented
'/timelines/direct' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo
'/timelines/home' => [Module\Api\Mastodon\Timelines\Home::class, [R::GET ]], '/timelines/home' => [Module\Api\Mastodon\Timelines\Home::class, [R::GET ]],
'/timelines/list/{id:\d+}' => [Module\Api\Mastodon\Timelines\ListTimeline::class, [R::GET ]], '/timelines/list/{id:\d+}' => [Module\Api\Mastodon\Timelines\ListTimeline::class, [R::GET ]],
'/timelines/public' => [Module\Api\Mastodon\Timelines\PublicTimeline::class, [R::GET ]], '/timelines/public' => [Module\Api\Mastodon\Timelines\PublicTimeline::class, [R::GET ]],