sloanelybutsurely.com/lib/core/syndication.ex

139 lines
4.1 KiB
Elixir

defmodule Core.Syndication do
alias __MODULE__
## mastodon
@mastodon_instance Application.compile_env!(:sloanely_but_surely, [
Core.Syndication,
:mastodon_instance
])
def get_mastodon_account(user) do
Core.Repo.get_by(Schema.MastodonAccount, user_id: user.id)
end
def save_mastodon_account(user, attrs) do
user
|> Ecto.build_assoc(:mastodon_account)
|> Syndication.MastodonAccount.changeset(attrs)
|> Core.Repo.insert()
end
def syndicate_to_mastodon(%Schema.Post{} = post) do
conn = build_mastodon_client_conn()
{:ok, resp} = MastodonClient.post(conn, "/api/v1/statuses", %{status: post.body})
post
|> Ecto.build_assoc(:mastodon_post)
|> Syndication.MastodonPost.changeset(%{
status_id: resp.body["id"],
url: resp.body["url"]
})
|> Core.Repo.insert()
{:ok, post}
end
defp get_mastodon_access_token! do
mastodon_account = Core.Repo.one!(Schema.MastodonAccount)
mastodon_account.access_token
end
defp build_mastodon_client_conn do
%MastodonClient.Conn{
instance: @mastodon_instance,
access_token: get_mastodon_access_token!()
}
end
## bluesky
def get_bluesky_account(%Schema.User{} = user) do
Core.Repo.get_by(Schema.BlueskyAccount, user_id: user.id)
end
def save_bluesky_account(user, identifier, password) do
with {:ok, session_resp} <-
Syndication.BlueskyClient.create_session(identifier, password),
{:ok, attrs} <- parse_bluesky_token_resp(session_resp) do
user
|> Ecto.build_assoc(:bluesky_account)
|> Syndication.BlueskyAccount.create_changeset(attrs)
|> Core.Repo.insert(
conflict_target: [:user_id],
on_conflict: {:replace_all_except, [:id, :inserted_at]}
)
end
end
def refresh_bluesky_account(%Schema.BlueskyAccount{} = bluesky_account) do
if DateTime.compare(DateTime.utc_now(), bluesky_account.access_jwt_exp) == :lt do
{:ok, bluesky_account}
else
with {:ok, refresh_resp} <-
Syndication.BlueskyClient.refresh_session(bluesky_account),
{:ok, attrs} <- parse_bluesky_token_resp(refresh_resp) do
%{"bluesky_account_id" => bluesky_account.id}
|> Core.Syndication.BlueskyRefreshWorker.new(
scheduled_at: DateTime.add(bluesky_account.refresh_jwt_exp, -1, :day)
)
|> Oban.insert()
bluesky_account
|> Syndication.BlueskyAccount.refresh_changeset(attrs)
|> Core.Repo.update()
end
end
end
def syndicate_to_bluesky(%Schema.Post{} = post) do
{:ok, bluesky_account} = get_bluesky_account!() |> refresh_bluesky_account()
with {:ok, resp} <- Syndication.BlueskyClient.post_status(bluesky_account, post.body) do
post
|> Ecto.build_assoc(:bluesky_post)
|> Syndication.BlueskyPost.changeset(resp.body)
|> Core.Repo.insert()
{:ok, post}
end
end
def bsky_app_url(%Schema.BlueskyPost{uri: uri}) do
[did, id] = Regex.run(~r|at://(did:.*)/app.bsky.feed.post/(.*)|, uri, capture: :all_but_first)
"https://bsky.app/profile/#{did}/post/#{id}"
end
defp parse_bluesky_token_resp(resp) do
with {:ok, %{iat: access_jwt_iat, exp: access_jwt_exp}} <-
parse_bluesky_jwt(resp.body["accessJwt"]),
{:ok, %{iat: refresh_jwt_iat, exp: refresh_jwt_exp}} <-
parse_bluesky_jwt(resp.body["refreshJwt"]) do
{:ok,
%{
handle: resp.body["handle"],
did: resp.body["did"],
access_jwt: resp.body["accessJwt"],
access_jwt_iat: access_jwt_iat,
access_jwt_exp: access_jwt_exp,
refresh_jwt: resp.body["refreshJwt"],
refresh_jwt_iat: refresh_jwt_iat,
refresh_jwt_exp: refresh_jwt_exp
}}
end
end
defp parse_bluesky_jwt(jwt) do
with {:ok, claims} <- Joken.peek_claims(jwt),
{:ok, iat} <- DateTime.from_unix(claims["iat"]),
{:ok, exp} <- DateTime.from_unix(claims["exp"]) do
{:ok, %{iat: iat, exp: exp}}
end
end
defp get_bluesky_account! do
Core.Repo.one!(Schema.BlueskyAccount)
end
end