diff options
author | href <href@random.sh> | 2021-09-01 10:30:18 +0200 |
---|---|---|
committer | href <href@random.sh> | 2021-09-01 10:30:18 +0200 |
commit | 75687711f35355bc30e4829439384aab28fcac6d (patch) | |
tree | 8f3256f472893c39720a684d390e890a152f7303 /lib | |
parent | link: post_* callbacks; html & pdftitle. (diff) |
Commit all the changes that hasn't been committed + updates.
Diffstat (limited to 'lib')
48 files changed, 1424 insertions, 335 deletions
@@ -8,7 +8,8 @@ defmodule IRC do :sender, :channel, :trigger, - :replyfun] + :replyfun, + :at] end defmodule Trigger do defstruct [:type, :trigger, :args] diff --git a/lib/irc/account.ex b/lib/irc/account.ex index 6f9eb05..0aa8638 100644 --- a/lib/irc/account.ex +++ b/lib/irc/account.ex @@ -19,7 +19,8 @@ defmodule IRC.Account do # FIXME: Ensure uniqueness of name? defstruct [:id, :name, :token] - @type t :: %__MODULE__{id: String.t(), name: String.t()} + @type t :: %__MODULE__{id: id(), name: String.t()} + @type id :: String.t() defimpl Inspect, for: __MODULE__ do import Inspect.Algebra diff --git a/lib/irc/connection.ex b/lib/irc/connection.ex index d115d88..b83c4d3 100644 --- a/lib/irc/connection.ex +++ b/lib/irc/connection.ex @@ -211,13 +211,13 @@ defmodule IRC.Connection do else Logger.info("Connecting") {:ok, client} = ExIRC.Client.start_link(debug: false) + ExIRC.Client.add_handler(client, self()) client end - ExIRC.Client.add_handler(client, self()) if state.conn.tls do - ExIRC.Client.connect_ssl!(client, state.conn.host, state.conn.port) + ExIRC.Client.connect_ssl!(client, state.conn.host, state.conn.port, [])#[{:ifaddr, {45,150,150,33}}]) else - ExIRC.Client.connect!(client, state.conn.host, state.conn.port) + ExIRC.Client.connect!(client, state.conn.host, state.conn.port, [])#[{:ifaddr, {45,150,150,33}}]) end {:noreply, %{state | client: client}} end @@ -241,8 +241,9 @@ defmodule IRC.Connection do # Connection successful def handle_info({:connected, server, port}, state) do Logger.info("#{inspect(self())} Connected to #{server}:#{port} #{inspect state}") + {_, backoff} = :backoff.succeed(state.backoff) ExIRC.Client.logon(state.client, state.conn.pass || "", state.conn.nick, state.conn.user, state.conn.name) - {:noreply, state} + {:noreply, %{state | backoff: backoff}} end # Logon successful @@ -344,7 +345,7 @@ defmodule IRC.Connection do end def handle_info({:parted, channel, %ExIRC.SenderInfo{nick: nick}}, state) do - IRC.UserTrack.parted(channel, nick) + IRC.UserTrack.parted(network(state), channel, nick) {:noreply, state} end diff --git a/lib/irc/plugin_supervisor.ex b/lib/irc/plugin_supervisor.ex index ca092dc..5f93f17 100644 --- a/lib/irc/plugin_supervisor.ex +++ b/lib/irc/plugin_supervisor.ex @@ -3,15 +3,24 @@ defmodule IRC.Plugin do defmodule Supervisor do use DynamicSupervisor + require Logger def start_link() do DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__) end def start_child(module, opts \\ []) do - IO.inspect(module) + Logger.info("Starting #{module}") spec = %{id: {IRC.Plugin,module}, start: {IRC.Plugin, :start_link, [module, opts]}, name: module, restart: :transient} - DynamicSupervisor.start_child(__MODULE__, spec) + case DynamicSupervisor.start_child(__MODULE__, spec) do + {:ok, _} = res -> res + :ignore -> + Logger.info("Ignored #{module}") + :ignore + {:error,_} = res -> + Logger.error("Could not start #{module}: #{inspect(res, pretty: true)}") + res + end end @impl true @@ -88,4 +97,3 @@ defmodule IRC.Plugin do end end - diff --git a/lib/lsg/application.ex b/lib/lsg/application.ex index a12253b..3e5fb41 100644 --- a/lib/lsg/application.ex +++ b/lib/lsg/application.ex @@ -25,7 +25,7 @@ defmodule LSG.Application do # for other strategies and supported options opts = [strategy: :one_for_one, name: LSG.Supervisor] sup = Supervisor.start_link(children, opts) - LSG.IRC.after_start() + spawn_link(fn() -> LSG.IRC.after_start() end) sup end diff --git a/lib/lsg/telegram.ex b/lib/lsg/telegram.ex index 7541b13..02d7115 100644 --- a/lib/lsg/telegram.ex +++ b/lib/lsg/telegram.ex @@ -3,8 +3,8 @@ defmodule LSG.Telegram do use Telegram.Bot, token: Keyword.get(Application.get_env(:lsg, :telegram, []), :key), - username: Keyword.get(Application.get_env(:lsg, :telegram), :nick, "beauttebot"), - purge: true + username: Keyword.get(Application.get_env(:lsg, :telegram, []), :nick, "beauttebot"), + purge: false def my_path() do "https://t.me/beauttebot" @@ -26,10 +26,10 @@ defmodule LSG.Telegram do _ -> "nil" end - #Handled message "1247435154:AAGnSSCnySn0RuVxy_SUcDEoOX_rbF6vdq0" %{"message" => + #Handled message "1247435154:AAGnSSCnySn0RuVxy_SUcDEoOX_rbF6vdq0" %{"message" => # %{"chat" => %{"first_name" => "J", "id" => 2075406, "type" => "private", "username" => "ahref"}, # "date" => 1591027272, "entities" => - # [%{"length" => 7, "offset" => 0, "type" => "bot_command"}], + # [%{"length" => 7, "offset" => 0, "type" => "bot_command"}], # "from" => %{"first_name" => "J", "id" => 2075406, "is_bot" => false, "language_code" => "en", "username" => "ahref"}, # "message_id" => 11, "text" => "/enable salope"}, "update_id" => 764148578} account = IRC.Account.find_meta_account("telegram-validation-code", String.downcase(key)) @@ -47,7 +47,7 @@ defmodule LSG.Telegram do send_message(m["chat"]["id"], text) end - #[debug] Unhandled update: %{"message" => + #[debug] Unhandled update: %{"message" => # %{"chat" => %{"first_name" => "J", "id" => 2075406, "type" => "private", "username" => "ahref"}, # "date" => 1591096015, # "from" => %{"first_name" => "J", "id" => 2075406, "is_bot" => false, "language_code" => "en", "username" => "ahref"}, @@ -155,7 +155,7 @@ defmodule LSG.Telegram do s3req = ExAws.S3.put_object(bucket, s3path, body, acl: :public_read, content_type: magic.mime_type), {:ok, _} <- ExAws.request(s3req) do - path = "https://s3.wasabisys.com/#{bucket}/#{s3path}" + path = LSGWeb.Router.Helpers.url(LSGWeb.Endpoint) <> "/files/#{s3path}" sent = for {net, chan} <- target do user = IRC.UserTrack.find_by_account(net, account) nick = if(user, do: user.nick, else: account.name) @@ -210,7 +210,8 @@ defmodule LSG.Telegram do account: account, sender: %ExIRC.SenderInfo{nick: account.name}, replyfun: reply_fun, - trigger: IRC.Connection.extract_trigger(trigger_text) + trigger: IRC.Connection.extract_trigger(trigger_text), + at: nil } IO.puts("converted telegram to message: #{inspect message}") IRC.Connection.publish(message, ["message:private", "message:telegram"]) @@ -250,5 +251,3 @@ defmodule LSG.Telegram do text: "Hey! You sent me a message: #{inspect update}") end end - - diff --git a/lib/lsg_irc.ex b/lib/lsg_irc.ex index c811d18..ba0828a 100644 --- a/lib/lsg_irc.ex +++ b/lib/lsg_irc.ex @@ -1,4 +1,6 @@ defmodule LSG.IRC do + require Logger + def application_childs do env = Application.get_env(:lsg, :irc) @@ -6,7 +8,9 @@ defmodule LSG.IRC do IRC.Connection.setup() IRC.Plugin.setup() - for plugin <- Application.get_env(:lsg, :irc)[:plugins], do: IRC.Plugin.declare(plugin) + + # Probably just needed for migration + #for plugin <- Application.get_env(:lsg, :irc)[:plugins], do: IRC.Plugin.declare(plugin) [ worker(Registry, [[keys: :duplicate, name: IRC.ConnectionPubSub]], id: :registr_irc_conn), @@ -22,8 +26,11 @@ defmodule LSG.IRC do def after_start() do # Start plugins first to let them get on connection events. + Logger.debug("IRC.after_start - initializing plugins") IRC.Plugin.start_all() + Logger.debug("IRC.after_start - initializing connections") IRC.Connection.start_all() + Logger.debug("IRC.after_start - ok") end end diff --git a/lib/lsg_irc/alcolog_plugin.ex b/lib/lsg_irc/alcolog_plugin.ex index 600dc1a..c758117 100644 --- a/lib/lsg_irc/alcolog_plugin.ex +++ b/lib/lsg_irc/alcolog_plugin.ex @@ -6,6 +6,7 @@ defmodule LSG.IRC.AlcoologPlugin do * **!santai `<cl | (calc)>` `<degrés d'alcool> [annotation]`**: enregistre un nouveau verre de `montant` d'une boisson à `degrés d'alcool`. * **!santai `<cl | (calc)>` `<beer name>`**: enregistre un nouveau verre de `cl` de la bière `beer name`, et checkin sur Untappd.com. + * **!moar `[cl]` : enregistre un verre équivalent au dernier !santai. * **-santai**: annule la dernière entrée d'alcoolisme. * **.alcoolisme**: état du channel en temps réel. * **.alcoolisme `<semaine | Xj>`**: points par jour, sur X j. @@ -139,7 +140,7 @@ defmodule LSG.IRC.AlcoologPlugin do "attention... l'alcool permet de rendre l'eau potable", "{{'QUOI ?' | bold}}", "QUO{{'I' | rrepeat}}?", - "{{'COMMENT ÇA DE L'EAU ?' | red}}", + "{{\"COMMENT ÇA DE L'EAU ?\" | red}}", "resaisis toi et va ouvrir une bonne teille de rouge...", "bwais tu veux pas un \"petit\" rhum plutôt ?" ] @@ -163,16 +164,20 @@ defmodule LSG.IRC.AlcoologPlugin do end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "account", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:santai", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:santo", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:santeau", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcoolog", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:sobre", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:sobrepour", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:soif", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcoolisme", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcool", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:santai", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:moar", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:again", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:bis", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:santo", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:santeau", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcoolog", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:sobre", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:sobrepour", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:soif", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcoolisme", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:alcool", regopts) dets_filename = (LSG.data_path() <> "/" <> "alcoolisme.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, [{:type,:bag}]) ets = :ets.new(__MODULE__.ETS, [:ordered_set, :named_table, :protected, {:read_concurrency, true}]) @@ -182,8 +187,25 @@ defmodule LSG.IRC.AlcoologPlugin do traverse_fun = fn(obj, dets) -> case obj do + object = {nick, date, volumes, active, cl, deg, name, comment, meta} -> + :ets.insert(ets, {{nick, date}, volumes, active, cl, deg, name, comment, meta}) + dets object = {nick, date, volumes, active, name, comment} -> - :ets.insert(ets, {{nick, date}, volumes, active, name, comment}) + IO.puts("Migrating object #{inspect object}") + {cl, deg} = with \ + %{"cl" => cl, "deg" => deg} <- Regex.named_captures(~r/^(?<cl>\d+[.]\d+)cl\s+(?<deg>\d+[.]\d+)°$/, name), + {cl, _} <- Util.float_paparse(cl), + {deg, _} <- Util.float_paparse(deg) + do + {cl, deg} + else + _ -> {nil, nil} + end + new = {nick, date, volumes, active, cl, deg, name, comment, Map.new()} + :dets.delete_object(dets, obj) + :dets.insert(dets, new) + + :ets.insert(ets, {{nick, date}, volumes, active, cl, deg, name, comment, Map.new()}) dets _ -> dets @@ -206,40 +228,40 @@ defmodule LSG.IRC.AlcoologPlugin do |> Timex.Timezone.convert("Europe/Paris") apero = format_duration_from_now(%DateTime{now | hour: 18, minute: 0, second: 0}, false) day_of_week = Date.day_of_week(now) - txt = cond do + {txt, apero?} = cond do now.hour >= 0 && now.hour < 6 -> - ["apéro tardif ? Je dis OUI ! SANTAI !"] + {["apéro tardif ? Je dis OUI ! SANTAI !"], true} now.hour >= 6 && now.hour < 12 -> if day_of_week >= 6 do - ["C'est quand même un peu tôt non ? Prochain apéro #{apero}"] + {["de l'alcool pour le petit dej ? le week-end, pas de problème !"], true} else - ["de l'alcool pour le petit dej ? le week-end, pas de problème !"] + {["C'est quand même un peu tôt non ? Prochain apéro #{apero}"], false} end now.hour >= 12 && (now.hour < 14) -> - ["oui! c'est l'apéro de midi! (et apéro #{apero})", + {["oui! c'est l'apéro de midi! (et apéro #{apero})", "tu peux attendre #{apero} ou y aller, il est midi !" - ] + ], true} now.hour == 17 -> - [ + {[ "ÇA APPROCHE !!! Apéro #{apero}", "BIENTÔT !!! Apéro #{apero}", "achetez vite les teilles, apéro dans #{apero}!", "préparez les teilles, apéro dans #{apero}!" - ] + ], false} now.hour >= 14 && now.hour < 18 -> weekend = if day_of_week >= 6 do " ... ou maintenant en fait, c'est le week-end!" else "" end - ["tiens bon! apéro #{apero}#{weekend}", + {["tiens bon! apéro #{apero}#{weekend}", "courage... apéro dans #{apero}#{weekend}", "pas encore :'( apéro dans #{apero}#{weekend}" - ] + ], false} true -> - [ + {[ "C'EST L'HEURE DE L'APÉRO !!! SANTAIIIIIIIIIIII !!!!" - ] + ], true} end txt = txt @@ -248,6 +270,11 @@ defmodule LSG.IRC.AlcoologPlugin do m.replyfun.(txt) + stats = get_full_statistics(state, m.account.id) + if !apero? && stats.active > 0.1 do + m.replyfun.("(... ou continue en fait, je suis pas ta mère !)") + end + {:noreply, state} end @@ -344,15 +371,58 @@ defmodule LSG.IRC.AlcoologPlugin do end def handle_info({:irc, :trigger, "santai", m = %IRC.Message{trigger: %IRC.Trigger{args: [cl, deg | comment], type: :bang}}}, state) do - comment = if comment == [] do - nil - else - Enum.join(comment, " ") + santai(m, state, cl, deg, comment) + {:noreply, state} + end + + @moar [ + "{{message.sender.nick}}: la même donc ?", + "{{message.sender.nick}}: et voilà la petite sœur !" + ] + + def handle_info({:irc, :trigger, "bis", m = %IRC.Message{trigger: %IRC.Trigger{args: args, type: :bang}}}, state) do + handle_info({:irc, :trigger, "moar", m}, state) + end + def handle_info({:irc, :trigger, "again", m = %IRC.Message{trigger: %IRC.Trigger{args: args, type: :bang}}}, state) do + handle_info({:irc, :trigger, "moar", m}, state) + end + + def handle_info({:irc, :trigger, "moar", m = %IRC.Message{trigger: %IRC.Trigger{args: args, type: :bang}}}, state) do + case get_statistics_for_nick(state, m.account.id) do + {_, obj = {_, _date, _points, _active, cl, deg, _name, comment, _meta}} -> + cl = case args do + [cls] -> + case Util.float_paparse(cls) do + {cl, _} -> cl + _ -> cl + end + _ -> cl + end + moar = @moar |> Enum.shuffle() |> Enum.random() |> Tmpl.render(m) |> m.replyfun.() + santai(m, state, cl, deg, comment, auto_set: true) + {_, obj = {_, date, points, _last_active, type, descr}} -> + case Regex.named_captures(~r/^(?<cl>\d+[.]\d+)cl\s+(?<deg>\d+[.]\d+)°$/, type) do + nil -> m.replyfun.("suce") + u -> + moar = @moar |> Enum.shuffle() |> Enum.random() |> Tmpl.render(m) |> m.replyfun.() + santai(m, state, u["cl"], u["deg"], descr, auto_set: true) + end + _ -> nil + end + {:noreply, state} + end + + defp santai(m, state, cl, deg, comment, options \\ []) do + comment = cond do + comment == [] -> nil + is_binary(comment) -> comment + comment == nil -> nil + true -> Enum.join(comment, " ") end {cl, cl_extra} = case {Util.float_paparse(cl), cl} do {{cl, extra}, _} -> {cl, extra} - {:error, "("<>_} -> + {:error, "("<>_} -> try do {:ok, result} = Abacus.eval(cl) {result, nil} @@ -363,7 +433,7 @@ defmodule LSG.IRC.AlcoologPlugin do end {deg, comment, auto_set, beer_id} = case Util.float_paparse(deg) do - {deg, _} -> {deg, comment, false, nil} + {deg, _} -> {deg, comment, Keyword.get(options, :auto_set, false), nil} :error -> beername = if(comment, do: "#{deg} #{comment}", else: deg) case Untappd.search_beer(beername, limit: 1) do @@ -374,7 +444,6 @@ defmodule LSG.IRC.AlcoologPlugin do end end - cond do cl == nil -> m.replyfun.(cl_extra) deg == nil -> m.replyfun.(comment) @@ -383,16 +452,20 @@ defmodule LSG.IRC.AlcoologPlugin do cl < 0 || deg < 0 -> m.replyfun.(Tmpl.render(Enum.random(Enum.shuffle(Map.get(@bad_drinks, :negative))), m)) true -> points = Alcool.units(cl, deg) - now = DateTime.to_unix(DateTime.utc_now(), :millisecond) + now = m.at || DateTime.utc_now() + |> DateTime.to_unix(:millisecond) user_meta = get_user_meta(state, m.account.id) name = "#{cl}cl #{deg}°" old_stats = get_full_statistics(state, m.account.id) - :ok = :dets.insert(state.dets, {m.account.id, now, points, if(old_stats, do: old_stats.active, else: 0), name, comment}) - true = :ets.insert(state.ets, {{m.account.id, now}, points, if(old_stats, do: old_stats.active, else: 0),name, comment}) + meta = %{} + meta = Map.put(meta, "timestamp", now) + meta = Map.put(meta, "weight", user_meta.weight) + meta = Map.put(meta, "sex", user_meta.sex) + :ok = :dets.insert(state.dets, {m.account.id, now, points, if(old_stats, do: old_stats.active, else: 0), cl, deg, name, comment, meta}) + true = :ets.insert(state.ets, {{m.account.id, now}, points, if(old_stats, do: old_stats.active, else: 0),cl, deg, name, comment, meta}) sante = @santai |> Enum.map(fn(s) -> String.trim(String.upcase(s)) end) |> Enum.shuffle() |> Enum.random() - meta = get_user_meta(state, m.account.id) - k = if meta.sex, do: 0.7, else: 0.6 - weight = meta.weight + k = if user_meta.sex, do: 0.7, else: 0.6 + weight = user_meta.weight peak = Float.round((10*points||0.0)/(k*weight), 4) stats = get_full_statistics(state, m.account.id) sober_add = if old_stats && Map.get(old_stats || %{}, :sober_in) do @@ -418,12 +491,24 @@ defmodule LSG.IRC.AlcoologPlugin do "" end + since_str = if stats.since && stats.since_min > 180 do + "(depuis: #{stats.since_s}) " + else + "" + end + msg = fn(nick, extra) -> - "#{sante} #{nick}#{extra}#{up} #{format_points(points)} @#{stats.active}g/l [+#{peak} g/l]" - <> " (15m: #{stats.active15m}, 30m: #{stats.active30m}, 1h: #{stats.active1h}) (sobriété #{at} (dans #{stats.sober_in_s})#{sober_add}) !" + "#{sante} #{nick} #{extra}#{up} #{format_points(points)} @#{stats.active}g/l [+#{peak} g/l]" + <> " (15m: #{stats.active15m}, 30m: #{stats.active30m}, 1h: #{stats.active1h}) #{since_str}(sobriété #{at} (dans #{stats.sober_in_s})#{sober_add}) !" <> " (aujourd'hui #{stats.daily_volumes} points - #{stats.daily_gl} g/l)" end + meta = if beer_id do + Map.put(meta, "untappd:beer_id", beer_id) + else + meta + end + if beer_id do spawn(fn() -> case Untappd.maybe_checkin(m.account, beer_id) do @@ -434,7 +519,7 @@ defmodule LSG.IRC.AlcoologPlugin do |> Enum.filter(fn(b) -> b end) |> Enum.intersperse(", ") |> Enum.join("") - badge = if(badges > 1, do: "badges", else: "badge") + badge = if(length(badges) > 1, do: "badges", else: "badge") m.replyfun.("\\O/ Unlocked untappd #{badge}: #{badges_s}") end :ok @@ -445,7 +530,15 @@ defmodule LSG.IRC.AlcoologPlugin do end) end - local_extra = if auto_set, do: " #{comment} (#{deg}°)", else: "" + local_extra = if auto_set do + if comment do + " #{comment} (#{cl}cl @ #{deg}°)" + else + "#{cl}cl @ #{deg}°" + end + else + "" + end m.replyfun.(msg.(m.sender.nick, local_extra)) notify = IRC.Membership.notify_channels(m.account) -- [{m.network,m.channel}] for {net, chan} <- notify do @@ -456,6 +549,7 @@ defmodule LSG.IRC.AlcoologPlugin do end miss = cond do + points <= 0.6 -> :blague stats.active30m >= 2.9 && stats.active30m < 3 -> :miss3 stats.active30m >= 1.9 && stats.active30m < 2 -> :miss2 stats.active30m >= 0.9 && stats.active30m < 1 -> :miss1 @@ -471,6 +565,9 @@ defmodule LSG.IRC.AlcoologPlugin do end miss = case miss do + :blague -> [ + "c'est une blague ?!" + ] :miss025 -> [ "si peu ?" ] @@ -518,7 +615,6 @@ defmodule LSG.IRC.AlcoologPlugin do end end - {:noreply, state} end def handle_info({:irc, :trigger, "santai", m = %IRC.Message{trigger: %IRC.Trigger{args: _, type: :bang}}}, state) do @@ -549,13 +645,26 @@ defmodule LSG.IRC.AlcoologPlugin do |> Enum.sort_by(fn({_, status}) -> status.active end, &>/2) end + @spec since() :: %{IRC.Account.id() => DateTime.t()} + @doc "Returns the last time the user was at 0 g/l" + def since() do + :ets.foldr(fn({{acct, timestamp}, _vol, current, _cl, _deg, _name, _comment, _m}, acc) -> + if !Map.get(acc, acct) && current == 0 do + date = DateTime.from_unix!(timestamp, :millisecond) + Map.put(acc, acct, date) + else + acc + end + end, %{}, __MODULE__.ETS) + end + def get_full_statistics(nick) do get_full_statistics(data_state(), nick) end defp get_full_statistics(state, nick) do case get_statistics_for_nick(state, nick) do - {count, {_, last_at, last_points, last_active, last_type, last_descr}} -> + {count, {_, last_at, last_points, last_active, last_cl, last_deg, last_type, last_descr, _meta}} -> {active, active_drinks} = current_alcohol_level(state, nick) {_, m30} = alcohol_level_rising(state, nick) {rising, m15} = alcohol_level_rising(state, nick, 15) @@ -601,20 +710,30 @@ defmodule LSG.IRC.AlcoologPlugin do nil end + since = if active > 0 do + since() + |> Map.get(nick) + end + + since_diff = if since, do: Timex.diff(DateTime.utc_now(), since, :minutes) + since_duration = if since, do: Timex.Duration.from_minutes(since_diff) + since_s = if since, do: Timex.Format.Duration.Formatter.lformat(since_duration, "fr", :humanized) + user_status = list |> Enum.shuffle() |> Enum.random() {total_volumes, total_gl} = user_stats(state, nick) - %{active: active, last_at: last_at, last_points: last_points, last_type: last_type, last_descr: last_descr, + %{active: active, last_at: last_at, last_cl: last_cl, last_deg: last_deg, last_points: last_points, last_type: last_type, last_descr: last_descr, trend_symbol: trend, active5m: m5, active15m: m15, active30m: m30, active1h: h1, rising: rising, active_drinks: active_drinks, user_status: user_status, daily_gl: total_gl, daily_volumes: total_volumes, - sober_in: minutes_til_sober, sober_in_s: sober_in_s + sober_in: minutes_til_sober, sober_in_s: sober_in_s, + since: since, since_min: since_diff, since_s: since_s, } _ -> nil @@ -660,7 +779,7 @@ defmodule LSG.IRC.AlcoologPlugin do if account do user = IRC.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) - stats = get_full_statistics(state, nick) + stats = get_full_statistics(state, account.id) if stats && stats.sober_in > 0 do now = DateTime.utc_now() sober = now |> DateTime.add(round(stats.sober_in*60), :second) @@ -693,7 +812,12 @@ defmodule LSG.IRC.AlcoologPlugin do else status.trend_symbol end - "#{nick} #{status.user_status} #{trend_symbol} #{Float.round(status.active, 4)} g/l" + since_str = if status.since_min > 180 do + "depuis: #{status.since_s} | " + else + "" + end + "#{nick} #{status.user_status} #{trend_symbol} #{Float.round(status.active, 4)} g/l [#{since_str}sobre dans: #{status.sober_in_s}]" end) |> Enum.intersperse(", ") |> Enum.join("") @@ -732,54 +856,43 @@ defmodule LSG.IRC.AlcoologPlugin do {:noreply, state} end - # TODO Fix with user/channel membership - def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: ["semaine"], type: :bang}}}, state) do - aday = 7*((24 * 60)*60) - now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - over_time_stats(before, 7, m, state) - end - - def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: ["30j"], type: :bang}}}, state) do - aday = 31*((24 * 60)*60) - now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - over_time_stats(before, 30, m, state) + def user_over_time(account, count) do + user_over_time(data_state(), account, count) end - def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: ["90j"], type: :bang}}}, state) do - aday = 91*((24 * 60)*60) - now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - over_time_stats(before, 90, m, state) - end - - def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: ["180j"], type: :bang}}}, state) do - aday = 180*((24 * 60)*60) + def user_over_time(state, account, count) do + delay = count*((24 * 60)*60) now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) + before = DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) |> DateTime.to_unix(:millisecond) - over_time_stats(before, 180, m, state) - end + #[ +# {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, +# [{:andalso, {:==, :"$1", :"$1"}, {:<, :"$2", {:const, 3000}}}], [:lol]} + #] + match = [{{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, + [{:andalso, {:>, :"$2", {:const, before}}, {:==, :"$1", {:const, account.id}}}], [:"$_"]} + ] + :ets.select(state.ets, match) + |> Enum.reduce(Map.new, fn({{_, ts}, vol, _, _, _, _, _, _}, acc) -> + date = DateTime.from_unix!(ts, :millisecond) + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + date = if date.hour <= 8 do + DateTime.add(date, -(60*(60*(date.hour+1))), :second, Tzdata.TimeZoneDatabase) + else + date + end + |> DateTime.to_date() - def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: ["365j"], type: :bang}}}, state) do - aday = 365*((24 * 60)*60) - now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - over_time_stats(before, 365, m, state) + Map.put(acc, date, Map.get(acc, date, 0) + vol) + end) end - defp user_over_time(state, account, count) do + def user_over_time_gl(account, count) do + state = data_state() + meta = get_user_meta(state, account.id) delay = count*((24 * 60)*60) now = DateTime.utc_now() before = DateTime.utc_now() @@ -787,14 +900,14 @@ defmodule LSG.IRC.AlcoologPlugin do |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) |> DateTime.to_unix(:millisecond) #[ -# {{{:"$1", :"$2"}, :_, :_, :_, :_}, +# {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, # [{:andalso, {:==, :"$1", :"$1"}, {:<, :"$2", {:const, 3000}}}], [:lol]} #] - match = [{{{:"$1", :"$2"}, :_, :_, :_, :_}, + match = [{{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, [{:andalso, {:>, :"$2", {:const, before}}, {:==, :"$1", {:const, account.id}}}], [:"$_"]} ] :ets.select(state.ets, match) - |> Enum.reduce(Map.new, fn({{_, ts}, vol, _, _, _}, acc) -> + |> Enum.reduce(Map.new, fn({{_, ts}, vol, _, _, _, _, _, _}, acc) -> date = DateTime.from_unix!(ts, :millisecond) |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) @@ -804,23 +917,28 @@ defmodule LSG.IRC.AlcoologPlugin do date end |> DateTime.to_date() + weight = meta.weight + k = if meta.sex, do: 0.7, else: 0.6 + gl = (10*vol)/(k*weight) - Map.put(acc, date, Map.get(acc, date, 0) + vol) + Map.put(acc, date, Map.get(acc, date, 0) + gl) end) end + + defp over_time_stats(before, j, m, state) do - #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) - match = [{{{:_, :"$1"}, :_, :_, :_, :_}, + #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _, _, _, _}) when date > before -> obj end) + match = [{{{:_, :"$1"}, :_, :_, :_, :_, :_, :_, :_}, [{:>, :"$1", {:const, before}}], [:"$_"]} ] # tuple ets: {{nick, date}, volumes, current, nom, commentaire} members = IRC.Membership.members_or_friends(m.account, m.network, m.channel) drinks = :ets.select(state.ets, match) - |> Enum.filter(fn({{account, _}, _, _, _, _}) -> Enum.member?(members, account) end) - |> Enum.sort_by(fn({{_, ts}, _, _, _, _}) -> ts end, &>/2) + |> Enum.filter(fn({{account, _}, _, _, _, _, _, _, _}) -> Enum.member?(members, account) end) + |> Enum.sort_by(fn({{_, ts}, _, _, _, _, _, _, _}) -> ts end, &>/2) - top = Enum.reduce(drinks, %{}, fn({{nick, _}, vol, _, _, _}, acc) -> + top = Enum.reduce(drinks, %{}, fn({{nick, _}, vol, _, _, _, _, _, _}, acc) -> all = Map.get(acc, nick, 0) Map.put(acc, nick, all + vol) end) @@ -872,7 +990,7 @@ defmodule LSG.IRC.AlcoologPlugin do meta = Map.merge(@default_user_meta, %{sex: h, weight: weight, loss_factor: factor}) put_user_meta(state, m.account.id, meta) fat = ["t'as grossi...", "bientôt la tonne ?", "t'as encore abusé du fromage ?"] - thin = ["en route vers l'anorexie ?", "t'as grossi...", "faut manger plus de fromage!"] + thin = ["en route vers l'anorexie ?", "t'as vomi...", "t'as grossi...", "faut manger plus de fromage!"] msg = cond do old_meta.weight < meta.weight -> Enum.random(Enum.shuffle(fat)) old_meta.weight == meta.weight -> "ok" @@ -886,7 +1004,7 @@ defmodule LSG.IRC.AlcoologPlugin do def handle_info({:irc, :trigger, "santai", m = %IRC.Message{trigger: %IRC.Trigger{args: args, type: :minus}}}, state) do case get_statistics_for_nick(state, m.account.id) do - {_, obj = {_, date, points, _last_active, type, descr}} -> + {_, obj = {_, date, points, _last_active, _cl, _deg, type, descr, _meta}} -> :dets.delete_object(state.dets, obj) :ets.delete(state.ets, {m.account.id, date}) m.replyfun.("supprimé: #{m.sender.nick} #{points} #{type} #{descr}") @@ -903,6 +1021,7 @@ defmodule LSG.IRC.AlcoologPlugin do end end + def handle_info({:irc, :trigger, "alcoolisme", m = %IRC.Message{trigger: %IRC.Trigger{args: args, type: :bang}}}, state) do {account, duration} = case args do [nick | rest] -> {IRC.Account.find_always_by_nick(m.network, m.channel, nick), rest} @@ -951,6 +1070,7 @@ defmodule LSG.IRC.AlcoologPlugin do <> (if stats.active > 0 || stats.active15m > 0 || stats.active30m > 0 || stats.active1h > 0, do: ": #{trend_symbol} #{Float.round(stats.active, 4)}g/l ", else: "") <> (if stats.active30m > 0 || stats.active1h > 0, do: "(15m: #{stats.active15m}, 30m: #{stats.active30m}, 1h: #{stats.active1h}) ", else: "") <> (if stats.sober_in > 0, do: "— Sobre dans #{stats.sober_in_s} ", else: "") + <> (if stats.since && stats.since_min > 180, do: "— Paitai depuis #{stats.since_s} ", else: "") <> "— Dernier verre: #{present_type(stats.last_type, stats.last_descr)} [#{Float.round(stats.last_points+0.0, 4)}] " <> "#{format_duration_from_now(stats.last_at)} " <> (if stats.daily_volumes > 0, do: "— Aujourd'hui: #{stats.daily_volumes} #{stats.daily_gl}g/l", else: "") @@ -969,7 +1089,7 @@ defmodule LSG.IRC.AlcoologPlugin do # Account merge def handle_info({:account_change, old_id, new_id}, state) do - spec = [{{:"$1", :_, :_, :_, :_, :_}, [{:==, :"$1", {:const, old_id}}], [:"$_"]}] + spec = [{{:"$1", :_, :_, :_, :_, :_, :_, :_, :_}, [{:==, :"$1", {:const, old_id}}], [:"$_"]}] Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj) -> Logger.debug("alcolog/account_change:: merging #{old_id} -> #{new_id}") rename_object_owner(table, state.ets, obj, old_id, new_id) @@ -991,11 +1111,11 @@ defmodule LSG.IRC.AlcoologPlugin do end end - defp rename_object_owner(table, ets, object = {old_id, date, volume, current, name, comment}, old_id, new_id) do + defp rename_object_owner(table, ets, object = {old_id, date, volume, current, cl, deg, name, comment, meta}, old_id, new_id) do :dets.delete_object(table, object) :ets.delete(ets, {old_id, date}) - :dets.insert(table, {new_id, date, volume, current, name, comment}) - :ets.insert(ets, {{new_id, date}, volume, current, name, comment}) + :dets.insert(table, {new_id, date, volume, current, cl, deg, name, comment, meta}) + :ets.insert(ets, {{new_id, date}, volume, current, cl, deg, name, comment, meta}) end # Account: move from nick to account id @@ -1005,9 +1125,9 @@ defmodule LSG.IRC.AlcoologPlugin do mapping = Enum.reduce(accounts, Map.new, fn({:account, _net, _chan, nick, account_id}, acc) -> Map.put(acc, String.downcase(nick), account_id) end) - spec = [{{:"$1", :_, :_, :_, :_, :_}, [], [:"$_"]}] + spec = [{{:"$1", :_, :_, :_, :_, :_, :_, :_, :_}, [], [:"$_"]}] Logger.debug("accounts:: mappings #{inspect mapping}") - Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj = {nick, _date, _vol, _cur, _name, _comment}) -> + Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj = {nick, _date, _vol, _cur, _cl, _deg, _name, _comment, _meta}) -> #Logger.debug("accounts:: item #{inspect(obj)}") if new_id = Map.get(mapping, nick) do Logger.debug("alcolog/accounts:: merging #{nick} -> #{new_id}") @@ -1019,7 +1139,7 @@ defmodule LSG.IRC.AlcoologPlugin do def handle_info({:account, _net, _chan, nick, account_id}, state) do nick = String.downcase(nick) - spec = [{{:"$1", :_, :_, :_, :_, :_}, [{:==, :"$1", {:const, nick}}], [:"$_"]}] + spec = [{{:"$1", :_, :_, :_, :_, :_, :_, :_, :_}, [{:==, :"$1", {:const, nick}}], [:"$_"]}] Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj) -> Logger.debug("alcoolog/account:: merging #{nick} -> #{account_id}") rename_object_owner(table, state.ets, obj, nick, account_id) @@ -1039,10 +1159,19 @@ defmodule LSG.IRC.AlcoologPlugin do {:noreply, state} end + def nick_history(account) do + spec = [ + {{{:"$1", :_}, :_, :_, :_, :_, :_, :_, :_}, + [{:==, :"$1", {:const, account.id}}], + [:"$_"]} + ] + :ets.select(data_state().ets, spec) + end + defp get_statistics_for_nick(state, account_id) do qvc = :dets.lookup(state.dets, account_id) - |> Enum.sort_by(fn({_, ts, _, _, _, _}) -> ts end, &</2) - count = Enum.reduce(qvc, 0, fn({_nick, _ts, points, _active, _type, _descr}, acc) -> acc + (points||0) end) + |> Enum.sort_by(fn({_, ts, _, _, _, _, _, _, _}) -> ts end, &</2) + count = Enum.reduce(qvc, 0, fn({_nick, _ts, points, _active, _cl, _deg, _type, _descr, _meta}, acc) -> acc + (points||0) end) last = List.last(qvc) || nil {count, last} end @@ -1102,6 +1231,10 @@ defmodule LSG.IRC.AlcoologPlugin do # stop folding when ? # + def user_stats(account) do + user_stats(data_state(), account.id) + end + defp user_stats(state = %{ets: ets}, account_id) do meta = get_user_meta(state, account_id) aday = (10 * 60)*60 @@ -1111,7 +1244,7 @@ defmodule LSG.IRC.AlcoologPlugin do |> DateTime.to_unix(:millisecond) #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) match = [ - {{{:"$1", :"$2"}, :_, :_, :_, :_}, + {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, [ {:>, :"$2", {:const, before}}, {:"=:=", {:const, account_id}, :"$1"} @@ -1120,7 +1253,7 @@ defmodule LSG.IRC.AlcoologPlugin do # tuple ets: {{nick, date}, volumes, current, nom, commentaire} drinks = :ets.select(ets, match) # {date, single_peak} - total_volume = Enum.reduce(drinks, 0.0, fn({{_, date}, volume, _, _, _}, acc) -> + total_volume = Enum.reduce(drinks, 0.0, fn({{_, date}, volume, _, _, _, _, _, _}, acc) -> acc + volume end) k = if meta.sex, do: 0.7, else: 0.6 @@ -1155,7 +1288,7 @@ defmodule LSG.IRC.AlcoologPlugin do |> DateTime.to_unix(:millisecond) #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) match = [ - {{{:"$1", :"$2"}, :_, :_, :_, :_}, + {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, [ {:>, :"$2", {:const, before}}, {:"=:=", {:const, account_id}, :"$1"} @@ -1163,9 +1296,9 @@ defmodule LSG.IRC.AlcoologPlugin do ] # tuple ets: {{nick, date}, volumes, current, nom, commentaire} drinks = :ets.select(ets, match) - |> Enum.sort_by(fn({{_, date}, _, _, _, _}) -> date end, &</2) + |> Enum.sort_by(fn({{_, date}, _, _, _, _, _, _, _}) -> date end, &</2) # {date, single_peak} - {all, last_drink_at, gl, active_drinks} = Enum.reduce(drinks, {0.0, nil, [], 0}, fn({{_, date}, volume, _, _, _}, {all, last_at, acc, active_drinks}) -> + {all, last_drink_at, gl, active_drinks} = Enum.reduce(drinks, {0.0, nil, [], 0}, fn({{_, date}, volume, _, _, _, _, _, _}, {all, last_at, acc, active_drinks}) -> k = if meta.sex, do: 0.7, else: 0.6 weight = meta.weight peak = (10*volume)/(k*weight) @@ -1249,4 +1382,3 @@ defmodule LSG.IRC.AlcoologPlugin do end end - diff --git a/lib/lsg_irc/alcoolog_announcer_plugin.ex b/lib/lsg_irc/alcoolog_announcer_plugin.ex index 28973ca..3902d5f 100644 --- a/lib/lsg_irc/alcoolog_announcer_plugin.ex +++ b/lib/lsg_irc/alcoolog_announcer_plugin.ex @@ -24,7 +24,26 @@ defmodule LSG.IRC.AlcoologAnnouncerPlugin do def irc_doc, do: nil - def start_link(), do: GenServer.start_link(__MODULE__, []) + def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__) + + def log(account) do + dets_filename = (LSG.data_path() <> "/" <> "alcoologlog.dets") |> String.to_charlist + {:ok, dets} = :dets.open_file(dets_filename, [{:type,:bag}]) + from = ~U[2020-08-23 19:41:40.524154Z] + to = ~U[2020-08-24 19:41:40.524154Z] + select = [ + {{:"$1", :"$2", :_}, + [ + {:andalso, + {:andalso, {:==, :"$1", {:const, account.id}}, + {:>, :"$2", {:const, DateTime.to_unix(from)}}}, + {:<, :"$2", {:const, DateTime.to_unix(to)}}} + ], [:"$_"]} + ] + res = :dets.select(dets, select) + :dets.close(dets) + res + end def init(_) do {:ok, _} = Registry.register(IRC.PubSub, "account", []) @@ -32,21 +51,42 @@ defmodule LSG.IRC.AlcoologAnnouncerPlugin do Process.send_after(self(), :stats, :timer.seconds(30)) dets_filename = (LSG.data_path() <> "/" <> "alcoologlog.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, [{:type,:bag}]) + ets = nil # :ets.new(__MODULE__.ETS, [:ordered_set, :named_table, :protected, {:read_concurrency, true}]) #:ok = LSG.IRC.SettingPlugin.declare("alcoolog.alerts", __MODULE__, true, :boolean) #:ok = LSG.IRC.SettingPlugin.declare("alcoolog.aperoalert", __MODULE__, true, :boolean) - {:ok, {stats, now(), dets}} + # + {:ok, {stats, now(), dets, ets}}#, {:continue, :traverse}} + end + + def handle_continue(:traverse, state = {_, _, dets, ets}) do + traverse_fun = fn(obj, dets) -> + case obj do + {nick, %DateTime{} = dt, active} -> + :dets.delete_object(dets, obj) + :dets.insert(dets, {nick, DateTime.to_unix(dt), active}) + IO.puts("ok #{inspect obj}") + dets + {nick, ts, value} -> + :ets.insert(ets, { {nick, ts}, value }) + dets + end + end + :dets.foldl(traverse_fun, dets, dets) + :dets.sync(dets) + IO.puts("alcoolog announcer fixed") + {:noreply, state} end def alcohol_reached(old, new, level) do (old.active < level && new.active >= level) && (new.active5m >= level) end - + def alcohol_below(old, new, level) do (old.active > level && new.active <= level) && (new.active5m <= level) end - def handle_info(:stats, {old_stats, old_now, dets}) do + def handle_info(:stats, {old_stats, old_now, dets, ets}) do stats = get_stats() now = now() @@ -73,10 +113,13 @@ defmodule LSG.IRC.AlcoologAnnouncerPlugin do new = Map.get(stats, acct, nil) #IO.puts "#{acct}: #{inspect(old)} -> #{inspect(new)}" + now = DateTime.to_unix(DateTime.utc_now()) if new && new[:active] do - :dets.insert(dets, {acct, DateTime.utc_now(), new[:active]}) + :dets.insert(dets, {acct, now, new[:active]}) + :ets.insert(ets, {{acct, now}, new[:active]}) else - :dets.insert(dets, {acct, DateTime.utc_now(), 0.0}) + :dets.insert(dets, {acct, now, 0.0}) + :ets.insert(ets, {{acct, now}, new[:active]}) end event = cond do @@ -206,7 +249,7 @@ defmodule LSG.IRC.AlcoologAnnouncerPlugin do timer() #IO.puts "tick stats ok" - {:noreply, {stats,now,dets}} + {:noreply, {stats,now,dets,ets}} end def handle_info(_, state) do diff --git a/lib/lsg_irc/base_plugin.ex b/lib/lsg_irc/base_plugin.ex index 69b02e8..a95f45c 100644 --- a/lib/lsg_irc/base_plugin.ex +++ b/lib/lsg_irc/base_plugin.ex @@ -3,14 +3,15 @@ defmodule LSG.IRC.BasePlugin do def irc_doc, do: nil def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:version", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:help", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:liquidrender", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:plugin", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:version", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:help", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:liquidrender", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:plugin", regopts) {:ok, nil} end @@ -105,4 +106,3 @@ defmodule LSG.IRC.BasePlugin do end end - diff --git a/lib/lsg_irc/bourosama_plugin.ex b/lib/lsg_irc/bourosama_plugin.ex index 7dde662..ba63d81 100644 --- a/lib/lsg_irc/bourosama_plugin.ex +++ b/lib/lsg_irc/bourosama_plugin.ex @@ -13,14 +13,15 @@ defmodule LSG.IRC.BoursoramaPlugin do end def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end @cac40_url "https://www.boursorama.com/bourse/actions/palmares/france/?france_filter%5Bmarket%5D=1rPCAC&france_filter%5Bsector%5D=&france_filter%5Bvariation%5D=50002&france_filter%5Bperiod%5D=1&france_filter%5Bfilter%5D=" def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:cac40", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:caca40", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:cac40", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:caca40", regopts) {:ok, nil} end diff --git a/lib/lsg_irc/calc_plugin.ex b/lib/lsg_irc/calc_plugin.ex index b8eee39..ca65675 100644 --- a/lib/lsg_irc/calc_plugin.ex +++ b/lib/lsg_irc/calc_plugin.ex @@ -8,11 +8,11 @@ defmodule LSG.IRC.CalcPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:calc", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:calc", [plugin: __MODULE__]) {:ok, nil} end @@ -35,4 +35,3 @@ defmodule LSG.IRC.CalcPlugin do end end - diff --git a/lib/lsg_irc/coronavirus_plugin.ex b/lib/lsg_irc/coronavirus_plugin.ex index 8038d14..b9a9e40 100644 --- a/lib/lsg_irc/coronavirus_plugin.ex +++ b/lib/lsg_irc/coronavirus_plugin.ex @@ -14,15 +14,22 @@ defmodule LSG.IRC.CoronavirusPlugin do """ def irc_doc, do: @moduledoc - def start_link(), do: GenServer.start_link(__MODULE__, []) + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:coronavirus", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:coronavirus", [plugin: __MODULE__]) + {:ok, nil, {:continue, :init}} + :ignore + end + + def handle_continue(:init, _) do date = Date.add(Date.utc_today(), -2) {data, _} = fetch_data(%{}, date) {data, next} = fetch_data(data) :timer.send_after(next, :update) - {:ok, %{data: data}} + {:noreply, %{data: data}} end def handle_info(:update, state) do @@ -163,4 +170,3 @@ defmodule LSG.IRC.CoronavirusPlugin do end end - diff --git a/lib/lsg_irc/correction_plugin.ex b/lib/lsg_irc/correction_plugin.ex index e7b2577..f370cf8 100644 --- a/lib/lsg_irc/correction_plugin.ex +++ b/lib/lsg_irc/correction_plugin.ex @@ -7,12 +7,12 @@ defmodule LSG.IRC.CorrectionPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "message", []) - {:ok, _} = Registry.register(IRC.PubSub, "triggers", []) + {:ok, _} = Registry.register(IRC.PubSub, "message", [plugin: __MODULE__]) + {:ok, _} = Registry.register(IRC.PubSub, "triggers", [plugin: __MODULE__]) {:ok, %{}} end @@ -25,7 +25,7 @@ defmodule LSG.IRC.CorrectionPlugin do {:noreply, correction(m, state)} end - defp correction(m, state) do + def correction(m, state) do history = Map.get(state, key(m), []) if String.starts_with?(m.text, "s/") do case String.split(m.text, "/") do diff --git a/lib/lsg_irc/dice_plugin.ex b/lib/lsg_irc/dice_plugin.ex index a507b8e..eafd88a 100644 --- a/lib/lsg_irc/dice_plugin.ex +++ b/lib/lsg_irc/dice_plugin.ex @@ -17,11 +17,11 @@ defmodule LSG.IRC.DicePlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:dice", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:dice", [plugin: __MODULE__]) {:ok, %__MODULE__{}} end diff --git a/lib/lsg_irc/finance_plugin.ex b/lib/lsg_irc/finance_plugin.ex index 7266a5e..51ec3f6 100644 --- a/lib/lsg_irc/finance_plugin.ex +++ b/lib/lsg_irc/finance_plugin.ex @@ -23,8 +23,8 @@ defmodule LSG.IRC.FinancePlugin do """ - @currency_list "https://www.alphavantage.co/physical_currency_list/" - @crypto_list "https://www.alphavantage.co/digital_currency_list/" + @currency_list "http://www.alphavantage.co/physical_currency_list/" + @crypto_list "http://www.alphavantage.co/digital_currency_list/" HTTPoison.start() load_currency = fn(url) -> @@ -47,13 +47,14 @@ defmodule LSG.IRC.FinancePlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:forex", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:currency", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:stocks", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:forex", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:currency", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:stocks", regopts) {:ok, nil} end diff --git a/lib/lsg_irc/kick_roulette_plugin.ex b/lib/lsg_irc/kick_roulette_plugin.ex index 83efcb0..f810a74 100644 --- a/lib/lsg_irc/kick_roulette_plugin.ex +++ b/lib/lsg_irc/kick_roulette_plugin.ex @@ -7,11 +7,11 @@ defmodule LSG.IRC.KickRoulettePlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:kick", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:kick", [plugin: __MODULE__]) {:ok, nil} end diff --git a/lib/lsg_irc/last_fm_plugin.ex b/lib/lsg_irc/last_fm_plugin.ex index 067a44e..8497f2d 100644 --- a/lib/lsg_irc/last_fm_plugin.ex +++ b/lib/lsg_irc/last_fm_plugin.ex @@ -14,13 +14,14 @@ defmodule LSG.IRC.LastFmPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "account", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:lastfm", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:lastfmall", []) + regopts = [type: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:lastfm", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:lastfmall", regopts) dets_filename = (LSG.data_path() <> "/" <> "lastfm.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, []) {:ok, %__MODULE__{dets: dets}} diff --git a/lib/lsg_irc/link_plugin.ex b/lib/lsg_irc/link_plugin.ex index ea6df0c..ced80b2 100644 --- a/lib/lsg_irc/link_plugin.ex +++ b/lib/lsg_irc/link_plugin.ex @@ -52,8 +52,8 @@ defmodule LSG.IRC.LinkPlugin do defstruct [:client] def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "message", []) - #{:ok, _} = Registry.register(IRC.PubSub, "message:telegram", []) + {:ok, _} = Registry.register(IRC.PubSub, "message", [plugin: __MODULE__]) + #{:ok, _} = Registry.register(IRC.PubSub, "message:telegram", [plugin: __MODULE__]) Logger.info("Link handler started") {:ok, %__MODULE__{}} end @@ -159,7 +159,7 @@ defmodule LSG.IRC.LinkPlugin do {length, _} = Integer.parse(length) handlers = Keyword.get(Application.get_env(:lsg, __MODULE__, [handlers: []]), :handlers) - handler = Enum.reduce_while(handlers, nil, fn({module, opts}, acc) -> + handler = Enum.reduce_while(handlers, false, fn({module, opts}, acc) -> module = Module.concat([module]) try do case module.post_match(url, content_type, headers, opts) do @@ -225,8 +225,9 @@ defmodule LSG.IRC.LinkPlugin do end end - defp get_body(_, len, client, _, _acc) do + defp get_body(_, len, client, h, _acc) do :hackney.close(client) + IO.inspect(h) {:ok, "Error: file over 30"} end @@ -243,6 +244,8 @@ defmodule LSG.IRC.LinkPlugin do {:error, status, _headers} -> text = Plug.Conn.Status.reason_phrase(status) {:ok, acc, "Error: HTTP #{text} (#{status})"} + {:error, {:tls_alert, {:handshake_failure, err}}} -> + {:ok, acc, "TLS Error: #{to_string(err)}"} {:error, reason} -> {:ok, acc, "Error: #{to_string(reason)}"} end diff --git a/lib/lsg_irc/np_handler.ex b/lib/lsg_irc/np_handler.ex index 5f724d4..1a52c75 100644 --- a/lib/lsg_irc/np_handler.ex +++ b/lib/lsg_irc/np_handler.ex @@ -8,12 +8,12 @@ defmodule LSG.IRC.NpHandler do 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]) + GenServer.start_link(__MODULE__, [client], name: __MODULE__) end def init([client]) do ExIRC.Client.add_handler client, self - {:ok, _} = Registry.register(LSG.BroadcastRegistry, "icecast", []) + {:ok, _} = Registry.register(LSG.BroadcastRegistry, "icecast", [plugin: __MODULE__]) {:ok, client} end diff --git a/lib/lsg_irc/outline_plugin.ex b/lib/lsg_irc/outline_plugin.ex index 7bfaac1..471448b 100644 --- a/lib/lsg_irc/outline_plugin.ex +++ b/lib/lsg_irc/outline_plugin.ex @@ -12,14 +12,15 @@ defmodule LSG.IRC.OutlinePlugin do require Logger def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end defstruct [:file, :hosts] def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:outline", []) - {:ok, _} = Registry.register(IRC.PubSub, "message", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:outline", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) file = Path.join(LSG.data_path, "/outline.txt") hosts = case File.read(file) do {:error, :enoent} -> @@ -51,7 +52,8 @@ defmodule LSG.IRC.OutlinePlugin do uri = URI.parse(word) if uri.scheme && uri.host do if Enum.any?(state.hosts, fn(host) -> String.ends_with?(uri.host, host) end) do - line = "-> https://outline.com/#{word}" + outline_url = outline(word) + line = "-> #{outline(word)}" message.replyfun.(line) end end @@ -69,4 +71,32 @@ defmodule LSG.IRC.OutlinePlugin do File.write(file, string) end + def outline(url) do + unexpanded = "https://outline.com/#{url}" + headers = [ + {"User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0"}, + {"Accept", "*/*"}, + {"Accept-Language", "en-US,en;q=0.5"}, + {"Origin", "https://outline.com"}, + {"DNT", "1"}, + {"Referer", unexpanded}, + {"Pragma", "no-cache"}, + {"Cache-Control", "no-cache"} + ] + params = %{"source_url" => url} + case HTTPoison.get("https://api.outline.com/v3/parse_article", headers, params: params) do + {:ok, %HTTPoison.Response{status_code: 200, body: json}} -> + body = Poison.decode!(json) + if Map.get(body, "success") do + code = get_in(body, ["data", "short_code"]) + "https://outline.com/#{code}" + else + unexpanded + end + error -> + Logger.info("outline.com error: #{inspect error}") + unexpanded + end + end + end diff --git a/lib/lsg_irc/preums_plugin.ex b/lib/lsg_irc/preums_plugin.ex index 1f9a76b..7bb2c78 100644 --- a/lib/lsg_irc/preums_plugin.ex +++ b/lib/lsg_irc/preums_plugin.ex @@ -6,6 +6,35 @@ defmodule LSG.IRC.PreumsPlugin do * `.preums`: stats des preums """ + # WIP Scores + # L'idée c'est de donner un score pour mettre un peu de challenge en pénalisant les preums faciles. + # + # Un preums ne vaut pas 1 point, mais plutôt 0.10 ou 0.05, et on arrondi au plus proche. C'est un jeu sur le long + # terme. Un gros bonus pourrait apporter beaucoup de points. + # + # Il faudrait ces données: + # - moyenne des preums + # - activité récente du channel et par nb actifs d'utilisateurs + # (aggréger memberships+usertrack last_active ?) + # (faire des stats d'activité habituelle (un peu a la pisg) ?) + # - preums consécutifs + # + # Malus: + # - est proche de la moyenne en faible activité + # - trop consécutif de l'utilisateur sauf si activité + # + # Bonus: + # - plus le preums est éloigné de la moyenne + # - après 18h double + # - plus l'activité est élévée, exponentiel selon la moyenne + # - derns entre 4 et 6 (pourrait être adapté selon les stats d'activité) + # + # WIP Badges: + # - derns + # - streaks + # - faciles + # - ? + require Logger @perfects [~r/preum(s|)/i] @@ -17,9 +46,9 @@ defmodule LSG.IRC.PreumsPlugin do end def all(dets, channel) do - fun = fn({{chan, date}, nick, time, perfect, text}, acc) -> + fun = fn({{chan, date}, account_id, time, perfect, text}, acc) -> if channel == chan do - [%{date: date, nick: nick, time: time, perfect: perfect, text: text} | acc] + [%{date: date, account_id: account_id, time: time, perfect: perfect, text: text} | acc] else acc end @@ -27,22 +56,28 @@ defmodule LSG.IRC.PreumsPlugin do :dets.foldl(fun, [], dets) end - def topnicks(dets, channel) do - fun = fn(x = {{chan, date}, nick, _time, _perfect, _text}, acc) -> + def topnicks(dets, channel, options \\ []) do + sort_elem = case Keyword.get(options, :sort_by, :score) do + :score -> 1 + :count -> 0 + end + + fun = fn(x = {{chan, date}, account_id, time, perfect, text}, acc) -> if (channel == nil and chan) or (channel == chan) do - count = Map.get(acc, nick, 0) - Map.put(acc, nick, count + 1) + {count, points} = Map.get(acc, account_id, {0, 0}) + score = score(chan, account_id, time, perfect, text) + Map.put(acc, account_id, {count + 1, points + score}) else acc end end :dets.foldl(fun, %{}, dets) - |> Enum.sort_by(fn({nick, count}) -> count end, &>=/2) + |> Enum.sort_by(fn({_account_id, value}) -> elem(value, sort_elem) end, &>=/2) end def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def dets do @@ -50,9 +85,10 @@ defmodule LSG.IRC.PreumsPlugin do end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "account", []) - {:ok, _} = Registry.register(IRC.PubSub, "message", []) - {:ok, _} = Registry.register(IRC.PubSub, "triggers", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "triggers", regopts) {:ok, dets} = :dets.open_file(dets(), [{:repair, :force}]) Util.ets_mutate_select_each(:dets, dets, [{:"$1", [], [:"$1"]}], fn(table, obj) -> {key, nick, now, perfect, text} = obj @@ -98,14 +134,17 @@ defmodule LSG.IRC.PreumsPlugin do i else case :dets.lookup(state.dets, key) do - [item = {^key, _nick, _now, _perfect, _text}] -> item + [item = {^key, _account_id, _now, _perfect, _text}] -> item _ -> nil end end if item do - {_, nick, date, _perfect, text} = item + {_, account_id, date, _perfect, text} = item h = "#{date.hour}:#{date.minute}:#{date.second}" + account = IRC.Account.get(account_id) + user = IRC.UserTrack.find_by_account(m.network, account) + nick = if(user, do: user.nick, else: account.name) m.replyfun.("preums: #{nick} à #{h}: “#{text}”") end {:noreply, state} @@ -115,11 +154,13 @@ defmodule LSG.IRC.PreumsPlugin do def handle_info({:irc, :trigger, "preums", m = %IRC.Message{trigger: %IRC.Trigger{type: :dot}}}, state) do channel = {m.network, m.channel} state = handle_preums(m, state) - top = topnicks(state.dets, channel) - |> Enum.map(fn({nick, count}) -> - "#{nick} (#{count})" + top = topnicks(state.dets, channel, sort_by: :score) + |> Enum.map(fn({account_id, {count, score}}) -> + account = IRC.Account.get(account_id) + user = IRC.UserTrack.find_by_account(m.network, account) + nick = if(user, do: user.nick, else: account.name) + "#{nick}: #{score} (#{count})" end) - |> Enum.filter(fn(x) -> x end) |> Enum.intersperse(", ") |> Enum.join("") msg = unless top == "" do @@ -227,4 +268,9 @@ defmodule LSG.IRC.PreumsPlugin do end end + def score(_chan, _account, _time, _perfect, _text) do + 1 + end + + end diff --git a/lib/lsg_irc/quatre_cent_vingt_plugin.ex b/lib/lsg_irc/quatre_cent_vingt_plugin.ex index db85d49..fff7e4f 100644 --- a/lib/lsg_irc/quatre_cent_vingt_plugin.ex +++ b/lib/lsg_irc/quatre_cent_vingt_plugin.ex @@ -30,16 +30,17 @@ defmodule LSG.IRC.QuatreCentVingtPlugin do def irc_doc, do: @moduledoc - def start_link, do: GenServer.start_link(__MODULE__, []) + def start_link, do: GenServer.start_link(__MODULE__, [], name: __MODULE__) def init(_) do for coeff <- @coeffs do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:#{420*coeff}", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:#{420*coeff}", [plugin: __MODULE__]) end - {:ok, _} = Registry.register(IRC.PubSub, "account", []) + {:ok, _} = Registry.register(IRC.PubSub, "account", [plugin: __MODULE__]) dets_filename = (LSG.data_path() <> "/420.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, [{:type,:bag},{:repair,:force}]) {:ok, dets} + :ignore end for coeff <- @coeffs do diff --git a/lib/lsg_irc/say_plugin.ex b/lib/lsg_irc/say_plugin.ex index 6a4f547..690d0a6 100644 --- a/lib/lsg_irc/say_plugin.ex +++ b/lib/lsg_irc/say_plugin.ex @@ -14,13 +14,14 @@ defmodule LSG.IRC.SayPlugin do end def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:say", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:asay", []) - {:ok, _} = Registry.register(IRC.PubSub, "message:private", []) + regopts = [type: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "trigger:say", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:asay", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "message:private", regopts) {:ok, nil} end diff --git a/lib/lsg_irc/script_plugin.ex b/lib/lsg_irc/script_plugin.ex index 28ae2a7..bae6f3f 100644 --- a/lib/lsg_irc/script_plugin.ex +++ b/lib/lsg_irc/script_plugin.ex @@ -19,11 +19,11 @@ defmodule LSG.IRC.ScriptPlugin do def irc_doc, do: @ircdoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:script", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:script", [plugin: __MODULE__]) dets_filename = (LSG.data_path() <> "/" <> "scripts.dets") |> String.to_charlist {:ok, dets} = :dets.open_file(dets_filename, []) {:ok, %{dets: dets}} @@ -40,4 +40,3 @@ defmodule LSG.IRC.ScriptPlugin do end end - diff --git a/lib/lsg_irc/seen_plugin.ex b/lib/lsg_irc/seen_plugin.ex index 8b2178f..f1a5473 100644 --- a/lib/lsg_irc/seen_plugin.ex +++ b/lib/lsg_irc/seen_plugin.ex @@ -7,12 +7,13 @@ defmodule LSG.IRC.SeenPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "triggers", []) - {:ok, _} = Registry.register(IRC.PubSub, "message", []) + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "triggers", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) dets_filename = (LSG.data_path() <> "/seen.dets") |> String.to_charlist() {:ok, dets} = :dets.open_file(dets_filename, []) {:ok, %{dets: dets}} diff --git a/lib/lsg_irc/sms_plugin.ex b/lib/lsg_irc/sms_plugin.ex index 60554fb..a37fb4e 100644 --- a/lib/lsg_irc/sms_plugin.ex +++ b/lib/lsg_irc/sms_plugin.ex @@ -54,7 +54,7 @@ defmodule LSG.IRC.SmsPlugin do end def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def path() do @@ -88,9 +88,10 @@ defmodule LSG.IRC.SmsPlugin do end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:sms", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:sms", [plugin: __MODULE__]) :ok = register_ovh_callback() {:ok, %{}} + :ignore end def handle_info({:irc, :trigger, "sms", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [nick | text]}}}, state) do diff --git a/lib/lsg_irc/tell_plugin.ex b/lib/lsg_irc/tell_plugin.ex new file mode 100644 index 0000000..a683b43 --- /dev/null +++ b/lib/lsg_irc/tell_plugin.ex @@ -0,0 +1,93 @@ +defmodule LSG.IRC.TellPlugin do + use GenServer + + @moduledoc """ + # Tell + + * **!tell `<nick>` `<message>`**: tell `message` to `nick` when they reconnect. + """ + + def irc_doc, do: @moduledoc + def start_link() do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + def dets do + (LSG.data_path() <> "/tell.dets") |> String.to_charlist() + end + + def init([]) do + regopts = [plugin: __MODULE__] + {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:tell", regopts) + {:ok, dets} = :dets.open_file(dets(), [type: :bag]) + {:ok, %{dets: dets}} + end + + def handle_info({:irc, :trigger, "tell", m = %IRC.Message{trigger: %IRC.Trigger{type: :bang, args: [nick_target | message]}}}, state) do + target = IRC.Account.find_always_by_nick(m.network, m.channel, nick_target) + message = Enum.join(message, " ") + with \ + {:target, %IRC.Account{} = target} <- {:target, target}, + {:same, false} <- {:same, target.id == m.account.id}, + target_user = IRC.UserTrack.find_by_account(m.network, target), + target_nick = if(target_user, do: target_user.nick, else: target.name), + present? = if(target_user, do: Map.has_key?(target_user.last_active, m.channel)), + {:absent, true, _} <- {:absent, !present?, target_nick}, + {:message, message} <- {:message, message} + do + obj = { {m.network, m.channel, target.id}, m.account.id, message, NaiveDateTime.utc_now()} + :dets.insert(state.dets, obj) + m.replyfun.("will tell to #{target_nick}") + else + {:same, _} -> m.replyfun.("are you so stupid that you need a bot to tell yourself things ?") + {:target, _} -> m.replyfun.("#{nick_target} unknown") + {:absent, _, nick} -> m.replyfun.("#{nick} is here, tell yourself!") + {:message, _} -> m.replyfun.("can't tell without a message") + end + {:noreply, state} + end + + def handle_info({:account, network, channel, nick, account_id}, state) do + messages = :dets.lookup(state.dets, {network, channel, account_id}) + if messages != [] do + strs = Enum.map(messages, fn({_, from, message, at}) -> + account = IRC.Account.get(from) + user = IRC.UserTrack.find_by_account(network, account) + fromnick = if user, do: user.nick, else: account.name + "#{nick}: <#{fromnick}> #{message}" + end) + Enum.each(strs, fn(s) -> IRC.Connection.broadcast_message(network, channel, s) end) + :dets.delete(state.dets, {network, channel, account_id}) + end + {:noreply, state} + end + + def handle_info({:account_change, old_id, new_id}, state) do + #:ets.fun2ms(fn({ {_net, _chan, target_id}, from_id, _, _} = obj) when (target_id == old_id) or (from_id == old_id) -> obj end) + spec = [{{{:"$1", :"$2", :"$3"}, :"$4", :_, :_}, [{:orelse, {:==, :"$3", {:const, old_id}}, {:==, :"$4", {:const, old_id}}}], [:"$_"]}] + Util.Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj) -> + case obj do + { {net, chan, ^old_id}, from_id, message, at } = obj -> + :dets.delete(obj) + :dets.insert(table, {{net, chan, new_id}, from_id, message, at}) + {key, ^old_id, message, at} = obj -> + :dets.delete(table, obj) + :dets.insert(table, {key, new_id, message, at}) + _ -> :ok + end + end) + {:noreply, state} + end + + + def handle_info(info, state) do + {:noreply, state} + end + + def terminate(_, state) do + :dets.close(state.dets) + :ok + end + +end diff --git a/lib/lsg_irc/txt_plugin.ex b/lib/lsg_irc/txt_plugin.ex index f8c3a29..0f97bcc 100644 --- a/lib/lsg_irc/txt_plugin.ex +++ b/lib/lsg_irc/txt_plugin.ex @@ -33,7 +33,7 @@ defmodule LSG.IRC.TxtPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end defstruct triggers: %{}, rw: true, locks: nil, markov_handler: nil, markov: nil @@ -43,7 +43,7 @@ defmodule LSG.IRC.TxtPlugin do {:ok, locks} = :dets.open_file(dets_locks_filename, []) markov_handler = Keyword.get(Application.get_env(:lsg, __MODULE__, []), :markov_handler, LSG.IRC.TxtPlugin.Markov.Native) {:ok, markov} = markov_handler.start_link() - {:ok, _} = Registry.register(IRC.PubSub, "triggers", []) + {:ok, _} = Registry.register(IRC.PubSub, "triggers", [plugin: __MODULE__]) {:ok, %__MODULE__{locks: locks, markov_handler: markov_handler, markov: markov, triggers: load()}} end @@ -147,13 +147,21 @@ defmodule LSG.IRC.TxtPlugin do def handle_info({:irc, :trigger, "txt", msg = %{trigger: %{type: :bang, args: args}}}, state) do grep = Enum.join(args, " ") - result = with_stateful_results(msg, {:bang,"txt",grep}, fn() -> + |> String.downcase + |> :unicode.characters_to_nfd_binary() + + result = with_stateful_results(msg, {:bang,"txt",msg.network,msg.channel,grep}, fn() -> Enum.reduce(state.triggers, [], fn({trigger, data}, acc) -> Enum.reduce(data, acc, fn({l, _}, acc) -> [{trigger, l} | acc] end) end) - |> Enum.filter(fn({_, line}) -> String.contains?(String.downcase(line), String.downcase(grep)) end) + |> Enum.filter(fn({_, line}) -> + line + |> String.downcase() + |> :unicode.characters_to_nfd_binary() + |> String.contains?(grep) + end) |> Enum.shuffle() end) diff --git a/lib/lsg_irc/untappd_plugin.ex b/lib/lsg_irc/untappd_plugin.ex index 02b22e3..69e4be6 100644 --- a/lib/lsg_irc/untappd_plugin.ex +++ b/lib/lsg_irc/untappd_plugin.ex @@ -18,7 +18,7 @@ defmodule LSG.IRC.UntappdPlugin do end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:beer", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:beer", [plugin: __MODULE__]) {:ok, %{}} end diff --git a/lib/lsg_irc/wikipedia_plugin.ex b/lib/lsg_irc/wikipedia_plugin.ex index 16e7c42..618eb66 100644 --- a/lib/lsg_irc/wikipedia_plugin.ex +++ b/lib/lsg_irc/wikipedia_plugin.ex @@ -11,11 +11,11 @@ defmodule LSG.IRC.WikipediaPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:wp", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:wp", [plugin: __MODULE__]) {:ok, nil} end diff --git a/lib/lsg_irc/wolfram_alpha_plugin.ex b/lib/lsg_irc/wolfram_alpha_plugin.ex index b34db56..c07f659 100644 --- a/lib/lsg_irc/wolfram_alpha_plugin.ex +++ b/lib/lsg_irc/wolfram_alpha_plugin.ex @@ -11,11 +11,11 @@ defmodule LSG.IRC.WolframAlphaPlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:wa", []) + {:ok, _} = Registry.register(IRC.PubSub, "trigger:wa", [plugin: __MODULE__]) {:ok, nil} end diff --git a/lib/lsg_irc/youtube_plugin.ex b/lib/lsg_irc/youtube_plugin.ex index e0781f0..49fc31c 100644 --- a/lib/lsg_irc/youtube_plugin.ex +++ b/lib/lsg_irc/youtube_plugin.ex @@ -12,12 +12,11 @@ defmodule LSG.IRC.YouTubePlugin do def irc_doc, do: @moduledoc def start_link() do - GenServer.start_link(__MODULE__, []) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do - {:ok, _} = Registry.register(IRC.PubSub, "trigger:yt", []) - {:ok, _} = Registry.register(IRC.PubSub, "trigger:youtube", []) + for t <- ["trigger:yt", "trigger:youtube"], do: {:ok, _} = Registry.register(IRC.PubSub, t, [plugin: __MODULE__]) {:ok, %__MODULE__{}} end diff --git a/lib/lsg_web.ex b/lib/lsg_web.ex index f5dee6f..113d00d 100644 --- a/lib/lsg_web.ex +++ b/lib/lsg_web.ex @@ -17,6 +17,10 @@ defmodule LSGWeb do and import those modules here. """ + def format_chan("##") do + "♯♯" + end + def format_chan("#") do "♯" end @@ -26,7 +30,10 @@ defmodule LSGWeb do end def reformat_chan("♯") do - "" + "#" + end + def reformat_chan("♯♯") do + "##" end def reformat_chan(chan) do diff --git a/lib/lsg_web/controllers/alcoolog_controller.ex b/lib/lsg_web/controllers/alcoolog_controller.ex index b88faa3..6542f15 100644 --- a/lib/lsg_web/controllers/alcoolog_controller.ex +++ b/lib/lsg_web/controllers/alcoolog_controller.ex @@ -16,6 +16,166 @@ defmodule LSGWeb.AlcoologController do end end + def nick(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + days = String.to_integer(Map.get(params, "days", "180")) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + if friend? do + stats = LSG.IRC.AlcoologPlugin.get_full_statistics(profile_account.id) + history = for {{nick, ts}, points, active, cl, deg, type, descr, meta} <- LSG.IRC.AlcoologPlugin.nick_history(profile_account) do + %{ + at: ts |> DateTime.from_unix!(:millisecond), + points: points, + active: active, + cl: cl, + deg: deg, + type: type, + description: descr, + meta: meta + } + end + history = Enum.sort(history, &(DateTime.compare(&1.at, &2.at) != :lt)) + |> IO.inspect() + conn + |> assign(:title, "alcoolog #{nick}") + |> render("user.html", network: network, profile: profile_account, days: days, nick: nick, history: history, stats: stats) + else + conn + |> put_status(404) + |> text("Page not found") + end + end + + def nick_stats_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + if friend? do + stats = LSG.IRC.AlcoologPlugin.get_full_statistics(profile_account.id) + + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(stats)) + else + conn + |> put_status(404) + |> json([]) + end + end + + def nick_gls_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + count = String.to_integer(Map.get(params, "days", "180")) + if friend? do + data = LSG.IRC.AlcoologPlugin.user_over_time_gl(profile_account, count) + delay = count*((24 * 60)*60) + now = DateTime.utc_now() + start_date = DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) + |> DateTime.to_date() + |> Date.to_erl() + filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl)) + |> Enum.to_list + |> Enum.map(&(:calendar.gregorian_days_to_date(&1))) + |> Enum.map(&Date.from_erl!(&1)) + |> Enum.map(fn(date) -> + %{date: date, gls: Map.get(data, date, 0)} + end) + |> Enum.sort(&(Date.compare(&1.date, &2.date) != :gt)) + + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(filled)) + else + conn + |> put_status(404) + |> json([]) + end + end + + + + def nick_volumes_json(conn = %{assigns: %{account: account}}, params = %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + count = String.to_integer(Map.get(params, "days", "180")) + if friend? do + data = LSG.IRC.AlcoologPlugin.user_over_time(profile_account, count) + delay = count*((24 * 60)*60) + now = DateTime.utc_now() + start_date = DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) + |> DateTime.to_date() + |> Date.to_erl() + filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl)) + |> Enum.to_list + |> Enum.map(&(:calendar.gregorian_days_to_date(&1))) + |> Enum.map(&Date.from_erl!(&1)) + |> Enum.map(fn(date) -> + %{date: date, volumes: Map.get(data, date, 0)} + end) + |> Enum.sort(&(Date.compare(&1.date, &2.date) != :gt)) + + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(filled)) + else + conn + |> put_status(404) + |> json([]) + end + end + + def nick_log_json(conn = %{assigns: %{account: account}}, %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + if friend? do + history = for {{nick, ts}, points, active, cl, deg, type, descr, meta} <- LSG.IRC.AlcoologPlugin.nick_history(profile_account) do + %{ + at: ts |> DateTime.from_unix!(:millisecond) |> DateTime.to_iso8601(), + points: points, + active: active, + cl: cl, + deg: deg, + type: type, + description: descr, + meta: meta + } + end + last = List.last(history) + {_, active} = LSG.IRC.AlcoologPlugin.user_stats(profile_account) + last = %{last | active: active, at: DateTime.utc_now() |> DateTime.to_iso8601()} + history = history ++ [last] + + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(history)) + else + conn + |> put_status(404) + |> json([]) + end + end + + def nick_history_json(conn = %{assigns: %{account: account}}, %{"network" => network, "nick" => nick}) do + profile_account = IRC.Account.find_always_by_nick(network, nick, nick) + friend? = Enum.member?(IRC.Membership.friends(account), profile_account.id) + if friend? do + history = for {_, date, value} <- LSG.IRC.AlcoologAnnouncerPlugin.log(profile_account) do + %{date: DateTime.to_iso8601(date), value: value} + end + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(history)) + else + conn + |> put_status(404) + |> json([]) + end + end + def index(conn = %{assigns: %{account: account}}, %{"network" => network, "chan" => channel}) do index(conn, account, network, LSGWeb.reformat_chan(channel)) end @@ -40,16 +200,22 @@ defmodule LSGWeb.AlcoologController do #end def index(conn, account, network, channel) do - aday = 18*((24 * 60)*60) + aday = ((24 * 60)*60) now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) + before7 = now + |> DateTime.add(-(7*aday), :second) + |> DateTime.to_unix(:millisecond) + before15 = now + |> DateTime.add(-(15*aday), :second) + |> DateTime.to_unix(:millisecond) + before31 = now + |> DateTime.add(-(31*aday), :second) + |> DateTime.to_unix(:millisecond) #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) match = [ - {{{:_, :"$1"}, :_, :_, :_, :_}, + {{{:_, :"$1"}, :_, :_, :_, :_, :_, :_, :_}, [ - {:>, :"$1", {:const, before}}, + {:>, :"$1", {:const, before15}}, ], [:"$_"]} ] @@ -58,13 +224,13 @@ defmodule LSGWeb.AlcoologController do members_ids = Enum.map(members, fn({account, _, nick}) -> account.id end) member_names = Enum.reduce(members, %{}, fn({account, _, nick}, acc) -> Map.put(acc, account.id, nick) end) drinks = :ets.select(LSG.IRC.AlcoologPlugin.ETS, match) - |> Enum.filter(fn({{account, _}, _, _, _, _}) -> Enum.member?(members_ids, account) end) - |> Enum.map(fn({{account, _}, _, _, _, _} = object) -> {object, Map.get(member_names, account)} end) - |> Enum.sort_by(fn({{{_, ts}, _, _, _, _}, _}) -> ts end, &>/2) + |> Enum.filter(fn({{account, _}, _vol, _cur, _cl, _deg, _name, _cmt, _meta}) -> Enum.member?(members_ids, account) end) + |> Enum.map(fn({{account, _}, _, _, _, _, _, _, _} = object) -> {object, Map.get(member_names, account)} end) + |> Enum.sort_by(fn({{{_, ts}, _, _, _, _, _, _, _}, _}) -> ts end, &>/2) stats = LSG.IRC.AlcoologPlugin.get_channel_statistics(account, network, channel) - top = Enum.reduce(drinks, %{}, fn({{{account_id, _}, vol, _, _, _}, _}, acc) -> + top = Enum.reduce(drinks, %{}, fn({{{account_id, _}, vol, _, _, _, _, _, _}, _}, acc) -> nick = Map.get(member_names, account_id) all = Map.get(acc, nick, 0) Map.put(acc, nick, all + vol) @@ -77,4 +243,81 @@ defmodule LSGWeb.AlcoologController do |> render("index.html", network: network, channel: channel, drinks: drinks, top: top, stats: stats) end + def index_gls_json(conn = %{assigns: %{account: account}}, %{"network" => network, "chan" => channel}) do + count = 30 + channel = LSGWeb.reformat_chan(channel) + members = IRC.Membership.expanded_members_or_friends(account, network, channel) + members_ids = Enum.map(members, fn({account, _, nick}) -> account.id end) + member_names = Enum.reduce(members, %{}, fn({account, _, nick}, acc) -> Map.put(acc, account.id, nick) end) + delay = count*((24 * 60)*60) + now = DateTime.utc_now() + start_date = DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) + |> DateTime.to_date() + |> Date.to_erl() + filled = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl)) + |> Enum.to_list + |> Enum.map(&(:calendar.gregorian_days_to_date(&1))) + |> Enum.map(&Date.from_erl!(&1)) + |> Enum.map(fn(date) -> + {date, (for {a, _, _} <- members, into: Map.new, do: {Map.get(member_names, a.id, a.id), 0})} + end) + |> Enum.into(Map.new) + + gls = Enum.reduce(members, filled, fn({account, _, _}, gls) -> + Enum.reduce(LSG.IRC.AlcoologPlugin.user_over_time_gl(account, count), gls, fn({date, gl}, gls) -> + u = Map.get(gls, date, %{}) + |> Map.put(Map.get(member_names, account.id, account.id), gl) + Map.put(gls, date, u) + end) + end) + + dates = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl)) + |> Enum.to_list + |> Enum.map(&(:calendar.gregorian_days_to_date(&1))) + |> Enum.map(&Date.from_erl!(&1)) + + filled2 = Enum.map(member_names, fn({_, name}) -> + history = (:calendar.date_to_gregorian_days(start_date) .. :calendar.date_to_gregorian_days(DateTime.utc_now |> DateTime.to_date() |> Date.to_erl)) + |> Enum.to_list + |> Enum.map(&(:calendar.gregorian_days_to_date(&1))) + |> Enum.map(&Date.from_erl!(&1)) + |> Enum.map(fn(date) -> + get_in(gls, [date, name]) #%{date: date, gl: get_in(gls, [date, name])} + end) + if Enum.all?(history, fn(x) -> x == 0 end) do + nil + else + %{name: name, history: history} + end + end) + |> Enum.filter(fn(x) -> x end) + + conn + |> put_resp_content_type("application/json") + |> text(Jason.encode!(%{labels: dates, data: filled2})) + end + + def minisync(conn, %{"user_id" => user_id, "key" => key, "value" => value}) do + account = IRC.Account.get(user_id) + if account do + ds = LSG.IRC.AlcoologPlugin.data_state() + meta = LSG.IRC.AlcoologPlugin.get_user_meta(ds, account.id) + case Float.parse(value) do + {val, _} -> + new_meta = Map.put(meta, String.to_existing_atom(key), val) + LSG.IRC.AlcoologPlugin.put_user_meta(ds, account.id, new_meta) + _ -> + conn + |> put_status(:unprocessable_entity) + |> text("invalid value") + end + else + conn + |> put_status(:not_found) + |> text("not found") + end + end + end diff --git a/lib/lsg_web/controllers/irc_controller.ex b/lib/lsg_web/controllers/irc_controller.ex index e0bf24d..317bb27 100644 --- a/lib/lsg_web/controllers/irc_controller.ex +++ b/lib/lsg_web/controllers/irc_controller.ex @@ -52,11 +52,16 @@ defmodule LSGWeb.IrcController do conn.assigns[:chan] -> "/#{conn.assigns.network}/#{LSGWeb.format_chan(conn.assigns.chan)}" true -> "/-" end - if Map.has_key?(data, txt) do + if lines = Map.get(data, txt) do + lines = Enum.map(lines, fn(line) -> + line + |> String.split("\\\\") + |> Enum.intersperse(Phoenix.HTML.Tag.tag(:br)) + end) conn |> assign(:breadcrumbs, [{"txt", "#{base_url}/txt"}]) |> assign(:title, "#{txt}.txt") - |> render("txt.html", name: txt, data: data[txt], doc: nil) + |> render("txt.html", name: txt, data: lines, doc: nil) else conn |> put_status(404) diff --git a/lib/lsg_web/router.ex b/lib/lsg_web/router.ex index de7fafb..d681ed9 100644 --- a/lib/lsg_web/router.ex +++ b/lib/lsg_web/router.ex @@ -33,13 +33,20 @@ defmodule LSGWeb.Router do get "/-/alcoolog", AlcoologController, :index get "/-/alcoolog/~/:account_name", AlcoologController, :index get "/:network", NetworkController, :index + get "/:network/~:nick/alcoolog", AlcoologController, :nick + get "/:network/~:nick/alcoolog/log.json", AlcoologController, :nick_log_json + get "/:network/~:nick/alcoolog/gls.json", AlcoologController, :nick_gls_json + get "/:network/~:nick/alcoolog/volumes.json", AlcoologController, :nick_volumes_json + get "/:network/~:nick/alcoolog/history.json", AlcoologController, :nick_history_json + get "/:network/~:nick/alcoolog/stats.json", AlcoologController, :nick_stats_json + get "/:network/:chan/alcoolog", AlcoologController, :index + get "/:network/:chan/alcoolog/gls.json", AlcoologController, :index_gls_json + put "/api/alcoolog/minisync/:user_id/meta/:key", AlcoologController, :minisync_put_meta get "/:network/:chan", IrcController, :index get "/:network/:chan/txt", IrcController, :txt get "/:network/:chan/txt/:name", IrcController, :txt get "/:network/:channel/preums", IrcController, :preums - get "/:network/:chan/alcoolog", AlcoologController, :index get "/:network/:chan/alcoolog/t/:token", AlcoologController, :token - get "/:network/alcoolog/~/:nick", AlcoologController, :nick end end diff --git a/lib/lsg_web/templates/alcoolog/index.html.eex b/lib/lsg_web/templates/alcoolog/index.html.eex index e656e64..3f522e9 100644 --- a/lib/lsg_web/templates/alcoolog/index.html.eex +++ b/lib/lsg_web/templates/alcoolog/index.html.eex @@ -5,54 +5,202 @@ ol li { </style> <%= if @stats == [] do %> - </strong><i>:o personne ne boit</i></strong> + <div class="rounded-md bg-red-50 p-4"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor"> + <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm leading-5 font-medium text-red-800"> + CATASTROPHE! Personne n'a bu!!!! + </h3> + </div> + </div> + </div> <% end %> -<ul> +<ul class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <%= for {nick, status} <- @stats do %> - <li><strong><%= nick %> <%= status.user_status %> - <%= status.trend_symbol %> <%= status.active %> g/l</strong><br/> - — 15m: <%= status.active15m %> g/l - 30m: <%= status.active30m %> g/l - 1h: <%= status.active1h %> g/l<br /> - — dernier verre: <%= status.last_type %> <%= status.last_descr %> (<%= status.last_points %>) - <%= LSGWeb.LayoutView.format_time(status.last_at) %> - <br /> - — sobre dans: <%= status.sober_in_s %> - <br /> - <small> - — aujourd'hui: <%= status.daily_volumes %> points, <%= status.daily_gl %> g/l - </small> + <li class="col-span-1 bg-white rounded-lg shadow"> + <div class="w-full flex items-center justify-between p-6 space-x-6"> + <div class="flex-1 truncate"> + <div class="flex items-center space-x-3"> + <h3 class="text-gray-900 text-base leading-5 font-semibold truncate"><%= link nick, to: alcoolog_path(@conn, :nick, @network, nick) %></h3> + <% rising_class = if status.rising, do: "teal", else: "red" %> + <span class="flex-shrink-0 inline-block px-2 py-0.5 text-<%= rising_class %>-800 text-sm leading-4 font-medium bg-<%= rising_class %>-100 rounded-full"> + <%= status.trend_symbol %> <%= Float.round(status.active, 4) %> g/l + </span> + </div> + <p class="mt-1 text-gray-700 text-sm leading-5 truncate"> + <span class="text-base"><%= status.last_cl %>cl @ <%= status.last_deg %>°</span> + <%= if status.last_descr && status.last_descr != "" do %> + <br /><%= status.last_descr %> + <% end %> + <br/><small><%= LSGWeb.LayoutView.format_time(status.last_at) %></small> + </p> + + <p class="mt-1 text-gray-500 text-sm leading-5 truncate"> + <br /> + — sobre dans: <%= status.sober_in_s %><br /> + <%= if status.since do %> + — depuis: <%= status.since_s %><br /> + <% end %> + <small> + — 15m: <%= status.active15m %> g/l - 30m: <%= status.active30m %> g/l - 1h: <%= status.active1h %> g/l<br /> + — aujourd'hui: <%= status.daily_volumes %> points, <%= status.daily_gl %> g/l + </small> + </p> + + + </div> + </div> </li> <% end %> </ul> <%= if @stats == %{} do %> - <strong><i>... et personne n'a bu :o :o :o</i></strong> + <div class="rounded-md bg-red-50 p-4"> + <div class="flex"> + <div class="flex-shrink-0"> + <svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor"> + <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /> + </svg> + </div> + <div class="ml-3"> + <h3 class="text-sm leading-5 font-medium text-red-800"> + ENCORE PIRE! Aucune boisson enregistrée! + </h3> + </div> + </div> + </div> <% else %> - <p> - top consommateur par volume, les 7 derniers jours: <%= Enum.intersperse(for({nick, count} <- @top, do: "#{nick}: #{Float.round(count,4)}"), ", ") %> - </p> - - <table class="table"> - <thead> - <tr> - <th scope="col">date</th> - <th scope="col">nick</th> - <th scope="col">points</th> - <th scope="col">nom</th> - </tr> - </thead> - <tbody> - <%= for {{{account, date}, points, _, nom, comment}, nick} <- @drinks do %> - <% date = DateTime.from_unix!(date, :millisecond) %> - <th scope="row"><%= LSGWeb.LayoutView.format_time(date, false) %></th> - <td><%= nick %></td> - <td><%= Float.round(points+0.0, 5) %></td> - <td><%= nom||"" %> <%= comment||"" %></td> - </tr> - <% end %> - </tbody> - </table> + +<canvas id="myChart" class="w-full" height="200"></canvas> + + <h2 class="leading-8 m-4 font-semibold text-2xl">Classement <span class="font-medium text-gray-600 text-lg">15 jours</span></h2> + +<ul class="grid grid-cols-1 gap-6 sm:grid-cols-5 lg:grid-cols-5"> + <%= for {{nick, count}, rank} <- Enum.with_index(@top) do %> + <% rank = rank + 1 %> + <% trophy = rank <= 3 %> + <% {colour, text} = case rank do +1 -> {"yellow-500", "font-semibold text-base"} +2 -> {"gray-500", "font-medium text-base"} +3 -> {"orange-300", "font-medium text-base"} +_ -> {"gray-300", ""} + end %> + <li class="col-span-1 bg-white rounded-lg shadow text-center <%= text %>"> + <%= if trophy do %> + <span class="text-<%= colour %>"> + <svg style="width: 1.125em; height: 1.5em; display: inline-block;" aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trophy" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" data-fa-i2svg=""><path fill="currentColor" d="M552 64H448V24c0-13.3-10.7-24-24-24H152c-13.3 0-24 10.7-24 24v40H24C10.7 64 0 74.7 0 88v56c0 35.7 22.5 72.4 61.9 100.7 31.5 22.7 69.8 37.1 110 41.7C203.3 338.5 240 360 240 360v72h-48c-35.3 0-64 20.7-64 56v12c0 6.6 5.4 12 12 12h296c6.6 0 12-5.4 12-12v-12c0-35.3-28.7-56-64-56h-48v-72s36.7-21.5 68.1-73.6c40.3-4.6 78.6-19 110-41.7 39.3-28.3 61.9-65 61.9-100.7V88c0-13.3-10.7-24-24-24zM99.3 192.8C74.9 175.2 64 155.6 64 144v-16h64.2c1 32.6 5.8 61.2 12.8 86.2-15.1-5.2-29.2-12.4-41.7-21.4zM512 144c0 16.1-17.7 36.1-35.3 48.8-12.5 9-26.7 16.2-41.8 21.4 7-25 11.8-53.6 12.8-86.2H512v16z"></path></svg> + </span> + <% end %> + #<%= rank %>: <%= link nick, to: alcoolog_path(@conn, :nick, @network, nick) %> + <br /> + <span class="text-xs"><%= Float.round(count, 4) %></span> + </li> + <% end %> +</ul> + + <h2 class="leading-8 m-4 font-semibold text-2xl">Historique</h2> +<div class="flex flex-col"> + <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> + <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> + <table class="min-w-full divide-y divide-gray-200"> + <thead> + <tr> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + date + </th> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + nick + </th> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + + </th> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + + </th> + </tr> + </thead> + <tbody> + <%= for {{{{account, date}, points, _active, cl, deg, nom, comment, _meta}, nick}, index} <- Enum.with_index(@drinks) do %> + <% class = if(Integer.is_even(index), do: "bg-gray-50", else: "bg-white") %> + <% date = DateTime.from_unix!(date, :millisecond) %> + <tr class="<%= class %>"> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900"> + <%= LSGWeb.LayoutView.format_time(date, false) %> + </td> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900"> + <%= link nick, to: alcoolog_path(@conn, :nick, @network, nick) %> + </td> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500"><%= cl %>cl <%= deg %>°</td> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500"><%= comment||"" %></td> + </tr> + <% end %> + </tbody> + </table> + </div> + </div> +</div> <% end %> <%= if @conn.assigns.account && (@network || @channel) do %> <%= link("alcoolog global", to: alcoolog_path(@conn, :index)) %> <% end %> + +<link href='/assets/css/metricsgraphics.css' rel='stylesheet' type='text/css'> +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.css" integrity="sha512-SUJFImtiT87gVCOXl3aGC00zfDl6ggYAw5+oheJvRJ8KBXZrr/TMISSdVJ5bBarbQDRC2pR5Kto3xTR0kpZInA==" crossorigin="anonymous" /> +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js" integrity="sha512-vBmx0N/uQOXznm/Nbkp7h0P1RfLSj0HQrFSzV8m7rOGyj30fYAOKHYvCNez+yM8IrfnW0TCodDEjRqf6fodf/Q==" crossorigin="anonymous"></script> +<script src="/assets/js/jquery3.1.0.min.js"></script> +<script src="/assets/js/d3.v4.min.js"></script> +<script src="/assets/js/metricsgraphics.min.js"></script> + <script type="text/javascript"> + (function() { + // oui s trè moch :( :( :( +var ctx = document.getElementById('myChart').getContext('2d'); + d3.json('<%= alcoolog_path(@conn, :index_gls_json, @network, LSGWeb.format_chan(@channel)) %>', function(data) { + + var dynamicColors = function() { + var r = Math.floor(Math.random() * 255); + var g = Math.floor(Math.random() * 255); + var b = Math.floor(Math.random() * 255); + return "rgb(" + r + "," + g + "," + b + ")"; + }; + + labels = data.labels; + datasets = $.map($.makeArray(data.data), function(set) { + return {label: set.name, data: set.history, fill: false, borderColor: dynamicColors()} + }); + + +var myChart = new Chart(ctx, { + type: 'line', + data: { + labels: labels, + datasets: datasets + }, + options: { + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'day' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } +}); +}); + + +})(); +</script> + diff --git a/lib/lsg_web/templates/alcoolog/user.html.eex b/lib/lsg_web/templates/alcoolog/user.html.eex new file mode 100644 index 0000000..3b2b20b --- /dev/null +++ b/lib/lsg_web/templates/alcoolog/user.html.eex @@ -0,0 +1,170 @@ +<%= if @stats.active > 0 do %> + <h3 class="text-gray-900 text-xl leading-5 font-semibold truncate"> + <% rising_class = if @stats.rising, do: "teal", else: "red" %> + <span class="flex-shrink-0 inline-block px-3 py-3 text-<%= rising_class %>-800 text-2xl leading-4 font-medium bg-<%= rising_class %>-100 rounded-full"> + <%= @stats.trend_symbol %> <%= Float.round(@stats.active, 4) %> g/l + </span> + + <span class="px-3"><%= @stats.last_cl %>cl @ <%= @stats.last_deg %>°</span> + + <span class="px-3 text-lg text-gray-700"><%= if @stats.last_descr && @stats.last_descr != "" do %> + <%= @stats.last_descr %> + <% end %> + </span> + + <span class="px-3 text-gray-500 text-sm font-thin"><%= LSGWeb.LayoutView.format_time(@stats.last_at) %></span> + + </h3> + + <p class="text-gray-500 font-thin text-lg"> + a commencé il y a <span class="text-gray-700 font-semibold"><%= @stats.since_s %></span> + — + sobre dans <span class="text-gray-700 font-semibold"><%= @stats.sober_in_s %></span> + </p> +<% else %> + <h3 class="text-gray-900 text-xl leading-5 font-semibold truncate"> + est sobre! + + <p class="text-gray-500 font-thin text-lg"> + dernier verre + <span class="px-3"><%= @stats.last_cl %>cl @ <%= @stats.last_deg %>°</span> + + <span class="px-3 text-lg text-gray-700"><%= if @stats.last_descr && @stats.last_descr != "" do %> + <%= @stats.last_descr %> + <% end %> + </span> + + <span class="px-3 text-gray-500 text-sm font-thin"><%= LSGWeb.LayoutView.format_time(@stats.last_at) %></span> + </h3> +<% end %> + +<canvas id="myChart" class="w-full" height="200"></canvas> +<canvas id="myChartGl" class="w-full" height="200"></canvas> + + +<h2 class="leading-8 m-4 font-semibold text-2xl">Historique</h2> +<div class="flex flex-col"> + <div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8"> + <div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b border-gray-200"> + <table class="min-w-full divide-y divide-gray-200"> + <thead> + <tr> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + date + </th> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + + </th> + <th class="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider"> + + </th> + </tr> + </thead> + <tbody> + <%= for {%{at: date, cl: cl, deg: deg, description: comment}, index} <- Enum.with_index(@history) do %> + <% class = if(Integer.is_even(index), do: "bg-gray-50", else: "bg-white") %> + <tr class="<%= class %>"> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900"> + <%= LSGWeb.LayoutView.format_time(date, false) %> + </td> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500"><%= cl %>cl <%= deg %>°</td> + <td class="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500"><%= comment||"" %></td> + </tr> + <% end %> + </tbody> + </table> + </div> + </div> +</div> + +<div id="metrics-gl" class="w-full"></div> + +<link href='/assets/css/metricsgraphics.css' rel='stylesheet' type='text/css'> +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.css" integrity="sha512-SUJFImtiT87gVCOXl3aGC00zfDl6ggYAw5+oheJvRJ8KBXZrr/TMISSdVJ5bBarbQDRC2pR5Kto3xTR0kpZInA==" crossorigin="anonymous" /> +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js" integrity="sha512-vBmx0N/uQOXznm/Nbkp7h0P1RfLSj0HQrFSzV8m7rOGyj30fYAOKHYvCNez+yM8IrfnW0TCodDEjRqf6fodf/Q==" crossorigin="anonymous"></script> +<script src="/assets/js/jquery3.1.0.min.js"></script> +<script src="/assets/js/d3.v4.min.js"></script> +<script src="/assets/js/metricsgraphics.min.js"></script> + <script type="text/javascript"> + (function() { +var ctx = document.getElementById('myChart').getContext('2d'); + d3.json('<%= alcoolog_path(@conn, :nick_volumes_json, @network, @nick, days: @days) %>', function(data) { +var myChart = new Chart(ctx, { + type: 'bar', + data: { + labels: data.map(function(data) { return new Date(data.date) }), + datasets: [{ + label: 'Volumes d\'alcool', + data: data.map(function(data) { return data.volumes }), + borderWidth: 1 + }] + }, + options: { + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'day' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } +}); +}); + + + + +var ctxgl = document.getElementById('myChartGl').getContext('2d'); + d3.json('<%= alcoolog_path(@conn, :nick_gls_json, @network, @nick, days: @days) %>', function(data) { +var myChartgl = new Chart(ctxgl, { + type: 'bar', + data: { + labels: data.map(function(data) { return new Date(data.date) }), + datasets: [{ + label: 'g/l ingérés', + data: data.map(function(data) { return data.gls }), + borderWidth: 1 + }] + }, + options: { + scales: { + xAxes: [{ + type: 'time', + time: { + unit: 'day' + } + }], + yAxes: [{ + ticks: { + beginAtZero: true + } + }] + } + } +}); +}); + + + d3.json('<%= alcoolog_path(@conn, :nick_log_json, @network, @nick, days: @days) %>', function(data) { + data = data.map(function(d) { + date = new Date(d.at); + return {"date": date, "active": d.active}; + }); + MG.data_graphic({ + title: "g/l, All Time", + data: data, + width: 1000, + height: 400, + target: document.getElementById('metrics-gl'), + x_accessor: 'date', + y_accessor: 'active', + }); + }); +})(); +</script> diff --git a/lib/lsg_web/templates/irc/index.html.eex b/lib/lsg_web/templates/irc/index.html.eex index 9e4f724..a8544b3 100644 --- a/lib/lsg_web/templates/irc/index.html.eex +++ b/lib/lsg_web/templates/irc/index.html.eex @@ -1,19 +1,12 @@ -<style type="text/css"> -.help-entry h1 { - font-size: 18px; -} -.help-entry h2 { - font-size: 16px; -} -</style> - -<p> +<div class="hidden sm:block"> + <nav class="flex flex-wrap content-center"> <% list = for {identifier, _} <- @commands do %> <% name = String.replace(identifier, "_", " ") %> - <%= link(name, to: "##{identifier}") %> + <%= link(name, to: "##{identifier}", class: "px-3 py-2 font-medium text-sm leading-5 rounded-md text-gray-500 hover:text-gray-700 focus:outline-none focus:text-indigo-600 focus:bg-indigo-50") %> <% end %> - <small><%= Enum.intersperse(list, " - ") %></small> -</p> + <%= list %> + </nav> +</div> <%= if @members != [] do %> <% users = for {_acc, user, nick} <- @members, do: if(user, do: nick, else: content_tag(:i, nick)) %> @@ -22,10 +15,12 @@ <% end %> <% end %> -<div class="irchelps"> +<div class="irchelps space-y-6"> <%= for {identifier, help} <- @commands do %> <%= if help do %> - <div class="help-entry" id="<%= identifier %>"><%= LSGWeb.LayoutView.liquid_markdown(@conn, help) %></div> + <div class="bg-white border-b border-gray-200" id="<%= identifier %>"> + <div class="prose w-auto max-w-full"><%= LSGWeb.LayoutView.liquid_markdown(@conn, help) %></div> + </div> <% end %> <% end %> </div> diff --git a/lib/lsg_web/templates/irc/txt.html.eex b/lib/lsg_web/templates/irc/txt.html.eex index e9df681..fd4ea00 100644 --- a/lib/lsg_web/templates/irc/txt.html.eex +++ b/lib/lsg_web/templates/irc/txt.html.eex @@ -1,15 +1,27 @@ <style type="text/css"> -ol li { - margin-bottom: 5px +:target { + background-color: #ffa; + font-weight: bold; +} +ol > li > a { + display: none; +} +ol > li:hover > a { + display: inline-block; + color: gray; } </style> -<ol> +<ol class="prose space-y-3 w-auto max-w-full list-outside list-decimal m-4 leading-loose"> <%= for {txt, id} <- Enum.with_index(@data) do %> - <li id="<%= @name %>-<%= id %>"><%= txt %></li> + <li class="text-lg" id="q<%= id+1 %>"> + + <%= txt %> + <a href="#q<%= id+1 %>" class="text-xs text-gray-500 space-x-3">#</a> + </li> <% end %> </ol> <p> - <a href="/irc/txt/<%= @name %>.txt">télécharger au format texte</a> + <a href="/-/txt/<%= @name %>.txt" class="text-xs text-gray-500">télécharger au format texte</a> </p> diff --git a/lib/lsg_web/templates/irc/txts.html.eex b/lib/lsg_web/templates/irc/txts.html.eex index 5971ffa..66321b5 100644 --- a/lib/lsg_web/templates/irc/txts.html.eex +++ b/lib/lsg_web/templates/irc/txts.html.eex @@ -1,25 +1,26 @@ -<style type="text/css"> -.help-entry h1 { - font-size: 18px; -} -.help-entry h2 { - font-size: 16px; -} -</style> - -<p><strong><%= @lines %></strong> lignes dans <strong><%= @files %></strong> fichiers. -<a href="#help">Aide</a>. -</p> - -<ul> +<div> + <h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide"> + <strong><%= @lines %></strong> lignes dans <strong><%= @files %></strong> fichiers + — + <a href="#help">Aide</a> + </h2> + <ul class="mt-3 grid grid-cols-1 gap-5 sm:gap-6 sm:grid-cols-4 lg:grid-cols-6"> <%= for {txt, data} <- @data do %> <% base_url = cond do @conn.assigns[:chan] -> "/#{@conn.assigns.network}/#{LSGWeb.format_chan(@conn.assigns.chan)}" true -> "/-" end %> - <li><a href="<%= base_url %>/txt/<%= txt %>"><%= txt %></a> <i>(<%= Enum.count(data) %>)</i></li> + <li class="col-span-1 flex shadow-sm rounded-m"> + <div class="flex-1 flex items-center justify-between border-l border-t border-r border-b border-gray-200 bg-white rounded-md truncate"> + <div class="flex-1 px-4 py-2 text-sm leading-5 truncate"> + <a href="<%= base_url %>/txt/<%= txt %>" class="text-gray-900 text-lg font-medium hover:text-gray-600 transition ease-in-out duration-150"><%= txt %></a> + <p class="text-gray-500"><%= Enum.count(data) %> lignes</p> + </div> + </div> + </li> <% end %> -</ul> + </ul> +</div> -<div class="help-entry" id="help"><%= LSGWeb.LayoutView.liquid_markdown(@conn, @doc) %></div> +<div class="prose" id="help"><%= LSGWeb.LayoutView.liquid_markdown(@conn, @doc) %></div> diff --git a/lib/lsg_web/templates/layout/app.html.eex b/lib/lsg_web/templates/layout/app.html.eex index 1871e8b..9e78e8e 100644 --- a/lib/lsg_web/templates/layout/app.html.eex +++ b/lib/lsg_web/templates/layout/app.html.eex @@ -8,28 +8,137 @@ <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="robots" content="noindex, noarchive, nofollow, nosnippet" /> <title><%= Map.get(assigns, :title, "") %></title> - <link rel="stylesheet" href="<%= static_path(@conn, "/assets/css/app.css") %>"> + <link rel="stylesheet" href="<%= static_path(@conn, "/assets/css/tailwind-ui.min.css") %>"> </head> <body> - <div class="container"> - <main role="main"> - <%= if !@conn.assigns[:no_header] do %> - <h1> - <small style="font-size: 14px;"> - <a href="/"><%= Keyword.get(Application.get_env(:lsg, :irc), :name, "ircbot") %></a> - <%= if @conn.assigns[:account], do: @conn.assigns.account.name %> - › +<div> + <div class="bg-gray-800 pb-32"> + <nav class="bg-gray-800"> + <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> + <div class="border-b border-gray-700"> + <div class="flex items-center justify-between h-16 px-4 sm:px-0"> + <div class="flex items-center"> + <div class="flex-shrink-0"> + <img class="h-8 w-8" src="https://tailwindui.com/img/logos/workflow-mark-on-dark.svg" alt="Workflow logo"> + </div> + <div class="hidden md:block"> + <div class="ml-10 flex items-baseline"> + <a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-white bg-gray-900 focus:outline-none focus:text-white focus:bg-gray-700">bot.goulag.org</a> + <!--<a href="#" class="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Team</a> + <a href="#" class="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Projects</a> + <a href="#" class="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Calendar</a> + <a href="#" class="ml-4 px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Reports</a>--> + </div> + </div> + </div> + <div class="hidden md:block"> + <div class="ml-4 flex items-center md:ml-6"> + <!--<button class="p-1 border-2 border-transparent text-gray-400 rounded-full hover:text-white focus:outline-none focus:text-white focus:bg-gray-700" aria-label="Notifications"> + <svg class="h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" /> + </svg> + </button>--> + <!-- Profile dropdown --> + <div class="ml-3 relative"> + <div> + <button class="max-w-xs flex items-center text-sm rounded-full text-white focus:outline-none focus:shadow-solid" id="user-menu" aria-label="User menu" aria-haspopup="true"> + <!--<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="">--> + ~<%= if @conn.assigns[:account], do: @conn.assigns.account.name %> + + </button> + </div> + <!-- + Profile dropdown panel, show/hide based on dropdown state. + + Entering: "transition ease-out duration-100" + From: "transform opacity-0 scale-95" + To: "transform opacity-100 scale-100" + Leaving: "transition ease-in duration-75" + From: "transform opacity-100 scale-100" + To: "transform opacity-0 scale-95" + --> + <!--<div class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg"> + <div class="py-1 rounded-md bg-white shadow-xs"> + <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Your Profile</a> + <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Settings</a> + <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Sign out</a> + </div> + </div>--> + </div> + </div> + </div> + <div class="-mr-2 flex md:hidden"> + <!-- Mobile menu button --> + <button class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:bg-gray-700 focus:text-white"> + <!-- Menu open: "hidden", Menu closed: "block" --> + <svg class="block h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /> + </svg> + <!-- Menu open: "block", Menu closed: "hidden" --> + <svg class="hidden h-6 w-6" stroke="currentColor" fill="none" viewBox="0 0 24 24"> + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> + </svg> + </button> + </div> + </div> + </div> + </div> + + <!-- + Mobile menu, toggle classes based on menu state. + + Open: "block", closed: "hidden" + --> + <div class="hidden border-b border-gray-700 md:hidden"> + <div class="px-2 py-3 sm:px-3"> + <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-white bg-gray-900 focus:outline-none focus:text-white focus:bg-gray-700">Dashboard</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Team</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Projects</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Calendar</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-300 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700">Reports</a> + </div> + <div class="pt-4 pb-3 border-t border-gray-700"> + <div class="flex items-center px-5"> + <div class="flex-shrink-0"> + <img class="h-10 w-10 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt=""> + </div> + <div class="ml-3"> + <div class="text-base font-medium leading-none text-white">Tom Cook</div> + <div class="mt-1 text-sm font-medium leading-none text-gray-400">tom@example.com</div> + </div> + </div> + <div class="mt-3 px-2" role="menu" aria-orientation="vertical" aria-labelledby="user-menu"> + <a href="#" class="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700" role="menuitem">Your Profile</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700" role="menuitem">Settings</a> + <a href="#" class="mt-1 block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:text-white focus:bg-gray-700" role="menuitem">Sign out</a> + </div> + </div> + </div> + </nav> + <header class="py-10"> + <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> + <h1 class="text-3xl leading-9 font-bold text-white"> <%= if n = @conn.assigns[:network] do %><a href="/<%= n %>"><%= n %></a> › <% end %> <%= if c = @conn.assigns[:chan] do %><a href="/<%= @conn.assigns.network %>/<%= LSGWeb.format_chan(c) %>"><%= c %></a> › <% end %> <%= for({name, href} <- @conn.assigns[:breadcrumbs]||[], do: [link(name, to: href), raw(" › ")]) %> - </small><br /> <%= @conn.assigns[:title] || @conn.assigns[:chan] || @conn.assigns[:network] %> - </h1> - <% end %> + </h1> + </div> + </header> + </div> + + <main class="-mt-32"> + <div class="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8"> + <!-- Replace with your content --> + <div class="bg-white rounded-lg shadow px-5 py-6 sm:px-6"> <%= render @view_module, @view_template, assigns %> - </main> + </div> + <!-- /End replace --> </div> + </main> +</div> + <script src="<%= static_path(@conn, "/assets/js/app.js") %>"></script> </body> </html> diff --git a/lib/lsg_web/templates/page/user.html.eex b/lib/lsg_web/templates/page/user.html.eex index 8a043a0..56fc485 100644 --- a/lib/lsg_web/templates/page/user.html.eex +++ b/lib/lsg_web/templates/page/user.html.eex @@ -1,3 +1,4 @@ +<div class="prose prose-lg"> <ul> <li><%= link("Help", to: "/-") %></li> </ul> @@ -36,3 +37,4 @@ <li><%= net %>: <%= to_string(predicate) %>, <%= v %></li> <% end %> </ul> +</div> diff --git a/lib/lsg_web/views/alcoolog_view.ex b/lib/lsg_web/views/alcoolog_view.ex index 2c55c09..ed3c9b4 100644 --- a/lib/lsg_web/views/alcoolog_view.ex +++ b/lib/lsg_web/views/alcoolog_view.ex @@ -1,4 +1,6 @@ defmodule LSGWeb.AlcoologView do use LSGWeb, :view + require Integer + end diff --git a/lib/lsg_web/views/layout_view.ex b/lib/lsg_web/views/layout_view.ex index b28d3c5..41c5341 100644 --- a/lib/lsg_web/views/layout_view.ex +++ b/lib/lsg_web/views/layout_view.ex @@ -60,11 +60,14 @@ defmodule LSGWeb.LayoutView do now_last_week = {y, w-1} now_last_roll = 7-Timex.days_to_beginning_of_week(now) + date_date = DateTime.to_date(date) + now_date = DateTime.to_date(date) + format = cond do date.year != now.year -> "{D}/{M}/{YYYY} {h24}:{m}" - date.day == now.day -> "{h24}:{m}" + date_date == now_date -> "{h24}:{m}" (now_week == date_week) || (date_week == now_last_week && (Date.day_of_week(date) >= now_last_roll)) -> "{WDfull} {h24}:{m}" - (now.month == date.month) -> "{WDfull} {D} {h24}:{m}" + (now.year == date.year && now.month == date.month) -> "{WDfull} {D} {h24}:{m}" true -> "{WDfull} {D} {M} {h24}:{m}" end diff --git a/lib/tmpl.ex b/lib/tmpl.ex index cc5ece2..1470603 100644 --- a/lib/tmpl.ex +++ b/lib/tmpl.ex @@ -65,6 +65,7 @@ defmodule Tmpl do {:ok, template_ast} -> do_render(template_ast, context, safe) {:error, err, pos} -> + Logger.debug("Liquid error: #{pos} - #{inspect template}") "[liquid ast error (at #{pos}): #{inspect err}]" end end diff --git a/lib/util.ex b/lib/util.ex index 22d1034..d35157b 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -4,13 +4,15 @@ defmodule Util do def plusminus(0), do: "0" def plusminus(number) when number < 0, do: "#{number}" - def float_paparse(string) do + def float_paparse(float) when is_float(float), do: {float, ""} + def float_paparse(int) when is_integer(int), do: {(int+0.0), ""} + def float_paparse(string) when is_binary(string) do string |> String.replace(",", ".") |> Float.parse() end - def ets_mutate_select_each(ets, table, spec, fun) do + def ets_mutate_select_each(ets, table, spec \\ [{:"$1", [], [:"$1"]}], fun) do ets.safe_fixtable(table, true) first = ets.select(table, spec, 1) do_ets_mutate_select_each(ets, table, fun, first) |