aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mastodon.py173
1 files changed, 114 insertions, 59 deletions
diff --git a/Mastodon.py b/Mastodon.py
index 5119688..2519bc8 100644
--- a/Mastodon.py
+++ b/Mastodon.py
@@ -5,16 +5,17 @@ import os
5import os.path 5import os.path
6 6
7class Mastodon: 7class Mastodon:
8 """ Super basic but thorough and easy to use mastodon.social 8 """
9 api wrapper in python. 9 Super basic but thorough and easy to use mastodon.social
10 api wrapper in python.
10 11
11 If anything is unclear, check the official API docs at 12 If anything is unclear, check the official API docs at
12 https://github.com/Gargron/mastodon/wiki/API 13 https://github.com/Gargron/mastodon/wiki/API
13 14
14 Presently, only username-password login is supported, somebody please 15 Presently, only username-password login is supported, somebody please
15 patch in Real Proper OAuth if desired. 16 patch in Real Proper OAuth if desired.
16 17
17 KNOWN BUGS: Media api does not work, reason unclear. 18 KNOWN BUGS: Media api does not work, reason unclear.
18 """ 19 """
19 __DEFAULT_BASE_URL = 'https://mastodon.social' 20 __DEFAULT_BASE_URL = 'https://mastodon.social'
20 21
@@ -23,13 +24,14 @@ class Mastodon:
23 ### 24 ###
24 @staticmethod 25 @staticmethod
25 def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL): 26 def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL):
26 """Creates a new app with given client_name and scopes (read, write, follow) 27 """
28 Creates a new app with given client_name and scopes (read, write, follow)
27 29
28 Specify redirect_uris if you want users to be redirected to a certain page after authenticating. 30 Specify redirect_uris if you want users to be redirected to a certain page after authenticating.
29 Specify to_file to persist your apps info to a file so you can use them in the constructor. 31 Specify to_file to persist your apps info to a file so you can use them in the constructor.
30 Specify api_base_url if you want to register an app on an instance different from the flagship one. 32 Specify api_base_url if you want to register an app on an instance different from the flagship one.
31 33
32 returns client_id and client_secret 34 Returns client_id and client_secret.
33 """ 35 """
34 request_data = { 36 request_data = {
35 'client_name': client_name, 37 'client_name': client_name,
@@ -54,13 +56,14 @@ class Mastodon:
54 # Authentication, including constructor 56 # Authentication, including constructor
55 ### 57 ###
56 def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL): 58 def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL):
57 """Create a new API wrapper instance based on the given client_secret and client_id. If you 59 """
58 give a client_id and it is not a file, you must also give a secret. 60 Creates a new API wrapper instance based on the given client_secret and client_id. If you
61 give a client_id and it is not a file, you must also give a secret.
59 62
60 You can also directly specify an access_token, directly or as a file. 63 You can also directly specify an access_token, directly or as a file.
61 64
62 Specify api_base_url if you wish to talk to an instance other than the flagship one. 65 Specify api_base_url if you wish to talk to an instance other than the flagship one.
63 If a file is given as client_id, read client ID and secret from that file 66 If a file is given as client_id, read client ID and secret from that file
64 """ 67 """
65 self.api_base_url = api_base_url 68 self.api_base_url = api_base_url
66 self.client_id = client_id 69 self.client_id = client_id
@@ -80,10 +83,11 @@ class Mastodon:
80 self.access_token = token_file.readline().rstrip() 83 self.access_token = token_file.readline().rstrip()
81 84
82 def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None): 85 def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None):
83 """Logs in and sets access_token to what was returned. 86 """
84 Can persist access token to file. 87 Logs in and sets access_token to what was returned.
88 Can persist access token to file.
85 89
86 Returns the access_token, as well. 90 Returns the access_token, as well.
87 """ 91 """
88 params = self.__generate_params(locals()) 92 params = self.__generate_params(locals())
89 params['client_id'] = self.client_id 93 params['client_id'] = self.client_id
@@ -104,8 +108,10 @@ class Mastodon:
104 # Reading data: Timelines 108 # Reading data: Timelines
105 ## 109 ##
106 def timeline(self, timeline = 'home', max_id = None, since_id = None, limit = None): 110 def timeline(self, timeline = 'home', max_id = None, since_id = None, limit = None):
107 """Returns statuses, most recent ones first. Timeline can be home, mentions, public 111 """
108 or tag/:hashtag""" 112 Returns statuses, most recent ones first. Timeline can be home, mentions, public
113 or tag/:hashtag
114 """
109 params = self.__generate_params(locals(), ['timeline']) 115 params = self.__generate_params(locals(), ['timeline'])
110 return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) 116 return self.__api_request('GET', '/api/v1/timelines/' + timeline, params)
111 117
@@ -113,58 +119,82 @@ class Mastodon:
113 # Reading data: Statuses 119 # Reading data: Statuses
114 ### 120 ###
115 def status(self, id): 121 def status(self, id):
116 """Returns status.""" 122 """
123 Returns a status.
124 """
117 return self.__api_request('GET', '/api/v1/statuses/' + str(id)) 125 return self.__api_request('GET', '/api/v1/statuses/' + str(id))
118 126
119 def status_context(self, id): 127 def status_context(self, id):
120 """Returns ancestors and descendants of the status.""" 128 """
129 Returns ancestors and descendants of the status.
130 """
121 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') 131 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context')
122 132
123 def status_reblogged_by(self, id): 133 def status_reblogged_by(self, id):
124 """Returns a list of users that have reblogged a status.""" 134 """
135 Returns a list of users that have reblogged a status.
136 """
125 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') 137 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by')
126 138
127 def status_favourited_by(self, id): 139 def status_favourited_by(self, id):
128 """Returns a list of users that have favourited a status.""" 140 """
141 Returns a list of users that have favourited a status.
142 """
129 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') 143 return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by')
130 144
131 ### 145 ###
132 # Reading data: Accounts 146 # Reading data: Accounts
133 ### 147 ###
134 def account(self, id): 148 def account(self, id):
135 """Returns account.""" 149 """
150 Returns account.
151 """
136 return self.__api_request('GET', '/api/v1/accounts/' + str(id)) 152 return self.__api_request('GET', '/api/v1/accounts/' + str(id))
137 153
138 def account_verify_credentials(self): 154 def account_verify_credentials(self):
139 """Returns authenticated user's account.""" 155 """
156 Returns authenticated user's account.
157 """
140 return self.__api_request('GET', '/api/v1/accounts/verify_credentials') 158 return self.__api_request('GET', '/api/v1/accounts/verify_credentials')
141 159
142 def account_statuses(self, id, max_id = None, since_id = None, limit = None): 160 def account_statuses(self, id, max_id = None, since_id = None, limit = None):
143 """Returns statuses by user. Same options as timeline are permitted.""" 161 """
162 Returns statuses by user. Same options as timeline are permitted.
163 """
144 params = self.__generate_params(locals(), ['id']) 164 params = self.__generate_params(locals(), ['id'])
145 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses') 165 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses')
146 166
147 def account_following(self, id): 167 def account_following(self, id):
148 """Returns users the given user is following.""" 168 """
169 Returns users the given user is following.
170 """
149 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following') 171 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following')
150 172
151 def account_followers(self, id): 173 def account_followers(self, id):
152 """Returns users the given user is followed by.""" 174 """
175 Returns users the given user is followed by.
176 """
153 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers') 177 return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers')
154 178
155 def account_relationships(self, id): 179 def account_relationships(self, id):
156 """Returns relationships (following, followed_by, blocking) of the logged in user to 180 """
157 a given account. id can be a list.""" 181 Returns relationships (following, followed_by, blocking) of the logged in user to
182 a given account. id can be a list.
183 """
158 params = self.__generate_params(locals()) 184 params = self.__generate_params(locals())
159 return self.__api_request('GET', '/api/v1/accounts/relationships', params) 185 return self.__api_request('GET', '/api/v1/accounts/relationships', params)
160 186
161 def account_suggestions(self): 187 def account_suggestions(self):
162 """Returns accounts that the system suggests the authenticated user to follow.""" 188 """
189 Returns accounts that the system suggests the authenticated user to follow.
190 """
163 return self.__api_request('GET', '/api/v1/accounts/suggestions') 191 return self.__api_request('GET', '/api/v1/accounts/suggestions')
164 192
165 def account_search(self, q, limit = None): 193 def account_search(self, q, limit = None):
166 """Returns matching accounts. Will lookup an account remotely if the search term is 194 """
167 in the username@domain format and not yet in the database.""" 195 Returns matching accounts. Will lookup an account remotely if the search term is
196 in the username@domain format and not yet in the database.
197 """
168 params = self.__generate_params(locals()) 198 params = self.__generate_params(locals())
169 return self.__api_request('GET', '/api/v1/accounts/search', params) 199 return self.__api_request('GET', '/api/v1/accounts/search', params)
170 200
@@ -172,19 +202,25 @@ class Mastodon:
172 # Writing data: Statuses 202 # Writing data: Statuses
173 ### 203 ###
174 def status_post(self, status, in_reply_to_id = None, media_ids = None): 204 def status_post(self, status, in_reply_to_id = None, media_ids = None):
175 """Posts a status. Can optionally be in reply to another status and contain 205 """
176 up to four pieces of media (Uploaded via media_post()). 206 Posts a status. Can optionally be in reply to another status and contain
207 up to four pieces of media (Uploaded via media_post()).
177 208
178 Returns the new status.""" 209 Returns the new status.
210 """
179 params = self.__generate_params(locals()) 211 params = self.__generate_params(locals())
180 return self.__api_request('POST', '/api/v1/statuses', params) 212 return self.__api_request('POST', '/api/v1/statuses', params)
181 213
182 def toot(self, status): 214 def toot(self, status):
183 """Synonym for status_post that only takes the status text as input.""" 215 """
216 Synonym for status_post that only takes the status text as input.
217 """
184 return self.status_post(status) 218 return self.status_post(status)
185 219
186 def status_delete(self, id): 220 def status_delete(self, id):
187 """Deletes a status""" 221 """
222 Deletes a status
223 """
188 return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) 224 return self.__api_request('DELETE', '/api/v1/statuses/' + str(id))
189 225
190 def status_reblog(self, id): 226 def status_reblog(self, id):
@@ -194,58 +230,73 @@ class Mastodon:
194 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") 230 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog")
195 231
196 def status_unreblog(self, id): 232 def status_unreblog(self, id):
197 """Un-reblogs a status. 233 """
234 Un-reblogs a status.
198 235
199 Returns the status that used to be reblogged.""" 236 Returns the status that used to be reblogged.
237 """
200 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") 238 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog")
201 239
202 def status_favourite(self, id): 240 def status_favourite(self, id):
203 """Favourites a status. 241 """
242 Favourites a status.
204 243
205 Returns the favourited status.""" 244 Returns the favourited status.
245 """
206 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") 246 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite")
207 247
208 def status_unfavourite(self, id): 248 def status_unfavourite(self, id):
209 """Favourites a status. 249 """Favourites a status.
210 250
211 Returns the un-favourited status.""" 251 Returns the un-favourited status.
252 """
212 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") 253 return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite")
213 254
214 ### 255 ###
215 # Writing data: Statuses 256 # Writing data: Statuses
216 ### 257 ###
217 def account_follow(self, id): 258 def account_follow(self, id):
218 """Follows a user. 259 """
260 Follows a user.
219 261
220 Returns the updated relationship to the user.""" 262 Returns the updated relationship to the user.
263 """
221 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") 264 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow")
222 265
223 def account_unfollow(self, id): 266 def account_unfollow(self, id):
224 """Unfollows a user. 267 """
268 Unfollows a user.
225 269
226 Returns the updated relationship to the user.""" 270 Returns the updated relationship to the user.
271 """
227 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") 272 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow")
228 273
229 def account_block(self, id): 274 def account_block(self, id):
230 """Blocks a user. 275 """
276 Blocks a user.
231 277
232 Returns the updated relationship to the user.""" 278 Returns the updated relationship to the user.
279 """
233 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") 280 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block")
234 281
235 def account_unblock(self, id): 282 def account_unblock(self, id):
236 """Unblocks a user. 283 """
284 Unblocks a user.
237 285
238 Returns the updated relationship to the user.""" 286 Returns the updated relationship to the user.
287 """
239 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") 288 return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock")
240 289
241 ### 290 ###
242 # Writing data: Media 291 # Writing data: Media
243 ### 292 ###
244 def media_post(self, media_file): 293 def media_post(self, media_file):
245 """Posts an image. media_file can either be image data or 294 """
246 a file name. 295 Posts an image. media_file can either be image data or
296 a file name.
247 297
248 Returns the ID of the media that can then be used in status_post().""" 298 Returns the ID of the media that can then be used in status_post().
299 """
249 if os.path.isfile(media_file): 300 if os.path.isfile(media_file):
250 media_file = open(media_file, 'rb') 301 media_file = open(media_file, 'rb')
251 302
@@ -255,7 +306,9 @@ class Mastodon:
255 # Internal helpers, dragons probably 306 # Internal helpers, dragons probably
256 ### 307 ###
257 def __api_request(self, method, endpoint, params = {}, files = {}): 308 def __api_request(self, method, endpoint, params = {}, files = {}):
258 """ Internal API request helper.""" 309 """
310 Internal API request helper.
311 """
259 response = None 312 response = None
260 headers = None 313 headers = None
261 314
@@ -280,7 +333,9 @@ class Mastodon:
280 return response.json() 333 return response.json()
281 334
282 def __generate_params(self, params, exclude = []): 335 def __generate_params(self, params, exclude = []):
283 """Internal named-parameters-to-dict helper""" 336 """
337 Internal named-parameters-to-dict helper.
338 """
284 params = dict(params) 339 params = dict(params)
285 340
286 del params['self'] 341 del params['self']
Powered by cgit v1.2.3 (git 2.41.0)