require "http/client" require "json" require "uri" module BlueskyApi extend self APPVIEW_HOST = "public.api.bsky.app" struct Profile include JSON::Serializable property did : String property handle : String @[JSON::Field(key: "displayName")] property display_name : String? property avatar : String? end struct FollowsResponse include JSON::Serializable property follows : Array(Profile) property cursor : String? end struct SearchResponse include JSON::Serializable property actors : Array(Profile) end def get_profile(did_or_handle : String) : Profile? params = HTTP::Params.encode({"actor" => did_or_handle}) uri = URI.parse("https://#{APPVIEW_HOST}/xrpc/app.bsky.actor.getProfile?#{params}") client = HTTP::Client.new(uri) client.connect_timeout = 10.seconds client.read_timeout = 30.seconds response = client.get(uri.request_target) client.close return nil unless response.status.success? Profile.from_json(response.body) rescue nil end def get_follows(did : String, cursor : String? = nil) : FollowsResponse? params = {"actor" => did, "limit" => "100"} params["cursor"] = cursor if cursor encoded = HTTP::Params.encode(params) uri = URI.parse("https://#{APPVIEW_HOST}/xrpc/app.bsky.graph.getFollows?#{encoded}") client = HTTP::Client.new(uri) client.connect_timeout = 10.seconds client.read_timeout = 30.seconds response = client.get(uri.request_target) client.close return nil unless response.status.success? FollowsResponse.from_json(response.body) rescue nil end def search_actors(query : String) : Array(Profile) params = HTTP::Params.encode({"q" => query, "limit" => "25"}) uri = URI.parse("https://#{APPVIEW_HOST}/xrpc/app.bsky.actor.searchActors?#{params}") client = HTTP::Client.new(uri) client.connect_timeout = 10.seconds client.read_timeout = 30.seconds response = client.get(uri.request_target) client.close return [] of Profile unless response.status.success? SearchResponse.from_json(response.body).actors rescue [] of Profile end end