This commit is contained in:
Moon Man 2024-08-24 00:17:21 +00:00
parent 97cf080d14
commit de9afad553
15 changed files with 380 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}"]
]

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
# 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").
vonbraun-*.tar
# Temporary files, for example, from tests.
/tmp/

2
.tool-versions Normal file
View File

@ -0,0 +1,2 @@
elixir 1.17.2-otp-26
erlang 26.2.5.2

3
config/config.exs Normal file
View File

@ -0,0 +1,3 @@
import Config
import_config "#{Mix.env()}.exs"

9
config/dev.exs Normal file
View File

@ -0,0 +1,9 @@
import Config
config :logger, level: :debug
config :vonbraun,
name: "Moon Man",
nickname: "moonman",
domain: "vonbraun.discontent.top",
summary: "I am the famous Fediverse Moon"

0
config/test.exs Normal file
View File

18
lib/vonbraun.ex Normal file
View File

@ -0,0 +1,18 @@
defmodule Vonbraun do
@moduledoc """
Documentation for `Vonbraun`.
"""
@doc """
Hello world.
## Examples
iex> Vonbraun.hello()
:world
"""
def hello do
:world
end
end

View File

@ -0,0 +1,19 @@
defmodule Vonbraun.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
@impl true
def start(_type, _args) do
children = [
{Bandit, scheme: :http, plug: Vonbraun.MyRouter, port: 4012}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Vonbraun.Supervisor]
Supervisor.start_link(children, opts)
end
end

View File

@ -0,0 +1,19 @@
defmodule Vonbraun.InboxRouter do
use Plug.Router
require Logger
plug :match
plug :dispatch
get "/" do
nickname = Application.fetch_env!(:vonbraun, :nickname)
if conn.params["user"] == nickname do
send_resp(conn, 200, "ok")
else
send_resp(conn, 404, "fuck off")
end
end
end

14
lib/vonbraun/my_plug.ex Normal file
View File

@ -0,0 +1,14 @@
defmodule Vonbraun.MyPlug do
import Plug.Conn
def init(options) do
# initialize options
options
end
def call(conn, _opts) do
conn
|> put_resp_content_type("text/plain")
|> send_resp(200, "Hello world")
end
end

219
lib/vonbraun/router.ex Normal file
View File

