Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions lib/teiserver/coordinator/coordinator_server.ex
Comment thread
L-e-x-o-n marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule Teiserver.Coordinator.CoordinatorServer do
alias Teiserver.Coordinator.Parser
alias Teiserver.Lobby
alias Teiserver.Moderation
alias Teiserver.Moderation.RefreshUserRestrictionsTask
alias Teiserver.Room
alias Teiserver.Telemetry

Expand Down Expand Up @@ -158,6 +159,7 @@ defmodule Teiserver.Coordinator.CoordinatorServer do
case converted_message do
^warning_response ->
Client.clear_awaiting_warn_ack(userid)
activate_warning_actions(userid)
CacheUser.send_direct_message(state.userid, userid, "Thank you")

_other_message ->
Expand Down Expand Up @@ -226,6 +228,28 @@ defmodule Teiserver.Coordinator.CoordinatorServer do
)
end

pending_actions =
Moderation.list_actions(
search: [
target_id: userid,
expiry: "Pending only"
]
)

{login_activated, _warning_pending} =
Enum.split_with(pending_actions, fn action ->
not Enum.member?(action.restrictions, "Warning reminder")
end)

db_user =
if Enum.empty?(login_activated) do
db_user
else
activate_actions(login_activated)
RefreshUserRestrictionsTask.refresh_user(userid)
Account.get_user(userid)
end

relevant_restrictions =
db_user.restrictions
|> Enum.filter(fn r -> not Enum.member?(["Bridging"], r) end)
Expand Down Expand Up @@ -319,6 +343,34 @@ defmodule Teiserver.Coordinator.CoordinatorServer do
{:noreply, state}
end

defp activate_warning_actions(userid) do
pending_warnings =
Moderation.list_actions(
search: [
target_id: userid,
expiry: "Pending only",
in_restrictions: "Warning reminder"
]
)

activate_actions(pending_warnings)

if not Enum.empty?(pending_warnings) do
RefreshUserRestrictionsTask.refresh_user(userid)
end
end

defp activate_actions(actions) do
now = DateTime.utc_now()

Enum.each(actions, fn action ->
if action.duration do
expires = DateTime.add(now, action.duration, :second)
Moderation.update_action(action, %{"expires" => expires})
end
end)
end

def make_and_cache_coordinator_account do
account = get_coordinator_account()
Teiserver.cache_put(:application_metadata_cache, "teiserver_coordinator_userid", account.id)
Expand Down
37 changes: 36 additions & 1 deletion lib/teiserver/helpers/date_helper.ex
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ defmodule Teiserver.Helper.DateHelper do
:ymd_hms ->
Calendar.strftime(the_time, "%Y-%m-%d %I:%M:%S")

:ymd_hms24 ->
Calendar.strftime(the_time, "%Y-%m-%d %H:%M:%S")

:hms ->
Calendar.strftime(the_time, "%I:%M:%S")

Expand Down Expand Up @@ -403,7 +406,11 @@ defmodule Teiserver.Helper.DateHelper do
@human_input_regex ~r/([1-9][0-9]*?)\s?(s|seconds?|h|hours?|d|days?|m|months?|y|years?)/

@spec human_input_to_datetime(String.t(), DateTime.t() | nil) :: {:ok, DateTime.t()} | nil
def human_input_to_datetime(human_input, now \\ nil) do
def human_input_to_datetime(human_input, now \\ nil)
def human_input_to_datetime(nil, _now), do: nil
def human_input_to_datetime("", _now), do: nil

def human_input_to_datetime(human_input, now) do
now = now || DateTime.utc_now()
human_input = String.downcase(human_input)

Expand All @@ -427,4 +434,32 @@ defmodule Teiserver.Helper.DateHelper do
nil
end
end

@doc """
Converts human input like "7d" directly to seconds without going through DateTime.
"""
@spec human_input_to_seconds(String.t() | nil) :: integer() | nil
def human_input_to_seconds(nil), do: nil
def human_input_to_seconds(""), do: nil

def human_input_to_seconds(human_input) do
human_input = String.downcase(human_input)

