Music streaming on ATProto!
1# AUTOGENERATED: This file was generated using the mix task `lexgen`. 2defmodule Atproto do 3 @default_pds_hostname Application.compile_env( 4 :atproto, 5 :default_pds_hostname, 6 "https://bsky.social" 7 ) 8 9 @typedoc """ 10 A type representing the names of the options that can be passed to `query/3` and `procedure/3`. 11 """ 12 @type xrpc_opt :: :pds_hostname | :authorization 13 14 @typedoc """ 15 A keyword list of options that can be passed to `query/3` and `procedure/3`. 16 """ 17 @type xrpc_opts :: [{xrpc_opt(), any()}] 18 19 @doc """ 20 Converts a JSON string, or decoded JSON map, into a struct based on the given module. 21 22 This function uses `String.to_existing_atom/1` to convert the keys of the map to atoms, meaning this will throw an error if the input JSON contains keys which are not already defined as atoms in the existing structs or codebase. 23 """ 24 @spec decode_to_struct(module(), binary() | map()) :: map() 25 def decode_to_struct(module, json) when is_binary(json) do 26 decode_to_struct(module, Jason.decode!(json, keys: :atoms!)) 27 end 28 29 def decode_to_struct(module, map) when is_map(map) do 30 Map.merge(module.new(), map) 31 end 32 33 @doc """ 34 Raises an error if any required parameters are missing from the given map. 35 """ 36 @spec ensure_required(map(), [String.t()]) :: map() 37 def ensure_required(params, required) do 38 if Enum.all?(required, fn key -> Map.has_key?(params, key) end) do 39 params 40 else 41 raise ArgumentError, "Missing one or more required parameters: #{Enum.join(required, ", ")}" 42 end 43 end 44 45 @doc """ 46 Executes a "GET" HTTP request and returns the response body as a map. 47 48 If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. 49 """ 50 @spec query(map(), String.t(), xrpc_opts()) :: Req.Request.t() 51 def query(params, target, opts \\ []) do 52 target 53 |> endpoint(opts) 54 |> URI.new!() 55 |> URI.append_query(URI.encode_query(params)) 56 |> Req.get(build_req_auth(opts)) 57 |> handle_response(opts) 58 end 59 60 @doc """ 61 Executes a "POST" HTTP request and returns the response body as a map. 62 63 If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. 64 """ 65 @spec procedure(map(), String.t(), xrpc_opts()) :: {:ok | :refresh | :error, map()} 66 def procedure(params, target, opts \\ []) do 67 req_opts = 68 opts 69 |> build_req_auth() 70 |> build_req_headers(opts, target) 71 |> build_req_body(params, target) 72 73 target 74 |> endpoint(opts) 75 |> URI.new!() 76 |> Req.post(req_opts) 77 |> handle_response(opts) 78 end 79 80 defp build_req_auth(opts) do 81 case Keyword.get(opts, :access_token) do 82 nil -> 83 case Keyword.get(opts, :admin_token) do 84 nil -> 85 [] 86 87 token -> 88 [auth: {:basic, "admin:#{token}"}] 89 end 90 91 token -> 92 [auth: {:bearer, token}] 93 end 94 end 95 96 defp build_req_headers(req_opts, opts, "com.atproto.repo.uploadBlob") do 97 [ 98 {:headers, 99 [ 100 {"Content-Type", Keyword.fetch!(opts, :content_type)}, 101 {"Content-Length", Keyword.fetch!(opts, :content_length)} 102 ]} 103 | req_opts 104 ] 105 end 106 107 defp build_req_headers(req_opts, _opts, _target), do: req_opts 108 109 defp build_req_body(opts, blob, "com.atproto.repo.uploadBlob") do 110 [{:body, blob} | opts] 111 end 112 113 defp build_req_body(opts, %{} = params, _target) when map_size(params) > 0 do 114 [{:json, params} | opts] 115 end 116 117 defp build_req_body(opts, _params, _target), do: opts 118 119 defp endpoint(target, opts) do 120 (Keyword.get(opts, :pds_hostname) || @default_pds_hostname) <> "/xrpc/" <> target 121 end 122 123 defp handle_response({:ok, %Req.Response{} = response}, opts) do 124 case response.status do 125 x when x in 200..299 -> 126 {:ok, response.body} 127 128 _ -> 129 if response.body["error"] == "ExpiredToken" do 130 {:ok, user} = 131 Com.Atproto.Server.RefreshSession.main(%{}, 132 access_token: Keyword.get(opts, :refresh_token) 133 ) 134 135 {:refresh, user} 136 else 137 {:error, response.body} 138 end 139 end 140 end 141 142 defp handle_response(error, _opts), do: error 143 144 @doc """ 145 Converts a "map-like" entity into a standard map. This will also omit any entries that have a `nil` value. 146 147 This is useful for converting structs or schemas into regular maps before sending them over XRPC requests. 148 149 You may optionally pass in an keyword list of options: 150 151 - `:stringify` - `boolean` - If `true`, converts the keys to strings. Otherwise, converts keys to atoms. Default is `false`. 152 - *Note*: When `false`, this feature uses the `to_existing_atom/1` function to avoid reckless conversion of string keys. 153 """ 154 @spec to_map(map() | struct()) :: map() 155 def to_map(%{__struct__: _} = m, opts \\ []) do 156 string_keys = Keyword.get(opts, :stringify, false) 157 158 m 159 |> Map.drop([:__struct__, :__meta__]) 160 |> Enum.map(fn 161 {_, nil} -> 162 nil 163 164 {k, v} when is_atom(k) -> 165 if string_keys, do: {to_string(k), v}, else: {k, v} 166 167 {k, v} when is_binary(k) -> 168 if string_keys, do: {k, v}, else: {String.to_existing_atom(k), v} 169 end) 170 |> Enum.reject(&is_nil/1) 171 |> Enum.into(%{}) 172 end 173end