summaryrefslogtreecommitdiff
path: root/lib/lsg_irc
diff options
context:
space:
mode:
authorhref <href@random.sh>2018-02-10 21:40:22 +0100
committerhref <href@random.sh>2018-02-10 21:40:22 +0100
commit935a36eecc0faea60236101e11bc9f7cf1872686 (patch)
treeb7b4358dee2eb3fc60681852f62c750ae8c05cb9 /lib/lsg_irc
parentsse / embedded player (diff)
update
Diffstat (limited to '')
-rw-r--r--lib/lsg_irc/broadcast_handler.ex2
-rw-r--r--lib/lsg_irc/connection_handler.ex4
-rw-r--r--lib/lsg_irc/dice_handler.ex72
-rw-r--r--lib/lsg_irc/last_fm_handler.ex138
-rw-r--r--lib/lsg_irc/login_handler.ex2
-rw-r--r--lib/lsg_irc/np_handler.ex8
-rw-r--r--lib/lsg_irc/text_trigger_handler.ex218
-rw-r--r--lib/lsg_irc/youtube_handler.ex77
8 files changed, 518 insertions, 3 deletions
diff --git a/lib/lsg_irc/broadcast_handler.ex b/lib/lsg_irc/broadcast_handler.ex
index 22ffbaf..19c41ea 100644
--- a/lib/lsg_irc/broadcast_handler.ex
+++ b/lib/lsg_irc/broadcast_handler.ex
@@ -1,4 +1,6 @@
defmodule LSG.IRC.BroadcastHandler do
+ def irc_doc, do: ""
+
def start_link(client) do
GenServer.start_link(__MODULE__, [client])
end
diff --git a/lib/lsg_irc/connection_handler.ex b/lib/lsg_irc/connection_handler.ex
index f3bb1d4..337fe00 100644
--- a/lib/lsg_irc/connection_handler.ex
+++ b/lib/lsg_irc/connection_handler.ex
@@ -3,9 +3,9 @@ defmodule LSG.IRC.ConnectionHandler do
defstruct host: "irc.quakenet.org",
port: 6667,
pass: "",
- nick: "bot115ans",
+ nick: "`115ans",
user: "115ans",
- name: "115ans.net",
+ name: "https://sys.115ans.net/irc",
client: nil
end
diff --git a/lib/lsg_irc/dice_handler.ex b/lib/lsg_irc/dice_handler.ex
new file mode 100644
index 0000000..b865100
--- /dev/null
+++ b/lib/lsg_irc/dice_handler.ex
@@ -0,0 +1,72 @@
+defmodule LSG.IRC.DiceHandler do
+ require Logger
+
+ @moduledoc """
+ # dice
+
+ !dice [6 | faces] [1 | rolls]
+ roll X times a dice of X faces.
+ """
+
+ @default_faces 6
+ @default_rolls 1
+ @max_rolls 50
+
+ defstruct client: nil, dets: nil
+
+ def irc_doc, do: @moduledoc
+
+ def start_link(client) do
+ GenServer.start_link(__MODULE__, [client])
+ end
+
+ def init([client]) do
+ ExIRC.Client.add_handler(client, self())
+ {:ok, %__MODULE__{client: client}}
+ end
+
+ def handle_info({:received, "!dice", sender, chan}, state) do
+ roll(state, sender, chan, @default_faces, @default_rolls)
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "!dice "<>params, sender, chan}, state) do
+ {faces, rolls} = case String.split(params, " ", parts: 2) do
+ [faces, rolls] -> {faces, rolls}
+ [faces] -> {faces, "1"}
+ end
+
+ to_integer = fn(string, default) ->
+ case Integer.parse(string) do
+ {int, _} -> int
+ _ -> default
+ end
+ end
+
+ {faces, rolls} = {to_integer.(faces, @default_faces), to_integer.(rolls, @default_rolls)}
+
+ roll(state, sender, chan, faces, rolls)
+
+ {:noreply, state}
+ end
+
+ def handle_info(info, state) do
+ {:noreply, state}
+ end
+
+ defp roll(state, %{nick: nick}, chan, faces, 1) when faces > 0 do
+ random = :crypto.rand_uniform(1, faces+1)
+ ExIRC.Client.msg(state.client, :privmsg, chan, "#{nick} dice: #{random}")
+ end
+ defp roll(state, %{nick: nick}, chan, faces, rolls) when faces > 0 and rolls > 0 and rolls <= @max_rolls do
+ {results, acc} = Enum.map_reduce(Range.new(1, rolls), 0, fn(i, acc) ->
+ random = :crypto.rand_uniform(1, faces+1)
+ {random, acc + random}
+ end)
+ results = Enum.join(results, "; ")
+ ExIRC.Client.msg(state.client, :privmsg, chan, "#{nick} dice [#{acc}] #{results}")
+ end
+
+ defp roll(_, _, _, _, _), do: nil
+
+end
diff --git a/lib/lsg_irc/last_fm_handler.ex b/lib/lsg_irc/last_fm_handler.ex
new file mode 100644
index 0000000..ad6dae9
--- /dev/null
+++ b/lib/lsg_irc/last_fm_handler.ex
@@ -0,0 +1,138 @@
+defmodule LSG.IRC.LastFmHandler do
+ require Logger
+
+ @moduledoc """
+ # last.fm
+
+ !lastfm [nick|username]
+ say what nick/specified username is listening on lastfm; if last.fm username is known (via +lastfm).
+ !lastfmall
+ say what known users (+lastfm) are listening
+ +lastfm <username>
+ links the nick who use the command to <username> last.fm account.
+ -lastfm
+ unlinks the nick's previously set last.fm username.
+ """
+
+ defstruct client: nil, dets: nil
+
+ def irc_doc, do: @moduledoc
+
+ def start_link(client) do
+ GenServer.start_link(__MODULE__, [client])
+ end
+
+ def init([client]) do
+ ExIRC.Client.add_handler(client, self())
+ dets_filename = Application.get_env(:lsg, __MODULE__)[:dets_path]
+ {:ok, dets} = :dets.open_file(dets_filename, [])
+ {:ok, %__MODULE__{client: client, dets: dets}}
+ end
+
+ def handle_info({:received, "+lastfm " <> username, sender, chan}, state) do
+ username = String.strip(username)
+ :ok = :dets.insert(state.dets, {String.downcase(sender.nick), username})
+ ExIRC.Client.msg(state.client, :privmsg, chan, "#{sender.nick}: nom d'utilisateur last.fm configuré: \"#{username}\"")
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "-lastfm", sender, chan}, state) do
+ text = case :dets.lookup(state.dets, sender.nick) do
+ [{_nick, username}] ->
+ :dets.delete(state.dets, sender.nick)
+ "#{sender.nick}: nom d'utilisateur last.fm enlevé"
+ _ -> ""
+ end
+ ExIRC.Client.msg(state.client, :privmsg, chan, text)
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "!lastfm", sender, chan}, state) do
+ irc_now_playing(sender.nick, chan, state)
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "!lastfm " <> nick_or_user, sender, chan}, state) do
+ irc_now_playing(nick_or_user, chan, state)
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "!lastfmall", sender, chan}, state) do
+ foldfun = fn({_nick, user}, acc) -> [user|acc] end
+ usernames = :dets.foldl(foldfun, [], state.dets)
+ |> Enum.uniq
+ for u <- usernames, do: irc_now_playing(u, chan, state)
+ {:noreply, state}
+ end
+
+ def handle_info(info, state) do
+ {:noreply, state}
+ end
+
+ defp irc_now_playing(nick_or_user, chan, state) do
+ nick_or_user = String.strip(nick_or_user)
+ username = case :dets.lookup(state.dets, nick_or_user) do
+ [{^nick_or_user, username}] -> username
+ _ -> nick_or_user
+ end
+
+ case now_playing(username) do
+ {:error, text} when is_binary(text) -> ExIRC.Client.msg(state.client, :privmsg, chan, text)
+ {:ok, map} when is_map(map) ->
+ text = format_now_playing(map)
+ user = lookup_nick(username, state)
+ if user && text, do: ExIRC.Client.msg(state.client, :privmsg, chan, "#{user} #{text}")
+ other ->
+ IO.inspect(other)
+ nil
+ end
+ end
+
+ defp now_playing(user) do
+ api = Application.get_env(:lsg, __MODULE__)[:api_key]
+ url = "http://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&format=json&limit=1" <> "&api_key=" <> api <> "&user="<> user
+ case HTTPoison.get(url) do
+ {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> Jason.decode(body)
+ {:ok, %HTTPoison.Response{status_code: 400}} -> {:error, "last.fm: utilisateur #{user} inexistant"}
+ {:ok, %HTTPoison.Response{status_code: code}} -> {:error, "last.fm: erreur #{to_string(code)}"}
+ error ->
+ Logger.error "Lastfm http error: #{inspect error}"
+ :error
+ end
+ end
+
+ defp format_now_playing(%{"recenttracks" => %{"track" => [track = %{"@attr" => %{"nowplaying" => "true"}}, _old]}}) do
+ format_track(true, track)
+ end
+
+ defp format_now_playing(%{"recenttracks" => %{"track" => [track]}}) do
+ format_track(false, track)
+ end
+
+ defp format_now_playing(%{"error" => err, "message" => message}) do
+ "last.fm error #{err}: #{message}"
+ end
+
+ defp format_now_playing(miss) do
+ nil
+ end
+
+ defp format_track(np, track) do
+ album = if track["album"]["#text"] do
+ " (" <> track["album"]["#text"] <> ")"
+ else
+ ""
+ end
+ action = if np, do: "écoute ", else: "a écouté "
+ action <> track["artist"]["#text"] <> " - " <> track["name"] <> album <> " — " <> track["url"]
+ end
+
+ defp lookup_nick(username, state) do
+ case :dets.match(state.dets, {:'$1', username}) do
+ [[match]] -> match
+ [[match] | _many] -> match
+ _ -> username
+ end
+ end
+
+end
diff --git a/lib/lsg_irc/login_handler.ex b/lib/lsg_irc/login_handler.ex
index b4757b1..f989b40 100644
--- a/lib/lsg_irc/login_handler.ex
+++ b/lib/lsg_irc/login_handler.ex
@@ -5,7 +5,7 @@ defmodule LSG.IRC.LoginHandler do
def init([client]) do
ExIRC.Client.add_handler client, self
- {:ok, {client, ["#lsg"]}}
+ {:ok, {client, ["#lsg", "#lsgtest"]}}
end
def handle_info(:logged_in, state = {client, channels}) do
diff --git a/lib/lsg_irc/np_handler.ex b/lib/lsg_irc/np_handler.ex
index 8bde293..b198cbc 100644
--- a/lib/lsg_irc/np_handler.ex
+++ b/lib/lsg_irc/np_handler.ex
@@ -1,4 +1,12 @@
defmodule LSG.IRC.NpHandler do
+ @moduledoc """
+ # np
+
+ !np
+ now playing on 115ans.net
+ """
+
+ def irc_doc, do: @moduledoc
def start_link(client) do
GenServer.start_link(__MODULE__, [client])
end
diff --git a/lib/lsg_irc/text_trigger_handler.ex b/lib/lsg_irc/text_trigger_handler.ex
new file mode 100644
index 0000000..0e9ef50
--- /dev/null
+++ b/lib/lsg_irc/text_trigger_handler.ex
@@ -0,0 +1,218 @@
+defmodule LSG.IRC.TxtHandler do
+ @moduledoc """
+ # [txt](/irc/txt)
+
+ !txt
+ statistics, file list
+ +txt <file>
+ create new <file>
+
+ !FILE
+ read a random line from the file
+ !FILE <index>
+ read line #<index> from the file
+ !FILE <query>
+ return a phrase from file who matches <query>
+ +FILE <text>
+ add <text> in FILE
+ -FILE <index>
+ remove line in FILE at index <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")
+
+ {trigger, opts}
+ end
+
+ defp env(), do: Application.get_env(:lsg, __MODULE__)
+
+end
diff --git a/lib/lsg_irc/youtube_handler.ex b/lib/lsg_irc/youtube_handler.ex
new file mode 100644
index 0000000..769f220
--- /dev/null
+++ b/lib/lsg_irc/youtube_handler.ex
@@ -0,0 +1,77 @@
+defmodule LSG.IRC.YouTubeHandler do
+ require Logger
+
+ @moduledoc """
+ # youtube
+
+ !youtube <recherche>
+ !yt <recherche>
+ cherche sur youtube (seulement le premier résultat).
+ """
+
+ defstruct client: nil, dets: nil
+
+ def irc_doc, do: @moduledoc
+
+ def start_link(client) do
+ GenServer.start_link(__MODULE__, [client])
+ end
+
+ def init([client]) do
+ ExIRC.Client.add_handler(client, self())
+ {:ok, %__MODULE__{client: client}}
+ end
+
+ def handle_info({:received, "!youtube " <> query, sender, chan}, state) do
+ irc_search(query, chan, state)
+ {:noreply, state}
+ end
+
+ def handle_info({:received, "!yt " <> query, sender, chan}, state) do
+ irc_search(query, chan, state)
+ {:noreply, state}
+ end
+
+ def handle_info(info, state) do
+ {:noreply, state}
+ end
+
+ defp irc_search(query, chan, state) do
+ case search(query) do
+ {:ok, %{"items" => [item | _]}} ->
+ title = get_in(item, ["snippet", "title"])
+ url = "https://youtube.com/watch?v=" <> get_in(item, ["id", "videoId"])
+ msg = "#{title} — #{url}"
+ ExIRC.Client.msg(state.client, :privmsg, chan, msg)
+ {:error, error} ->
+ ExIRC.Client.msg(state.client, :privmsg, chan, "Erreur YouTube: "<>error)
+ _ ->
+ nil
+ end
+ end
+
+ defp search(query) do
+ query = query
+ |> String.strip
+ key = Application.get_env(:lsg, __MODULE__)[:api_key]
+ params = %{
+ "key" => key,
+ "maxResults" => 1,
+ "part" => "snippet",
+ "safeSearch" => "none",
+ "type" => "video",
+ "q" => query,
+ }
+ url = "https://www.googleapis.com/youtube/v3/search"
+ case HTTPoison.get(url, [], params: params) do
+ {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> Jason.decode(body)
+ {:ok, %HTTPoison.Response{status_code: 400, body: body}} ->
+ Logger.error "YouTube HTTP 400: #{inspect body}"
+ {:error, "http 400"}
+ error ->
+ Logger.error "YouTube http error: #{inspect error}"
+ :error
+ end
+ end
+
+end