diff options
author | href <href@random.sh> | 2018-02-10 21:40:22 +0100 |
---|---|---|
committer | href <href@random.sh> | 2018-02-10 21:40:22 +0100 |
commit | 935a36eecc0faea60236101e11bc9f7cf1872686 (patch) | |
tree | b7b4358dee2eb3fc60681852f62c750ae8c05cb9 /lib/lsg_irc | |
parent | sse / embedded player (diff) |
update
Diffstat (limited to '')
-rw-r--r-- | lib/lsg_irc/broadcast_handler.ex | 2 | ||||
-rw-r--r-- | lib/lsg_irc/connection_handler.ex | 4 | ||||
-rw-r--r-- | lib/lsg_irc/dice_handler.ex | 72 | ||||
-rw-r--r-- | lib/lsg_irc/last_fm_handler.ex | 138 | ||||
-rw-r--r-- | lib/lsg_irc/login_handler.ex | 2 | ||||
-rw-r--r-- | lib/lsg_irc/np_handler.ex | 8 | ||||
-rw-r--r-- | lib/lsg_irc/text_trigger_handler.ex | 218 | ||||
-rw-r--r-- | lib/lsg_irc/youtube_handler.ex | 77 |
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 |