diff options
author | Lorenz Diener <[email protected]> | 2016-11-25 20:57:53 +0100 |
---|---|---|
committer | Lorenz Diener <[email protected]> | 2016-11-25 20:57:53 +0100 |
commit | ab5889404144834b2671d2d25b207fe761418d5c (patch) | |
tree | 0bd05a5cd7d233b102cbf89176b88bf2af92ad5e /mastodon | |
parent | e4e3a8eb93721bf12e4a5b2bf45dc3c2a473dc6f (diff) | |
parent | b958ce54ba32968ef159bda91c8f480c74374f68 (diff) | |
download | mastodon.py-ab5889404144834b2671d2d25b207fe761418d5c.tar.gz |
Merge remote-tracking branch 'refs/remotes/origin/master' into ratelimits
# Conflicts:
# mastodon/Mastodon.py
Diffstat (limited to 'mastodon')
-rw-r--r-- | mastodon/Mastodon.py | 176 |
1 files changed, 104 insertions, 72 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 8af739d..bc1c52b 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py | |||
@@ -29,7 +29,7 @@ class Mastodon: | |||
29 | @staticmethod | 29 | @staticmethod |
30 | def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL): | 30 | def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL): |
31 | """ | 31 | """ |
32 | Creates a new app with given client_name and scopes (read, write, follow) | 32 | Create a new app with given client_name and scopes (read, write, follow) |
33 | 33 | ||
34 | Specify redirect_uris if you want users to be redirected to a certain page after authenticating. | 34 | Specify redirect_uris if you want users to be redirected to a certain page after authenticating. |
35 | Specify to_file to persist your apps info to a file so you can use them in the constructor. | 35 | Specify to_file to persist your apps info to a file so you can use them in the constructor. |
@@ -64,7 +64,7 @@ class Mastodon: | |||
64 | ### | 64 | ### |
65 | def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL, debug_requests = False, ratelimit_method = "wait", ratelimit_pacefactor = 0.9): | 65 | def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL, debug_requests = False, ratelimit_method = "wait", ratelimit_pacefactor = 0.9): |
66 | """ | 66 | """ |
67 | Creates a new API wrapper instance based on the given client_secret and client_id. If you | 67 | Create a new API wrapper instance based on the given client_secret and client_id. If you |
68 | give a client_id and it is not a file, you must also give a secret. | 68 | give a client_id and it is not a file, you must also give a secret. |
69 | 69 | ||
70 | You can also directly specify an access_token, directly or as a file. | 70 | You can also directly specify an access_token, directly or as a file. |
@@ -108,15 +108,15 @@ class Mastodon: | |||
108 | 108 | ||
109 | def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None): | 109 | def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None): |
110 | """ | 110 | """ |
111 | Logs in and sets access_token to what was returned. Note that your | 111 | Log in and sets access_token to what was returned. Note that your |
112 | username is the e-mail you use to log in into mastodon. | 112 | username is the e-mail you use to log in into mastodon. |
113 | 113 | ||
114 | Can persist access token to file, to be used in the constructor. | 114 | Can persist access token to file, to be used in the constructor. |
115 | 115 | ||
116 | Will throw an exception if username / password are wrong, scopes are not | 116 | Will throw a MastodonIllegalArgumentError if username / password |
117 | valid or granted scopes differ from requested. | 117 | are wrong, scopes are not valid or granted scopes differ from requested. |
118 | 118 | ||
119 | Returns the access_token, as well. | 119 | Returns the access_token. |
120 | """ | 120 | """ |
121 | params = self.__generate_params(locals()) | 121 | params = self.__generate_params(locals()) |
122 | params['client_id'] = self.client_id | 122 | params['client_id'] = self.client_id |
@@ -125,7 +125,7 @@ class Mastodon: | |||
125 | params['scope'] = " ".join(scopes) | 125 | params['scope'] = " ".join(scopes) |
126 | 126 | ||
127 | try: | 127 | try: |
128 | response = self.__api_request('POST', '/oauth/token', params) | 128 | response = self.__api_request('POST', '/oauth/token', params, do_ratelimiting = False) |
129 | self.access_token = response['access_token'] | 129 | self.access_token = response['access_token'] |
130 | except: | 130 | except: |
131 | raise MastodonIllegalArgumentError('Invalid user name, password or scopes.') | 131 | raise MastodonIllegalArgumentError('Invalid user name, password or scopes.') |
@@ -147,35 +147,45 @@ class Mastodon: | |||
147 | ## | 147 | ## |
148 | def timeline(self, timeline = "home", max_id = None, since_id = None, limit = None): | 148 | def timeline(self, timeline = "home", max_id = None, since_id = None, limit = None): |
149 | """ | 149 | """ |
150 | Returns statuses, most recent ones first. Timeline can be home, mentions, public | 150 | Fetch statuses, most recent ones first. Timeline can be home, mentions, public |
151 | or tag/hashtag. See the following functions documentation for what those do. | 151 | or tag/hashtag. See the following functions documentation for what those do. |
152 | 152 | ||
153 | The default timeline is the "home" timeline. | 153 | The default timeline is the "home" timeline. |
154 | |||
155 | Returns a list of toot dicts. | ||
154 | """ | 156 | """ |
155 | params = self.__generate_params(locals(), ['timeline']) | 157 | params = self.__generate_params(locals(), ['timeline']) |
156 | return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) | 158 | return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) |
157 | 159 | ||
158 | def timeline_home(self, max_id = None, since_id = None, limit = None): | 160 | def timeline_home(self, max_id = None, since_id = None, limit = None): |
159 | """ | 161 | """ |
160 | Returns the authenticated users home timeline (i.e. followed users and self). | 162 | Fetch the authenticated users home timeline (i.e. followed users and self). |
163 | |||
164 | Returns a list of toot dicts. | ||
161 | """ | 165 | """ |
162 | return self.timeline('home', max_id = max_id, since_id = since_id, limit = limit) | 166 | return self.timeline('home', max_id = max_id, since_id = since_id, limit = limit) |
163 | 167 | ||
164 | def timeline_mentions(self, max_id = None, since_id = None, limit = None): | 168 | def timeline_mentions(self, max_id = None, since_id = None, limit = None): |
165 | """ | 169 | """ |
166 | Returns the authenticated users mentions. | 170 | Fetches the authenticated users mentions. |
171 | |||
172 | Returns a list of toot dicts. | ||
167 | """ | 173 | """ |
168 | return self.timeline('mentions', max_id = max_id, since_id = since_id, limit = limit) | 174 | return self.timeline('mentions', max_id = max_id, since_id = since_id, limit = limit) |
169 | 175 | ||
170 | def timeline_public(self, max_id = None, since_id = None, limit = None): | 176 | def timeline_public(self, max_id = None, since_id = None, limit = None): |
171 | """ | 177 | """ |
172 | Returns the public / visible-network timeline. | 178 | Fetches the public / visible-network timeline. |
179 | |||
180 | Returns a list of toot dicts. | ||
173 | """ | 181 | """ |
174 | return self.timeline('public', max_id = max_id, since_id = since_id, limit = limit) | 182 | return self.timeline('public', max_id = max_id, since_id = since_id, limit = limit) |
175 | 183 | ||
176 | def timeline_hashtag(self, hashtag, max_id = None, since_id = None, limit = None): | 184 | def timeline_hashtag(self, hashtag, max_id = None, since_id = None, limit = None): |
177 | """ | 185 | """ |
178 | Returns all toots with a given hashtag. | 186 | Fetch a timeline of toots with a given hashtag. |
187 | |||
188 | Returns a list of toot dicts. | ||
179 | """ | 189 | """ |
180 | return self.timeline('tag/' + str(hashtag), max_id = max_id, since_id = since_id, limit = limit) | 190 | return self.timeline('tag/' + str(hashtag), max_id = max_id, since_id = since_id, limit = limit) |
181 | 191 | ||
@@ -184,25 +194,33 @@ class Mastodon: | |||
184 | ### | 194 | ### |
185 | def status(self, id): | 195 | def status(self, id): |
186 | """ | 196 | """ |
187 | Returns a status. | 197 | Fetch information about a single toot. |
198 | |||
199 | Returns a toot dict. | ||
188 | """ | 200 | """ |
189 | return self.__api_request('GET', '/api/v1/statuses/' + str(id)) | 201 | return self.__api_request('GET', '/api/v1/statuses/' + str(id)) |
190 | 202 | ||
191 | def status_context(self, id): | 203 | def status_context(self, id): |
192 | """ | 204 | """ |
193 | Returns ancestors and descendants of the status. | 205 | Fetch information about ancestors and descendants of a toot. |
206 | |||
207 | Returns a context dict. | ||
194 | """ | 208 | """ |
195 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') | 209 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') |
196 | 210 | ||
197 | def status_reblogged_by(self, id): | 211 | def status_reblogged_by(self, id): |
198 | """ | 212 | """ |
199 | Returns a list of users that have reblogged a status. | 213 | Fetch a list of users that have reblogged a status. |
214 | |||
215 | Returns a list of user dicts. | ||
200 | """ | 216 | """ |
201 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') | 217 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') |
202 | 218 | ||
203 | def status_favourited_by(self, id): | 219 | def status_favourited_by(self, id): |
204 | """ | 220 | """ |
205 | Returns a list of users that have favourited a status. | 221 | Fetch a list of users that have favourited a status. |
222 | |||
223 | Returns a list of user dicts. | ||
206 | """ | 224 | """ |
207 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') | 225 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') |
208 | 226 | ||
@@ -211,8 +229,10 @@ class Mastodon: | |||
211 | ### | 229 | ### |
212 | def notifications(self): | 230 | def notifications(self): |
213 | """ | 231 | """ |
214 | Returns notifications (mentions, favourites, reblogs, follows) for the authenticated | 232 | Fetch notifications (mentions, favourites, reblogs, follows) for the authenticated |
215 | user. | 233 | user. |
234 | |||
235 | Returns a list of notification dicts. | ||
216 | """ | 236 | """ |
217 | return self.__api_request('GET', '/api/v1/notifications') | 237 | return self.__api_request('GET', '/api/v1/notifications') |
218 | 238 | ||
@@ -221,53 +241,61 @@ class Mastodon: | |||
221 | ### | 241 | ### |
222 | def account(self, id): | 242 | def account(self, id): |
223 | """ | 243 | """ |
224 | Returns account. | 244 | Fetch account information by user id. |
245 | |||
246 | Returns a user dict. | ||
225 | """ | 247 | """ |
226 | return self.__api_request('GET', '/api/v1/accounts/' + str(id)) | 248 | return self.__api_request('GET', '/api/v1/accounts/' + str(id)) |
227 | 249 | ||
228 | def account_verify_credentials(self): | 250 | def account_verify_credentials(self): |
229 | """ | 251 | """ |
230 | Returns authenticated user's account. | 252 | Fetch authenticated user's account information. |
253 | |||
254 | Returns a user dict. | ||
231 | """ | 255 | """ |
232 | return self.__api_request('GET', '/api/v1/accounts/verify_credentials') | 256 | return self.__api_request('GET', '/api/v1/accounts/verify_credentials') |
233 | 257 | ||
234 | def account_statuses(self, id, max_id = None, since_id = None, limit = None): | 258 | def account_statuses(self, id, max_id = None, since_id = None, limit = None): |
235 | """ | 259 | """ |
236 | Returns statuses by user. Same options as timeline are permitted. | 260 | Fetch statuses by user id. Same options as timeline are permitted. |
261 | |||
262 | Returns a list of toot dicts. | ||
237 | """ | 263 | """ |
238 | params = self.__generate_params(locals(), ['id']) | 264 | params = self.__generate_params(locals(), ['id']) |
239 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses', params) | 265 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses', params) |
240 | 266 | ||
241 | def account_following(self, id): | 267 | def account_following(self, id): |
242 | """ | 268 | """ |
243 | Returns users the given user is following. | 269 | Fetch users the given user is following. |
270 | |||
271 | Returns a list of user dicts. | ||
244 | """ | 272 | """ |
245 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following') | 273 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following') |
246 | 274 | ||
247 | def account_followers(self, id): | 275 | def account_followers(self, id): |
248 | """ | 276 | """ |
249 | Returns users the given user is followed by. | 277 | Fetch users the given user is followed by. |
278 | |||
279 | Returns a list of user dicts. | ||
250 | """ | 280 | """ |
251 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers') | 281 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers') |
252 | 282 | ||
253 | def account_relationships(self, id): | 283 | def account_relationships(self, id): |
254 | """ | 284 | """ |
255 | Returns relationships (following, followed_by, blocking) of the logged in user to | 285 | Fetch relationships (following, followed_by, blocking) of the logged in user to |
256 | a given account. id can be a list. | 286 | a given account. id can be a list. |
287 | |||
288 | Returns a list of relationship dicts. | ||
257 | """ | 289 | """ |
258 | params = self.__generate_params(locals()) | 290 | params = self.__generate_params(locals()) |
259 | return self.__api_request('GET', '/api/v1/accounts/relationships', params) | 291 | return self.__api_request('GET', '/api/v1/accounts/relationships', params) |
260 | |||
261 | def account_suggestions(self): | ||
262 | """ | ||
263 | Returns accounts that the system suggests the authenticated user to follow. | ||
264 | """ | ||
265 | return self.__api_request('GET', '/api/v1/accounts/suggestions') | ||
266 | 292 | ||
267 | def account_search(self, q, limit = None): | 293 | def account_search(self, q, limit = None): |
268 | """ | 294 | """ |
269 | Returns matching accounts. Will lookup an account remotely if the search term is | 295 | Fetch matching accounts. Will lookup an account remotely if the search term is |
270 | in the username@domain format and not yet in the database. | 296 | in the username@domain format and not yet in the database. |
297 | |||
298 | Returns a list of user dicts. | ||
271 | """ | 299 | """ |
272 | params = self.__generate_params(locals()) | 300 | params = self.__generate_params(locals()) |
273 | return self.__api_request('GET', '/api/v1/accounts/search', params) | 301 | return self.__api_request('GET', '/api/v1/accounts/search', params) |
@@ -277,10 +305,10 @@ class Mastodon: | |||
277 | ### | 305 | ### |
278 | def status_post(self, status, in_reply_to_id = None, media_ids = None): | 306 | def status_post(self, status, in_reply_to_id = None, media_ids = None): |
279 | """ | 307 | """ |
280 | Posts a status. Can optionally be in reply to another status and contain | 308 | Post a status. Can optionally be in reply to another status and contain |
281 | up to four pieces of media (Uploaded via media_post()). | 309 | up to four pieces of media (Uploaded via media_post()). |
282 | 310 | ||
283 | Returns the new status. | 311 | Returns a toot dict with the new status. |
284 | """ | 312 | """ |
285 | params = self.__generate_params(locals()) | 313 | params = self.__generate_params(locals()) |
286 | return self.__api_request('POST', '/api/v1/statuses', params) | 314 | return self.__api_request('POST', '/api/v1/statuses', params) |
@@ -288,41 +316,46 @@ class Mastodon: | |||
288 | def toot(self, status): | 316 | def toot(self, status): |
289 | """ | 317 | """ |
290 | Synonym for status_post that only takes the status text as input. | 318 | Synonym for status_post that only takes the status text as input. |
319 | |||
320 | Returns a toot dict with the new status. | ||
291 | """ | 321 | """ |
292 | return self.status_post(status) | 322 | return self.status_post(status) |
293 | 323 | ||
294 | def status_delete(self, id): | 324 | def status_delete(self, id): |
295 | """ | 325 | """ |
296 | Deletes a status | 326 | Delete a status |
327 | |||
328 | Returns an empty dict for good measure. | ||
297 | """ | 329 | """ |
298 | return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) | 330 | return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) |
299 | 331 | ||
300 | def status_reblog(self, id): | 332 | def status_reblog(self, id): |
301 | """Reblogs a status. | 333 | """Reblog a status. |
302 | 334 | ||
303 | Returns a new status that wraps around the reblogged one.""" | 335 | Returns a toot with with a new status that wraps around the reblogged one. |
336 | """ | ||
304 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") | 337 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") |
305 | 338 | ||
306 | def status_unreblog(self, id): | 339 | def status_unreblog(self, id): |
307 | """ | 340 | """ |
308 | Un-reblogs a status. | 341 | Un-reblog a status. |
309 | 342 | ||
310 | Returns the status that used to be reblogged. | 343 | Returns a toot dict with the status that used to be reblogged. |
311 | """ | 344 | """ |
312 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") | 345 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") |
313 | 346 | ||
314 | def status_favourite(self, id): | 347 | def status_favourite(self, id): |
315 | """ | 348 | """ |
316 | Favourites a status. | 349 | Favourite a status. |
317 | 350 | ||
318 | Returns the favourited status. | 351 | Returns a toot dict with the favourited status. |
319 | """ | 352 | """ |
320 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") | 353 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") |
321 | 354 | ||
322 | def status_unfavourite(self, id): | 355 | def status_unfavourite(self, id): |
323 | """Favourites a status. | 356 | """Favourite a status. |
324 | 357 | ||
325 | Returns the un-favourited status. | 358 | Returns a toot dict with the un-favourited status. |
326 | """ | 359 | """ |
327 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") | 360 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") |
328 | 361 | ||
@@ -331,33 +364,33 @@ class Mastodon: | |||
331 | ### | 364 | ### |
332 | def account_follow(self, id): | 365 | def account_follow(self, id): |
333 | """ | 366 | """ |
334 | Follows a user. | 367 | Follow a user. |
335 | 368 | ||
336 | Returns the updated relationship to the user. | 369 | Returns a relationship dict containing the updated relationship to the user. |
337 | """ | 370 | """ |
338 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") | 371 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") |
339 | 372 | ||
340 | def account_unfollow(self, id): | 373 | def account_unfollow(self, id): |
341 | """ | 374 | """ |
342 | Unfollows a user. | 375 | Unfollow a user. |
343 | 376 | ||
344 | Returns the updated relationship to the user. | 377 | Returns a relationship dict containing the updated relationship to the user. |
345 | """ | 378 | """ |
346 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") | 379 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") |
347 | 380 | ||
348 | def account_block(self, id): | 381 | def account_block(self, id): |
349 | """ | 382 | """ |
350 | Blocks a user. | 383 | Block a user. |
351 | 384 | ||
352 | Returns the updated relationship to the user. | 385 | Returns a relationship dict containing the updated relationship to the user. |
353 | """ | 386 | """ |
354 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") | 387 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") |
355 | 388 | ||
356 | def account_unblock(self, id): | 389 | def account_unblock(self, id): |
357 | """ | 390 | """ |
358 | Unblocks a user. | 391 | Unblock a user. |
359 | 392 | ||
360 | Returns the updated relationship to the user. | 393 | Returns a relationship dict containing the updated relationship to the user. |
361 | """ | 394 | """ |
362 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") | 395 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") |
363 | 396 | ||
@@ -366,20 +399,18 @@ class Mastodon: | |||
366 | ### | 399 | ### |
367 | def media_post(self, media_file, mime_type = None): | 400 | def media_post(self, media_file, mime_type = None): |
368 | """ | 401 | """ |
369 | Posts an image. media_file can either be image data or | 402 | Post an image. media_file can either be image data or |
370 | a file name. If image data is passed directly, the mime | 403 | a file name. If image data is passed directly, the mime |
371 | type has to be specified manually, otherwise, it is | 404 | type has to be specified manually, otherwise, it is |
372 | determined from the file name. | 405 | determined from the file name. |
373 | 406 | ||
374 | Returns the uploaded media metadata object. Importantly, this contains | ||
375 | the ID that can then be used in status_post() to attach the media to | ||
376 | a toot. | ||
377 | |||
378 | Throws a MastodonIllegalArgumentError if the mime type of the | 407 | Throws a MastodonIllegalArgumentError if the mime type of the |
379 | passed data or file can not be determined properly. | 408 | passed data or file can not be determined properly. |
409 | |||
410 | Returns a media dict. This contains the id that can be used in | ||
411 | status_post to attach the media file to a toot. | ||
380 | """ | 412 | """ |
381 | 413 | if os.path.isfile(media_file) and mime_type == None: | |
382 | if os.path.isfile(media_file): | ||
383 | mime_type = mimetypes.guess_type(media_file)[0] | 414 | mime_type = mimetypes.guess_type(media_file)[0] |
384 | media_file = open(media_file, 'rb') | 415 | media_file = open(media_file, 'rb') |
385 | 416 | ||
@@ -395,7 +426,7 @@ class Mastodon: | |||
395 | ### | 426 | ### |
396 | # Internal helpers, dragons probably | 427 | # Internal helpers, dragons probably |
397 | ### | 428 | ### |
398 | def __api_request(self, method, endpoint, params = {}, files = {}): | 429 | def __api_request(self, method, endpoint, params = {}, files = {}, do_ratelimiting = True): |
399 | """ | 430 | """ |
400 | Internal API request helper. | 431 | Internal API request helper. |
401 | 432 | ||
@@ -410,7 +441,7 @@ class Mastodon: | |||
410 | 441 | ||
411 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it | 442 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it |
412 | # would take to not hit the rate limit at that request rate. | 443 | # would take to not hit the rate limit at that request rate. |
413 | if self.ratelimit_method == "pace": | 444 | if do_ratelimiting and self.ratelimit_method == "pace": |
414 | if self.ratelimit_remaining == 0: | 445 | if self.ratelimit_remaining == 0: |
415 | to_next = self.ratelimit_reset - time.time() | 446 | to_next = self.ratelimit_reset - time.time() |
416 | if to_next > 0: | 447 | if to_next > 0: |
@@ -472,20 +503,21 @@ class Mastodon: | |||
472 | raise MastodonAPIError("Could not parse response as JSON, respose code was " + str(response_object.status_code)) | 503 | raise MastodonAPIError("Could not parse response as JSON, respose code was " + str(response_object.status_code)) |
473 | 504 | ||
474 | # Handle rate limiting | 505 | # Handle rate limiting |
475 | self.ratelimit_remaining = int(response_object.headers['X-RateLimit-Remaining']) | 506 | if 'X-RateLimit-Remaining' in response_object.headers and do_ratelimiting: |
476 | self.ratelimit_limit = int(response_object.headers['X-RateLimit-Limit']) | 507 | self.ratelimit_remaining = int(response_object.headers['X-RateLimit-Remaining']) |
477 | self.ratelimit_reset = (datetime.strptime(response_object.headers['X-RateLimit-Reset'], "%Y-%m-%dT%H:%M:%S.%fZ") - datetime(1970, 1, 1)).total_seconds() | 508 | self.ratelimit_limit = int(response_object.headers['X-RateLimit-Limit']) |
478 | self.ratelimit_lastcall = time.time() | 509 | self.ratelimit_reset = (datetime.strptime(response_object.headers['X-RateLimit-Reset'], "%Y-%m-%dT%H:%M:%S.%fZ") - datetime(1970, 1, 1)).total_seconds() |
479 | 510 | self.ratelimit_lastcall = time.time() | |
480 | if "error" in response and response["error"] == "Throttled": | 511 | |
481 | if self.ratelimit_method == "throw": | 512 | if "error" in response and response["error"] == "Throttled": |
482 | raise MastodonRatelimitError("Hit rate limit.") | 513 | if self.ratelimit_method == "throw": |
483 | 514 | raise MastodonRatelimitError("Hit rate limit.") | |
484 | if self.ratelimit_method == "wait" or self.ratelimit_method == "pace": | 515 | |
485 | to_next = self.ratelimit_reset - time.time() | 516 | if self.ratelimit_method == "wait" or self.ratelimit_method == "pace": |
486 | if to_next > 0: | 517 | to_next = self.ratelimit_reset - time.time() |
487 | time.sleep(to_next) | 518 | if to_next > 0: |
488 | request_complete = False | 519 | time.sleep(to_next) |
520 | request_complete = False | ||
489 | 521 | ||
490 | return response | 522 | return response |
491 | 523 | ||