Toot toooooooot (Bluesky-Mastodon cross-poster)
1require 'json' 2require 'net/http' 3require 'uri' 4 5class MastodonAPI 6 class UnauthenticatedError < StandardError 7 end 8 9 class UnexpectedResponseError < StandardError 10 end 11 12 class APIError < StandardError 13 attr_reader :response 14 15 def initialize(response) 16 @response = response 17 super("APIError #{response.code}: #{response.body}") 18 end 19 20 def status 21 response.code.to_i 22 end 23 end 24 25 attr_accessor :access_token 26 27 def initialize(host, access_token = nil) 28 @host = host 29 @root = "https://#{@host}/api/v1" 30 @access_token = access_token 31 end 32 33 def oauth_login_with_password(client_id, client_secret, email, password, scopes) 34 url = URI("https://#{@host}/oauth/token") 35 36 params = { 37 client_id: client_id, 38 client_secret: client_secret, 39 grant_type: 'password', 40 scope: scopes, 41 username: email, 42 password: password 43 } 44 45 response = Net::HTTP.post_form(url, params) 46 status = response.code.to_i 47 48 if status / 100 == 2 49 JSON.parse(response.body) 50 else 51 raise APIError.new(response) 52 end 53 end 54 55 def account_info 56 raise UnauthenticatedError.new unless @access_token 57 get_json("/accounts/verify_credentials") 58 end 59 60 def lookup_account(username) 61 json = get_json("/accounts/lookup", { acct: username }) 62 raise UnexpectedResponseError.new unless json.is_a?(Hash) && json['id'].is_a?(String) 63 json 64 end 65 66 def account_statuses(user_id, params = {}) 67 get_json("/accounts/#{user_id}/statuses", params) 68 end 69 70 def post_status(text) 71 post_json("/statuses", { 72 status: text 73 }) 74 end 75 76 def get_json(path, params = {}) 77 url = URI(path.start_with?('https://') ? path : @root + path) 78 url.query = URI.encode_www_form(params) if params 79 80 headers = {} 81 headers['Authorization'] = "Bearer #{@access_token}" if @access_token 82 83 response = Net::HTTP.get_response(url, headers) 84 status = response.code.to_i 85 86 if status / 100 == 2 87 JSON.parse(response.body) 88 elsif status / 100 == 3 89 get_json(response['Location']) 90 else 91 raise APIError.new(response) 92 end 93 end 94 95 def post_json(path, params = {}) 96 url = URI(path.start_with?('https://') ? path : @root + path) 97 98 headers = {} 99 headers['Authorization'] = "Bearer #{@access_token}" if @access_token 100 101 request = Net::HTTP::Post.new(url, headers) 102 request.form_data = params 103 104 response = Net::HTTP.start(url.hostname, url.port, :use_ssl => true) do |http| 105 http.request(request) 106 end 107 108 status = response.code.to_i 109 110 if status / 100 == 2 111 JSON.parse(response.body) 112 else 113 raise APIError.new(response) 114 end 115 end 116end