diff options
-rw-r--r-- | TODO.md | 4 | ||||
-rw-r--r-- | docs/index.rst | 49 | ||||
-rw-r--r-- | mastodon/Mastodon.py | 139 | ||||
-rw-r--r-- | tests/cassettes/test_admin_stats.yaml | 201 | ||||
-rw-r--r-- | tests/setup.sql | 5 | ||||
-rw-r--r-- | tests/test_admin.py | 48 |
6 files changed, 436 insertions, 10 deletions
@@ -44,7 +44,7 @@ Refer to mastodon changelog and API docs for details when implementing, add or m | |||
44 | * [x] Add support for incoming edited posts | 44 | * [x] Add support for incoming edited posts |
45 | * [x] Add notifications for posts deleted by moderators <- by email. not actually API relevant. | 45 | * [x] Add notifications for posts deleted by moderators <- by email. not actually API relevant. |
46 | * [x] Add explore page with trending posts and links | 46 | * [x] Add explore page with trending posts and links |
47 | * [ ] Add graphs and retention metrics to admin dashboard | 47 | * [x] Add graphs and retention metrics to admin dashboard |
48 | * [ ] Add GET /api/v1/accounts/familiar_followers to REST API | 48 | * [ ] Add GET /api/v1/accounts/familiar_followers to REST API |
49 | * [ ] Add POST /api/v1/accounts/:id/remove_from_followers to REST API | 49 | * [ ] Add POST /api/v1/accounts/:id/remove_from_followers to REST API |
50 | * [x] Add category and rule_ids params to POST /api/v1/reports IN REST API | 50 | * [x] Add category and rule_ids params to POST /api/v1/reports IN REST API |
@@ -55,7 +55,7 @@ Refer to mastodon changelog and API docs for details when implementing, add or m | |||
55 | 55 | ||
56 | 3.5.3 | 56 | 3.5.3 |
57 | ----- | 57 | ----- |
58 | * [ ] Add limited attribute to accounts in REST API | 58 | * [later with tool to update dicts] Add limited attribute to accounts in REST API |
59 | 59 | ||
60 | 4.0.0 and beyond | 60 | 4.0.0 and beyond |
61 | ---------------- | 61 | ---------------- |
diff --git a/docs/index.rst b/docs/index.rst index 95ae0aa..e7d52dc 100644 --- a/docs/index.rst +++ b/docs/index.rst | |||
@@ -345,6 +345,18 @@ Toot dicts | |||
345 | 'poll': # A poll dict if a poll is attached to this status. | 345 | 'poll': # A poll dict if a poll is attached to this status. |
346 | } | 346 | } |
347 | 347 | ||
348 | Status edit dicts | ||
349 | ~~~~~~~~~~~~~~~~~ | ||
350 | .. _status edit dict: | ||
351 | |||
352 | .. code-block:: python | ||
353 | |||
354 | mastodonstatus_history(id)[0] | ||
355 | # Returns the following dictionary | ||
356 | { | ||
357 | TODO | ||
358 | } | ||
359 | |||
348 | Mention dicts | 360 | Mention dicts |
349 | ~~~~~~~~~~~~~ | 361 | ~~~~~~~~~~~~~ |
350 | .. _mention dict: | 362 | .. _mention dict: |
@@ -902,13 +914,37 @@ Admin domain block dicts | |||
902 | 'obfuscate': #Boolean. True if domain name is obfuscated when listing. | 914 | 'obfuscate': #Boolean. True if domain name is obfuscated when listing. |
903 | } | 915 | } |
904 | 916 | ||
905 | Status edit dicts | 917 | Admin measure dicts |
906 | ~~~~~~~~~~~~~~~~~ | 918 | ~~~~~~~~~~~~~~~~~~~ |
907 | .. _status edit dict: | 919 | .. _admin measure dict: |
908 | 920 | ||
909 | .. code-block:: python | 921 | .. code-block:: python |
910 | 922 | ||
911 | mastodonstatus_history(id)[0] | 923 | api.admin_measures(datetime.now() - timedelta(hours=24*5), datetime.now(), active_users=True) |
924 | # Returns the following dictionary | ||
925 | { | ||
926 | TODO | ||
927 | } | ||
928 | |||
929 | Admin dimension dicts | ||
930 | ~~~~~~~~~~~~~~~~~~~~~ | ||
931 | .. _admin dimension dict: | ||
932 | |||
933 | .. code-block:: python | ||
934 | |||
935 | api.admin_dimensions(datetime.now() - timedelta(hours=24*5), datetime.now(), languages=True) | ||
936 | # Returns the following dictionary | ||
937 | { | ||
938 | TODO | ||
939 | } | ||
940 | |||
941 | Admin retention dicts | ||
942 | ~~~~~~~~~~~~~~~~~~~~~ | ||
943 | .. _admin retention dict: | ||
944 | |||
945 | .. code-block:: python | ||
946 | |||
947 | api.admin_retention(datetime.now() - timedelta(hours=24*5), datetime.now()) | ||
912 | # Returns the following dictionary | 948 | # Returns the following dictionary |
913 | { | 949 | { |
914 | TODO | 950 | TODO |
@@ -1471,6 +1507,11 @@ have admin: scopes attached with a lot of care, but be extra careful with those | |||
1471 | .. automethod:: Mastodon.admin_update_domain_block | 1507 | .. automethod:: Mastodon.admin_update_domain_block |
1472 | .. automethod:: Mastodon.admin_delete_domain_block | 1508 | .. automethod:: Mastodon.admin_delete_domain_block |
1473 | 1509 | ||
1510 | .. automethod:: Mastodon.admin_measures | ||
1511 | .. automethod:: Mastodon.admin_dimensions | ||
1512 | .. automethod:: Mastodon.admin_retention | ||
1513 | |||
1514 | |||
1474 | Acknowledgements | 1515 | Acknowledgements |
1475 | ---------------- | 1516 | ---------------- |
1476 | Mastodon.py contains work by a large number of contributors, many of which have | 1517 | Mastodon.py contains work by a large number of contributors, many of which have |
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 82e058c..03aa413 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py | |||
@@ -108,7 +108,6 @@ def api_version(created_ver, last_changed_ver, return_value_ver): | |||
108 | raise MastodonVersionError( | 108 | raise MastodonVersionError( |
109 | "Version check failed (Need version " + version + ")") | 109 | "Version check failed (Need version " + version + ")") |
110 | elif major == self.mastodon_major and minor > self.mastodon_minor: | 110 | elif major == self.mastodon_major and minor > self.mastodon_minor: |
111 | print(self.mastodon_minor) | ||
112 | raise MastodonVersionError( | 111 | raise MastodonVersionError( |
113 | "Version check failed (Need version " + version + ")") | 112 | "Version check failed (Need version " + version + ")") |
114 | elif major == self.mastodon_major and minor == self.mastodon_minor and patch > self.mastodon_patch: | 113 | elif major == self.mastodon_major and minor == self.mastodon_minor and patch > self.mastodon_patch: |
@@ -264,6 +263,9 @@ class Mastodon: | |||
264 | __DICT_VERSION_ANNOUNCEMENT = bigger_version("3.1.0", __DICT_VERSION_REACTION) | 263 | __DICT_VERSION_ANNOUNCEMENT = bigger_version("3.1.0", __DICT_VERSION_REACTION) |
265 | __DICT_VERSION_STATUS_EDIT = "3.5.0" | 264 | __DICT_VERSION_STATUS_EDIT = "3.5.0" |
266 | __DICT_VERSION_ADMIN_DOMAIN_BLOCK = "4.0.0" | 265 | __DICT_VERSION_ADMIN_DOMAIN_BLOCK = "4.0.0" |
266 | __DICT_VERSION_ADMIN_MEASURE = "3.5.0" | ||
267 | __DICT_VERSION_ADMIN_DIMENSION = "3.5.0" | ||
268 | __DICT_VERSION_ADMIN_RETENTION = "3.5.0" | ||
267 | 269 | ||
268 | ### | 270 | ### |
269 | # Registering apps | 271 | # Registering apps |
@@ -432,7 +434,6 @@ class Mastodon: | |||
432 | try_base_url = secret_file.readline().rstrip() | 434 | try_base_url = secret_file.readline().rstrip() |
433 | if try_base_url is not None and len(try_base_url) != 0: | 435 | if try_base_url is not None and len(try_base_url) != 0: |
434 | try_base_url = Mastodon.__protocolize(try_base_url) | 436 | try_base_url = Mastodon.__protocolize(try_base_url) |
435 | print(self.api_base_url, try_base_url) | ||
436 | if not (self.api_base_url is None or try_base_url == self.api_base_url): | 437 | if not (self.api_base_url is None or try_base_url == self.api_base_url): |
437 | raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified') | 438 | raise MastodonIllegalArgumentError('Mismatch in base URLs between files and/or specified') |
438 | self.api_base_url = try_base_url | 439 | self.api_base_url = try_base_url |
@@ -544,7 +545,6 @@ class Mastodon: | |||
544 | We parse this from the hopefully present "Date" header, but make no effort to compensate for latency. | 545 | We parse this from the hopefully present "Date" header, but make no effort to compensate for latency. |
545 | """ | 546 | """ |
546 | response = self.__api_request("HEAD", "/", return_response_object=True) | 547 | response = self.__api_request("HEAD", "/", return_response_object=True) |
547 | print(response.headers) | ||
548 | if 'Date' in response.headers: | 548 | if 'Date' in response.headers: |
549 | server_time_datetime = dateutil.parser.parse(response.headers['Date']) | 549 | server_time_datetime = dateutil.parser.parse(response.headers['Date']) |
550 | 550 | ||
@@ -3456,6 +3456,130 @@ class Mastodon: | |||
3456 | else: | 3456 | else: |
3457 | raise AttributeError("You must provide an id of an existing domain block to remove it.") | 3457 | raise AttributeError("You must provide an id of an existing domain block to remove it.") |
3458 | 3458 | ||
3459 | @api_version("3.5.0", "3.5.0", __DICT_VERSION_ADMIN_MEASURE) | ||
3460 | def admin_measures(self, start_at, end_at, active_users=False, new_users=False, interactions=False, opened_reports = False, resolved_reports=False, | ||
3461 | tag_accounts=None, tag_uses=None, tag_servers=None, instance_accounts=None, instance_media_attachments=None, instance_reports=None, | ||
3462 | instance_statuses=None, instance_follows=None, instance_followers=None): | ||
3463 | """ | ||
3464 | Retrieves numerical instance information for the time period (at day granularity) between `start_at` and `end_at`. | ||
3465 | |||
3466 | * `active_users`: Pass true to retrieve the number of active users on your instance within the time period | ||
3467 | * `new_users`: Pass true to retrieve the number of users who joined your instance within the time period | ||
3468 | * `interactions`: Pass true to retrieve the number of interactions (favourites, boosts, replies) on local statuses within the time period | ||
3469 | * `opened_reports`: Pass true to retrieve the number of reports filed within the time period | ||
3470 | * `resolved_reports` = Pass true to retrieve the number of reports resolved within the time period | ||
3471 | * `tag_accounts`: Pass a tag ID to get the number of accounts which used that tag in at least one status within the time period | ||
3472 | * `tag_uses`: Pass a tag ID to get the number of statuses which used that tag within the time period | ||
3473 | * `tag_servers`: Pass a tag ID to to get the number of remote origin servers for statuses which used that tag within the time period | ||
3474 | * `instance_accounts`: Pass a domain to get the number of accounts originating from that remote domain within the time period | ||
3475 | * `instance_media_attachments`: Pass a domain to get the amount of space used by media attachments from that remote domain within the time period | ||
3476 | * `instance_reports`: Pass a domain to get the number of reports filed against accounts from that remote domain within the time period | ||
3477 | * `instance_statuses`: Pass a domain to get the number of statuses originating from that remote domain within the time period | ||
3478 | * `instance_follows`: Pass a domain to get the number of accounts from a remote domain followed by that local user within the time period | ||
3479 | * `instance_followers`: Pass a domain to get the number of local accounts followed by accounts from that remote domain within the time period | ||
3480 | |||
3481 | This API call is relatively expensive - watch your servers load if you want to get a lot of statistical data. Especially the instance_statuses stats | ||
3482 | might take a long time to compute and, in fact, time out. | ||
3483 | |||
3484 | There is currently no way to get tag IDs implemented in Mastodon.py, because the Mastodon public API does not implement one. This will be fixed in a future | ||
3485 | release. | ||
3486 | |||
3487 | Returns a list of `admin measure dicts`_. | ||
3488 | """ | ||
3489 | params_init = locals() | ||
3490 | keys = [] | ||
3491 | for key in ["active_users", "new_users", "interactions", "opened_reports", "resolved_reports"]: | ||
3492 | if params_init[key] == True: | ||
3493 | keys.append(key) | ||
3494 | |||
3495 | params = {} | ||
3496 | for key in ["tag_accounts", "tag_uses", "tag_servers"]: | ||
3497 | if params_init[key] is not None: | ||
3498 | keys.append(key) | ||
3499 | params[key] = {"id": self.__unpack_id(params_init[key])} | ||
3500 | for key in ["instance_accounts", "instance_media_attachments", "instance_reports", "instance_statuses", "instance_follows", "instance_followers"]: | ||
3501 | if params_init[key] is not None: | ||
3502 | keys.append(key) | ||
3503 | params[key] = {"domain": Mastodon.__deprotocolize(params_init[key]).split("/")[0]} | ||
3504 | |||
3505 | if len(keys) == 0: | ||
3506 | raise MastodonIllegalArgumentError("Must request at least one metric.") | ||
3507 | |||
3508 | params["keys"] = keys | ||
3509 | params["start_at"] = self.__consistent_isoformat_utc(start_at) | ||
3510 | params["end_at"] = self.__consistent_isoformat_utc(end_at) | ||
3511 | |||
3512 | return self.__api_request('POST', '/api/v1/admin/measures', params, use_json=True) | ||
3513 | |||
3514 | @api_version("3.5.0", "3.5.0", __DICT_VERSION_ADMIN_DIMENSION) | ||
3515 | def admin_dimensions(self, start_at, end_at, limit=None, languages=False, sources=False, servers=False, space_usage=False, software_versions=False, | ||
3516 | tag_servers=None, tag_languages=None, instance_accounts=None, instance_languages=None): | ||
3517 | """ | ||
3518 | Retrieves primarily categorical instance information for the time period (at day granularity) between `start_at` and `end_at`. | ||
3519 | |||
3520 | * `languages`: Pass true to get the most-used languages on this server | ||
3521 | * `sources`: Pass true to get the most-used client apps on this server | ||
3522 | * `servers`: Pass true to get the remote servers with the most statuses | ||
3523 | * `space_usage`: Pass true to get the how much space is used by different components your software stack | ||
3524 | * `software_versions`: Pass true to get the version numbers for your software stack | ||
3525 | * `tag_servers`: Pass a tag ID to get the most-common servers for statuses including a trending tag | ||
3526 | * `tag_languages`: Pass a tag ID to get the most-used languages for statuses including a trending tag | ||
3527 | * `instance_accounts`: Pass a domain to get the most-followed accounts from a remote server | ||
3528 | * `instance_languages`: Pass a domain to get the most-used languages from a remote server | ||
3529 | |||
3530 | Pass `limit` to set how many results you want on queries where that makes sense. | ||
3531 | |||
3532 | This API call is relatively expensive - watch your servers load if you want to get a lot of statistical data. | ||
3533 | |||
3534 | There is currently no way to get tag IDs implemented in Mastodon.py, because the Mastodon public API does not implement one. This will be fixed in a future | ||
3535 | release. | ||
3536 | |||
3537 | Returns a list of `admin dimensions dicts`_. | ||
3538 | """ | ||
3539 | params_init = locals() | ||
3540 | keys = [] | ||
3541 | for key in ["languages", "sources", "servers", "space_usage", "software_versions"]: | ||
3542 | if params_init[key] == True: | ||
3543 | keys.append(key) | ||
3544 | |||
3545 | params = {} | ||
3546 | for key in ["tag_servers", "tag_languages"]: | ||
3547 | if params_init[key] is not None: | ||
3548 | keys.append(key) | ||
3549 | params[key] = {"id": self.__unpack_id(params_init[key])} | ||
3550 | for key in ["instance_accounts", "instance_languages"]: | ||
3551 | if params_init[key] is not None: | ||
3552 | keys.append(key) | ||
3553 | params[key] = {"domain": Mastodon.__deprotocolize(params_init[key]).split("/")[0]} | ||
3554 | |||
3555 | if len(keys) == 0: | ||
3556 | raise MastodonIllegalArgumentError("Must request at least one dimension.") | ||
3557 | |||
3558 | params["keys"] = keys | ||
3559 | if limit is not None: | ||
3560 | params["limit"] = limit | ||
3561 | params["start_at"] = self.__consistent_isoformat_utc(start_at) | ||
3562 | params["end_at"] = self.__consistent_isoformat_utc(end_at) | ||
3563 | |||
3564 | return self.__api_request('POST', '/api/v1/admin/dimensions', params, use_json=True) | ||
3565 | |||
3566 | @api_version("3.5.0", "3.5.0", __DICT_VERSION_ADMIN_RETENTION) | ||
3567 | def admin_retention(self, start_at, end_at, frequency="day"): | ||
3568 | """ | ||
3569 | Gets user retention statistics (at `frequency` - "day" or "month" - granularity) between `start_at` and `end_at`. | ||
3570 | |||
3571 | Returns a list of `admin retention dicts`_ | ||
3572 | """ | ||
3573 | if not frequency in ["day", "month"]: | ||
3574 | raise MastodonIllegalArgumentError("Frequency must be day or month") | ||
3575 | |||
3576 | params = { | ||
3577 | "start_at": self.__consistent_isoformat_utc(start_at), | ||
3578 | "end_at": self.__consistent_isoformat_utc(end_at), | ||
3579 | "frequency": frequency | ||
3580 | } | ||
3581 | return self.__api_request('POST', '/api/v1/admin/retention', params) | ||
3582 | |||
3459 | ### | 3583 | ### |
3460 | # Push subscription crypto utilities | 3584 | # Push subscription crypto utilities |
3461 | ### | 3585 | ### |
@@ -3942,7 +4066,6 @@ class Mastodon: | |||
3942 | if not response_object.ok: | 4066 | if not response_object.ok: |
3943 | try: | 4067 | try: |
3944 | response = response_object.json(object_hook=self.__json_hooks) | 4068 | response = response_object.json(object_hook=self.__json_hooks) |
3945 | print(response) | ||
3946 | if isinstance(response, dict) and 'error' in response: | 4069 | if isinstance(response, dict) and 'error' in response: |
3947 | error_msg = response['error'] | 4070 | error_msg = response['error'] |
3948 | elif isinstance(response, str): | 4071 | elif isinstance(response, str): |
@@ -4348,6 +4471,14 @@ class Mastodon: | |||
4348 | base_url = base_url.rstrip("/") | 4471 | base_url = base_url.rstrip("/") |
4349 | return base_url | 4472 | return base_url |
4350 | 4473 | ||
4474 | @staticmethod | ||
4475 | def __deprotocolize(base_url): | ||
4476 | """Internal helper to strip http and https from a URL""" | ||
4477 | if base_url.startswith("http://"): | ||
4478 | base_url = base_url[7:] | ||
4479 | elif base_url.startswith("https://") or base_url.startswith("onion://"): | ||
4480 | base_url = base_url[8:] | ||
4481 | return base_url | ||
4351 | 4482 | ||
4352 | ## | 4483 | ## |
4353 | # Exceptions | 4484 | # Exceptions |
diff --git a/tests/cassettes/test_admin_stats.yaml b/tests/cassettes/test_admin_stats.yaml new file mode 100644 index 0000000..dc88b9b --- /dev/null +++ b/tests/cassettes/test_admin_stats.yaml | |||
@@ -0,0 +1,201 @@ | |||
1 | interactions: | ||
2 | - request: | ||
3 | body: '{"instance_accounts": {"domain": "chitter.xyz"}, "instance_media_attachments": | ||
4 | {"domain": "chitter.xyz"}, "instance_reports": {"domain": "chitter.xyz"}, "instance_statuses": | ||
5 | {"domain": "chitter.xyz"}, "instance_follows": {"domain": "chitter.xyz"}, "instance_followers": | ||
6 | {"domain": "chitter.xyz"}, "keys": ["active_users", "new_users", "opened_reports", | ||
7 | "resolved_reports", "instance_accounts", "instance_media_attachments", "instance_reports", | ||
8 | "instance_statuses", "instance_follows", "instance_followers"], "start_at": | ||
9 | "2022-11-22T00:42:51+00:00", "end_at": "2022-11-27T00:42:51+00:00"}' | ||
10 | headers: | ||
11 | Accept: | ||
12 | - '*/*' | ||
13 | Accept-Encoding: | ||
14 | - gzip, deflate | ||
15 | Authorization: | ||
16 | - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 | ||
17 | Connection: | ||
18 | - keep-alive | ||
19 | Content-Length: | ||
20 | - '587' | ||
21 | Content-Type: | ||
22 | - application/json | ||
23 | User-Agent: | ||
24 | - tests/v311 | ||
25 | method: POST | ||
26 | uri: http://localhost:3000/api/v1/admin/measures | ||
27 | response: | ||
28 | body: | ||
29 | string: '[{"key":"active_users","unit":null,"total":"2","previous_total":"0","data":[{"date":"2022-11-22T00:00:00Z","value":"0"},{"date":"2022-11-23T00:00:00Z","value":"0"},{"date":"2022-11-24T00:00:00Z","value":"0"},{"date":"2022-11-25T00:00:00Z","value":"0"},{"date":"2022-11-26T00:00:00Z","value":"2"}]},{"key":"new_users","unit":null,"total":"4","previous_total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"4"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"opened_reports","unit":null,"total":"0","previous_total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"resolved_reports","unit":null,"total":"0","previous_total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"instance_accounts","unit":null,"total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"instance_media_attachments","unit":"bytes","total":"0","human_value":"0 | ||
30 | Bytes","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":""},{"date":"2022-11-23T00:00:00.000+00:00","value":""},{"date":"2022-11-24T00:00:00.000+00:00","value":""},{"date":"2022-11-25T00:00:00.000+00:00","value":""},{"date":"2022-11-26T00:00:00.000+00:00","value":""},{"date":"2022-11-27T00:00:00.000+00:00","value":""}]},{"key":"instance_reports","unit":null,"total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"instance_statuses","unit":null,"total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"instance_follows","unit":null,"total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]},{"key":"instance_followers","unit":null,"total":"0","data":[{"date":"2022-11-22T00:00:00.000+00:00","value":"0"},{"date":"2022-11-23T00:00:00.000+00:00","value":"0"},{"date":"2022-11-24T00:00:00.000+00:00","value":"0"},{"date":"2022-11-25T00:00:00.000+00:00","value":"0"},{"date":"2022-11-26T00:00:00.000+00:00","value":"0"},{"date":"2022-11-27T00:00:00.000+00:00","value":"0"}]}]' | ||
31 | headers: | ||
32 | Cache-Control: | ||
33 | - no-store | ||
34 | Content-Security-Policy: | ||
35 | - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src | ||
36 | ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; | ||
37 | style-src ''self'' http://localhost:3000 ''nonce-drS6KPeE5pwtqRFGPVh3ww==''; | ||
38 | media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' | ||
39 | https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' | ||
40 | data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 | ||
41 | ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' | ||
42 | ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; | ||
43 | worker-src ''self'' blob: http://localhost:3000' | ||
44 | Content-Type: | ||
45 | - application/json; charset=utf-8 | ||
46 | ETag: | ||
47 | - W/"bb40e02b66cfdf5be1ff5a980c8242af" | ||
48 | Referrer-Policy: | ||
49 | - strict-origin-when-cross-origin | ||
50 | Transfer-Encoding: | ||
51 | - chunked | ||
52 | Vary: | ||
53 | - Accept, Origin | ||
54 | X-Content-Type-Options: | ||
55 | - nosniff | ||
56 | X-Download-Options: | ||
57 | - noopen | ||
58 | X-Frame-Options: | ||
59 | - SAMEORIGIN | ||
60 | X-Permitted-Cross-Domain-Policies: | ||
61 | - none | ||
62 | X-Request-Id: | ||
63 | - 11b2cb9c-d3c4-41ba-802d-888e1ee62c9e | ||
64 | X-Runtime: | ||
65 | - '0.540102' | ||
66 | X-XSS-Protection: | ||
67 | - 1; mode=block | ||
68 | status: | ||
69 | code: 200 | ||
70 | message: OK | ||
71 | - request: | ||
72 | body: '{"instance_accounts": {"domain": "chitter.xyz"}, "instance_languages": | ||
73 | {"domain": "chitter.xyz"}, "keys": ["languages", "sources", "servers", "space_usage", | ||
74 | "instance_accounts", "instance_languages"], "limit": 3, "start_at": "2022-11-22T00:42:52+00:00", | ||
75 | "end_at": "2022-11-27T00:42:52+00:00"}' | ||
76 | headers: | ||
77 | Accept: | ||
78 | - '*/*' | ||
79 | Accept-Encoding: | ||
80 | - gzip, deflate | ||
81 | Authorization: | ||
82 | - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 | ||
83 | Connection: | ||
84 | - keep-alive | ||
85 | Content-Length: | ||
86 | - '292' | ||
87 | Content-Type: | ||
88 | - application/json | ||
89 | User-Agent: | ||
90 | - tests/v311 | ||
91 | method: POST | ||
92 | uri: http://localhost:3000/api/v1/admin/dimensions | ||
93 | response: | ||
94 | body: | ||
95 | string: '[{"key":"languages","data":[{"key":"de","human_key":"German","value":"1"}]},{"key":"sources","data":[{"key":"web","human_key":"Website","value":"4"}]},{"key":"servers","data":[]},{"key":"space_usage","data":[{"key":"postgresql","human_key":"PostgreSQL","value":"16610095","unit":"bytes","human_value":"15.8 | ||
96 | MB"},{"key":"redis","human_key":"Redis","value":"1560216","unit":"bytes","human_value":"1.49 | ||
97 | MB"},{"key":"media","human_key":"Media storage","value":"0","unit":"bytes","human_value":"0 | ||
98 | Bytes"}]},{"key":"instance_accounts","data":[]},{"key":"instance_languages","data":[]}]' | ||
99 | headers: | ||
100 | Cache-Control: | ||
101 | - no-store | ||
102 | Content-Security-Policy: | ||
103 | - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src | ||
104 | ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; | ||
105 | style-src ''self'' http://localhost:3000 ''nonce-tTPpzIcGAZb7y2EXyfEsFg==''; | ||
106 | media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' | ||
107 | https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' | ||
108 | data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 | ||
109 | ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' | ||
110 | ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; | ||
111 | worker-src ''self'' blob: http://localhost:3000' | ||
112 | Content-Type: | ||
113 | - application/json; charset=utf-8 | ||
114 | ETag: | ||
115 | - W/"b5c3e0d37fd2fdab9859f566f5b2fa2e" | ||
116 | Referrer-Policy: | ||
117 | - strict-origin-when-cross-origin | ||
118 | Transfer-Encoding: | ||
119 | - chunked | ||
120 | Vary: | ||
121 | - Accept, Origin | ||
122 | X-Content-Type-Options: | ||
123 | - nosniff | ||
124 | X-Download-Options: | ||
125 | - noopen | ||
126 | X-Frame-Options: | ||
127 | - SAMEORIGIN | ||
128 | X-Permitted-Cross-Domain-Policies: | ||
129 | - none | ||
130 | X-Request-Id: | ||
131 | - 7332d9ca-00cb-4eaf-9d95-ee3baf8414f9 | ||
132 | X-Runtime: | ||
133 | - '0.066790' | ||
134 | X-XSS-Protection: | ||
135 | - 1; mode=block | ||
136 | status: | ||
137 | code: 200 | ||
138 | message: OK | ||
139 | - request: | ||
140 | body: start_at=2022-11-17T00%3A42%3A52%2B00%3A00&end_at=2022-11-27T00%3A42%3A52%2B00%3A00&frequency=day | ||
141 | headers: | ||
142 | Accept: | ||
143 | - '*/*' | ||
144 | Accept-Encoding: | ||
145 | - gzip, deflate | ||
146 | Authorization: | ||
147 | - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 | ||
148 | Connection: | ||
149 | - keep-alive | ||
150 | Content-Length: | ||
151 | - '97' | ||
152 | Content-Type: | ||
153 | - application/x-www-form-urlencoded | ||
154 | User-Agent: | ||
155 | - tests/v311 | ||
156 | method: POST | ||
157 | uri: http://localhost:3000/api/v1/admin/retention | ||
158 | response: | ||
159 | body: | ||
160 | string: '[{"period":"2022-11-17T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-17T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-18T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-19T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-20T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-21T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-18T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-18T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-19T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-20T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-21T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-19T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-19T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-20T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-21T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-20T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-20T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-21T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-21T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-21T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-22T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-22T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-23T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-23T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-24T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-24T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-25T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-25T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-26T00:00:00+00:00","rate":0.0,"value":"0"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-26T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-26T00:00:00+00:00","rate":0.5,"value":"2"},{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]},{"period":"2022-11-27T00:00:00+00:00","frequency":"day","data":[{"date":"2022-11-27T00:00:00+00:00","rate":0.0,"value":"0"}]}]' | ||
161 | headers: | ||
162 | Cache-Control: | ||
163 | - no-store | ||
164 | Content-Security-Policy: | ||
165 | - 'base-uri ''none''; default-src ''none''; frame-ancestors ''none''; font-src | ||
166 | ''self'' http://localhost:3000; img-src ''self'' https: data: blob: http://localhost:3000; | ||
167 | style-src ''self'' http://localhost:3000 ''nonce-AkyM5KkEra/OBSMZSu3SqQ==''; | ||
168 | media-src ''self'' https: data: http://localhost:3000; frame-src ''self'' | ||
169 | https:; manifest-src ''self'' http://localhost:3000; connect-src ''self'' | ||
170 | data: blob: http://localhost:3000 http://localhost:3000 ws://localhost:4000 | ||
171 | ws://localhost:3035 http://localhost:3035; script-src ''self'' ''unsafe-inline'' | ||
172 | ''unsafe-eval'' http://localhost:3000; child-src ''self'' blob: http://localhost:3000; | ||
173 | worker-src ''self'' blob: http://localhost:3000' | ||
174 | Content-Type: | ||
175 | - application/json; charset=utf-8 | ||
176 | ETag: | ||
177 | - W/"c607f49eb27c19d561dab0434594a06e" | ||
178 | Referrer-Policy: | ||
179 | - strict-origin-when-cross-origin | ||
180 | Transfer-Encoding: | ||
181 | - chunked | ||
182 | Vary: | ||
183 | - Accept, Origin | ||
184 | X-Content-Type-Options: | ||
185 | - nosniff | ||
186 | X-Download-Options: | ||
187 | - noopen | ||
188 | X-Frame-Options: | ||
189 | - SAMEORIGIN | ||
190 | X-Permitted-Cross-Domain-Policies: | ||
191 | - none | ||
192 | X-Request-Id: | ||
193 | - 67a94c6b-e1fe-4c60-90c5-478c98e3e0f7 | ||
194 | X-Runtime: | ||
195 | - '0.435749' | ||
196 | X-XSS-Protection: | ||
197 | - 1; mode=block | ||
198 | status: | ||
199 | code: 200 | ||
200 | message: OK | ||
201 | version: 1 | ||
diff --git a/tests/setup.sql b/tests/setup.sql index c9e908d..1d19bc8 100644 --- a/tests/setup.sql +++ b/tests/setup.sql | |||
@@ -27,6 +27,11 @@ UPDATE users SET | |||
27 | encrypted_password = '$2a$10$8eAdhF69RiZiV0puZ.8iOOgMqBACmwJu8Z9X4CiN91iwRXbeC2jvi' | 27 | encrypted_password = '$2a$10$8eAdhF69RiZiV0puZ.8iOOgMqBACmwJu8Z9X4CiN91iwRXbeC2jvi' |
28 | WHERE email = 'mastodonpy_test_2@localhost:3000'; | 28 | WHERE email = 'mastodonpy_test_2@localhost:3000'; |
29 | 29 | ||
30 | UPDATE users SET | ||
31 | locale = 'de', | ||
32 | encrypted_password = '$2a$10$8eAdhF69RiZiV0puZ.8iOOgMqBACmwJu8Z9X4CiN91iwRXbeC2jvi' | ||
33 | WHERE email = '[email protected]'; | ||
34 | |||
30 | INSERT INTO oauth_applications ( | 35 | INSERT INTO oauth_applications ( |
31 | id, | 36 | id, |
32 | name, | 37 | name, |
diff --git a/tests/test_admin.py b/tests/test_admin.py index 887ed14..49d9876 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py | |||
@@ -1,5 +1,7 @@ | |||
1 | import pytest | 1 | import pytest |
2 | import time | 2 | import time |
3 | from datetime import datetime, timedelta | ||
4 | from mastodon import MastodonIllegalArgumentError | ||
3 | 5 | ||
4 | @pytest.mark.vcr() | 6 | @pytest.mark.vcr() |
5 | def test_admin_accounts(api2): | 7 | def test_admin_accounts(api2): |
@@ -134,3 +136,49 @@ def test_admin_domain_blocks(api2): | |||
134 | assert block3.private_comment == "jk ilu <3" | 136 | assert block3.private_comment == "jk ilu <3" |
135 | api2.admin_delete_domain_block(block2) | 137 | api2.admin_delete_domain_block(block2) |
136 | assert not block3.id in map(lambda x: x.id, api2.admin_domain_blocks()) | 138 | assert not block3.id in map(lambda x: x.id, api2.admin_domain_blocks()) |
139 | |||
140 | @pytest.mark.vcr() | ||
141 | def test_admin_stats(api2): | ||
142 | assert api2.admin_measures( | ||
143 | datetime.now() - timedelta(hours=24*5), | ||
144 | datetime.now(), | ||
145 | active_users=True, | ||
146 | new_users=True, | ||
147 | opened_reports=True, | ||
148 | resolved_reports=True, | ||
149 | instance_accounts="chitter.xyz", | ||
150 | instance_media_attachments="chitter.xyz", | ||
151 | instance_reports="http://chitter.xyz/", | ||
152 | instance_statuses="chitter.xyz", | ||
153 | instance_follows="http://chitter.xyz", | ||
154 | instance_followers="chitter.xyz", | ||
155 | #tag_accounts=0, | ||
156 | #tag_uses=0, | ||
157 | #tag_servers=0, | ||
158 | ) | ||
159 | |||
160 | assert api2.admin_dimensions( | ||
161 | datetime.now() - timedelta(hours=24*5), | ||
162 | datetime.now(), | ||
163 | limit=3, | ||
164 | languages=True, | ||
165 | sources=True, | ||
166 | servers=True, | ||
167 | space_usage=True, | ||
168 | #tag_servers=0, | ||
169 | #tag_languages=0, | ||
170 | instance_accounts="chitter.xyz", | ||
171 | instance_languages="https://chitter.xyz" | ||
172 | ) | ||
173 | |||
174 | api2.admin_retention( | ||
175 | datetime.now() - timedelta(days=10), | ||
176 | datetime.now() | ||
177 | ) | ||
178 | |||
179 | with pytest.raises(MastodonIllegalArgumentError): | ||
180 | api2.admin_retention( | ||
181 | datetime.now() - timedelta(days=10), | ||
182 | datetime.now(), | ||
183 | frequency="dayz" | ||
184 | ) \ No newline at end of file | ||