@ -0,0 +1,219 @@
defmodule Vonbraun.MyRouter do
use Plug.Router
require Logger
alias Vonbraun.InboxRouter
plug :match
plug :dispatch
defp render_outbox(nickname, domain), do: """
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://#{domain}/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"first": "https://#{domain}/users/#{nickname}/outbox?page=true",
"id": "https://#{domain}/users/#{nickname}/outbox",
"type": "OrderedCollection"
}
"""
defp render_outbox_page(nickname, domain), do: """
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://#{domain}/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"id": "https://#{domain}/users/#{nickname}/outbox?page=true",
"orderedItems": [
],
"partOf": "https://#{domain}/users/#{nickname}/outbox",
"type": "OrderedCollectionPage"
}
"""
get "/.well-known/webfinger" do
conn = Plug.Conn.fetch_query_params(conn)
nickname = Application.fetch_env!(:vonbraun, :nickname)
domain = Application.fetch_env!(:vonbraun, :domain)
with {:resource, resource} when is_binary(resource) <- {:resource, conn.params["resource"]},
{:admin_user, true} <- {:admin_user, resource in ["acct:#{nickname}", "acct:#{nickname}@#{domain}", "https://#{domain}/users/#{nickname}"]} do
out = """
{
"subject": "acct:#{nickname}@#{domain}",
"aliases": [
"https://#{domain}/users/#{nickname}"
],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://#{domain}/users/#{nickname}"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://#{domain}/users/#{nickname}"
}
]
}
"""
conn = Plug.Conn.put_resp_content_type(conn, "application/jrd+json")
send_resp(conn, 200, out)
else
{:resource, resource} ->
Logger.error("not a binary resource: #{resource}")
send_resp(conn, 404, "oops")
{:admin_user, false} ->
Logger.error("Bad user: #{conn.params["resource"]}")
send_resp(conn, 404, "oops")
end
end
get "/users/:user" do
nickname = Application.fetch_env!(:vonbraun, :nickname)
if conn.params["user"] == nickname do
domain = Application.fetch_env!(:vonbraun, :domain)
name = Application.fetch_env!(:vonbraun, :name)
summary = Application.fetch_env!(:vonbraun, :summary)
out = """
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://#{domain}/schemas/litepub-0.1.jsonld",
{
"@language": "und"
}
],
"alsoKnownAs": [],
"attachment": [],
"capabilities": {
"acceptsChatMessages": true
},
"discoverable": false,
"endpoints": {
"sharedInbox": "https://#{domain}/inbox"
},
"followers": "https://#{domain}/users/#{nickname}/followers",
"following": "https://#{domain}/users/#{nickname}/following",
"icon": {
"type": "Image",
"url": "https://#{domain}/icon.png"
},
"id": "https://#{domain}/users/#{nickname}",
"inbox": "https://#{domain}/users/#{nickname}/inbox",
"manuallyApprovesFollowers": false,
"name": "#{name}",
"outbox": "https://#{domain}/users/#{nickname}/outbox",
"preferredUsername": "#{nickname}",
"publicKey": {
"id": "https://#{domain}/users/#{nickname}#main-key",
"owner": "https://#{domain}/users/#{nickname}",
"publicKeyPem": ""
},
"summary": "#{summary}",
"tag": [],
"type": "Person",
"url": "https://#{domain}/users/#{nickname}",
"vcard:bday": null,
"webfinger": "acct:#{nickname}@#{domain}"
}
"""
conn = Plug.Conn.put_resp_content_type(conn, "application/activity+json")
send_resp(conn, 200, out)
else
send_resp(conn, 404, "not found")
end
end
get "/users/:user/following" do
nickname = Application.fetch_env!(:vonbraun, :nickname)
if conn.params["user"] == nickname do
domain = Application.fetch_env!(:vonbraun, :domain)
out = """
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://#{domain}/users/#{nickname}/following",
"type": "OrderedCollection",
"totalItems": 0,
"orderedItems": [
]
}
"""
conn = Plug.Conn.put_resp_content_type(conn, "application/activity+json")
send_resp(conn, 200, out)
else
send_resp(conn, 404, "not found")
end
end
get "/users/:user/followers" do
nickname = Application.fetch_env!(:vonbraun, :nickname)
if conn.params["user"] == nickname do
domain = Application.fetch_env!(:vonbraun, :domain)
out = """
{
"@context": "https://www.w3.org/ns/activitystreams",
"id": "https://#{domain}/users/#{nickname}/followers",
"type": "OrderedCollection",
"totalItems": 0,
"orderedItems": [
]
}
"""
conn = Plug.Conn.put_resp_content_type(conn, "application/activity+json")
send_resp(conn, 200, out)
else
send_resp(conn, 404, "not found")
end
end
get "/users/:user/outbox" do
nickname = Application.fetch_env!(:vonbraun, :nickname)
domain = Application.fetch_env!(:vonbraun, :domain)
user = conn.params["user"]
conn = Plug.Conn.fetch_query_params(conn)
with {:user, ^nickname} <- {:user, user},
{:page, "true"} <- {:page, conn.params["page"]} do
conn = Plug.Conn.put_resp_content_type(conn, "application/activity+json")
send_resp(conn, 200, render_outbox_page(nickname, domain))
else
{:user, _} ->
send_resp(conn, 404, "fuck off")
{:page, _} ->
conn = Plug.Conn.put_resp_content_type(conn, "application/activity+json")
send_resp(conn, 200, render_outbox(nickname, domain))
end
end
forward "/inbox", to: InboxRouter
forward "/users/:user/inbox", to: InboxRouter
match _ do
send_resp(conn, 404, "oops")
end
end

28
mix.exs Normal file
View File

@ -0,0 +1,28 @@
defmodule Vonbraun.MixProject do
use Mix.Project
def project do
[
app: :vonbraun,
version: "0.1.0",
elixir: "~> 1.17",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger],
mod: {Vonbraun.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:bandit, "~> 1.5.7"}
]
end
end

10
mix.lock Normal file
View File

@ -0,0 +1,10 @@
%{
"bandit": {:hex, :bandit, "1.5.7", "6856b1e1df4f2b0cb3df1377eab7891bec2da6a7fd69dc78594ad3e152363a50", [:mix], [{:hpax, "~> 1.0.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f2dd92ae87d2cbea2fa9aa1652db157b6cba6c405cb44d4f6dd87abba41371cd"},
"hpax": {:hex, :hpax, "1.0.0", "28dcf54509fe2152a3d040e4e3df5b265dcb6cb532029ecbacf4ce52caea3fd2", [:mix], [], "hexpm", "7f1314731d711e2ca5fdc7fd361296593fc2542570b3105595bb0bc6d0fad601"},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"thousand_island": {:hex, :thousand_island, "1.3.5", "6022b6338f1635b3d32406ff98d68b843ba73b3aa95cfc27154223244f3a6ca5", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2be6954916fdfe4756af3239fb6b6d75d0b8063b5df03ba76fd8a4c87849e180"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
}

1
test/test_helper.exs Normal file
View File

@ -0,0 +1 @@
ExUnit.start()

8
test/vonbraun_test.exs Normal file
View File

@ -0,0 +1,8 @@
defmodule VonbraunTest do
use ExUnit.Case
doctest Vonbraun
test "greets the world" do
assert Vonbraun.hello() == :world
end
end