diff options
author | href <href@random.sh> | 2018-02-17 21:21:42 +0100 |
---|---|---|
committer | href <href@random.sh> | 2018-02-17 21:21:42 +0100 |
commit | 50c6a09ff64cb081b27a0c30790b86873449d172 (patch) | |
tree | ba1bebfc7e367f169276692e0ac02709d62d6516 /lib/lsg_irc | |
parent | txt: fix against malicious filenames (aka 'fuck you shiv') (diff) |
:)
Diffstat (limited to 'lib/lsg_irc')
-rw-r--r-- | lib/lsg_irc/admin_handler.ex | 32 | ||||
-rw-r--r-- | lib/lsg_irc/base_handler.ex | 37 | ||||
-rw-r--r-- | lib/lsg_irc/calc_handler.ex | 38 | ||||
-rw-r--r-- | lib/lsg_irc/connection_handler.ex | 17 | ||||
-rw-r--r-- | lib/lsg_irc/dice_handler.ex | 10 | ||||
-rw-r--r-- | lib/lsg_irc/handler.ex | 24 | ||||
-rw-r--r-- | lib/lsg_irc/kick_roulette_handler.ex | 29 | ||||
-rw-r--r-- | lib/lsg_irc/last_fm_handler.ex | 15 | ||||
-rw-r--r-- | lib/lsg_irc/np_handler.ex | 4 | ||||
-rw-r--r-- | lib/lsg_irc/txt_handler.ex (renamed from lib/lsg_irc/text_trigger_handler.ex) | 175 | ||||
-rw-r--r-- | lib/lsg_irc/user_track.ex | 150 | ||||
-rw-r--r-- | lib/lsg_irc/user_track_handler.ex | 93 | ||||
-rw-r--r-- | lib/lsg_irc/youtube_handler.ex | 4 |
13 files changed, 562 insertions, 66 deletions
diff --git a/lib/lsg_irc/admin_handler.ex b/lib/lsg_irc/admin_handler.ex new file mode 100644 index 0000000..27d35c3 --- /dev/null +++ b/lib/lsg_irc/admin_handler.ex @@ -0,0 +1,32 @@ +defmodule LSG.IRC.AdminHandler do + @moduledoc """ + # admin + + !op + op; requiert admin + """ + + def irc_doc, do: nil + + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, client} + end + + def handle_info({:received, "!op", sender, chan}, client) do + if LSG.IRC.admin?(sender) do + ExIRC.Client.mode(client, chan, "+o", sender.nick) + end + {:noreply, client} + end + + def handle_info(msg, client) do + IO.inspect(msg) + {:noreply, client} + end + +end diff --git a/lib/lsg_irc/base_handler.ex b/lib/lsg_irc/base_handler.ex new file mode 100644 index 0000000..9145936 --- /dev/null +++ b/lib/lsg_irc/base_handler.ex @@ -0,0 +1,37 @@ +defmodule LSG.IRC.BaseHandler do + + use LSG.IRC.Handler + + def irc_doc, do: nil + + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, client} + end + + def handle_info({:received, "!help", %ExIRC.SenderInfo{nick: nick}, chan}, client) do + url = LSGWeb.Router.Helpers.irc_url(LSGWeb.Endpoint, :index) + ExIRC.Client.msg(client, :privmsg, chan, "#{nick}: #{url}") + {:noreply, client} + end + + def handle_info({:received, "version", %ExIRC.SenderInfo{nick: nick}}, client) do + {:ok, vsn} = :application.get_key(:lsg, :vsn) + ver = List.to_string(vsn) + url = LSGWeb.Router.Helpers.irc_url(LSGWeb.Endpoint, :index) + version = "v#{ver} ; #{url} ; source: https://git.yt/115ans/sys" + ExIRC.Client.msg(client, :privmsg, nick, version) + {:noreply, client} + end + + def handle_info(msg, client) do + IO.inspect(msg) + {:noreply, client} + end + +end + diff --git a/lib/lsg_irc/calc_handler.ex b/lib/lsg_irc/calc_handler.ex new file mode 100644 index 0000000..0f74ac9 --- /dev/null +++ b/lib/lsg_irc/calc_handler.ex @@ -0,0 +1,38 @@ +defmodule LSG.IRC.CalcHandler do + @moduledoc """ + # calc + + * **!calc `<expression>`**: évalue l'expression mathématique `<expression>`. + """ + + 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, client} + end + + def handle_info({:received, "!calc "<>expr, %ExIRC.SenderInfo{nick: nick}, chan}, client) do + IO.inspect "HAZ CALC " <>inspect(expr) + result = try do + case Abacus.eval(expr) do + {:ok, result} -> result + error -> inspect(error) + end + rescue + error -> "#{error.message}" + end + ExIRC.Client.msg(client, :privmsg, chan, "#{nick}: #{expr} = #{result}") + {:noreply, client} + end + + def handle_info(msg, client) do + {:noreply, client} + end + +end + diff --git a/lib/lsg_irc/connection_handler.ex b/lib/lsg_irc/connection_handler.ex index 337fe00..8d07e58 100644 --- a/lib/lsg_irc/connection_handler.ex +++ b/lib/lsg_irc/connection_handler.ex @@ -1,19 +1,20 @@ defmodule LSG.IRC.ConnectionHandler do defmodule State do - defstruct host: "irc.quakenet.org", - port: 6667, - pass: "", - nick: "`115ans", - user: "115ans", - name: "https://sys.115ans.net/irc", - client: nil + defstruct [:host, :port, :pass, :nick, :name, :user, :client] end def start_link(client) do - GenServer.start_link(__MODULE__, [%State{client: client}]) + irc = Application.get_env(:lsg, :irc)[:irc] + host = irc[:host] + port = irc[:port] + nick = irc[:nick] + user = irc[:user] + name = irc[:name] + GenServer.start_link(__MODULE__, [%State{client: client, host: host, port: port, nick: nick, user: user, name: name}]) end def init([state]) do + IO.puts inspect(state) ExIRC.Client.add_handler state.client, self ExIRC.Client.connect! state.client, state.host, state.port {:ok, state} diff --git a/lib/lsg_irc/dice_handler.ex b/lib/lsg_irc/dice_handler.ex index b865100..b07b59b 100644 --- a/lib/lsg_irc/dice_handler.ex +++ b/lib/lsg_irc/dice_handler.ex @@ -4,14 +4,14 @@ defmodule LSG.IRC.DiceHandler do @moduledoc """ # dice - !dice [6 | faces] [1 | rolls] - roll X times a dice of X faces. + * **!dice `[1 | lancés]` `[6 | faces]`**: lance une ou plusieurs fois un dé de 6 ou autre faces """ @default_faces 6 @default_rolls 1 @max_rolls 50 + def short_irc_doc, do: "!dice (jeter un dé)" defstruct client: nil, dets: nil def irc_doc, do: @moduledoc @@ -31,9 +31,9 @@ defmodule LSG.IRC.DiceHandler do 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"} + {rolls, faces} = case String.split(params, " ", parts: 2) do + [faces, rolls] -> {rolls, faces} + [rolls] -> {rolls, @default_faces} end to_integer = fn(string, default) -> diff --git a/lib/lsg_irc/handler.ex b/lib/lsg_irc/handler.ex new file mode 100644 index 0000000..19c0945 --- /dev/null +++ b/lib/lsg_irc/handler.ex @@ -0,0 +1,24 @@ +defmodule LSG.IRC.Handler do + defmacro __using__(_) do + quote do + alias LSG.IRC + alias LSG.IRC.UserTrack + alias ExIRC.Client + require LSG.IRC.Handler + import LSG.IRC.Handler + end + end + + def privmsg(client, {nil, %ExIRC.SenderInfo{nick: nick}}, message) do + privmsg(client, nick, message) + end + + def privmsg(client, {channel, _}, message) do + privmsg(client, channel, message) + end + + def privmsg(client, target, message) when is_binary(target) do + ExIRC.Client.msg(client, :privmsg, target, message) + end + +end diff --git a/lib/lsg_irc/kick_roulette_handler.ex b/lib/lsg_irc/kick_roulette_handler.ex new file mode 100644 index 0000000..7bfa90b --- /dev/null +++ b/lib/lsg_irc/kick_roulette_handler.ex @@ -0,0 +1,29 @@ +defmodule LSG.IRC.KickRouletteHandler do + @moduledoc """ + # kick roulette + + * **!kick** (à peu près une chance sur 5) + """ + + 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, client} + end + + def handle_info({:received, "!kick", sender, chan}, client) do + if 5 == :crypto.rand_uniform(1, 6) do + ExIRC.Client.kick(client, chan, sender.nick, "perdu") + end + {:noreply, client} + end + + def handle_info(msg, client) do + {:noreply, client} + end + +end diff --git a/lib/lsg_irc/last_fm_handler.ex b/lib/lsg_irc/last_fm_handler.ex index ad6dae9..6ecdab9 100644 --- a/lib/lsg_irc/last_fm_handler.ex +++ b/lib/lsg_irc/last_fm_handler.ex @@ -4,14 +4,9 @@ defmodule LSG.IRC.LastFmHandler do @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. + * **!lastfm `[nick|username]`** + * **!lastfmall** + * **+lastfm `<username last.fm>`, -lastfm** """ defstruct client: nil, dets: nil @@ -24,7 +19,7 @@ defmodule LSG.IRC.LastFmHandler do def init([client]) do ExIRC.Client.add_handler(client, self()) - dets_filename = Application.get_env(:lsg, __MODULE__)[:dets_path] + dets_filename = (LSG.data_path() <> "/" <> "lastfm.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, []) {:ok, %__MODULE__{client: client, dets: dets}} end @@ -128,7 +123,7 @@ defmodule LSG.IRC.LastFmHandler do end defp lookup_nick(username, state) do - case :dets.match(state.dets, {:'$1', username}) do + case :dets.match(state.dets, {:'$1', String.downcase(username)}) do [[match]] -> match [[match] | _many] -> match _ -> username diff --git a/lib/lsg_irc/np_handler.ex b/lib/lsg_irc/np_handler.ex index b198cbc..5f724d4 100644 --- a/lib/lsg_irc/np_handler.ex +++ b/lib/lsg_irc/np_handler.ex @@ -2,10 +2,10 @@ defmodule LSG.IRC.NpHandler do @moduledoc """ # np - !np - now playing on 115ans.net + * **!np** chanson/émission actuellement sur le stream de 115ans.net """ + def short_irc_doc, do: "!np (en ce moment sur 115ans)" def irc_doc, do: @moduledoc def start_link(client) do GenServer.start_link(__MODULE__, [client]) diff --git a/lib/lsg_irc/text_trigger_handler.ex b/lib/lsg_irc/txt_handler.ex index e8331f5..f946092 100644 --- a/lib/lsg_irc/text_trigger_handler.ex +++ b/lib/lsg_irc/txt_handler.ex @@ -1,34 +1,38 @@ defmodule LSG.IRC.TxtHandler do + alias LSG.IRC.UserTrack + @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> + * **!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: %{} + defstruct client: nil, triggers: %{}, rw: true, locks: nil def init([client]) do - state = %__MODULE__{client: client} + 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 @@ -37,16 +41,76 @@ defmodule LSG.IRC.TxtHandler 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}) -> - "#{key}: #{to_string(Enum.count(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" - (detail<>total) + + ro = if !state.rw, do: " (lecture seule activée)", else: "" + + (detail<>total<>ro) |> String.codepoints |> Enum.chunk_every(440) |> Enum.map(&Enum.join/1) @@ -63,29 +127,37 @@ defmodule LSG.IRC.TxtHandler do {:noreply, state} end - def handle_info({:received, "+txt "<>trigger, _, chan}, state) do - {trigger, _} = clean_trigger(trigger) - if create_file(trigger) do + 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} + _ -> {: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})") + 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} + _ -> {:noreply, state} end end - def handle_info({:received, "-"<>trigger_and_id, _, chan}, state) do + 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) @@ -102,18 +174,19 @@ defmodule LSG.IRC.TxtHandler do 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") + Path.wildcard(directory() <> "/*.txt") |> Enum.reduce(%{}, fn(path, m) -> file = Path.basename(path) [key, "txt"] = String.split(file, ".", parts: 2) - data = dir <> file + data = directory() <> file |> File.read! |> String.split("\n") |> Enum.reject(fn(line) -> @@ -135,7 +208,7 @@ defmodule LSG.IRC.TxtHandler do |> Enum.sort_by(fn({_, idx}) -> idx end) |> Enum.map(fn({text, _}) -> text end) |> Enum.join("\n") - File.write!(env()[:directory] <> "/" <> trigger <> ".txt", data<>"\n", []) + File.write!(directory() <> "/" <> trigger <> ".txt", data<>"\n", []) end defp get_random(triggers, trigger, []) do @@ -175,8 +248,8 @@ defmodule LSG.IRC.TxtHandler do end defp create_file(name) do - File.touch!(env()[:directory] <> "/" <> name <> ".txt") - true + File.touch!(directory() <> "/" <> name <> ".txt") + :ok end defp add(triggers, trigger_and_content) do @@ -184,10 +257,13 @@ defmodule LSG.IRC.TxtHandler 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 + File.write!(directory() <> "/" <> trigger <> ".txt", content<>"\n", [:append]) + idx = Enum.count(triggers[trigger])+1 + {:ok, idx} + else + :error end - _ -> false + _ -> :error end end @@ -214,6 +290,29 @@ defmodule LSG.IRC.TxtHandler do {trigger, opts} end - defp env(), do: Application.get_env(:lsg, __MODULE__) + 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 diff --git a/lib/lsg_irc/user_track.ex b/lib/lsg_irc/user_track.ex new file mode 100644 index 0000000..b67b9f6 --- /dev/null +++ b/lib/lsg_irc/user_track.ex @@ -0,0 +1,150 @@ +defmodule LSG.IRC.UserTrack do + @moduledoc """ + User Track DB & Utilities + """ + + @ets LSG.IRC.UserTrack.Storage + # {uuid, nick, nicks, privilege_map} + # Privilege map: + # %{"#channel" => [:operator, :voice] + defmodule Storage do + + def delete(id) do + op(fn(ets) -> :ets.delete(ets, id) end) + end + + def insert(tuple) do + op(fn(ets) -> :ets.insert(ets, tuple) end) + end + + def op(fun) do + GenServer.call(__MODULE__, {:op, fun}) + end + + def start_link do + GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + end + + def init([]) do + ets = :ets.new(__MODULE__, [:set, :named_table, :protected, {:read_concurrency, true}]) + {:ok, ets} + end + + def handle_call({:op, fun}, _from, ets) do + returned = try do + {:ok, fun.(ets)} + rescue + rescued -> {:error, rescued} + catch + rescued -> {:error, rescued} + end + {:reply, returned, ets} + end + end + + defmodule Id, do: use EntropyString + + defmodule User do + defstruct [:id, :nick, :nicks, :username, :host, :realname, :privileges] + + def to_tuple(u = %__MODULE__{}) do + {u.id || LSG.IRC.UserTrack.Id.large_id, u.nick, u.nicks || [], u.username, u.host, u.realname, u.privileges} + end + + def from_tuple({id, nick, nicks, username, host, realname, privs}) do + %__MODULE__{id: id, nick: nick, nicks: nicks, username: username, realname: realname, privileges: privs} + end + end + + def find_by_nick(nick) do + case :ets.match(@ets, {:'$1', nick, :_, :_, :_, :_, :_}) do + [[id]] -> lookup(id) + _ -> nil + end + end + + def to_list, do: :ets.tab2list(@ets) + + def lookup(id) do + case :ets.lookup(@ets, id) do + [] -> nil + [tuple] -> User.from_tuple(tuple) + end + end + + def operator?(channel, nick) do + if user = find_by_nick(nick) do + privs = Map.get(user.privileges, channel, []) + Enum.member?(privs, :admin) || Enum.member?(privs, :operator) + else + false + end + end + + def joined(c, s), do: joined(c,s,[]) + + def joined(channel, sender=%{nick: nick, user: uname, host: host}, privileges) do + privileges = if LSG.IRC.admin?(sender) do + privileges ++ [:admin] + else privileges end + user = if user = find_by_nick(nick) do + %User{user | username: uname, host: host, privileges: Map.put(user.privileges || %{}, channel, privileges)} + else + %User{nick: nick, username: uname, host: host, privileges: %{channel => privileges}} + end + + Storage.op(fn(ets) -> + :ets.insert(ets, User.to_tuple(user)) + end) + end + + def joined(channel, nick, privileges) do + user = if user = find_by_nick(nick) do + %User{user | privileges: Map.put(user.privileges, channel, privileges)} + else + %User{nick: nick, privileges: %{channel => privileges}} + end + + Storage.op(fn(ets) -> + :ets.insert(ets, User.to_tuple(user)) + end) + end + + def renamed(old_nick, new_nick) do + if user = find_by_nick(old_nick) do + user = %User{user | nick: new_nick, nicks: [old_nick|user.nicks]} + Storage.insert(User.to_tuple(user)) + end + end + + def change_privileges(channel, nick, {add, remove}) do + if user = find_by_nick(nick) do + privs = Map.get(user.privileges, channel) + + privs = Enum.reduce(add, privs, fn(priv, acc) -> [priv|acc] end) + privs = Enum.reduce(remove, privs, fn(priv, acc) -> List.delete(acc, priv) end) + + user = %User{user | privileges: Map.put(user.privileges, channel, privs)} + Storage.insert(User.to_tuple(user)) + end + end + + def parted(channel, nick) do + if user = find_by_nick(nick) do + privs = Map.delete(user.privileges, channel) + if Enum.count(privs) > 0 do + user = %User{user | privileges: privs} + Storage.insert(User.to_tuple(user)) + else + Storage.delete(user.id) + end + end + end + + def quitted(sender) do + if user = find_by_nick(sender.nick) do + Storage.delete(user.id) + end + end + +end diff --git a/lib/lsg_irc/user_track_handler.ex b/lib/lsg_irc/user_track_handler.ex new file mode 100644 index 0000000..d167af5 --- /dev/null +++ b/lib/lsg_irc/user_track_handler.ex @@ -0,0 +1,93 @@ +defmodule LSG.IRC.UserTrackHandler do + @moduledoc """ + # User Track Handler + + This handlers keeps track of users presence and privileges. + + Planned API: + + UserTrackHandler.operator?(%ExIRC.Sender{nick: "href", …}, "#channel") :: boolean + + """ + + def irc_doc, do: nil + + def start_link(client) do + GenServer.start_link(__MODULE__, [client]) + end + + defstruct client: nil, ets: nil + + def init([client]) do + ExIRC.Client.add_handler client, self + {:ok, %__MODULE__{client: client}} + end + + def handle_info({:joined, channel}, state) do + ExIRC.Client.who(state.client, channel) + {:noreply, state} + end + + def handle_info({:who, channel, whos}, state) do + Enum.map(whos, fn(who = %ExIRC.Who{nick: nick, operator?: operator}) -> + priv = if operator, do: [:operator], else: [] + LSG.IRC.UserTrack.joined(channel, who, priv) + end) + {:noreply, state} + end + + def handle_info({:quit, _reason, sender}, state) do + LSG.IRC.UserTrack.quitted(sender) + {:noreply, state} + end + + def handle_info({:joined, channel, sender}, state) do + LSG.IRC.UserTrack.joined(channel, sender, []) + {:noreply, state} + end + + def handle_info({:kicked, nick, _by, channel, _reason}, state) do + parted(channel, nick) + {:noreply, state} + end + + def handle_info({:parted, channel, %ExIRC.SenderInfo{nick: nick}}, state) do + parted(channel, nick) + {:noreply, state} + end + + def handle_info({:mode, [channel, mode, nick]}, state) do + mode(channel, nick, mode) + {:noreply, state} + end + + def handle_info({:nick_changed, old_nick, new_nick}, state) do + rename(old_nick, new_nick) + {:noreply, state} + end + + def handle_info(msg, state) do + {:noreply, state} + end + + defp parted(channel, nick) do + LSG.IRC.UserTrack.parted(channel, nick) + :ok + end + + defp mode(channel, nick, "+o") do + LSG.IRC.UserTrack.change_privileges(channel, nick, {[:operator], []}) + :ok + end + + defp mode(channel, nick, "-o") do + LSG.IRC.UserTrack.change_privileges(channel, nick, {[], [:operator]}) + :ok + end + + defp rename(old, new) do + LSG.IRC.UserTrack.renamed(old, new) + :ok + end + +end diff --git a/lib/lsg_irc/youtube_handler.ex b/lib/lsg_irc/youtube_handler.ex index 769f220..e7eadfc 100644 --- a/lib/lsg_irc/youtube_handler.ex +++ b/lib/lsg_irc/youtube_handler.ex @@ -4,9 +4,7 @@ defmodule LSG.IRC.YouTubeHandler do @moduledoc """ # youtube - !youtube <recherche> - !yt <recherche> - cherche sur youtube (seulement le premier résultat). + * **!yt `<recherche>`**, !youtube `<recherche>`: retourne le premier résultat de la `<recherche>` YouTube """ defstruct client: nil, dets: nil |