summaryrefslogtreecommitdiff
path: root/lib/lsg_irc/txt_handler.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/lsg_irc/txt_handler.ex')
-rw-r--r--lib/lsg_irc/txt_handler.ex318
1 files changed, 318 insertions, 0 deletions
diff --git a/lib/lsg_irc/txt_handler.ex b/lib/lsg_irc/txt_handler.ex
new file mode 100644
index 0000000..f946092
--- /dev/null
+++ b/lib/lsg_irc/txt_handler.ex
@@ -0,0 +1,318 @@
+defmodule LSG.IRC.TxtHandler do
+ alias LSG.IRC.UserTrack
+
+ @moduledoc """
+ # [txt](/irc/txt)
+
+ * **!txt**: liste des fichiers et statistiques.
+ Les fichiers avec une `*` sont vérrouillés.
+ [Voir sur le web](/irc/txt).
+
+ * **!`FICHIER`**: lis aléatoirement une ligne du fichier `FICHIER`.
+ * **!`FICHIER` `<chiffre>`**: lis la ligne `<chiffre>` du fichier `FICHIER`.
+ * **!`FILE` `<recherche>`**: recherche une ligne contenant `<recherche>` dans `FICHIER`.
+
+ * **+txt `<file`>**: crée le fichier `<file>`.
+ * **+`FICHIER` `<texte>`**: ajoute une ligne `<texte>` dans le fichier `FICHIER`.
+ * **-`FICHIER` `<chiffre>`**: supprime la ligne `<chiffre>` du fichier `FICHIER`.
+
+ * **+txtro, +txtrw**. op seulement. active/désactive le mode lecture seule.
+ * **+txtlock `<fichier>`, -txtlock `<fichier>`**. op seulement. active/désactive le verrouillage d'un fichier.
+ """
+
+ def short_irc_doc, do: "!txt https://sys.115ans.net/irc/txt "
+ def irc_doc, do: @moduledoc
+
+ def start_link(client) do
+ GenServer.start_link(__MODULE__, [client])
+ end
+
+ defstruct client: nil, triggers: %{}, rw: true, locks: nil
+
+ def init([client]) do
+ dets_locks_filename = (LSG.data_path() <> "/" <> "txtlocks.dets") |> String.to_charlist
+ {:ok, locks} = :dets.open_file(dets_locks_filename, [])
+ state = %__MODULE__{client: client, locks: locks}
+ 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
+
+ ## -- ADMIN RO/RW
+ def handle_info({:received, "+txtrw", %ExIRC.SenderInfo{nick: nick}, chan}, state = %__MODULE__{rw: false}) do
+ if UserTrack.operator?(chan, nick) do
+ say(state, chan, "txt: écriture réactivée")
+ {:noreply, %__MODULE__{state | rw: true}}
+ else
+ {:noreply, state}
+ end
+ end
+
+ def handle_info({:received, "+txtro", %ExIRC.SenderInfo{nick: nick}, chan}, state = %__MODULE__{rw: true}) do
+ if UserTrack.operator?(chan, nick) do
+ say(state, chan, "txt: écriture désactivée")
+ {:noreply, %__MODULE__{state | rw: false}}
+ else
+ {:noreply, state}
+ end
+ end
+
+ def handle_info({:received, "+txtro", _, _}, state) do
+ {:noreply, state}
+ end
+ def handle_info({:received, "+txtrw", _, _}, state) do
+ {:noreply, state}
+ end
+
+ ## -- ADMIN LOCKS
+ def handle_info({:received, "+txtlock " <> trigger, %ExIRC.SenderInfo{nick: nick}, chan}, state) do
+ with \
+ {trigger, _} <- clean_trigger(trigger),
+ true <- UserTrack.operator?(chan, nick)
+ do
+ :dets.insert(state.locks, {trigger})
+ say(state, chan, "txt: #{trigger} verrouillé")
+ end
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "-txtlock " <> trigger, %ExIRC.SenderInfo{nick: nick}, chan}, state) do
+ with \
+ {trigger, _} <- clean_trigger(trigger),
+ true <- UserTrack.operator?(chan, nick),
+ true <- :dets.member(state.locks, trigger)
+ do
+ :dets.delete(state.locks, trigger)
+ say(state, chan, "txt: #{trigger} déverrouillé")
+ end
+ {:noreply, state}
+ end
+
+ # -- ADD
+
+ def handle_info({:received, "!txt", _, chan}, state) do
+ map = Enum.map(state.triggers, fn({key, data}) ->
+ locked? = case :dets.lookup(state.locks, key) do
+ [{trigger}] -> "*"
+ _ -> ""
+ end
+
+ "#{key}: #{to_string(Enum.count(data))}#{locked?}"
+ 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"
+
+ ro = if !state.rw, do: " (lecture seule activée)", else: ""
+
+ (detail<>total<>ro)
+ |> 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, sender=%ExIRC.SenderInfo{nick: nick}, chan}, state) do
+ with \
+ {trigger, _} <- clean_trigger(trigger),
+ true <- can_write?(state, chan, sender, trigger),
+ :ok <- 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, sender=%ExIRC.SenderInfo{nick: nick}, chan}, state) do
+ with \
+ {trigger, _} <- clean_trigger(trigger_and_content),
+ true <- can_write?(state, chan, sender, trigger),
+ {:ok, idx} <- add(state.triggers, trigger_and_content)
+ do
+ ExIRC.Client.msg(state.client, :privmsg, chan, "#{nick}: ajouté à #{trigger}. (#{idx})")
+ {:noreply, %__MODULE__{state | triggers: load()}}
+ else
+ _ -> {:noreply, state}
+ end
+ end
+
+ def handle_info({:received, "-"<>trigger_and_id, sender=%ExIRC.SenderInfo{nick: nick}, chan}, state) do
+ with \
+ [trigger, id] <- String.split(trigger_and_id, " ", parts: 2),
+ {trigger, _} = clean_trigger(trigger),
+ true <- can_write?(state, chan, sender, 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
+ Path.wildcard(directory() <> "/*.txt")
+ |> Enum.reduce(%{}, fn(path, m) ->
+ file = Path.basename(path)
+ [key, "txt"] = String.split(file, ".", parts: 2)
+ data = directory() <> 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!(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!(directory() <> "/" <> name <> ".txt")
+ :ok
+ 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!(directory() <> "/" <> trigger <> ".txt", content<>"\n", [:append])
+ idx = Enum.count(triggers[trigger])+1
+ {:ok, idx}
+ else
+ :error
+ end
+ _ -> :error
+ 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 directory() do
+ Application.get_env(:lsg, :data_path) <> "/irc.txt/"
+ end
+
+ defp can_write?(state = %__MODULE__{rw: rw?, locks: locks}, channel, sender, trigger) do
+ admin? = LSG.IRC.admin?(sender)
+ operator? = LSG.IRC.UserTrack.operator?(channel, sender.nick)
+ locked? = case :dets.lookup(locks, trigger) do
+ [{trigger}] -> true
+ _ -> false
+ end
+ unlocked? = if rw? == false, do: false, else: !locked?
+ can? = admin? || operator? || unlocked?
+
+ if !can? do
+ reason = if !rw?, do: "lecture seule", else: "fichier vérrouillé"
+ say(state, channel, "#{sender.nick}: permission refusée (#{reason})")
+ end
+ can?
+ end
+
+ defp say(%__MODULE__{client: client}, chan, message) do
+ ExIRC.Client.msg(client, :privmsg, chan, message)
+ end
+
+end