aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormicah <[email protected]>2022-11-25 21:45:52 +0000
committermicah <[email protected]>2022-11-25 21:45:52 +0000
commitdd23f8b7c5e46989b2a73b21816c59bbbd34ef35 (patch)
treedd9c08cce97aec58daacd8f7b852d2e64886f951 /mastodon
parent2c7b58568db735534912b2bc0990294e9d4a9c19 (diff)
parent99514e50d1e1ef6330397c2cada203dfa3891b3a (diff)
downloadmastodon.py-dd23f8b7c5e46989b2a73b21816c59bbbd34ef35.tar.gz
Merge remote-tracking branch 'origin/master' into catgoat/domain_blocks
Diffstat (limited to 'mastodon')
-rw-r--r--mastodon/Mastodon.py244
1 files changed, 174 insertions, 70 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index 3d04955..befb030 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -167,7 +167,6 @@ class Mastodon:
167 If anything is unclear, check the official API docs at 167 If anything is unclear, check the official API docs at
168 https://github.com/mastodon/documentation/blob/master/content/en/client/intro.md 168 https://github.com/mastodon/documentation/blob/master/content/en/client/intro.md
169 """ 169 """
170 __DEFAULT_BASE_URL = 'https://mastodon.social'
171 __DEFAULT_TIMEOUT = 300 170 __DEFAULT_TIMEOUT = 300
172 __DEFAULT_STREAM_TIMEOUT = 300 171 __DEFAULT_STREAM_TIMEOUT = 300
173 __DEFAULT_STREAM_RECONNECT_WAIT_SEC = 5 172 __DEFAULT_STREAM_RECONNECT_WAIT_SEC = 5
@@ -260,17 +259,17 @@ class Mastodon:
260 ### 259 ###
261 @staticmethod 260 @staticmethod
262 def create_app(client_name, scopes=__DEFAULT_SCOPES, redirect_uris=None, website=None, to_file=None, 261 def create_app(client_name, scopes=__DEFAULT_SCOPES, redirect_uris=None, website=None, to_file=None,
263 api_base_url=__DEFAULT_BASE_URL, request_timeout=__DEFAULT_TIMEOUT, session=None): 262 api_base_url=None, request_timeout=__DEFAULT_TIMEOUT, session=None):
264 """ 263 """
265 Create a new app with given `client_name` and `scopes` (The basic scopes are "read", "write", "follow" and "push" 264 Create a new app with given `client_name` and `scopes` (The basic scopes are "read", "write", "follow" and "push"
266 - more granular scopes are available, please refer to Mastodon documentation for which). 265 - more granular scopes are available, please refer to Mastodon documentation for which) on the instance given
266 by `api_base_url`.
267 267
268 Specify `redirect_uris` if you want users to be redirected to a certain page after authenticating in an OAuth flow. 268 Specify `redirect_uris` if you want users to be redirected to a certain page after authenticating in an OAuth flow.
269 You can specify multiple URLs by passing a list. Note that if you wish to use OAuth authentication with redirects, 269 You can specify multiple URLs by passing a list. Note that if you wish to use OAuth authentication with redirects,
270 the redirect URI must be one of the URLs specified here. 270 the redirect URI must be one of the URLs specified here.
271 271
272 Specify `to_file` to persist your app's info to a file so you can use it in the constructor. 272 Specify `to_file` to persist your app's info to a file so you can use it in the constructor.
273 Specify `api_base_url` if you want to register an app on an instance different from the flagship one.
274 Specify `website` to give a website for your app. 273 Specify `website` to give a website for your app.
275 274
276 Specify `session` with a requests.Session for it to be used instead of the default. This can be 275 Specify `session` with a requests.Session for it to be used instead of the default. This can be
@@ -282,6 +281,8 @@ class Mastodon:
282 281
283 Returns `client_id` and `client_secret`, both as strings. 282 Returns `client_id` and `client_secret`, both as strings.
284 """ 283 """
284 if api_base_url is None:
285 raise MastodonIllegalArgumentError("API base URL is required.")
285 api_base_url = Mastodon.__protocolize(api_base_url) 286 api_base_url = Mastodon.__protocolize(api_base_url)
286 287
287 request_data = { 288 request_data = {
@@ -299,12 +300,10 @@ class Mastodon:
299 if website is not None: 300 if website is not None:
300 request_data['website'] = website 301 request_data['website'] = website
301 if session: 302 if session:
302 ret = session.post(api_base_url + '/api/v1/apps', 303 ret = session.post(api_base_url + '/api/v1/apps', data=request_data, timeout=request_timeout)
303 data=request_data, timeout=request_timeout)
304 response = ret.json() 304 response = ret.json()
305 else: 305 else:
306 response = requests.post( 306 response = requests.post(api_base_url + '/api/v1/apps', data=request_data, timeout=request_timeout)
307 api_base_url + '/api/v1/apps', data=request_data, timeout=request_timeout)
308 response = response.json() 307 response = response.json()
309 except Exception as e: 308 except Exception as e:
310 raise MastodonNetworkError("Could not complete request: %s" % e) 309 raise MastodonNetworkError("Could not complete request: %s" % e)
@@ -321,17 +320,15 @@ class Mastodon:
321 ### 320 ###
322 # Authentication, including constructor 321 # Authentication, including constructor
323 ### 322 ###
324 def __init__(self, client_id=None, client_secret=None, access_token=None, 323 def __init__(self, client_id=None, client_secret=None, access_token=None, api_base_url=None, debug_requests=False,
325 api_base_url=None, debug_requests=False, 324 ratelimit_method="wait", ratelimit_pacefactor=1.1, request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None,
326 ratelimit_method="wait", ratelimit_pacefactor=1.1, 325 version_check_mode="created", session=None, feature_set="mainline", user_agent="mastodonpy", lang=None):
327 request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, 326 """
328 version_check_mode="created", session=None, feature_set="mainline", user_agent="mastodonpy"): 327 Create a new API wrapper instance based on the given `client_secret` and `client_id` on the
329 """ 328 instance given by `api_base_url`. If you give a `client_id` and it is not a file, you must
330 Create a new API wrapper instance based on the given `client_secret` and `client_id`. If you 329 also give a secret. If you specify an `access_token` then you don't need to specify a `client_id`.
331 give a `client_id` and it is not a file, you must also give a secret. If you specify an 330 It is allowed to specify neither - in this case, you will be restricted to only using endpoints
332 `access_token` then you don't need to specify a `client_id`. It is allowed to specify 331 that do not require authentication. If a file is given as `client_id`, client ID, secret and
333 neither - in this case, you will be restricted to only using endpoints that do not
334 require authentication. If a file is given as `client_id`, client ID, secret and
335 base url are read from that file. 332 base url are read from that file.
336 333
337 You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). If 334 You can also specify an `access_token`, directly or as a file (as written by `log_in()`_). If
@@ -347,10 +344,6 @@ class Mastodon:
347 even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also 344 even in "wait" and "pace" mode, requests can still fail due to network or other problems! Also
348 note that "pace" and "wait" are NOT thread safe. 345 note that "pace" and "wait" are NOT thread safe.
349 346
350 Specify `api_base_url` if you wish to talk to an instance other than the flagship one. When
351 reading from client id or access token files as written by Mastodon.py 1.5.0 or larger,
352 this can be omitted.
353
354 By default, a timeout of 300 seconds is used for all requests. If you wish to change this, 347 By default, a timeout of 300 seconds is used for all requests. If you wish to change this,
355 pass the desired timeout (in seconds) as `request_timeout`. 348 pass the desired timeout (in seconds) as `request_timeout`.
356 349
@@ -376,12 +369,15 @@ class Mastodon:
376 the app name will be used as `User-Agent` header as default. It is possible to modify old secret files and append 369 the app name will be used as `User-Agent` header as default. It is possible to modify old secret files and append
377 a client app name to use it as a `User-Agent` name. 370 a client app name to use it as a `User-Agent` name.
378 371
372 `lang` can be used to change the locale Mastodon will use to generate responses. Valid parameters are all ISO 639-1 (two letter)
373 or for a language that has none, 639-3 (three letter) language codes. This affects some error messages (those related to validation) and
374 trends. You can change the language using `set_language()`_.
375
379 If no other `User-Agent` is specified, "mastodonpy" will be used. 376 If no other `User-Agent` is specified, "mastodonpy" will be used.
380 """ 377 """
381 self.api_base_url = None 378 self.api_base_url = api_base_url
382 if api_base_url is not None: 379 if self.api_base_url is not None:
383 self.api_base_url = Mastodon.__protocolize(api_base_url) 380 self.api_base_url = self.__protocolize(self.api_base_url)
384
385 self.client_id = client_id 381 self.client_id = client_id
386 self.client_secret = client_secret 382 self.client_secret = client_secret
387 self.access_token = access_token 383 self.access_token = access_token
@@ -389,7 +385,7 @@ class Mastodon:
389 self.ratelimit_method = ratelimit_method 385 self.ratelimit_method = ratelimit_method
390 self._token_expired = datetime.datetime.now() 386 self._token_expired = datetime.datetime.now()
391 self._refresh_token = None 387 self._refresh_token = None
392 388
393 self.__logged_in_id = None 389 self.__logged_in_id = None
394 390
395 self.ratelimit_limit = 300 391 self.ratelimit_limit = 300
@@ -412,6 +408,9 @@ class Mastodon:
412 # General defined user-agent 408 # General defined user-agent
413 self.user_agent = user_agent 409 self.user_agent = user_agent
414 410
411 # Save language
412 self.lang = lang
413
415 # Token loading 414 # Token loading
416 if self.client_id is not None: 415 if self.client_id is not None:
417 if os.path.isfile(self.client_id): 416 if os.path.isfile(self.client_id):
@@ -422,9 +421,9 @@ class Mastodon:
422 try_base_url = secret_file.readline().rstrip() 421 try_base_url = secret_file.readline().rstrip()
423 if try_base_url is not None and len(try_base_url) != 0: 422 if try_base_url is not None and len(try_base_url) != 0:
424 try_base_url = Mastodon.__protocolize(try_base_url) 423 try_base_url = Mastodon.__protocolize(try_base_url)
424 print(self.api_base_url, try_base_url)
425 if not (self.api_base_url is None or try_base_url == self.api_base_url): 425 if not (self.api_base_url is None or try_base_url == self.api_base_url):
426 raise MastodonIllegalArgumentError( 426 raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified')
427 'Mismatch in base URLs between files and/or specified')
428 self.api_base_url = try_base_url 427 self.api_base_url = try_base_url
429 428
430 # With new registrations we support the 4th line to store a client_name and use it as user-agent 429 # With new registrations we support the 4th line to store a client_name and use it as user-agent
@@ -433,8 +432,7 @@ class Mastodon:
433 self.user_agent = client_name.rstrip() 432 self.user_agent = client_name.rstrip()
434 else: 433 else:
435 if self.client_secret is None: 434 if self.client_secret is None:
436 raise MastodonIllegalArgumentError( 435 raise MastodonIllegalArgumentError('Specified client id directly, but did not supply secret')
437 'Specified client id directly, but did not supply secret')
438 436
439 if self.access_token is not None and os.path.isfile(self.access_token): 437 if self.access_token is not None and os.path.isfile(self.access_token):
440 with open(self.access_token, 'r') as token_file: 438 with open(self.access_token, 'r') as token_file:
@@ -444,10 +442,14 @@ class Mastodon:
444 if try_base_url is not None and len(try_base_url) != 0: 442 if try_base_url is not None and len(try_base_url) != 0:
445 try_base_url = Mastodon.__protocolize(try_base_url) 443 try_base_url = Mastodon.__protocolize(try_base_url)
446 if not (self.api_base_url is None or try_base_url == self.api_base_url): 444 if not (self.api_base_url is None or try_base_url == self.api_base_url):
447 raise MastodonIllegalArgumentError( 445 raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified')
448 'Mismatch in base URLs between files and/or specified')
449 self.api_base_url = try_base_url 446 self.api_base_url = try_base_url
450 447
448 # Verify we have a base URL, protocolize
449 if self.api_base_url is None:
450 raise MastodonIllegalArgumentError("API base URL is required.")
451 self.api_base_url = Mastodon.__protocolize(self.api_base_url)
452
451 if not version_check_mode in ["created", "changed", "none"]: 453 if not version_check_mode in ["created", "changed", "none"]:
452 raise MastodonIllegalArgumentError("Invalid version check method.") 454 raise MastodonIllegalArgumentError("Invalid version check method.")
453 self.version_check_mode = version_check_mode 455 self.version_check_mode = version_check_mode
@@ -470,6 +472,13 @@ class Mastodon:
470 if ratelimit_method not in ["throw", "wait", "pace"]: 472 if ratelimit_method not in ["throw", "wait", "pace"]:
471 raise MastodonIllegalArgumentError("Invalid ratelimit method.") 473 raise MastodonIllegalArgumentError("Invalid ratelimit method.")
472 474
475 def set_language(self, lang):
476 """
477 Set the locale Mastodon will use to generate responses. Valid parameters are all ISO 639-1 (two letter) or, for languages that do
478 not have one, 639-3 (three letter) language codes. This affects some error messages (those related to validation) and trends.
479 """
480 self.lang = lang
481
473 def __normalize_version_string(self, version_string): 482 def __normalize_version_string(self, version_string):
474 # Split off everything after the first space, to take care of Pleromalikes so that the parser doesn't get confused in case those have a + somewhere in their version 483 # Split off everything after the first space, to take care of Pleromalikes so that the parser doesn't get confused in case those have a + somewhere in their version
475 version_string = version_string.split(" ")[0] 484 version_string = version_string.split(" ")[0]
@@ -541,24 +550,27 @@ class Mastodon:
541 """ 550 """
542 return Mastodon.__SUPPORTED_MASTODON_VERSION 551 return Mastodon.__SUPPORTED_MASTODON_VERSION
543 552
544 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", scopes=__DEFAULT_SCOPES, force_login=False, state=None): 553 def auth_request_url(self, client_id=None, redirect_uris="urn:ietf:wg:oauth:2.0:oob", scopes=__DEFAULT_SCOPES, force_login=False, state=None, lang=None):
545 """ 554 """
546 Returns the URL that a client needs to request an OAuth grant from the server. 555 Returns the URL that a client needs to request an OAuth grant from the server.
547 556
548 To log in with OAuth, send your user to this URL. The user will then log in and 557 To log in with OAuth, send your user to this URL. The user will then log in and
549 get a code which you can pass to log_in. 558 get a code which you can pass to `log_in()`_.
550 559
551 scopes are as in `log_in()`_, redirect_uris is where the user should be redirected to 560 `scopes` are as in `log_in()`_, redirect_uris is where the user should be redirected to
552 after authentication. Note that redirect_uris must be one of the URLs given during 561 after authentication. Note that `redirect_uris` must be one of the URLs given during
553 app registration. When using urn:ietf:wg:oauth:2.0:oob, the code is simply displayed, 562 app registration. When using urn:ietf:wg:oauth:2.0:oob, the code is simply displayed,
554 otherwise it is added to the given URL as the "code" request parameter. 563 otherwise it is added to the given URL as the "code" request parameter.
555 564
556 Pass force_login if you want the user to always log in even when already logged 565 Pass force_login if you want the user to always log in even when already logged
557 into web Mastodon (i.e. when registering multiple different accounts in an app). 566 into web Mastodon (i.e. when registering multiple different accounts in an app).
558 567
559 State is the oauth `state`parameter to pass to the server. It is strongly suggested 568 `state` is the oauth `state` parameter to pass to the server. It is strongly suggested
560 to use a random, nonguessable value (i.e. nothing meaningful and no incrementing ID) 569 to use a random, nonguessable value (i.e. nothing meaningful and no incrementing ID)
561 to preserve security guarantees. It can be left out for non-web login flows. 570 to preserve security guarantees. It can be left out for non-web login flows.
571
572 Pass an ISO 639-1 (two letter) or, for languages that do not have one, 639-3 (three letter)
573 language code as `lang` to control the display language for the oauth form.
562 """ 574 """
563 if client_id is None: 575 if client_id is None:
564 client_id = self.client_id 576 client_id = self.client_id
@@ -574,6 +586,7 @@ class Mastodon:
574 params['scope'] = " ".join(scopes) 586 params['scope'] = " ".join(scopes)
575 params['force_login'] = force_login 587 params['force_login'] = force_login
576 params['state'] = state 588 params['state'] = state
589 params['lang'] = lang
577 formatted_params = urlencode(params) 590 formatted_params = urlencode(params)
578 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) 591 return "".join([self.api_base_url, "/oauth/authorize?", formatted_params])
579 592
@@ -675,8 +688,9 @@ class Mastodon:
675 Creates a new user account with the given username, password and email. "agreement" 688 Creates a new user account with the given username, password and email. "agreement"
676 must be set to true (after showing the user the instance's user agreement and having 689 must be set to true (after showing the user the instance's user agreement and having
677 them agree to it), "locale" specifies the language for the confirmation email as an 690 them agree to it), "locale" specifies the language for the confirmation email as an
678 ISO 639-1 (two-letter) language code. `reason` can be used to specify why a user 691 ISO 639-1 (two letter) or, if a language does not have one, 639-3 (three letter) language
679 would like to join if approved-registrations mode is on. 692 code. `reason` can be used to specify why a user would like to join if approved-registrations
693 mode is on.
680 694
681 Does not require an access token, but does require a client grant. 695 Does not require an access token, but does require a client grant.
682 696
@@ -999,7 +1013,7 @@ class Mastodon:
999 Does not require authentication for publicly visible statuses. 1013 Does not require authentication for publicly visible statuses.
1000 1014
1001 This function is deprecated as of 3.0.0 and the endpoint does not 1015 This function is deprecated as of 3.0.0 and the endpoint does not
1002 exist anymore - you should just use the "card" field of the status dicts 1016 exist anymore - you should just use the "card" field of the toot dicts
1003 instead. Mastodon.py will try to mimic the old behaviour, but this 1017 instead. Mastodon.py will try to mimic the old behaviour, but this
1004 is somewhat inefficient and not guaranteed to be the case forever. 1018 is somewhat inefficient and not guaranteed to be the case forever.
1005 1019
@@ -1540,9 +1554,16 @@ class Mastodon:
1540 ### 1554 ###
1541 # Reading data: Trends 1555 # Reading data: Trends
1542 ### 1556 ###
1543 @api_version("2.4.3", "3.0.0", __DICT_VERSION_HASHTAG) 1557 @api_version("2.4.3", "3.5.0", __DICT_VERSION_HASHTAG)
1544 def trends(self, limit=None): 1558 def trends(self, limit=None):
1545 """ 1559 """
1560 Alias for `trending_tags()`_
1561 """
1562 return self.trending_tags(limit=limit)
1563
1564 @api_version("3.5.0", "3.5.0", __DICT_VERSION_HASHTAG)
1565 def trending_tags(self, limit=None, lang=None):
1566 """
1546 Fetch trending-hashtag information, if the instance provides such information. 1567 Fetch trending-hashtag information, if the instance provides such information.
1547 1568
1548 Specify `limit` to limit how many results are returned (the maximum number 1569 Specify `limit` to limit how many results are returned (the maximum number
@@ -1551,13 +1572,49 @@ class Mastodon:
1551 Does not require authentication unless locked down by the administrator. 1572 Does not require authentication unless locked down by the administrator.
1552 1573
1553 Important versioning note: This endpoint does not exist for Mastodon versions 1574 Important versioning note: This endpoint does not exist for Mastodon versions
1554 between 2.8.0 (inclusive) and 3.0.0 (exclusive). 1575 between 2.8.0 (inclusive) and 3.0.0 (exclusive).
1576
1577 Pass `lang` to override the global locale parameter, which may affect trend ordering.
1555 1578
1556 Returns a list of `hashtag dicts`_, sorted by the instance's trending algorithm, 1579 Returns a list of `hashtag dicts`_, sorted by the instance's trending algorithm,
1557 descending. 1580 descending.
1558 """ 1581 """
1559 params = self.__generate_params(locals()) 1582 params = self.__generate_params(locals())
1560 return self.__api_request('GET', '/api/v1/trends', params) 1583 if self.verify_minimum_version("3.5.0", cached=True):
1584 # Starting 3.5.0, old version is deprecated
1585 return self.__api_request('GET', '/api/v1/trends/tags', params)
1586 else:
1587 return self.__api_request('GET', '/api/v1/trends', params)
1588
1589 @api_version("3.5.0", "3.5.0", __DICT_VERSION_STATUS)
1590 def trending_statuses(self):
1591 """
1592 Fetch trending-status information, if the instance provides such information.
1593
1594 Specify `limit` to limit how many results are returned (the maximum number
1595 of results is 10, the endpoint is not paginated).
1596
1597 Pass `lang` to override the global locale parameter, which may affect trend ordering.
1598
1599 Returns a list of `toot dicts`_, sorted by the instances's trending algorithm,
1600 descending.
1601 """
1602 params = self.__generate_params(locals())
1603 return self.__api_request('GET', '/api/v1/trends/statuses', params)
1604
1605 @api_version("3.5.0", "3.5.0", __DICT_VERSION_CARD)
1606 def trending_links(self):
1607 """
1608 Fetch trending-link information, if the instance provides such information.
1609
1610 Specify `limit` to limit how many results are returned (the maximum number
1611 of results is 10, the endpoint is not paginated).
1612
1613 Returns a list of `card dicts`_, sorted by the instances's trending algorithm,
1614 descending.
1615 """
1616 params = self.__generate_params(locals())
1617 return self.__api_request('GET', '/api/v1/trends/links', params)
1561 1618
1562 ### 1619 ###
1563 # Reading data: Lists 1620 # Reading data: Lists
@@ -1656,6 +1713,8 @@ class Mastodon:
1656 Warning: This method has now finally been removed, and will not 1713 Warning: This method has now finally been removed, and will not
1657 work on Mastodon versions 2.5.0 and above. 1714 work on Mastodon versions 2.5.0 and above.
1658 """ 1715 """
1716 if self.verify_minimum_version("2.5.0", cached=True):
1717 raise MastodonVersionError("API removed in Mastodon 2.5.0")
1659 return self.__api_request('GET', '/api/v1/reports') 1718 return self.__api_request('GET', '/api/v1/reports')
1660 1719
1661 ### 1720 ###
@@ -1943,7 +2002,8 @@ class Mastodon:
1943 displayed. 2002 displayed.
1944 2003
1945 Specify `language` to override automatic language detection. The parameter 2004 Specify `language` to override automatic language detection. The parameter
1946 accepts all valid ISO 639-2 language codes. 2005 accepts all valid ISO 639-1 (2-letter) or for languages where that do not
2006 have one, 639-3 (three letter) language codes.
1947 2007
1948 You can set `idempotency_key` to a value to uniquely identify an attempt 2008 You can set `idempotency_key` to a value to uniquely identify an attempt
1949 at posting a status. Even if you call this function more than once, 2009 at posting a status. Even if you call this function more than once,
@@ -2496,7 +2556,7 @@ class Mastodon:
2496 """ 2556 """
2497 Set a note (visible to the logged in user only) for the given account. 2557 Set a note (visible to the logged in user only) for the given account.
2498 2558
2499 Returns a `status dict`_ with the `note` updated. 2559 Returns a `toot dict`_ with the `note` updated.
2500 """ 2560 """
2501 id = self.__unpack_id(id) 2561 id = self.__unpack_id(id)
2502 params = self.__generate_params(locals(), ["id"]) 2562 params = self.__generate_params(locals(), ["id"])
@@ -2662,18 +2722,25 @@ class Mastodon:
2662 ### 2722 ###
2663 # Writing data: Reports 2723 # Writing data: Reports
2664 ### 2724 ###
2665 @api_version("1.1.0", "2.5.0", __DICT_VERSION_REPORT) 2725 @api_version("1.1.0", "3.5.0", __DICT_VERSION_REPORT)
2666 def report(self, account_id, status_ids=None, comment=None, forward=False): 2726 def report(self, account_id, status_ids=None, comment=None, forward=False, category=None, rule_ids=None):
2667 """ 2727 """
2668 Report statuses to the instances administrators. 2728 Report statuses to the instances administrators.
2669 2729
2670 Accepts a list of toot IDs associated with the report, and a comment. 2730 Accepts a list of toot IDs associated with the report, and a comment.
2671 2731
2672 Set forward to True to forward a report of a remote user to that users 2732 Starting with Mastodon 3.5.0, you can also pass a `category` (one out of
2733 "spam", "violation" or "other") and `rule_ids` (a list of rule IDs corresponding
2734 to the rules returned by the `instance()`_ API).
2735
2736 Set `forward` to True to forward a report of a remote user to that users
2673 instance as well as sending it to the instance local administrators. 2737 instance as well as sending it to the instance local administrators.
2674 2738
2675 Returns a `report dict`_. 2739 Returns a `report dict`_.
2676 """ 2740 """
2741 if category is not None and not category in ["spam", "violation", "other"]:
2742 raise MastodonIllegalArgumentError("Invalid report category (must be spam, violation or other)")
2743
2677 account_id = self.__unpack_id(account_id) 2744 account_id = self.__unpack_id(account_id)
2678 2745
2679 if status_ids is not None: 2746 if status_ids is not None:
@@ -3263,6 +3330,39 @@ class Mastodon:
3263 id = self.__unpack_id(id) 3330 id = self.__unpack_id(id)
3264 return self.__api_request('POST', '/api/v1/admin/reports/{0}/resolve'.format(id)) 3331 return self.__api_request('POST', '/api/v1/admin/reports/{0}/resolve'.format(id))
3265 3332
3333 @api_version("3.5.0", "3.5.0", __DICT_VERSION_HASHTAG)
3334 def admin_trending_tags(self, limit=None):
3335 """
3336 Admin version of `trending_tags()`_. Includes unapproved tags.
3337
3338 Returns a list of `hashtag dicts`_, sorted by the instance's trending algorithm,
3339 descending.
3340 """
3341 params = self.__generate_params(locals())
3342 return self.__api_request('GET', '/api/v1/admin/trends/tags', params)
3343
3344 @api_version("3.5.0", "3.5.0", __DICT_VERSION_STATUS)
3345 def admin_trending_statuses(self):
3346 """
3347 Admin version of `trending_statuses()`_. Includes unapproved tags.
3348
3349 Returns a list of `toot dicts`_, sorted by the instance's trending algorithm,
3350 descending.
3351 """
3352 params = self.__generate_params(locals())
3353 return self.__api_request('GET', '/api/v1/admin/trends/statuses', params)
3354
3355 @api_version("3.5.0", "3.5.0", __DICT_VERSION_CARD)
3356 def admin_trending_links(self):
3357 """
3358 Admin version of `trending_links()`_. Includes unapproved tags.
3359
3360 Returns a list of `card dicts`_, sorted by the instance's trending algorithm,
3361 descending.
3362 """
3363 params = self.__generate_params(locals())
3364 return self.__api_request('GET', '/api/v1/admin/trends/links', params)
3365
3266 @api_version("4.0.0","4.0.0","4.0.0") 3366 @api_version("4.0.0","4.0.0","4.0.0")
3267 def admin_domain_blocks(self, id:str=None, limit:int=None): 3367 def admin_domain_blocks(self, id:str=None, limit:int=None):
3268 """ 3368 """
@@ -3275,7 +3375,7 @@ class Mastodon:
3275 id = self.__unpack_id(id) 3375 id = self.__unpack_id(id)
3276 if id is not None: 3376 if id is not None:
3277 return self.__api_request('GET', '/api/v1/admin/domain_blocks/{0}'.format(id)) 3377 return self.__api_request('GET', '/api/v1/admin/domain_blocks/{0}'.format(id))
3278 else 3378 else:
3279 params = params = self.__generate_params(locals(),['limit']) 3379 params = params = self.__generate_params(locals(),['limit'])
3280 return self.__api_request('GET', '/api/v1/admin/domain_blocks/') 3380 return self.__api_request('GET', '/api/v1/admin/domain_blocks/')
3281 3381
@@ -3303,8 +3403,7 @@ class Mastodon:
3303 params = self.__generate_params(locals()) 3403 params = self.__generate_params(locals())
3304 3404
3305 3405
3306 self.__api_request( 3406 self.__api_request('POST', '/api/v1/admin/domain_blocks/', params)
3307 'POST', '/api/v1/admin/domain_blocks/, params)
3308 3407
3309 @api_version("4.0.0","4.0.0","4.0.0") 3408 @api_version("4.0.0","4.0.0","4.0.0")
3310 def admin_update_domain_block(self, id:str, severity:str=None, reject_media:bool=None, reject_reports:bool=None, private_comment:str=None, public_comment:str=None, obfuscate:bool=None): 3409 def admin_update_domain_block(self, id:str, severity:str=None, reject_media:bool=None, reject_reports:bool=None, private_comment:str=None, public_comment:str=None, obfuscate:bool=None):
@@ -3329,8 +3428,7 @@ class Mastodon:
3329 3428
3330 params = self.__generate_params(locals()) 3429 params = self.__generate_params(locals())
3331 3430
3332 self.__api_request( 3431 self.__api_request('PUT', '/api/v1/admin/domain_blocks/', params)
3333 'PUT', '/api/v1/admin/domain_blocks/, params)
3334 3432
3335 @api_version("4.0.0","4.0.0","4.0.0") 3433 @api_version("4.0.0","4.0.0","4.0.0")
3336 def admin_delete_domain_blocks(self, id:str=None): 3434 def admin_delete_domain_blocks(self, id:str=None):
@@ -3344,7 +3442,7 @@ class Mastodon:
3344 id = self.__unpack_id(id) 3442 id = self.__unpack_id(id)
3345 if id is not None: 3443 if id is not None:
3346 return self.__api_request('DELETE', '/api/v1/admin/domain_blocks/{0}'.format(id)) 3444 return self.__api_request('DELETE', '/api/v1/admin/domain_blocks/{0}'.format(id))
3347 else 3445 else:
3348 raise AttributeError("You must provide an id of an existing domain block to remove it.") 3446 raise AttributeError("You must provide an id of an existing domain block to remove it.")
3349 3447
3350 ### 3448 ###
@@ -3644,6 +3742,7 @@ class Mastodon:
3644 """ 3742 """
3645 known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", 3743 known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at",
3646 "updated_at", "last_status_at", "starts_at", "ends_at", "published_at", "edited_at"] 3744 "updated_at", "last_status_at", "starts_at", "ends_at", "published_at", "edited_at"]
3745 mark_delete = []
3647 for k, v in json_object.items(): 3746 for k, v in json_object.items():
3648 if k in known_date_fields: 3747 if k in known_date_fields:
3649 if v is not None: 3748 if v is not None:
@@ -3653,11 +3752,11 @@ class Mastodon:
3653 else: 3752 else:
3654 json_object[k] = dateutil.parser.parse(v) 3753 json_object[k] = dateutil.parser.parse(v)
3655 except: 3754 except:
3656 if isinstance(v, str) and len(x.strip()) == 0: 3755 # When we can't parse a date, we just leave the field out
3657 # Pleroma bug workaround: Empty string becomes start of epoch 3756 mark_delete.append(k)
3658 json_object[k] = datetime.datetime.fromtimestamp(0) 3757 # Two step process because otherwise python gets very upset
3659 else: 3758 for k in mark_delete:
3660 raise MastodonAPIError('Encountered invalid date.') 3759 del json_object[k]
3661 return json_object 3760 return json_object
3662 3761
3663 @staticmethod 3762 @staticmethod
@@ -3710,12 +3809,20 @@ class Mastodon:
3710 isotime = isotime[:-2] + ":" + isotime[-2:] 3809 isotime = isotime[:-2] + ":" + isotime[-2:]
3711 return isotime 3810 return isotime
3712 3811
3713 def __api_request(self, method, endpoint, params={}, files={}, headers={}, access_token_override=None, base_url_override=None, do_ratelimiting=True, use_json=False, parse=True, return_response_object=False, skip_error_check=False): 3812 def __api_request(self, method, endpoint, params={}, files={}, headers={}, access_token_override=None, base_url_override=None,
3813 do_ratelimiting=True, use_json=False, parse=True, return_response_object=False, skip_error_check=False, lang_override=None):
3714 """ 3814 """
3715 Internal API request helper. 3815 Internal API request helper.
3716 """ 3816 """
3717 response = None 3817 response = None
3718 remaining_wait = 0 3818 remaining_wait = 0
3819
3820 # Add language to params if not None
3821 lang = self.lang
3822 if lang_override is not None:
3823 lang = lang_override
3824 if lang is not None:
3825 params["lang"] = lang
3719 3826
3720 # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it 3827 # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it
3721 # would take to not hit the rate limit at that request rate. 3828 # would take to not hit the rate limit at that request rate.
@@ -3774,8 +3881,7 @@ class Mastodon:
3774 else: 3881 else:
3775 kwargs['data'] = params 3882 kwargs['data'] = params
3776 3883
3777 response_object = self.session.request( 3884 response_object = self.session.request(method, base_url + endpoint, **kwargs)
3778 method, base_url + endpoint, **kwargs)
3779 except Exception as e: 3885 except Exception as e:
3780 raise MastodonNetworkError( 3886 raise MastodonNetworkError(
3781 "Could not complete request: %s" % e) 3887 "Could not complete request: %s" % e)
@@ -3818,15 +3924,14 @@ class Mastodon:
3818 3924
3819 # Handle response 3925 # Handle response
3820 if self.debug_requests: 3926 if self.debug_requests:
3821 print('Mastodon: Response received with code ' + 3927 print('Mastodon: Response received with code ' + str(response_object.status_code) + '.')
3822 str(response_object.status_code) + '.')
3823 print('response headers: ' + str(response_object.headers)) 3928 print('response headers: ' + str(response_object.headers))
3824 print('Response text content: ' + str(response_object.text)) 3929 print('Response text content: ' + str(response_object.text))
3825 3930
3826 if not response_object.ok: 3931 if not response_object.ok:
3827 try: 3932 try:
3828 response = response_object.json( 3933 response = response_object.json(object_hook=self.__json_hooks)
3829 object_hook=self.__json_hooks) 3934 print(response)
3830 if isinstance(response, dict) and 'error' in response: 3935 if isinstance(response, dict) and 'error' in response:
3831 error_msg = response['error'] 3936 error_msg = response['error']
3832 elif isinstance(response, str): 3937 elif isinstance(response, str):
@@ -3880,8 +3985,7 @@ class Mastodon:
3880 3985
3881 if parse: 3986 if parse:
3882 try: 3987 try:
3883 response = response_object.json( 3988 response = response_object.json(object_hook=self.__json_hooks)
3884 object_hook=self.__json_hooks)
3885 except: 3989 except:
3886 raise MastodonAPIError( 3990 raise MastodonAPIError(
3887 "Could not parse response as JSON, response code was %s, " 3991 "Could not parse response as JSON, response code was %s, "
Powered by cgit v1.2.3 (git 2.41.0)