diff --git a/include/api.php b/include/api.php index 27c4366584..afd28b6155 100644 --- a/include/api.php +++ b/include/api.php @@ -1014,112 +1014,6 @@ function api_media_metadata_create($type) api_register_func('api/media/metadata/create', 'api_media_metadata_create', true, API_METHOD_POST); -/** - * Returns statuses that match a specified query. - * - * @see https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets - * - * @param string $type Return format: json, xml, atom, rss - * - * @return array|string - * @throws BadRequestException if the "q" parameter is missing. - * @throws ForbiddenException - * @throws ImagickException - * @throws InternalServerErrorException - * @throws UnauthorizedException - */ -function api_search($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $uid = BaseApi::getCurrentUserID(); - - if (empty($_REQUEST['q'])) { - throw new BadRequestException('q parameter is required.'); - } - - $searchTerm = trim(rawurldecode($_REQUEST['q'])); - - $data = []; - $data['status'] = []; - $count = 15; - $exclude_replies = !empty($_REQUEST['exclude_replies']); - if (!empty($_REQUEST['rpp'])) { - $count = $_REQUEST['rpp']; - } elseif (!empty($_REQUEST['count'])) { - $count = $_REQUEST['count']; - } - - $since_id = $_REQUEST['since_id'] ?? 0; - $max_id = $_REQUEST['max_id'] ?? 0; - $page = $_REQUEST['page'] ?? 1; - - $start = max(0, ($page - 1) * $count); - - $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; - if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) { - $searchTerm = $matches[1]; - $condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, $uid]; - $tags = DBA::select('tag-search-view', ['uri-id'], $condition); - $uriids = []; - while ($tag = DBA::fetch($tags)) { - $uriids[] = $tag['uri-id']; - } - DBA::close($tags); - - if (empty($uriids)) { - return DI::apiResponse()->formatData('statuses', $type, $data); - } - - $condition = ['uri-id' => $uriids]; - if ($exclude_replies) { - $condition['gravity'] = GRAVITY_PARENT; - } - - $params['group_by'] = ['uri-id']; - } else { - $condition = ["`id` > ? - " . ($exclude_replies ? " AND `gravity` = " . GRAVITY_PARENT : ' ') . " - AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `body` LIKE CONCAT('%',?,'%')", - $since_id, $uid, $_REQUEST['q']]; - if ($max_id > 0) { - $condition[0] .= ' AND `id` <= ?'; - $condition[] = $max_id; - } - } - - $statuses = []; - - if (parse_url($searchTerm, PHP_URL_SCHEME) != '') { - $id = Item::fetchByLink($searchTerm, $uid); - if (!$id) { - // Public post - $id = Item::fetchByLink($searchTerm); - } - - if (!empty($id)) { - $statuses = Post::select([], ['id' => $id]); - } - } - - $statuses = $statuses ?: Post::selectForUser($uid, [], $condition, $params); - - $include_entities = strtolower(($_REQUEST['include_entities'] ?? 'false') == 'true'); - - $ret = []; - while ($status = DBA::fetch($statuses)) { - $ret[] = DI::twitterStatus()->createFromUriId($status['uri-id'], $status['uid'], $include_entities)->toArray(); - } - DBA::close($statuses); - - $data['status'] = $ret; - - return DI::apiResponse()->formatData('statuses', $type, $data); -} - -api_register_func('api/search/tweets', 'api_search', true); -api_register_func('api/search', 'api_search', true); - /** * Repeats a status. * @@ -1330,72 +1224,6 @@ function api_lists_ownerships($type) api_register_func('api/lists/ownerships', 'api_lists_ownerships', true); -/** - * Returns recent statuses from users in the specified group. - * - * @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/create-manage-lists/api-reference/get-lists-ownerships - */ -function api_lists_statuses($type) -{ - BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); - $uid = BaseApi::getCurrentUserID(); - - if (empty($_REQUEST['list_id'])) { - throw new BadRequestException('list_id not specified'); - } - - // params - $count = $_REQUEST['count'] ?? 20; - $page = $_REQUEST['page'] ?? 1; - $since_id = $_REQUEST['since_id'] ?? 0; - $max_id = $_REQUEST['max_id'] ?? 0; - $exclude_replies = (!empty($_REQUEST['exclude_replies']) ? 1 : 0); - $conversation_id = $_REQUEST['conversation_id'] ?? 0; - - $start = max(0, ($page - 1) * $count); - - $groups = DBA::selectToArray('group_member', ['contact-id'], ['gid' => 1]); - $gids = array_column($groups, 'contact-id'); - $condition = ['uid' => $uid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'group-id' => $gids]; - $condition = DBA::mergeConditions($condition, ["`id` > ?", $since_id]); - - if ($max_id > 0) { - $condition[0] .= " AND `id` <= ?"; - $condition[] = $max_id; - } - if ($exclude_replies > 0) { - $condition[0] .= ' AND `gravity` = ?'; - $condition[] = GRAVITY_PARENT; - } - if ($conversation_id > 0) { - $condition[0] .= " AND `parent` = ?"; - $condition[] = $conversation_id; - } - - $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; - $statuses = Post::selectForUser($uid, [], $condition, $params); - - $include_entities = strtolower(($_REQUEST['include_entities'] ?? 'false') == 'true'); - - $items = []; - while ($status = DBA::fetch($statuses)) { - $items[] = DI::twitterStatus()->createFromUriId($status['uri-id'], $status['uid'], $include_entities)->toArray(); - } - DBA::close($statuses); - - return DI::apiResponse()->formatData("statuses", $type, ['status' => $items], Contact::getPublicIdByUserId($uid)); -} - -api_register_func('api/lists/statuses', 'api_lists_statuses', true); - /** * Returns either the friends of the follower list * diff --git a/mod/photos.php b/mod/photos.php index b4ffb756c1..4d75658315 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -1432,7 +1432,7 @@ function photos_content(App $a) ]; $title_e = $item['title']; - $body_e = BBCode::convert($item['body']); + $body_e = BBCode::convertForUriId($item['uri-id'], $item['body']); $comments .= Renderer::replaceMacros($template,[ '$id' => $item['id'], diff --git a/mod/poco.php b/mod/poco.php index 3656571719..651b9938b9 100644 --- a/mod/poco.php +++ b/mod/poco.php @@ -123,7 +123,7 @@ function poco_init(App $a) { } $about = DI::cache()->get("about:" . $contact['updated'] . ":" . $contact['nurl']); if (is_null($about)) { - $about = BBCode::convert($contact['about'], false); + $about = BBCode::convertForUriId($contact['uri-id'], $contact['about']); DI::cache()->set("about:" . $contact['updated'] . ":" . $contact['nurl'], $about); } diff --git a/src/Module/Api/Twitter/Lists/Statuses.php b/src/Module/Api/Twitter/Lists/Statuses.php new file mode 100644 index 0000000000..5c32457dbf --- /dev/null +++ b/src/Module/Api/Twitter/Lists/Statuses.php @@ -0,0 +1,88 @@ +. + * + */ + +namespace Friendica\Module\Api\Twitter\Lists; + +use Friendica\Database\DBA; +use Friendica\Module\BaseApi; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Post; +use Friendica\Network\HTTPException\BadRequestException; + +/** + * Returns recent statuses from users in the specified group. + * + * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships + */ +class Statuses extends BaseApi +{ + public function rawContent() + { + BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); + $uid = BaseApi::getCurrentUserID(); + + if (empty($_REQUEST['list_id'])) { + throw new BadRequestException('list_id not specified'); + } + + // params + $count = $_REQUEST['count'] ?? 20; + $page = $_REQUEST['page'] ?? 1; + $since_id = $_REQUEST['since_id'] ?? 0; + $max_id = $_REQUEST['max_id'] ?? 0; + $exclude_replies = (!empty($_REQUEST['exclude_replies']) ? 1 : 0); + $conversation_id = $_REQUEST['conversation_id'] ?? 0; + + $start = max(0, ($page - 1) * $count); + + $groups = DBA::selectToArray('group_member', ['contact-id'], ['gid' => 1]); + $gids = array_column($groups, 'contact-id'); + $condition = ['uid' => $uid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'group-id' => $gids]; + $condition = DBA::mergeConditions($condition, ["`id` > ?", $since_id]); + + if ($max_id > 0) { + $condition[0] .= " AND `id` <= ?"; + $condition[] = $max_id; + } + if ($exclude_replies > 0) { + $condition[0] .= ' AND `gravity` = ?'; + $condition[] = GRAVITY_PARENT; + } + if ($conversation_id > 0) { + $condition[0] .= " AND `parent` = ?"; + $condition[] = $conversation_id; + } + + $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; + $statuses = Post::selectForUser($uid, [], $condition, $params); + + $include_entities = strtolower(($_REQUEST['include_entities'] ?? 'false') == 'true'); + + $items = []; + while ($status = DBA::fetch($statuses)) { + $items[] = DI::twitterStatus()->createFromUriId($status['uri-id'], $status['uid'], $include_entities)->toArray(); + } + DBA::close($statuses); + + DI::apiResponse()->exit('statuses', ['status' => $items], $this->parameters['extension'] ?? null, Contact::getPublicIdByUserId($uid)); + } +} diff --git a/src/Module/Api/Twitter/Search/Tweets.php b/src/Module/Api/Twitter/Search/Tweets.php new file mode 100644 index 0000000000..2711558ac8 --- /dev/null +++ b/src/Module/Api/Twitter/Search/Tweets.php @@ -0,0 +1,125 @@ +. + * + */ + +namespace Friendica\Module\Api\Twitter\Search; + +use Friendica\Database\DBA; +use Friendica\Module\BaseApi; +use Friendica\DI; +use Friendica\Model\Contact; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Network\HTTPException\BadRequestException; + +/** + * Returns statuses that match a specified query. + * + * @see https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets + */ +class Tweets extends BaseApi +{ + public function rawContent() + { + BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); + $uid = BaseApi::getCurrentUserID(); + + if (empty($_REQUEST['q'])) { + throw new BadRequestException('q parameter is required.'); + } + + $searchTerm = trim(rawurldecode($_REQUEST['q'])); + + $data = []; + $data['status'] = []; + $count = 15; + $exclude_replies = !empty($_REQUEST['exclude_replies']); + if (!empty($_REQUEST['rpp'])) { + $count = $_REQUEST['rpp']; + } elseif (!empty($_REQUEST['count'])) { + $count = $_REQUEST['count']; + } + + $since_id = $_REQUEST['since_id'] ?? 0; + $max_id = $_REQUEST['max_id'] ?? 0; + $page = $_REQUEST['page'] ?? 1; + + $start = max(0, ($page - 1) * $count); + + $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; + if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) { + $searchTerm = $matches[1]; + $condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, $uid]; + $tags = DBA::select('tag-search-view', ['uri-id'], $condition); + $uriids = []; + while ($tag = DBA::fetch($tags)) { + $uriids[] = $tag['uri-id']; + } + DBA::close($tags); + + if (empty($uriids)) { + DI::apiResponse()->exit('statuses', $data, $this->parameters['extension'] ?? null, Contact::getPublicIdByUserId($uid)); + } + + $condition = ['uri-id' => $uriids]; + if ($exclude_replies) { + $condition['gravity'] = GRAVITY_PARENT; + } + + $params['group_by'] = ['uri-id']; + } else { + $condition = ["`id` > ? + " . ($exclude_replies ? " AND `gravity` = " . GRAVITY_PARENT : ' ') . " + AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) + AND `body` LIKE CONCAT('%',?,'%')", + $since_id, $uid, $_REQUEST['q']]; + if ($max_id > 0) { + $condition[0] .= ' AND `id` <= ?'; + $condition[] = $max_id; + } + } + + $statuses = []; + + if (parse_url($searchTerm, PHP_URL_SCHEME) != '') { + $id = Item::fetchByLink($searchTerm, $uid); + if (!$id) { + // Public post + $id = Item::fetchByLink($searchTerm); + } + + if (!empty($id)) { + $statuses = Post::select([], ['id' => $id]); + } + } + + $statuses = $statuses ?: Post::selectForUser($uid, [], $condition, $params); + + $include_entities = strtolower(($_REQUEST['include_entities'] ?? 'false') == 'true'); + + $ret = []; + while ($status = DBA::fetch($statuses)) { + $ret[] = DI::twitterStatus()->createFromUriId($status['uri-id'], $status['uid'], $include_entities)->toArray(); + } + DBA::close($statuses); + + DI::apiResponse()->exit('statuses', ['status' => $ret], $this->parameters['extension'] ?? null, Contact::getPublicIdByUserId($uid)); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index dd4580f46e..467f3cff43 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -73,7 +73,7 @@ $apiRoutes = [ '/friendica' => [ '/activity/{verb:attendmaybe|attendno|attendyes|dislike|like|unattendmaybe|unattendno|unattendyes|undislike|unlike}[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Activity::class, [ R::POST]], - '/notification/seen[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], + '/notification/seen[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Notification\Seen::class, [ R::POST]], '/notification[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Notification::class, [R::GET ]], '/direct_messages_setseen[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\DirectMessages\Setseen::class, [ R::POST]], '/direct_messages_search[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], @@ -97,20 +97,20 @@ $apiRoutes = [ '/help/test[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Help\Test::class, [R::GET ]], '/lists' => [ - '/create[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], - '/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::DELETE, R::POST]], - '/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/ownerships[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/statuses[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/subscriptions[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/update[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], + '/create[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], + '/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::DELETE, R::POST]], + '/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/ownerships[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/statuses[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Lists\Statuses::class, [R::GET ]], + '/subscriptions[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/update[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], ], '/media/upload[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], '/media/metadata/create[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], '/saved_searches/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\SavedSearches::class, [R::GET ]], - '/search/tweets[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], - '/search[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], + '/search/tweets[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Search\Tweets::class, [R::GET ]], + '/search[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Search\Tweets::class, [R::GET ]], '/statusnet/config[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\GNUSocial\Config::class, [R::GET ]], '/statusnet/conversation[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Statusnet\Conversation::class, [R::GET ]], '/statusnet/conversation/{id:\d+}[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Statusnet\Conversation::class, [R::GET ]],