diff options
Diffstat (limited to 'lib')
27 files changed, 394 insertions, 61 deletions
diff --git a/lib/irc/account.ex b/lib/irc/account.ex index 0aa8638..c835d55 100644 --- a/lib/irc/account.ex +++ b/lib/irc/account.ex @@ -268,7 +268,7 @@ defmodule IRC.Account do def irc_doc, do: @moduledoc def start_link(), do: GenServer.start_link(__MODULE__, [], name: __MODULE__) def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "message:private", []) + {:ok, _} = Registry.register(IRC.PubSub, "messages:private", []) {:ok, nil} end diff --git a/lib/irc/connection.ex b/lib/irc/connection.ex index e856114..eff5930 100644 --- a/lib/irc/connection.ex +++ b/lib/irc/connection.ex @@ -165,6 +165,10 @@ defmodule IRC.Connection do end end + def update_connection(connection) do + :dets.insert(dets(), to_tuple(connection)) + end + def start_link(conn) do GenServer.start_link(__MODULE__, [conn], name: {:global, conn.id}) end @@ -275,12 +279,12 @@ defmodule IRC.Connection do def handle_info({:received, text, sender, chan}, state) do reply_fun = fn(text) -> irc_reply(state, {chan, sender}, text) end account = IRC.Account.lookup(sender) - message = %IRC.Message{text: text, network: network(state), account: account, sender: sender, channel: chan, replyfun: reply_fun, trigger: extract_trigger(text)} + message = %IRC.Message{at: NaiveDateTime.utc_now(), text: text, network: network(state), account: account, sender: sender, channel: chan, replyfun: reply_fun, trigger: extract_trigger(text)} message = case IRC.UserTrack.messaged(message) do :ok -> message {:ok, message} -> message end - publish(message, ["message:#{chan}", "#{message.network}/#{chan}:message"]) + publish(message, ["#{message.network}/#{chan}:messages"]) {:noreply, state} end @@ -293,7 +297,7 @@ defmodule IRC.Connection do :ok -> message {:ok, message} -> message end - publish(message, ["message:private"]) + publish(message, ["messages:private", "#{message.network}/#{account.id}:messages"]) {:noreply, state} end @@ -336,8 +340,8 @@ defmodule IRC.Connection do {:noreply, state} end - def handle_info({:quit, _reason, sender}, state) do - IRC.UserTrack.quitted(sender) + def handle_info({:quit, reason, sender}, state) do + IRC.UserTrack.quitted(sender, reason) {:noreply, state} end @@ -378,18 +382,32 @@ defmodule IRC.Connection do def publish(pub), do: publish(pub, []) def publish(m = %IRC.Message{trigger: nil}, keys) do - dispatch(["message"] ++ keys, {:irc, :text, m}) + dispatch(["messages"] ++ keys, {:irc, :text, m}) end def publish(m = %IRC.Message{trigger: t = %IRC.Trigger{trigger: trigger}}, keys) do dispatch(["triggers", "#{m.network}/#{m.channel}:triggers", "trigger:"<>trigger], {:irc, :trigger, trigger, m}) end + def publish_event(net, event = %{type: _}) when is_binary(net) do + event = event + |> Map.put(:at, NaiveDateTime.utc_now()) + |> Map.put(:network, net) + dispatch("#{net}:events", {:irc, :event, event}) + end + def publish_event({net, chan}, event = %{type: type}) do + event = event + |> Map.put(:at, NaiveDateTime.utc_now()) + |> Map.put(:network, net) + |> Map.put(:channel, chan) + dispatch("#{net}/#{chan}:events", {:irc, :event, event}) + end + def dispatch(keys, content, sub \\ IRC.PubSub) def dispatch(key, content, sub) when is_binary(key), do: dispatch([key], content, sub) def dispatch(keys, content, sub) when is_list(keys) do - IO.puts "dispatching to #{inspect({sub,keys})} --> #{inspect content}" + Logger.debug("dispatch #{inspect keys} = #{inspect content}") for key <- keys do spawn(fn() -> Registry.dispatch(sub, key, fn h -> for {pid, _} <- h, do: send(pid, content) @@ -419,13 +437,15 @@ defmodule IRC.Connection do # irc_reply(ExIRC.Client pid, {channel or nick, ExIRC.Sender}, binary | replies # replies :: {:kick, reason} | {:kick, nick, reason} | {:mode, mode, nick} - defp irc_reply(%{client: client, network: network}, {target, _}, text) when is_binary(text) or is_list(text) do + defp irc_reply(state = %{client: client, network: network}, {target, _}, text) when is_binary(text) or is_list(text) do lines = IRC.splitlong(text) |> Enum.map(fn(x) -> if(is_list(x), do: x, else: String.split(x, "\n")) end) |> List.flatten() - for line <- lines do + outputs = for line <- lines do ExIRC.Client.msg(client, :privmsg, target, line) + {:irc, :out, %IRC.Message{network: network, channel: target, text: line, sender: %ExIRC.SenderInfo{nick: state.conn.nick}, at: NaiveDateTime.utc_now()}} end + for f <- outputs, do: dispatch(["irc:outputs", "#{network}/#{target}:outputs"], f) case :global.whereis_name({LSG.TelegramRoom, network, target}) do pid when is_pid(pid) -> send(pid, {:raw, text}) _ -> :ok diff --git a/lib/irc/puppet_connection.ex b/lib/irc/puppet_connection.ex index 68f1425..8c9b218 100644 --- a/lib/irc/puppet_connection.ex +++ b/lib/irc/puppet_connection.ex @@ -52,6 +52,10 @@ defmodule IRC.PuppetConnection do GenServer.cast(pid, {:send_message, channel, text}) end + def start(account = %IRC.Account{}, connection = %IRC.Connection{}) do + IRC.PuppetConnection.Supervisor.start_child(account, connection) + end + def start_link(account_id, connection_id) do GenServer.start_link(__MODULE__, [account_id, connection_id], name: name(account_id, connection_id)) end diff --git a/lib/irc/user_track.ex b/lib/irc/user_track.ex index 5e1c3a3..4b1ee67 100644 --- a/lib/irc/user_track.ex +++ b/lib/irc/user_track.ex @@ -183,7 +183,7 @@ defmodule IRC.UserTrack do user = if user = find_by_nick(sender.network, nick) do %User{user | username: uname, host: host, privileges: Map.put(user.privileges || %{}, channel, privileges)} else - user = %User{network: sender.network, nick: nick, username: uname, host: host, privileges: %{channel => privileges}} + user = %User{id: IRC.UserTrack.Id.large_id, network: sender.network, nick: nick, username: uname, host: host, privileges: %{channel => privileges}} account = IRC.Account.lookup(user).id user = %User{user | account: account} @@ -197,6 +197,8 @@ defmodule IRC.UserTrack do Storage.op(fn(ets) -> :ets.insert(ets, User.to_tuple(user)) end) + + IRC.Connection.publish_event({sender.network, channel}, %{type: :join, user_id: user.id, account_id: user.account}) end #def joined(network, channel, nick, privileges) do @@ -235,6 +237,8 @@ defmodule IRC.UserTrack do account = IRC.Account.lookup(user, false) || old_account user = %User{user | nick: new_nick, account: account.id, nicks: [old_nick|user.nicks]} Storage.insert(User.to_tuple(user)) + channels = for {channel, _} <- user.privileges, do: channel + IRC.Connection.publish_event(network, %{type: :nick, user_id: user.id, account_id: account.id, nick: new_nick, old_nick: old_nick}) end end @@ -247,9 +251,11 @@ defmodule IRC.UserTrack do user = %User{user | privileges: Map.put(user.privileges, channel, privs)} Storage.insert(User.to_tuple(user)) + IRC.Connection.publish_event({network, channel}, %{type: :privileges, user_id: user.id, account_id: user.account, added: add, removed: remove}) end end + # XXX: Reason def parted(channel, %{network: network, nick: nick}) do parted(network, channel, nick) end @@ -265,16 +271,21 @@ defmodule IRC.UserTrack do if Enum.count(privs) > 0 do user = %User{user | privileges: privs} Storage.insert(User.to_tuple(user)) + IRC.Connection.publish_event({network, channel}, %{type: :part, user_id: user.id, account_id: user.account, reason: nil}) else + IRC.Connection.publish_event(network, %{type: :quit, user_id: user.id, account_id: user.account, reason: "Left all known channels"}) Storage.delete(user.id) end end end - def quitted(sender) do + def quitted(sender, reason) do if user = find_by_nick(sender.network, sender.nick) do if user.account do - for({channel, _} <- user.privileges, do: IRC.Membership.touch(user.account, sender.network, channel)) + for {channel, _} <- user.privileges do + IRC.Membership.touch(user.account, sender.network, channel) + end + IRC.Connection.publish_event(sender.network, %{type: :quit, user_id: user.id, account_id: user.account, reason: reason}) end Storage.delete(user.id) end @@ -288,4 +299,8 @@ defmodule IRC.UserTrack do %User{user | last_active: last_active} end + defp userchans(%{privileges: privileges}) do + for({chan, _} <- privileges, do: chan) + end + end diff --git a/lib/lsg/telegram.ex b/lib/lsg/telegram.ex index e8758e3..63940dc 100644 --- a/lib/lsg/telegram.ex +++ b/lib/lsg/telegram.ex @@ -206,7 +206,7 @@ defmodule LSG.Telegram do trigger: IRC.Connection.extract_trigger(trigger_text), at: nil } - IRC.Connection.publish(message, ["message:private", "message:telegram"]) + IRC.Connection.publish(message, ["messages:private", "messages:telegram", "telegram/#{account.id}:messages"]) message end diff --git a/lib/lsg/telegram_room.ex b/lib/lsg/telegram_room.ex index 9504cd4..1eeec8f 100644 --- a/lib/lsg/telegram_room.ex +++ b/lib/lsg/telegram_room.ex @@ -12,7 +12,7 @@ defmodule LSG.TelegramRoom do case IRC.Connection.get_network(net, chan) do %IRC.Connection{} -> :global.register_name({__MODULE__, net, chan}, self()) - {:ok, _} = Registry.register(IRC.PubSub, "#{net}/#{chan}:message", plugin: __MODULE__) + {:ok, _} = Registry.register(IRC.PubSub, "#{net}/#{chan}:messages", plugin: __MODULE__) {:ok, _} = Registry.register(IRC.PubSub, "#{net}/#{chan}:triggers", plugin: __MODULE__) err -> Logger.warn("Did not found telegram match for #{id} \"#{chat["title"]}\"") @@ -44,6 +44,10 @@ defmodule LSG.TelegramRoom do {:ok, state} end + def handle_info({:irc, _, _, message}, state) do + handle_info({:irc, nil, message}, state) + end + def handle_info({:irc, _, %IRC.Message{sender: %{nick: nick}, text: text}}, state) do LSG.Telegram.send_message(state.id, "<#{nick}> #{text}") {:ok, state} diff --git a/lib/lsg_irc/correction_plugin.ex b/lib/lsg_irc/correction_plugin.ex index f370cf8..a77c4a2 100644 --- a/lib/lsg_irc/correction_plugin.ex +++ b/lib/lsg_irc/correction_plugin.ex @@ -11,7 +11,7 @@ defmodule LSG.IRC.CorrectionPlugin do end def init(_) do - {:ok, _} = Registry.register(IRC.PubSub, "message", [plugin: __MODULE__]) + {:ok, _} = Registry.register(IRC.PubSub, "messages", [plugin: __MODULE__]) {:ok, _} = Registry.register(IRC.PubSub, "triggers", [plugin: __MODULE__]) {:ok, %{}} end diff --git a/lib/lsg_irc/last_fm_plugin.ex b/lib/lsg_irc/last_fm_plugin.ex index 04f887c..0c6b8a6 100644 --- a/lib/lsg_irc/last_fm_plugin.ex +++ b/lib/lsg_irc/last_fm_plugin.ex @@ -98,8 +98,7 @@ defmodule LSG.IRC.LastFmPlugin do username = case :dets.lookup(state.dets, id_or_user) do [{_, username}] -> username - _ -> - id_or_user + _ -> id_or_user end case now_playing(username) do @@ -108,7 +107,7 @@ defmodule LSG.IRC.LastFmPlugin do {:ok, map} when is_map(map) -> track = fetch_track(username, map) text = format_now_playing(map, track) - user = if account = IRC.Account.get(username) do + user = if account = IRC.Account.get(id_or_user) do user = IRC.UserTrack.find_by_account(message.network, account) if(user, do: user.nick, else: account.name) else diff --git a/lib/lsg_irc/link_plugin.ex b/lib/lsg_irc/link_plugin.ex index 0c785a0..3d657ad 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", [plugin: __MODULE__]) - #{:ok, _} = Registry.register(IRC.PubSub, "message:telegram", [plugin: __MODULE__]) + {:ok, _} = Registry.register(IRC.PubSub, "messages", [plugin: __MODULE__]) + #{:ok, _} = Registry.register(IRC.PubSub, "messages:telegram", [plugin: __MODULE__]) Logger.info("Link handler started") {:ok, %__MODULE__{}} end diff --git a/lib/lsg_irc/link_plugin/youtube.ex b/lib/lsg_irc/link_plugin/youtube.ex index fd0f1b4..536cab6 100644 --- a/lib/lsg_irc/link_plugin/youtube.ex +++ b/lib/lsg_irc/link_plugin/youtube.ex @@ -54,8 +54,8 @@ defmodule LSG.IRC.LinkPlugin.YouTube do |> Timex.format("{relative}", :relative) |> elem(1) - line = if Keyword.get(opts, :invidious, "yewtu.be") do - ["-> https://#{}host}/watch?v=#{video_id}"] + line = if host = Keyword.get(opts, :invidious, "yewtu.be") do + ["-> https://#{host}/watch?v=#{video_id}"] else [] end diff --git a/lib/lsg_irc/outline_plugin.ex b/lib/lsg_irc/outline_plugin.ex index d28d0d0..47fa6fa 100644 --- a/lib/lsg_irc/outline_plugin.ex +++ b/lib/lsg_irc/outline_plugin.ex @@ -21,7 +21,7 @@ defmodule LSG.IRC.OutlinePlugin do def init([]) do regopts = [plugin: __MODULE__] {:ok, _} = Registry.register(IRC.PubSub, "trigger:outline", regopts) - {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "messages", regopts) file = Path.join(LSG.data_path, "/outline.txt") hosts = case File.read(file) do {:error, :enoent} -> diff --git a/lib/lsg_irc/preums_plugin.ex b/lib/lsg_irc/preums_plugin.ex index 7bb2c78..68257f0 100644 --- a/lib/lsg_irc/preums_plugin.ex +++ b/lib/lsg_irc/preums_plugin.ex @@ -87,7 +87,7 @@ defmodule LSG.IRC.PreumsPlugin do def init([]) do regopts = [plugin: __MODULE__] {:ok, _} = Registry.register(IRC.PubSub, "account", regopts) - {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "messages", 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) -> diff --git a/lib/lsg_irc/say_plugin.ex b/lib/lsg_irc/say_plugin.ex index 085ca92..8e93ec2 100644 --- a/lib/lsg_irc/say_plugin.ex +++ b/lib/lsg_irc/say_plugin.ex @@ -21,7 +21,7 @@ defmodule LSG.IRC.SayPlugin do 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, _} = Registry.register(IRC.PubSub, "messages:private", regopts) {:ok, nil} end diff --git a/lib/lsg_irc/seen_plugin.ex b/lib/lsg_irc/seen_plugin.ex index f1a5473..405c372 100644 --- a/lib/lsg_irc/seen_plugin.ex +++ b/lib/lsg_irc/seen_plugin.ex @@ -13,7 +13,7 @@ defmodule LSG.IRC.SeenPlugin do def init([]) do regopts = [plugin: __MODULE__] {:ok, _} = Registry.register(IRC.PubSub, "triggers", regopts) - {:ok, _} = Registry.register(IRC.PubSub, "message", regopts) + {:ok, _} = Registry.register(IRC.PubSub, "messages", 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 a37fb4e..b183f7d 100644 --- a/lib/lsg_irc/sms_plugin.ex +++ b/lib/lsg_irc/sms_plugin.ex @@ -44,7 +44,7 @@ defmodule LSG.IRC.SmsPlugin do trigger: IRC.Connection.extract_trigger(trigger_text) } IO.puts("converted sms to message: #{inspect message}") - IRC.Connection.publish(message, ["message:sms"]) + IRC.Connection.publish(message, ["messages:sms"]) message end end diff --git a/lib/lsg_web.ex b/lib/lsg_web.ex index 113d00d..eb0cdc5 100644 --- a/lib/lsg_web.ex +++ b/lib/lsg_web.ex @@ -63,6 +63,8 @@ defmodule LSGWeb do import LSGWeb.Router.Helpers import LSGWeb.ErrorHelpers import LSGWeb.Gettext + + import Phoenix.LiveView.Helpers end end @@ -71,6 +73,7 @@ defmodule LSGWeb do use Phoenix.Router import Plug.Conn import Phoenix.Controller + import Phoenix.LiveView.Router end end diff --git a/lib/lsg_web/components/component.ex b/lib/lsg_web/components/component.ex new file mode 100644 index 0000000..37d75e3 --- /dev/null +++ b/lib/lsg_web/components/component.ex @@ -0,0 +1,40 @@ +defmodule LSGWeb.Component do + use Phoenix.Component + + @date_time_default_format "%F %H:%M" + @date_time_formats %{"time-24-with-seconds" => "%H:%M:%S"} + def naive_date_time_utc(assigns = %{format: format}) do + assigns = assign(assigns, :format, Map.get(@date_time_formats, format, format)) + ~H""" + <time class="component" + id={"time-#{:erlang.phash2(@datetime)}"} + phx-hook="NaiveDateTimeUTC" + data-time-format={get_luxon_format(@format)} + datetime={NaiveDateTime.to_iso8601(@datetime)}> + <%= Timex.format!(@datetime, @format, :strftime) %> + </time> + """ + end + def naive_date_time_utc(assigns) do + naive_date_time_utc(assign(assigns, :format, "%F %H:%M")) + end + def get_luxon_format("%H:%M:%S"), do: "TIME_24_WITH_SECONDS" + + def nick(assigns = %{self: false}) do + ~H""" + <span class="nickname" data-account-id={@account_id} data-user-id={@user_id}> + <%= @nick %> + </span> + """ + end + + def nick(assigns = %{self: true}) do + ~H""" + <span class="nickname self" data-account-id={@account_id} data-user-id={@user_id}> + You + </span> + """ + end + + +end diff --git a/lib/lsg_web/components/event_component.ex b/lib/lsg_web/components/event_component.ex new file mode 100644 index 0000000..3b9cd3b --- /dev/null +++ b/lib/lsg_web/components/event_component.ex @@ -0,0 +1,36 @@ +defmodule LSGWeb.EventComponent do + use Phoenix.Component + + def content(assigns = %{event: %{type: :quit}}) do + ~H""" + <LSGWeb.Component.nick self={@self} nick={@user.nick} user_id={@user.id} account_id={@user.account} /> + has quit: + <span class="reason"><%= @reason %></span> + """ + end + + def content(assigns = %{event: %{type: :part}}) do + ~H""" + <LSGWeb.Component.nick self={@self} nick={@user.nick} user_id={@user.id} account_id={@user.account} /> + has left: + <span class="reason"><%= @reason %></span> + """ + end + + def content(assigns = %{event: %{type: :nick}}) do + ~H""" + <span class="old-nick"><%= @old_nick %></span> + is now known as + <LSGWeb.Component.nick self={@self} nick={@user.nick} user_id={@user.id} account_id={@user.account} /> + """ + end + + def content(assigns = %{event: %{type: :join}}) do + ~H""" + <LSGWeb.Component.nick self={@self} nick={@user.nick} user_id={@user.id} account_id={@user.account} /> + joined + """ + end + + +end diff --git a/lib/lsg_web/components/message_component.ex b/lib/lsg_web/components/message_component.ex new file mode 100644 index 0000000..2381411 --- /dev/null +++ b/lib/lsg_web/components/message_component.ex @@ -0,0 +1,10 @@ +defmodule LSGWeb.MessageComponent do + use Phoenix.Component + + def content(assigns) do + ~H""" + <div class="inline-block flex-grow cursor-default"><%= @text %></div> + """ + end + +end diff --git a/lib/lsg_web/controllers/irc_auth_sse_controller.ex b/lib/lsg_web/controllers/irc_auth_sse_controller.ex index c39a866..f370d97 100644 --- a/lib/lsg_web/controllers/irc_auth_sse_controller.ex +++ b/lib/lsg_web/controllers/irc_auth_sse_controller.ex @@ -26,7 +26,7 @@ defmodule LSGWeb.IrcAuthSseController do def subscribe(conn) do :timer.send_interval(@ping_interval, {:event, :ping}) :timer.send_after(@expire_delay, {:event, :expire}) - {:ok, _} = Registry.register(IRC.PubSub, "message:private", []) + {:ok, _} = Registry.register(IRC.PubSub, "messages:private", []) conn end diff --git a/lib/lsg_web/endpoint.ex b/lib/lsg_web/endpoint.ex index 37f7e84..4dd8151 100644 --- a/lib/lsg_web/endpoint.ex +++ b/lib/lsg_web/endpoint.ex @@ -1,8 +1,6 @@ defmodule LSGWeb.Endpoint do use Phoenix.Endpoint, otp_app: :lsg - socket "/socket", LSGWeb.UserSocket, websocket: true - # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phoenix.digest @@ -30,13 +28,18 @@ defmodule LSGWeb.Endpoint do plug Plug.MethodOverride plug Plug.Head + @session_options [store: :cookie, + key: "_lsg_key", + signing_salt: "+p7K3wrj"] + + + socket "/live", Phoenix.LiveView.Socket, + websocket: [connect_info: [session: @session_options]] + # The session will be stored in the cookie and signed, # this means its contents can be read but not tampered with. # Set :encryption_salt if you would also like to encrypt it. - plug Plug.Session, - store: :cookie, - key: "_lsg_key", - signing_salt: "+p7K3wrj" + plug Plug.Session, @session_options plug LSGWeb.Router diff --git a/lib/lsg_web/live/chat_live.ex b/lib/lsg_web/live/chat_live.ex new file mode 100644 index 0000000..a2b4c13 --- /dev/null +++ b/lib/lsg_web/live/chat_live.ex @@ -0,0 +1,101 @@ +defmodule LSGWeb.ChatLive do + use Phoenix.LiveView + use Phoenix.HTML + require Logger + + def mount(%{"network" => network, "chan" => chan}, %{"account" => account_id}, socket) do + chan = LSGWeb.reformat_chan(chan) + connection = IRC.Connection.get_network(network, chan) + account = IRC.Account.get(account_id) + membership = IRC.Membership.of_account(IRC.Account.get("DRgpD4fLf8PDJMLp8Dtb")) + if account && connection && Enum.member?(membership, {connection.network, chan}) do + {:ok, _} = Registry.register(IRC.PubSub, "#{connection.network}:events", plugin: __MODULE__) + for t <- ["messages", "triggers", "outputs", "events"] do + {:ok, _} = Registry.register(IRC.PubSub, "#{connection.network}/#{chan}:#{t}", plugin: __MODULE__) + end + + IRC.PuppetConnection.start(account, connection) + + users = IRC.UserTrack.channel(connection.network, chan) + |> Enum.map(fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple) end) + |> Enum.reduce(Map.new, fn(user = %{id: id}, acc) -> + Map.put(acc, id, user) + 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, []) + |> 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 = IRC.Account.get(socket.assigns.account_id) + 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 = IRC.UserTrack.lookup(id) do + IO.puts("JOIN USER JOIN USER JOIN USER") + + socket = socket + |> assign(:users, Map.put(socket.assigns.users, id, user)) + |> assign(:backlog, socket.assigns.backlog ++ [event]) + + IO.inspect(socket.assigns.users) + + {:noreply, socket} + else + IO.puts("\n\n\n?!\n\n\n?!\n\n\n\n") + {: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)) + |> assign(:backlog, socket.assigns.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)) + |> assign(:backlog, socket.assigns.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)) + |> assign(:backlog, socket.assigns.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 + socket = socket + |> assign(:backlog, socket.assigns.backlog ++ [message]) + {:noreply, socket} + end + + def handle_info(info, socket) do + Logger.debug("Unhandled info: #{inspect info}") + {:noreply, socket} + end + +end diff --git a/lib/lsg_web/live/chat_live.html.heex b/lib/lsg_web/live/chat_live.html.heex new file mode 100644 index 0000000..01d8b3a --- /dev/null +++ b/lib/lsg_web/live/chat_live.html.heex @@ -0,0 +1,96 @@ +<div class="chat" data-turbo="false"> + + <div class="py-4 px-4 bg-gradient-to-b from-black to-gray-900"> + <div class="grid grid-cols-2"> + <h1 class="text-gray-50 tracking-tight font-extrabold text-xl"> + <%= @network %> + <span class="font-bold"><%= @chan %></span> + </h1> + <div class="text-right"> + <a href="/" class="text-gray-400"><%= @account_id %></a> + </div> + </div> + </div> + + <div class="body"> + + <div class="log"> + <%= if Enum.empty?(@backlog) do %> + <p class="disconnected text-center text-6xl tracking-tight font-extrabold text-red-800 w-full my-24 mx-auto overflow-y-auto"> + Disconnected + </p> + <p class="phx-errored text-center text-6xl tracking-tight font-extrabold text-red-800 w-full my-24 mx-auto overflow-y-auto"> + Oh no error + </p> + <% end %> + + <ul class="pt-4 pl-4"> + <%= for message <- @backlog do %> + <%= if is_map(message) && Map.get(message, :__struct__) == IRC.Message do %> + <li class="flex gap-2 place-items-center message" + data-account-id={message.account.id}> + <LSGWeb.Component.naive_date_time_utc datetime={message.at} format="time-24-with-seconds" /> + <span class="inline-block font-bold flex-none cursor-default"><%= message.sender.nick %></span> + <span class="inline-block flex-grow cursor-default"> + <LSGWeb.MessageComponent.content + self={message.account.id == @account_id} + text={message.text} + /> + </span> + </li> + <% end %> + + <%= if is_binary(message) do %> + <li class="notice"><%= message %></li> + <% end %> + + <%= if is_map(message) && Map.get(message, :type) do %> + <li class="flex gap-2 place-items-center event"> + <LSGWeb.Component.naive_date_time_utc datetime={message.at} format="time-24-with-seconds" /> + <span class="inline-block font-bold flex-none cursor-default text-gray-700">* * *</span> + <span class="inline-block flex-grow cursor-default text-gray-700"> + <LSGWeb.EventComponent.content event={message} + self={@users[message.user_id] && @users[message.user_id].account == @account_id} + user={@users[message.user_id]} + /> + </span> + </li> + <% end %> + <% end %> + </ul> + </div> + + <aside> + <%= for {_, user} <- @users do %> + <details class="user dropdown"> + <summary><%= user.nick %></summary> + <div class="content"> + <h3 class="text-xl font-bold"><%= user.nick %></h3> + + <ul class="mt-4 space-y-2"> + <li class="">User: <span class="font-bold"><%= user.username %></span></li> + <li class="">Name: <%= user.realname || user.nick %></li> + <li class="">Host: <span class="font-mono"><%= user.host %></span></li> + </ul> + + <div class="mt-4 font-xs text-gray-300 text-center"> + UID: <%= user.id %> + <br /> + AID: <%= user.account %> + </div> + </div> + </details> + <% end %> + </aside> + + </div> + + <.form let={f} id={"form-#{@counter}"} for={:message} phx-submit="send" class="w-full px-4 pt-4"> + <div> + <div class="mt-1 flex rounded-md shadow-sm border border-gray-300"> + <%= text_input f, :text, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full border rounded-md pl-4 sm:text-sm border-gray-300", autofocus: true, 'phx-hook': "AutoFocus", autocomplete: "off", placeholder: "Don't be shy, say something…" %> + <%= submit content_tag(:span, "Send"), class: "-ml-px relative inline-flex items-center space-x-2 px-4 py-2 border border-gray-300 text-sm font-medium rounded-r-md text-gray-700 bg-gray-50 hover:bg-gray-100 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"%> + </div> + </div> + </.form> +</div> diff --git a/lib/lsg_web/router.ex b/lib/lsg_web/router.ex index d681ed9..5fcf0a8 100644 --- a/lib/lsg_web/router.ex +++ b/lib/lsg_web/router.ex @@ -5,8 +5,10 @@ defmodule LSGWeb.Router do plug :accepts, ["html", "txt"] plug :fetch_session plug :fetch_flash - #plug :protect_from_forgery - #plug :put_secure_browser_headers + plug :fetch_live_flash + plug :protect_from_forgery + plug :put_secure_browser_headers + plug :put_root_layout, {LSGWeb.LayoutView, :root} end pipeline :api do @@ -22,8 +24,6 @@ defmodule LSGWeb.Router do scope "/", LSGWeb do pipe_through :browser get "/", PageController, :index - get "/embed/widget", PageController, :widget - get "/api", PageController, :api get "/login/irc/:token", PageController, :token, as: :login get "/api/untappd/callback", UntappdController, :callback, as: :untappd_callback @@ -43,6 +43,7 @@ defmodule LSGWeb.Router do 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 + live "/:network/:chan/live", ChatLive get "/:network/:chan/txt", IrcController, :txt get "/:network/:chan/txt/:name", IrcController, :txt get "/:network/:channel/preums", IrcController, :preums diff --git a/lib/lsg_web/templates/irc/index.html.eex b/lib/lsg_web/templates/irc/index.html.eex index a8544b3..f20f444 100644 --- a/lib/lsg_web/templates/irc/index.html.eex +++ b/lib/lsg_web/templates/irc/index.html.eex @@ -1,5 +1,6 @@ <div class="hidden sm:block"> - <nav class="flex flex-wrap content-center"> + <nav class="flex flex-wrap content-center"> + <%= link("live", to: LSGWeb.Router.Helpers.live_path(LSGWeb.Endpoint, LSGWeb.ChatLive, @network, LSGWeb.format_chan(@chan)), 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") %> <% list = for {identifier, _} <- @commands do %> <% name = String.replace(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") %> diff --git a/lib/lsg_web/templates/layout/app.html.eex b/lib/lsg_web/templates/layout/app.html.eex index 9ad05a6..bca1555 100644 --- a/lib/lsg_web/templates/layout/app.html.eex +++ b/lib/lsg_web/templates/layout/app.html.eex @@ -1,18 +1,3 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <%= page_title(@conn) %> - <meta charset="utf-8"> - <meta name="robots" content="noindex, nofollow, nosnippet"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> - <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/site.css") %>"> - <script src="<%= static_path(@conn, "/assets/site.js") %>" defer></script> - </head> - - <body> <div> <div class="bg-gray-800 pb-32"> <nav class="bg-gray-800"> @@ -129,16 +114,13 @@ </header> </div> - <main class="-mt-32"> - <div class="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8"> + <main class="-mt-32 h-full"> + <div class="max-w-7xl h-full 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"> + <div class="bg-white h-full rounded-lg shadow px-5 py-6 sm:px-6"> <%= @inner_content %> </div> <!-- /End replace --> </div> </main> </div> - - </body> -</html> diff --git a/lib/lsg_web/templates/layout/root.html.leex b/lib/lsg_web/templates/layout/root.html.leex new file mode 100644 index 0000000..6a48506 --- /dev/null +++ b/lib/lsg_web/templates/layout/root.html.leex @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <%= page_title(@conn) %> + <meta charset="utf-8"> + <meta name="robots" content="noindex, nofollow, nosnippet"> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <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/site.css") %>"> + <%= csrf_meta_tag() %> + <script src="<%= static_path(@conn, "/assets/site.js") %>" defer></script> + </head> + <body> + <%= @inner_content %> + </body> +</html> |