search_controller.ex (3717B)
1 # Pleroma: A lightweight social networking server 2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> 3 # SPDX-License-Identifier: AGPL-3.0-only 4 5 defmodule Pleroma.Web.MastodonAPI.SearchController do 6 use Pleroma.Web, :controller 7 8 alias Pleroma.Activity 9 alias Pleroma.Plugs.RateLimiter 10 alias Pleroma.Repo 11 alias Pleroma.User 12 alias Pleroma.Web 13 alias Pleroma.Web.ControllerHelper 14 alias Pleroma.Web.MastodonAPI.AccountView 15 alias Pleroma.Web.MastodonAPI.StatusView 16 17 require Logger 18 plug(RateLimiter, :search when action in [:search, :search2, :account_search]) 19 20 def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do 21 accounts = User.search(query, search_options(params, user)) 22 res = AccountView.render("accounts.json", users: accounts, for: user, as: :user) 23 24 json(conn, res) 25 end 26 27 def search2(conn, params), do: do_search(:v2, conn, params) 28 def search(conn, params), do: do_search(:v1, conn, params) 29 30 defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = params) do 31 options = search_options(params, user) 32 timeout = Keyword.get(Repo.config(), :timeout, 15_000) 33 default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []} 34 35 result = 36 default_values 37 |> Enum.map(fn {resource, default_value} -> 38 if params["type"] == nil or params["type"] == resource do 39 {resource, fn -> resource_search(version, resource, query, options) end} 40 else 41 {resource, fn -> default_value end} 42 end 43 end) 44 |> Task.async_stream(fn {resource, f} -> {resource, with_fallback(f)} end, 45 timeout: timeout, 46 on_timeout: :kill_task 47 ) 48 |> Enum.reduce(default_values, fn 49 {:ok, {resource, result}}, acc -> 50 Map.put(acc, resource, result) 51 52 _error, acc -> 53 acc 54 end) 55 56 json(conn, result) 57 end 58 59 defp search_options(params, user) do 60 [ 61 resolve: params["resolve"] == "true", 62 following: params["following"] == "true", 63 limit: ControllerHelper.fetch_integer_param(params, "limit"), 64 offset: ControllerHelper.fetch_integer_param(params, "offset"), 65 type: params["type"], 66 author: get_author(params), 67 for_user: user 68 ] 69 |> Enum.filter(&elem(&1, 1)) 70 end 71 72 defp resource_search(_, "accounts", query, options) do 73 accounts = with_fallback(fn -> User.search(query, options) end) 74 AccountView.render("accounts.json", users: accounts, for: options[:for_user], as: :user) 75 end 76 77 defp resource_search(_, "statuses", query, options) do 78 statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) 79 StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity) 80 end 81 82 defp resource_search(:v2, "hashtags", query, _options) do 83 tags_path = Web.base_url() <> "/tag/" 84 85 query 86 |> prepare_tags() 87 |> Enum.map(fn tag -> 88 tag = String.trim_leading(tag, "#") 89 %{name: tag, url: tags_path <> tag} 90 end) 91 end 92 93 defp resource_search(:v1, "hashtags", query, _options) do 94 query 95 |> prepare_tags() 96 |> Enum.map(fn tag -> String.trim_leading(tag, "#") end) 97 end 98 99 defp prepare_tags(query) do 100 query 101 |> String.split() 102 |> Enum.uniq() 103 |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end) 104 end 105 106 defp with_fallback(f, fallback \\ []) do 107 try do 108 f.() 109 rescue 110 error -> 111 Logger.error("#{__MODULE__} search error: #{inspect(error)}") 112 fallback 113 end 114 end 115 116 defp get_author(%{"account_id" => account_id}) when is_binary(account_id), 117 do: User.get_cached_by_id(account_id) 118 119 defp get_author(_params), do: nil 120 end