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