diff options
Diffstat (limited to 'lib/plugins/txt.ex')
-rw-r--r-- | lib/plugins/txt.ex | 410 |
1 files changed, 248 insertions, 162 deletions
diff --git a/lib/plugins/txt.ex b/lib/plugins/txt.ex index 3b431e9..c582e67 100644 --- a/lib/plugins/txt.ex +++ b/lib/plugins/txt.ex @@ -53,12 +53,21 @@ defmodule Nola.Plugins.Txt do end def init([]) do - dets_locks_filename = (Nola.data_path() <> "/" <> "txtlocks.dets") |> String.to_charlist + dets_locks_filename = (Nola.data_path() <> "/" <> "txtlocks.dets") |> String.to_charlist() {:ok, locks} = :dets.open_file(dets_locks_filename, []) - markov_handler = Keyword.get(Application.get_env(:nola, __MODULE__, []), :markov_handler, Nola.Plugins.Txt.Markov.Native) + + markov_handler = + Keyword.get( + Application.get_env(:nola, __MODULE__, []), + :markov_handler, + Nola.Plugins.Txt.MarkovPyMarkovify + ) + {:ok, markov} = markov_handler.start_link() - {:ok, _} = Registry.register(Nola.PubSub, "triggers", [plugin: __MODULE__]) - {:ok, %__MODULE__{locks: locks, markov_handler: markov_handler, markov: markov, triggers: load()}} + {:ok, _} = Registry.register(Nola.PubSub, "triggers", plugin: __MODULE__) + + {:ok, + %__MODULE__{locks: locks, markov_handler: markov_handler, markov: markov, triggers: load()}} end def handle_info({:received, "!reload", _, chan}, state) do @@ -69,7 +78,10 @@ defmodule Nola.Plugins.Txt do # ADMIN: RW/RO # - def handle_info({:irc, :trigger, "txtrw", msg = %{channel: channel, trigger: %{type: :plus}}}, state = %{rw: false}) do + def handle_info( + {:irc, :trigger, "txtrw", msg = %{channel: channel, trigger: %{type: :plus}}}, + state = %{rw: false} + ) do if channel && UserTrack.operator?(msg.network, channel, msg.sender.nick) do msg.replyfun.("txt: écriture réactivée") {:noreply, %__MODULE__{state | rw: true}} @@ -78,7 +90,10 @@ defmodule Nola.Plugins.Txt do end end - def handle_info({:irc, :trigger, "txtrw", msg = %{channel: channel, trigger: %{type: :minus}}}, state = %{rw: true}) do + def handle_info( + {:irc, :trigger, "txtrw", msg = %{channel: channel, trigger: %{type: :minus}}}, + state = %{rw: true} + ) do if channel && UserTrack.operator?(msg.network, channel, msg.sender.nick) do msg.replyfun.("txt: écriture désactivée") {:noreply, %__MODULE__{state | rw: false}} @@ -91,26 +106,30 @@ defmodule Nola.Plugins.Txt do # ADMIN: LOCKS # - def handle_info({:irc, :trigger, "txtlock", msg = %{trigger: %{type: :plus, args: [trigger]}}}, state) do - with \ - {trigger, _} <- clean_trigger(trigger), - true <- UserTrack.operator?(msg.network, msg.channel, msg.sender.nick) - do + def handle_info( + {:irc, :trigger, "txtlock", msg = %{trigger: %{type: :plus, args: [trigger]}}}, + state + ) do + with {trigger, _} <- clean_trigger(trigger), + true <- UserTrack.operator?(msg.network, msg.channel, msg.sender.nick) do :dets.insert(state.locks, {trigger}) msg.replyfun.("txt: #{trigger} verrouillé") end + {:noreply, state} end - def handle_info({:irc, :trigger, "txtlock", msg = %{trigger: %{type: :minus, args: [trigger]}}}, state) do - with \ - {trigger, _} <- clean_trigger(trigger), - true <- UserTrack.operator?(msg.network, msg.channel, msg.sender.nick), - true <- :dets.member(state.locks, trigger) - do + def handle_info( + {:irc, :trigger, "txtlock", msg = %{trigger: %{type: :minus, args: [trigger]}}}, + state + ) do + with {trigger, _} <- clean_trigger(trigger), + true <- UserTrack.operator?(msg.network, msg.channel, msg.sender.nick), + true <- :dets.member(state.locks, trigger) do :dets.delete(state.locks, trigger) msg.replyfun.("txt: #{trigger} déverrouillé") end + {:noreply, state} end @@ -119,26 +138,26 @@ defmodule Nola.Plugins.Txt do # def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :dot}}}, state) do - map = Enum.map(state.triggers, fn({key, data}) -> - ignore? = String.contains?(key, ".") - locked? = case :dets.lookup(state.locks, key) do - [{trigger}] -> "*" - _ -> "" - end + total = + Enum.reduce(state.triggers, 0, fn {_, data}, acc -> + acc + Enum.count(data) + end) - unless ignore?, do: "#{key}: #{to_string(Enum.count(data))}#{locked?}" - end) - |> Enum.filter(& &1) - 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" + link = + NolaWeb.Router.Helpers.irc_url( + NolaWeb.Endpoint, + :txt, + msg.network, + NolaWeb.format_chan(msg.channel) + ) + + total = "#{Enum.count(state.triggers)} fichiers, #{to_string(total)} lignes: #{link}" ro = if !state.rw, do: " (lecture seule activée)", else: "" - (detail<>total<>ro) + (total <> ro) |> msg.replyfun.() + {:noreply, state} end @@ -147,48 +166,53 @@ defmodule Nola.Plugins.Txt do # def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :bang, args: []}}}, state) do - result = Enum.reduce(state.triggers, [], fn({trigger, data}, acc) -> - Enum.reduce(data, acc, fn({l, _}, acc) -> - [{trigger, l} | acc] + result = + Enum.reduce(state.triggers, [], fn {trigger, data}, acc -> + Enum.reduce(data, acc, fn {l, _}, acc -> + [{trigger, l} | acc] + end) end) - end) - |> Enum.shuffle() + |> Enum.shuffle() if !Enum.empty?(result) do {source, line} = Enum.random(result) msg.replyfun.(format_line(line, "#{source}: ", msg)) end + {:noreply, state} end def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :bang, args: args}}}, state) do - grep = Enum.join(args, " ") - |> String.downcase - |> :unicode.characters_to_nfd_binary() - - result = with_stateful_results(msg, {:bang,"txt",msg.network,msg.channel,grep}, fn() -> - Enum.reduce(state.triggers, [], fn({trigger, data}, acc) -> - if !String.contains?(trigger, ".") do - Enum.reduce(data, acc, fn({l, _}, acc) -> - [{trigger, l} | acc] - end) - else - acc - end - end) - |> Enum.filter(fn({_, line}) -> - line - |> String.downcase() - |> :unicode.characters_to_nfd_binary() - |> String.contains?(grep) + grep = + Enum.join(args, " ") + |> String.downcase() + |> :unicode.characters_to_nfd_binary() + + result = + with_stateful_results(msg, {:bang, "txt", msg.network, msg.channel, grep}, fn -> + Enum.reduce(state.triggers, [], fn {trigger, data}, acc -> + if !String.contains?(trigger, ".") do + Enum.reduce(data, acc, fn {l, _}, acc -> + [{trigger, l} | acc] + end) + else + acc + end + end) + |> Enum.filter(fn {_, line} -> + line + |> String.downcase() + |> :unicode.characters_to_nfd_binary() + |> String.contains?(grep) + end) + |> Enum.shuffle() end) - |> Enum.shuffle() - end) if result do {source, line} = result msg.replyfun.(["#{source}: " | line]) end + {:noreply, state} end @@ -200,11 +224,15 @@ defmodule Nola.Plugins.Txt do end def with_stateful_results(key, initfun) do - pid = case :global.whereis_name(key) do - :undefined -> - start_stateful_results(key, initfun.()) - pid -> pid - end + pid = + case :global.whereis_name(key) do + :undefined -> + start_stateful_results(key, initfun.()) + + pid -> + pid + end + if pid, do: wait_stateful_results(key, initfun, pid) end @@ -214,19 +242,24 @@ defmodule Nola.Plugins.Txt do def start_stateful_results(key, list) do me = self() - {pid, _} = spawn_monitor(fn() -> - Process.monitor(me) - stateful_results(me, list) - end) + + {pid, _} = + spawn_monitor(fn -> + Process.monitor(me) + stateful_results(me, list) + end) + :yes = :global.register_name(key, pid) pid end def wait_stateful_results(key, initfun, pid) do send(pid, :get) + receive do {:stateful_results, line} -> line + {:DOWN, _ref, :process, ^pid, reason} -> with_stateful_results(key, initfun) after @@ -246,10 +279,11 @@ defmodule Nola.Plugins.Txt do :get -> send(owner, {:stateful_results, line}) stateful_results(owner, rest) + {:DOWN, _ref, :process, ^owner, _} -> :ok - after - @stateful_results_expire -> :ok + after + @stateful_results_expire -> :ok end end @@ -261,20 +295,28 @@ defmodule Nola.Plugins.Txt do case state.markov_handler.sentence(state.markov) do {:ok, line} -> msg.replyfun.(line) + error -> - Logger.error "Txt Markov error: "<>inspect error + Logger.error("Txt Markov error: " <> inspect(error)) end + {:noreply, state} end - def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :tilde, args: complete}}}, state) do + def handle_info( + {:irc, :trigger, "txt", msg = %{trigger: %{type: :tilde, args: complete}}}, + state + ) do complete = Enum.join(complete, " ") + case state.markov_handler.complete_sentence(complete, state.markov) do {:ok, line} -> msg.replyfun.(line) + error -> - Logger.error "Txt Markov error: "<>inspect error + Logger.error("Txt Markov error: " <> inspect(error)) end + {:noreply, state} end @@ -282,12 +324,13 @@ defmodule Nola.Plugins.Txt do # TXT CREATE # - def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :plus, args: [trigger]}}}, state) do - with \ - {trigger, _} <- clean_trigger(trigger), - true <- can_write?(state, msg, trigger), - :ok <- create_file(trigger) - do + def handle_info( + {:irc, :trigger, "txt", msg = %{trigger: %{type: :plus, args: [trigger]}}}, + state + ) do + with {trigger, _} <- clean_trigger(trigger), + true <- can_write?(state, msg, trigger), + :ok <- create_file(trigger) do msg.replyfun.("#{trigger}.txt créé. Ajouter: `+#{trigger} …` ; Lire: `!#{trigger}`") {:noreply, %__MODULE__{state | triggers: load()}} else @@ -301,23 +344,35 @@ defmodule Nola.Plugins.Txt do def handle_info({:irc, :trigger, trigger, m = %{trigger: %{type: :query, args: opts}}}, state) do {trigger, _} = clean_trigger(trigger) + if Map.get(state.triggers, trigger) do - url = if m.channel do - NolaWeb.Router.Helpers.irc_url(NolaWeb.Endpoint, :txt, m.network, NolaWeb.format_chan(m.channel), trigger) - else - NolaWeb.Router.Helpers.irc_url(NolaWeb.Endpoint, :txt, trigger) - end + url = + if m.channel do + NolaWeb.Router.Helpers.irc_url( + NolaWeb.Endpoint, + :txt, + m.network, + NolaWeb.format_chan(m.channel), + trigger + ) + else + NolaWeb.Router.Helpers.irc_url(NolaWeb.Endpoint, :txt, trigger) + end + m.replyfun.("-> #{url}") end + {:noreply, state} end def handle_info({:irc, :trigger, trigger, msg = %{trigger: %{type: :bang, args: opts}}}, state) do {trigger, _} = clean_trigger(trigger) line = get_random(msg, state.triggers, trigger, String.trim(Enum.join(opts, " "))) + if line do msg.replyfun.(format_line(line, nil, msg)) end + {:noreply, state} end @@ -325,18 +380,20 @@ defmodule Nola.Plugins.Txt do # TXT: ADD # - def handle_info({:irc, :trigger, trigger, msg = %{trigger: %{type: :plus, args: content}}}, state) do - with \ - true <- can_write?(state, msg, trigger), - {:ok, idx} <- add(state.triggers, msg.text) - do + def handle_info( + {:irc, :trigger, trigger, msg = %{trigger: %{type: :plus, args: content}}}, + state + ) do + with true <- can_write?(state, msg, trigger), + {:ok, idx} <- add(state.triggers, msg.text) do msg.replyfun.("#{msg.sender.nick}: ajouté à #{trigger}. (#{idx})") {:noreply, %__MODULE__{state | triggers: load()}} else {:error, {:jaro, string, idx}} -> msg.replyfun.("#{msg.sender.nick}: doublon #{trigger}##{idx}: #{string}") + error -> - Logger.debug("txt add failed: #{inspect error}") + Logger.debug("txt add failed: #{inspect(error)}") {:noreply, state} end end @@ -346,13 +403,11 @@ defmodule Nola.Plugins.Txt do # def handle_info({:irc, :trigger, trigger, msg = %{trigger: %{type: :minus, args: [id]}}}, state) do - with \ - true <- can_write?(state, msg, 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) + with true <- can_write?(state, msg, 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) msg.replyfun.("#{msg.sender.nick}: #{trigger}.txt##{id} supprimée: #{text}") dump(trigger, data) @@ -363,7 +418,7 @@ defmodule Nola.Plugins.Txt do end end - def handle_info(:reload_markov, state=%__MODULE__{triggers: triggers, markov: markov}) do + def handle_info(:reload_markov, state = %__MODULE__{triggers: triggers, markov: markov}) do state.markov_handler.reload(state.triggers, state.markov) {:noreply, state} end @@ -382,41 +437,48 @@ defmodule Nola.Plugins.Txt do :dets.sync(state.locks) :dets.close(state.locks) end + :ok end # Load/Reloads text files from disk defp load() do - triggers = Path.wildcard(directory() <> "/*.txt") - |> Enum.reduce(%{}, fn(path, m) -> - file = Path.basename(path) - key = String.replace(file, ".txt", "") - data = directory() <> file - |> File.read! - |> String.split("\n") - |> Enum.reject(fn(line) -> - cond do - line == "" -> true - !line -> true - true -> false - end + triggers = + Path.wildcard(directory() <> "/*.txt") + |> Enum.reduce(%{}, fn path, m -> + file = Path.basename(path) + key = String.replace(file, ".txt", "") + + 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.with_index - Map.put(m, key, data) - end) - |> Enum.sort - |> Enum.into(Map.new) + |> Enum.sort() + |> Enum.into(Map.new()) send(self(), :reload_markov) triggers 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", []) + 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(msg, triggers, trigger, []) do @@ -429,30 +491,36 @@ defmodule Nola.Plugins.Txt do end defp get_random(msg, triggers, trigger, opt) do - arg = case Integer.parse(opt) do - {pos, ""} -> {:index, pos} - {_pos, _some_string} -> {:grep, opt} - _error -> {:grep, opt} - end + arg = + case Integer.parse(opt) do + {pos, ""} -> {:index, pos} + {_pos, _some_string} -> {:grep, opt} + _error -> {:grep, opt} + end + get_with_param(msg, triggers, trigger, arg) end defp get_with_param(msg, triggers, trigger, {:index, pos}) do data = Map.get(triggers, trigger, %{}) - case Enum.find(data, fn({_, index}) -> index+1 == pos end) do + + case Enum.find(data, fn {_, index} -> index + 1 == pos end) do {text, _} -> text _ -> nil end end defp get_with_param(msg, triggers, trigger, {:grep, query}) do - out = with_stateful_results(msg, {:grep, trigger, query}, fn() -> - data = Map.get(triggers, trigger, %{}) - regex = Regex.compile!("#{query}", "i") - Enum.filter(data, fn({txt, _}) -> Regex.match?(regex, txt) end) - |> Enum.map(fn({txt, _}) -> txt end) - |> Enum.shuffle() - end) + out = + with_stateful_results(msg, {:grep, trigger, query}, fn -> + data = Map.get(triggers, trigger, %{}) + regex = Regex.compile!("#{query}", "i") + + Enum.filter(data, fn {txt, _} -> Regex.match?(regex, txt) end) + |> Enum.map(fn {txt, _} -> txt end) + |> Enum.shuffle() + end) + if out, do: out end @@ -466,50 +534,57 @@ defmodule Nola.Plugins.Txt do [trigger, content] -> {trigger, _} = clean_trigger(trigger) - if Map.has_key?(triggers, trigger) do - jaro = Enum.find(triggers[trigger], fn({string, idx}) -> String.jaro_distance(content, string) > 0.9 end) + jaro = + Enum.find(triggers[trigger], fn {string, idx} -> + String.jaro_distance(content, string) > 0.9 + end) if jaro do {string, idx} = jaro - {:error, {:jaro, string, idx}} + {:error, {:jaro, string, idx + 1}} else - File.write!(directory() <> "/" <> trigger <> ".txt", content<>"\n", [:append]) - idx = Enum.count(triggers[trigger])+1 + File.write!(directory() <> "/" <> trigger <> ".txt", content <> "\n", [:append]) + idx = Enum.count(triggers[trigger]) + 1 {:ok, idx} end else {:error, :notxt} end - _ -> {:error, :badarg} + + _ -> + {:error, :badarg} 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 - |> :unicode.characters_to_nfd_binary() - |> String.replace(~r/[^a-z0-9._]/, "") - |> String.trim(".") - |> String.trim("_") + [trigger | opts] = + trigger + |> String.strip() + |> String.split(" ", parts: 2) + + trigger = + trigger + |> String.downcase() + |> :unicode.characters_to_nfd_binary() + |> String.replace(~r/[^a-z0-9._]/, "") + |> String.trim(".") + |> String.trim("_") {trigger, opts} end def format_line(line, prefix, msg) do prefix = unless(prefix, do: "", else: prefix) - prefix <> line + + (prefix <> line) |> String.split("\\\\") - |> Enum.map(fn(line) -> + |> Enum.map(fn line -> String.split(line, "\\\\\\\\") end) |> List.flatten() - |> Enum.map(fn(line) -> + |> Enum.map(fn line -> String.trim(line) |> Tmpl.render(msg) end) @@ -521,10 +596,13 @@ defmodule Nola.Plugins.Txt do defp can_write?(%{rw: rw?, locks: locks}, msg = %{channel: nil, sender: sender}, trigger) do admin? = Nola.Irc.admin?(sender) - locked? = case :dets.lookup(locks, trigger) do - [{trigger}] -> true - _ -> false - end + + locked? = + case :dets.lookup(locks, trigger) do + [{trigger}] -> true + _ -> false + end + unlocked? = if rw? == false, do: false, else: !locked? can? = unlocked? || admin? @@ -533,16 +611,24 @@ defmodule Nola.Plugins.Txt do reason = if !rw?, do: "lecture seule", else: "fichier vérrouillé" msg.replyfun.("#{sender.nick}: permission refusée (#{reason})") end + can? end - defp can_write?(state = %__MODULE__{rw: rw?, locks: locks}, msg = %{channel: channel, sender: sender}, trigger) do + defp can_write?( + state = %__MODULE__{rw: rw?, locks: locks}, + msg = %{channel: channel, sender: sender}, + trigger + ) do admin? = Nola.Irc.admin?(sender) operator? = Nola.UserTrack.operator?(msg.network, channel, sender.nick) - locked? = case :dets.lookup(locks, trigger) do - [{trigger}] -> true - _ -> false - end + + locked? = + case :dets.lookup(locks, trigger) do + [{trigger}] -> true + _ -> false + end + unlocked? = if rw? == false, do: false, else: !locked? can? = admin? || operator? || unlocked? @@ -550,7 +636,7 @@ defmodule Nola.Plugins.Txt do reason = if !rw?, do: "lecture seule", else: "fichier vérrouillé" msg.replyfun.("#{sender.nick}: permission refusée (#{reason})") end + can? end - end |