From 3aa883f1e3dde7604703758d3e8b8b1ba9935825 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 13 May 2021 21:15:32 +0000 Subject: [PATCH 1/2] API: List handling, dummy endpoints --- .../Api/Mastodon/Accounts/IdentityProofs.php | 42 +++++++ src/Module/Api/Mastodon/Announcements.php | 43 +++++++ src/Module/Api/Mastodon/Endorsements.php | 40 ++++++ src/Module/Api/Mastodon/Lists.php | 55 ++++++-- src/Module/Api/Mastodon/Markers.php | 47 +++++++ src/Module/Api/Mastodon/Media.php | 1 + src/Module/Api/Mastodon/Proofs.php | 40 ++++++ src/Module/Api/Mastodon/ScheduledStatuses.php | 40 ++++++ src/Module/Api/Mastodon/Statuses.php | 6 + src/Module/BaseApi.php | 41 +++++- src/Module/OAuth/Authorize.php | 2 +- static/routes.config.php | 117 +++++++++--------- 12 files changed, 405 insertions(+), 69 deletions(-) create mode 100644 src/Module/Api/Mastodon/Accounts/IdentityProofs.php create mode 100644 src/Module/Api/Mastodon/Announcements.php create mode 100644 src/Module/Api/Mastodon/Endorsements.php create mode 100644 src/Module/Api/Mastodon/Markers.php create mode 100644 src/Module/Api/Mastodon/Proofs.php create mode 100644 src/Module/Api/Mastodon/ScheduledStatuses.php diff --git a/src/Module/Api/Mastodon/Accounts/IdentityProofs.php b/src/Module/Api/Mastodon/Accounts/IdentityProofs.php new file mode 100644 index 000000000..5a7d3a840 --- /dev/null +++ b/src/Module/Api/Mastodon/Accounts/IdentityProofs.php @@ -0,0 +1,42 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon\Accounts; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/accounts/ + */ +class IdentityProofs extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + self::login(); + + System::jsonExit([]); + } +} diff --git a/src/Module/Api/Mastodon/Announcements.php b/src/Module/Api/Mastodon/Announcements.php new file mode 100644 index 000000000..80b25bf5c --- /dev/null +++ b/src/Module/Api/Mastodon/Announcements.php @@ -0,0 +1,43 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/announcements/ + */ +class Announcements extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + self::login(); + + // @todo Possibly use the message from the pageheader addon for this + System::jsonExit([]); + } +} diff --git a/src/Module/Api/Mastodon/Endorsements.php b/src/Module/Api/Mastodon/Endorsements.php new file mode 100644 index 000000000..9c5e853bc --- /dev/null +++ b/src/Module/Api/Mastodon/Endorsements.php @@ -0,0 +1,40 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/accounts/endorsements/ + */ +class Endorsements extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + System::jsonExit([]); + } +} diff --git a/src/Module/Api/Mastodon/Lists.php b/src/Module/Api/Mastodon/Lists.php index d9f9c6e5a..e655edcb2 100644 --- a/src/Module/Api/Mastodon/Lists.php +++ b/src/Module/Api/Mastodon/Lists.php @@ -22,9 +22,9 @@ namespace Friendica\Module\Api\Mastodon; use Friendica\Core\System; -use Friendica\Database\DBA; use Friendica\DI; use Friendica\Module\BaseApi; +use Friendica\Model\Group; /** * @see https://docs.joinmastodon.org/methods/timelines/lists/ @@ -33,17 +33,55 @@ class Lists extends BaseApi { public static function delete(array $parameters = []) { - self::unsupported('delete'); + self::login(); + + $uid = self::getCurrentUserID(); + + if (empty($parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + if (!Group::exists($parameters['id'], $uid)) { + DI::mstdnError()->RecordNotFound(); + } + + if (!Group::remove($parameters['id'])) { + DI::mstdnError()->InternalError(); + } + + System::jsonExit([]); } public static function post(array $parameters = []) { - self::unsupported('post'); + self::login(); + + $uid = self::getCurrentUserID(); + $title = $_REQUEST['title'] ?? ''; + + if (empty($title)) { + DI::mstdnError()->UnprocessableEntity(); + } + + Group::create($uid, $title); + + $id = Group::getIdByName($uid, $title); + if (!$id) { + DI::mstdnError()->InternalError(); + } + + System::jsonExit(DI::mstdnList()->createFromGroupId($id)); } public static function put(array $parameters = []) { - self::unsupported('put'); + $data = self::getPutData(); + + if (empty($data['title']) || empty($parameters['id'])) { + DI::mstdnError()->UnprocessableEntity(); + } + + Group::update($parameters['id'], $data['title']); } /** @@ -58,14 +96,15 @@ class Lists extends BaseApi if (empty($parameters['id'])) { $lists = []; - $groups = DBA::select('group', ['id'], ['uid' => $uid, 'deleted' => false]); - while ($group = DBA::fetch($groups)) { + $groups = Group::getByUserId($uid); + + foreach ($groups as $group) { $lists[] = DI::mstdnList()->createFromGroupId($group['id']); } - DBA::close($groups); } else { $id = $parameters['id']; - if (!DBA::exists('group',['uid' => $uid, 'deleted' => false])) { + + if (!Group::exists($id, $uid)) { DI::mstdnError()->RecordNotFound(); } $lists = DI::mstdnList()->createFromGroupId($id); diff --git a/src/Module/Api/Mastodon/Markers.php b/src/Module/Api/Mastodon/Markers.php new file mode 100644 index 000000000..45ad3927d --- /dev/null +++ b/src/Module/Api/Mastodon/Markers.php @@ -0,0 +1,47 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/timelines/markers/ + */ +class Markers extends BaseApi +{ + public static function post(array $parameters = []) + { + self::unsupported('post'); + } + + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + self::login(); + + System::jsonExit([]); + } +} diff --git a/src/Module/Api/Mastodon/Media.php b/src/Module/Api/Mastodon/Media.php index 297d7d7a3..82844a6ff 100644 --- a/src/Module/Api/Mastodon/Media.php +++ b/src/Module/Api/Mastodon/Media.php @@ -33,6 +33,7 @@ class Media extends BaseApi { public static function put(array $parameters = []) { + $data = self::getPutData(); self::unsupported('put'); } diff --git a/src/Module/Api/Mastodon/Proofs.php b/src/Module/Api/Mastodon/Proofs.php new file mode 100644 index 000000000..1d09a9211 --- /dev/null +++ b/src/Module/Api/Mastodon/Proofs.php @@ -0,0 +1,40 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/proofs/ + */ +class Proofs extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + System::jsonError(404, ['error' => 'Record not found']); + } +} diff --git a/src/Module/Api/Mastodon/ScheduledStatuses.php b/src/Module/Api/Mastodon/ScheduledStatuses.php new file mode 100644 index 000000000..28f174da1 --- /dev/null +++ b/src/Module/Api/Mastodon/ScheduledStatuses.php @@ -0,0 +1,40 @@ +. + * + */ + +namespace Friendica\Module\Api\Mastodon; + +use Friendica\Core\System; +use Friendica\Module\BaseApi; + +/** + * @see https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/ + */ +class ScheduledStatuses extends BaseApi +{ + /** + * @param array $parameters + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + public static function rawContent(array $parameters = []) + { + System::jsonExit([]); + } +} diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index 900452385..afaf15986 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -30,6 +30,12 @@ use Friendica\Module\BaseApi; */ class Statuses extends BaseApi { + public static function post(array $parameters = []) + { + $data = self::getJsonPostData(); + self::unsupported('post'); + } + public static function delete(array $parameters = []) { self::unsupported('delete'); diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index a53c008c6..f826f4ad2 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -21,7 +21,6 @@ namespace Friendica\Module; -use Exception; use Friendica\BaseModule; use Friendica\Core\Logger; use Friendica\Core\System; @@ -30,6 +29,7 @@ use Friendica\Database\DBA; use Friendica\DI; use Friendica\Network\HTTPException; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Network; require_once __DIR__ . '/../../include/api.php'; @@ -127,6 +127,45 @@ class BaseApi extends BaseModule System::jsonError(501, $errorobj->toArray()); } + /** + * Get post data that is transmitted as JSON + * + * @return array request data + */ + public static function getJsonPostData() + { + $postdata = Network::postdata(); + if (empty($postdata)) { + return []; + } + + return json_decode($postdata, true); + } + + /** + * Get request data for put requests + * + * @return array request data + */ + public static function getPutData() + { + $rawdata = Network::postdata(); + if (empty($rawdata)) { + return []; + } + + $putdata = []; + + foreach (explode('&', $rawdata) as $value) { + $data = explode('=', $value); + if (count($data) == 2) { + $putdata[$data[0]] = urldecode($data[1]); + } + } + + return $putdata; + } + /** * Log in user via OAuth1 or Simple HTTP Auth. * diff --git a/src/Module/OAuth/Authorize.php b/src/Module/OAuth/Authorize.php index 57efe70c6..3834c0cd5 100644 --- a/src/Module/OAuth/Authorize.php +++ b/src/Module/OAuth/Authorize.php @@ -85,6 +85,6 @@ class Authorize extends BaseApi DI::mstdnError()->UnprocessableEntity(); } - DI::app()->redirect($application['redirect_uri'] . '?' . http_build_query(['code' => $token['code'], 'state' => $state])); + DI::app()->redirect($application['redirect_uri'] . (strpos($application['redirect_uri'], '?') ? '&' : '?') . http_build_query(['code' => $token['code'], 'state' => $state])); } } diff --git a/static/routes.config.php b/static/routes.config.php index 9d8e1fda4..3a19ff379 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -57,52 +57,52 @@ return [ '/api' => [ '/v1' => [ - '/accounts' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented + '/accounts' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported '/accounts/{id:\d+}' => [Module\Api\Mastodon\Accounts::class, [R::GET ]], '/accounts/{id:\d+}/statuses' => [Module\Api\Mastodon\Accounts\Statuses::class, [R::GET ]], '/accounts/{id:\d+}/followers' => [Module\Api\Mastodon\Accounts\Followers::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+}/identity_proofs' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/accounts/{id:\d+}/follow' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/unfollow' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/block' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/unblock' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/mute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/unmute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/accounts/{id:\d+}/pin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/accounts/{id:\d+}/unpin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/accounts/{id:\d+}/note' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], + '/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+}/unfollow' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/accounts/{id:\d+}/block' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/accounts/{id:\d+}/unblock' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/accounts/{id:\d+}/mute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/accounts/{id:\d+}/unmute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/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/relationships' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/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\Unimplemented::class, [R::PATCH ]], - '/admin/accounts' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/admin/accounts/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/admin/accounts/{id:\d+}/{action}' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/admin/reports' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/admin/reports/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/admin/reports/{id:\d+}/{action}' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/announcements' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/announcements/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/announcements/{id:\d+}/reactions/{name}' => [Module\Api\Mastodon\Unimplemented::class, [R::PUT, R::DELETE]], // not implemented + '/accounts/update_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::PATCH ]], // @todo + '/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 + '/admin/reports' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/admin/reports/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/admin/reports/{id:\d+}/{action}' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported + '/announcements' => [Module\Api\Mastodon\Announcements::class, [R::GET ]], // Dummy, not supported + '/announcements/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported + '/announcements/{id:\d+}/reactions/{name}' => [Module\Api\Mastodon\Unimplemented::class, [R::PUT, R::DELETE]], // not supported '/apps' => [Module\Api\Mastodon\Apps::class, [ R::POST]], - '/apps/verify_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], + '/apps/verify_credentials' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo '/blocks' => [Module\Api\Mastodon\Blocks::class, [R::GET ]], '/bookmarks' => [Module\Api\Mastodon\Bookmarks::class, [R::GET ]], '/conversations' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented '/conversations/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented '/conversations/{id:\d+}/read' => [Module\Api\Mastodon\Unimplemented::class, [R::POST ]], // not implemented '/custom_emojis' => [Module\Api\Mastodon\CustomEmojis::class, [R::GET ]], - '/domain_blocks' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::DELETE]], // not implemented + '/domain_blocks' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::DELETE]], // not supported '/directory' => [Module\Api\Mastodon\Directory::class, [R::GET ]], - '/endorsements' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented + '/endorsements' => [Module\Api\Mastodon\Endorsements::class, [R::GET ]], // Dummy, not supported '/favourites' => [Module\Api\Mastodon\Favourited::class, [R::GET ]], - '/featured_tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST]], // not implemented - '/featured_tags/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented - '/featured_tags/suggestions' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/filters' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/filters/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::PUT, R::DELETE]], // not implemented + '/featured_tags' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST]], // not supported + '/featured_tags/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not supported + '/featured_tags/suggestions' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/filters' => [Module\Api\Mastodon\Filters::class, [R::GET ]], // Dummy, not supported + '/filters/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST, R::PUT, R::DELETE]], // not supported '/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 ]], @@ -111,35 +111,35 @@ return [ '/lists' => [Module\Api\Mastodon\Lists::class, [R::GET, R::POST]], '/lists/{id:\d+}' => [Module\Api\Mastodon\Lists::class, [R::GET, R::PUT, R::DELETE]], '/lists/{id:\d+}/accounts' => [Module\Api\Mastodon\Lists\Accounts::class, [R::GET, R::POST, R::DELETE]], - '/markers' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::POST]], // not implemented - '/media' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/media/{id:\d+}' => [Module\Api\Mastodon\Media::class, [R::GET, R::PUT]], + '/markers' => [Module\Api\Mastodon\Markers::class, [R::GET, R::POST]], // Dummy, not supported + '/media' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/media/{id:\d+}' => [Module\Api\Mastodon\Media::class, [R::GET, R::PUT ]], '/mutes' => [Module\Api\Mastodon\Mutes::class, [R::GET ]], '/notifications' => [Module\Api\Mastodon\Notifications::class, [R::GET ]], '/notifications/{id:\d+}' => [Module\Api\Mastodon\Notifications::class, [R::GET ]], - '/notifications/clear' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/notifications/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/polls/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/polls/{id:\d+}/votes' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented + '/notifications/clear' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/notifications/{id:\d+}/dismiss' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/polls/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not supported + '/polls/{id:\d+}/votes' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported '/preferences' => [Module\Api\Mastodon\Preferences::class, [R::GET ]], - '/reports' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not implemented - '/scheduled_statuses' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // not implemented - '/scheduled_statuses/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::PUT, R::DELETE]], // not implemented - '/statuses' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], + '/reports' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // not supported + '/scheduled_statuses' => [Module\Api\Mastodon\ScheduledStatuses::class, [R::GET ]], // Dummy, not supported + '/scheduled_statuses/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::GET, R::PUT, R::DELETE]], // not supported + '/statuses' => [Module\Api\Mastodon\Statuses::class, [ R::POST]], '/statuses/{id:\d+}' => [Module\Api\Mastodon\Statuses::class, [R::GET, R::DELETE]], '/statuses/{id:\d+}/context' => [Module\Api\Mastodon\Statuses\Context::class, [R::GET ]], '/statuses/{id:\d+}/reblogged_by' => [Module\Api\Mastodon\Statuses\RebloggedBy::class, [R::GET ]], '/statuses/{id:\d+}/favourited_by' => [Module\Api\Mastodon\Statuses\FavouritedBy::class, [R::GET ]], - '/statuses/{id:\d+}/favourite' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/unfavourite' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/reblog' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/unreblog' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/bookmark' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/unbookmark' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/mute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/unmute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/pin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], - '/statuses/{id:\d+}/unpin' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], + '/statuses/{id:\d+}/favourite' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/unfavourite' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/reblog' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/unreblog' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/bookmark' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/unbookmark' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/mute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/unmute' => [Module\Api\Mastodon\Unimplemented::class, [ R::POST]], // @todo + '/statuses/{id:\d+}/pin' => [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/{id:\d+}' => [Module\Api\Mastodon\Unimplemented::class, [R::DELETE ]], // not implemented '/timelines/home' => [Module\Api\Mastodon\Timelines\Home::class, [R::GET ]], @@ -152,14 +152,15 @@ return [ '/search' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], // @todo ], '/friendica' => [ - '/profile/show' => [Module\Api\Friendica\Profile\Show::class , [R::GET ]], - '/events' => [Module\Api\Friendica\Events\Index::class , [R::GET ]], + '/profile/show' => [Module\Api\Friendica\Profile\Show::class, [R::GET ]], + '/events' => [Module\Api\Friendica\Events\Index::class, [R::GET ]], ], - '/followers/ids' => [Module\Api\Twitter\FollowersIds::class , [R::GET ]], - '/followers/list' => [Module\Api\Twitter\FollowersList::class , [R::GET ]], - '/friends/ids' => [Module\Api\Twitter\FriendsIds::class , [R::GET ]], - '/friends/list' => [Module\Api\Twitter\FriendsList::class , [R::GET ]], - '/oembed' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], + '/followers/ids' => [Module\Api\Twitter\FollowersIds::class, [R::GET ]], + '/followers/list' => [Module\Api\Twitter\FollowersList::class, [R::GET ]], + '/friends/ids' => [Module\Api\Twitter\FriendsIds::class, [R::GET ]], + '/friends/list' => [Module\Api\Twitter\FriendsList::class, [R::GET ]], + '/oembed' => [Module\Api\Mastodon\Unimplemented::class, [R::GET ]], + '/proofs' => [Module\Api\Mastodon\Proofs::class, [R::GET ]], // Dummy, not supported ], '/admin' => [ @@ -363,8 +364,6 @@ return [ '/pretheme' => [Module\ThemeDetails::class, [R::GET]], '/probe' => [Module\Debug\Probe::class, [R::GET]], - '/proofs' => [Module\Api\Mastodon\Unimplemented::class, [R::GET]], - '/profile/{nickname}' => $profileRoutes, '/u/{nickname}' => $profileRoutes, '/~{nickname}' => $profileRoutes, From 8b841dfa50a50a8676bb6a2a0824a4b48aa156b6 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 13 May 2021 22:00:40 +0000 Subject: [PATCH 2/2] Fix connection issues with AndStatus --- doc/API-Mastodon.md | 3 ++- src/Module/OAuth/Token.php | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/API-Mastodon.md b/doc/API-Mastodon.md index 18af62be6..09cf169e2 100644 --- a/doc/API-Mastodon.md +++ b/doc/API-Mastodon.md @@ -16,12 +16,13 @@ Supported mobile apps: - Tusky - Husky - twitlatte +- AndStatus +- Twidere Unsupported mobile apps: - [Subway Tooter](https://github.com/tateisu/SubwayTooter) Uses the wrong grant_type when requesting a token, possibly a problem in the server type detection of the app. See issue https://github.com/tateisu/SubwayTooter/issues/156 - [Mammut](https://github.com/jamiesanson/Mammut) States that the instance doesn't exist. Most likely an issue in the vitality check of the app, see issue https://github.com/jamiesanson/Mammut/issues/19 -- [AndStatus](https://github.com/andstatus/andstatus) Doesn't provide all data at token request, see issue https://github.com/andstatus/andstatus/issues/537 - [Fedilab](https://framagit.org/tom79/fedilab) Automatically uses the legacy API, see issue: https://framagit.org/tom79/fedilab/-/issues/520 ## Entities diff --git a/src/Module/OAuth/Token.php b/src/Module/OAuth/Token.php index c3aaac6d1..0a1a32b74 100644 --- a/src/Module/OAuth/Token.php +++ b/src/Module/OAuth/Token.php @@ -41,6 +41,15 @@ class Token extends BaseApi $client_id = $_REQUEST['client_id'] ?? ''; $client_secret = $_REQUEST['client_secret'] ?? ''; + // AndStatus transmits the client data in the AUTHORIZATION header field, see https://github.com/andstatus/andstatus/issues/530 + if (empty($client_id) && !empty($_SERVER['HTTP_AUTHORIZATION']) && (substr($_SERVER['HTTP_AUTHORIZATION'], 0, 6) == 'Basic ')) { + $datapair = explode(':', base64_decode(trim(substr($_SERVER['HTTP_AUTHORIZATION'], 6)))); + if (count($datapair) == 2) { + $client_id = $datapair[0]; + $client_secret = $datapair[1]; + } + } + if ($grant_type != 'authorization_code') { Logger::warning('Unsupported or missing grant type', ['request' => $_REQUEST]); DI::mstdnError()->UnprocessableEntity(DI::l10n()->t('Unsupported or missing grant type'));