summaryrefslogtreecommitdiff
path: root/lib/lsg
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
parentsse / embedded player (diff)
update
Diffstat (limited to '')
-rw-r--r--lib/lsg/application.ex11
-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
-rw-r--r--lib/lsg_web/controllers/irc_controller.ex51
-rw-r--r--lib/lsg_web/controllers/page_controller.ex11
-rw-r--r--lib/lsg_web/router.ex9
-rw-r--r--lib/lsg_web/templates/irc/index.html.eex29
-rw-r--r--lib/lsg_web/templates/irc/txt.html.eex19
-rw-r--r--lib/lsg_web/templates/irc/txts.html.eex8
-rw-r--r--lib/lsg_web/templates/layout/app.html.eex23
-rw-r--r--lib/lsg_web/templates/page/api.html.eex35
-rw-r--r--lib/lsg_web/templates/page/index.html.eex37
-rw-r--r--lib/lsg_web/templates/page/irc.html.eex19
-rw-r--r--lib/lsg_web/views/irc_view.ex3
20 files changed, 712 insertions, 64 deletions
diff --git a/lib/lsg/application.ex b/lib/lsg/application.ex
index 73ec04d..0df3c87 100644
--- a/lib/lsg/application.ex
+++ b/lib/lsg/application.ex
@@ -18,9 +18,7 @@ defmodule LSG.Application do
worker(LSG.Icecast, []),
worker(LSG.IRC.ConnectionHandler, [irc_client]),
worker(LSG.IRC.LoginHandler, [irc_client]),
- worker(LSG.IRC.BroadcastHandler, [irc_client]),
- worker(LSG.IRC.NpHandler, [irc_client]),
- ]
+ ] ++ irc_handlers(irc_client)
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
@@ -34,4 +32,11 @@ defmodule LSG.Application do
LSGWeb.Endpoint.config_change(changed, removed)
:ok
end
+
+ defp irc_handlers(irc_client) do
+ import Supervisor.Spec
+ for handler <- Application.get_env(:lsg, :irc)[:handlers] do
+ worker(handler, [irc_client])
+ end
+ end
end
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
diff --git a/lib/lsg_web/controllers/irc_controller.ex b/lib/lsg_web/controllers/irc_controller.ex
new file mode 100644
index 0000000..a5a68f7
--- /dev/null
+++ b/lib/lsg_web/controllers/irc_controller.ex
@@ -0,0 +1,51 @@
+defmodule LSGWeb.IrcController do
+ use LSGWeb, :controller
+
+ def index(conn, _) do
+ commands = for mod <- Application.get_env(:lsg, :irc)[:handlers] do
+ mod.irc_doc()
+ end
+ render conn, "index.html", commands: commands
+ end
+
+ def txt(conn, %{"name" => name}), do: do_txt(conn, name)
+ def txt(conn, _), do: do_txt(conn, nil)
+
+ defp do_txt(conn, nil) do
+ render conn, "txts.html", data: data()
+ end
+
+ defp do_txt(conn, txt) do
+ data = data()
+ if Map.has_key?(data, txt) do
+ render(conn, "txt.html", name: txt, data: data[txt])
+ else
+ conn
+ |> put_status(404)
+ end
+ end
+
+ defp data() do
+ dir = Application.get_env(:lsg, LSG.IRC.TxtHandler)[:directory]
+ Path.wildcard(dir <> "/*.txt")
+ |> Enum.reduce(%{}, fn(path, m) ->
+ path = String.split(path, "/")
+ file = List.last(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)
+ Map.put(m, key, data)
+ end)
+ |> Enum.sort
+ |> Enum.into(Map.new)
+ end
+
+end
diff --git a/lib/lsg_web/controllers/page_controller.ex b/lib/lsg_web/controllers/page_controller.ex
index 3d4e444..b356b9c 100644
--- a/lib/lsg_web/controllers/page_controller.ex
+++ b/lib/lsg_web/controllers/page_controller.ex
@@ -5,6 +5,17 @@ defmodule LSGWeb.PageController do
render conn, "index.html"
end
+ def api(conn, _params) do
+ render conn, "api.html"
+ end
+
+ def irc(conn, _) do
+ bot_helps = for mod <- Application.get_env(:lsg, :irc)[:handlers] do
+ mod.irc_doc()
+ end
+ render conn, "irc.html", bot_helps: bot_helps
+ end
+
def icecast(conn, _params) do
conn
|> json(LSG.IcecastAgent.get)
diff --git a/lib/lsg_web/router.ex b/lib/lsg_web/router.ex
index db0e5cd..9373e85 100644
--- a/lib/lsg_web/router.ex
+++ b/lib/lsg_web/router.ex
@@ -14,9 +14,14 @@ defmodule LSGWeb.Router do
end
scope "/", LSGWeb do
- pipe_through :browser # Use the default browser stack
-
+ pipe_through :browser
+ get "/", PageController, :index
get "/embed/widget", PageController, :widget
+ get "/api", PageController, :api
+
+ get "/irc", IrcController, :index
+ get "/irc/txt", IrcController, :txt
+ get "/irc/txt/:name", IrcController, :txt
end
scope "/api", LSGWeb do
diff --git a/lib/lsg_web/templates/irc/index.html.eex b/lib/lsg_web/templates/irc/index.html.eex
new file mode 100644
index 0000000..91873e6
--- /dev/null
+++ b/lib/lsg_web/templates/irc/index.html.eex
@@ -0,0 +1,29 @@
+<h1>bot `115ans</h1>
+
+<p>
+Si vous cherchez l'IRC c'est <a href="https://115ans.net/irc/">par là</a>.
+<br />
+<a href="/irc/stats/">Statistiques</a>.
+</p>
+
+
+<style type="text/css">
+.help-entry h1 {
+ font-size: 18px;
+}
+.help-entry h2 {
+ font-size: 16px;
+}
+</style>
+
+<div class="irchelps">
+ <%= for help <- @commands do %>
+ <div class="help-entry"><%= help |> Earmark.as_html! |> raw() %></div>
+ <% end %>
+</div>
+
+<p>
+<small>
+ source: <a href="https://git.yt/115ans/sys">git.yt/115ans/sys</a>
+</small>
+</p>
diff --git a/lib/lsg_web/templates/irc/txt.html.eex b/lib/lsg_web/templates/irc/txt.html.eex
new file mode 100644
index 0000000..4ffde50
--- /dev/null
+++ b/lib/lsg_web/templates/irc/txt.html.eex
@@ -0,0 +1,19 @@
+<style type="text/css">
+h1 small {
+ font-size: 14px;
+}
+ol li {
+ margin-bottom: 5px
+}
+</style>
+
+<h1>
+ <small><a href="/irc/txt">irc.txt</a>:</small><br/>
+ <%= @name %>.txt</h1>
+
+<ol>
+ <%= for {txt, id} <- Enum.with_index(@data) do %>
+ <li id="<%= @name %>-<%= id %>"><%= txt %></li>
+ <% end %>
+</ol>
+
diff --git a/lib/lsg_web/templates/irc/txts.html.eex b/lib/lsg_web/templates/irc/txts.html.eex
new file mode 100644
index 0000000..7c96ed9
--- /dev/null
+++ b/lib/lsg_web/templates/irc/txts.html.eex
@@ -0,0 +1,8 @@
+<h1>irc.txt</h1>
+
+<ul>
+ <%= for {txt, data} <- @data do %>
+ <li><a href="/irc/txt/<%= txt %>"><%= txt %></a> <i>(<%= Enum.count(data) %>)</i></li>
+ <% end %>
+</ul>
+
diff --git a/lib/lsg_web/templates/layout/app.html.eex b/lib/lsg_web/templates/layout/app.html.eex
index 0d91f12..1c8f900 100644
--- a/lib/lsg_web/templates/layout/app.html.eex
+++ b/lib/lsg_web/templates/layout/app.html.eex
@@ -4,32 +4,15 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="description" content="">
- <meta name="author" content="">
-
- <title>Hello LSG!</title>
- <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
+ <link rel="stylesheet" href="<%= static_path(@conn, "/assets/css/app.css") %>">
</head>
<body>
<div class="container">
- <header class="header">
- <nav role="navigation">
- <ul class="nav nav-pills pull-right">
- <li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
- </ul>
- </nav>
- <span class="logo"></span>
- </header>
-
- <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
- <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
-
<main role="main">
<%= render @view_module, @view_template, assigns %>
</main>
-
- </div> <!-- /container -->
- <script src="<%= static_path(@conn, "/js/app.js") %>"></script>
+ </div>
+ <script src="<%= static_path(@conn, "/assets/js/app.js") %>"></script>
</body>
</html>
diff --git a/lib/lsg_web/templates/page/api.html.eex b/lib/lsg_web/templates/page/api.html.eex
new file mode 100644
index 0000000..03dfa6b
--- /dev/null
+++ b/lib/lsg_web/templates/page/api.html.eex
@@ -0,0 +1,35 @@
+<h1>sys.115ans.net/api</h1>
+
+<h2>Icecast Status</h2>
+
+<h3>GET /api/icecast.json</h3>
+
+<p>
+ Content-Type: <code>application/json</code>
+</p>
+
+<pre><code>
+{
+ "np": String,
+ "genre": null | String,
+ "live": false | true
+}
+</pre></code>
+
+<h3>GET /api/icecast.sse</h3>
+<p>
+ Content-Type: <code>text/event-stream</code>
+</p>
+
+<p>
+ Stream of:
+</p>
+
+ <ul>
+ <li><strong>icecast</strong> events (same format as <code>/api/icecast.json</code>)</li>
+ <li><strong>ping</strong> events (to keep-alive connection. You can safely ignore them)</li>
+ </ul>
+<p>
+ On client connection, the server sends the latest <code>icecast</code> status known.
+</p>
+
diff --git a/lib/lsg_web/templates/page/index.html.eex b/lib/lsg_web/templates/page/index.html.eex
index 0988ea5..98e407c 100644
--- a/lib/lsg_web/templates/page/index.html.eex
+++ b/lib/lsg_web/templates/page/index.html.eex
@@ -1,36 +1 @@
-<div class="jumbotron">
- <h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
- <p class="lead">A productive web framework that<br />does not compromise speed and maintainability.</p>
-</div>
-
-<div class="row marketing">
- <div class="col-lg-6">
- <h4>Resources</h4>
- <ul>
- <li>
- <a href="http://phoenixframework.org/docs/overview">Guides</a>
- </li>
- <li>
- <a href="https://hexdocs.pm/phoenix">Docs</a>
- </li>
- <li>
- <a href="https://github.com/phoenixframework/phoenix">Source</a>
- </li>
- </ul>
- </div>
-
- <div class="col-lg-6">
- <h4>Help</h4>
- <ul>
- <li>
- <a href="http://groups.google.com/group/phoenix-talk">Mailing list</a>
- </li>
- <li>
- <a href="http://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on freenode IRC</a>
- </li>
- <li>
- <a href="https://twitter.com/elixirphoenix">@elixirphoenix</a>
- </li>
- </ul>
- </div>
-</div>
+<p>Vous n'avez rien de mieux à faire ? Non ? Allez sur <a href="https://115ans.net">115ans.net</a>, alors.</p>
diff --git a/lib/lsg_web/templates/page/irc.html.eex b/lib/lsg_web/templates/page/irc.html.eex
new file mode 100644
index 0000000..f6598ee
--- /dev/null
+++ b/lib/lsg_web/templates/page/irc.html.eex
@@ -0,0 +1,19 @@
+<h1>bot `115ans</h1>
+
+<p>Si vous cherchez l'IRC c'est <a href="https://115ans.net/irc/">par là</a>.</p>
+
+<style type="text/css">
+.help-entry h1 {
+ font-size: 18px;
+}
+.help-entry h2 {
+ font-size: 16px;
+}
+</style>
+
+<div class="irchelps">
+ <%= for help <- @bot_helps do %>
+ <div class="help-entry"><%= help |> Earmark.as_html! |> raw() %></div>
+ <% end %>
+</div>
+
diff --git a/lib/lsg_web/views/irc_view.ex b/lib/lsg_web/views/irc_view.ex
new file mode 100644
index 0000000..36a9bc4
--- /dev/null
+++ b/lib/lsg_web/views/irc_view.ex
@@ -0,0 +1,3 @@
+defmodule LSGWeb.IrcView do
+ use LSGWeb, :view
+end