case Regex.run(@human_input_regex, human_input) do
[_full, count, unit] ->
count = String.to_integer(count)
unit = String.first(unit)

case unit do
"s" -> count
"h" -> count * 3_600
"d" -> count * 86_400
"m" -> count * 30 * 86_400
"y" -> count * 365 * 86_400
end

_any_other ->
nil
end
end
end
5 changes: 5 additions & 0 deletions lib/teiserver/moderation/libs/action_lib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ defmodule Teiserver.Moderation.ActionLib do

def _search(query, :expiry, "All"), do: query

def _search(query, :expiry, "Pending only") do
from actions in query,
where: is_nil(actions.expires)
end

def _search(query, :expiry, "Completed only") do
from actions in query,
where: actions.expires < ^DateTime.utc_now()
Expand Down
2 changes: 1 addition & 1 deletion lib/teiserver/moderation/libs/test_lib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule Teiserver.Moderation.ModerationTestLib do
reason: "Reason",
restrictions: ["Login"],
score_modifier: 1000,
expires: DateTime.shift(DateTime.utc_now(), day: 5)
duration: 5 * 86_400
}
|> Map.merge(attrs)
|> Moderation.create_action()
Expand Down
15 changes: 7 additions & 8 deletions lib/teiserver/moderation/schemas/action.ex
Comment thread
BElluu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ defmodule Teiserver.Moderation.Action do
@moduledoc false

alias Ecto.Changeset
alias Teiserver.Helper.DateHelper

use TeiserverWeb, :schema

Expand All @@ -14,6 +13,7 @@ defmodule Teiserver.Moderation.Action do
field :restrictions, {:array, :string}
field :score_modifier, :integer
field :expires, :naive_datetime
field :duration, :integer

field :hidden, :boolean, default: false

Expand All @@ -31,23 +31,22 @@ defmodule Teiserver.Moderation.Action do
struct
|> cast(
params,
~w(target_id reason restrictions score_modifier expires notes hidden discord_message_id appeal_status)a
~w(target_id reason restrictions score_modifier expires duration notes hidden discord_message_id appeal_status)a
)
|> validate_required(~w(target_id reason restrictions expires score_modifier)a)
|> validate_required(~w(target_id reason restrictions score_modifier)a)
|> adjust_restrictions()
|> validate_length(:restrictions, min: 1)
end

defp adjust_restrictions(%Ecto.Changeset{} = struct) do
years = DateTime.shift(DateTime.utc_now(), year: 10)
expires = Changeset.get_field(struct, :expires, [])
inbound_restrictions = Changeset.get_field(struct, :restrictions, [])
duration = Changeset.get_field(struct, :duration)
inbound_restrictions = Changeset.get_field(struct, :restrictions) || []

new_restrictions =
if DateHelper.greater_than(expires, years) and Enum.member?(inbound_restrictions, "Login") do
if is_nil(duration) and Enum.member?(inbound_restrictions, "Login") do
["Permanently banned" | inbound_restrictions] |> Enum.uniq()
else
(inbound_restrictions || []) |> List.delete("Permanently banned")
List.delete(inbound_restrictions, "Permanently banned")
end

Changeset.put_change(struct, :restrictions, new_restrictions)
Expand Down
20 changes: 12 additions & 8 deletions lib/teiserver/moderation/tasks/refresh_user_restrictions_task.ex
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,21 @@ defmodule Teiserver.Moderation.RefreshUserRestrictionsTask do
|> List.flatten()
|> Enum.uniq()

new_restricted_until =
expires_dates =
actions
|> Enum.map(fn a -> a.expires end)
|> List.flatten()
|> Enum.reduce(nil, fn
dt1, nil ->
dt1
|> Enum.reject(&is_nil/1)

dt1, dt2 ->
if DateHelper.compare(dt1, dt2) == :lt, do: dt1, else: dt2
end)
new_restricted_until =
case expires_dates do
[] ->
nil

dates ->
Enum.reduce(dates, fn dt1, dt2 ->
if DateHelper.compare(dt1, dt2) == :lt, do: dt1, else: dt2
end)
end

expires_as_string = new_restricted_until |> Jason.encode!() |> Jason.decode!()

