This commit is contained in:
LordMZTE 2023-02-07 20:09:46 +01:00
commit 68abaaded2
Signed by: LordMZTE
GPG Key ID: B64802DC33A64FF6
9 changed files with 232 additions and 0 deletions

4
.formatter.exs Normal file
View File

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
gh2ntfy-*.tar
# Temporary files, for example, from tests.
/tmp/
# Silly lockfiles
mix.lock

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Gh2ntfy
A service to forward github notifications to ntfy
## Setup
- Build a release by running `MIX_ENV=prod mix release`
- Check `runtime.exs` for configuration
- Start as usual

3
config/runtime.exs Normal file
View File

@ -0,0 +1,3 @@
import Config
config :gh2ntfy, topic: System.get_env("GH2NTFY_TOPIC")
config :gh2ntfy, token: System.get_env("GH2NTFY_GITHUB_TOKEN")

View File

@ -0,0 +1,16 @@
defmodule Gh2ntfy.Application do
use Application
def start(_type, _args) do
children = [
{Finch, name: Gh2ntfy.Finch},
{Gh2ntfy.Ntfyer, Application.fetch_env!(:gh2ntfy, :topic)},
{Gh2ntfy.GhPoll, Application.fetch_env!(:gh2ntfy, :token)},
Gh2ntfy.LastPoll
]
opts = [strategy: :one_for_one, name: Gh2ntfy.Supervisor]
{:ok, _} = Supervisor.start_link(children, opts)
end
end

87
lib/gh2ntfy/gh_poll.ex Normal file
View File

@ -0,0 +1,87 @@
defmodule Gh2ntfy.GhPoll do
alias Gh2ntfy.LastPoll
require Logger
use GenServer
def start_link(token) when is_binary(token) do
GenServer.start_link(__MODULE__, token, name: __MODULE__)
end
@impl true
def init(token) do
Process.send(self(), :poll, [])
{:ok, token}
end
@impl true
def handle_info(:poll, token) do
schedule()
Logger.info("polling notifications")
result =
Finch.build(
:get,
"https://api.github.com/notifications?since=#{LastPoll.get_as_iso8601()}",
[
{"Authorization", "Bearer " <> token},
{"Time-Zone", "Etc/UTC"}
]
)
|> Finch.request(Gh2ntfy.Finch)
case result do
{:ok, %{body: body}} ->
case Poison.decode(body) do
{:ok, res} ->
Gh2ntfy.LastPoll.set_now()
process_response(res)
{:error, e} ->
Logger.error("invalid json from github: " <> Exception.format(:error, e))
end
{:error, e} ->
Logger.error("requesting info: " <> Exception.format(:error, e))
end
{:noreply, token}
end
defp process_response(res) do
Enum.each(res, fn notif ->
case notif do
%{
"repository" => %{"full_name" => title},
"subject" => %{"title" => body, "latest_comment_url" => cmt_url},
"reason" => reason
} ->
url_headers =
try do
[{"Click", get_issuecomment_url(cmt_url)}]
rescue
e ->
Logger.warn("fetching issuecomment URL: " <> Exception.format(:error, e))
[]
end
Gh2ntfy.Ntfyer.send_notif("GitHub: " <> title, "#{reason}\n#{body}", url_headers)
_ ->
Logger.warn("invalid data from github")
nil
end
end)
end
defp schedule do
Process.send_after(self(), :poll, :timer.minutes(30))
end
defp get_issuecomment_url(api_url) do
{:ok, %{body: body}} = Finch.build(:get, api_url) |> Finch.request(Gh2ntfy.Finch)
%{"html_url" => url} = Poison.decode!(body)
url
end
end

22
lib/gh2ntfy/last_poll.ex Normal file
View File

@ -0,0 +1,22 @@
defmodule Gh2ntfy.LastPoll do
use Agent
def child_spec([]) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, []}
}
end
def start_link do
Agent.start_link(fn -> ~U[2000-01-01 00:00:00Z] end, name: __MODULE__)
end
def get_as_iso8601 do
Agent.get(__MODULE__, &DateTime.to_iso8601/1)
end
def set_now do
Agent.update(__MODULE__, fn _ -> DateTime.now!("Etc/UTC") end)
end
end

33
lib/gh2ntfy/ntfyer.ex Normal file
View File

@ -0,0 +1,33 @@
defmodule Gh2ntfy.Ntfyer do
require Logger
use GenServer
def start_link(topic) when is_binary(topic) do
GenServer.start_link(__MODULE__, topic, name: __MODULE__)
end
def send_notif(title, body, headers \\ []) do
GenServer.cast(__MODULE__, {:send_notif, title, body, headers})
end
@impl true
def init(topic), do: {:ok, topic}
@impl true
def handle_cast({:send_notif, title, body, headers}, topic) do
Logger.info("sending notification with title '#{title}'")
# TODO: allow other ntfy instances
Finch.build(
:post,
"https://ntfy.sh/" <> topic,
[
{"Title", title}
] ++ headers,
body
)
|> Finch.request(Gh2ntfy.Finch)
{:noreply, topic}
end
end

29
mix.exs Normal file
View File

@ -0,0 +1,29 @@
defmodule Gh2ntfy.MixProject do
use Mix.Project
def project do
[
app: :gh2ntfy,
version: "0.1.0",
elixir: "~> 1.14",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Gh2ntfy.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:finch, "~> 0.14.0"},
{:poison, "~> 5.0"}
]
end
end