mobilizon/lib/service/anti_spam/akismet.ex
Thomas Citharel 618b3d23d9
refactor(anti-spam): make anti-spam agnostic from Akismet
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
2023-06-01 14:48:42 +02:00

260 lines
7.2 KiB
Elixir

defmodule Mobilizon.Service.AntiSpam.Akismet do
@moduledoc """
Validate user data
"""
alias Exkismet.Comment, as: AkismetComment
alias Mobilizon.{Actors, Discussions, Events, Users}
alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Comment
alias Mobilizon.Events.Event
alias Mobilizon.Reports.Report
alias Mobilizon.Service.AntiSpam.Provider
alias Mobilizon.Users.User
alias Mobilizon.Web.Endpoint
require Logger
@behaviour Provider
@env Application.compile_env(:mobilizon, :env)
@impl Provider
@spec check_user(String.t(), String.t(), String.t()) ::
:ham | :spam | :discard | {:error, HTTPoison.Response.t()}
def check_user(email, ip, user_agent) do
check_content(%AkismetComment{
blog: homepage(),
user_ip: ip,
comment_author_email: email,
user_agent: user_agent,
comment_type: "signup"
})
end
@impl Provider
@spec check_profile(String.t(), String.t(), String.t() | nil, String.t(), String.t()) ::
:ham | :spam | :discard | {:error, HTTPoison.Response.t()}
def check_profile(username, summary, email \\ nil, ip \\ "127.0.0.1", user_agent \\ nil) do
check_content(%AkismetComment{
blog: homepage(),
user_ip: ip,
comment_author: username,
comment_author_email: email,
comment_content: summary,
user_agent: user_agent,
comment_type: "signup"
})
end
@impl Provider
@spec check_event(String.t(), String.t(), String.t() | nil, String.t(), String.t()) ::
:ham | :spam | :discard | {:error, HTTPoison.Response.t()}
def check_event(event_body, username, email \\ nil, ip \\ "127.0.0.1", user_agent \\ nil) do
check_content(%AkismetComment{
blog: homepage(),
user_ip: ip,
comment_author: username,
comment_author_email: email,
comment_content: event_body,
user_agent: user_agent,
comment_type: "blog-post"
})
end
@impl Provider
@spec check_comment(String.t(), String.t(), boolean(), String.t() | nil, String.t(), String.t()) ::
:ham | :spam | :discard | {:error, HTTPoison.Response.t()}
def check_comment(
comment_body,
username,
is_reply?,
email \\ nil,
ip \\ "127.0.0.1",
user_agent \\ nil
) do
check_content(%AkismetComment{
blog: homepage(),
user_ip: ip,
comment_author: username,
comment_author_email: email,
comment_content: comment_body,
user_agent: user_agent,
comment_type: if(is_reply?, do: "reply", else: "comment")
})
end
@spec report_ham(Report.t()) :: :ok | {:error, atom()} | {:error, HTTPoison.Response.t()}
def report_ham(%Report{} = report) do
report
|> report_to_akismet_comment()
|> submit_ham()
end
@spec report_spam(Report.t()) :: :ok | {:error, atom()} | {:error, HTTPoison.Response.t()}
def report_spam(%Report{} = report) do
report
|> report_to_akismet_comment()
|> submit_spam()
end
@spec homepage() :: String.t()
defp homepage do
Endpoint.url()
end
defp check_content(%AkismetComment{} = comment) do
if @env != :test and ready?() do
comment
|> Exkismet.comment_check(key: api_key())
else
:ham
end
end
defp api_key do
Application.get_env(:mobilizon, __MODULE__) |> get_in([:key])
end
@impl Provider
def ready?, do: !is_nil(api_key())
@spec report_to_akismet_comment(Report.t()) :: AkismetComment.t() | {:error, atom()}
defp report_to_akismet_comment(%Report{comments: [comment | _]}) do
with %Comment{text: body, actor: %Actor{} = actor} <-
Discussions.get_comment_with_preload(comment.id),
{email, preferred_username, ip} <- actor_details(actor) do
%AkismetComment{
blog: homepage(),
comment_content: body,
comment_author_email: email,
comment_author: preferred_username,
user_ip: ip
}
else
{:error, err} ->
{:error, err}
err ->
{:error, err}
end
end
defp report_to_akismet_comment(%Report{event: %Event{id: event_id}}) do
with %Event{description: body, organizer_actor: %Actor{} = actor} <-
Events.get_event_with_preload!(event_id),
{email, preferred_username, ip} <- actor_details(actor) do
%AkismetComment{
blog: homepage(),
comment_content: body,
comment_author_email: email,
comment_author: preferred_username,
user_ip: ip
}
else
{:error, err} ->
{:error, err}
err ->
{:error, err}
end
end
defp report_to_akismet_comment(%Report{reported_id: reported_id}) do
case reported_id |> Actors.get_actor_with_preload!() |> actor_details() do
{email, preferred_username, ip} ->
%AkismetComment{
blog: homepage(),
comment_author_email: email,
comment_author: preferred_username,
user_ip: ip
}
{:error, err} ->
{:error, err}
err ->
{:error, err}
end
end
@spec actor_details(Actor.t()) :: {String.t(), String.t(), any()} | {:error, :invalid_actor}
defp actor_details(%Actor{
type: :Person,
preferred_username: preferred_username,
user: %User{
current_sign_in_ip: current_sign_in_ip,
last_sign_in_ip: last_sign_in_ip,
email: email
}
}) do
{email, preferred_username, current_sign_in_ip || last_sign_in_ip}
end
defp actor_details(%Actor{
type: :Person,
preferred_username: preferred_username,
user_id: user_id
})
when not is_nil(user_id) do
case user_id |> Users.get_user() |> user_details() do
{email, ip} ->
{preferred_username, email, ip}
_ ->
{:error, :invalid_actor}
end
end
defp actor_details(%Actor{
type: :Person,
preferred_username: preferred_username,
user_id: nil
}) do
{nil, preferred_username, "127.0.0.1"}
end
defp actor_details(_) do
{:error, :invalid_actor}
end
@spec user_details(User.t()) :: {String.t(), any()} | {:error, :user_not_found}
defp user_details(%User{
current_sign_in_ip: current_sign_in_ip,
last_sign_in_ip: last_sign_in_ip,
email: email
}) do
{email, current_sign_in_ip || last_sign_in_ip}
end
defp user_details(_), do: {:error, :user_not_found}
@spec submit_spam(AkismetComment.t() | :error) ::
:ok | {:error, atom()} | {:error, HTTPoison.Response.t()}
defp submit_spam(%AkismetComment{} = comment) do
comment
|> tap(fn comment ->
Logger.info("Submitting content to Akismet as spam: #{inspect(comment)}")
end)
|> Exkismet.submit_spam(key: api_key())
|> log_response()
end
defp submit_spam({:error, err}), do: {:error, err}
@spec submit_ham(AkismetComment.t() | :error) ::
:ok | {:error, atom()} | {:error, HTTPoison.Response.t()}
defp submit_ham(%AkismetComment{} = comment) do
comment
|> tap(fn comment ->
Logger.info("Submitting content to Akismet as ham: #{inspect(comment)}")
end)
|> Exkismet.submit_ham(key: api_key())
|> log_response()
end
defp submit_ham({:error, err}), do: {:error, err}
defp log_response(res),
do: tap(res, fn res -> Logger.debug("Return from Akismet is: #{inspect(res)}") end)
end