postland/lib/postland/activities.ex
2024-10-23 19:10:48 -05:00

181 lines
4.4 KiB
Elixir

defmodule Postland.Activities do
use Phoenix.VerifiedRoutes, endpoint: PostlandWeb.Endpoint, router: PostlandWeb.Router
require Logger
alias Ecto.Multi
alias Postland.Activity
alias Postland.Repo
alias Postland.Follows
def record_markdown_post(markdown) do
id = Ecto.UUID.autogenerate()
html = Earmark.as_html!(markdown)
activity =
%{
"@context" => [
"https://www.w3.org/ns/activitystreams",
%{
"ostatus" => "http://ostatus.org#",
"atomUri" => "ostatus:atomUri",
"inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
"conversation" => "ostatus:conversation",
"sensitive" => "as:sensitive",
"toot" => "http://joinmastodon.org/ns#",
"votersCount" => "toot:votersCount"
}
],
"id" => url(~p"/activities/#{id}"),
"type" => "Create",
"actor" => Postland.my_actor_id(),
"object" => %{
"id" => url(~p"/activities/#{id}"),
"type" => "Note",
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"attributedTo" => Postland.my_actor_id(),
"mediaType" => "text/html",
"content" => html,
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [
"#{Postland.my_actor_id()}/followers"
]
}
}
activity
|> Postland.Activity.changeset()
|> record_create_activity()
|> broadcast_to_followers(activity)
end
def broadcast_to_followers({:ok, _} = result, activity) do
my_actor_id = Postland.my_actor_id()
private_key = Postland.Accounts.solo_user().private_key
Postland.Follows.all_followers()
|> Enum.map(fn %{follower: follower} ->
inbox = Postland.Actors.inbox(follower)
dbg(follower)
dbg(inbox)
case inbox do
nil ->
:ok
inbox ->
ActivityPub.post_activity(
my_actor_id,
private_key,
inbox,
activity
)
end
end)
result
end
def broadcast_to_followers(result, _activity), do: result
def record_create_activity(changeset) do
Multi.new()
|> Multi.insert(:activity, changeset)
|> record_objects(changeset)
|> Repo.transaction()
end
def record_objects(multi, activity_changeset) do
activity_changeset
|> Ecto.Changeset.get_field(:data)
|> Map.get("object")
|> List.wrap()
|> Enum.group_by(fn object ->
{Map.get(object, "id"), Map.get(object, "type")}
end)
|> Enum.reduce(multi, fn {{id, type}, note_as_list_of_types}, multi ->
Multi.insert(
multi,
"#{id}.#{type}",
Postland.Object.changeset(%{
"id" => id,
"type" => type,
"items" => note_as_list_of_types
})
)
end)
end
def process_activity(params) do
case record_activity(params) do
{:ok, activity} ->
cause_effects(activity)
other ->
other
end
end
def record_activity(params) do
params
|> Activity.changeset()
|> Repo.insert()
end
# TODO: add effects for CRUD notes
def cause_effects(
%Activity{type: "Accept", data: %{"object" => %{"type" => "Follow", "id" => follow_id}}} =
activity
) do
pattern = ~r|/follows/([^/]+)|
actor_id =
case Regex.run(pattern, follow_id) do
[_, encoded_actor_id] ->
Base.url_decode64!(encoded_actor_id, padding: false)
_other ->
nil
end
case Follows.get(Postland.my_actor_id(), actor_id) do
nil ->
Logger.warning("Got accept for a follow we don't have in the db: #{actor_id}")
{:ok, activity}
request ->
Follows.confirm_request(request)
end
{:ok, activity}
end
def cause_effects(%Activity{type: "Create"} = activity) do
changeset = Activity.changeset(activity.data)
Multi.new()
|> record_objects(changeset)
|> Repo.transaction()
end
def cause_effects(
%Activity{actor_id: actor_id, type: "Follow", data: %{"object" => object}} = activity
) do
if object == Postland.my_actor_id() do
case Postland.Follows.record_inbound_request(actor_id) do
{:ok, _follow} ->
{:ok, activity}
other ->
other
end
else
{:ok, activity}
end
end
def cause_effects(activity), do: {:ok, activity}
end