1.8 KiB · text History 4b6fe3b
defmodule OhMyExOhIrcd.Scrobbles.ChannelConfig do
  @moduledoc """
  Per-channel ScrobbleServ configuration. One row per registered channel
  that has been set up for scrobbling. `joined` tracks whether
  ScrobbleServ is currently sitting in the channel (so it can re-join on
  restart); `allowlist` holds downcased `who` display names.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias OhMyExOhIrcd.Channels.ChannelRecord

  @default_format "%who% is now playing %artist% - %track%"

  schema "scrobble_channel_configs" do
    field :enabled, :boolean, default: true
    field :joined, :boolean, default: false
    field :format, :string, default: @default_format
    field :rate_limit_seconds, :integer, default: 30
    field :restrict_to_allowlist, :boolean, default: false
    field :allowlist, {:array, :string}, default: []

    belongs_to :channel, ChannelRecord

    timestamps(type: :utc_datetime)
  end

  @doc "Default announcement template."
  def default_format, do: @default_format

  def changeset(record, attrs) do
    record
    |> cast(attrs, [
      :channel_id,
      :enabled,
      :joined,
      :format,
      :rate_limit_seconds,
      :restrict_to_allowlist,
      :allowlist
    ])
    |> validate_required([:channel_id])
    |> validate_length(:format, min: 1, max: 390)
    |> validate_number(:rate_limit_seconds,
      greater_than_or_equal_to: 0,
      less_than_or_equal_to: 86_400
    )
    |> sanitize_format()
    |> unique_constraint(:channel_id)
    |> assoc_constraint(:channel)
  end

  # Strip CR/LF/NUL/control chars from the template so an op can't smuggle
  # an IRC line break into announcements.
  defp sanitize_format(changeset) do
    update_change(changeset, :format, fn fmt ->
      fmt
      |> String.replace(~r/[\x00-\x1f]/, "")
      |> String.trim()
    end)
  end
end