From 1d9916bc01e121b53dee4eb11b7cae958fc2f9d8 Mon Sep 17 00:00:00 2001 From: Jordan Bracco Date: Tue, 20 Dec 2022 03:03:58 +0000 Subject: Rename IRC.UserTrack to Nola.UserTrack, refs T77 --- lib/irc/connection.ex | 32 ++-- lib/irc/irc.ex | 2 +- lib/irc/puppet_connection.ex | 6 +- lib/irc/user_track.ex | 329 --------------------------------- lib/matrix/room.ex | 12 +- lib/nola/account.ex | 8 +- lib/nola/auth_token.ex | 2 +- lib/nola/membership.ex | 2 +- lib/nola/plugins.ex | 1 + lib/nola/token.ex | 2 +- lib/nola/user_track.ex | 329 +++++++++++++++++++++++++++++++++ lib/plugins/account.ex | 2 +- lib/plugins/alcoolog.ex | 12 +- lib/plugins/alcoolog_announcer.ex | 2 +- lib/plugins/last_fm.ex | 2 +- lib/plugins/preums.ex | 4 +- lib/plugins/tell.ex | 4 +- lib/plugins/txt.ex | 4 +- lib/tmpl.ex | 2 +- lib/web/controllers/irc_controller.ex | 2 +- lib/web/controllers/page_controller.ex | 2 +- lib/web/live/chat_live.ex | 6 +- 22 files changed, 384 insertions(+), 383 deletions(-) delete mode 100644 lib/irc/user_track.ex create mode 100644 lib/nola/user_track.ex diff --git a/lib/irc/connection.ex b/lib/irc/connection.ex index 49f5774..9bb09ed 100644 --- a/lib/irc/connection.ex +++ b/lib/irc/connection.ex @@ -264,7 +264,7 @@ defmodule IRC.Connection do # ISUP def handle_info({:isup, network}, state) when is_binary(network) do - IRC.UserTrack.clear_network(state.network) + Nola.UserTrack.clear_network(state.network) if network != state.network do Logger.warn("Possibly misconfigured network: #{network} != #{state.network}") end @@ -279,11 +279,11 @@ defmodule IRC.Connection do # Received something in a channel def handle_info({:received, text, sender, chan}, state) do - user = if user = IRC.UserTrack.find_by_nick(state.network, sender.nick) do + user = if user = Nola.UserTrack.find_by_nick(state.network, sender.nick) do user else Logger.error("Could not lookup user for message: #{inspect {state.network, chan, sender.nick}}") - user = IRC.UserTrack.joined(chan, sender, []) + user = Nola.UserTrack.joined(chan, sender, []) ExIRC.Client.who(state.client, chan) # Rewho everything in case of need ? We shouldn't not know that user.. user end @@ -297,7 +297,7 @@ defmodule IRC.Connection do message = %IRC.Message{id: FlakeId.get(), transport: :irc, at: NaiveDateTime.utc_now(), text: text, network: state.network, account: account, sender: sender, channel: chan, replyfun: reply_fun, trigger: extract_trigger(text)} - message = case IRC.UserTrack.messaged(message) do + message = case Nola.UserTrack.messaged(message) do :ok -> message {:ok, message} -> message end @@ -313,7 +313,7 @@ defmodule IRC.Connection do account = Nola.Account.lookup(sender) message = %IRC.Message{id: FlakeId.get(), transport: :irc, text: text, network: state.network, at: NaiveDateTime.utc_now(), account: account, sender: sender, replyfun: reply_fun, trigger: extract_trigger(text)} - message = case IRC.UserTrack.messaged(message) do + message = case Nola.UserTrack.messaged(message) do :ok -> message {:ok, message} -> message end @@ -324,7 +324,7 @@ defmodule IRC.Connection do ## -- Broadcast def handle_info({:broadcast, net, account = %Nola.Account{}, message}, state) do if net == state.conn.network do - user = IRC.UserTrack.find_by_account(net, account) + user = Nola.UserTrack.find_by_account(net, account) if user do irc_reply(state, {user.nick, nil}, message) end @@ -349,7 +349,7 @@ defmodule IRC.Connection do accounts = Enum.map(whos, fn(who = %ExIRC.Who{nick: nick, operator?: operator}) -> priv = if operator, do: [:operator], else: [] # Don't touch -- on WHO the bot joined, not the users. - IRC.UserTrack.joined(channel, who, priv, false) + Nola.UserTrack.joined(channel, who, priv, false) account = Nola.Account.lookup(who) if account do {:account, who.network, channel, who.nick, account.id} @@ -361,12 +361,12 @@ defmodule IRC.Connection do end def handle_info({:quit, reason, sender}, state) do - IRC.UserTrack.quitted(sender, reason) + Nola.UserTrack.quitted(sender, reason) {:noreply, state} end def handle_info({:joined, channel, sender}, state) do - IRC.UserTrack.joined(channel, sender, []) + Nola.UserTrack.joined(channel, sender, []) account = Nola.Account.lookup(sender) if account do dispatch("account", {:account, sender.network, channel, sender.nick, account.id}) @@ -375,12 +375,12 @@ defmodule IRC.Connection do end def handle_info({:kicked, nick, _by, channel, _reason}, state) do - IRC.UserTrack.parted(state.network, channel, nick) + Nola.UserTrack.parted(state.network, channel, nick) {:noreply, state} end def handle_info({:parted, channel, %ExIRC.SenderInfo{nick: nick}}, state) do - IRC.UserTrack.parted(state.network, channel, nick) + Nola.UserTrack.parted(state.network, channel, nick) {:noreply, state} end @@ -390,7 +390,7 @@ defmodule IRC.Connection do end def handle_info({:nick_changed, old_nick, new_nick}, state) do - IRC.UserTrack.renamed(state.network, old_nick, new_nick) + Nola.UserTrack.renamed(state.network, old_nick, new_nick) {:noreply, state} end @@ -490,22 +490,22 @@ defmodule IRC.Connection do end defp track_mode(network, channel, nick, "+o") do - IRC.UserTrack.change_privileges(network, channel, nick, {[:operator], []}) + Nola.UserTrack.change_privileges(network, channel, nick, {[:operator], []}) :ok end defp track_mode(network, channel, nick, "-o") do - IRC.UserTrack.change_privileges(network, channel, nick, {[], [:operator]}) + Nola.UserTrack.change_privileges(network, channel, nick, {[], [:operator]}) :ok end defp track_mode(network, channel, nick, "+v") do - IRC.UserTrack.change_privileges(network, channel, nick, {[:voice], []}) + Nola.UserTrack.change_privileges(network, channel, nick, {[:voice], []}) :ok end defp track_mode(network, channel, nick, "-v") do - IRC.UserTrack.change_privileges(network, channel, nick, {[], [:voice]}) + Nola.UserTrack.change_privileges(network, channel, nick, {[], [:voice]}) :ok end diff --git a/lib/irc/irc.ex b/lib/irc/irc.ex index 7cd9dc8..93525e4 100644 --- a/lib/irc/irc.ex +++ b/lib/irc/irc.ex @@ -25,7 +25,7 @@ defmodule IRC do if connection && (force_puppet || IRC.PuppetConnection.whereis(account, connection)) do IRC.PuppetConnection.start_and_send_message(account, connection, channel, text) else - user = IRC.UserTrack.find_by_account(network, account) + user = Nola.UserTrack.find_by_account(network, account) nick = if(user, do: user.nick, else: account.name) IRC.Connection.broadcast_message(network, channel, "<#{nick}> #{text}") end diff --git a/lib/irc/puppet_connection.ex b/lib/irc/puppet_connection.ex index fd0a98e..75a06f3 100644 --- a/lib/irc/puppet_connection.ex +++ b/lib/irc/puppet_connection.ex @@ -153,7 +153,7 @@ defmodule IRC.PuppetConnection do IRC.Connection.broadcast_message(state.network, channel, text) end message = %IRC.Message{id: FlakeId.get(), at: NaiveDateTime.utc_now(), text: text, network: state.network, account: account, sender: sender, channel: channel, replyfun: reply_fun, trigger: IRC.Connection.extract_trigger(text), meta: meta} - message = case IRC.UserTrack.messaged(message) do + message = case Nola.UserTrack.messaged(message) do :ok -> message {:ok, message} -> message end @@ -200,7 +200,7 @@ defmodule IRC.PuppetConnection do Logger.info("#{inspect(self())} Logged in") {_, backoff} = :backoff.succeed(state.backoff) # Create an UserTrack entry for the client so it's authenticated to the right account_id already. - IRC.UserTrack.connected(state.network, suffix_nick(make_nick(state)), make_nick(state), "puppet.", state.account_id, %{puppet: true}) + Nola.UserTrack.connected(state.network, suffix_nick(make_nick(state)), make_nick(state), "puppet.", state.account_id, %{puppet: true}) {:noreply, %{state | backoff: backoff}} end @@ -220,7 +220,7 @@ defmodule IRC.PuppetConnection do def make_nick(state) do account = Nola.Account.get(state.account_id) - user = IRC.UserTrack.find_by_account(state.network, account) + user = Nola.UserTrack.find_by_account(state.network, account) base_nick = if(user, do: user.nick, else: account.name) clean_nick = case String.split(base_nick, ":", parts: 2) do ["@"<>nick, _] -> nick diff --git a/lib/irc/user_track.ex b/lib/irc/user_track.ex deleted file mode 100644 index 56a319f..0000000 --- a/lib/irc/user_track.ex +++ /dev/null @@ -1,329 +0,0 @@ -defmodule IRC.UserTrack do - @moduledoc """ - User Track DB & Utilities - """ - - @ets IRC.UserTrack.Storage - # {uuid, network, nick, nicks, privilege_map} - # Privilege map: - # %{"#channel" => [:operator, :voice] - defmodule Storage do - - def delete(id) do - op(fn(ets) -> :ets.delete(ets, id) end) - end - - def insert(tuple) do - op(fn(ets) -> :ets.insert(ets, tuple) end) - end - - def clear_network(network) do - op(fn(ets) -> - spec = [ - {{:_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_, :_}, - [ - {:==, :"$1", {:const, network}} - ], [:"$_"]} - ] - :ets.match_delete(ets, spec) - end) - end - - def op(fun) do - GenServer.call(__MODULE__, {:op, fun}) - end - - def start_link do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) - end - - def init([]) do - ets = :ets.new(__MODULE__, [:set, :named_table, :protected, {:read_concurrency, true}]) - {:ok, ets} - end - - def handle_call({:op, fun}, _from, ets) do - returned = try do - {:ok, fun.(ets)} - rescue - rescued -> {:error, rescued} - catch - rescued -> {:error, rescued} - end - {:reply, returned, ets} - end - - def terminate(_reason, ets) do - :ok - end - end - - defmodule Id, do: use EntropyString - - defmodule User do - defstruct [:id, :account, :network, :nick, {:nicks, []}, :username, :host, :realname, {:privileges, %{}}, {:last_active, %{}}, {:options, %{}}] - - def to_tuple(u = %__MODULE__{}) do - {u.id || IRC.UserTrack.Id.large_id, u.network, u.account, String.downcase(u.nick), u.nick, u.nicks || [], u.username, u.host, u.realname, u.privileges, u.last_active, u.options} - end - - #tuple size: 11 - def from_tuple({id, network, account, _downcased_nick, nick, nicks, username, host, realname, privs, last_active, opts}) do - struct = %__MODULE__{id: id, account: account, network: network, nick: nick, nicks: nicks, username: username, host: host, realname: realname, privileges: privs, last_active: last_active, options: opts} - end - end - - def find_by_account(%Nola.Account{id: id}) do - #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) - spec = [ - {{:_, :_, :"$2", :_, :_, :_, :_, :_, :_, :_, :_, :_}, - [ - {:==, :"$2", {:const, id}} - ], [:"$_"]} - ] - results = :ets.select(@ets, spec) - |> Enum.filter(& &1) - for obj <- results, do: User.from_tuple(obj) - end - - def find_by_account(network, nil) do - nil - end - - def find_by_account(network, %Nola.Account{id: id}) do - #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) - spec = [ - {{:_, :"$1", :"$2", :_, :_, :_, :_, :_, :_, :_, :_, :_}, - [ - {:andalso, {:==, :"$1", {:const, network}}, - {:==, :"$2", {:const, id}}} - ], [:"$_"]} - ] - case :ets.select(@ets, spec) do - results = [_r | _] -> - result = results - |> Enum.reject(fn({_, net, _, _, _, _, _, _, _, _, actives, opts}) -> network != "matrix" && net == "matrix" end) - |> Enum.reject(fn({_, net, _, _, _, _, _, _, _, _, actives, opts}) -> network != "telegram" && net == "telegram" end) - |> Enum.reject(fn({_, _, _, _, _, _, _, _, _, _, actives, opts}) -> network not in ["matrix", "telegram"] && Map.get(opts, :puppet) end) - |> Enum.sort_by(fn({_, _, _, _, _, _, _, _, _, _, actives, _}) -> - Map.get(actives, nil) - end, {:desc, NaiveDateTime}) - |> List.first - - if result, do: User.from_tuple(result) - _ -> nil - end - end - - def clear_network(network) do - Storage.clear_network(network) - end - - - def merge_account(old_id, new_id) do - #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) - spec = [ - {{:_, :_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_}, - [ - {:==, :"$1", {:const, old_id}} - ], [:"$_"]} - ] - Enum.each(:ets.select(@ets, spec), fn({id, net, _, downcased_nick, nick, nicks, username, host, realname, privs, active, opts}) -> - Storage.op(fn(ets) -> - :ets.insert(@ets, {id, net, new_id, downcased_nick, nick, nicks, username, host, realname, privs, active, opts}) - end) - end) - end - - def find_by_nick(%ExIRC.Who{network: network, nick: nick}) do - find_by_nick(network, nick) - end - - - def find_by_nick(%ExIRC.SenderInfo{network: network, nick: nick}) do - find_by_nick(network, nick) - end - - def find_by_nick(network, nick) do - case :ets.match(@ets, {:"$1", network, :_, String.downcase(nick), :_, :_, :_, :_, :_, :_, :_, :_}) do - [[id] | _] -> lookup(id) - _ -> - nil - end - end - - def to_list, do: :ets.tab2list(@ets) - - def lookup(id) do - case :ets.lookup(@ets, id) do - [] -> nil - [tuple] -> User.from_tuple(tuple) - end - end - - def operator?(network, channel, nick) do - if user = find_by_nick(network, nick) do - privs = Map.get(user.privileges, channel, []) - Enum.member?(privs, :admin) || Enum.member?(privs, :operator) - else - false - end - end - - def channel(network, channel) do - Enum.filter(to_list(), fn({_, network, _, _, _, _, _, _, _, channels, _, _}) -> - Map.get(channels, channel) - end) - end - - # TODO - def connected(network, nick, user, host, account_id, opts \\ %{}) do - if account = Nola.Account.get(account_id) do - user = if user = find_by_nick(network, nick) do - user - else - user = %User{id: IRC.UserTrack.Id.large_id, account: account_id, network: network, nick: nick, username: user, host: host, privileges: %{}, options: opts} - Storage.op(fn(ets) -> - :ets.insert(ets, User.to_tuple(user)) - end) - user - end - - IRC.Connection.publish_event(network, %{type: :connect, user_id: user.id, account_id: user.account}) - :ok - else - :error - end - end - - def joined(c, s), do: joined(c,s,[]) - - def joined(channel, sender=%{nick: nick, user: uname, host: host}, privileges, touch \\ true) do - privileges = if IRC.admin?(sender) do - privileges ++ [:admin] - else privileges end - 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{id: IRC.UserTrack.Id.large_id, network: sender.network, nick: nick, username: uname, host: host, privileges: %{channel => privileges}} - - account = Nola.Account.lookup(user).id - user = %User{user | account: account} - end - user = touch_struct(user, channel) - - if touch && user.account do - Nola.Membership.touch(user.account, sender.network, channel) - end - - 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}) - - user - end - - #def joined(network, channel, nick, privileges) do - # user = if user = find_by_nick(network, nick) do - # %User{user | privileges: Map.put(user.privileges, channel, privileges)} - # else - # %User{nick: nick, privileges: %{channel => privileges}} - # end - # - # Storage.op(fn(ets) -> - # :ets.insert(ets, User.to_tuple(user)) - # end) - #end - - def messaged(%IRC.Message{network: network, account: account, channel: chan, sender: %{nick: nick}} = m) do - {user, account} = if user = find_by_nick(network, nick) do - {touch_struct(user, chan), account || Nola.Account.lookup(user)} - else - user = %User{network: network, nick: nick, privileges: %{}} - account = Nola.Account.lookup(user) - {%User{user | account: account.id}, account} - end - Storage.insert(User.to_tuple(user)) - if chan, do: Nola.Membership.touch(account, network, chan) - if !m.account do - {:ok, %IRC.Message{m | account: account}} - else - :ok - end - end - - def renamed(network, old_nick, new_nick) do - if user = find_by_nick(network, old_nick) do - old_account = Nola.Account.lookup(user) - user = %User{user | nick: new_nick, nicks: [old_nick|user.nicks]} - account = Nola.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 - - def change_privileges(network, channel, nick, {add, remove}) do - if user = find_by_nick(network, nick) do - privs = Map.get(user.privileges, channel) - - privs = Enum.reduce(add, privs, fn(priv, acc) -> [priv|acc] end) - privs = Enum.reduce(remove, privs, fn(priv, acc) -> List.delete(acc, priv) end) - - 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 - - def parted(network, channel, nick) do - if user = find_by_nick(network, nick) do - if user.account do - Nola.Membership.touch(user.account, network, channel) - end - - privs = Map.delete(user.privileges, channel) - lasts = Map.delete(user.last_active, channel) - 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, reason) do - if user = find_by_nick(sender.network, sender.nick) do - if user.account do - for {channel, _} <- user.privileges do - Nola.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 - end - - defp touch_struct(user = %User{last_active: last_active}, channel) do - now = NaiveDateTime.utc_now() - last_active = last_active - |> Map.put(channel, now) - |> Map.put(nil, now) - %User{user | last_active: last_active} - end - - defp userchans(%{privileges: privileges}) do - for({chan, _} <- privileges, do: chan) - end -end diff --git a/lib/matrix/room.ex b/lib/matrix/room.ex index 57f35b8..757aad0 100644 --- a/lib/matrix/room.ex +++ b/lib/matrix/room.ex @@ -95,11 +95,11 @@ defmodule Nola.Matrix.Room do end) |> Enum.filter(& &1) - for m <- members, do: IRC.UserTrack.joined(state.id, %{network: "matrix", nick: m, user: m, host: "matrix."}, [], true) + for m <- members, do: Nola.UserTrack.joined(state.id, %{network: "matrix", nick: m, user: m, host: "matrix."}, [], true) - accounts = IRC.UserTrack.channel(state.network, state.channel) + accounts = Nola.UserTrack.channel(state.network, state.channel) |> Enum.filter(& &1) - |> Enum.map(fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple).account end) + |> Enum.map(fn(tuple) -> Nola.UserTrack.User.from_tuple(tuple).account end) |> Enum.uniq() |> Enum.each(fn(account_id) -> introduce_irc_account(account_id, state) @@ -152,12 +152,12 @@ defmodule Nola.Matrix.Room do def handle_matrix(event = %{type: "m.room.member", user_id: user_id, content: %{"membership" => "join"}}, state) do _account = get_account(event, state) - IRC.UserTrack.joined(state.id, %{network: "matrix", nick: user_id, user: user_id, host: "matrix."}, [], true) + Nola.UserTrack.joined(state.id, %{network: "matrix", nick: user_id, user: user_id, host: "matrix."}, [], true) {:noreply, state} end def handle_matrix(event = %{type: "m.room.member", user_id: user_id, content: %{"membership" => "leave"}}, state) do - IRC.UserTrack.parted(state.id, %{network: "matrix", nick: user_id}) + Nola.UserTrack.parted(state.id, %{network: "matrix", nick: user_id}) {:noreply, state} end @@ -178,7 +178,7 @@ defmodule Nola.Matrix.Room do defp introduce_irc_account(account_id, state) do mxid = Matrix.get_or_create_matrix_user(account_id) account = Nola.Account.get(account_id) - user = IRC.UserTrack.find_by_account(state.network, account) + user = Nola.UserTrack.find_by_account(state.network, account) base_nick = if(user, do: user.nick, else: account.name) case Client.Profile.put_displayname(client(user_id: mxid), base_nick) do :ok -> :ok diff --git a/lib/nola/account.ex b/lib/nola/account.ex index 4f1e7ff..06ca993 100644 --- a/lib/nola/account.ex +++ b/lib/nola/account.ex @@ -1,5 +1,5 @@ defmodule Nola.Account do - alias IRC.UserTrack.User + alias Nola.UserTrack.User @moduledoc """ Account registry.... @@ -136,7 +136,7 @@ defmodule Nola.Account do end :dets.delete(file("db"), old_id) Nola.Membership.merge_account(old_id, new_id) - IRC.UserTrack.merge_account(old_id, new_id) + Nola.UserTrack.merge_account(old_id, new_id) IRC.Connection.dispatch("account", {:account_change, old_id, new_id}) IRC.Connection.dispatch("conn", {:account_change, old_id, new_id}) end @@ -203,7 +203,7 @@ defmodule Nola.Account do end defp do_lookup(sender = %ExIRC.Who{}, make_default) do - if user = IRC.UserTrack.find_by_nick(sender) do + if user = Nola.UserTrack.find_by_nick(sender) do lookup(user, make_default) else #FIXME this will never work with continued lookup by other methods as Who isn't compatible @@ -212,7 +212,7 @@ defmodule Nola.Account do end defp do_lookup(sender = %ExIRC.SenderInfo{}, make_default) do - lookup(IRC.UserTrack.find_by_nick(sender), make_default) + lookup(Nola.UserTrack.find_by_nick(sender), make_default) end defp do_lookup(user = %User{account: id}, make_default) when is_binary(id) do diff --git a/lib/nola/auth_token.ex b/lib/nola/auth_token.ex index d125ea4..9760ec7 100644 --- a/lib/nola/auth_token.ex +++ b/lib/nola/auth_token.ex @@ -50,7 +50,7 @@ defmodule Nola.AuthToken do end def handle_call({:new, account, perks}, _, state) do - id = IRC.UserTrack.Id.token() + id = Nola.UserTrack.Id.token() expire = DateTime.utc_now() |> DateTime.add(15*60, :second) {:reply, {:ok, id}, Map.put(state, id, {account, expire, perks})} diff --git a/lib/nola/membership.ex b/lib/nola/membership.ex index b98efd7..1c7303b 100644 --- a/lib/nola/membership.ex +++ b/lib/nola/membership.ex @@ -117,7 +117,7 @@ defmodule Nola.Membership do defp expand(network, list) do for id <- list do if account = Nola.Account.get(id) do - user = IRC.UserTrack.find_by_account(network, account) + user = Nola.UserTrack.find_by_account(network, account) nick = if(user, do: user.nick, else: account.name) {account, user, nick} end diff --git a/lib/nola/plugins.ex b/lib/nola/plugins.ex index 4b55f4c..b0c3ce3 100644 --- a/lib/nola/plugins.ex +++ b/lib/nola/plugins.ex @@ -47,6 +47,7 @@ defmodule Nola.Plugins do end def start_all() do + Logger.info("starting plugins.") for mod <- enabled(), do: {mod, __MODULE__.Supervisor.start_child(mod)} end diff --git a/lib/nola/token.ex b/lib/nola/token.ex index 563ac72..179bed2 100644 --- a/lib/nola/token.ex +++ b/lib/nola/token.ex @@ -27,7 +27,7 @@ defmodule Nola.Token do end def handle_call({:new, cred}, _, ets) do - id = IRC.UserTrack.Id.large_id() + id = Nola.UserTrack.Id.large_id() expire = DateTime.utc_now() |> DateTime.add(15*60, :second) obj = {id, cred, expire} diff --git a/lib/nola/user_track.ex b/lib/nola/user_track.ex new file mode 100644 index 0000000..2a051f9 --- /dev/null +++ b/lib/nola/user_track.ex @@ -0,0 +1,329 @@ +defmodule Nola.UserTrack do + @moduledoc """ + User Track DB & Utilities + """ + + @ets Nola.UserTrack.Storage + # {uuid, network, nick, nicks, privilege_map} + # Privilege map: + # %{"#channel" => [:operator, :voice] + defmodule Storage do + + def delete(id) do + op(fn(ets) -> :ets.delete(ets, id) end) + end + + def insert(tuple) do + op(fn(ets) -> :ets.insert(ets, tuple) end) + end + + def clear_network(network) do + op(fn(ets) -> + spec = [ + {{:_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_, :_}, + [ + {:==, :"$1", {:const, network}} + ], [:"$_"]} + ] + :ets.match_delete(ets, spec) + end) + end + + def op(fun) do + GenServer.call(__MODULE__, {:op, fun}) + end + + def start_link do + GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + end + + def init([]) do + ets = :ets.new(__MODULE__, [:set, :named_table, :protected, {:read_concurrency, true}]) + {:ok, ets} + end + + def handle_call({:op, fun}, _from, ets) do + returned = try do + {:ok, fun.(ets)} + rescue + rescued -> {:error, rescued} + catch + rescued -> {:error, rescued} + end + {:reply, returned, ets} + end + + def terminate(_reason, ets) do + :ok + end + end + + defmodule Id, do: use EntropyString + + defmodule User do + defstruct [:id, :account, :network, :nick, {:nicks, []}, :username, :host, :realname, {:privileges, %{}}, {:last_active, %{}}, {:options, %{}}] + + def to_tuple(u = %__MODULE__{}) do + {u.id || Nola.UserTrack.Id.large_id, u.network, u.account, String.downcase(u.nick), u.nick, u.nicks || [], u.username, u.host, u.realname, u.privileges, u.last_active, u.options} + end + + #tuple size: 11 + def from_tuple({id, network, account, _downcased_nick, nick, nicks, username, host, realname, privs, last_active, opts}) do + struct = %__MODULE__{id: id, account: account, network: network, nick: nick, nicks: nicks, username: username, host: host, realname: realname, privileges: privs, last_active: last_active, options: opts} + end + end + + def find_by_account(%Nola.Account{id: id}) do + #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) + spec = [ + {{:_, :_, :"$2", :_, :_, :_, :_, :_, :_, :_, :_, :_}, + [ + {:==, :"$2", {:const, id}} + ], [:"$_"]} + ] + results = :ets.select(@ets, spec) + |> Enum.filter(& &1) + for obj <- results, do: User.from_tuple(obj) + end + + def find_by_account(network, nil) do + nil + end + + def find_by_account(network, %Nola.Account{id: id}) do + #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) + spec = [ + {{:_, :"$1", :"$2", :_, :_, :_, :_, :_, :_, :_, :_, :_}, + [ + {:andalso, {:==, :"$1", {:const, network}}, + {:==, :"$2", {:const, id}}} + ], [:"$_"]} + ] + case :ets.select(@ets, spec) do + results = [_r | _] -> + result = results + |> Enum.reject(fn({_, net, _, _, _, _, _, _, _, _, actives, opts}) -> network != "matrix" && net == "matrix" end) + |> Enum.reject(fn({_, net, _, _, _, _, _, _, _, _, actives, opts}) -> network != "telegram" && net == "telegram" end) + |> Enum.reject(fn({_, _, _, _, _, _, _, _, _, _, actives, opts}) -> network not in ["matrix", "telegram"] && Map.get(opts, :puppet) end) + |> Enum.sort_by(fn({_, _, _, _, _, _, _, _, _, _, actives, _}) -> + Map.get(actives, nil) + end, {:desc, NaiveDateTime}) + |> List.first + + if result, do: User.from_tuple(result) + _ -> nil + end + end + + def clear_network(network) do + Storage.clear_network(network) + end + + + def merge_account(old_id, new_id) do + #iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) + spec = [ + {{:_, :_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_}, + [ + {:==, :"$1", {:const, old_id}} + ], [:"$_"]} + ] + Enum.each(:ets.select(@ets, spec), fn({id, net, _, downcased_nick, nick, nicks, username, host, realname, privs, active, opts}) -> + Storage.op(fn(ets) -> + :ets.insert(@ets, {id, net, new_id, downcased_nick, nick, nicks, username, host, realname, privs, active, opts}) + end) + end) + end + + def find_by_nick(%ExIRC.Who{network: network, nick: nick}) do + find_by_nick(network, nick) + end + + + def find_by_nick(%ExIRC.SenderInfo{network: network, nick: nick}) do + find_by_nick(network, nick) + end + + def find_by_nick(network, nick) do + case :ets.match(@ets, {:"$1", network, :_, String.downcase(nick), :_, :_, :_, :_, :_, :_, :_, :_}) do + [[id] | _] -> lookup(id) + _ -> + nil + end + end + + def to_list, do: :ets.tab2list(@ets) + + def lookup(id) do + case :ets.lookup(@ets, id) do + [] -> nil + [tuple] -> User.from_tuple(tuple) + end + end + + def operator?(network, channel, nick) do + if user = find_by_nick(network, nick) do + privs = Map.get(user.privileges, channel, []) + Enum.member?(privs, :admin) || Enum.member?(privs, :operator) + else + false + end + end + + def channel(network, channel) do + Enum.filter(to_list(), fn({_, network, _, _, _, _, _, _, _, channels, _, _}) -> + Map.get(channels, channel) + end) + end + + # TODO + def connected(network, nick, user, host, account_id, opts \\ %{}) do + if account = Nola.Account.get(account_id) do + user = if user = find_by_nick(network, nick) do + user + else + user = %User{id: Nola.UserTrack.Id.large_id, account: account_id, network: network, nick: nick, username: user, host: host, privileges: %{}, options: opts} + Storage.op(fn(ets) -> + :ets.insert(ets, User.to_tuple(user)) + end) + user + end + + IRC.Connection.publish_event(network, %{type: :connect, user_id: user.id, account_id: user.account}) + :ok + else + :error + end + end + + def joined(c, s), do: joined(c,s,[]) + + def joined(channel, sender=%{nick: nick, user: uname, host: host}, privileges, touch \\ true) do + privileges = if IRC.admin?(sender) do + privileges ++ [:admin] + else privileges end + 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{id: Nola.UserTrack.Id.large_id, network: sender.network, nick: nick, username: uname, host: host, privileges: %{channel => privileges}} + + account = Nola.Account.lookup(user).id + user = %User{user | account: account} + end + user = touch_struct(user, channel) + + if touch && user.account do + Nola.Membership.touch(user.account, sender.network, channel) + end + + 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}) + + user + end + + #def joined(network, channel, nick, privileges) do + # user = if user = find_by_nick(network, nick) do + # %User{user | privileges: Map.put(user.privileges, channel, privileges)} + # else + # %User{nick: nick, privileges: %{channel => privileges}} + # end + # + # Storage.op(fn(ets) -> + # :ets.insert(ets, User.to_tuple(user)) + # end) + #end + + def messaged(%IRC.Message{network: network, account: account, channel: chan, sender: %{nick: nick}} = m) do + {user, account} = if user = find_by_nick(network, nick) do + {touch_struct(user, chan), account || Nola.Account.lookup(user)} + else + user = %User{network: network, nick: nick, privileges: %{}} + account = Nola.Account.lookup(user) + {%User{user | account: account.id}, account} + end + Storage.insert(User.to_tuple(user)) + if chan, do: Nola.Membership.touch(account, network, chan) + if !m.account do + {:ok, %IRC.Message{m | account: account}} + else + :ok + end + end + + def renamed(network, old_nick, new_nick) do + if user = find_by_nick(network, old_nick) do + old_account = Nola.Account.lookup(user) + user = %User{user | nick: new_nick, nicks: [old_nick|user.nicks]} + account = Nola.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 + + def change_privileges(network, channel, nick, {add, remove}) do + if user = find_by_nick(network, nick) do + privs = Map.get(user.privileges, channel) + + privs = Enum.reduce(add, privs, fn(priv, acc) -> [priv|acc] end) + privs = Enum.reduce(remove, privs, fn(priv, acc) -> List.delete(acc, priv) end) + + 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 + + def parted(network, channel, nick) do + if user = find_by_nick(network, nick) do + if user.account do + Nola.Membership.touch(user.account, network, channel) + end + + privs = Map.delete(user.privileges, channel) + lasts = Map.delete(user.last_active, channel) + 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, reason) do + if user = find_by_nick(sender.network, sender.nick) do + if user.account do + for {channel, _} <- user.privileges do + Nola.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 + end + + defp touch_struct(user = %User{last_active: last_active}, channel) do + now = NaiveDateTime.utc_now() + last_active = last_active + |> Map.put(channel, now) + |> Map.put(nil, now) + %User{user | last_active: last_active} + end + + defp userchans(%{privileges: privileges}) do + for({chan, _} <- privileges, do: chan) + end +end diff --git a/lib/plugins/account.ex b/lib/plugins/account.ex index 747fbc7..96405bb 100644 --- a/lib/plugins/account.ex +++ b/lib/plugins/account.ex @@ -47,7 +47,7 @@ defmodule Nola.Plugins.Account do end def handle_info({:irc, :text, m = %IRC.Message{account: account, text: "whoami"}}, state) do - users = for user <- IRC.UserTrack.find_by_account(m.account) do + users = for user <- Nola.UserTrack.find_by_account(m.account) do chans = Enum.map(user.privileges, fn({chan, _}) -> chan end) |> Enum.join(" ") "#{user.network} - #{user.nick}!#{user.username}@#{user.host} - #{chans}" diff --git a/lib/plugins/alcoolog.ex b/lib/plugins/alcoolog.ex index 738be71..cc56c4f 100644 --- a/lib/plugins/alcoolog.ex +++ b/lib/plugins/alcoolog.ex @@ -426,7 +426,7 @@ defmodule Nola.Plugins.Alcoolog do m.replyfun.(msg.(m.sender.nick, local_extra)) notify = Nola.Membership.notify_channels(m.account) -- [{m.network,m.channel}] for {net, chan} <- notify do - user = IRC.UserTrack.find_by_account(net, m.account) + user = Nola.UserTrack.find_by_account(net, m.account) nick = if(user, do: user.nick, else: m.account.name) extra = " " <> present_type(name, comment) <> "" IRC.Connection.broadcast_message(net, chan, msg.(nick, extra)) @@ -452,7 +452,7 @@ defmodule Nola.Plugins.Alcoolog do miss = Nola.Plugins.Txt.random("alcoolog.#{to_string(miss)}") if miss do for {net, chan} <- Nola.Membership.notify_channels(m.account) do - user = IRC.UserTrack.find_by_account(net, m.account) + user = Nola.UserTrack.find_by_account(net, m.account) nick = if(user, do: user.nick, else: m.account.name) IRC.Connection.broadcast_message(net, chan, "#{nick}: #{miss}") end @@ -617,7 +617,7 @@ defmodule Nola.Plugins.Alcoolog do end if account do - user = IRC.UserTrack.find_by_account(m.network, account) + 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 @@ -785,7 +785,7 @@ defmodule Nola.Plugins.Alcoolog do |> Enum.sort_by(fn({_nick, count}) -> count end, &>/2) |> Enum.map(fn({nick, count}) -> account = Nola.Account.get(nick) - user = IRC.UserTrack.find_by_account(m.network, account) + user = Nola.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) "#{nick}: #{Float.round(count, 4)}" end) @@ -851,7 +851,7 @@ defmodule Nola.Plugins.Alcoolog do Nola.Plugins.Txt.reply_random(m, "alcoolog.delete") notify = Nola.Membership.notify_channels(m.account) -- [{m.network,m.channel}] for {net, chan} <- notify do - user = IRC.UserTrack.find_by_account(net, m.account) + user = Nola.UserTrack.find_by_account(net, m.account) nick = if(user, do: user.nick, else: m.account.name) IRC.Connection.broadcast_message(net, chan, "#{nick} -santai #{points} #{type} #{descr}") end @@ -877,7 +877,7 @@ defmodule Nola.Plugins.Alcoolog do end _ -> nil end - user = IRC.UserTrack.find_by_account(m.network, account) + 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 diff --git a/lib/plugins/alcoolog_announcer.ex b/lib/plugins/alcoolog_announcer.ex index 9f66799..937002e 100644 --- a/lib/plugins/alcoolog_announcer.ex +++ b/lib/plugins/alcoolog_announcer.ex @@ -236,7 +236,7 @@ defmodule Nola.Plugins.AlcoologAnnouncer do #IO.puts("#{acct}: #{message}") account = Nola.Account.get(acct) for {net, chan} <- Nola.Membership.notify_channels(account) do - user = IRC.UserTrack.find_by_account(net, account) + user = Nola.UserTrack.find_by_account(net, account) nick = if(user, do: user.nick, else: account.name) IRC.Connection.broadcast_message(net, chan, "#{nick}: #{message}") end diff --git a/lib/plugins/last_fm.ex b/lib/plugins/last_fm.ex index 68c55ee..8e872ea 100644 --- a/lib/plugins/last_fm.ex +++ b/lib/plugins/last_fm.ex @@ -108,7 +108,7 @@ defmodule Nola.Plugins.LastFm do track = fetch_track(username, map) text = format_now_playing(map, track) user = if account = Nola.Account.get(id_or_user) do - user = IRC.UserTrack.find_by_account(message.network, account) + user = Nola.UserTrack.find_by_account(message.network, account) if(user, do: user.nick, else: account.name) else username diff --git a/lib/plugins/preums.ex b/lib/plugins/preums.ex index c55248d..505ce7f 100644 --- a/lib/plugins/preums.ex +++ b/lib/plugins/preums.ex @@ -143,7 +143,7 @@ defmodule Nola.Plugins.Preums do {_, account_id, date, _perfect, text} = item h = "#{date.hour}:#{date.minute}:#{date.second}" account = Nola.Account.get(account_id) - user = IRC.UserTrack.find_by_account(m.network, account) + user = Nola.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) m.replyfun.("preums: #{nick} à #{h}: “#{text}”") end @@ -157,7 +157,7 @@ defmodule Nola.Plugins.Preums do top = topnicks(state.dets, channel, sort_by: :score) |> Enum.map(fn({account_id, {count, score}}) -> account = Nola.Account.get(account_id) - user = IRC.UserTrack.find_by_account(m.network, account) + user = Nola.UserTrack.find_by_account(m.network, account) nick = if(user, do: user.nick, else: account.name) "#{nick}: #{score} (#{count})" end) diff --git a/lib/plugins/tell.ex b/lib/plugins/tell.ex index 2f874d0..43da9e7 100644 --- a/lib/plugins/tell.ex +++ b/lib/plugins/tell.ex @@ -43,7 +43,7 @@ defmodule Nola.Plugins.Tell do if messages != [] do strs = Enum.map(messages, fn({_, from, message, at}) -> account = Nola.Account.get(from) - user = IRC.UserTrack.find_by_account(network, account) + user = Nola.UserTrack.find_by_account(network, account) fromnick = if user, do: user.nick, else: account.name "#{nick}: <#{fromnick}> #{message}" end) @@ -86,7 +86,7 @@ defmodule Nola.Plugins.Tell do with \ {:target, %Nola.Account{} = target} <- {:target, target}, {:same, false} <- {:same, target.id == m.account.id}, - target_user = IRC.UserTrack.find_by_account(m.network, target), + target_user = Nola.UserTrack.find_by_account(m.network, target), target_nick = if(target_user, do: target_user.nick, else: target.name), present? = if(target_user, do: Map.has_key?(target_user.last_active, m.channel)), {:absent, true, _} <- {:absent, !present?, target_nick}, diff --git a/lib/plugins/txt.ex b/lib/plugins/txt.ex index f33aab0..4f9a803 100644 --- a/lib/plugins/txt.ex +++ b/lib/plugins/txt.ex @@ -1,5 +1,5 @@ defmodule Nola.Plugins.Txt do - alias IRC.UserTrack + alias Nola.UserTrack require Logger @moduledoc """ @@ -538,7 +538,7 @@ defmodule Nola.Plugins.Txt do defp can_write?(state = %__MODULE__{rw: rw?, locks: locks}, msg = %{channel: channel, sender: sender}, trigger) do admin? = IRC.admin?(sender) - operator? = IRC.UserTrack.operator?(msg.network, channel, sender.nick) + operator? = Nola.UserTrack.operator?(msg.network, channel, sender.nick) locked? = case :dets.lookup(locks, trigger) do [{trigger}] -> true _ -> false diff --git a/lib/tmpl.ex b/lib/tmpl.ex index f4dac02..62c2a46 100644 --- a/lib/tmpl.ex +++ b/lib/tmpl.ex @@ -43,7 +43,7 @@ defmodule Tmpl do end def account_nick(%{"id" => id, "name" => name}, %{variables: %{"message" => %{"network" => network}}}) do - if user = IRC.UserTrack.find_by_account(network, %Nola.Account{id: id}) do + if user = Nola.UserTrack.find_by_account(network, %Nola.Account{id: id}) do user.nick else name diff --git a/lib/web/controllers/irc_controller.ex b/lib/web/controllers/irc_controller.ex index 441cbe7..d2ba04a 100644 --- a/lib/web/controllers/irc_controller.ex +++ b/lib/web/controllers/irc_controller.ex @@ -15,7 +15,7 @@ defmodule NolaWeb.IrcController do |> Enum.filter(& &1) |> Enum.filter(fn({_, doc}) -> doc end) members = cond do - network && channel -> Enum.map(IRC.UserTrack.channel(network, channel), fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple) end) + 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 diff --git a/lib/web/controllers/page_controller.ex b/lib/web/controllers/page_controller.ex index a6b85b6..bf79413 100644 --- a/lib/web/controllers/page_controller.ex +++ b/lib/web/controllers/page_controller.ex @@ -24,7 +24,7 @@ defmodule NolaWeb.PageController do def index(conn = %{assigns: %{account: account}}, _) do memberships = Nola.Membership.of_account(account) - users = IRC.UserTrack.find_by_account(account) + users = Nola.UserTrack.find_by_account(account) metas = Nola.Account.get_all_meta(account) predicates = Nola.Account.get_predicates(account) conn diff --git a/lib/web/live/chat_live.ex b/lib/web/live/chat_live.ex index 2d5e289..3f126f2 100644 --- a/lib/web/live/chat_live.ex +++ b/lib/web/live/chat_live.ex @@ -16,8 +16,8 @@ defmodule NolaWeb.ChatLive do IRC.PuppetConnection.start(account, connection) - users = IRC.UserTrack.channel(connection.network, chan) - |> Enum.map(fn(tuple) -> IRC.UserTrack.User.from_tuple(tuple) end) + 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) @@ -53,7 +53,7 @@ defmodule NolaWeb.ChatLive do end def handle_info({:irc, :event, event = %{type: :join, user_id: id}}, socket) do - if user = IRC.UserTrack.lookup(id) do + if user = Nola.UserTrack.lookup(id) do socket = socket |> assign(:users, Map.put(socket.assigns.users, id, user)) |> append_to_backlog(event) -- cgit v1.2.3