Expand Down
27 changes: 15 additions & 12 deletions lib/teiserver_web/controllers/moderation/action_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -246,16 +246,7 @@ defmodule TeiserverWeb.Moderation.ActionController do
def create(conn, %{"action" => action_params}) do
user = Account.get_user(action_params["target_id"])

expires_string = action_params["expires"] || ""

expires =
case DateHelper.human_input_to_datetime(expires_string) do
{:ok, expires_datetime} ->
expires_datetime

nil ->
action_params["expires"]
end
duration_seconds = DateHelper.human_input_to_seconds(action_params["duration"])

restrictions =
action_params["restrictions"]
Expand All @@ -265,7 +256,8 @@ defmodule TeiserverWeb.Moderation.ActionController do
action_params =
Map.merge(action_params, %{
"restrictions" => restrictions,
"expires" => expires
"duration" => duration_seconds,
"expires" => nil
})

report_ids =
Expand Down Expand Up @@ -350,14 +342,25 @@ defmodule TeiserverWeb.Moderation.ActionController do
def update(conn, %{"id" => id, "action" => action_params}) do
action = Moderation.get_action!(id)

duration_seconds = DateHelper.human_input_to_seconds(action_params["duration"])

restrictions =
action_params["restrictions"]
|> Map.values()
|> Enum.reject(fn v -> v == "false" end)

expires =
if duration_seconds != nil and action.expires != nil do
NaiveDateTime.add(NaiveDateTime.utc_now(), duration_seconds, :second)
else
nil
end

action_params =
Map.merge(action_params, %{
"restrictions" => restrictions
"restrictions" => restrictions,
"duration" => duration_seconds,
"expires" => expires
})

case Moderation.update_action(action, action_params) do
Expand Down
2 changes: 1 addition & 1 deletion lib/teiserver_web/controllers/moderation/ban_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ defmodule TeiserverWeb.Moderation.BanController do
reason: ban.reason,
restrictions: ["Login"],
score_modifier: 0,
expires: DateTime.shift(DateTime.utc_now(), year: 1000)
duration: nil
})

ActionLib.maybe_create_discord_post(action)
Expand Down
20 changes: 17 additions & 3 deletions lib/teiserver_web/templates/moderation/action/form.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,23 @@

<div class="col-lg-6">
<div class="form-group my-2">
{label(f, :expires, class: "control-label")}
{text_input(f, :expires, class: "form-control")}
{error_tag(f, :expires)}
{label(f, :duration, class: "control-label")}
{text_input(f, :duration,
class: "form-control",
placeholder: "e.g. 7d, 24h, 1m, 1y",
value: seconds_to_duration_input(Ecto.Changeset.get_field(@changeset, :duration))
)}
<small class="text-muted">
Timer starts when the user logs in and acknowledges the action.
Leave empty for a permanent ban.
Format: <code>s</code>
seconds, <code>h</code>
hours, <code>d</code>
days, <code>m</code>
months, <code>y</code>
years.
</small>
{error_tag(f, :duration)}
</div>
</div>
</div>
Expand Down
16 changes: 2 additions & 14 deletions lib/teiserver_web/templates/moderation/action/index.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -53,20 +53,8 @@
<td>{action.reason |> String.slice(0..100)}</td>
<td>{action.restrictions |> Enum.join(", ")}</td>

<td>{action.inserted_at |> date_to_str(format: :hms_or_ymd)}</td>
<%= if action.expires do %>
<%= if NaiveDateTime.compare(NaiveDateTime.utc_now(), action.expires) == :gt do %>
<td>Expired</td>
<% else %>
<td>{action.expires |> date_to_str(format: :hms_or_ymd)}</td>
<% end %>

<td>{duration_to_str(action.inserted_at, action.expires)}</td>
<% else %>
<td colspan="2" class="text-warning">
Permanent
</td>
<% end %>
<td>{action.inserted_at |> date_to_str(format: :ymd_hms24)}</td>
<.action_status action={action} />

<td>
<a href={~p"/moderation/action/#{action.id}"} class="btn btn-secondary btn-sm">
Expand Down
Loading
Loading