diff --git a/lib/util.ex b/lib/util.ex index 8bd3b9d..a402519 100644 --- a/lib/util.ex +++ b/lib/util.ex @@ -1,74 +1,86 @@ defmodule Util do + defmodule Map do + + def put_if_not_null(map, _key, nil) do + map + end + + def put_if_not_null(map, key, value) do + Elixir.Map.put(map, key, value) + end + + end + def to_naive_date_time(naive = %NaiveDateTime{}), do: naive def to_naive_date_time(datetime = %DateTime{}), do: DateTime.to_naive(datetime) def to_naive_date_time(timestamp) when is_integer(timestamp) do timestamp |> to_date_time() |> to_naive_date_time() end def to_date_time(naive_or_timestamp, timezone \\ "Europe/Paris") def to_date_time(date = %DateTime{}, timezone) do DateTime.shift_zone!(date, timezone, Tzdata.TimeZoneDatabase) end def to_date_time(naive = %NaiveDateTime{}, timezone) do DateTime.from_naive!(naive, timezone, Tzdata.TimeZoneDatabase) end # todo: this is wrong. def to_date_time(timestamp, timezone) when is_integer(timestamp) do timestamp |> DateTime.from_unix!(:millisecond) |> DateTime.shift_zone!(timezone, Tzdata.TimeZoneDatabase) end def plusminus(number) when number > 0, do: "+#{number}" def plusminus(0), do: "0" def plusminus(number) when number < 0, do: "#{number}" 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 \\ [{:"$1", [], [:"$1"]}], fun) do ets.safe_fixtable(table, true) first = ets.select(table, spec, 1) do_ets_mutate_select_each(ets, table, fun, first) after ets.safe_fixtable(table, false) end defp do_ets_mutate_select_each(_, _, _, :'$end_of_table') do :ok end defp do_ets_mutate_select_each(ets, table, fun, {objs, continuation}) do for obj <- objs, do: fun.(table, obj) do_ets_mutate_select_each(ets, table, fun, ets.select(continuation)) end def ets_mutate_each(ets, table, fun) do ets.safe_fixtable(table, true) first = ets.first(table) do_ets_mutate_each(ets, table, fun, first) after ets.safe_fixtable(table, false) end defp do_ets_mutate_each(ets, table, fun, key) do case ets.lookup(table, key) do [elem] -> fun.(table, elem) _ -> nil end do_ets_mutate_each(ets, table, fun, ets.next(table, key)) end end diff --git a/lib/web/controllers/irc_controller.ex b/lib/web/controllers/irc_controller.ex index e87382b..a78582e 100644 --- a/lib/web/controllers/irc_controller.ex +++ b/lib/web/controllers/irc_controller.ex @@ -1,101 +1,101 @@ defmodule NolaWeb.IrcController do use NolaWeb, :controller plug NolaWeb.ContextPlug def index(conn, params) do network = Map.get(params, "network") channel = if c = Map.get(params, "chan"), do: NolaWeb.reformat_chan(c) commands = for mod <- Enum.uniq([Nola.Plugins.Account] ++ Nola.Plugins.enabled()) do if is_atom(mod) do identifier = Module.split(mod) |> List.last |> Macro.underscore - {identifier, mod.irc_doc()} + if Kernel.function_exported?(mod, :irc_doc, 0), do: {identifier, mod.irc_doc()} end end |> Enum.filter(& &1) |> Enum.filter(fn({_, doc}) -> doc end) members = cond do network && channel -> Enum.map(Nola.UserTrack.channel(network, channel), fn(tuple) -> Nola.UserTrack.User.from_tuple(tuple) end) true -> Nola.Membership.of_account(conn.assigns.account) end render conn, "index.html", network: network, commands: commands, channel: channel, members: members end def txt(conn, %{"name" => name}) do if String.contains?(name, ".txt") do name = String.replace(name, ".txt", "") data = data() if Map.has_key?(data, name) do lines = Enum.join(data[name], "\n") text(conn, lines) else conn |> put_status(404) |> text("Not found") end else do_txt(conn, name) end end def txt(conn, _), do: do_txt(conn, nil) defp do_txt(conn, nil) do doc = Nola.Plugins.Txt.irc_doc() data = data() main = Enum.filter(data, fn({trigger, _}) -> !String.contains?(trigger, ".") end) |> Enum.into(Map.new) system = Enum.filter(data, fn({trigger, _}) -> String.contains?(trigger, ".") end) |> Enum.into(Map.new) lines = Enum.reduce(main, 0, fn({_, lines}, acc) -> acc + Enum.count(lines) end) conn |> assign(:title, "txt") |> render("txts.html", data: main, doc: doc, files: Enum.count(main), lines: lines, system: system) end defp do_txt(conn, txt) do data = data() base_url = cond do conn.assigns[:chan] -> "/#{conn.assigns.network}/#{NolaWeb.format_chan(conn.assigns.chan)}" true -> "/-" end 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: lines, doc: nil) else conn |> put_status(404) |> text("Not found") end end defp data() do dir = Application.get_env(:nola, :data_path) <> "/irc.txt/" Path.wildcard(dir <> "/*.txt") |> Enum.reduce(%{}, fn(path, m) -> path = String.split(path, "/") file = List.last(path) key = String.replace(file, ".txt", "") data = dir <> file |> File.read! |> String.split("\n") |> Enum.reject(fn(line) -> cond do line == "" -> true !line -> true true -> false end end) Map.put(m, key, data) end) |> Enum.sort |> Enum.into(Map.new) end end diff --git a/lib/web/live/chat_live.ex b/lib/web/live/chat_live.ex index 8a9f6f2..6902250 100644 --- a/lib/web/live/chat_live.ex +++ b/lib/web/live/chat_live.ex @@ -1,120 +1,121 @@ defmodule NolaWeb.ChatLive do use Phoenix.LiveView use Phoenix.HTML require Logger def mount(%{"network" => network, "chan" => chan}, %{"account" => account_id}, socket) do chan = NolaWeb.reformat_chan(chan) connection = Nola.Irc.Connection.get_network(network, chan) account = Nola.Account.get(account_id) - membership = Nola.Membership.of_account(Nola.Account.get("DRgpD4fLf8PDJMLp8Dtb")) + membership = Nola.Membership.of_account(Nola.Account.get(account.id)) if account && connection && Enum.member?(membership, {connection.network, chan}) do {:ok, _} = Registry.register(Nola.PubSub, "#{connection.network}:events", plugin: __MODULE__) for t <- ["messages", "triggers", "outputs", "events"] do {:ok, _} = Registry.register(Nola.PubSub, "#{connection.network}/#{chan}:#{t}", plugin: __MODULE__) end Nola.Irc.PuppetConnection.start(account, connection) users = Nola.UserTrack.channel(connection.network, chan) |> Enum.map(fn(tuple) -> Nola.UserTrack.User.from_tuple(tuple) end) |> Enum.reduce(Map.new, fn(user = %{id: id}, acc) -> Map.put(acc, id, user) end) backlog = case Nola.Plugins.Buffer.select_buffer(connection.network, chan) do {backlog, _} -> {backlog, _} = Enum.reduce(backlog, {backlog, nil}, &reduce_contextual_event/2) Enum.reverse(backlog) _ -> [] end socket = socket |> assign(:connection_id, connection.id) |> assign(:network, connection.network) |> assign(:chan, chan) |> assign(:title, "live") |> assign(:channel, chan) |> assign(:account_id, account.id) |> assign(:backlog, backlog) |> assign(:users, users) |> assign(:counter, 0) {:ok, socket} else {:ok, redirect(socket, to: "/")} end end def handle_event("send", %{"message" => %{"text" => text}}, socket) do account = Nola.Account.get(socket.assigns.account_id) Nola.Irc.send_message_as(account, socket.assigns.network, socket.assigns.channel, text, true) {:noreply, assign(socket, :counter, socket.assigns.counter + 1)} end def handle_info({:irc, :event, event = %{type: :join, user_id: id}}, socket) do if user = Nola.UserTrack.lookup(id) do socket = socket |> assign(:users, Map.put(socket.assigns.users, id, user)) |> append_to_backlog(event) {:noreply, socket} else {:noreply, socket} end end def handle_info({:irc, :event, event = %{type: :nick, user_id: id, nick: nick}}, socket) do socket = socket |> assign(:users, update_in(socket.assigns.users, [id, :nick], nick)) |> append_to_backlog(event) {:noreply, socket} end def handle_info({:irc, :event, event = %{type: :quit, user_id: id}}, socket) do socket = socket |> assign(:users, Map.delete(socket.assigns.users, id)) |> append_to_backlog(event) {:noreply, socket} end def handle_info({:irc, :event, event = %{type: :part, user_id: id}}, socket) do socket = socket |> assign(:users, Map.delete(socket.assigns.users, id)) |> append_to_backlog(event) {:noreply, socket} end def handle_info({:irc, :trigger, _, message}, socket) do handle_info({:irc, nil, message}, socket) end - def handle_info({:irc, :text, message}, socket) do - IO.inspect({:live_message, message}) + # type is text, out, or nil if it's self? + def handle_info({:irc, type, message = %Nola.Message{}}, socket) do + IO.inspect({:live_message, type, message}) socket = socket |> append_to_backlog(message) {:noreply, socket} end def handle_info(info, socket) do Logger.debug("Unhandled info: #{inspect info}") {:noreply, socket} end defp append_to_backlog(socket, line) do {add, _} = reduce_contextual_event(line, {[], List.last(socket.assigns.backlog)}) assign(socket, :backlog, socket.assigns.backlog ++ add) end defp reduce_contextual_event(line, {acc, nil}) do {[line | acc], line} end defp reduce_contextual_event(line, {acc, last}) do if NaiveDateTime.to_date(last.at) != NaiveDateTime.to_date(line.at) do {[%{type: :day_changed, date: NaiveDateTime.to_date(line.at), at: nil}, line | acc], line} else {[line | acc], line} end end end diff --git a/lib/web/live/chat_live.html.heex b/lib/web/live/chat_live.html.heex index 470604f..c3bb030 100644 --- a/lib/web/live/chat_live.html.heex +++ b/lib/web/live/chat_live.html.heex @@ -1,91 +1,91 @@
Disconnected :'(
Oh no error >:(