summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md39
-rw-r--r--config/config.exs6
-rw-r--r--lib/irc.ex34
-rw-r--r--lib/irc/connection_handler.ex (renamed from lib/lsg_irc/connection_handler.ex)3
-rw-r--r--lib/irc/login_handler.ex (renamed from lib/lsg_irc/login_handler.ex)2
-rw-r--r--lib/irc/pubsub_handler.ex123
-rw-r--r--lib/irc/user_track.ex (renamed from lib/lsg_irc/user_track.ex)12
-rw-r--r--lib/irc/user_track_handler.ex (renamed from lib/lsg_irc/user_track_handler.ex)16
-rw-r--r--lib/lsg/application.ex2
-rw-r--r--lib/lsg_irc.ex24
-rw-r--r--lib/lsg_irc/admin_handler.ex15
-rw-r--r--lib/lsg_irc/base_handler.ex2
-rw-r--r--lib/lsg_irc/calc_handler.ex38
-rw-r--r--lib/lsg_irc/calc_plugin.ex38
-rw-r--r--lib/lsg_irc/dice_handler.ex1
-rw-r--r--lib/lsg_irc/handler.ex24
-rw-r--r--lib/lsg_irc/kick_roulette_handler.ex2
-rw-r--r--lib/lsg_irc/last_fm_handler.ex8
-rw-r--r--lib/lsg_irc/quatre_cent_vingt_plugin.ex105
-rw-r--r--lib/lsg_irc/txt_handler.ex14
-rw-r--r--lib/lsg_irc/wikipedia_plugin.ex90
-rw-r--r--lib/lsg_irc/youtube_handler.ex2
-rw-r--r--lib/lsg_web/controllers/irc_controller.ex2
-rw-r--r--mix.exs3
-rw-r--r--mix.lock3
26 files changed, 505 insertions, 104 deletions
diff --git a/.gitignore b/.gitignore
index bf4c600..639379c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,3 +17,4 @@ erl_crash.dump
/config/secret.exs
/priv/irc.txt
/priv/*.dets
+/priv/irc.txt*
diff --git a/README.md b/README.md
index 574f910..75131f3 100644
--- a/README.md
+++ b/README.md
@@ -19,3 +19,42 @@ Ready to run in production? Please [check our deployment guides](http://www.phoe
Build a release using `MIX_ENV=prod mix release`.
+# bot
+
+## ideas
+
+* rate limiting
+ only allow x/messages per x/period
+ ignore user for exponential values
+ notice user "Enhance Your Calm!"
+* duck games with mendiant taxe radio
+* markov txt
+ * markov par txt
+ * markov global
+* log to dets
+ * !seen
+ * !grab
+* 420 counter
+* kick roulette
+ * counts ("won" and "lost")
+ * !kickrandom (+ counts)
+ * ?
+* partyline en pv
+ * partyline! pour join la PL
+ * parler en query <=> broadcast a tout les personnes qui ont "partyline!"
+* account/admin auth by password? (hosts are not always stable)
+* reminder
+* admin: /msg bot bsay message => broadcast message to channels
+* admin: /msg bot say #channel message => message to #channel
+
+21:13:35.202 [error] GenServer #PID<0.18734.0> terminating
+** (BadMapError) expected a map, got: nil
+ (elixir) lib/map.ex:424: Map.get(nil, "Content Type:", false)
+ (lsg) lib/lsg/icecast.ex:66: LSG.Icecast.update_stats/1
+ (lsg) lib/lsg/icecast.ex:29: LSG.Icecast.poll/1
+ (lsg) lib/lsg/icecast.ex:16: LSG.Icecast.handle_cast/2
+ (stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
+ (stdlib) gen_server.erl:686: :gen_server.handle_msg/6
+ (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
+Last message: {:"$gen_cast", :poll}
+
diff --git a/config/config.exs b/config/config.exs
index c16b3d5..b306b79 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -39,8 +39,14 @@ config :lsg, :irc,
LSG.IRC.KickRouletteHandler,
LSG.IRC.LastFmHandler,
LSG.IRC.YouTubeHandler,
+ LSG.IRC.WikipediaHandler,
LSG.IRC.DiceHandler,
LSG.IRC.CalcHandler,
+ ],
+ plugins: [
+ LSG.IRC.CalcPlugin,
+ LSG.IRC.WikipediaPlugin,
+ LSG.IRC.QuatreCentVingtPlugin,
]
#admins: [
# # Format is {nick, user, host}. :_ for any value.
diff --git a/lib/irc.ex b/lib/irc.ex
new file mode 100644
index 0000000..6ba819c
--- /dev/null
+++ b/lib/irc.ex
@@ -0,0 +1,34 @@
+defmodule IRC do
+
+ defmodule Message do
+ defstruct [:text,
+ :sender,
+ :channel,
+ :trigger,
+ :replyfun]
+ end
+ defmodule Trigger do
+ defstruct [:type, :trigger, :args]
+ end
+
+ def register(key) do
+ case Registry.register(IRC.PubSub, key, []) do
+ {:ok, _} -> :ok
+ error -> error
+ end
+ end
+
+ def admin?(%Message{sender: sender}), do: admin?(sender)
+
+ def admin?(%{nick: nick, user: user, host: host}) do
+ for {n, u, h} <- Application.get_env(:lsg, :irc, [])[:admins]||[] do
+ admin_part_match?(n, nick) && admin_part_match?(u, user) && admin_part_match?(h, host)
+ end
+ |> Enum.any?
+ end
+
+ defp admin_part_match?(:_, _), do: true
+ defp admin_part_match?(a, a), do: true
+ defp admin_part_match?(_, _), do: false
+
+end
diff --git a/lib/lsg_irc/connection_handler.ex b/lib/irc/connection_handler.ex
index 8d07e58..1c335f2 100644
--- a/lib/lsg_irc/connection_handler.ex
+++ b/lib/irc/connection_handler.ex
@@ -1,4 +1,4 @@
-defmodule LSG.IRC.ConnectionHandler do
+defmodule IRC.ConnectionHandler do
defmodule State do
defstruct [:host, :port, :pass, :nick, :name, :user, :client]
end
@@ -14,7 +14,6 @@ defmodule LSG.IRC.ConnectionHandler do
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/login_handler.ex b/lib/irc/login_handler.ex
index 47ed792..4b02dc2 100644
--- a/lib/lsg_irc/login_handler.ex
+++ b/lib/irc/login_handler.ex
@@ -1,4 +1,4 @@
-defmodule LSG.IRC.LoginHandler do
+defmodule IRC.LoginHandler do
def start_link(client) do
GenServer.start_link(__MODULE__, [client])
end
diff --git a/lib/irc/pubsub_handler.ex b/lib/irc/pubsub_handler.ex
new file mode 100644
index 0000000..3e78d7b
--- /dev/null
+++ b/lib/irc/pubsub_handler.ex
@@ -0,0 +1,123 @@
+defmodule IRC.PubSubHandler do
+ @moduledoc """
+ # IRC PubSub
+
+ Provides a nicer abstraction over ExIRC's handlers.
+
+ ## PubSub topics
+
+ * `message` -- all messages (including triggers)
+ * `message:private` -- all messages without a channel
+ * `message:#CHANNEL` -- all messages within `#CHANNEL`
+ * `triggers` -- all triggers
+ * `trigger:TRIGGER` -- any message with a trigger `TRIGGER`
+
+ ## Replying to %IRC.Message{}
+
+ Each `IRC.Message` comes with a dedicated `replyfun`, to which you only have to pass either:
+
+ """
+ def irc_doc, do: nil
+
+ def start_link(client) do
+ GenServer.start_link(__MODULE__, [client], [name: __MODULE__])
+ end
+
+ def init([client]) do
+ ExIRC.Client.add_handler(client, self())
+ {:ok, client}
+ end
+
+ @triggers %{
+ "!" => :bang,
+ "+" => :plus,
+ "-" => :minus,
+ "?" => :query,
+ "." => :dot,
+ }
+
+ def handle_info({:received, text, sender, chan}, client) do
+ reply_fun = fn(text) -> irc_reply(client, {chan, sender}, text) end
+ message = %IRC.Message{text: text, sender: sender, channel: chan, replyfun: reply_fun, trigger: extract_trigger(text)}
+ publish(message, ["message:#{chan}"])
+ {:noreply, client}
+ end
+
+ def handle_info({:received, text, sender}, client) do
+ reply_fun = fn(text) -> irc_reply(client, {sender.nick, sender}, text) end
+ message = %IRC.Message{text: text, sender: sender, replyfun: reply_fun, trigger: extract_trigger(text)}
+ publish(message, ["message:private"])
+ {:noreply, client}
+ end
+
+ def handle_info(unhandled, client) do
+ IO.puts inspect(unhandled)
+ {:noreply, client}
+ end
+
+ defp publish(pub), do: publish(pub, [])
+
+ defp publish(m = %IRC.Message{trigger: nil}, keys) do
+ dispatch(["message"] ++ keys, {:irc, :text, m})
+ end
+
+ defp publish(m = %IRC.Message{trigger: t = %IRC.Trigger{trigger: trigger}}, keys) do
+ dispatch(["message", "triggers", "trigger:"<>trigger]++keys, {:irc, :trigger, trigger, m})
+ end
+
+ defp dispatch(key, content) when is_binary(key), do: dispatch([key], content)
+ defp dispatch(keys, content) when is_list(keys) do
+ IO.puts "dispatching to #{inspect(keys)} --> #{inspect content}"
+ for key <- keys do
+ spawn(fn() -> Registry.dispatch(IRC.PubSub, key, fn h ->
+ for {pid, _} <- h, do: send(pid, content)
+ end) end)
+ end
+ end
+
+ #
+ # Triggers
+ #
+
+
+ for {trigger, name} <- @triggers do
+ defp extract_trigger(unquote(trigger)<>text) do
+ text = String.strip(text)
+ [trigger | args] = String.split(text, " ")
+ %IRC.Trigger{type: unquote(name), trigger: trigger, args: args}
+ end
+ end
+
+ defp extract_trigger(_), do: nil
+
+ #
+ # IRC Replies
+ #
+
+ # irc_reply(ExIRC.Client pid, {channel or nick, ExIRC.Sender}, binary | replies
+ # replies :: {:kick, reason} | {:kick, nick, reason} | {:mode, mode, nick}
+ defp irc_reply(client, {target, _}, text) when is_binary(text) do
+ ExIRC.Client.msg(client, :privmsg, target, text)
+ end
+
+ defp irc_reply(client, {target, %{nick: nick}}, {:kick, reason}) do
+ ExIRC.Client.kick(client, target, nick, reason)
+ end
+
+ defp irc_reply(client, {target, _}, {:kick, nick, reason}) do
+ ExIRC.Client.kick(client, target, nick, reason)
+ end
+
+ defp irc_reply(client, {target, %{nick: nick}}, {:mode, mode}) do
+ ExIRC.Client.mode(client, target, mode, nick)
+ end
+
+ defp irc_reply(client, target, {:mode, mode, nick}) do
+ ExIRC.Client.mode(client, target, mode, nick)
+ end
+
+ defp irc_reply(client, target, {:channel_mode, mode}) do
+ ExIRC.Client.mode(client, target, mode)
+ end
+
+end
diff --git a/lib/lsg_irc/user_track.ex b/lib/irc/user_track.ex
index b67b9f6..2614d98 100644
--- a/lib/lsg_irc/user_track.ex
+++ b/lib/irc/user_track.ex
@@ -1,9 +1,9 @@
-defmodule LSG.IRC.UserTrack do
+defmodule IRC.UserTrack do
@moduledoc """
User Track DB & Utilities
"""
- @ets LSG.IRC.UserTrack.Storage
+ @ets IRC.UserTrack.Storage
# {uuid, nick, nicks, privilege_map}
# Privilege map:
# %{"#channel" => [:operator, :voice]
@@ -40,6 +40,10 @@ defmodule LSG.IRC.UserTrack do
end
{:reply, returned, ets}
end
+
+ def terminate(_reason, ets) do
+ :ok
+ end
end
defmodule Id, do: use EntropyString
@@ -48,7 +52,7 @@ defmodule LSG.IRC.UserTrack 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}
+ {u.id || 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
@@ -84,7 +88,7 @@ defmodule LSG.IRC.UserTrack do
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 = if IRC.admin?(sender) do
privileges ++ [:admin]
else privileges end
user = if user = find_by_nick(nick) do
diff --git a/lib/lsg_irc/user_track_handler.ex b/lib/irc/user_track_handler.ex
index d167af5..0ae802a 100644
--- a/lib/lsg_irc/user_track_handler.ex
+++ b/lib/irc/user_track_handler.ex
@@ -1,4 +1,4 @@
-defmodule LSG.IRC.UserTrackHandler do
+defmodule IRC.UserTrackHandler do
@moduledoc """
# User Track Handler
@@ -31,18 +31,18 @@ defmodule LSG.IRC.UserTrackHandler do
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)
+ IRC.UserTrack.joined(channel, who, priv)
end)
{:noreply, state}
end
def handle_info({:quit, _reason, sender}, state) do
- LSG.IRC.UserTrack.quitted(sender)
+ IRC.UserTrack.quitted(sender)
{:noreply, state}
end
def handle_info({:joined, channel, sender}, state) do
- LSG.IRC.UserTrack.joined(channel, sender, [])
+ IRC.UserTrack.joined(channel, sender, [])
{:noreply, state}
end
@@ -71,22 +71,22 @@ defmodule LSG.IRC.UserTrackHandler do
end
defp parted(channel, nick) do
- LSG.IRC.UserTrack.parted(channel, nick)
+ IRC.UserTrack.parted(channel, nick)
:ok
end
defp mode(channel, nick, "+o") do
- LSG.IRC.UserTrack.change_privileges(channel, nick, {[:operator], []})
+ IRC.UserTrack.change_privileges(channel, nick, {[:operator], []})
:ok
end
defp mode(channel, nick, "-o") do
- LSG.IRC.UserTrack.change_privileges(channel, nick, {[], [:operator]})
+ IRC.UserTrack.change_privileges(channel, nick, {[], [:operator]})
:ok
end
defp rename(old, new) do
- LSG.IRC.UserTrack.renamed(old, new)
+ IRC.UserTrack.renamed(old, new)
:ok
end
diff --git a/lib/lsg/application.ex b/lib/lsg/application.ex
index 23db221..cc4c120 100644
--- a/lib/lsg/application.ex
+++ b/lib/lsg/application.ex
@@ -12,7 +12,7 @@ defmodule LSG.Application do
supervisor(LSGWeb.Endpoint, []),
# Start your own worker by calling: LSG.Worker.start_link(arg1, arg2, arg3)
# worker(LSG.Worker, [arg1, arg2, arg3]),
- worker(Registry, [[keys: :duplicate, name: LSG.BroadcastRegistry]]),
+ worker(Registry, [[keys: :duplicate, name: LSG.BroadcastRegistry]], id: :registry_broadcast),
worker(LSG.IcecastAgent, []),
worker(LSG.Icecast, []),
] ++ LSG.IRC.application_childs
diff --git a/lib/lsg_irc.ex b/lib/lsg_irc.ex
index b988e04..482cb5d 100644
--- a/lib/lsg_irc.ex
+++ b/lib/lsg_irc.ex
@@ -4,26 +4,22 @@ defmodule LSG.IRC do
{:ok, irc_client} = ExIRC.start_link!
import Supervisor.Spec
[
- worker(LSG.IRC.UserTrack.Storage, []),
- worker(LSG.IRC.ConnectionHandler, [irc_client]),
- worker(LSG.IRC.LoginHandler, [irc_client]),
- worker(LSG.IRC.UserTrackHandler, [irc_client]),
+ worker(Registry, [[keys: :duplicate, name: IRC.PubSub]], id: :registry_irc),
+ worker(IRC.UserTrack.Storage, []),
+ worker(IRC.ConnectionHandler, [irc_client]),
+ worker(IRC.LoginHandler, [irc_client]),
+ worker(IRC.UserTrackHandler, [irc_client]),
+ worker(IRC.PubSubHandler, [irc_client], [name: :irc_pub_sub]),
]
++
for handler <- Application.get_env(:lsg, :irc)[:handlers] do
- worker(handler, [irc_client])
+ worker(handler, [irc_client], [name: handler])
end
- end
-
- def admin?(%{nick: nick, user: user, host: host}) do
- for {n, u, h} <- Application.get_env(:lsg, :irc, [])[:admins]||[] do
- admin_part_match?(n, nick) && admin_part_match?(u, user) && admin_part_match?(h, host)
+ ++
+ for plugin <- Application.get_env(:lsg, :irc)[:plugins] do
+ worker(plugin, [], [name: plugin])
end
- |> Enum.any?
end
- defp admin_part_match?(:_, _), do: true
- defp admin_part_match?(a, a), do: true
- defp admin_part_match?(_, _), do: false
end
diff --git a/lib/lsg_irc/admin_handler.ex b/lib/lsg_irc/admin_handler.ex
index 27d35c3..fab4dbc 100644
--- a/lib/lsg_irc/admin_handler.ex
+++ b/lib/lsg_irc/admin_handler.ex
@@ -14,18 +14,27 @@ defmodule LSG.IRC.AdminHandler do
def init([client]) do
ExIRC.Client.add_handler client, self
+ :ok = IRC.register("op")
{:ok, client}
end
- def handle_info({:received, "!op", sender, chan}, client) do
- if LSG.IRC.admin?(sender) do
+ def handle_info({:irc, :trigger, "op", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang}, sender: sender}}, client) do
+ if IRC.admin?(sender) do
+ m.replyfun.({:mode, "+o"})
+ else
+ m.replyfun.({:kick, "non"})
+ end
+ {:noreply, client}
+ end
+
+ def handle_info({:joined, chan, sender}, client) do
+ if 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
diff --git a/lib/lsg_irc/base_handler.ex b/lib/lsg_irc/base_handler.ex
index 9145936..35b2ade 100644
--- a/lib/lsg_irc/base_handler.ex
+++ b/lib/lsg_irc/base_handler.ex
@@ -1,7 +1,5 @@
defmodule LSG.IRC.BaseHandler do
- use LSG.IRC.Handler
-
def irc_doc, do: nil
def start_link(client) do
diff --git a/lib/lsg_irc/calc_handler.ex b/lib/lsg_irc/calc_handler.ex
deleted file mode 100644
index 0f74ac9..0000000
--- a/lib/lsg_irc/calc_handler.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-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/calc_plugin.ex b/lib/lsg_irc/calc_plugin.ex
new file mode 100644
index 0000000..6e4e30c
--- /dev/null
+++ b/lib/lsg_irc/calc_plugin.ex
@@ -0,0 +1,38 @@
+defmodule LSG.IRC.CalcPlugin do
+ @moduledoc """
+ # calc
+
+ * **!calc `<expression>`**: évalue l'expression mathématique `<expression>`.
+ """
+
+ def irc_doc, do: @moduledoc
+
+ def start_link() do
+ GenServer.start_link(__MODULE__, [])
+ end
+
+ def init(_) do
+ {:ok, _} = Registry.register(IRC.PubSub, "trigger:calc", [])
+ {:ok, nil}
+ end
+
+ def handle_info({:irc, :trigger, "calc", message = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: expr_list}}}, state) do
+ expr = Enum.join(expr_list, " ")
+ result = try do
+ case Abacus.eval(expr) do
+ {:ok, result} -> result
+ error -> inspect(error)
+ end
+ rescue
+ error -> "#{error.message}"
+ end
+ message.replyfun.("#{message.sender.nick}: #{expr} = #{result}")
+ {:noreply, state}
+ end
+
+ def handle_info(msg, state) do
+ {:noreply, state}
+ end
+
+end
+
diff --git a/lib/lsg_irc/dice_handler.ex b/lib/lsg_irc/dice_handler.ex
index b07b59b..7ff7b4d 100644
--- a/lib/lsg_irc/dice_handler.ex
+++ b/lib/lsg_irc/dice_handler.ex
@@ -22,6 +22,7 @@ defmodule LSG.IRC.DiceHandler do
def init([client]) do
ExIRC.Client.add_handler(client, self())
+ {:ok, _} = Registry.register(IRC.PubSub, "dice", [])
{:ok, %__MODULE__{client: client}}
end
diff --git a/lib/lsg_irc/handler.ex b/lib/lsg_irc/handler.ex
deleted file mode 100644
index 19c0945..0000000
--- a/lib/lsg_irc/handler.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-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
index 3591b5e..ece1b95 100644
--- a/lib/lsg_irc/kick_roulette_handler.ex
+++ b/lib/lsg_irc/kick_roulette_handler.ex
@@ -18,7 +18,7 @@ defmodule LSG.IRC.KickRouletteHandler do
def handle_info({:received, "!kick", sender, chan}, client) do
if 5 == :crypto.rand_uniform(1, 6) do
spawn(fn() ->
- :timer.sleep(:crypto.rand_uniform(500, 15_000))
+ :timer.sleep(:crypto.rand_uniform(200, 10_000))
ExIRC.Client.kick(client, chan, sender.nick, "perdu")
end)
end
diff --git a/lib/lsg_irc/last_fm_handler.ex b/lib/lsg_irc/last_fm_handler.ex
index fe767b1..4eef24e 100644
--- a/lib/lsg_irc/last_fm_handler.ex
+++ b/lib/lsg_irc/last_fm_handler.ex
@@ -64,6 +64,14 @@ defmodule LSG.IRC.LastFmHandler do
{:noreply, state}
end
+ def terminate(_reason, state) do
+ if state.dets do
+ :dets.sync(state.dets)
+ :dets.close(state.dets)
+ end
+ :ok
+ 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, String.downcase(nick_or_user)) do
diff --git a/lib/lsg_irc/quatre_cent_vingt_plugin.ex b/lib/lsg_irc/quatre_cent_vingt_plugin.ex
new file mode 100644
index 0000000..579858e
--- /dev/null
+++ b/lib/lsg_irc/quatre_cent_vingt_plugin.ex
@@ -0,0 +1,105 @@
+defmodule LSG.IRC.QuatreCentVingtPlugin do
+ require Logger
+
+ @moduledoc """
+ # 420
+
+ * **!420**: recorde un nouveau 420.
+ * **!420 pseudo**: stats du pseudo.
+ """
+
+ @achievements %{
+ 1 => ["[le premier… il faut bien commencer un jour]"],
+ 10 => ["T'en es seulement à 10 ? ╭∩╮(Ο_Ο)╭∩╮"],
+ 42 => ["Bravo, et est-ce que autant de pétards t'on aidés à trouver la Réponse ? ٩(- ̮̮̃-̃)۶ [42]"],
+ 100 => ["°º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸ 100 °º¤ø,¸¸,ø¤º°`°º¤ø,¸,ø¤°º¤ø,¸¸,ø¤º°`°º¤ø,¸"],
+ 115 => [" ۜ\(סּںסּَ` )/ۜ 115!!"]
+ }
+
+ @emojis [
+ "\\o/",
+ "~o~",
+ "~~o∞~~",
+ "*\\o/*",
+ "**\\o/**",
+ "*ô*",
+ ]
+
+ @coeffs Range.new(1, 10)
+
+ def irc_doc, do: @moduledoc
+
+ def start_link, do: GenServer.start_link(__MODULE__, [])
+
+ def init(_) do
+ for coeff <- @coeffs do
+ {:ok, _} = Registry.register(IRC.PubSub, "trigger:#{420*coeff}", [])
+ end
+ dets_filename = (LSG.data_path() <> "/" <> "420.dets") |> String.to_charlist
+ {:ok, dets} = :dets.open_file(dets_filename, [{:type,:bag}])
+ {:ok, dets}
+ end
+
+ for coeff <- @coeffs do
+ qvc = to_string(420 * coeff)
+ def handle_info({:irc, :trigger, unquote(qvc), m = %IRC.Message{trigger: %IRC.Trigger{args: [], type: :bang}}}, dets) do
+ {count, last} = get_statistics_for_nick(dets, m.sender.nick)
+ count = count + unquote(coeff)
+ text = achievement_text(count)
+ now = DateTime.to_unix(DateTime.utc_now())-1 # this is ugly
+ for i <- Range.new(1, unquote(coeff)) do
+ :ok = :dets.insert(dets, {String.downcase(m.sender.nick), now+i})
+ end
+ last_s = if last do
+ last_s = format_relative_timestamp(last)
+ " (le dernier était #{last_s})"
+ else
+ ""
+ end
+ m.replyfun.("#{m.sender.nick} 420 +#{unquote(coeff)} #{text}#{last_s}")
+ {:noreply, dets}
+ end
+ end
+
+ def handle_info({:irc, :trigger, "420", m = %IRC.Message{trigger: %IRC.Trigger{args: [nick], type: :bang}}}, dets) do
+ text = case get_statistics_for_nick(dets, nick) do
+ {0, _} -> "#{nick} n'a jamais !420 ... honte à lui."
+ {count, last} ->
+ last_s = format_relative_timestamp(last)
+ "#{nick} 420: total #{count}, le dernier #{last_s}"
+ end
+ m.replyfun.(text)
+ {:noreply, dets}
+ end
+
+ defp format_relative_timestamp(timestamp) do
+ alias Timex.Format.DateTime.Formatters
+ alias Timex.Timezone
+ date = timestamp
+ |> DateTime.from_unix!
+ |> Timezone.convert("Europe/Paris")
+
+ {:ok, relative} = Formatters.Relative.relative_to(date, Timex.now("Europe/Paris"), "{relative}", "fr")
+ {:ok, detail} = Formatters.Default.lformat(date, " ({h24}:{m})", "fr")
+
+ relative <> detail
+ end
+
+ defp get_statistics_for_nick(dets, nick) do
+ qvc = :dets.lookup(dets, String.downcase(nick)) |> Enum.sort
+ count = Enum.reduce(qvc, 0, fn(_, acc) -> acc + 1 end)
+ {_, last} = List.last(qvc) || {nil, nil}
+ {count, last}
+ end
+
+ @achievements_keys Map.keys(@achievements)
+ defp achievement_text(count) when count in @achievements_keys do
+ Enum.random(Map.get(@achievements, count))
+ end
+
+ defp achievement_text(count) do
+ emoji = Enum.random(@emojis)
+ "#{emoji} [#{count}]"
+ end
+
+end
diff --git a/lib/lsg_irc/txt_handler.ex b/lib/lsg_irc/txt_handler.ex
index efe2b68..032c11c 100644
--- a/lib/lsg_irc/txt_handler.ex
+++ b/lib/lsg_irc/txt_handler.ex
@@ -1,5 +1,5 @@
defmodule LSG.IRC.TxtHandler do
- alias LSG.IRC.UserTrack
+ alias IRC.UserTrack
require Logger
@moduledoc """
@@ -213,6 +213,14 @@ defmodule LSG.IRC.TxtHandler do
{:noreply, state}
end
+ def terminate(_reason, state) do
+ if state.locks 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")
@@ -331,8 +339,8 @@ defmodule LSG.IRC.TxtHandler do
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)
+ admin? = IRC.admin?(sender)
+ operator? = IRC.UserTrack.operator?(channel, sender.nick)
locked? = case :dets.lookup(locks, trigger) do
[{trigger}] -> true
_ -> false
diff --git a/lib/lsg_irc/wikipedia_plugin.ex b/lib/lsg_irc/wikipedia_plugin.ex
new file mode 100644
index 0000000..4ee95b1
--- /dev/null
+++ b/lib/lsg_irc/wikipedia_plugin.ex
@@ -0,0 +1,90 @@
+defmodule LSG.IRC.WikipediaPlugin do
+ require Logger
+
+ @moduledoc """
+ # wikipédia
+
+ * **!wp `<recherche>`**: retourne le premier résultat de la `<recherche>` Wikipedia
+ * **!wp**: un article Wikipédia au hasard
+ """
+
+ def irc_doc, do: @moduledoc
+
+ def start_link() do
+ GenServer.start_link(__MODULE__, [])
+ end
+
+ def init(_) do
+ {:ok, _} = Registry.register(IRC.PubSub, "trigger:wp", [])
+ {:ok, nil}
+ end
+
+ def handle_info({:irc, :trigger, "wp", message = %IRC.Message{trigger: %IRC.Trigger{args: []}}}, state) do
+ irc_random(message)
+ {:noreply, state}
+ end
+ def handle_info({:irc, :trigger, "wp", message = %IRC.Message{trigger: %IRC.Trigger{args: args}}}, state) do
+ irc_search(Enum.join(args, " "), message)
+ {:noreply, state}
+ end
+
+ def handle_info(info, state) do
+ {:noreply, state}
+ end
+
+ defp irc_search("", message), do: irc_random(message)
+ defp irc_search(query, message) do
+ params = %{
+ "action" => "query",
+ "list" => "search",
+ "srsearch" => String.strip(query),
+ "srlimit" => 1,
+ }
+ case query_wikipedia(params) do
+ {:ok, %{"query" => %{"search" => [item | _]}}} ->
+ title = item["title"]
+ url = "https://fr.wikipedia.org/wiki/" <> String.replace(title, " ", "_")
+ msg = "Wikipédia: #{title} — #{url}"
+ message.replyfun.(msg)
+ _ ->
+ nil
+ end
+ end
+
+ defp irc_random(message) do
+ params = %{
+ "action" => "query",
+ "generator" => "random",
+ "grnnamespace" => 0,
+ "prop" => "info"
+ }
+ case query_wikipedia(params) do
+ {:ok, %{"query" => %{"pages" => map = %{}}}} ->
+ [{_, item}] = Map.to_list(map)
+ title = item["title"]
+ url = "https://fr.wikipedia.org/wiki/" <> String.replace(title, " ", "_")
+ msg = "Wikipédia: #{title} — #{url}"
+ message.replyfun.(msg)
+ _ ->
+ nil
+ end
+ end
+
+ defp query_wikipedia(params) do
+ url = "https://fr.wikipedia.org/w/api.php"
+ params = params
+ |> Map.put("format", "json")
+ |> Map.put("utf8", "")
+
+ 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 "Wikipedia HTTP 400: #{inspect body}"
+ {:error, "http 400"}
+ error ->
+ Logger.error "Wikipedia http error: #{inspect error}"
+ {:error, "http client error"}
+ end
+ end
+
+end
diff --git a/lib/lsg_irc/youtube_handler.ex b/lib/lsg_irc/youtube_handler.ex
index e7eadfc..51584a2 100644
--- a/lib/lsg_irc/youtube_handler.ex
+++ b/lib/lsg_irc/youtube_handler.ex
@@ -7,7 +7,7 @@ defmodule LSG.IRC.YouTubeHandler do
* **!yt `<recherche>`**, !youtube `<recherche>`: retourne le premier résultat de la `<recherche>` YouTube
"""
- defstruct client: nil, dets: nil
+ defstruct client: nil
def irc_doc, do: @moduledoc
diff --git a/lib/lsg_web/controllers/irc_controller.ex b/lib/lsg_web/controllers/irc_controller.ex
index bff5476..af7fff1 100644
--- a/lib/lsg_web/controllers/irc_controller.ex
+++ b/lib/lsg_web/controllers/irc_controller.ex
@@ -3,7 +3,7 @@ defmodule LSGWeb.IrcController do
def index(conn, _) do
doc = LSG.IRC.TxtHandler.irc_doc()
- commands = for mod <- Application.get_env(:lsg, :irc)[:handlers] do
+ commands = for mod <- (Application.get_env(:lsg, :irc)[:plugins] ++ Application.get_env(:lsg, :irc)[:handlers]) do
mod.irc_doc()
end
|> Enum.reject(fn(i) -> i == nil end)
diff --git a/mix.exs b/mix.exs
index df51af7..0189b8f 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule LSG.Mixfile do
def project do
[
app: :lsg,
- version: "0.0.2",
+ version: "0.2.2",
elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env),
compilers: [:phoenix, :gettext] ++ Mix.compilers,
@@ -41,6 +41,7 @@ defmodule LSG.Mixfile do
{:entropy_string, "~> 1.0.0"},
{:abacus, "~> 0.3.3"},
{:ex_chain, github: "eljojo/ex_chain"},
+ {:timex, "~> 3.2"},
]
end
end
diff --git a/mix.lock b/mix.lock
index 051ee62..caea4fd 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,5 +1,6 @@
%{"abacus": {:hex, :abacus, "0.3.3", "f2f11e23073f5e16af36ac425cd9fa9a338695e2f8014684239fa14a9171d5f6", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
+ "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
"distillery": {:hex, :distillery, "1.5.2", "eec18b2d37b55b0bcb670cf2bcf64228ed38ce8b046bb30a9b636a6f5a4c0080", [:mix], [], "hexpm"},
@@ -27,4 +28,6 @@
"poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm"},
"ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
+ "timex": {:hex, :timex, "3.2.1", "639975eac45c4c08c2dbf7fc53033c313ff1f94fad9282af03619a3826493612", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
+ "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}}