diff options
author | Jordan Bracco <href@random.sh> | 2022-12-20 00:21:54 +0000 |
---|---|---|
committer | Jordan Bracco <href@random.sh> | 2022-12-20 19:29:41 +0100 |
commit | 2d83df8b32bff7f0028923bb5b64dc0b55f20d03 (patch) | |
tree | 1207e67b5b15f540963db05e7be89f3ca950e724 /lib/nola | |
parent | Nola rename, the end. pt 6. Refs T77. (diff) |
Nola rename: The Big Move, Refs T77
Diffstat (limited to 'lib/nola')
-rw-r--r-- | lib/nola/application.ex | 56 | ||||
-rw-r--r-- | lib/nola/auth_token.ex | 59 | ||||
-rw-r--r-- | lib/nola/icecast.ex | 117 | ||||
-rw-r--r-- | lib/nola/icecast_agent.ex | 17 | ||||
-rw-r--r-- | lib/nola/nola.ex | 30 | ||||
-rw-r--r-- | lib/nola/subnet.ex | 84 | ||||
-rw-r--r-- | lib/nola/token.ex | 38 |
7 files changed, 401 insertions, 0 deletions
diff --git a/lib/nola/application.ex b/lib/nola/application.ex new file mode 100644 index 0000000..4f3d1da --- /dev/null +++ b/lib/nola/application.ex @@ -0,0 +1,56 @@ +defmodule Nola.Application do + use Application + + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + def start(_type, _args) do + import Supervisor.Spec + + Logger.add_backend(Sentry.LoggerBackend) + :ok = Nola.Matrix.setup() + :ok = Nola.TelegramRoom.setup() + + # Define workers and child supervisors to be supervised + children = [ + # Start the endpoint when the application starts + supervisor(NolaWeb.Endpoint, []), + # Start your own worker by calling: Nola.Worker.start_link(arg1, arg2, arg3) + # worker(Nola.Worker, [arg1, arg2, arg3]), + worker(Registry, [[keys: :duplicate, name: Nola.BroadcastRegistry]], id: :registry_broadcast), + worker(Nola.IcecastAgent, []), + worker(Nola.Token, []), + worker(Nola.AuthToken, []), + Nola.Subnet, + {GenMagic.Pool, [name: Nola.GenMagic, pool_size: 2]}, + #worker(Nola.Icecast, []), + ] ++ Nola.IRC.application_childs + ++ Nola.Matrix.application_childs + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Nola.Supervisor] + sup = Supervisor.start_link(children, opts) + start_telegram() + spawn_link(fn() -> Nola.IRC.after_start() end) + spawn_link(fn() -> Nola.Matrix.after_start() end) + spawn_link(fn() -> Nola.TelegramRoom.after_start() end) + sup + end + + # Tell Phoenix to update the endpoint configuration + # whenever the application is updated. + def config_change(changed, _new, removed) do + NolaWeb.Endpoint.config_change(changed, removed) + :ok + end + + defp start_telegram() do + token = Keyword.get(Application.get_env(:nola, :telegram, []), :key) + options = [ + username: Keyword.get(Application.get_env(:nola, :telegram, []), :nick, "beauttebot"), + purge: false + ] + telegram = Telegram.Bot.ChatBot.Supervisor.start_link({Nola.Telegram, token, options}) + end + +end diff --git a/lib/nola/auth_token.ex b/lib/nola/auth_token.ex new file mode 100644 index 0000000..d125ea4 --- /dev/null +++ b/lib/nola/auth_token.ex @@ -0,0 +1,59 @@ +defmodule Nola.AuthToken do + use GenServer + + def start_link() do + GenServer.start_link(__MODULE__, [], [name: __MODULE__]) + end + + def lookup(id) do + GenServer.call(__MODULE__, {:lookup, id}) + end + + def new_path(account, perks \\ nil) do + case new(account, perks) do + {:ok, id} -> + NolaWeb.Router.Helpers.login_path(NolaWeb.Endpoint, :token, id) + error -> + error + end + end + + def new_url(account, perks \\ nil) do + case new(account, perks) do + {:ok, id} -> + NolaWeb.Router.Helpers.login_url(NolaWeb.Endpoint, :token, id) + error -> + error + end + end + + def new(account, perks \\ nil) do + GenServer.call(__MODULE__, {:new, account, perks}) + end + + def init(_) do + {: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 + {:reply, {:ok, account, perks}, Map.delete(state, id)} + else + x -> + IO.inspect(x) + {:reply, {:error, :invalid_token}, state} + end + end + + def handle_call({:new, account, perks}, _, state) do + id = IRC.UserTrack.Id.token() + 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 new file mode 100644 index 0000000..5a53192 --- /dev/null +++ b/lib/nola/icecast.ex @@ -0,0 +1,117 @@ +defmodule Nola.Icecast do + use GenServer + require Logger + @hackney_pool :default + @httpoison_opts [hackney: [pool: @hackney_pool]] + @fuse __MODULE__ + + def start_link, do: GenServer.start_link(__MODULE__, [], []) + + def init(_) do + GenServer.cast(self(), :poll) + {:ok, nil} + end + + def handle_cast(:poll, state) do + state = poll(state) + {:noreply, state} + end + + def handle_info(:poll, state) do + state = poll(state) + {:noreply, state} + 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 + end + error -> + Logger.error "Icecast HTTP Error: #{inspect error}" + state + 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} + end) + |> Enum.into(Map.new) + {mount, stats} + end) + |> 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 + + 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}" + end + + defp update_json_stats(error) do + 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 + :ok -> run_request(method, uri, body, headers, options) + :blown -> :blown + end + end + + # 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, 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} + + # + # -- URIs + # + + defp stats_json_url do + base_url() <> "/status-json.xsl" + end + + 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 new file mode 100644 index 0000000..8a3a72b --- /dev/null +++ b/lib/nola/icecast_agent.ex @@ -0,0 +1,17 @@ +defmodule Nola.IcecastAgent do + use Agent + + def start_link() do + Agent.start_link(fn -> nil end, name: __MODULE__) + end + + def update(stats) do + Agent.update(__MODULE__, fn(_old) -> stats end) + end + + def get do + Agent.get(__MODULE__, fn(stats) -> stats end) + end + +end + diff --git a/lib/nola/nola.ex b/lib/nola/nola.ex new file mode 100644 index 0000000..0acb76e --- /dev/null +++ b/lib/nola/nola.ex @@ -0,0 +1,30 @@ +defmodule Nola do + + @default_brand [ + name: "Nola, + source_url: "https://phab.random.sh/source/Bot/", + owner: "Ashamed owner", + owner_email: "contact@my.nola.bot" + ] + + def env(), do: Application.get_env(:nola) + def env(key, default \\ nil), do: Application.get_env(:nola, key, default) + + def brand(), do: env(:brand, @default_brand) + def brand(key), do: Keyword.get(brand(), key) + def name(), do: brand(:name) + def source_url(), do: brand(:source_url) + + def data_path(suffix) do + Path.join(data_path(), suffix) + end + + def data_path do + Application.get_env(:nola, :data_path) + end + + def version do + Application.spec(:nola)[:vsn] + end + +end diff --git a/lib/nola/subnet.ex b/lib/nola/subnet.ex new file mode 100644 index 0000000..ac9d8e6 --- /dev/null +++ b/lib/nola/subnet.ex @@ -0,0 +1,84 @@ +defmodule Nola.Subnet do + use Agent + + def start_link(_) do + Agent.start_link(&setup/0, name: __MODULE__) + end + + def assignations() do + :dets.select(dets(), [{{:"$1", :"$2"}, [is_binary: :"$2"], [{{:"$1", :"$2"}}]}]) + end + + def find_subnet_for(binary) when is_binary(binary) do + case :dets.select(dets(), [{{:"$1", :"$2"}, [{:==, :"$2", binary}], [{{:"$1", :"$2"}}]}]) do + [{subnet, _}] -> subnet + _ -> nil + end + 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 + + case result do + {:new, subnet} -> + ip = Pfx.host(subnet, 1) + set_reverse(binary, ip) + subnet + {:ok, subnet} -> + subnet + end + end + + def set_reverse(name, ip, value \\ nil) + + def set_reverse(name, ip, nil) do + set_reverse(name, ip, "#{name}.users.goulag.org") + end + + def set_reverse(_, ip, value) do + ptr_zone = "3.0.0.2.d.f.0.a.2.ip6.arpa" + 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)) + 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)) + :ok + end + + @doc false + def dets() do + (Nola.data_path() <> "/subnets.dets") |> String.to_charlist() + end + + @doc false + def setup() do + {:ok, dets} = :dets.open_file(dets(), []) + dets + end + + 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 + +end diff --git a/lib/nola/token.ex b/lib/nola/token.ex new file mode 100644 index 0000000..563ac72 --- /dev/null +++ b/lib/nola/token.ex @@ -0,0 +1,38 @@ +defmodule Nola.Token do + use GenServer + + def start_link() do + 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 + {:ok, cred} + else + err -> {:error, err} + end + end + + def new(cred) do + GenServer.call(__MODULE__, {:new, cred}) + end + + def init(_) do + ets = :ets.new(__MODULE__.ETS, [:ordered_set, :named_table, :protected, {:read_concurrency, true}]) + {:ok, ets} + end + + def handle_call({:new, cred}, _, ets) do + id = IRC.UserTrack.Id.large_id() + expire = DateTime.utc_now() + |> DateTime.add(15*60, :second) + obj = {id, cred, expire} + :ets.insert(ets, obj) + {:reply, {:ok, id}, ets} + end + +end |