summaryrefslogtreecommitdiff
path: root/lib/plugins/txt.ex
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugins/txt.ex')
-rw-r--r--lib/plugins/txt.ex410
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