logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: 90f91168f7ed9af6a4141fafa11417a6419a0c83
parent 557a7d736a873bf57c3e3e271669c0815fd6fe28
Author: marcin mikołajczak <git@mkljczk.pl>
Date:   Sun, 30 Oct 2022 18:52:26 +0100

Expose translation service availability

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>

Diffstat:

Mconfig/description.exs13+++++++++++++
Alib/pleroma/translation.ex54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/pleroma/translation/deepl.ex75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/pleroma/translation/libretranslate.ex66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/pleroma/translation/service.ex20++++++++++++++++++++
Mlib/pleroma/web/api_spec/operations/status_operation.ex6+++---
Mlib/pleroma/web/mastodon_api/views/instance_view.ex3++-
7 files changed, 233 insertions(+), 4 deletions(-)

diff --git a/config/description.exs b/config/description.exs @@ -3523,5 +3523,18 @@ config :pleroma, :config_description, [ suggestion: [100_000] } ] + }, + %{ + group: :pleroma, + key: Pleroma.Translation, + type: :group, + description: "Translation providers", + children: [ + %{ + key: Pleroma.Translation, + type: :service, + suggestions: [Pleroma.Translation.DeepL, Pleroma.Translation.LibreTranslate] + } + ] } ] diff --git a/lib/pleroma/translation.ex b/lib/pleroma/translation.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation do + @cache_ttl 86_400_000 + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + + def configured? do + service = get_service() + + !!service and service.configured? + end + + def translate(text, source_language, target_language) do + cache_key = get_cache_key(text, source_language, target_language) + + case @cachex.get(:translations_cache, cache_key) do + {:ok, nil} -> + service = get_service() + + result = + if !service or !service.configured? do + {:error, :not_found} + else + service.translate(text, source_language, target_language) + end + + store_result(result, cache_key) + + result + + {:ok, result} -> + {:ok, result} + + {:error, error} -> + {:error, error} + end + end + + defp get_service, do: Pleroma.Config.get([__MODULE__, :service]) + + defp get_cache_key(text, source_language, target_language) do + "#{source_language}/#{target_language}/#{content_hash(text)}" + end + + defp store_result({:ok, result}, cache_key) do + @cachex.put(:translations_cache, cache_key, result, ttl: @cache_ttl) + end + + defp store_result(_, _), do: nil + + defp content_hash(text), do: :crypto.hash(:sha256, text) |> Base.encode64() +end diff --git a/lib/pleroma/translation/deepl.ex b/lib/pleroma/translation/deepl.ex @@ -0,0 +1,75 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.DeepL do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Translation.Service + + @behaviour Service + + @impl Service + def configured? do + not_empty_string(get_plan()) and not_empty_string(get_api_key()) + end + + @impl Service + def translate(content, source_language, target_language) do + endpoint = endpoint_url() + + case Pleroma.HTTP.post( + endpoint <> + "?" <> + URI.encode_query(%{ + text: content, + source_lang: source_language |> String.upcase(), + target_lang: target_language, + tag_handling: "html" + }), + "", + [ + {"Content-Type", "application/x-www-form-urlencoded"}, + {"Authorization", "DeepL-Auth-Key #{get_api_key()}"} + ] + ) do + {:ok, %{status: 429}} -> + {:error, :too_many_requests} + + {:ok, %{status: 456}} -> + {:error, :quota_exceeded} + + {:ok, %{status: 200} = res} -> + %{ + "translations" => [ + %{"text" => content, "detected_source_language" => detected_source_language} + ] + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: detected_source_language, + provider: "DeepL" + }} + + _ -> + {:error, :internal_server_error} + end + end + + defp endpoint_url do + case get_plan() do + "free" -> "https://api-free.deepl.com/v2/translate" + _ -> "https://api.deepl.com/v2/translate" + end + end + + defp get_plan do + Pleroma.Config.get([__MODULE__, :plan]) + end + + defp get_api_key do + Pleroma.Config.get([__MODULE__, :api_key]) + end +end diff --git a/lib/pleroma/translation/libretranslate.ex b/lib/pleroma/translation/libretranslate.ex @@ -0,0 +1,66 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.LibreTranslate do + import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1] + + alias Pleroma.Translation.Service + + @behaviour Service + + @impl Service + def configured?, do: not_empty_string(get_base_url()) + + @impl Service + def translate(content, source_language, target_language) do + endpoint = endpoint_url() + + case Pleroma.HTTP.post( + endpoint, + Jason.encode!(%{ + q: content, + source: source_language |> String.upcase(), + target: target_language, + format: "html", + api_key: get_api_key() + }), + [ + {"Content-Type", "application/json"} + ] + ) do + {:ok, %{status: 429}} -> + {:error, :too_many_requests} + + {:ok, %{status: 403}} -> + {:error, :quota_exceeded} + + {:ok, %{status: 200} = res} -> + %{ + "translatedText" => content + } = Jason.decode!(res.body) + + {:ok, + %{ + content: content, + detected_source_language: source_language, + provider: "LibreTranslate" + }} + + _ -> + {:error, :internal_server_error} + end + end + + defp endpoint_url do + get_base_url() <> "/translate" + end + + defp get_base_url do + Pleroma.Config.get([__MODULE__, :base_url]) + end + + defp get_api_key do + Pleroma.Config.get([__MODULE__, :api_key], "") + end +end diff --git a/lib/pleroma/translation/service.ex b/lib/pleroma/translation/service.ex @@ -0,0 +1,20 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Translation.Service do + @callback configured?() :: boolean() + + @callback translate( + content :: String.t(), + source_language :: String.t(), + target_language :: String.t() + ) :: + {:ok, + %{ + content: String.t(), + detected_source_language: String.t(), + provider: String.t() + }} + | {:error, atom()} +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -433,9 +433,9 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do required: false ), responses: %{ - 200 => Operation.response("Translation", "application/json", translation()) - 400 => Operation.response("Error", "application/json", ApiError) - 404 => Operation.response("Error", "application/json", ApiError) + 200 => Operation.response("Translation", "application/json", translation()), + 400 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError), 503 => Operation.response("Error", "application/json", ApiError) } } diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -202,7 +202,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do }, vapid: %{ public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) - } + }, + translation: %{enabled: Pleroma.Translation.configured?} }) end