Current section

Files

Jump to
bluesky_ex lib record_manager.ex
Raw

lib/record_manager.ex

defmodule BlueskyEx.Client.RecordManager.FetchBuilder do
@moduledoc """
A helper module to provide generator functions for RecordManager.
"""
alias BlueskyEx.Client.Session
alias HTTPoison.Response
@type query_opt :: {:query, (Session.t(), Keyword.t() -> any()) | nil}
@type body_opt :: {:body, (Session.t(), Keyword.t() -> String.t()) | nil}
@type macro_opts :: [query_opt | body_opt]
@spec build_action(atom(), Macro.t(), macro_opts) :: any()
defmacro build_action(func, func_opts_type \\ [], opts \\ []) do
query_fn = Keyword.get(opts, :query, nil)
body_fn = Keyword.get(opts, :body, nil)
endpoint = Keyword.get(opts, :endpoint, func)
quote do
@spec unquote(func)(Session.t(), unquote(func_opts_type)) :: Response.t()
def unquote(func)(session, options \\ []) do
query = generate_fn(unquote(query_fn), session, options)
body = generate_fn(unquote(body_fn), session, options)
fetch_data(unquote(endpoint), session, query: query, body: body)
end
end
end
defmacro build_create_and_delete_actions(
action_name,
create_params,
create_body_fn
) do
create_action_name = :"create_#{action_name}"
delete_action_name = :"delete_#{action_name}"
quote do
build_action(unquote(create_action_name), unquote(create_params),
endpoint: :create_record,
body: fn session, args ->
create_body = unquote(create_body_fn).(args)
build_create_body(session, "app.bsky.feed.#{unquote(action_name)}", create_body)
end
)
build_action(unquote(delete_action_name), [rkey: String.t()],
endpoint: :delete_record,
body: fn session, rkey: rkey ->
build_delete_body(session, "app.bsky.feed.#{unquote(action_name)}", %{rkey: rkey})
end
)
end
end
end
defmodule BlueskyEx.Client.RecordManager do
@moduledoc """
A module to namespace functions that interact with a Bluesky feed.
"""
alias BlueskyEx.Client.{RequestUtils, Session}
alias HTTPoison.Response
require BlueskyEx.Client.RecordManager.FetchBuilder
import BlueskyEx.Client.RecordManager.FetchBuilder
@type feed_query_opts :: [
limit: non_neg_integer(),
algorithm: String.t()
]
# READ
build_action(:get_account_invite_codes)
build_action(:get_profile, [], query: &build_actor_query/2)
build_action(:get_notifications, feed_query_opts, query: &build_feed_query/2)
build_action(:get_popular, feed_query_opts, query: &build_feed_query/2)
build_action(:get_timeline, feed_query_opts, query: &build_feed_query/2)
# CREATE
build_create_and_delete_actions(
:post,
[text: String.t()],
fn text: text -> %{text: text} end
)
build_create_and_delete_actions(
:like,
[uri: String.t(), cid: String.t()],
fn uri: uri, cid: cid -> %{subject: %{uri: uri, cid: cid}} end
)
build_create_and_delete_actions(
:repost,
[uri: String.t(), cid: String.t()],
fn uri: uri, cid: cid -> %{subject: %{uri: uri, cid: cid}} end
)
@typep fetch_options :: [
{:body, String.t()}
| {:query, RequestUtils.URI.query_params()}
]
@spec fetch_data(atom(), Session.t(), fetch_options) :: Response.t()
defp fetch_data(request_type, %Session{pds: pds} = session, options) do
query = options[:query]
body = options[:body]
args =
case query do
nil -> [pds]
_ -> [pds, query]
end
uri = apply(RequestUtils.URI, request_type, args)
RequestUtils.make_request(uri, body: body, session: session)
end
@spec build_create_body(Session.t(), String.t(), map()) :: String.t()
defp build_create_body(session, type, fields) do
Jason.encode!(%{
collection: type,
repo: session.did,
record:
Map.merge(
%{
"$type": type,
createdAt: timestamp_now()
},
fields
)
})
end
@spec build_delete_body(Session.t(), String.t(), %{rkey: String.t()}) :: String.t()
defp build_delete_body(session, type, rkey_field) do
%{
collection: type,
repo: session.did
}
|> Map.merge(rkey_field)
|> Jason.encode!()
end
@spec timestamp_now :: String.t()
defp timestamp_now do
DateTime.utc_now() |> DateTime.to_iso8601()
end
@spec build_feed_query(Session.t(), Keyword.t()) :: RequestUtils.URI.query_params()
defp build_feed_query(_session, opts) do
algorithm = Keyword.get(opts, :algorithm, "reverse-chronological")
limit = Keyword.get(opts, :limit, 30)
%{"limit" => limit, "algorithm" => algorithm}
end
@spec build_actor_query(Session.t(), Keyword.t()) :: RequestUtils.URI.query_params()
defp build_actor_query(session, _opts) do
%{"actor" => session.did}
end
@typep generator :: (Session.t(), Keyword.t() -> any())
@spec generate_fn(generator | nil, Session.t(), Keyword.t()) :: any()
defp generate_fn(generator, _session, _opts) when is_nil(generator), do: nil
defp generate_fn(generator, session, opts) when is_function(generator, 2),
do: generator.(session, opts)
end