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 params = {
35 client_id: client_id,
36 client_secret: client_secret,
37 grant_type: 'password',
38 scope: scopes,
39 username: email,
40 password: password
41 }
42
43 post_json("https://#{@host}/oauth/token", params)
44 end
45
46 def account_info
47 raise UnauthenticatedError.new unless @access_token
48 get_json("/accounts/verify_credentials")
49 end
50
51 def lookup_account(username)
52 json = get_json("/accounts/lookup", { acct: username })
53 raise UnexpectedResponseError.new unless json.is_a?(Hash) && json['id'].is_a?(String)
54 json
55 end
56
57 def account_statuses(user_id, params = {})
58 get_json("/accounts/#{user_id}/statuses", params)
59 end
60
61 def post_status(text)
62 post_json("/statuses", {
63 status: text
64 })
65 end
66
67 def get_json(path, params = {})
68 url = URI(path.start_with?('https://') ? path : @root + path)
69 url.query = URI.encode_www_form(params) if params
70
71 headers = {}
72 headers['Authorization'] = "Bearer #{@access_token}" if @access_token
73
74 response = Net::HTTP.get_response(url, headers)
75 status = response.code.to_i
76
77 if status / 100 == 2
78 JSON.parse(response.body)
79 elsif status / 100 == 3
80 get_json(response['Location'])
81 else
82 raise APIError.new(response)
83 end
84 end
85
86 def post_json(path, params = {})
87 url = URI(path.start_with?('https://') ? path : @root + path)
88
89 headers = {}
90 headers['Authorization'] = "Bearer #{@access_token}" if @access_token
91
92 request = Net::HTTP::Post.new(url, headers)
93 request.form_data = params
94
95 response = Net::HTTP.start(url.hostname, url.port, :use_ssl => true) do |http|
96 http.request(request)
97 end
98
99 status = response.code.to_i
100
101 if status / 100 == 2
102 JSON.parse(response.body)
103 else
104 raise APIError.new(response)
105 end
106 end
107end