diff options
author | Jordan Bracco <href@random.sh> | 2025-06-25 19:22:59 +0200 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2025-06-25 19:22:59 +0200 |
commit | c934e79e5852e05f714b2d542cc2678e287c49b8 (patch) | |
tree | 55779a0168260fce03e4775eacdd613ffc945588 /lib/nola | |
parent | updates (diff) |
format.
Diffstat (limited to 'lib/nola')
-rw-r--r-- | lib/nola/account.ex | 58 | ||||
-rw-r--r-- | lib/nola/auth_token.ex | 21 | ||||
-rw-r--r-- | lib/nola/icecast.ex | 100 | ||||
-rw-r--r-- | lib/nola/icecast_agent.ex | 6 | ||||
-rw-r--r-- | lib/nola/membership.ex | 43 | ||||
-rw-r--r-- | lib/nola/message.ex | 21 | ||||
-rw-r--r-- | lib/nola/plugins.ex | 44 | ||||
-rw-r--r-- | lib/nola/subnet.ex | 67 | ||||
-rw-r--r-- | lib/nola/token.ex | 24 | ||||
-rw-r--r-- | lib/nola/trigger.ex | 1 | ||||
-rw-r--r-- | lib/nola/user_track.ex | 323 |
11 files changed, 465 insertions, 243 deletions
diff --git a/lib/nola/account.ex b/lib/nola/account.ex index 70e9e40..d850a82 100644 --- a/lib/nola/account.ex +++ b/lib/nola/account.ex @@ -44,7 +44,7 @@ defmodule Nola.Account do end def start_link() do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do @@ -63,6 +63,7 @@ defmodule Nola.Account do def get_by_name(name) do spec = [{{:_, :"$1", :_}, [{:==, :"$1", {:const, name}}], [:"$_"]}] + case :dets.select(file("db"), spec) do [account] -> from_tuple(account) _ -> nil @@ -71,7 +72,7 @@ defmodule Nola.Account do def get_meta(%__MODULE__{id: id}, key, default \\ nil) do case :dets.lookup(file("meta"), {id, key}) do - [{_, value}] -> (value || default) + [{_, value}] -> value || default _ -> default end end @@ -86,8 +87,12 @@ defmodule Nola.Account do @doc "Find an account given a specific meta `key` and `value`." @spec find_meta_account(String.t(), String.t()) :: t() | nil def find_meta_account(key, value) do - #spec = [{{{:"$1", :"$2"}, :"$3"}, [:andalso, {:==, :"$2", {:const, key}}, {:==, :"$3", {:const, value}}], [:"$1"]}] - spec = [{{{:"$1", :"$2"}, :"$3"}, [{:andalso, {:==, :"$2", {:const, key}}, {:==, {:const, value}, :"$3"}}], [:"$1"]}] + # spec = [{{{:"$1", :"$2"}, :"$3"}, [:andalso, {:==, :"$2", {:const, key}}, {:==, :"$3", {:const, value}}], [:"$1"]}] + spec = [ + {{{:"$1", :"$2"}, :"$3"}, + [{:andalso, {:==, :"$2", {:const, key}}, {:==, {:const, value}, :"$3"}}], [:"$1"]} + ] + case :dets.select(file("meta"), spec) do [id] -> get(id) _ -> nil @@ -100,7 +105,7 @@ defmodule Nola.Account do end def put_user_meta(account = %__MODULE__{}, key, value) do - put_meta(account, "u:"<>key, value) + put_meta(account, "u:" <> key, value) end def put_meta(%__MODULE__{id: id}, key, value) do @@ -112,15 +117,15 @@ defmodule Nola.Account do end def all_accounts() do - :dets.traverse(file("db"), fn(obj) -> {:continue, from_tuple(obj)} end) + :dets.traverse(file("db"), fn obj -> {:continue, from_tuple(obj)} end) end def all_predicates() do - :dets.traverse(file("predicates"), fn(obj) -> {:continue, obj} end) + :dets.traverse(file("predicates"), fn obj -> {:continue, obj} end) end def all_meta() do - :dets.traverse(file("meta"), fn(obj) -> {:continue, obj} end) + :dets.traverse(file("meta"), fn obj -> {:continue, obj} end) end def merge_account(old_id, new_id) do @@ -130,16 +135,19 @@ defmodule Nola.Account do for pred <- predicates, do: :ok = :dets.insert(file("predicates"), {pred, new_id}) spec = [{{{:"$1", :"$2"}, :"$3"}, [{:==, :"$1", {:const, old_id}}], [{{:"$2", :"$3"}}]}] metas = :dets.select(file("meta"), spec) - for {k,v} <- metas do + + for {k, v} <- metas do :dets.delete(file("meta"), {{old_id, k}}) :ok = :dets.insert(file("meta"), {{new_id, k}, v}) end + :dets.delete(file("db"), old_id) Nola.Membership.merge_account(old_id, new_id) Nola.UserTrack.merge_account(old_id, new_id) Nola.Irc.Connection.dispatch("account", {:account_change, old_id, new_id}) Nola.Irc.Connection.dispatch("conn", {:account_change, old_id, new_id}) end + :ok end @@ -150,16 +158,15 @@ defmodule Nola.Account do @doc "Always find an account by nickname, even if offline. Uses predicates and then account name." def find_always_by_nick(network, chan, nick) do - with \ - nil <- find_by_nick(network, nick), + with nil <- find_by_nick(network, nick), nil <- do_lookup(%User{network: network, nick: nick}, false), - nil <- get_by_name(nick) - do + nil <- get_by_name(nick) do nil else %__MODULE__{} = account -> memberships = Nola.Membership.of_account(account) - if Enum.any?(memberships, fn({net, ch}) -> (net == network) or (chan && chan == ch) end) do + + if Enum.any?(memberships, fn {net, ch} -> net == network or (chan && chan == ch) end) do account else nil @@ -173,9 +180,14 @@ defmodule Nola.Account do def lookup(something, make_default \\ true) do account = do_lookup(something, make_default) + if account && Map.get(something, :nick) do - Nola.Irc.Connection.dispatch("account", {:account_auth, Map.get(something, :nick), account.id}) + Nola.Irc.Connection.dispatch( + "account", + {:account_auth, Map.get(something, :nick), account.id} + ) end + account end @@ -198,7 +210,8 @@ defmodule Nola.Account do end end - defp do_lookup(message = %Nola.Message{account: account_id}, make_default) when is_binary(account_id) do + defp do_lookup(message = %Nola.Message{account: account_id}, make_default) + when is_binary(account_id) do get(account_id) end @@ -206,8 +219,12 @@ defmodule Nola.Account 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 - lookup_by_nick(sender, :dets.lookup(file("predicates"), {sender.network,{:nick, sender.nick}}), make_default) + # FIXME this will never work with continued lookup by other methods as Who isn't compatible + lookup_by_nick( + sender, + :dets.lookup(file("predicates"), {sender.network, {:nick, sender.nick}}), + make_default + ) end end @@ -220,7 +237,7 @@ defmodule Nola.Account do end defp do_lookup(user = %User{network: server, nick: nick}, make_default) do - lookup_by_nick(user, :dets.lookup(file("predicates"), {server,{:nick, nick}}), make_default) + lookup_by_nick(user, :dets.lookup(file("predicates"), {server, {:nick, nick}}), make_default) end defp do_lookup(nil, _) do @@ -232,7 +249,7 @@ defmodule Nola.Account do end defp lookup_by_nick(user, _, make_default) do - #authenticate_by_host(user) + # authenticate_by_host(user) if make_default, do: new_account(user), else: nil end @@ -259,5 +276,4 @@ defmodule Nola.Account do spec = [{{:"$1", :"$2"}, [{:==, :"$2", {:const, account.id}}], [:"$1"]}] :dets.select(file("predicates"), spec) end - end diff --git a/lib/nola/auth_token.ex b/lib/nola/auth_token.ex index 9760ec7..0da4aab 100644 --- a/lib/nola/auth_token.ex +++ b/lib/nola/auth_token.ex @@ -2,7 +2,7 @@ defmodule Nola.AuthToken do use GenServer def start_link() do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def lookup(id) do @@ -13,6 +13,7 @@ defmodule Nola.AuthToken do case new(account, perks) do {:ok, id} -> NolaWeb.Router.Helpers.login_path(NolaWeb.Endpoint, :token, id) + error -> error end @@ -22,6 +23,7 @@ defmodule Nola.AuthToken do case new(account, perks) do {:ok, id} -> NolaWeb.Router.Helpers.login_url(NolaWeb.Endpoint, :token, id) + error -> error end @@ -32,15 +34,14 @@ defmodule Nola.AuthToken do end def init(_) do - {:ok, Map.new} + {:ok, Map.new()} end def handle_call({:lookup, id}, _, state) do IO.inspect(state) - with \ - {account, date, perks} <- Map.get(state, id), - d when d > 0 <- DateTime.diff(date, DateTime.utc_now()) - do + + with {account, date, perks} <- Map.get(state, id), + d when d > 0 <- DateTime.diff(date, DateTime.utc_now()) do {:reply, {:ok, account, perks}, Map.delete(state, id)} else x -> @@ -51,9 +52,11 @@ defmodule Nola.AuthToken do def handle_call({:new, account, perks}, _, state) do id = Nola.UserTrack.Id.token() - expire = DateTime.utc_now() - |> DateTime.add(15*60, :second) + + expire = + DateTime.utc_now() + |> DateTime.add(15 * 60, :second) + {:reply, {:ok, id}, Map.put(state, id, {account, expire, perks})} end - end diff --git a/lib/nola/icecast.ex b/lib/nola/icecast.ex index 5a53192..021af0b 100644 --- a/lib/nola/icecast.ex +++ b/lib/nola/icecast.ex @@ -23,69 +23,82 @@ defmodule Nola.Icecast do end defp poll(state) do - state = case request(base_url(), :get) do - {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> - #update_json_stats(Jason.decode(body)) - stats = update_stats(body) - if state != stats do - Logger.info "Icecast Update: " <> inspect(stats) - Nola.IcecastAgent.update(stats) - Registry.dispatch(Nola.BroadcastRegistry, "icecast", fn ws -> - for {pid, _} <- ws, do: send(pid, {:icecast, stats}) - end) - stats - else + state = + case request(base_url(), :get) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + # update_json_stats(Jason.decode(body)) + stats = update_stats(body) + + if state != stats do + Logger.info("Icecast Update: " <> inspect(stats)) + Nola.IcecastAgent.update(stats) + + Registry.dispatch(Nola.BroadcastRegistry, "icecast", fn ws -> + for {pid, _} <- ws, do: send(pid, {:icecast, stats}) + end) + + stats + else + state + end + + error -> + Logger.error("Icecast HTTP Error: #{inspect(error)}") state - end - error -> - Logger.error "Icecast HTTP Error: #{inspect error}" - state - end + end + interval = Application.get_env(:nola, :icecast_poll_interval, 60_000) :timer.send_after(interval, :poll) state end defp update_stats(html) do - raw = Floki.find(html, "div.roundbox") - |> Enum.map(fn(html) -> - html = Floki.raw_html(html) - [{"h3", _, ["Mount Point /"<>mount]}] = Floki.find(html, "h3.mount") - stats = Floki.find(html, "tr") - |> Enum.map(fn({"tr", _, tds}) -> - [{"td", _, keys}, {"td", _, values}] = tds - key = List.first(keys) - value = List.first(values) - {key, value} + raw = + Floki.find(html, "div.roundbox") + |> Enum.map(fn html -> + html = Floki.raw_html(html) + [{"h3", _, ["Mount Point /" <> mount]}] = Floki.find(html, "h3.mount") + + stats = + Floki.find(html, "tr") + |> Enum.map(fn {"tr", _, tds} -> + [{"td", _, keys}, {"td", _, values}] = tds + key = List.first(keys) + value = List.first(values) + {key, value} + end) + |> Enum.into(Map.new()) + + {mount, stats} end) - |> Enum.into(Map.new) - {mount, stats} - end) - |> Enum.into(Map.new) + |> Enum.into(Map.new()) live? = if Map.get(raw["live"], "Content Type:", false), do: true, else: false - np = if live? do - raw["live"]["Currently playing:"] - else - raw["autodj"]["Currently playing:"] - end + + np = + if live? do + raw["live"]["Currently playing:"] + else + raw["autodj"]["Currently playing:"] + end genre = raw["live"]["Genre:"] || nil %{np: np || "", live: live? || false, genre: genre} end defp update_json_stats({:ok, body}) do - Logger.debug "JSON STATS: #{inspect body}" + Logger.debug("JSON STATS: #{inspect(body)}") end defp update_json_stats(error) do - Logger.error "Failed to decode JSON Stats: #{inspect error}" + Logger.error("Failed to decode JSON Stats: #{inspect(error)}") end defp request(uri, method, body \\ [], headers \\ []) do headers = [{"user-agent", "Nola-API[115ans.net, sys.115ans.net] href@random.sh"}] ++ headers options = @httpoison_opts - case :ok do #:fuse.ask(@fuse, :sync) do + # :fuse.ask(@fuse, :sync) do + case :ok do :ok -> run_request(method, uri, body, headers, options) :blown -> :blown end @@ -93,14 +106,18 @@ defmodule Nola.Icecast do # This is to work around hackney's behaviour of returning `{:error, :closed}` when a pool connection has been closed # (keep-alive expired). We just retry the request immediatly up to five times. - defp run_request(method, uri, body, headers, options), do: run_request(method, uri, body, headers, options, 0) + defp run_request(method, uri, body, headers, options), + do: run_request(method, uri, body, headers, options, 0) + defp run_request(method, uri, body, headers, options, retries) when retries < 4 do case HTTPoison.request(method, uri, body, headers, options) do {:error, :closed} -> run_request(method, uri, body, headers, options, retries + 1) other -> other end end - defp run_request(method, uri, body, headers, options, _exceeded_retries), do: {:error, :unavailable} + + defp run_request(method, uri, body, headers, options, _exceeded_retries), + do: {:error, :unavailable} # # -- URIs @@ -113,5 +130,4 @@ defmodule Nola.Icecast do defp base_url do "http://91.121.59.45:8089" end - end diff --git a/lib/nola/icecast_agent.ex b/lib/nola/icecast_agent.ex index 8a3a72b..563b372 100644 --- a/lib/nola/icecast_agent.ex +++ b/lib/nola/icecast_agent.ex @@ -6,12 +6,10 @@ defmodule Nola.IcecastAgent do end def update(stats) do - Agent.update(__MODULE__, fn(_old) -> stats end) + Agent.update(__MODULE__, fn _old -> stats end) end def get do - Agent.get(__MODULE__, fn(stats) -> stats end) + Agent.get(__MODULE__, fn stats -> stats end) end - end - diff --git a/lib/nola/membership.ex b/lib/nola/membership.ex index 1c7303b..182f44d 100644 --- a/lib/nola/membership.ex +++ b/lib/nola/membership.ex @@ -7,11 +7,11 @@ defmodule Nola.Membership do # Format: {key, last_seen} defp dets() do - to_charlist(Nola.data_path <> "/memberships.dets") + to_charlist(Nola.data_path() <> "/memberships.dets") end def start_link() do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init(_) do @@ -25,9 +25,10 @@ defmodule Nola.Membership do end def merge_account(old_id, new_id) do - #iex(37)> :ets.fun2ms(fn({{old_id, _, _}, _}=obj) when old_id == "42" -> obj end) + # iex(37)> :ets.fun2ms(fn({{old_id, _, _}, _}=obj) when old_id == "42" -> obj end) spec = [{{{:"$1", :_, :_}, :_}, [{:==, :"$1", {:const, old_id}}], [:"$_"]}] - Util.ets_mutate_select_each(:dets, dets(), spec, fn(table, obj = {{_old, net, chan}, ts}) -> + + Util.ets_mutate_select_each(:dets, dets(), spec, fn table, obj = {{_old, net, chan}, ts} -> :dets.delete_object(table, obj) :dets.insert(table, {{new_id, net, chan}, ts}) end) @@ -36,6 +37,7 @@ defmodule Nola.Membership do def touch(%Nola.Account{id: id}, network, channel) do :dets.insert(dets(), {{id, network, channel}, NaiveDateTime.utc_now()}) end + def touch(account_id, network, channel) do if account = Nola.Account.get(account_id) do touch(account, network, channel) @@ -43,21 +45,28 @@ defmodule Nola.Membership do end def notify_channels(account, minutes \\ 30, last_active \\ true) do - not_before = NaiveDateTime.add(NaiveDateTime.utc_now(), (minutes*-60), :second) + not_before = NaiveDateTime.add(NaiveDateTime.utc_now(), minutes * -60, :second) spec = [{{{:"$1", :_, :_}, :_}, [{:==, :"$1", {:const, account.id}}], [:"$_"]}] - memberships = :dets.select(dets(), spec) - |> Enum.sort_by(fn({_, ts}) -> ts end, {:desc, NaiveDateTime}) - active_memberships = Enum.filter(memberships, fn({_, ts}) -> NaiveDateTime.compare(ts, not_before) == :gt end) + + memberships = + :dets.select(dets(), spec) + |> Enum.sort_by(fn {_, ts} -> ts end, {:desc, NaiveDateTime}) + + active_memberships = + Enum.filter(memberships, fn {_, ts} -> NaiveDateTime.compare(ts, not_before) == :gt end) + cond do active_memberships == [] && last_active -> case memberships do - [{{_, net, chan}, _}|_] -> [{net, chan}] + [{{_, net, chan}, _} | _] -> [{net, chan}] _ -> [] end + active_memberships == [] -> [] + true -> - Enum.map(active_memberships, fn({{_, net, chan}, _}) -> {net,chan} end) + Enum.map(active_memberships, fn {{_, net, chan}, _} -> {net, chan} end) end end @@ -78,16 +87,18 @@ defmodule Nola.Membership do end def members(network, channel) do - #iex(19)> :ets.fun2ms(fn({{id, net, chan}, ts}) when net == network and chan == channel and ts > min_seen -> id end) - limit = 0 # NaiveDateTime.add(NaiveDateTime.utc_now, 30*((24*-60)*60), :second) + # iex(19)> :ets.fun2ms(fn({{id, net, chan}, ts}) when net == network and chan == channel and ts > min_seen -> id end) + # NaiveDateTime.add(NaiveDateTime.utc_now, 30*((24*-60)*60), :second) + limit = 0 + spec = [ {{{:"$1", :"$2", :"$3"}, :"$4"}, [ - {:andalso, - {:andalso, {:==, :"$2", {:const, network}}, {:==, :"$3", {:const, channel}}}, + {:andalso, {:andalso, {:==, :"$2", {:const, network}}, {:==, :"$3", {:const, channel}}}, {:>, :"$4", {:const, limit}}} ], [:"$1"]} ] + :dets.select(dets(), spec) end @@ -122,8 +133,6 @@ defmodule Nola.Membership do {account, user, nick} end end - |> Enum.filter(fn(x) -> x end) + |> Enum.filter(fn x -> x end) end - - end diff --git a/lib/nola/message.ex b/lib/nola/message.ex index b4e76da..1819e77 100644 --- a/lib/nola/message.ex +++ b/lib/nola/message.ex @@ -9,15 +9,14 @@ defmodule Nola.Message do defstruct [ :id, :text, - {:transport, :irc}, - :network, - :account, - :sender, - :channel, - :trigger, - :replyfun, - :at, - {:meta, %{}} - ] - + {:transport, :irc}, + :network, + :account, + :sender, + :channel, + :trigger, + :replyfun, + :at, + {:meta, %{}} + ] end diff --git a/lib/nola/plugins.ex b/lib/nola/plugins.ex index ac94736..6bfcfec 100644 --- a/lib/nola/plugins.ex +++ b/lib/nola/plugins.ex @@ -31,7 +31,7 @@ defmodule Nola.Plugins do Nola.Plugins.Untappd, Nola.Plugins.UserMention, Nola.Plugins.WolframAlpha, - Nola.Plugins.YouTube, + Nola.Plugins.YouTube ] defmodule Supervisor do @@ -44,13 +44,23 @@ defmodule Nola.Plugins do def start_child(module, opts \\ []) do Logger.info("Starting #{module}") - spec = %{id: {Nola.Plugins,module}, start: {Nola.Plugins, :start_link, [module, opts]}, name: module, restart: :transient} + + spec = %{ + id: {Nola.Plugins, module}, + start: {Nola.Plugins, :start_link, [module, opts]}, + name: module, + restart: :transient + } + case DynamicSupervisor.start_child(__MODULE__, spec) do - {:ok, _} = res -> res + {:ok, _} = res -> + res + :ignore -> Logger.warn("Ignored #{module}") :ignore - {:error,_} = res -> + + {:error, _} = res -> Logger.error("Could not start #{module}: #{inspect(res, pretty: true)}") res end @@ -73,10 +83,14 @@ defmodule Nola.Plugins do end def enabled() do - :dets.foldl(fn - {name, true, _}, acc -> [name | acc] - _, acc -> acc - end, [], dets()) + :dets.foldl( + fn + {name, true, _}, acc -> [name | acc] + _, acc -> acc + end, + [], + dets() + ) end def start_all() do @@ -107,10 +121,12 @@ defmodule Nola.Plugins do @doc "Enables or disables a plugin" def switch(name, value) when is_boolean(value) do - last = case get(name) do - {:ok, last} -> last - _ -> nil - end + last = + case get(name) do + {:ok, last} -> last + _ -> nil + end + :dets.insert(dets(), {name, value, last}) end @@ -124,8 +140,7 @@ defmodule Nola.Plugins do def start_link(module, options \\ []) do with {:disabled, {_, true, last}} <- {:disabled, get(module)}, - {:throttled, false} <- {:throttled, false} - do + {:throttled, false} <- {:throttled, false} do module.start_link() else {error, _} -> @@ -133,5 +148,4 @@ defmodule Nola.Plugins do :ignore end end - end diff --git a/lib/nola/subnet.ex b/lib/nola/subnet.ex index ac9d8e6..de469a6 100644 --- a/lib/nola/subnet.ex +++ b/lib/nola/subnet.ex @@ -17,22 +17,24 @@ defmodule Nola.Subnet do end def assign(binary) when is_binary(binary) do - result = if subnet = find_subnet_for(binary) do - {:ok, subnet} - else - Agent.get_and_update(__MODULE__, fn(dets) -> - {subnet, _} = available_select(dets) - :dets.insert(dets, {subnet, binary}) - :dets.sync(dets) - {{:new, subnet}, dets} - end) - end + result = + if subnet = find_subnet_for(binary) do + {:ok, subnet} + else + Agent.get_and_update(__MODULE__, fn dets -> + {subnet, _} = available_select(dets) + :dets.insert(dets, {subnet, binary}) + :dets.sync(dets) + {{:new, subnet}, dets} + end) + end case result do {:new, subnet} -> ip = Pfx.host(subnet, 1) set_reverse(binary, ip) subnet + {:ok, subnet} -> subnet end @@ -49,16 +51,40 @@ defmodule Nola.Subnet do ip_fqdn = Pfx.dns_ptr(ip) ip_local = String.replace(ip_fqdn, ".#{ptr_zone}", "") rev? = String.ends_with?(value, ".users.goulag.org") + if rev? do {:ok, rev_zone} = PowerDNSex.show_zone("users.goulag.org") - rev_update? = Enum.any?(rev_zone.rrsets, fn(rr) -> rr.name == "#{ip_fqdn}." end) - record = %{name: "#{value}.", type: "AAAA", ttl: 8600, records: [%{content: ip, disabled: false}]} - if(rev_update?, do: PowerDNSex.update_record(rev_zone, record), else: PowerDNSex.create_record(rev_zone, record)) + rev_update? = Enum.any?(rev_zone.rrsets, fn rr -> rr.name == "#{ip_fqdn}." end) + + record = %{ + name: "#{value}.", + type: "AAAA", + ttl: 8600, + records: [%{content: ip, disabled: false}] + } + + if(rev_update?, + do: PowerDNSex.update_record(rev_zone, record), + else: PowerDNSex.create_record(rev_zone, record) + ) end + {:ok, zone} = PowerDNSex.show_zone(ptr_zone) - update? = Enum.any?(zone.rrsets, fn(rr) -> rr.name == "#{ip_fqdn}." end) - record = %{name: "#{ip_fqdn}.", type: "PTR", ttl: 3600, records: [%{content: "#{value}.", disabled: false}]} - pdns = if(update?, do: PowerDNSex.update_record(zone, record), else: PowerDNSex.create_record(zone, record)) + update? = Enum.any?(zone.rrsets, fn rr -> rr.name == "#{ip_fqdn}." end) + + record = %{ + name: "#{ip_fqdn}.", + type: "PTR", + ttl: 3600, + records: [%{content: "#{value}.", disabled: false}] + } + + pdns = + if(update?, + do: PowerDNSex.update_record(zone, record), + else: PowerDNSex.create_record(zone, record) + ) + :ok end @@ -76,9 +102,10 @@ defmodule Nola.Subnet do defp available_select(dets) do spec = [{{:"$1", :"$2"}, [is_integer: :"$2"], [{{:"$1", :"$2"}}]}] {subnets, _} = :dets.select(dets, spec, 20) - subnet = subnets - |> Enum.sort_by(fn({_, last}) -> last end) - |> List.first() - end + subnet = + subnets + |> Enum.sort_by(fn {_, last} -> last end) + |> List.first() + end end diff --git a/lib/nola/token.ex b/lib/nola/token.ex index 179bed2..f4fdd86 100644 --- a/lib/nola/token.ex +++ b/lib/nola/token.ex @@ -2,15 +2,15 @@ defmodule Nola.Token do use GenServer def start_link() do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def lookup(id) do - with \ - [{_, cred, date}] <- :ets.lookup(__MODULE__.ETS, id), - IO.inspect("cred: #{inspect cred} valid for #{inspect date} now #{inspect DateTime.utc_now()}"), - d when d > 0 <- DateTime.diff(date, DateTime.utc_now()) - do + with [{_, cred, date}] <- :ets.lookup(__MODULE__.ETS, id), + IO.inspect( + "cred: #{inspect(cred)} valid for #{inspect(date)} now #{inspect(DateTime.utc_now())}" + ), + d when d > 0 <- DateTime.diff(date, DateTime.utc_now()) do {:ok, cred} else err -> {:error, err} @@ -22,17 +22,21 @@ defmodule Nola.Token do end def init(_) do - ets = :ets.new(__MODULE__.ETS, [:ordered_set, :named_table, :protected, {:read_concurrency, true}]) + ets = + :ets.new(__MODULE__.ETS, [:ordered_set, :named_table, :protected, {:read_concurrency, true}]) + {:ok, ets} end def handle_call({:new, cred}, _, ets) do id = Nola.UserTrack.Id.large_id() - expire = DateTime.utc_now() - |> DateTime.add(15*60, :second) + + expire = + DateTime.utc_now() + |> DateTime.add(15 * 60, :second) + obj = {id, cred, expire} :ets.insert(ets, obj) {:reply, {:ok, id}, ets} end - end diff --git a/lib/nola/trigger.ex b/lib/nola/trigger.ex index 1dec9ac..d3e791b 100644 --- a/lib/nola/trigger.ex +++ b/lib/nola/trigger.ex @@ -8,5 +8,4 @@ defmodule Nola.Trigger do :trigger, :args ] - end diff --git a/lib/nola/user_track.ex b/lib/nola/user_track.ex index c1218b0..0b07a91 100644 --- a/lib/nola/user_track.ex +++ b/lib/nola/user_track.ex @@ -8,23 +8,23 @@ defmodule Nola.UserTrack do # Privilege map: # %{"#channel" => [:operator, :voice] defmodule Storage do - def delete(id) do - op(fn(ets) -> :ets.delete(ets, id) end) + op(fn ets -> :ets.delete(ets, id) end) end def insert(tuple) do - op(fn(ets) -> :ets.insert(ets, tuple) end) + op(fn ets -> :ets.insert(ets, tuple) end) end def clear_network(network) do - op(fn(ets) -> + op(fn ets -> spec = [ {{:_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_, :_}, - [ - {:==, :"$1", {:const, network}} - ], [:"$_"]} + [ + {:==, :"$1", {:const, network}} + ], [:"$_"]} ] + :ets.match_delete(ets, spec) end) end @@ -34,7 +34,7 @@ defmodule Nola.UserTrack do end def start_link do - GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + GenServer.start_link(__MODULE__, [], name: __MODULE__) end def init([]) do @@ -43,13 +43,15 @@ defmodule Nola.UserTrack do end def handle_call({:op, fun}, _from, ets) do - returned = try do - {:ok, fun.(ets)} - rescue - rescued -> {:error, rescued} - catch - rescued -> {:error, rescued} - end + returned = + try do + {:ok, fun.(ets)} + rescue + rescued -> {:error, rescued} + catch + rescued -> {:error, rescued} + end + {:reply, returned, ets} end @@ -58,31 +60,63 @@ defmodule Nola.UserTrack do end end - defmodule Id, do: use EntropyString + defmodule Id, do: use(EntropyString) defmodule User do - defstruct [:id, :account, :network, :nick, {:nicks, []}, :username, :host, :realname, {:privileges, %{}}, {:last_active, %{}}, {:options, %{}}] + 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} + {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} + # 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) + # iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) spec = [ {{:_, :_, :"$2", :_, :_, :_, :_, :_, :_, :_, :_, :_}, [ - {:==, :"$2", {:const, id}} + {:==, :"$2", {:const, id}} ], [:"$_"]} ] - results = :ets.select(@ets, spec) - |> Enum.filter(& &1) + + results = + :ets.select(@ets, spec) + |> Enum.filter(& &1) + for obj <- results, do: User.from_tuple(obj) end @@ -91,27 +125,39 @@ defmodule Nola.UserTrack do 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) + # 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}}} + {: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 + 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 + + _ -> + nil end end @@ -119,18 +165,23 @@ defmodule Nola.UserTrack 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) + # iex(15)> :ets.fun2ms(fn(obj = {_, net, acct, _, _, _, _, _, _}) when net == network and acct == account -> obj end) spec = [ {{:_, :_, :"$1", :_, :_, :_, :_, :_, :_, :_, :_, :_}, [ - {:==, :"$1", {:const, old_id}} + {:==, :"$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}) + + 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 @@ -139,14 +190,18 @@ defmodule Nola.UserTrack 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) + case :ets.match( + @ets, + {:"$1", network, :_, String.downcase(nick), :_, :_, :_, :_, :_, :_, :_, :_} + ) do + [[id] | _] -> + lookup(id) + _ -> nil end @@ -171,7 +226,7 @@ defmodule Nola.UserTrack do end def channel(network, channel) do - Enum.filter(to_list(), fn({_, network, _, _, _, _, _, _, _, channels, _, _}) -> + Enum.filter(to_list(), fn {_, network, _, _, _, _, _, _, _, channels, _, _} -> Map.get(channels, channel) end) end @@ -179,53 +234,92 @@ defmodule Nola.UserTrack do # 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 + 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 + + Nola.Irc.Connection.publish_event(network, %{ + type: :connect, + user_id: user.id, + account_id: user.account + }) - Nola.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(c, s), do: joined(c, s, []) - def joined(channel, sender=%{nick: nick, user: uname, host: host}, privileges, touch \\ true) do - privileges = if Nola.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}} + def joined(channel, sender = %{nick: nick, user: uname, host: host}, privileges, touch \\ true) do + privileges = + if Nola.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 - 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) -> + Storage.op(fn ets -> :ets.insert(ets, User.to_tuple(user)) end) - Nola.Irc.Connection.publish_event({sender.network, channel}, %{type: :join, user_id: user.id, account_id: user.account}) + Nola.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 + # 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 @@ -235,18 +329,24 @@ defmodule Nola.UserTrack do # Storage.op(fn(ets) -> # :ets.insert(ets, User.to_tuple(user)) # end) - #end + # end + + def messaged( + %Nola.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 - def messaged(%Nola.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, %Nola.Message{m | account: account}} else @@ -257,12 +357,19 @@ defmodule Nola.UserTrack do 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]} + 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]} + 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 - Nola.Irc.Connection.publish_event(network, %{type: :nick, user_id: user.id, account_id: account.id, nick: new_nick, old_nick: old_nick}) + + Nola.Irc.Connection.publish_event(network, %{ + type: :nick, + user_id: user.id, + account_id: account.id, + nick: new_nick, + old_nick: old_nick + }) end end @@ -270,12 +377,19 @@ defmodule Nola.UserTrack 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) + 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)) - Nola.Irc.Connection.publish_event({network, channel}, %{type: :privileges, user_id: user.id, account_id: user.account, added: add, removed: remove}) + + Nola.Irc.Connection.publish_event({network, channel}, %{ + type: :privileges, + user_id: user.id, + account_id: user.account, + added: add, + removed: remove + }) end end @@ -292,12 +406,25 @@ defmodule Nola.UserTrack do 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)) - Nola.Irc.Connection.publish_event({network, channel}, %{type: :part, user_id: user.id, account_id: user.account, reason: nil}) + + Nola.Irc.Connection.publish_event({network, channel}, %{ + type: :part, + user_id: user.id, + account_id: user.account, + reason: nil + }) else - Nola.Irc.Connection.publish_event(network, %{type: :quit, user_id: user.id, account_id: user.account, reason: "Left all known channels"}) + Nola.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 @@ -309,17 +436,27 @@ defmodule Nola.UserTrack do for {channel, _} <- user.privileges do Nola.Membership.touch(user.account, sender.network, channel) end - Nola.Irc.Connection.publish_event(sender.network, %{type: :quit, user_id: user.id, account_id: user.account, reason: reason}) + + Nola.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) + + last_active = + last_active + |> Map.put(channel, now) + |> Map.put(nil, now) + %User{user | last_active: last_active} end |