diff --git a/include/api.php b/include/api.php index 86792cde50..9ed7d74218 100644 --- a/include/api.php +++ b/include/api.php @@ -1186,182 +1186,6 @@ function api_lists_ownerships($type) api_register_func('api/lists/ownerships', 'api_lists_ownerships', true); -/** - * Returns either the friends of the follower list - * - * Considers friends and followers lists to be private and won't return - * anything if any user_id parameter is passed. - * - * @param string $qtype Either "friends" or "followers" - * @return boolean|array - * @throws BadRequestException - * @throws ForbiddenException - * @throws ImagickException - * @throws InternalServerErrorException - * @throws UnauthorizedException - */ -function api_statuses_f($qtype) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $uid = BaseApi::getCurrentUserID(); - - // pagination - $count = $_GET['count'] ?? 20; - $page = $_GET['page'] ?? 1; - - $start = max(0, ($page - 1) * $count); - - if (!empty($_GET['cursor']) && $_GET['cursor'] == 'undefined') { - /* this is to stop Hotot to load friends multiple times - * I'm not sure if I'm missing return something or - * is a bug in hotot. Workaround, meantime - */ - - /*$ret=Array(); - return array('$users' => $ret);*/ - return false; - } - - $sql_extra = ''; - if ($qtype == 'friends') { - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(Contact::SHARING), intval(Contact::FRIEND)); - } elseif ($qtype == 'followers') { - $sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(Contact::FOLLOWER), intval(Contact::FRIEND)); - } - - if ($qtype == 'blocks') { - $sql_filter = 'AND `blocked` AND NOT `pending`'; - } elseif ($qtype == 'incoming') { - $sql_filter = 'AND `pending`'; - } else { - $sql_filter = 'AND (NOT `blocked` OR `pending`)'; - } - - // @todo This query most likely can be replaced with a Contact::select... - $r = DBA::toArray(DBA::p( - "SELECT `id` - FROM `contact` - WHERE `uid` = ? - AND NOT `self` - $sql_filter - $sql_extra - ORDER BY `nick` - LIMIT ?, ?", - $uid, - $start, - $count - )); - - $ret = []; - foreach ($r as $cid) { - $user = DI::twitterUser()->createFromContactId($cid['id'], $uid, false)->toArray(); - // "uid" is only needed for some internal stuff, so remove it from here - unset($user['uid']); - - if ($user) { - $ret[] = $user; - } - } - - return ['user' => $ret]; -} - -/** - * Returns the list of friends of the provided user - * - * @deprecated By Twitter API in favor of friends/list - * - * @param string $type Either "json" or "xml" - * @return boolean|string|array - * @throws BadRequestException - * @throws ForbiddenException - */ -function api_statuses_friends($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $data = api_statuses_f("friends"); - if ($data === false) { - return false; - } - return DI::apiResponse()->formatData("users", $type, $data); -} - -api_register_func('api/statuses/friends', 'api_statuses_friends', true); - -/** - * Returns the list of followers of the provided user - * - * @deprecated By Twitter API in favor of friends/list - * - * @param string $type Either "json" or "xml" - * @return boolean|string|array - * @throws BadRequestException - * @throws ForbiddenException - */ -function api_statuses_followers($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $data = api_statuses_f("followers"); - if ($data === false) { - return false; - } - return DI::apiResponse()->formatData("users", $type, $data); -} - -api_register_func('api/statuses/followers', 'api_statuses_followers', true); - -/** - * Returns the list of blocked users - * - * @see https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list - * - * @param string $type Either "json" or "xml" - * - * @return boolean|string|array - * @throws BadRequestException - * @throws ForbiddenException - */ -function api_blocks_list($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $data = api_statuses_f('blocks'); - if ($data === false) { - return false; - } - return DI::apiResponse()->formatData("users", $type, $data); -} - -api_register_func('api/blocks/list', 'api_blocks_list', true); - -/** - * Returns the list of pending users IDs - * - * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming - * - * @param string $type Either "json" or "xml" - * - * @return boolean|string|array - * @throws BadRequestException - * @throws ForbiddenException - */ -function api_friendships_incoming($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $data = api_statuses_f('incoming'); - if ($data === false) { - return false; - } - - $ids = []; - foreach ($data['user'] as $user) { - $ids[] = $user['id']; - } - - return DI::apiResponse()->formatData("ids", $type, ['id' => $ids]); -} - -api_register_func('api/friendships/incoming', 'api_friendships_incoming', true); - /** * Sends a new direct message. * diff --git a/src/Module/Api/Twitter/Blocks/Ids.php b/src/Module/Api/Twitter/Blocks/Ids.php new file mode 100644 index 0000000000..bd785ea374 --- /dev/null +++ b/src/Module/Api/Twitter/Blocks/Ids.php @@ -0,0 +1,91 @@ +. + * + */ + +namespace Friendica\Module\Api\Twitter\Blocks; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\Module\Api\Twitter\ContactEndpoint; +use Friendica\Module\BaseApi; + +/** + * @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/mute-block-report-users/api-reference/get-blocks-ids + */ +class Ids extends ContactEndpoint +{ + public function rawContent() + { + self::checkAllowedScope(self::SCOPE_READ); + $uid = BaseApi::getCurrentUserID(); + + // Expected value for user_id parameter: public/user contact id + $cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]); + $stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]); + $count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [ + 'default' => self::DEFAULT_COUNT, + 'min_range' => 1, + 'max_range' => self::MAX_COUNT, + ]]); + // Friendica-specific + $since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT); + $max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT); + $min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT); + + $params = ['order' => ['cid' => true], 'limit' => $count]; + + $condition = ['uid' => $uid, 'blocked' => true]; + + $total_count = (int)DBA::count('user-contact', $condition); + + if (!empty($max_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]); + } + + if (!empty($since_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]); + } + + if (!empty($min_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]); + + $params['order'] = ['cid']; + } + + $ids = []; + + $contacts = DBA::select('user-contact', ['cid'], $condition, $params); + while ($contact = DBA::fetch($contacts)) { + self::setBoundaries($contact['cid']); + $ids[] = $contact['cid']; + } + DBA::close($contacts); + + if (!empty($min_id)) { + array_reverse($ids); + } + + $return = self::ids($ids, $total_count, $cursor, $count, $stringify_ids); + + self::setLinkHeader(); + + System::jsonExit($return); + } +} diff --git a/src/Module/Api/Twitter/Blocks/Lists.php b/src/Module/Api/Twitter/Blocks/Lists.php new file mode 100644 index 0000000000..5b611b16a2 --- /dev/null +++ b/src/Module/Api/Twitter/Blocks/Lists.php @@ -0,0 +1,92 @@ +. + * + */ + +namespace Friendica\Module\Api\Twitter\Blocks; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\Module\Api\Twitter\ContactEndpoint; +use Friendica\Module\BaseApi; + +/** + * @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list + */ +class Lists extends ContactEndpoint +{ + public function rawContent() + { + self::checkAllowedScope(self::SCOPE_READ); + $uid = BaseApi::getCurrentUserID(); + + // Expected value for user_id parameter: public/user contact id + $cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]); + $skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]); + $include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]); + $count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [ + 'default' => self::DEFAULT_COUNT, + 'min_range' => 1, + 'max_range' => self::MAX_COUNT, + ]]); + // Friendica-specific + $since_id = filter_input(INPUT_GET, 'since_id', FILTER_VALIDATE_INT); + $max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT); + $min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT); + + $params = ['order' => ['cid' => true], 'limit' => $count]; + + $condition = ['uid' => $uid, 'blocked' => true]; + + $total_count = (int)DBA::count('user-contact', $condition); + + if (!empty($max_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]); + } + + if (!empty($since_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]); + } + + if (!empty($min_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]); + + $params['order'] = ['cid']; + } + + $ids = []; + + $contacts = DBA::select('user-contact', ['cid'], $condition, $params); + while ($contact = DBA::fetch($contacts)) { + self::setBoundaries($contact['cid']); + $ids[] = $contact['cid']; + } + DBA::close($contacts); + + if (!empty($min_id)) { + array_reverse($ids); + } + + $return = self::list($ids, $total_count, $uid, $cursor, $count, $skip_status, $include_user_entities); + + self::setLinkHeader(); + + System::jsonExit($return); + } +} diff --git a/src/Module/Api/Twitter/Friendships/Incoming.php b/src/Module/Api/Twitter/Friendships/Incoming.php new file mode 100644 index 0000000000..58b6042266 --- /dev/null +++ b/src/Module/Api/Twitter/Friendships/Incoming.php @@ -0,0 +1,91 @@ +. + * + */ + +namespace Friendica\Module\Api\Twitter\Friendships; + +use Friendica\Core\System; +use Friendica\Database\DBA; +use Friendica\Module\Api\Twitter\ContactEndpoint; +use Friendica\Module\BaseApi; + +/** + * @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming + */ +class Incoming extends ContactEndpoint +{ + public function rawContent() + { + self::checkAllowedScope(self::SCOPE_READ); + $uid = BaseApi::getCurrentUserID(); + + // Expected value for user_id parameter: public/user contact id + $cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]); + $stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]); + $count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [ + 'default' => self::DEFAULT_COUNT, + 'min_range' => 1, + 'max_range' => self::MAX_COUNT, + ]]); + // Friendica-specific + $since_id = filter_input(INPUT_GET, 'since_id', FILTER_VALIDATE_INT); + $max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT); + $min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT); + + $params = ['order' => ['cid' => true], 'limit' => $count]; + + $condition = ['uid' => $uid, 'pending' => true]; + + $total_count = (int)DBA::count('user-contact', $condition); + + if (!empty($max_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]); + } + + if (!empty($since_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]); + } + + if (!empty($min_id)) { + $condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]); + + $params['order'] = ['cid']; + } + + $ids = []; + + $contacts = DBA::select('user-contact', ['cid'], $condition, $params); + while ($contact = DBA::fetch($contacts)) { + self::setBoundaries($contact['cid']); + $ids[] = $contact['cid']; + } + DBA::close($contacts); + + if (!empty($min_id)) { + array_reverse($ids); + } + + $return = self::ids($ids, $total_count, $cursor, $count, $stringify_ids); + + self::setLinkHeader(); + + System::jsonExit($return); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 467f3cff43..717adf7b73 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -48,7 +48,8 @@ $apiRoutes = [ '/update_profile_image[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], ], - '/blocks/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/blocks/ids[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Blocks\Ids::class, [R::GET ]], + '/blocks/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Blocks\Lists::class, [R::GET ]], '/conversation/show[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Statusnet\Conversation::class, [R::GET ]], '/direct_messages' => [ '/all[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], @@ -68,7 +69,7 @@ $apiRoutes = [ '/friends/ids[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Ids::class, [R::GET ]], '/friends/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Lists::class, [R::GET ]], '/friendships/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], - '/friendships/incoming[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/friendships/incoming[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friendships\Incoming::class, [R::GET ]], '/friendica' => [ '/activity/{verb:attendmaybe|attendno|attendyes|dislike|like|unattendmaybe|unattendno|unattendyes|undislike|unlike}[.{extension:json|xml|rss|atom}]' @@ -118,8 +119,8 @@ $apiRoutes = [ '/statuses' => [ '/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::DELETE, R::POST]], - '/followers[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/friends[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/followers[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Followers\Lists::class, [R::GET ]], + '/friends[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Lists::class, [R::GET ]], '/friends_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]], '/home_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]], '/mediap[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], diff --git a/tests/legacy/ApiTest.php b/tests/legacy/ApiTest.php index d1c2f25ebf..df76a2ae18 100644 --- a/tests/legacy/ApiTest.php +++ b/tests/legacy/ApiTest.php @@ -2309,9 +2309,9 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFWithFriends() { - $_GET['page'] = -1; - $result = api_statuses_f('friends'); - self::assertArrayHasKey('user', $result); + // $_GET['page'] = -1; + // $result = api_statuses_f('friends'); + // self::assertArrayHasKey('user', $result); } /** @@ -2321,8 +2321,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFWithFollowers() { - $result = api_statuses_f('followers'); - self::assertArrayHasKey('user', $result); + // $result = api_statuses_f('followers'); + // self::assertArrayHasKey('user', $result); } /** @@ -2332,8 +2332,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFWithBlocks() { - $result = api_statuses_f('blocks'); - self::assertArrayHasKey('user', $result); + // $result = api_statuses_f('blocks'); + // self::assertArrayHasKey('user', $result); } /** @@ -2343,8 +2343,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFWithIncoming() { - $result = api_statuses_f('incoming'); - self::assertArrayHasKey('user', $result); + // $result = api_statuses_f('incoming'); + // self::assertArrayHasKey('user', $result); } /** @@ -2354,8 +2354,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFWithUndefinedCursor() { - $_GET['cursor'] = 'undefined'; - self::assertFalse(api_statuses_f('friends')); + // $_GET['cursor'] = 'undefined'; + // self::assertFalse(api_statuses_f('friends')); } /** @@ -2365,8 +2365,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFriends() { - $result = api_statuses_friends('json'); - self::assertArrayHasKey('user', $result); + // $result = api_statuses_friends('json'); + // self::assertArrayHasKey('user', $result); } /** @@ -2376,8 +2376,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFriendsWithUndefinedCursor() { - $_GET['cursor'] = 'undefined'; - self::assertFalse(api_statuses_friends('json')); + // $_GET['cursor'] = 'undefined'; + // self::assertFalse(api_statuses_friends('json')); } /** @@ -2387,8 +2387,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFollowers() { - $result = api_statuses_followers('json'); - self::assertArrayHasKey('user', $result); + // $result = api_statuses_followers('json'); + // self::assertArrayHasKey('user', $result); } /** @@ -2398,8 +2398,8 @@ class ApiTest extends FixtureTest */ public function testApiStatusesFollowersWithUndefinedCursor() { - $_GET['cursor'] = 'undefined'; - self::assertFalse(api_statuses_followers('json')); + // $_GET['cursor'] = 'undefined'; + // self::assertFalse(api_statuses_followers('json')); } /** @@ -2409,8 +2409,8 @@ class ApiTest extends FixtureTest */ public function testApiBlocksList() { - $result = api_blocks_list('json'); - self::assertArrayHasKey('user', $result); + // $result = api_blocks_list('json'); + // self::assertArrayHasKey('user', $result); } /** @@ -2420,8 +2420,8 @@ class ApiTest extends FixtureTest */ public function testApiBlocksListWithUndefinedCursor() { - $_GET['cursor'] = 'undefined'; - self::assertFalse(api_blocks_list('json')); + // $_GET['cursor'] = 'undefined'; + // self::assertFalse(api_blocks_list('json')); } /** @@ -2431,8 +2431,8 @@ class ApiTest extends FixtureTest */ public function testApiFriendshipsIncoming() { - $result = api_friendships_incoming('json'); - self::assertArrayHasKey('id', $result); + // $result = api_friendships_incoming('json'); + // self::assertArrayHasKey('id', $result); } /** @@ -2442,8 +2442,8 @@ class ApiTest extends FixtureTest */ public function testApiFriendshipsIncomingWithUndefinedCursor() { - $_GET['cursor'] = 'undefined'; - self::assertFalse(api_friendships_incoming('json')); + // $_GET['cursor'] = 'undefined'; + // self::assertFalse(api_friendships_incoming('json')); } /**