diff options
Diffstat (limited to 'lib/plugins/alcoolog.ex')
-rw-r--r-- | lib/plugins/alcoolog.ex | 1689 |
1 files changed, 1078 insertions, 611 deletions
diff --git a/lib/plugins/alcoolog.ex b/lib/plugins/alcoolog.ex index 69bd60c..de69b13 100644 --- a/lib/plugins/alcoolog.ex +++ b/lib/plugins/alcoolog.ex @@ -49,27 +49,40 @@ defmodule Nola.Plugins.Alcoolog do @default_user_meta %{weight: 77.4, sex: true, loss_factor: 15} def data_state() do - dets_filename = (Nola.data_path() <> "/" <> "alcoolisme.dets") |> String.to_charlist - dets_meta_filename = (Nola.data_path() <> "/" <> "alcoolisme_meta.dets") |> String.to_charlist + dets_filename = (Nola.data_path() <> "/" <> "alcoolisme.dets") |> String.to_charlist() + + dets_meta_filename = + (Nola.data_path() <> "/" <> "alcoolisme_meta.dets") |> String.to_charlist() + %{dets: dets_filename, meta: dets_meta_filename, ets: __MODULE__.ETS} end def init(_) do - triggers = for(t <- @pubsub_triggers, do: "trigger:"<>t) + triggers = for(t <- @pubsub_triggers, do: "trigger:" <> t) + for sub <- @pubsub ++ triggers do {:ok, _} = Registry.register(Nola.PubSub, sub, plugin: __MODULE__) end - dets_filename = (Nola.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}]) - dets_meta_filename = (Nola.data_path() <> "/" <> "alcoolisme_meta.dets") |> String.to_charlist - {:ok, meta} = :dets.open_file(dets_meta_filename, [{:type,:set}]) - traverse_fun = fn(obj, dets) -> + + dets_filename = (Nola.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}]) + + dets_meta_filename = + (Nola.data_path() <> "/" <> "alcoolisme_meta.dets") |> String.to_charlist() + + {:ok, meta} = :dets.open_file(dets_meta_filename, [{:type, :set}]) + + traverse_fun = fn obj, dets -> case obj do object = {nick, naive = %NaiveDateTime{}, volumes, active, cl, deg, name, comment} -> - date = naive - |> DateTime.from_naive!("Etc/UTC") - |> DateTime.to_unix() + date = + naive + |> DateTime.from_naive!("Etc/UTC") + |> DateTime.to_unix() + new = {nick, date, volumes, active, cl, deg, name, comment, Map.new()} :dets.delete_object(dets, object) :dets.insert(dets, new) @@ -77,9 +90,11 @@ defmodule Nola.Plugins.Alcoolog do dets object = {nick, naive = %NaiveDateTime{}, volumes, active, cl, deg, name, comment, meta} -> - date = naive - |> DateTime.from_naive!("Etc/UTC") - |> DateTime.to_unix() + date = + naive + |> DateTime.from_naive!("Etc/UTC") + |> DateTime.to_unix() + new = {nick, date, volumes, active, cl, deg, name, comment, Map.new()} :dets.delete_object(dets, object) :dets.insert(dets, new) @@ -94,6 +109,7 @@ defmodule Nola.Plugins.Alcoolog do dets end end + :dets.foldl(traverse_fun, dets, dets) :dets.sync(dets) state = %{dets: dets, meta: meta, ets: ets} @@ -101,59 +117,82 @@ defmodule Nola.Plugins.Alcoolog do end @eau ["santo", "santeau"] - def handle_info({:irc, :trigger, santeau, m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, state) when santeau in @eau do + def handle_info( + {:irc, :trigger, santeau, + m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, + state + ) + when santeau in @eau do Nola.Plugins.Txt.reply_random(m, "alcoolog.santo") {:noreply, state} end - def handle_info({:irc, :trigger, "soif", m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, state) do - now = DateTime.utc_now() - |> Timex.Timezone.convert("Europe/Paris") + def handle_info( + {:irc, :trigger, "soif", m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, + state + ) do + now = + DateTime.utc_now() + |> 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, apero?} = cond do - now.hour >= 0 && now.hour < 6 -> - {["apéro tardif ? Je dis OUI ! SANTAI !"], true} - now.hour >= 6 && now.hour < 12 -> - if day_of_week >= 6 do - {["de l'alcool pour le petit dej ? le week-end, pas de problème !"], true} - else - {["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})", - "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}", - "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 - |> Enum.shuffle() - |> Enum.random() + {txt, apero?} = + cond do + now.hour >= 0 && now.hour < 6 -> + {["apéro tardif ? Je dis OUI ! SANTAI !"], true} + + now.hour >= 6 && now.hour < 12 -> + if day_of_week >= 6 do + {["de l'alcool pour le petit dej ? le week-end, pas de problème !"], true} + else + {["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})", + "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}", + "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 + |> Enum.shuffle() + |> Enum.random() 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 @@ -161,99 +200,151 @@ defmodule Nola.Plugins.Alcoolog do {:noreply, state} end - def handle_info({:irc, :trigger, "sobrepour", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "sobrepour", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, + state + ) do args = Enum.join(args, " ") {:ok, now} = DateTime.now("Europe/Paris", Tzdata.TimeZoneDatabase) - time = case args do - "demain " <> time -> - {h, m} = case String.split(time, [":", "h"]) do - [hour, ""] -> - IO.puts ("h #{inspect hour}") - {h, _} = Integer.parse(hour) - {h, 0} - [hour, min] when min != "" -> - {h, _} = Integer.parse(hour) - {m, _} = Integer.parse(min) - {h, m} - [hour] -> - IO.puts ("h #{inspect hour}") - {h, _} = Integer.parse(hour) - {h, 0} - _ -> {0, 0} - end - secs = ((60*60)*24) - day = DateTime.add(now, secs, :second, Tzdata.TimeZoneDatabase) - %DateTime{day | hour: h, minute: m, second: 0} - "après demain " <> time -> - secs = 2*((60*60)*24) - DateTime.add(now, secs, :second, Tzdata.TimeZoneDatabase) - datetime -> - case Timex.Parse.DateTime.Parser.parse(datetime, "{}") do - {:ok, dt} -> dt - _ -> nil - end - end + + time = + case args do + "demain " <> time -> + {h, m} = + case String.split(time, [":", "h"]) do + [hour, ""] -> + IO.puts("h #{inspect(hour)}") + {h, _} = Integer.parse(hour) + {h, 0} + + [hour, min] when min != "" -> + {h, _} = Integer.parse(hour) + {m, _} = Integer.parse(min) + {h, m} + + [hour] -> + IO.puts("h #{inspect(hour)}") + {h, _} = Integer.parse(hour) + {h, 0} + + _ -> + {0, 0} + end + + secs = 60 * 60 * 24 + day = DateTime.add(now, secs, :second, Tzdata.TimeZoneDatabase) + %DateTime{day | hour: h, minute: m, second: 0} + + "après demain " <> time -> + secs = 2 * (60 * 60 * 24) + DateTime.add(now, secs, :second, Tzdata.TimeZoneDatabase) + + datetime -> + case Timex.Parse.DateTime.Parser.parse(datetime, "{}") do + {:ok, dt} -> dt + _ -> nil + end + end if time do meta = get_user_meta(state, m.account.id) stats = get_full_statistics(state, m.account.id) - duration = round(DateTime.diff(time, now)/60.0) + duration = round(DateTime.diff(time, now) / 60.0) - IO.puts "diff #{inspect duration} sober in #{inspect stats.sober_in}" + IO.puts("diff #{inspect(duration)} sober in #{inspect(stats.sober_in)}") if duration < stats.sober_in do int = stats.sober_in - duration m.replyfun.("désolé, aucune chance! tu seras sobre #{format_minute_duration(int)} après!") else remaining = duration - stats.sober_in + if remaining < 30 do m.replyfun.("moins de 30 minutes de sobriété, c'est impossible de boire plus") else - loss_per_minute = ((meta.loss_factor/100)/60) - remaining_gl = (remaining-30)*loss_per_minute - m.replyfun.("marge de boisson: #{inspect remaining} minutes, #{remaining_gl} g/l") + loss_per_minute = meta.loss_factor / 100 / 60 + remaining_gl = (remaining - 30) * loss_per_minute + m.replyfun.("marge de boisson: #{inspect(remaining)} minutes, #{remaining_gl} g/l") end end - end + {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolog", m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :plus}}}, state) do + def handle_info( + {:irc, :trigger, "alcoolog", + m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :plus}}}, + state + ) do {:ok, token} = Nola.Token.new({:alcoolog, :index, m.sender.network, m.channel}) - url = NolaWeb.Router.Helpers.alcoolog_url(NolaWeb.Endpoint, :index, m.network, NolaWeb.format_chan(m.channel), token) + + url = + NolaWeb.Router.Helpers.alcoolog_url( + NolaWeb.Endpoint, + :index, + m.network, + NolaWeb.format_chan(m.channel), + token + ) + m.replyfun.("-> #{url}") {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolog", m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :bang}}}, state) do - url = NolaWeb.Router.Helpers.alcoolog_url(NolaWeb.Endpoint, :index, m.network, NolaWeb.format_chan(m.channel)) + def handle_info( + {:irc, :trigger, "alcoolog", + m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :bang}}}, + state + ) do + url = + NolaWeb.Router.Helpers.alcoolog_url( + NolaWeb.Endpoint, + :index, + m.network, + NolaWeb.format_chan(m.channel) + ) + m.replyfun.("-> #{url}") {:noreply, state} end - def handle_info({:irc, :trigger, "alcool", m = %Nola.Message{trigger: %Nola.Trigger{args: args = [cl, deg], type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "alcool", + m = %Nola.Message{trigger: %Nola.Trigger{args: args = [cl, deg], type: :bang}}}, + state + ) do {cl, _} = Util.float_paparse(cl) {deg, _} = Util.float_paparse(deg) points = Alcool.units(cl, deg) meta = get_user_meta(state, m.account.id) k = if meta.sex, do: 0.7, else: 0.6 weight = meta.weight - gl = (10*points)/(k*weight) - duration = round(gl/((meta.loss_factor/100)/60))+30 - sober_in_s = if duration > 0 do + gl = 10 * points / (k * weight) + duration = round(gl / (meta.loss_factor / 100 / 60)) + 30 + + sober_in_s = + if duration > 0 do duration = Timex.Duration.from_minutes(duration) - Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) - else - "" - end + Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) + else + "" + end + + m.replyfun.( + "Il y a #{Float.round(points + 0.0, 4)} unités d'alcool dans #{cl}cl à #{deg}° (#{Float.round(gl + 0.0, 4)} g/l, #{sober_in_s})" + ) - m.replyfun.("Il y a #{Float.round(points+0.0, 4)} unités d'alcool dans #{cl}cl à #{deg}° (#{Float.round(gl + 0.0, 4)} g/l, #{sober_in_s})") {:noreply, state} end - def handle_info({:irc, :trigger, "santai", m = %Nola.Message{trigger: %Nola.Trigger{args: [cl, deg | comment], type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "santai", + m = %Nola.Message{trigger: %Nola.Trigger{args: [cl, deg | comment], type: :bang}}}, + state + ) do santai(m, state, cl, deg, comment) {:noreply, state} end @@ -263,80 +354,138 @@ defmodule Nola.Plugins.Alcoolog do "{{message.sender.nick}}: et voilà la petite sœur !" ] - def handle_info({:irc, :trigger, "bis", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "bis", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, + state + ) do handle_info({:irc, :trigger, "moar", m}, state) end - def handle_info({:irc, :trigger, "again", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do + + def handle_info( + {:irc, :trigger, "again", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, + state + ) do handle_info({:irc, :trigger, "moar", m}, state) end - def handle_info({:irc, :trigger, "moar", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "moar", + m = %Nola.Message{trigger: %Nola.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 + 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") + 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 + + _ -> + 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 + 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, "("<>_} -> - try do - {:ok, result} = Abacus.eval(cl) - {result, nil} - rescue - _ -> {nil, "cl: invalid calc expression"} - end - {:error, _} -> {nil, "cl: invalid value"} - end + {cl, cl_extra} = + case {Util.float_paparse(cl), cl} do + {{cl, extra}, _} -> + {cl, extra} + + {:error, "(" <> _} -> + try do + {:ok, result} = Abacus.eval(cl) + {result, nil} + rescue + _ -> {nil, "cl: invalid calc expression"} + end - {deg, comment, auto_set, beer_id} = case Util.float_paparse(deg) do - {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 - {:ok, %{"response" => %{"beers" => %{"count" => count, "items" => [%{"beer" => beer, "brewery" => brewery} | _]}}}} -> - {Map.get(beer, "beer_abv"), "#{Map.get(brewery, "brewery_name")}: #{Map.get(beer, "beer_name")}", true, Map.get(beer, "bid")} - _ -> + {:error, _} -> + {nil, "cl: invalid value"} + end + + {deg, comment, auto_set, beer_id} = + case Util.float_paparse(deg) do + {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 + {:ok, + %{ + "response" => %{ + "beers" => %{ + "count" => count, + "items" => [%{"beer" => beer, "brewery" => brewery} | _] + } + } + }} -> + {Map.get(beer, "beer_abv"), + "#{Map.get(brewery, "brewery_name")}: #{Map.get(beer, "beer_name")}", true, + Map.get(beer, "bid")} + + _ -> {deg, "could not find beer", false, nil} - end - end + end + end cond do - cl == nil -> m.replyfun.(cl_extra) - deg == nil -> m.replyfun.(comment) - cl >= 500 || deg >= 100 -> Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_toohuge") - cl == 0 || deg == 0 -> Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_zero") - cl < 0 || deg < 0 -> Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_negative") + cl == nil -> + m.replyfun.(cl_extra) + + deg == nil -> + m.replyfun.(comment) + + cl >= 500 || deg >= 100 -> + Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_toohuge") + + cl == 0 || deg == 0 -> + Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_zero") + + cl < 0 || deg < 0 -> + Nola.Plugins.Txt.reply_random(m, "alcoolog.drink_negative") + true -> points = Alcool.units(cl, deg) - now = m.at || DateTime.utc_now() - |> DateTime.to_unix(: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) @@ -344,87 +493,132 @@ defmodule Nola.Plugins.Alcoolog do 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() + + :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() sante = Nola.Plugins.Txt.random("alcoolog.santai") 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) + 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 - mins = round(stats.sober_in - old_stats.sober_in) - " [+#{mins}m]" - else - "" - end + + sober_add = + if old_stats && Map.get(old_stats || %{}, :sober_in) do + mins = round(stats.sober_in - old_stats.sober_in) + " [+#{mins}m]" + else + "" + end + nonow = DateTime.utc_now() - sober = nonow |> DateTime.add(round(stats.sober_in*60), :second) - |> Timex.Timezone.convert("Europe/Paris") - at = if nonow.day == sober.day do - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "aujourd'hui {h24}:{m}", "fr") - detail - else - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") - detail - end - up = if stats.active_drinks > 1 do - " " <> Enum.join(for(_ <- 1..stats.active_drinks, do: "▲")) <> "" - else - "" - end + sober = + nonow + |> DateTime.add(round(stats.sober_in * 60), :second) + |> Timex.Timezone.convert("Europe/Paris") - since_str = if stats.since && stats.since_min > 180 do - "(depuis: #{stats.since_s}) " - else - "" - end + at = + if nonow.day == sober.day do + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat( + sober, + "aujourd'hui {h24}:{m}", + "fr" + ) - 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}) #{since_str}(sobriété #{at} (dans #{stats.sober_in_s})#{sober_add}) !" - <> " (aujourd'hui #{stats.daily_volumes} points - #{stats.daily_gl} g/l)" - end + detail + else + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") - meta = if beer_id do - Map.put(meta, "untappd:beer_id", beer_id) - else - meta + detail + end + + up = + if stats.active_drinks > 1 do + " " <> Enum.join(for(_ <- 1..stats.active_drinks, do: "▲")) <> "" + else + "" + 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}) #{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() -> + spawn(fn -> case Untappd.maybe_checkin(m.account, beer_id) do {:ok, body} -> badges = get_in(body, ["badges", "items"]) + if badges != [] do - badges_s = Enum.map(badges, fn(badge) -> Map.get(badge, "badge_name") end) - |> Enum.filter(fn(b) -> b end) - |> Enum.intersperse(", ") - |> Enum.join("") - badge = if(length(badges) > 1, do: "badges", else: "badge") - m.replyfun.("\\O/ Unlocked untappd #{badge}: #{badges_s}") + badges_s = + Enum.map(badges, fn badge -> Map.get(badge, "badge_name") end) + |> Enum.filter(fn b -> b end) + |> Enum.intersperse(", ") + |> Enum.join("") + + badge = if(length(badges) > 1, do: "badges", else: "badge") + m.replyfun.("\\O/ Unlocked untappd #{badge}: #{badges_s}") end + :ok - {:error, {:http_error, error}} when is_integer(error) -> m.replyfun.("Checkin to Untappd failed: #{to_string(error)}") - {:error, {:http_error, error}} -> m.replyfun.("Checkin to Untappd failed: #{inspect error}") - _ -> :error + + {:error, {:http_error, error}} when is_integer(error) -> + m.replyfun.("Checkin to Untappd failed: #{to_string(error)}") + + {:error, {:http_error, error}} -> + m.replyfun.("Checkin to Untappd failed: #{inspect(error)}") + + _ -> + :error end end) end - local_extra = if auto_set do - if comment do - " #{comment} (#{cl}cl @ #{deg}°)" + local_extra = + if auto_set do + if comment do + " #{comment} (#{cl}cl @ #{deg}°)" + else + "#{cl}cl @ #{deg}°" + end else - "#{cl}cl @ #{deg}°" + "" end - else - "" - end + m.replyfun.(msg.(m.sender.nick, local_extra)) - notify = Nola.Membership.notify_channels(m.account) -- [{m.network,m.channel}] + notify = Nola.Membership.notify_channels(m.account) -- [{m.network, m.channel}] + for {net, chan} <- notify do user = Nola.UserTrack.find_by_account(net, m.account) nick = if(user, do: user.nick, else: m.account.name) @@ -432,24 +626,26 @@ defmodule Nola.Plugins.Alcoolog do Nola.Irc.Connection.broadcast_message(net, chan, msg.(nick, extra)) end - miss = cond do - points <= 0.6 -> :small - stats.active30m >= 2.9 && stats.active30m < 3 -> :miss3 - stats.active30m >= 1.9 && stats.active30m < 2 -> :miss2 - stats.active30m >= 0.9 && stats.active30m < 1 -> :miss1 - stats.active30m >= 0.45 && stats.active30m < 0.5 -> :miss05 - stats.active30m >= 0.20 && stats.active30m < 0.20 -> :miss025 - stats.active30m >= 3 && stats.active1h < 3.15 -> :small3 - stats.active30m >= 2 && stats.active1h < 2.15 -> :small2 - stats.active30m >= 1.5 && stats.active1h < 1.5 -> :small15 - stats.active30m >= 1 && stats.active1h < 1.15 -> :small1 - stats.active30m >= 0.5 && stats.active1h <= 0.51 -> :small05 - stats.active30m >= 0.25 && stats.active30m <= 0.255 -> :small025 - true -> nil - end + miss = + cond do + points <= 0.6 -> :small + stats.active30m >= 2.9 && stats.active30m < 3 -> :miss3 + stats.active30m >= 1.9 && stats.active30m < 2 -> :miss2 + stats.active30m >= 0.9 && stats.active30m < 1 -> :miss1 + stats.active30m >= 0.45 && stats.active30m < 0.5 -> :miss05 + stats.active30m >= 0.20 && stats.active30m < 0.20 -> :miss025 + stats.active30m >= 3 && stats.active1h < 3.15 -> :small3 + stats.active30m >= 2 && stats.active1h < 2.15 -> :small2 + stats.active30m >= 1.5 && stats.active1h < 1.5 -> :small15 + stats.active30m >= 1 && stats.active1h < 1.15 -> :small1 + stats.active30m >= 0.5 && stats.active1h <= 0.51 -> :small05 + stats.active30m >= 0.25 && stats.active30m <= 0.255 -> :small025 + true -> nil + end if miss do miss = Nola.Plugins.Txt.random("alcoolog.#{to_string(miss)}") + if miss do for {net, chan} <- Nola.Membership.notify_channels(m.account) do user = Nola.UserTrack.find_by_account(net, m.account) @@ -461,45 +657,59 @@ defmodule Nola.Plugins.Alcoolog do end end - def handle_info({:irc, :trigger, "santai", m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, state) do + def handle_info( + {:irc, :trigger, "santai", + m = %Nola.Message{trigger: %Nola.Trigger{args: _, type: :bang}}}, + state + ) do m.replyfun.("!santai <cl> <degrés> [commentaire]") {:noreply, state} end def get_all_stats() do Nola.Account.all_accounts() - |> Enum.map(fn(account) -> {account.id, get_full_statistics(account.id)} end) - |> Enum.filter(fn({_nick, status}) -> status && (status.active > 0 || status.active30m > 0) end) - |> Enum.sort_by(fn({_, status}) -> status.active end, &>/2) + |> Enum.map(fn account -> {account.id, get_full_statistics(account.id)} end) + |> Enum.filter(fn {_nick, status} -> + status && (status.active > 0 || status.active30m > 0) + end) + |> Enum.sort_by(fn {_, status} -> status.active end, &>/2) end def get_channel_statistics(account, network, nil) do Nola.Membership.expanded_members_or_friends(account, network, nil) - |> Enum.map(fn({account, _, nick}) -> {nick, get_full_statistics(account.id)} end) - |> Enum.filter(fn({_nick, status}) -> status && (status.active > 0 || status.active30m > 0) end) - |> Enum.sort_by(fn({_, status}) -> status.active end, &>/2) + |> Enum.map(fn {account, _, nick} -> {nick, get_full_statistics(account.id)} end) + |> Enum.filter(fn {_nick, status} -> + status && (status.active > 0 || status.active30m > 0) + end) + |> Enum.sort_by(fn {_, status} -> status.active end, &>/2) end def get_channel_statistics(_, network, channel), do: get_channel_statistics(network, channel) def get_channel_statistics(network, channel) do Nola.Membership.expanded_members(network, channel) - |> Enum.map(fn({account, _, nick}) -> {nick, get_full_statistics(account.id)} end) - |> Enum.filter(fn({_nick, status}) -> status && (status.active > 0 || status.active30m > 0) end) - |> Enum.sort_by(fn({_, status}) -> status.active end, &>/2) + |> Enum.map(fn {account, _, nick} -> {nick, get_full_statistics(account.id)} end) + |> Enum.filter(fn {_nick, status} -> + status && (status.active > 0 || status.active30m > 0) + end) + |> Enum.sort_by(fn {_, status} -> status.active end, &>/2) end @spec since() :: %{Nola.Account.id() => DateTime.t()} @doc "Returns the last time the user was at 0 g/l" def since() do - :ets.foldr(fn({{acct, timestamp_or_date}, _vol, current, _cl, _deg, _name, _comment, _m}, acc) -> - if !Map.get(acc, acct) && current == 0 do - date = Util.to_date_time(timestamp_or_date) - Map.put(acc, acct, date) - else - acc - end - end, %{}, __MODULE__.ETS) + :ets.foldr( + fn {{acct, timestamp_or_date}, _vol, current, _cl, _deg, _name, _comment, _m}, acc -> + if !Map.get(acc, acct) && current == 0 do + date = Util.to_date_time(timestamp_or_date) + Map.put(acc, acct, date) + else + acc + end + end, + %{}, + __MODULE__.ETS + ) end def get_full_statistics(nick) do @@ -508,129 +718,204 @@ defmodule Nola.Plugins.Alcoolog do defp get_full_statistics(state, nick) do case get_statistics_for_nick(state, nick) do - {count, {_, last_at, last_points, last_active, last_cl, last_deg, last_type, last_descr, _meta}} -> + {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) {_, m5} = alcohol_level_rising(state, nick, 5) {_, h1} = alcohol_level_rising(state, nick, 60) - trend = if rising do - "▲" - else - "▼" - end - user_state = cond do - active <= 0.0 -> :sober - active <= 0.25 -> :low - active <= 0.50 -> :legal - active <= 1.0 -> :legalhigh - active <= 2.5 -> :high - active < 3 -> :toohigh - true -> :sick - end + trend = + if rising do + "▲" + else + "▼" + end - rising_file_key = if rising, do: "_rising", else: "" - txt_file = "alcoolog." <> "user_" <> to_string(user_state) <> rising_file_key - user_status = Nola.Plugins.Txt.random(txt_file) - - meta = get_user_meta(state, nick) - minutes_til_sober = h1/((meta.loss_factor/100)/60) - minutes_til_sober = cond do - active < 0 -> 0 - m15 < 0 -> 15 - m30 < 0 -> 30 - h1 < 0 -> 60 - minutes_til_sober > 0 -> - Float.round(minutes_til_sober+60) - true -> 0 - end + user_state = + cond do + active <= 0.0 -> :sober + active <= 0.25 -> :low + active <= 0.50 -> :legal + active <= 1.0 -> :legalhigh + active <= 2.5 -> :high + active < 3 -> :toohigh + true -> :sick + end + + rising_file_key = if rising, do: "_rising", else: "" + txt_file = "alcoolog." <> "user_" <> to_string(user_state) <> rising_file_key + user_status = Nola.Plugins.Txt.random(txt_file) + + meta = get_user_meta(state, nick) + minutes_til_sober = h1 / (meta.loss_factor / 100 / 60) + + minutes_til_sober = + cond do + active < 0 -> + 0 + + m15 < 0 -> + 15 + + m30 < 0 -> + 30 + + h1 < 0 -> + 60 + + minutes_til_sober > 0 -> + Float.round(minutes_til_sober + 60) + + true -> + 0 + end duration = Timex.Duration.from_minutes(minutes_til_sober) - sober_in_s = if minutes_til_sober > 0 do - Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) - else - nil - end - since = if active > 0 do - since() - |> Map.get(nick) - end + sober_in_s = + if minutes_til_sober > 0 do + Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) + else + 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) - {total_volumes, total_gl} = user_stats(state, nick) + since_s = + if since, do: Timex.Format.Duration.Formatter.lformat(since_duration, "fr", :humanized) + {total_volumes, total_gl} = user_stats(state, nick) - %{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, + %{ + 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, + 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, - since: since, since_min: since_diff, since_s: since_s, + daily_gl: total_gl, + daily_volumes: total_volumes, + sober_in: minutes_til_sober, + sober_in_s: sober_in_s, + since: since, + since_min: since_diff, + since_s: since_s } - _ -> - nil + + _ -> + nil end end - def handle_info({:irc, :trigger, "sobre", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :dot}}}, state) do - nicks = Nola.Membership.expanded_members_or_friends(m.account, m.network, m.channel) - |> Enum.map(fn({account, _, nick}) -> {nick, get_full_statistics(state, account.id)} end) - |> Enum.filter(fn({_nick, status}) -> status && status.sober_in && status.sober_in > 0 end) - |> Enum.sort_by(fn({_, status}) -> status.sober_in end, &</2) - |> Enum.map(fn({nick, stats}) -> - now = DateTime.utc_now() - sober = now |> DateTime.add(round(stats.sober_in*60), :second) - |> Timex.Timezone.convert("Europe/Paris") - at = if now.day == sober.day do - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "aujourd'hui {h24}:{m}", "fr") - detail - else - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") - detail - end - "#{nick} sobre #{at} (dans #{stats.sober_in_s})" - end) - |> Enum.intersperse(", ") - |> Enum.join("") - |> (fn(line) -> - case line do - "" -> "tout le monde est sobre......." - line -> line - end - end).() - |> m.replyfun.() + def handle_info( + {:irc, :trigger, "sobre", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :dot}}}, + state + ) do + nicks = + Nola.Membership.expanded_members_or_friends(m.account, m.network, m.channel) + |> Enum.map(fn {account, _, nick} -> {nick, get_full_statistics(state, account.id)} end) + |> Enum.filter(fn {_nick, status} -> status && status.sober_in && status.sober_in > 0 end) + |> Enum.sort_by(fn {_, status} -> status.sober_in end, &</2) + |> Enum.map(fn {nick, stats} -> + now = DateTime.utc_now() + + sober = + now + |> DateTime.add(round(stats.sober_in * 60), :second) + |> Timex.Timezone.convert("Europe/Paris") + + at = + if now.day == sober.day do + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat( + sober, + "aujourd'hui {h24}:{m}", + "fr" + ) + + detail + else + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") + + detail + end + + "#{nick} sobre #{at} (dans #{stats.sober_in_s})" + end) + |> Enum.intersperse(", ") + |> Enum.join("") + |> (fn line -> + case line do + "" -> "tout le monde est sobre......." + line -> line + end + end).() + |> m.replyfun.() + {:noreply, state} end - def handle_info({:irc, :trigger, "sobre", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do - account = case args do - [nick] -> Nola.Account.find_always_by_nick(m.network, m.channel, nick) - [] -> m.account - end + def handle_info( + {:irc, :trigger, "sobre", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, + state + ) do + account = + case args do + [nick] -> Nola.Account.find_always_by_nick(m.network, m.channel, nick) + [] -> m.account + end if account do user = Nola.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) 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) - |> Timex.Timezone.convert("Europe/Paris") - at = if now.day == sober.day do - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "aujourd'hui {h24}:{m}", "fr") + + sober = + now + |> DateTime.add(round(stats.sober_in * 60), :second) + |> Timex.Timezone.convert("Europe/Paris") + + at = + if now.day == sober.day do + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat( + sober, + "aujourd'hui {h24}:{m}", + "fr" + ) + detail else - {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") + {:ok, detail} = + Timex.Format.DateTime.Formatters.Default.lformat(sober, "{WDfull} {h24}:{m}", "fr") + detail end + m.replyfun.("#{nick} sera sobre #{at} (dans #{stats.sober_in_s})!") else m.replyfun.("#{nick} est déjà sobre. aidez le !") @@ -638,61 +923,85 @@ defmodule Nola.Plugins.Alcoolog do else m.replyfun.("inconnu") end + {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolisme", m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :dot}}}, state) do - nicks = Nola.Membership.expanded_members_or_friends(m.account, m.network, m.channel) - |> Enum.map(fn({account, _, nick}) -> {nick, get_full_statistics(state, account.id)} end) - |> Enum.filter(fn({_nick, status}) -> status && (status.active > 0 || status.active30m > 0) end) - |> Enum.sort_by(fn({_, status}) -> status.active end, &>/2) - |> Enum.map(fn({nick, status}) -> - trend_symbol = if status.active_drinks > 1 do - Enum.join(for(_ <- 1..status.active_drinks, do: status.trend_symbol)) - else - status.trend_symbol - end - since_str = if status.since_min > 180 do - "depuis: #{status.since_s} | " + def handle_info( + {:irc, :trigger, "alcoolisme", + m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :dot}}}, + state + ) do + nicks = + Nola.Membership.expanded_members_or_friends(m.account, m.network, m.channel) + |> Enum.map(fn {account, _, nick} -> {nick, get_full_statistics(state, account.id)} end) + |> Enum.filter(fn {_nick, status} -> + status && (status.active > 0 || status.active30m > 0) + end) + |> Enum.sort_by(fn {_, status} -> status.active end, &>/2) + |> Enum.map(fn {nick, status} -> + trend_symbol = + if status.active_drinks > 1 do + Enum.join(for(_ <- 1..status.active_drinks, do: status.trend_symbol)) + else + status.trend_symbol + end + + 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("") + + msg = + if nicks == "" do + "wtf?!?! personne n'a bu!" else - "" + nicks 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("") - - msg = if nicks == "" do - "wtf?!?! personne n'a bu!" - else - nicks - end m.replyfun.(msg) {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolisme", m = %Nola.Message{trigger: %Nola.Trigger{args: [time], type: :dot}}}, state) do - time = case time do - "semaine" -> 7 - string -> - case Integer.parse(string) do - {time, "j"} -> time - {time, "J"} -> time - _ -> nil - end - end + def handle_info( + {:irc, :trigger, "alcoolisme", + m = %Nola.Message{trigger: %Nola.Trigger{args: [time], type: :dot}}}, + state + ) do + time = + case time do + "semaine" -> + 7 + + string -> + case Integer.parse(string) do + {time, "j"} -> time + {time, "J"} -> time + _ -> nil + end + end if time do - aday = time*((24 * 60)*60) + aday = time * (24 * 60 * 60) now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) + + before = + now + |> DateTime.add(-aday, :second) + |> DateTime.to_unix(:millisecond) + over_time_stats(before, time, m, state) else m.replyfun.(".alcooolisme semaine|Xj") end + {:noreply, state} end @@ -701,30 +1010,37 @@ defmodule Nola.Plugins.Alcoolog do end def user_over_time(state, account, count) do - delay = count*((24 * 60)*60) + delay = count * (24 * 60 * 60) now = DateTime.utc_now() - before = DateTime.utc_now() - |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) - |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) - |> DateTime.to_unix(:millisecond) - #[ -# {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, -# [{:andalso, {:==, :"$1", :"$1"}, {:<, :"$2", {:const, 3000}}}], [:lol]} - #] - match = [{{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, + + before = + DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) + |> DateTime.to_unix(:millisecond) + + # [ + # {{{:"$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() + :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() Map.put(acc, date, Map.get(acc, date, 0) + vol) end) @@ -733,164 +1049,219 @@ defmodule Nola.Plugins.Alcoolog do def user_over_time_gl(account, count) do state = data_state() meta = get_user_meta(state, account.id) - delay = count*((24 * 60)*60) + delay = count * (24 * 60 * 60) now = DateTime.utc_now() - before = DateTime.utc_now() - |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) - |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) - |> DateTime.to_unix(:millisecond) - #[ -# {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, -# [{:andalso, {:==, :"$1", :"$1"}, {:<, :"$2", {:const, 3000}}}], [:lol]} - #] - match = [{{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, + + before = + DateTime.utc_now() + |> DateTime.shift_zone!("Europe/Paris", Tzdata.TimeZoneDatabase) + |> DateTime.add(-delay, :second, Tzdata.TimeZoneDatabase) + |> DateTime.to_unix(:millisecond) + + # [ + # {{{:"$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) + |> 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() - date = if date.hour <= 8 do - DateTime.add(date, -(60*(60*(date.hour+1))), :second, Tzdata.TimeZoneDatabase) - else - date - end - |> DateTime.to_date() weight = meta.weight k = if meta.sex, do: 0.7, else: 0.6 - gl = (10*vol)/(k*weight) + gl = 10 * vol / (k * weight) 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"}, :_, :_, :_, :_, :_, :_, :_}, - [{:>, :"$1", {:const, before}}], [:"$_"]} + # 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} + + # tuple ets: {{nick, date}, volumes, current, nom, commentaire} members = Nola.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) - top = Enum.reduce(drinks, %{}, fn({{nick, _}, vol, _, _, _, _, _, _}, acc) -> - all = Map.get(acc, nick, 0) - Map.put(acc, nick, all + vol) - end) - |> Enum.sort_by(fn({_nick, count}) -> count end, &>/2) - |> Enum.map(fn({nick, count}) -> - account = Nola.Account.get(nick) - user = Nola.UserTrack.find_by_account(m.network, account) - nick = if(user, do: user.nick, else: account.name) - "#{nick}: #{Float.round(count, 4)}" - end) - |> Enum.intersperse(", ") + drinks = + :ets.select(state.ets, match) + |> 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 -> + all = Map.get(acc, nick, 0) + Map.put(acc, nick, all + vol) + end) + |> Enum.sort_by(fn {_nick, count} -> count end, &>/2) + |> Enum.map(fn {nick, count} -> + account = Nola.Account.get(nick) + user = Nola.UserTrack.find_by_account(m.network, account) + nick = if(user, do: user.nick, else: account.name) + "#{nick}: #{Float.round(count, 4)}" + end) + |> Enum.intersperse(", ") m.replyfun.("sur #{j} jours: #{top}") {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolisme", m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :plus}}}, state) do + def handle_info( + {:irc, :trigger, "alcoolisme", + m = %Nola.Message{trigger: %Nola.Trigger{args: [], type: :plus}}}, + state + ) do meta = get_user_meta(state, m.account.id) hf = if meta.sex, do: "h", else: "f" - m.replyfun.("+alcoolisme sexe: #{hf} poids: #{meta.weight} facteur de perte: #{meta.loss_factor}") + + m.replyfun.( + "+alcoolisme sexe: #{hf} poids: #{meta.weight} facteur de perte: #{meta.loss_factor}" + ) + {:noreply, state} end - def handle_info({:irc, :trigger, "alcoolisme", m = %Nola.Message{trigger: %Nola.Trigger{args: [h, weight | rest], type: :plus}}}, state) do - h = case h do - "h" -> true - "f" -> false - _ -> nil - end + def handle_info( + {:irc, :trigger, "alcoolisme", + m = %Nola.Message{trigger: %Nola.Trigger{args: [h, weight | rest], type: :plus}}}, + state + ) do + h = + case h do + "h" -> true + "f" -> false + _ -> nil + end - weight = case Util.float_paparse(weight) do + weight = + case Util.float_paparse(weight) do {weight, _} -> weight _ -> nil end - {factor} = case rest do + {factor} = + case rest do [factor] -> case Util.float_paparse(factor) do {float, _} -> {float} _ -> {@default_user_meta.loss_factor} end - _ -> {@default_user_meta.loss_factor} + + _ -> + {@default_user_meta.loss_factor} end - if h == nil || weight == nil do - m.replyfun.("paramètres invalides") - else - old_meta = get_user_meta(state, m.account.id) - meta = Map.merge(@default_user_meta, %{sex: h, weight: weight, loss_factor: factor}) - put_user_meta(state, m.account.id, meta) - cond do - old_meta.weight < meta.weight -> - Nola.Plugins.Txt.reply_random(m, "alcoolog.fatter") - old_meta.weight == meta.weight -> - m.replyfun.("aucun changement!") - true -> - Nola.Plugins.Txt.reply_random(m, "alcoolog.thinner") - end + if h == nil || weight == nil do + m.replyfun.("paramètres invalides") + else + old_meta = get_user_meta(state, m.account.id) + meta = Map.merge(@default_user_meta, %{sex: h, weight: weight, loss_factor: factor}) + put_user_meta(state, m.account.id, meta) + + cond do + old_meta.weight < meta.weight -> + Nola.Plugins.Txt.reply_random(m, "alcoolog.fatter") + + old_meta.weight == meta.weight -> + m.replyfun.("aucun changement!") + + true -> + Nola.Plugins.Txt.reply_random(m, "alcoolog.thinner") end + end {:noreply, state} end - def handle_info({:irc, :trigger, "santai", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :minus}}}, state) do + def handle_info( + {:irc, :trigger, "santai", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :minus}}}, + state + ) do case get_statistics_for_nick(state, m.account.id) do {_, 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}") Nola.Plugins.Txt.reply_random(m, "alcoolog.delete") - notify = Nola.Membership.notify_channels(m.account) -- [{m.network,m.channel}] + notify = Nola.Membership.notify_channels(m.account) -- [{m.network, m.channel}] + for {net, chan} <- notify do user = Nola.UserTrack.find_by_account(net, m.account) nick = if(user, do: user.nick, else: m.account.name) - Nola.Irc.Connection.broadcast_message(net, chan, "#{nick} -santai #{points} #{type} #{descr}") + + Nola.Irc.Connection.broadcast_message( + net, + chan, + "#{nick} -santai #{points} #{type} #{descr}" + ) end + {:noreply, state} + _ -> {:noreply, state} end end + def handle_info( + {:irc, :trigger, "alcoolisme", + m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, + state + ) do + {account, duration} = + case args do + [nick | rest] -> {Nola.Account.find_always_by_nick(m.network, m.channel, nick), rest} + [] -> {m.account, []} + end - def handle_info({:irc, :trigger, "alcoolisme", m = %Nola.Message{trigger: %Nola.Trigger{args: args, type: :bang}}}, state) do - {account, duration} = case args do - [nick | rest] -> {Nola.Account.find_always_by_nick(m.network, m.channel, nick), rest} - [] -> {m.account, []} - end if account do - duration = case duration do - ["semaine"] -> 7 - [j] -> - case Integer.parse(j) do - {j, "j"} -> j - _ -> nil - end - _ -> nil - end + duration = + case duration do + ["semaine"] -> + 7 + + [j] -> + case Integer.parse(j) do + {j, "j"} -> j + _ -> nil + end + + _ -> + nil + end + user = Nola.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) + if duration do if duration > 90 do m.replyfun.("trop gros, ça rentrera pas") else # duration stats - stats = user_over_time(state, account, duration) - |> Enum.sort_by(fn({k,_v}) -> k end, {:asc, Date}) - |> Enum.map(fn({date, count}) -> - "#{date.day}: #{Float.round(count, 2)}" - end) - |> Enum.intersperse(", ") - |> Enum.join("") + stats = + user_over_time(state, account, duration) + |> Enum.sort_by(fn {k, _v} -> k end, {:asc, Date}) + |> Enum.map(fn {date, count} -> + "#{date.day}: #{Float.round(count, 2)}" + end) + |> Enum.intersperse(", ") + |> Enum.join("") if stats == "" do m.replyfun.("alcoolisme a zéro sur #{duration}j :/") @@ -900,47 +1271,67 @@ defmodule Nola.Plugins.Alcoolog do end else if stats = get_full_statistics(state, account.id) do - trend_symbol = if stats.active_drinks > 1 do - Enum.join(for(_ <- 1..stats.active_drinks, do: stats.trend_symbol)) - else - stats.trend_symbol - end - # TODO: Lookup nick for account_id - msg = "#{nick} #{stats.user_status} " - <> (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: "") + trend_symbol = + if stats.active_drinks > 1 do + Enum.join(for(_ <- 1..stats.active_drinks, do: stats.trend_symbol)) + else + stats.trend_symbol + end + + # TODO: Lookup nick for account_id + msg = + "#{nick} #{stats.user_status} " <> + 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: "" m.replyfun.(msg) else m.replyfun.("honteux mais #{nick} n'a pas l'air alcoolique du tout. /kick") end end - else + else m.replyfun.("je ne connais pas cet utilisateur") end + {:noreply, state} end - # Account merge def handle_info({:account_change, old_id, new_id}, state) do 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}") + + 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) end) + case :dets.lookup(state.meta, {:meta, old_id}) do [{_, meta}] -> :dets.delete(state.meta, {:meta, old_id}) :dets.insert(state.meta, {{:meta, new_id}, meta}) + _ -> :ok end + {:noreply, state} end @@ -951,7 +1342,13 @@ defmodule Nola.Plugins.Alcoolog do end end - defp rename_object_owner(table, ets, object = {old_id, date, volume, current, cl, deg, name, comment, meta}, 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, cl, deg, name, comment, meta}) @@ -960,58 +1357,75 @@ defmodule Nola.Plugins.Alcoolog do # Account: move from nick to account id def handle_info({:accounts, accounts}, state) do - #for x={:account, _, _, _, _} <- accounts, do: handle_info(x, state) - #{:noreply, state} - mapping = Enum.reduce(accounts, Map.new, fn({:account, _net, _chan, nick, account_id}, acc) -> - Map.put(acc, String.downcase(nick), account_id) - end) + # for x={:account, _, _, _, _} <- accounts, do: handle_info(x, state) + # {:noreply, state} + 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", :_, :_, :_, :_, :_, :_, :_, :_}, [], [:"$_"]}] - Logger.debug("accounts:: mappings #{inspect mapping}") - 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)}") + Logger.debug("accounts:: mappings #{inspect(mapping)}") + + 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}") rename_object_owner(table, state.ets, obj, nick, new_id) end end) + {:noreply, state} end def handle_info({:account, _net, _chan, nick, account_id}, state) do nick = String.downcase(nick) spec = [{{:"$1", :_, :_, :_, :_, :_, :_, :_, :_}, [{:==, :"$1", {:const, nick}}], [:"$_"]}] - Util.ets_mutate_select_each(:dets, state.dets, spec, fn(table, obj) -> + + 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) end) + case :dets.lookup(state.meta, {:meta, nick}) do [{_, meta}] -> :dets.delete(state.meta, {:meta, nick}) :dets.insert(state.meta, {{:meta, account_id}, meta}) + _ -> :ok end + {:noreply, state} end def handle_info(t, state) do - Logger.debug("#{__MODULE__}: unhandled info #{inspect t}") + Logger.debug("#{__MODULE__}: unhandled info #{inspect(t)}") {:noreply, state} end def nick_history(account) do spec = [ - {{{:"$1", :_}, :_, :_, :_, :_, :_, :_, :_}, - [{:==, :"$1", {:const, account.id}}], - [:"$_"]} + {{{:"$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, _cl, _deg, _type, _descr, _meta}, acc) -> acc + (points||0) end) + 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, _cl, _deg, _type, _descr, _meta}, + acc -> + acc + (points || 0) + end) + last = List.last(qvc) || nil {count, last} end @@ -1022,26 +1436,34 @@ defmodule Nola.Plugins.Alcoolog do def format_points(int) when is_integer(int) and int > 0 do "+#{Integer.to_string(int)}" end + def format_points(int) when is_integer(int) and int < 0 do Integer.to_string(int) end + def format_points(int) when is_float(int) and int > 0 do - "+#{Float.to_string(Float.round(int,4))}" + "+#{Float.to_string(Float.round(int, 4))}" end + def format_points(int) when is_float(int) and int < 0 do - Float.to_string(Float.round(int,4)) + Float.to_string(Float.round(int, 4)) end + def format_points(0), do: "0" def format_points(0.0), do: "0" defp format_relative_timestamp(timestamp) do alias Timex.Format.DateTime.Formatters alias Timex.Timezone - date = timestamp - |> DateTime.from_unix!(:millisecond) - |> Timezone.convert("Europe/Paris") - {:ok, relative} = Formatters.Relative.relative_to(date, Timex.now("Europe/Paris"), "{relative}", "fr") + date = + timestamp + |> DateTime.from_unix!(:millisecond) + |> Timezone.convert("Europe/Paris") + + {:ok, relative} = + Formatters.Relative.relative_to(date, Timex.now("Europe/Paris"), "{relative}", "fr") + {:ok, detail} = Formatters.Default.lformat(date, " ({h24}:{m})", "fr") relative <> detail @@ -1056,10 +1478,12 @@ defmodule Nola.Plugins.Alcoolog do case :dets.lookup(meta, {:meta, account_id}) do [{{:meta, _}, meta}] -> Map.merge(@default_user_meta, meta) + _ -> @default_user_meta end end + # Calcul g/l actuel: # 1. load user meta # 2. foldr ets @@ -1077,12 +1501,15 @@ defmodule Nola.Plugins.Alcoolog do defp user_stats(state = %{ets: ets}, account_id) do meta = get_user_meta(state, account_id) - aday = (10 * 60)*60 + aday = 10 * 60 * 60 now = DateTime.utc_now() - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) + + before = + now + |> DateTime.add(-aday, :second) + |> DateTime.to_unix(:millisecond) + + # match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) match = [ {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, [ @@ -1090,43 +1517,57 @@ defmodule Nola.Plugins.Alcoolog do {:"=:=", {:const, account_id}, :"$1"} ], [:"$_"]} ] - # tuple ets: {{nick, date}, volumes, current, nom, commentaire} + + # 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) -> - acc + volume - end) + total_volume = + Enum.reduce(drinks, 0.0, fn {{_, date}, volume, _, _, _, _, _, _}, acc -> + acc + volume + end) + k = if meta.sex, do: 0.7, else: 0.6 weight = meta.weight - gl = (10*total_volume)/(k*weight) + gl = 10 * total_volume / (k * weight) {Float.round(total_volume + 0.0, 4), Float.round(gl + 0.0, 4)} end defp alcohol_level_rising(state, account_id, minutes \\ 30) do {now, _} = current_alcohol_level(state, account_id) - soon_date = DateTime.utc_now - |> DateTime.add(minutes*60, :second) + + soon_date = + DateTime.utc_now() + |> DateTime.add(minutes * 60, :second) + {soon, _} = current_alcohol_level(state, account_id, soon_date) - soon = cond do - soon < 0 -> 0.0 - true -> soon - end - #IO.puts "soon #{soon_date} - #{inspect soon} #{inspect now}" - {soon > now, Float.round(soon+0.0, 4)} + + soon = + cond do + soon < 0 -> 0.0 + true -> soon + end + + # IO.puts "soon #{soon_date} - #{inspect soon} #{inspect now}" + {soon > now, Float.round(soon + 0.0, 4)} end defp current_alcohol_level(state = %{ets: ets}, account_id, now \\ nil) do meta = get_user_meta(state, account_id) - aday = ((24*7) * 60)*60 - now = if now do + aday = 24 * 7 * 60 * 60 + + now = + if now do + now + else + DateTime.utc_now() + end + + before = now - else - DateTime.utc_now() - end - before = now - |> DateTime.add(-aday, :second) - |> DateTime.to_unix(:millisecond) - #match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) + |> DateTime.add(-aday, :second) + |> DateTime.to_unix(:millisecond) + + # match = :ets.fun2ms(fn(obj = {{^nick, date}, _, _, _, _}) when date > before -> obj end) match = [ {{{:"$1", :"$2"}, :_, :_, :_, :_, :_, :_, :_}, [ @@ -1134,59 +1575,77 @@ defmodule Nola.Plugins.Alcoolog do {:"=:=", {:const, account_id}, :"$1"} ], [:"$_"]} ] - # tuple ets: {{nick, date}, volumes, current, nom, commentaire} - drinks = :ets.select(ets, match) - |> Enum.sort_by(fn({{_, date}, _, _, _, _, _, _, _}) -> date end, &</2) + + # tuple ets: {{nick, date}, volumes, current, nom, commentaire} + drinks = + :ets.select(ets, match) + |> 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}) -> - k = if meta.sex, do: 0.7, else: 0.6 - weight = meta.weight - peak = (10*volume)/(k*weight) - date = case date do - ts when is_integer(ts) -> DateTime.from_unix!(ts, :millisecond) - date = %NaiveDateTime{} -> DateTime.from_naive!(date, "Etc/UTC") - date = %DateTime{} -> date - end - last_at = last_at || date - mins_since = round(DateTime.diff(now, date)/60.0) - #IO.puts "Drink: #{inspect({date, volume})} - mins since: #{inspect mins_since} - last drink at #{inspect last_at}" - # Apply loss since `last_at` on `all` - # - all = if last_at do - mins_since_last = round(DateTime.diff(date, last_at)/60.0) - loss = ((meta.loss_factor/100)/60)*(mins_since_last) - #IO.puts "Applying last drink loss: from #{all}, loss of #{inspect loss} (mins since #{inspect mins_since_first})" - cond do - (all-loss) > 0 -> all - loss - true -> 0.0 + {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) + + date = + case date do + ts when is_integer(ts) -> DateTime.from_unix!(ts, :millisecond) + date = %NaiveDateTime{} -> DateTime.from_naive!(date, "Etc/UTC") + date = %DateTime{} -> date + end + + last_at = last_at || date + mins_since = round(DateTime.diff(now, date) / 60.0) + + # IO.puts "Drink: #{inspect({date, volume})} - mins since: #{inspect mins_since} - last drink at #{inspect last_at}" + # Apply loss since `last_at` on `all` + # + all = + if last_at do + mins_since_last = round(DateTime.diff(date, last_at) / 60.0) + loss = meta.loss_factor / 100 / 60 * mins_since_last + + # IO.puts "Applying last drink loss: from #{all}, loss of #{inspect loss} (mins since #{inspect mins_since_first})" + cond do + all - loss > 0 -> all - loss + true -> 0.0 + end + else + all + end + + # IO.puts "Applying last drink current before drink: #{inspect all}" + if mins_since < 30 do + per_min = peak / 30.0 + current = per_min * mins_since + + # IO.puts "Applying current drink 30m: from #{peak}, loss of #{inspect per_min}/min (mins since #{inspect mins_since})" + {all + current, date, [{date, current} | acc], active_drinks + 1} + else + {all + peak, date, [{date, peak} | acc], active_drinks} end + end) + + # IO.puts "last drink #{inspect last_drink_at}" + mins_since_last = + if last_drink_at do + round(DateTime.diff(now, last_drink_at) / 60.0) else - all + 0 end - #IO.puts "Applying last drink current before drink: #{inspect all}" - if mins_since < 30 do - per_min = (peak)/30.0 - current = (per_min*mins_since) - #IO.puts "Applying current drink 30m: from #{peak}, loss of #{inspect per_min}/min (mins since #{inspect mins_since})" - {all + current, date, [{date, current} | acc], active_drinks + 1} + + # Si on a déjà bu y'a déjà moins 15 minutes (big up le binge drinking), on applique plus de perte + level = + if mins_since_last > 15 do + loss = meta.loss_factor / 100 / 60 * mins_since_last + Float.round(all - loss, 4) else - {all + peak, date, [{date, peak} | acc], active_drinks} + all end - end) - #IO.puts "last drink #{inspect last_drink_at}" - mins_since_last = if last_drink_at do - round(DateTime.diff(now, last_drink_at)/60.0) - else - 0 - end - # Si on a déjà bu y'a déjà moins 15 minutes (big up le binge drinking), on applique plus de perte - level = if mins_since_last > 15 do - loss = ((meta.loss_factor/100)/60)*(mins_since_last) - Float.round(all - loss, 4) - else - all - end - #IO.puts "\n LEVEL #{inspect level}\n\n\n\n" + + # IO.puts "\n LEVEL #{inspect level}\n\n\n\n" cond do level < 0 -> {0.0, 0} true -> {level, active_drinks} @@ -1194,23 +1653,31 @@ defmodule Nola.Plugins.Alcoolog do end defp format_duration_from_now(date, with_detail \\ true) do - date = if is_integer(date) do - date = DateTime.from_unix!(date, :millisecond) + date = + if is_integer(date) do + date = + DateTime.from_unix!(date, :millisecond) + |> Timex.Timezone.convert("Europe/Paris") + else + Util.to_naive_date_time(date) + end + + now = + DateTime.utc_now() |> Timex.Timezone.convert("Europe/Paris") - else - Util.to_naive_date_time(date) - end - now = DateTime.utc_now() - |> Timex.Timezone.convert("Europe/Paris") + {:ok, detail} = Timex.Format.DateTime.Formatters.Default.lformat(date, "({h24}:{m})", "fr") - mins_since = round(DateTime.diff(now, date)/60.0) + mins_since = round(DateTime.diff(now, date) / 60.0) + if ago = format_minute_duration(mins_since) do - word = if mins_since > 0 do - "il y a " - else - "dans " - end + word = + if mins_since > 0 do + "il y a " + else + "dans " + end + word <> ago <> if(with_detail, do: " #{detail}", else: "") else "maintenant #{detail}" @@ -1218,12 +1685,12 @@ defmodule Nola.Plugins.Alcoolog do end defp format_minute_duration(minutes) do - sober_in_s = if (minutes != 0) do - duration = Timex.Duration.from_minutes(minutes) - Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) - else - nil - end + sober_in_s = + if minutes != 0 do + duration = Timex.Duration.from_minutes(minutes) + Timex.Format.Duration.Formatter.lformat(duration, "fr", :humanized) + else + nil + end end - end |