defmodule LSG.IRC.TxtHandler do @moduledoc """ # [txt](/irc/txt) !txt statistics, file list +txt create new !FILE read a random line from the file !FILE read line # from the file !FILE return a phrase from file who matches +FILE add in FILE -FILE remove line in FILE at index """ def irc_doc, do: @moduledoc def start_link(client) do GenServer.start_link(__MODULE__, [client]) end defstruct client: nil, triggers: %{} def init([client]) do state = %__MODULE__{client: client} ExIRC.Client.add_handler(client, self()) {:ok, %__MODULE__{state | triggers: load()}} end def handle_info({:received, "!reload", _, chan}, state) do {:noreply, %__MODULE__{state | triggers: load()}} end def handle_info({:received, "!txt", _, chan}, state) do map = Enum.map(state.triggers, fn({key, data}) -> "#{key}: #{to_string(Enum.count(data))}" end) total = Enum.reduce(state.triggers, 0, fn({_, data}, acc) -> acc + Enum.count(data) end) detail = Enum.join(map, ", ") total = ". total: #{Enum.count(state.triggers)} fichiers, #{to_string(total)} lignes. Détail: https://sys.115ans.net/irc/txt" (detail<>total) |> String.codepoints |> Enum.chunk_every(440) |> Enum.map(&Enum.join/1) |> Enum.map(fn(line) -> ExIRC.Client.msg(state.client, :privmsg, chan, line) end) {:noreply, state} end def handle_info({:received, "!"<>trigger, _, chan}, state) do {trigger, opts} = clean_trigger(trigger) line = get_random(state.triggers, trigger, opts) if line do ExIRC.Client.msg(state.client, :privmsg, chan, line) end {:noreply, state} end def handle_info({:received, "+txt "<>trigger, _, chan}, state) do {trigger, _} = clean_trigger(trigger) if create_file(trigger) do ExIRC.Client.msg(state.client, :privmsg, chan, "#{trigger}.txt créé. Ajouter: `+#{trigger} …` ; Lire: `!#{trigger}`") {:noreply, %__MODULE__{state | triggers: load()}} else {:noreply, state} end end def handle_info({:received, "+"<>trigger_and_content, _, chan}, state) do if idx = add(state.triggers, trigger_and_content) do ExIRC.Client.msg(state.client, :privmsg, chan, "ajouté. (#{idx})") {:noreply, %__MODULE__{state | triggers: load()}} else {:noreply, state} end end def handle_info({:received, "-"<>trigger_and_id, _, chan}, state) do with \ [trigger, id] <- String.split(trigger_and_id, " ", parts: 2), {trigger, _} = clean_trigger(trigger), data <- Map.get(state.triggers, trigger), {id, ""} <- Integer.parse(id), {text, _id} <- Enum.find(data, fn({_, idx}) -> id-1 == idx end) do data = data |> Enum.into(Map.new) data = Map.delete(data, text) ExIRC.Client.msg(state.client, :privmsg, chan, "#{trigger}.txt##{id} supprimée: #{text}") dump(trigger, data) {:noreply, %__MODULE__{state | triggers: load()}} else error -> IO.inspect("error " <> inspect(error)) {:noreply, state} end end def handle_info(msg, state) do {:noreply, state} end # Load/Reloads text files from disk defp load() do dir = env()[:directory] Path.wildcard(dir <> "/*.txt") |> Enum.reduce(%{}, fn(path, m) -> file = Path.basename(path) [key, "txt"] = String.split(file, ".", parts: 2) data = dir <> file |> File.read! |> String.split("\n") |> Enum.reject(fn(line) -> cond do line == "" -> true !line -> true true -> false end end) |> Enum.with_index Map.put(m, key, data) end) |> Enum.sort |> Enum.into(Map.new) end defp dump(trigger, data) do data = data |> Enum.sort_by(fn({_, idx}) -> idx end) |> Enum.map(fn({text, _}) -> text end) |> Enum.join("\n") File.write!(env()[:directory] <> "/" <> trigger <> ".txt", data<>"\n", []) end defp get_random(triggers, trigger, []) do if data = Map.get(triggers, trigger) do {data, _idx} = Enum.random(data) data else nil end end defp get_random(triggers, trigger, [opt]) do arg = case Integer.parse(opt) do {pos, ""} -> {:index, pos} {_pos, _some_string} -> {:grep, opt} _error -> {:grep, opt} end get_with_param(triggers, trigger, arg) end defp get_with_param(triggers, trigger, {:index, pos}) do data = Map.get(triggers, trigger, %{}) case Enum.find(data, fn({_, index}) -> index+1 == pos end) do {text, _} -> text _ -> nil end end defp get_with_param(triggers, trigger, {:grep, query}) do data = Map.get(triggers, trigger, %{}) regex = Regex.compile!("#{query}", "i") out = Enum.filter(data, fn({txt, _}) -> Regex.match?(regex, txt) end) |> Enum.map(fn({txt, _}) -> txt end) if !Enum.empty?(out) do Enum.random(out) end end defp create_file(name) do File.touch!(env()[:directory] <> "/" <> name <> ".txt") true end defp add(triggers, trigger_and_content) do case String.split(trigger_and_content, " ", parts: 2) do [trigger, content] -> {trigger, _} = clean_trigger(trigger) if Map.has_key?(triggers, trigger) do File.write!(env()[:directory] <> "/" <> trigger <> ".txt", content<>"\n", [:append]) Enum.count(triggers[trigger])+1 end _ -> false end end # fixme: this is definitely the ugliest thing i've ever done defp clean_trigger(trigger) do [trigger | opts] = trigger |> String.strip |> String.split(" ", parts: 2) trigger = trigger |> String.downcase |> String.replace("à", "a") |> String.replace("ä", "a") |> String.replace("â", "a") |> String.replace("é", "e") |> String.replace("è", "e") |> String.replace("ê", "e") |> String.replace("ë", "e") |> String.replace("ç", "c") |> String.replace("ï", "i") |> String.replace("î", "i") |> String.replace(~r/[^a-z0-9]/, "") {trigger, opts} end defp env(), do: Application.get_env(:lsg, __MODULE__) end