summaryrefslogtreecommitdiff
path: root/lib/nola
diff options
context:
space:
mode:
Diffstat (limited to 'lib/nola')
-rw-r--r--lib/nola/account.ex8
-rw-r--r--lib/nola/auth_token.ex2
-rw-r--r--lib/nola/membership.ex2
-rw-r--r--lib/nola/plugins.ex1
-rw-r--r--lib/nola/token.ex2
-rw-r--r--lib/nola/user_track.ex329
6 files changed, 337 insertions, 7 deletions
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