diff --git a/config/config.exs b/config/config.exs index 5746851..93a4ffb 100644 --- a/config/config.exs +++ b/config/config.exs @@ -14,10 +14,13 @@ config :vonbraun, Vonbraun.Cache, # GC max timeout: 10 min gc_cleanup_max_timeout: :timer.minutes(10) -config :vonbraun, - json_ld: %{ - "https://www.w3.org/ns/activitystreams" => - File.read!(Path.join(Path.dirname(__DIR__), "priv/jsonld/activitystreams.json")) - } +#config :vonbraun, +# json_ld: %{ +# "https://www.w3.org/ns/activitystreams" => +# File.read!(Path.join(Path.dirname(__DIR__), "priv/jsonld/activitystreams.json")) +# } + + config :vonbraun, + json_ld: %{} import_config "#{Mix.env()}.exs" diff --git a/lib/vonbraun/activity_pub/object.ex b/lib/vonbraun/activity_pub/object.ex index 2bbc2ff..52ab918 100644 --- a/lib/vonbraun/activity_pub/object.ex +++ b/lib/vonbraun/activity_pub/object.ex @@ -1,5 +1,6 @@ defmodule Vonbraun.ActivityPub.Object do @context "https://www.w3.org/ns/activitystreams" + @public_to "https://www.w3.org/ns/activitystreams#Public" @spec my_id() :: String.t() def my_id() do @@ -13,6 +14,24 @@ defmodule Vonbraun.ActivityPub.Object do "#{my_id()}#main-key" end + def add_context(object = %{"@context" => context}) + when is_list(context) or is_binary(context) do + cond do + is_list(context) && @context not in context -> + Map.put(object, "@context", [@context | context]) + + is_binary(context) && @context != context -> + Map.put(object, "@context", [@context, context]) + + true -> + object + end + end + + def add_context(object = %{}) do + Map.put(object, "@context", @context) + end + def activity(type, id, object, options \\ []) when is_binary(type) and is_binary(id) and (is_binary(object) or is_map(object)) do to = @@ -51,6 +70,37 @@ defmodule Vonbraun.ActivityPub.Object do } end + def copy_recipients(target = %{}, source = %{}) do + ["to", "cc", "bcc", "bto"] + |> Enum.reduce(target, fn key, target -> + recipients = Map.get(source, key, []) + Map.put(target, key, recipients) + end) + end + + def listify(string) when is_binary(string) do + [string] + end + + def listify(list) when is_list(list) do + list + end + + def mark_public(object = %{}) do + to = Map.get(object, "to", []) + cc = Map.get(object, "cc", []) + + to_public? = to == @public_to or (is_list(to) && @public_to in to) + cc_public? = cc == @public_to or (is_list(cc) && @public_to in cc) + + if to_public? || cc_public? do + object + else + to = [@public_to | listify(to)] + Map.put(object, "to", to) + end + end + @spec create_actor_activity_id(String.t(), String.t()) :: String.t() def create_actor_activity_id(actor_id, verb) when is_binary(actor_id) and is_binary(verb) do "https://#{Application.fetch_env!(:vonbraun, :domain)}/id/#{URI.encode(verb)}:#{URI.encode(actor_id)}" diff --git a/lib/vonbraun/control.ex b/lib/vonbraun/control.ex index dca4087..457a32f 100644 --- a/lib/vonbraun/control.ex +++ b/lib/vonbraun/control.ex @@ -3,6 +3,7 @@ defmodule Vonbraun.Control do alias Vonbraun.ActivityPub.Object alias Vonbraun.ActivityPubReq alias Vonbraun.Ecto.Schema.Actor + alias Vonbraun.CryptoID @spec follow(String.t()) :: :ok @@ -104,6 +105,38 @@ defmodule Vonbraun.Control do end def post_note(content, public?, to \\ []) - when is_binary(content) and is_list(to) and is_boolean(public?) do + when is_binary(content) and (is_list(to) or is_binary(to)) and is_boolean(public?) do + to = Object.listify(to) + {activity_id, now_ms} = CryptoID.now!() + object_id = CryptoID.encrypt_id(now_ms) + + object = + %{ + "@id" => object_id, + "@type" => "Note", + "to" => to, + "content" => content + } + |> Object.add_context() + + object = + if public? do + Object.mark_public(object) + else + object + end + + activity = Object.activity("Create", activity_id, object, to: to) + Logger.debug("Here is the raw activity:\n#{Jason.encode!(activity)}") + + expanded_activity = + JSON.LD.expand(activity, + compact_arrays: false, + document_loader: Vonbraun.JSONLD.DocumentLoaderAgent + ) + + Logger.debug(("Here is the expanded activity:\n#{Jason.encode!(expanded_activity)}")) + + activity end end diff --git a/lib/vonbraun/json_ld/document_loader_agent.ex b/lib/vonbraun/json_ld/document_loader_agent.ex index 2006f12..8652b25 100644 --- a/lib/vonbraun/json_ld/document_loader_agent.ex +++ b/lib/vonbraun/json_ld/document_loader_agent.ex @@ -2,6 +2,7 @@ defmodule Vonbraun.JSONLD.DocumentLoaderAgent do alias JSON.LD.DocumentLoader.RemoteDocument alias Req.Response use Agent + require Logger @behaviour JSON.LD.DocumentLoader @@ -10,7 +11,7 @@ defmodule Vonbraun.JSONLD.DocumentLoaderAgent do end defp preload() do - Application.get_env(:vonbraun, :jsonld) + Application.get_env(:vonbraun, :json_ld) |> Enum.reduce(Map.new(), fn {url, data}, map -> Map.put(map, url, {:ok, Jason.decode!(data)}) end) @@ -37,11 +38,11 @@ defmodule Vonbraun.JSONLD.DocumentLoaderAgent do {:ok, %Response{ :status => status, - :body => %{} = body, + :body => body, :headers => %{"content-type" => ["application/ld+json"]} }} when status >= 200 and status <= 299 -> - {:ok, body} + Jason.decode(body) {:ok, %Response{:headers => %{"link" => [content]}}} when is_binary(content) -> with {link_url, props} <- parse_link_header(content), @@ -72,18 +73,24 @@ defmodule Vonbraun.JSONLD.DocumentLoaderAgent do @spec load(String.t(), any()) :: {:ok, RemoteDocument.t()} | {:error, any()} def load(url, _options) when is_binary(url) do + Logger.debug("Attempting to load JSON-LD context: #{url}") + with {:cache, nil} <- {:cache, Agent.get(__MODULE__, fn state -> Map.get(state, url) end)}, + :ok <- Logger.debug("No cached JSON-LD context, so querying"), {:get, {:ok, body}} <- {:get, http_get(url)}, {:data, data} <- {:data, %RemoteDocument{document: body, document_url: url}}, {:update, :ok} <- - {:update, Agent.update(__MODULE__, fn state -> Map.put(state, url, data) end)} do - {:ok, body} + {:update, Agent.update(__MODULE__, fn state -> Map.put(state, url, {:ok, data}) end)} do + Logger.debug("Got remote one, so returning it.") + {:ok, data} else {:cache, response} -> + Logger.debug("Got cached version: #{inspect(response)}") response {:get, error = {:error, _}} -> + Logger.error("There was an error: #{inspect(error)}") Agent.update(__MODULE__, fn state -> Map.put(state, url, error) end) error end diff --git a/test/scratch/me_activity_1.json b/test/scratch/me_activity_1.json new file mode 100644 index 0000000..049e180 --- /dev/null +++ b/test/scratch/me_activity_1.json @@ -0,0 +1,14 @@ +{ + "ledger": "vonbraun-test", + "insert": { + "@context": "https://www.w3.org/ns/activitystreams", + "@type": "Create", + "actor": "https://vonbraun.example.net/users/moonman", + "object": { + "@id": "https://vonbraun.example.net/id/1", + "@type": "Note", + "content": "This is a simple note." + }, + "published": "2024-01-25T12:34:56Z" + } +} \ No newline at end of file