- [GET api/friendships/incoming](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming)
- Unsupported parameters
- `stringify_ids`
-- [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
- - Unsupported parameters:
- - `user_id`: Relationships aren't returned for other users than self
- - `screen_name`: Relationships aren't returned for other users than self
-- [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
- - Unsupported parameters:
- - `user_id`: Relationships aren't returned for other users than self
- - `screen_name`: Relationships aren't returned for other users than self
+
+- - [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
+ - [GET api/followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
+ - [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
+ - [GET api/friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
+ - Additional parameter:
+ - `since_id`: Same behavior as `cursor`, use the `next_cursor` value to load the next page.
+ - Unsupported parameter:
+ - `skip_status`: No status is returned even if it isn't set to true.
+ - Caveats:
+ - `user_id` must be the ID of a contact associated with a local user account.
+ - `screen_name` must be associated with a local user account.
+ - `screen_name` trumps `user_id` if both are provided (undocumented Twitter behavior).
+ - Will succeed but return an empty array for users hiding their contact lists.
- [POST api/friendships/destroy](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy)
+
+
## Non-implemented endpoints
- [GET oauth/authenticate](https://developer.twitter.com/en/docs/basics/authentication/api-reference/authenticate)
- [POST lists/subscribers/destroy](https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy)
-- [GET followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
-- [GET friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
- [GET friendships/lookup](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup)
- [GET friendships/no_retweets/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids)
- [GET friendships/outgoing](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing)
api_register_func('api/gnusocial/version', 'api_statusnet_version', false);
api_register_func('api/statusnet/version', 'api_statusnet_version', false);
-/**
- *
- * @param string $type Return type (atom, rss, xml, json)
- *
- * @param int $rel A contact relationship constant
- * @return array|string|void
- * @throws BadRequestException
- * @throws ForbiddenException
- * @throws ImagickException
- * @throws InternalServerErrorException
- * @throws UnauthorizedException
- * @todo use api_format_data() to return data
- */
-function api_ff_ids($type, int $rel)
-{
- if (!api_user()) {
- throw new ForbiddenException();
- }
-
- $a = DI::app();
-
- api_get_user($a);
-
- $stringify_ids = $_REQUEST['stringify_ids'] ?? false;
-
- $contacts = DBA::p("SELECT `pcontact`.`id`
- FROM `contact`
- INNER JOIN `contact` AS `pcontact`
- ON `contact`.`nurl` = `pcontact`.`nurl`
- AND `pcontact`.`uid` = 0
- WHERE `contact`.`uid` = ?
- AND NOT `contact`.`self`
- AND `contact`.`rel` IN (?, ?)",
- api_user(),
- $rel,
- Contact::FRIEND
- );
-
- $ids = [];
- foreach (DBA::toArray($contacts) as $contact) {
- if ($stringify_ids) {
- $ids[] = $contact['id'];
- } else {
- $ids[] = intval($contact['id']);
- }
- }
-
- return api_format_data('ids', $type, ['id' => $ids]);
-}
-
-/**
- * Returns the ID of every user the user is following.
- *
- * @param string $type Return type (atom, rss, xml, json)
- *
- * @return array|string
- * @throws BadRequestException
- * @throws ForbiddenException
- * @throws ImagickException
- * @throws InternalServerErrorException
- * @throws UnauthorizedException
- * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
- */
-function api_friends_ids($type)
-{
- return api_ff_ids($type, Contact::SHARING);
-}
-
-/**
- * Returns the ID of every user following the user.
- *
- * @param string $type Return type (atom, rss, xml, json)
- *
- * @return array|string
- * @throws BadRequestException
- * @throws ForbiddenException
- * @throws ImagickException
- * @throws InternalServerErrorException
- * @throws UnauthorizedException
- * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
- */
-function api_followers_ids($type)
-{
- return api_ff_ids($type, Contact::FOLLOWER);
-}
-
-/// @TODO move to top of file or somewhere better
-api_register_func('api/friends/ids', 'api_friends_ids', true);
-api_register_func('api/followers/ids', 'api_followers_ids', true);
-
/**
* Sends a new direct message.
*
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @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\Twitter;
+
+use Friendica\Database\DBA;
+use Friendica\DI;
+use Friendica\Model\Profile;
+use Friendica\Model\User;
+use Friendica\Module\BaseApi;
+use Friendica\Model\Contact;
+use Friendica\Network\HTTPException;
+use Friendica\Util\Strings;
+
+abstract class ContactEndpoint extends BaseApi
+{
+ const DEFAULT_COUNT = 20;
+ const MAX_COUNT = 200;
+
+ public static function init(array $parameters = [])
+ {
+ parent::init($parameters);
+
+ if (!self::login()) {
+ throw new HTTPException\UnauthorizedException();
+ }
+ }
+
+ /**
+ * Computes the uid from the contact_id + screen_name parameters
+ *
+ * @param int|null $contact_id
+ * @param string $screen_name
+ * @return int
+ * @throws HTTPException\NotFoundException
+ */
+ protected static function getUid(int $contact_id = null, string $screen_name = null)
+ {
+ $uid = self::$current_user_id;
+
+ if ($contact_id || $screen_name) {
+ // screen_name trumps user_id when both are provided
+ if (!$screen_name) {
+ $contact = Contact::getById($contact_id, ['nick', 'url']);
+ // We don't have the followers of remote accounts so we check for locality
+ if (empty($contact) || !Strings::startsWith($contact['url'], DI::baseUrl()->get())) {
+ throw new HTTPException\NotFoundException(DI::l10n()->t('Contact not found'));
+ }
+
+ $screen_name = $contact['nick'];
+ }
+
+ $user = User::getByNickname($screen_name, ['uid']);
+ if (empty($user)) {
+ throw new HTTPException\NotFoundException(DI::l10n()->t('User not found'));
+ }
+
+ $uid = $user['uid'];
+ }
+
+ return $uid;
+ }
+
+ /**
+ * This methods expands the contact ids into full user objects in an existing result set.
+ *
+ * @param mixed $rel A relationship constant or a list of them
+ * @param int $uid The local user id we query the contacts from
+ * @param int $cursor
+ * @param int $count
+ * @param bool $skip_status
+ * @param bool $include_user_entities
+ * @return array
+ * @throws HTTPException\InternalServerErrorException
+ * @throws HTTPException\NotFoundException
+ * @throws \ImagickException
+ */
+ protected static function list($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $skip_status = false, bool $include_user_entities = true)
+ {
+ $return = self::ids($rel, $uid, $cursor, $count);
+
+ $users = [];
+ foreach ($return['ids'] as $contactId) {
+ $users[] = DI::twitterUser()->createFromContactId($contactId, $uid, $skip_status, $include_user_entities);
+ }
+
+ unset($return['ids']);
+ $return['users'] = $users;
+
+ $return = [
+ 'users' => $users,
+ 'next_cursor' => $return['next_cursor'],
+ 'next_cursor_str' => $return['next_cursor_str'],
+ 'previous_cursor' => $return['previous_cursor'],
+ 'previous_cursor_str' => $return['previous_cursor_str'],
+ 'total_count' => $return['total_count'],
+ ];
+
+
+
+ return $return;
+ }
+
+ /**
+ * @param mixed $rel A relationship constant or a list of them
+ * @param int $uid The local user id we query the contacts from
+ * @param int $cursor
+ * @param int $count
+ * @param bool $stringify_ids
+ * @return array
+ * @throws HTTPException\NotFoundException
+ */
+ protected static function ids($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $stringify_ids = false)
+ {
+ $hide_friends = false;
+ if ($uid != self::$current_user_id) {
+ $profile = Profile::getByUID($uid);
+ if (empty($profile)) {
+ throw new HTTPException\NotFoundException(DI::l10n()->t('Profile not found'));
+ }
+
+ $hide_friends = (bool)$profile['hide-friends'];
+ }
+
+ $condition = DBA::collapseCondition([
+ 'rel' => $rel,
+ 'uid' => $uid,
+ 'self' => false,
+ 'deleted' => false,
+ 'hidden' => false,
+ 'archive' => false,
+ 'pending' => false
+ ]);
+
+ if ($cursor !== -1) {
+ $condition[0] .= " AND `id` > ?";
+ $condition[] = $cursor;
+ }
+
+ $ids = [];
+ $next_cursor = 0;
+ $previous_cursor = 0;
+ $total_count = 0;
+ if (!$hide_friends) {
+ $total_count = DBA::count('contact', $condition);
+
+ $contacts = Contact::selectToArray(['id'], $condition, ['limit' => $count, 'order' => ['id']]);
+
+ // Contains user-specific contact ids
+ $ids = array_column($contacts, 'id');
+
+ // Cursor is on the user-specific contact id since it's the sort field
+ if (count($ids)) {
+ $next_cursor = $ids[count($ids) -1];
+ }
+
+ // Conversion to public contact ids
+ array_walk($ids, function (&$contactId) use ($uid, $stringify_ids) {
+ $cdata = Contact::getPublicAndUserContacID($contactId, $uid);
+ if ($stringify_ids) {
+ $contactId = (string)$cdata['public'];
+ } else {
+ $contactId = (int)$cdata['public'];
+ }
+ });
+
+ // No next page
+ if ($total_count <= count($contacts)) {
+ $next_cursor = 0;
+ }
+ }
+
+ $return = [
+ 'ids' => $ids,
+ 'next_cursor' => $next_cursor,
+ 'next_cursor_str' => (string)$next_cursor,
+ 'previous_cursor' => $previous_cursor,
+ 'previous_cursor_str' => (string)$previous_cursor,
+ 'total_count' => $total_count,
+ ];
+
+ return $return;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @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\Twitter;
+
+use Friendica\Core\System;
+use Friendica\Model\Contact;
+
+/**
+ * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
+ */
+class FollowersIds extends ContactEndpoint
+{
+ public static function rawContent(array $parameters = [])
+ {
+ // Expected value for user_id parameter: public/user contact id
+ $contact_id = $_GET['user_id'] ?? null;
+ $screen_name = $_GET['screen_name'] ?? null;
+ $cursor = $_GET['cursor'] ?? $_GET['since_id'] ?? -1;
+ $stringify_ids = ($_GET['stringify_ids'] ?? 'false') != 'false';
+ $count = min((int) ($_GET['count'] ?? self::DEFAULT_COUNT), self::MAX_COUNT);
+
+ System::jsonExit(self::ids(
+ [Contact::FOLLOWER, Contact::FRIEND],
+ self::getUid($contact_id, $screen_name),
+ $cursor,
+ $count,
+ $stringify_ids
+ ));
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @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\Twitter;
+
+use Friendica\Core\System;
+use Friendica\Model\Contact;
+
+/**
+ * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list
+ */
+class FollowersList extends ContactEndpoint
+{
+ public static function rawContent(array $parameters = [])
+ {
+ // Expected value for user_id parameter: public/user contact id
+ $contact_id = $_GET['user_id'] ?? null;
+ $screen_name = $_GET['screen_name'] ?? null;
+ $cursor = $_GET['cursor'] ?? $_GET['since_id'] ?? -1;
+ $count = min((int) ($_GET['count'] ?? self::DEFAULT_COUNT), self::MAX_COUNT);
+ $skip_status = in_array(($_GET['skip_status'] ?? false), [true, 'true', 't', 1, '1']);
+ $include_user_entities = ($_GET['include_user_entities'] ?? 'true') != 'false';
+
+ System::jsonExit(self::list(
+ [Contact::FOLLOWER, Contact::FRIEND],
+ self::getUid($contact_id, $screen_name),
+ $cursor,
+ $count,
+ $skip_status,
+ $include_user_entities
+ ));
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @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\Twitter;
+
+use Friendica\Core\System;
+use Friendica\Model\Contact;
+
+/**
+ * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
+ */
+class FriendsIds extends ContactEndpoint
+{
+ public static function rawContent(array $parameters = [])
+ {
+ // Expected value for user_id parameter: public/user contact id
+ $contact_id = $_GET['user_id'] ?? null;
+ $screen_name = $_GET['screen_name'] ?? null;
+ $cursor = $_GET['cursor'] ?? $_GET['since_id'] ?? -1;
+ $stringify_ids = ($_GET['stringify_ids'] ?? 'false') != 'false';
+ $count = min((int) ($_GET['count'] ?? self::DEFAULT_COUNT), self::MAX_COUNT);
+
+ System::jsonExit(self::ids(
+ [Contact::SHARING, Contact::FRIEND],
+ self::getUid($contact_id, $screen_name),
+ $cursor,
+ $count,
+ $stringify_ids
+ ));
+ }
+}
--- /dev/null
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @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\Twitter;
+
+use Friendica\Core\System;
+use Friendica\Model\Contact;
+
+/**
+ * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list
+ */
+class FriendsList extends ContactEndpoint
+{
+ public static function rawContent(array $parameters = [])
+ {
+ // Expected value for user_id parameter: public/user contact id
+ $contact_id = $_GET['user_id'] ?? null;
+ $screen_name = $_GET['screen_name'] ?? null;
+ $cursor = $_GET['cursor'] ?? $_GET['since_id'] ?? -1;
+ $count = min((int) ($_GET['count'] ?? self::DEFAULT_COUNT), self::MAX_COUNT);
+ $skip_status = in_array(($_GET['skip_status'] ?? false), [true, 'true', 't', 1, '1']);
+ $include_user_entities = ($_GET['include_user_entities'] ?? 'true') != 'false';
+
+ System::jsonExit(self::list(
+ [Contact::SHARING, Contact::FRIEND],
+ self::getUid($contact_id, $screen_name),
+ $cursor,
+ $count,
+ $skip_status,
+ $include_user_entities
+ ));
+ }
+}
'/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 ]],
],
'/admin' => [
$this->assertEquals('0.9.7', $result['version']);
}
- /**
- * Test the api_ff_ids() function.
- *
- * @return void
- */
- public function testApiFfIds()
- {
- $result = api_ff_ids('json', Contact::FOLLOWER);
- $this->assertEquals(['id' => []], $result);
- }
-
- /**
- * Test the api_ff_ids() function with a result.
- *
- * @return void
- */
- public function testApiFfIdsWithResult()
- {
- $this->markTestIncomplete();
- }
-
- /**
- * Test the api_ff_ids() function without an authenticated user.
- *
- * @return void
- * @expectedException Friendica\Network\HTTPException\ForbiddenException
- */
- public function testApiFfIdsWithoutAuthenticatedUser()
- {
- $_SESSION['authenticated'] = false;
- api_ff_ids('json', Contact::FOLLOWER);
- }
-
- /**
- * Test the api_friends_ids() function.
- *
- * @return void
- */
- public function testApiFriendsIds()
- {
- $result = api_friends_ids('json');
- $this->assertEquals(['id' => []], $result);
- }
-
- /**
- * Test the api_followers_ids() function.
- *
- * @return void
- */
- public function testApiFollowersIds()
- {
- $result = api_followers_ids('json');
- $this->assertEquals(['id' => []], $result);
- }
-
/**
* Test the api_direct_messages_new() function.
*