feat(spam): Introduce checking new accounts, events & comments for spam with the help of Akismet
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
251
lib/service/akismet.ex
Normal file
251
lib/service/akismet.ex
Normal file
@@ -0,0 +1,251 @@
|
||||
defmodule Mobilizon.Service.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.Users.User
|
||||
alias Mobilizon.Web.Endpoint
|
||||
require Logger
|
||||
|
||||
@env Application.compile_env(:mobilizon, :env)
|
||||
|
||||
@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
|
||||
|
||||
@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
|
||||
|
||||
@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
|
||||
|
||||
@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
|
||||
|
||||
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}
|
||||
|
||||
err ->
|
||||
{: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(err) 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
|
||||
Reference in New Issue
Block a user