diff options
Diffstat (limited to 'mastodon')
-rw-r--r-- | mastodon/Mastodon.py | 83 |
1 files changed, 57 insertions, 26 deletions
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 6a783b4..a60994e 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py | |||
@@ -320,11 +320,9 @@ class Mastodon: | |||
320 | ### | 320 | ### |
321 | # Authentication, including constructor | 321 | # Authentication, including constructor |
322 | ### | 322 | ### |
323 | 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, |
324 | api_base_url=None, debug_requests=False, | 324 | ratelimit_method="wait", ratelimit_pacefactor=1.1, request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, |
325 | ratelimit_method="wait", ratelimit_pacefactor=1.1, | 325 | version_check_mode="created", session=None, feature_set="mainline", user_agent="mastodonpy", lang=None): |
326 | request_timeout=__DEFAULT_TIMEOUT, mastodon_version=None, | ||
327 | version_check_mode="created", session=None, feature_set="mainline", user_agent="mastodonpy"): | ||
328 | """ | 326 | """ |
329 | Create a new API wrapper instance based on the given `client_secret` and `client_id` on the | 327 | Create a new API wrapper instance based on the given `client_secret` and `client_id` on the |
330 | instance given by `api_base_url`. If you give a `client_id` and it is not a file, you must | 328 | instance given by `api_base_url`. If you give a `client_id` and it is not a file, you must |
@@ -371,6 +369,10 @@ class Mastodon: | |||
371 | 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 |
372 | 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. |
373 | 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 | |||
374 | If no other `User-Agent` is specified, "mastodonpy" will be used. | 376 | If no other `User-Agent` is specified, "mastodonpy" will be used. |
375 | """ | 377 | """ |
376 | self.api_base_url = api_base_url | 378 | self.api_base_url = api_base_url |
@@ -383,7 +385,7 @@ class Mastodon: | |||
383 | self.ratelimit_method = ratelimit_method | 385 | self.ratelimit_method = ratelimit_method |
384 | self._token_expired = datetime.datetime.now() | 386 | self._token_expired = datetime.datetime.now() |
385 | self._refresh_token = None | 387 | self._refresh_token = None |
386 | 388 | ||
387 | self.__logged_in_id = None | 389 | self.__logged_in_id = None |
388 | 390 | ||
389 | self.ratelimit_limit = 300 | 391 | self.ratelimit_limit = 300 |
@@ -406,6 +408,9 @@ class Mastodon: | |||
406 | # General defined user-agent | 408 | # General defined user-agent |
407 | self.user_agent = user_agent | 409 | self.user_agent = user_agent |
408 | 410 | ||
411 | # Save language | ||
412 | self.lang = lang | ||
413 | |||
409 | # Token loading | 414 | # Token loading |
410 | if self.client_id is not None: | 415 | if self.client_id is not None: |
411 | if os.path.isfile(self.client_id): | 416 | if os.path.isfile(self.client_id): |
@@ -467,6 +472,13 @@ class Mastodon: | |||
467 | if ratelimit_method not in ["throw", "wait", "pace"]: | 472 | if ratelimit_method not in ["throw", "wait", "pace"]: |
468 | raise MastodonIllegalArgumentError("Invalid ratelimit method.") | 473 | raise MastodonIllegalArgumentError("Invalid ratelimit method.") |
469 | 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 | |||
470 | def __normalize_version_string(self, version_string): | 482 | def __normalize_version_string(self, version_string): |
471 | # 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 |
472 | version_string = version_string.split(" ")[0] | 484 | version_string = version_string.split(" ")[0] |
@@ -538,24 +550,27 @@ class Mastodon: | |||
538 | """ | 550 | """ |
539 | return Mastodon.__SUPPORTED_MASTODON_VERSION | 551 | return Mastodon.__SUPPORTED_MASTODON_VERSION |
540 | 552 | ||
541 | 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): |
542 | """ | 554 | """ |
543 | 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. |
544 | 556 | ||
545 | 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 |
546 | get a code which you can pass to log_in. | 558 | get a code which you can pass to `log_in()`_. |
547 | 559 | ||
548 | 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 |
549 | 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 |
550 | 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, |
551 | 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. |
552 | 564 | ||
553 | 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 |
554 | 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). |
555 | 567 | ||
556 | 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 |
557 | 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) |
558 | 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. | ||
559 | """ | 574 | """ |
560 | if client_id is None: | 575 | if client_id is None: |
561 | client_id = self.client_id | 576 | client_id = self.client_id |
@@ -571,6 +586,7 @@ class Mastodon: | |||
571 | params['scope'] = " ".join(scopes) | 586 | params['scope'] = " ".join(scopes) |
572 | params['force_login'] = force_login | 587 | params['force_login'] = force_login |
573 | params['state'] = state | 588 | params['state'] = state |
589 | params['lang'] = lang | ||
574 | formatted_params = urlencode(params) | 590 | formatted_params = urlencode(params) |
575 | return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) | 591 | return "".join([self.api_base_url, "/oauth/authorize?", formatted_params]) |
576 | 592 | ||
@@ -672,8 +688,9 @@ class Mastodon: | |||
672 | 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" |
673 | 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 |
674 | 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 |
675 | 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 |
676 | 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. | ||
677 | 694 | ||
678 | 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. |
679 | 696 | ||
@@ -1542,10 +1559,10 @@ class Mastodon: | |||
1542 | """ | 1559 | """ |
1543 | Alias for `trending_tags()`_ | 1560 | Alias for `trending_tags()`_ |
1544 | """ | 1561 | """ |
1545 | return self.trending_tags(limit = limit) | 1562 | return self.trending_tags(limit=limit) |
1546 | 1563 | ||
1547 | @api_version("3.5.0", "3.5.0", __DICT_VERSION_HASHTAG) | 1564 | @api_version("3.5.0", "3.5.0", __DICT_VERSION_HASHTAG) |
1548 | def trending_tags(self, limit=None): | 1565 | def trending_tags(self, limit=None, lang=None): |
1549 | """ | 1566 | """ |
1550 | Fetch trending-hashtag information, if the instance provides such information. | 1567 | Fetch trending-hashtag information, if the instance provides such information. |
1551 | 1568 | ||
@@ -1557,6 +1574,8 @@ class Mastodon: | |||
1557 | Important versioning note: This endpoint does not exist for Mastodon versions | 1574 | Important versioning note: This endpoint does not exist for Mastodon versions |
1558 | between 2.8.0 (inclusive) and 3.0.0 (exclusive). | 1575 | between 2.8.0 (inclusive) and 3.0.0 (exclusive). |
1559 | 1576 | ||
1577 | Pass `lang` to override the global locale parameter, which may affect trend ordering. | ||
1578 | |||
1560 | 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, |
1561 | descending. | 1580 | descending. |
1562 | """ | 1581 | """ |
@@ -1575,6 +1594,8 @@ class Mastodon: | |||
1575 | Specify `limit` to limit how many results are returned (the maximum number | 1594 | Specify `limit` to limit how many results are returned (the maximum number |
1576 | of results is 10, the endpoint is not paginated). | 1595 | of results is 10, the endpoint is not paginated). |
1577 | 1596 | ||
1597 | Pass `lang` to override the global locale parameter, which may affect trend ordering. | ||
1598 | |||
1578 | Returns a list of `toot dicts`_, sorted by the instances's trending algorithm, | 1599 | Returns a list of `toot dicts`_, sorted by the instances's trending algorithm, |
1579 | descending. | 1600 | descending. |
1580 | """ | 1601 | """ |
@@ -1981,7 +2002,8 @@ class Mastodon: | |||
1981 | displayed. | 2002 | displayed. |
1982 | 2003 | ||
1983 | Specify `language` to override automatic language detection. The parameter | 2004 | Specify `language` to override automatic language detection. The parameter |
1984 | 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. | ||
1985 | 2007 | ||
1986 | 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 |
1987 | 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, |
@@ -3638,6 +3660,7 @@ class Mastodon: | |||
3638 | """ | 3660 | """ |
3639 | known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", | 3661 | known_date_fields = ["created_at", "week", "day", "expires_at", "scheduled_at", |
3640 | "updated_at", "last_status_at", "starts_at", "ends_at", "published_at", "edited_at"] | 3662 | "updated_at", "last_status_at", "starts_at", "ends_at", "published_at", "edited_at"] |
3663 | mark_delete = [] | ||
3641 | for k, v in json_object.items(): | 3664 | for k, v in json_object.items(): |
3642 | if k in known_date_fields: | 3665 | if k in known_date_fields: |
3643 | if v is not None: | 3666 | if v is not None: |
@@ -3648,7 +3671,10 @@ class Mastodon: | |||
3648 | json_object[k] = dateutil.parser.parse(v) | 3671 | json_object[k] = dateutil.parser.parse(v) |
3649 | except: | 3672 | except: |
3650 | # When we can't parse a date, we just leave the field out | 3673 | # When we can't parse a date, we just leave the field out |
3651 | del json_object[k] | 3674 | mark_delete.append(k) |
3675 | # Two step process because otherwise python gets very upset | ||
3676 | for k in mark_delete: | ||
3677 | del json_object[k] | ||
3652 | return json_object | 3678 | return json_object |
3653 | 3679 | ||
3654 | @staticmethod | 3680 | @staticmethod |
@@ -3701,12 +3727,20 @@ class Mastodon: | |||
3701 | isotime = isotime[:-2] + ":" + isotime[-2:] | 3727 | isotime = isotime[:-2] + ":" + isotime[-2:] |
3702 | return isotime | 3728 | return isotime |
3703 | 3729 | ||
3704 | 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): | 3730 | def __api_request(self, method, endpoint, params={}, files={}, headers={}, access_token_override=None, base_url_override=None, |
3731 | do_ratelimiting=True, use_json=False, parse=True, return_response_object=False, skip_error_check=False, lang_override=None): | ||
3705 | """ | 3732 | """ |
3706 | Internal API request helper. | 3733 | Internal API request helper. |
3707 | """ | 3734 | """ |
3708 | response = None | 3735 | response = None |
3709 | remaining_wait = 0 | 3736 | remaining_wait = 0 |
3737 | |||
3738 | # Add language to params if not None | ||
3739 | lang = self.lang | ||
3740 | if lang_override is not None: | ||
3741 | lang = lang_override | ||
3742 | if lang is not None: | ||
3743 | params["lang"] = lang | ||
3710 | 3744 | ||
3711 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it | 3745 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it |
3712 | # would take to not hit the rate limit at that request rate. | 3746 | # would take to not hit the rate limit at that request rate. |
@@ -3765,8 +3799,7 @@ class Mastodon: | |||
3765 | else: | 3799 | else: |
3766 | kwargs['data'] = params | 3800 | kwargs['data'] = params |
3767 | 3801 | ||
3768 | response_object = self.session.request( | 3802 | response_object = self.session.request(method, base_url + endpoint, **kwargs) |
3769 | method, base_url + endpoint, **kwargs) | ||
3770 | except Exception as e: | 3803 | except Exception as e: |
3771 | raise MastodonNetworkError( | 3804 | raise MastodonNetworkError( |
3772 | "Could not complete request: %s" % e) | 3805 | "Could not complete request: %s" % e) |
@@ -3809,15 +3842,14 @@ class Mastodon: | |||
3809 | 3842 | ||
3810 | # Handle response | 3843 | # Handle response |
3811 | if self.debug_requests: | 3844 | if self.debug_requests: |
3812 | print('Mastodon: Response received with code ' + | 3845 | print('Mastodon: Response received with code ' + str(response_object.status_code) + '.') |
3813 | str(response_object.status_code) + '.') | ||
3814 | print('response headers: ' + str(response_object.headers)) | 3846 | print('response headers: ' + str(response_object.headers)) |
3815 | print('Response text content: ' + str(response_object.text)) | 3847 | print('Response text content: ' + str(response_object.text)) |
3816 | 3848 | ||
3817 | if not response_object.ok: | 3849 | if not response_object.ok: |
3818 | try: | 3850 | try: |
3819 | response = response_object.json( | 3851 | response = response_object.json(object_hook=self.__json_hooks) |
3820 | object_hook=self.__json_hooks) | 3852 | print(response) |
3821 | if isinstance(response, dict) and 'error' in response: | 3853 | if isinstance(response, dict) and 'error' in response: |
3822 | error_msg = response['error'] | 3854 | error_msg = response['error'] |
3823 | elif isinstance(response, str): | 3855 | elif isinstance(response, str): |
@@ -3871,8 +3903,7 @@ class Mastodon: | |||
3871 | 3903 | ||
3872 | if parse: | 3904 | if parse: |
3873 | try: | 3905 | try: |
3874 | response = response_object.json( | 3906 | response = response_object.json(object_hook=self.__json_hooks) |
3875 | object_hook=self.__json_hooks) | ||
3876 | except: | 3907 | except: |
3877 | raise MastodonAPIError( | 3908 | raise MastodonAPIError( |
3878 | "Could not parse response as JSON, response code was %s, " | 3909 | "Could not parse response as JSON, response code was %s, " |