-defmodule Pleroma.Search.Elasticsearch do
- @behaviour Pleroma.Search
+# Akkoma: A lightweight social networking server
+# Copyright © 2022-2022 Akkoma Authors <https://git.ihatebeinga.live/IHBAGang/akkoma/>
+# SPDX-License-Identifier: AGPL-3.0-only
- alias Pleroma.Web.MastodonAPI.StatusView
+defmodule Pleroma.Search.Elasticsearch do
+ @behaviour Pleroma.Search.SearchBackend
- defp to_es(term) when is_binary(term) do
- %{
- match: %{
- content: %{
- query: term,
- operator: "AND"
- }
- }
- }
- end
+ alias Pleroma.Activity
+ alias Pleroma.Object.Fetcher
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Search.Elasticsearch.Parsers
- defp to_es({:quoted, term}), do: to_es(term)
+ def es_query(:activity, query, offset, limit) do
+ must = Parsers.Activity.parse(query)
- defp to_es({:filter, ["hashtag", query]}) do
%{
- term: %{
- hashtags: %{
- value: query
+ size: limit,
+ from: offset,
+ terminate_after: 50,
+ timeout: "5s",
+ sort: [
+ "_score",
+ %{"_timestamp" => %{order: "desc", format: "basic_date_time"}}
+ ],
+ query: %{
+ bool: %{
+ must: must
}
}
}
end
- defp to_es({:filter, [field, query]}) do
- %{
- term: %{
- field => %{
- value: query
- }
- }
- }
+ defp maybe_fetch(:activity, search_query) do
+ with true <- Regex.match?(~r/https?:/, search_query),
+ {:ok, object} <- Fetcher.fetch_object_from_id(search_query),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]) do
+ activity
+ else
+ _ -> nil
+ end
end
- defp parse(query) do
- query
- |> SearchParser.parse!()
- |> Enum.map(&to_es/1)
- end
+ def search(user, query, options) do
+ limit = Enum.min([Keyword.get(options, :limit), 40])
+ offset = Keyword.get(options, :offset, 0)
- @impl Pleroma.Search
- def search(%{assigns: %{user: user}} = _conn, %{q: query} = _params, _options) do
- q = %{
- query: %{
- bool: %{
- must: parse(query)
- }
- }
- }
+ parsed_query =
+ query
+ |> String.trim()
+ |> SearchParser.parse!()
- out = Pleroma.Elasticsearch.search_activities(q)
+ activity_fetch_task =
+ Task.async(fn ->
+ maybe_fetch(:activity, String.trim(query))
+ end)
- with {:ok, raw_results} <- out do
- results =
- raw_results
- |> Map.get(:body, %{})
- |> Map.get("hits", %{})
- |> Map.get("hits", [])
- |> Enum.map(fn result -> result["_id"] end)
- |> Pleroma.Activity.all_by_ids_with_object()
+ activity_task =
+ Task.async(fn ->
+ q = es_query(:activity, parsed_query, offset, limit)
- %{
- "accounts" => [],
- "hashtags" => [],
- "statuses" =>
- StatusView.render("index.json",
- activities: results,
- for: user,
- as: :activity
- )
- }
- end
+ :activities
+ |> Pleroma.Search.Elasticsearch.Store.search(q)
+ |> Enum.filter(fn x ->
+ x.data["type"] == "Create" && x.object.data["type"] == "Note" &&
+ Visibility.visible_for_user?(x, user)
+ end)
+ end)
+
+ activity_results = Task.await(activity_task)
+ direct_activity = Task.await(activity_fetch_task)
+
+ activity_results =
+ if direct_activity == nil do
+ activity_results
+ else
+ [direct_activity | activity_results]
+ end
+
+ activity_results
+ end
+
+ @impl true
+ def add_to_index(activity) do
+ Elasticsearch.put_document(Pleroma.Search.Elasticsearch.Cluster, activity, "activities")
+ end
+
+ @impl true
+ def remove_from_index(object) do
+ Elasticsearch.delete_document(Pleroma.Search.Elasticsearch.Cluster, object, "activities")
end
end