diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35eafc9..42a90a8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -44,7 +44,7 @@ v2.0.0 (IN PROGRESS) * Add notification policy and requests (`notifications_policy`, `update_notifications_policy`, `notifications_requests`, `notification_request`, `accept_notification_request`, `reject_notification_request`, `notifications_merged`, `accept_multiple_notification_requests`, `dismiss_multiple_notification_requests`) * Add `instance_languages` * Add notification grouping (`grouped_notifications`, `grouped_notification`, `dismiss_grouped_notification`, `grouped_notification_accounts`, `unread_grouped_notifications_count`) - +* Add v2 filer API (`filters_v2`, `filter_v2`, `create_filter_v2`, `update_filter_v2`, `delete_filter_v2`, `filter_keywords_v2`, `add_filter_keyword_v2`, `delete_filter_keyword_v2`, `filter_statuses_v2`, `add_filter_status_v2`, `filter_status_v2`, `delete_filter_status_v2`) v1.8.1 ------ diff --git a/TODO.md b/TODO.md index 48c458d..48b7f7a 100644 --- a/TODO.md +++ b/TODO.md @@ -6,10 +6,6 @@ Refer to mastodon changelog and API docs for details when implementing, add or m ----------- * [ ] to be collected here -4.3.0 TODOs ------------ -* ????? - General improvements that would be good to do before doing another release -------------------------------------------------------------------------- * [ ] Get test coverage like, real high diff --git a/docs/09_notifications.rst b/docs/09_notifications.rst index 666de52..f278678 100644 --- a/docs/09_notifications.rst +++ b/docs/09_notifications.rst @@ -45,26 +45,26 @@ to accept or reject notification requests for filtered notifications. .. automethod:: Mastodon.notifications_merged -Keyword filters ---------------- +Keyword Filters (v2) +-------------------- These functions allow you to get information about keyword filters as well as to create and update filters. -**Very Important Note: The filtering system was revised in 4.0.0. This means that these functions will now not work anymore if an instance is on Mastodon 4.0.0 or above. -When updating Mastodon.py for 4.0.0, we'll make an effort to emulate old behaviour, but this will not always be possible. Consider these methods deprecated, for now.** +NB: The filters are checked server side, but the server still returns all statuses to the client, just with +a `filtered` attribute. Filtered notifications most likely end up as notification requests, but I have not +validated this. -The filters are applied to everything - notifications, timeline, .... - -Reading -~~~~~~~ -.. automethod:: Mastodon.filters -.. automethod:: Mastodon.filter -.. automethod:: Mastodon.filters_apply - -Writing -~~~~~~~ -.. automethod:: Mastodon.filter_create -.. automethod:: Mastodon.filter_update -.. automethod:: Mastodon.filter_delete +.. automethod:: Mastodon.filters_v2 +.. automethod:: Mastodon.filter_v2 +.. automethod:: Mastodon.create_filter_v2 +.. automethod:: Mastodon.update_filter_v2 +.. automethod:: Mastodon.delete_filter_v2 +.. automethod:: Mastodon.filter_keywords_v2 +.. automethod:: Mastodon.add_filter_keyword_v2 +.. automethod:: Mastodon.delete_filter_keyword_v2 +.. automethod:: Mastodon.filter_statuses_v2 +.. automethod:: Mastodon.add_filter_status_v2 +.. automethod:: Mastodon.filter_status_v2 +.. automethod:: Mastodon.delete_filter_status_v2 Push notifications @@ -119,4 +119,22 @@ This is a minimal usage example for the push API, including a small http server pass finally: httpd.server_close() - api.push_subscription_delete() \ No newline at end of file + api.push_subscription_delete() + +Keyword filters (v1, deprecated) +-------------------------------- +These functions allow you to get information about keyword filters as well as to create and update filters. + +These APIs are deprecated in favor of the v2 APIs - I would recommend using those instead. + +Reading +~~~~~~~ +.. automethod:: Mastodon.filters +.. automethod:: Mastodon.filter +.. automethod:: Mastodon.filters_apply + +Writing +~~~~~~~ +.. automethod:: Mastodon.filter_create +.. automethod:: Mastodon.filter_update +.. automethod:: Mastodon.filter_delete \ No newline at end of file diff --git a/mastodon/filters.py b/mastodon/filters.py index 5b5a155..3966ace 100644 --- a/mastodon/filters.py +++ b/mastodon/filters.py @@ -6,10 +6,10 @@ from mastodon.errors import MastodonIllegalArgumentError from mastodon.utility import api_version from mastodon.internals import Mastodon as Internals -from mastodon.return_types import Filter, FilterV2, Status, Notification +from mastodon.return_types import Filter, FilterV2, Status, Notification, FilterKeyword, FilterStatus from mastodon.types_base import PaginatableList, NonPaginatableList, IdType -from typing import Union, Optional +from typing import Union, Optional, List, Dict class Mastodon(Internals): @@ -17,22 +17,20 @@ class Mastodon(Internals): # Reading data: Keyword filters ### @api_version("2.4.3", "2.4.3") - def filters(self) -> Union[NonPaginatableList[Filter], NonPaginatableList[FilterV2]]: + def filters(self) -> NonPaginatableList[Filter]: """ Fetch all of the logged-in user's filters. """ return self.__api_request('GET', '/api/v1/filters') @api_version("2.4.3", "2.4.3") - def filter(self, id: Union[Filter, FilterV2, IdType]) -> Union[Filter, FilterV2]: + def filter(self, id: Union[Filter, IdType]) -> Filter: """ Fetches information about the filter with the specified `id`. """ id = self.__unpack_id(id) return self.__api_request('GET', f'/api/v1/filters/{id}') - # TODO: Add v2 filter support - # TODO: test this properly @api_version("2.4.3", "2.4.3") def filters_apply(self, objects: Union[PaginatableList[Status], PaginatableList[Notification]], filters: Union[NonPaginatableList[Filter], NonPaginatableList[FilterV2]], context: str) -> Union[PaginatableList[Status], PaginatableList[Notification]]: """ @@ -41,6 +39,9 @@ class Mastodon(Internals): apply all filters that match the context provided in `context`, i.e. if you want to apply only notification-relevant filters, specify 'notifications'. Valid contexts are 'home', 'notifications', 'public' and 'thread'. + + NB: This is for v1 filters. v2 filters are applied by the server, which adds the "filtered" + attribute to filtered statuses. """ # Build filter regex filter_strings = [] @@ -71,7 +72,7 @@ class Mastodon(Internals): # Writing data: Keyword filters ### @api_version("2.4.3", "2.4.3") - def filter_create(self, phrase: str, context: str, irreversible: bool = False, whole_word: bool = True, expires_in: Optional[int] = None) -> Union[Filter, FilterV2]: + def filter_create(self, phrase: str, context: str, irreversible: bool = False, whole_word: bool = True, expires_in: Optional[int] = None) -> Filter: """ Creates a new keyword filter. `phrase` is the phrase that should be filtered out, `context` specifies from where to filter the keywords. @@ -97,7 +98,7 @@ class Mastodon(Internals): return self.__api_request('POST', '/api/v1/filters', params) @api_version("2.4.3", "2.4.3") - def filter_update(self, id: Union[Filter, FilterV2, IdType], phrase: Optional[str] = None, context: Optional[str] = None, irreversible: Optional[bool] = None, whole_word: Optional[bool] = None, expires_in: Optional[int] = None) -> Union[Filter, FilterV2]: + def filter_update(self, id: Union[Filter, IdType], phrase: Optional[str] = None, context: Optional[str] = None, irreversible: Optional[bool] = None, whole_word: Optional[bool] = None, expires_in: Optional[int] = None) -> Filter: """ Updates the filter with the given `id`. Parameters are the same as in `filter_create()`. @@ -115,3 +116,150 @@ class Mastodon(Internals): """ id = self.__unpack_id(id) self.__api_request('DELETE', f'/api/v1/filters/{id}') + + ### + # Filters v2 api + ### + @api_version("4.0.0", "4.0.0") + def filters_v2(self) -> NonPaginatableList[FilterV2]: + """ + Fetch all filters for the authenticated user. + """ + return self.__api_request('GET', '/api/v2/filters') + + @api_version("4.0.0", "4.0.0") + def filter_v2(self, filter_id: Union[Filter, IdType]) -> Filter: + """ + Fetch a specific filter by its ID. + """ + filter_id = self.__unpack_id(filter_id) + return self.__api_request('GET', f'/api/v2/filters/{filter_id}') + + @api_version("4.0.0", "4.0.0") + def create_filter_v2( + self, + title: str, + context: List[str], + filter_action: str, + expires_in: Optional[int] = None, + keywords_attributes: Optional[List[Dict[str, Union[str, bool]]]] = None + ) -> FilterV2: + """ + Create a new filter with the given parameters. + + `title` is a human readable name for the filter. + + `context` is list of contexts where the filter should apply. Valid values are: + - "home": Filter applies to the home timeline. + - "notifications": Filter applies to notifications. Filtered notifications land in notification requests. + - "public": Filter applies to the public timelines. + - "thread": Filter applies to conversations. + - "account": Filter applies to account timelines. + + `filter_action` gives the policy to be applied when the filter is matched. Valid values are: + - "warn": The user is warned if the content matches the filter. + - "hide": The content is completely hidden if it matches the filter. + NB: Even if you specify "hide", the status will still be returned - it will just have the "filtered" attribute set. + + pass a number of seconds as `expires_in` to make the filter expire in that many seconds. Use None for no expiration. + + pass a list of keyword dicts to initially as `keywords_attributes`, each with the following values: + - "keyword": The term to filter on. + - "whole_word": Whether word boundaries should be considered. + """ + params = self.__generate_params(locals(), for_json=True) + return self.__api_request('POST', '/api/v2/filters', params, use_json=True) + + @api_version("4.0.0", "4.0.0") + def update_filter_v2( + self, + filter_id: Union[FilterV2, IdType], + title: Optional[str] = None, + context: Optional[List[str]] = None, + filter_action: Optional[str] = None, + expires_in: Optional[int] = None, + keywords_attributes: Optional[List[Dict[str, Union[str, bool, int]]]] = None + ) -> FilterV2: + """ + Update an existing filter with the given parameters. + + Parameters are as in `create_filter_v2()`. Only the parameters you want to update need to be provided. + """ + filter_id = self.__unpack_id(filter_id) + params = self.__generate_params(locals(), for_json=True) + return self.__api_request('PUT', f'/api/v2/filters/{filter_id}', params, use_json=True) + + @api_version("4.0.0", "4.0.0") + def delete_filter_v2(self, filter_id: Union[FilterV2, IdType]) -> None: + """ + Delete an existing filter. + """ + filter_id = self.__unpack_id(filter_id) + self.__api_request('DELETE', f'/api/v2/filters/{filter_id}') + + @api_version("4.0.0", "4.0.0") + def filter_keywords_v2(self, filter_id: Union[FilterV2, IdType]) -> NonPaginatableList[FilterKeyword]: + """ + Fetch all keywords associated with a given filter. + """ + filter_id = self.__unpack_id(filter_id) + return self.__api_request('GET', f'/api/v2/filters/{filter_id}/keywords') + + @api_version("4.0.0", "4.0.0") + def add_filter_keyword_v2( + self, + filter_id: Union[FilterV2, IdType], + keyword: str, + whole_word: bool = False + ) -> FilterKeyword: + """ + Add a single keyword to an existing filter. + + Parameters are as in `create_filter_v2()` `keywords_attributes`. + """ + filter_id = self.__unpack_id(filter_id) + params = self.__generate_params(locals()) + return self.__api_request('POST', f'/api/v2/filters/{filter_id}/keywords', params) + + @api_version("4.0.0", "4.0.0") + def delete_filter_keyword_v2(self, keyword_id: Union[FilterKeyword, IdType]) -> None: + """ + Delete a single keyword from any filter. + """ + keyword_id = self.__unpack_id(keyword_id) + self.__api_request('DELETE', f'/api/v2/filters/keywords/{keyword_id}') + + @api_version("4.0.0", "4.0.0") + def filter_statuses_v2(self, filter_id: Union[FilterV2, IdType]) -> List[FilterStatus]: + """ + Retrieve all status-based filters for a FilterV2. + """ + filter_id = self.__unpack_id(filter_id) + return self.__api_request('GET', f'/api/v2/filters/{filter_id}/statuses') + + @api_version("4.0.0", "4.0.0") + def add_filter_status_v2(self, filter_id: Union[FilterV2, IdType], status_id: Union[Status, IdType]) -> FilterStatus: + """ + Add a status to a filter, which will then match on that status in addition to any keywords. + Includes reblogs, does not include replies. + """ + filter_id = self.__unpack_id(filter_id) + status_id = self.__unpack_id(status_id) + params = self.__generate_params({"status_id": status_id}) + return self.__api_request('POST', f'/api/v2/filters/{filter_id}/statuses', params) + + @api_version("4.0.0", "4.0.0") + def filter_status_v2(self, filter_status_id: Union[FilterStatus, IdType]) -> FilterStatus: + """ + Fetch a single status-based filter by its ID. + """ + filter_status_id = self.__unpack_id(filter_status_id) + return self.__api_request('GET', f'/api/v2/filters/statuses/{filter_status_id}') + + @api_version("4.0.0", "4.0.0") + def delete_filter_status_v2(self, filter_status_id: Union[FilterStatus, IdType]) -> None: + """ + Remove a status filter from a FilterV2. + """ + filter_status_id = self.__unpack_id(filter_status_id) + self.__api_request('DELETE', f'/api/v2/filters/statuses/{filter_status_id}') diff --git a/mastodon/internals.py b/mastodon/internals.py index 5f71fb9..f8e66a0 100644 --- a/mastodon/internals.py +++ b/mastodon/internals.py @@ -275,7 +275,7 @@ class Mastodon(): response = response_object.content # Parse link headers - if isinstance(response, list) or force_pagination and 'Link' in response_object.headers and response_object.headers['Link'] != "": + if (isinstance(response, list) or force_pagination) and 'Link' in response_object.headers and response_object.headers['Link'] != "": if not isinstance(response, PaginatableList) and not force_pagination: response = PaginatableList(response) tmp_urls = requests.utils.parse_header_links(response_object.headers['Link'].rstrip('>').replace('>,<', ',<')) diff --git a/srcgen/return_types.json b/srcgen/return_types.json index e1780ea..832ff2f 100644 --- a/srcgen/return_types.json +++ b/srcgen/return_types.json @@ -2712,7 +2712,7 @@ { "name": "Filter (v2)", "python_name": "FilterV2", - "func_call": "mastodon.filters()[0]", + "func_call": "mastodon.filters_v2()[0]", "masto_doc_link": "https://docs.joinmastodon.org/entities/Filter/", "func_call_real": null, "func_call_additional": null, @@ -8638,7 +8638,7 @@ { "name": "Filter keyword", "python_name": "FilterKeyword", - "func_call": "TODO_TO_BE_IMPLEMENTED", + "func_call": "mastodon.filters_v2()[0].keywords[0]", "func_call_real": null, "func_call_additional": null, "func_alternate_acc": null, @@ -8696,7 +8696,7 @@ { "name": "Filter status", "python_name": "FilterStatus", - "func_call": "TODO_TO_BE_IMPLEMENTED", + "func_call": "mastodon.filter_statuses_v2(mastodon.filters_v2()[0])[0]", "func_call_real": null, "func_call_additional": null, "func_alternate_acc": null, @@ -9423,7 +9423,7 @@ { "name": "Grouped Notifications Results", "python_name": "GroupedNotificationsResults", - "func_call": "TODO_TO_BE_IMPLEMENTED", + "func_call": "mastodon.grouped_notifications()", "func_call_real": null, "func_call_additional": null, "func_alternate_acc": null, @@ -9496,7 +9496,7 @@ { "name": "Partial Account With Avatar", "python_name": "PartialAccountWithAvatar", - "func_call": "TODO_TO_BE_IMPLEMENTED", + "func_call": "mastodon.grouped_notifications().partial_accounts[0]", "func_call_real": null, "func_call_additional": null, "func_alternate_acc": null, @@ -9579,7 +9579,7 @@ { "name": "Notification Group", "python_name": "NotificationGroup", - "func_call": "TODO_TO_BE_IMPLEMENTED", + "func_call": "mastodon.grouped_notifications().notification_groups[0]", "func_call_real": null, "func_call_additional": null, "func_alternate_acc": null, diff --git a/tests/cassettes/test_v2_filters_keywords.yaml b/tests/cassettes/test_v2_filters_keywords.yaml new file mode 100644 index 0000000..5fb990a --- /dev/null +++ b/tests/cassettes/test_v2_filters_keywords.yaml @@ -0,0 +1,556 @@ +interactions: +- request: + body: '{"title": "Test Filter", "context": ["home", "public"], "filter_action": + "warn", "keywords_attributes": [{"keyword": "spam", "whole_word": true}, {"keyword": + "eggs", "whole_word": false}]}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '188' + Content-Type: + - application/json + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v2/filters + response: + body: + string: '{"id":"3","title":"Test Filter","context":["home","public"],"expires_at":null,"filter_action":"warn","keywords":[{"id":"3","keyword":"spam","whole_word":true},{"id":"4","keyword":"eggs","whole_word":false}],"statuses":[]}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '221' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"ef312da2a51d3dd730869912d62e8291" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.05, sql.active_record;dur=8.10, cache_generate.active_support;dur=1.75, + cache_write.active_support;dur=0.13, instantiation.active_record;dur=0.71, + start_processing.action_controller;dur=0.00, unpermitted_parameters.action_controller;dur=0.00, + transaction.active_record;dur=6.02, cache_delete.active_support;dur=0.02, + render.active_model_serializers;dur=2.59, process_action.action_controller;dur=31.66 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.740703Z' + X-Request-Id: + - 310e16c6-d402-405c-b0d2-a9d1237f59a6 + X-Runtime: + - '0.056475' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/3 + response: + body: + string: '{"id":"3","title":"Test Filter","context":["home","public"],"expires_at":null,"filter_action":"warn","keywords":[{"id":"3","keyword":"spam","whole_word":true},{"id":"4","keyword":"eggs","whole_word":false}],"statuses":[]}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '221' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"ef312da2a51d3dd730869912d62e8291" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.78, cache_generate.active_support;dur=0.86, + cache_write.active_support;dur=0.08, instantiation.active_record;dur=0.34, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=2.11, + process_action.action_controller;dur=34.30 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.791946Z' + X-Request-Id: + - 51c60902-1535-42da-a13e-171a92121aa5 + X-Runtime: + - '0.048975' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: '{"filter_id": "3", "title": "Updated Title", "filter_action": "hide"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '69' + Content-Type: + - application/json + User-Agent: + - tests/v311 + method: PUT + uri: http://localhost:3000/api/v2/filters/3 + response: + body: + string: '{"id":"3","title":"Updated Title","context":["home","public"],"expires_at":null,"filter_action":"hide","keywords":[{"id":"3","keyword":"spam","whole_word":true},{"id":"4","keyword":"eggs","whole_word":false}],"statuses":[]}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '223' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"ef90ce7160a9385a7e513d9523756dd2" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=4.16, cache_generate.active_support;dur=0.79, + cache_write.active_support;dur=0.07, instantiation.active_record;dur=0.38, + start_processing.action_controller;dur=0.00, unpermitted_parameters.action_controller;dur=0.00, + transaction.active_record;dur=3.96, cache_delete.active_support;dur=0.02, + render.active_model_serializers;dur=2.02, process_action.action_controller;dur=37.48 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.856526Z' + X-Request-Id: + - 923a6488-e0da-442e-a610-d1ad4e20248a + X-Runtime: + - '0.052710' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/3/keywords + response: + body: + string: '[{"id":"3","keyword":"spam","whole_word":true},{"id":"4","keyword":"eggs","whole_word":false}]' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '94' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"fd65742072146847502a0a2c6a04f472" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.64, cache_generate.active_support;dur=0.68, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.31, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.21, + process_action.action_controller;dur=19.97 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.902788Z' + X-Request-Id: + - 0feb5320-7184-4733-a299-751865258133 + X-Runtime: + - '0.034602' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: filter_id=3&keyword=foo&whole_word=1 + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '36' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v2/filters/3/keywords + response: + body: + string: '{"id":"5","keyword":"foo","whole_word":true}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '44' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"1b64a2f9390c9282d3922ce7fa6a55a4" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=3.38, cache_generate.active_support;dur=0.67, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.29, + start_processing.action_controller;dur=0.00, unpermitted_parameters.action_controller;dur=0.00, + transaction.active_record;dur=2.88, cache_delete.active_support;dur=0.01, + render.active_model_serializers;dur=0.14, process_action.action_controller;dur=24.91 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.942786Z' + X-Request-Id: + - ab80df2e-e971-4de5-80e3-a8de25d2563a + X-Runtime: + - '0.039518' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/3/keywords + response: + body: + string: '[{"id":"3","keyword":"spam","whole_word":true},{"id":"4","keyword":"eggs","whole_word":false},{"id":"5","keyword":"foo","whole_word":true}]' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '139' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"a3c7206fea8e0a68cdbb63e7454aadd2" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.57, cache_generate.active_support;dur=0.67, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.27, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.24, + process_action.action_controller;dur=18.91 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.983496Z' + X-Request-Id: + - 9c69600a-f430-4d93-afdb-c541f94bc4af + X-Runtime: + - '0.032516' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - tests/v311 + method: DELETE + uri: http://localhost:3000/api/v2/filters/keywords/3 + response: + body: + string: '{}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"44136fa355b3678a1146ad16f7e8649e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.01, sql.active_record;dur=3.93, cache_generate.active_support;dur=0.54, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.62, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=3.35, + cache_delete.active_support;dur=0.02, render.active_model_serializers;dur=0.03, + process_action.action_controller;dur=28.96 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.019360Z' + X-Request-Id: + - deefa0be-2b24-4fd6-93dc-4e2f43b3bd5c + X-Runtime: + - '0.042246' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/3/keywords + response: + body: + string: '[{"id":"4","keyword":"eggs","whole_word":false},{"id":"5","keyword":"foo","whole_word":true}]' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '93' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"ae9198d796f6174ca3033d3f89b908cc" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.59, cache_generate.active_support;dur=0.90, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.28, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.20, + process_action.action_controller;dur=30.29 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.076833Z' + X-Request-Id: + - 34eb8d75-a112-421a-80af-8dc34c2a9197 + X-Runtime: + - '0.044549' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - tests/v311 + method: DELETE + uri: http://localhost:3000/api/v2/filters/3 + response: + body: + string: '{}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"44136fa355b3678a1146ad16f7e8649e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=3.85, cache_generate.active_support;dur=0.59, + cache_write.active_support;dur=0.07, instantiation.active_record;dur=0.36, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=5.77, + cache_delete.active_support;dur=0.02, render.active_model_serializers;dur=0.03, + process_action.action_controller;dur=25.98 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:45:00.114148Z' + X-Request-Id: + - b71d21d2-2e27-478c-8b5f-eb053ddac138 + X-Runtime: + - '0.039812' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +version: 1 diff --git a/tests/cassettes/test_v2_filters_statuses.yaml b/tests/cassettes/test_v2_filters_statuses.yaml new file mode 100644 index 0000000..27d501a --- /dev/null +++ b/tests/cassettes/test_v2_filters_statuses.yaml @@ -0,0 +1,559 @@ +interactions: +- request: + body: '{"title": "Status Filter Test", "context": ["home"], "filter_action": "warn"}' + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '77' + Content-Type: + - application/json + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v2/filters + response: + body: + string: '{"id":"5","title":"Status Filter Test","context":["home"],"expires_at":null,"filter_action":"warn","keywords":[],"statuses":[]}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '127' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"bafc2639f8776dc7b204e418be951a08" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.03, sql.active_record;dur=4.61, cache_generate.active_support;dur=1.26, + cache_write.active_support;dur=0.12, instantiation.active_record;dur=0.46, + start_processing.action_controller;dur=0.00, unpermitted_parameters.action_controller;dur=0.00, + transaction.active_record;dur=4.12, cache_delete.active_support;dur=0.02, + render.active_model_serializers;dur=7.12, process_action.action_controller;dur=32.97 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.442802Z' + X-Request-Id: + - df6666ea-6834-41d8-b40c-950e2fe46f32 + X-Runtime: + - '0.050382' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: status=This+is+a+test+status+for+filter%21&visibility=public + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN + Connection: + - keep-alive + Content-Length: + - '60' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v1/statuses + response: + body: + string: '{"id":"114009620185192115","created_at":"2025-02-15T19:45:45.502Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/114009620185192115","url":"http://localhost:3000/@mastodonpy_test/114009620185192115","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"\u003cp\u003eThis + is a test status for filter!\u003c/p\u003e","filtered":[],"reblog":null,"application":{"name":"Mastodon.py + test suite","website":null},"account":{"id":"114009019326978043","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"indexable":false,"group":false,"created_at":"2025-02-15T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","uri":"http://localhost:3000/users/mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":2,"last_status_at":"2025-02-15","hide_collections":null,"noindex":false,"emojis":[],"roles":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '1508' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"a87810105cba0ef1ee5b9b4df19ed227" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.07, sql.active_record;dur=12.11, cache_generate.active_support;dur=3.18, + cache_write.active_support;dur=0.24, instantiation.active_record;dur=0.50, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=12.75, + render.active_model_serializers;dur=12.94, process_action.action_controller;dur=57.88 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '295' + X-RateLimit-Reset: + - '2025-02-15T21:00:00.537289Z' + X-Request-Id: + - bfa31377-96dd-4adf-9d22-d848247ea7cb + X-Runtime: + - '0.072455' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: status_id=114009620185192115 + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '28' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - tests/v311 + method: POST + uri: http://localhost:3000/api/v2/filters/5/statuses + response: + body: + string: '{"id":"1","status_id":"114009620185192115"}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '43' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"6f22aff59bd092b2dbe5960b33c5417a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=5.94, cache_generate.active_support;dur=0.96, + cache_write.active_support;dur=0.07, instantiation.active_record;dur=0.59, + start_processing.action_controller;dur=0.00, unpermitted_parameters.action_controller;dur=0.00, + transaction.active_record;dur=10.71, cache_delete.active_support;dur=0.02, + render.active_model_serializers;dur=0.20, process_action.action_controller;dur=39.17 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.599982Z' + X-Request-Id: + - b25c0312-bd58-4e65-87cb-1de757937b8d + X-Runtime: + - '0.055019' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/5/statuses + response: + body: + string: '[{"id":"1","status_id":"114009620185192115"}]' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '45' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"8eab4711dd0ffd6329a3d42c15ef7ec5" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.74, cache_generate.active_support;dur=4.99, + cache_write.active_support;dur=0.08, instantiation.active_record;dur=0.34, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.17, + process_action.action_controller;dur=20.18 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.661225Z' + X-Request-Id: + - c90842b6-03c7-4745-94b5-4e4c12f47bf1 + X-Runtime: + - '0.039457' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/statuses/1 + response: + body: + string: '{"id":"1","status_id":"114009620185192115"}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '43' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"6f22aff59bd092b2dbe5960b33c5417a" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.90, cache_generate.active_support;dur=0.65, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.65, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.20, + process_action.action_controller;dur=20.98 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.699471Z' + X-Request-Id: + - f3e2f718-f74c-4170-9c6c-914276108d3b + X-Runtime: + - '0.035255' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + User-Agent: + - tests/v311 + method: GET + uri: http://localhost:3000/api/v2/filters/5/statuses + response: + body: + string: '[{"id":"1","status_id":"114009620185192115"}]' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '45' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"8eab4711dd0ffd6329a3d42c15ef7ec5" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=0.78, cache_generate.active_support;dur=0.77, + cache_write.active_support;dur=0.07, instantiation.active_record;dur=0.30, + start_processing.action_controller;dur=0.00, render.active_model_serializers;dur=0.16, + process_action.action_controller;dur=20.17 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.738456Z' + X-Request-Id: + - f1d09889-4335-420b-b6f0-1fe860272fcd + X-Runtime: + - '0.034827' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - tests/v311 + method: DELETE + uri: http://localhost:3000/api/v2/filters/statuses/1 + response: + body: + string: '{}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"44136fa355b3678a1146ad16f7e8649e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=3.93, cache_generate.active_support;dur=0.74, + cache_write.active_support;dur=0.07, instantiation.active_record;dur=0.37, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=3.25, + cache_delete.active_support;dur=0.02, render.active_model_serializers;dur=0.04, + process_action.action_controller;dur=24.55 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.776839Z' + X-Request-Id: + - f53a1489-e5c4-4124-b45a-4c5b07407ae1 + X-Runtime: + - '0.038984' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN_2 + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - tests/v311 + method: DELETE + uri: http://localhost:3000/api/v2/filters/5 + response: + body: + string: '{}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '2' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"44136fa355b3678a1146ad16f7e8649e" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.02, sql.active_record;dur=3.21, cache_generate.active_support;dur=0.70, + cache_write.active_support;dur=0.06, instantiation.active_record;dur=0.35, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=3.93, + cache_delete.active_support;dur=0.02, render.active_model_serializers;dur=0.02, + process_action.action_controller;dur=30.31 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '300' + X-RateLimit-Remaining: + - '299' + X-RateLimit-Reset: + - '2025-02-15T19:50:00.818776Z' + X-Request-Id: + - a9e30741-58ea-4e6b-bbad-8813bced2035 + X-Runtime: + - '0.044376' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Authorization: + - Bearer __MASTODON_PY_TEST_ACCESS_TOKEN + Connection: + - keep-alive + Content-Length: + - '0' + User-Agent: + - tests/v311 + method: DELETE + uri: http://localhost:3000/api/v1/statuses/114009620185192115 + response: + body: + string: '{"id":"114009620185192115","created_at":"2025-02-15T19:45:45.502Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"ja","uri":"http://localhost:3000/users/mastodonpy_test/statuses/114009620185192115","url":"http://localhost:3000/@mastodonpy_test/114009620185192115","replies_count":0,"reblogs_count":0,"favourites_count":0,"edited_at":null,"favourited":false,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"text":"This + is a test status for filter!","filtered":[],"reblog":null,"application":{"name":"Mastodon.py + test suite","website":null},"account":{"id":"114009019326978043","username":"mastodonpy_test","acct":"mastodonpy_test","display_name":"","locked":true,"bot":false,"discoverable":null,"indexable":false,"group":false,"created_at":"2025-02-15T00:00:00.000Z","note":"","url":"http://localhost:3000/@mastodonpy_test","uri":"http://localhost:3000/users/mastodonpy_test","avatar":"http://localhost:3000/avatars/original/missing.png","avatar_static":"http://localhost:3000/avatars/original/missing.png","header":"http://localhost:3000/headers/original/missing.png","header_static":"http://localhost:3000/headers/original/missing.png","followers_count":0,"following_count":0,"statuses_count":1,"last_status_at":"2025-02-15","hide_collections":null,"noindex":false,"emojis":[],"roles":[],"fields":[]},"media_attachments":[],"mentions":[],"tags":[],"emojis":[],"card":null,"poll":null}' + headers: + Cache-Control: + - private, no-store + Content-Length: + - '1478' + Content-Security-Policy: + - default-src 'none'; frame-ancestors 'none'; form-action 'none' + Content-Type: + - application/json; charset=utf-8 + ETag: + - W/"23a5447cefb48522ba69e630745b64ee" + Referrer-Policy: + - strict-origin-when-cross-origin + Server-Timing: + - cache_read.active_support;dur=0.05, sql.active_record;dur=6.79, cache_generate.active_support;dur=2.78, + cache_write.active_support;dur=0.13, instantiation.active_record;dur=0.65, + start_processing.action_controller;dur=0.00, transaction.active_record;dur=3.95, + render.active_model_serializers;dur=16.41, process_action.action_controller;dur=43.33 + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-RateLimit-Limit: + - '30' + X-RateLimit-Remaining: + - '29' + X-RateLimit-Reset: + - '2025-02-15T20:00:00.867159Z' + X-Request-Id: + - 238dbde5-f195-469e-871f-69bb758f2ddc + X-Runtime: + - '0.057672' + X-XSS-Protection: + - '0' + vary: + - Authorization, Origin + status: + code: 200 + message: OK +version: 1 diff --git a/tests/test_filters.py b/tests/test_filters.py index e89a9d0..123bdac 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -115,3 +115,87 @@ def test_filter_clientside(api, api2): api2.status_delete(status_2) api2.status_delete(status_3) api2.status_delete(status_4) + +@pytest.mark.vcr() +def test_v2_filters_keywords(api2): + new_filter = api2.create_filter_v2( + title="Test Filter", + context=["home", "public"], + filter_action="warn", + keywords_attributes=[ + {"keyword": "spam", "whole_word": True}, + {"keyword": "eggs", "whole_word": False} + ] + ) + assert new_filter is not None + assert new_filter["title"] == "Test Filter" + assert len(new_filter["keywords"]) == 2 + + filter_id = new_filter["id"] + + try: + fetched_filter = api2.filter_v2(filter_id) + assert fetched_filter["title"] == "Test Filter" + + updated_filter = api2.update_filter_v2( + filter_id, + title="Updated Title", + filter_action="hide" + ) + assert updated_filter["title"] == "Updated Title" + assert updated_filter["filter_action"] == "hide" + + filter_keywords = api2.filter_keywords_v2(filter_id) + assert len(filter_keywords) == 2 + keyword_ids = [kw["id"] for kw in filter_keywords] + + new_kw = api2.add_filter_keyword_v2(filter_id, keyword="foo", whole_word=True) + assert new_kw["keyword"] == "foo" + updated_keywords = api2.filter_keywords_v2(filter_id) + assert len(updated_keywords) == 3 + + api2.delete_filter_keyword_v2(keyword_ids[0]) + after_delete_keywords = api2.filter_keywords_v2(filter_id) + assert len(after_delete_keywords) == 2 + + finally: + api2.delete_filter_v2(filter_id) + +@pytest.mark.vcr() +def test_v2_filters_statuses(api2, api): + new_filter = api2.create_filter_v2( + title="Status Filter Test", + context=["home"], + filter_action="warn", + ) + assert new_filter is not None + filter_id = new_filter["id"] + + status = api.status_post("This is a test status for filter!", visibility="public") + status_id = status["id"] + + try: + filter_status = api2.add_filter_status_v2(filter_id, status_id) + assert filter_status is not None + assert filter_status["status_id"] == status_id + + statuses_list = api2.filter_statuses_v2(filter_id) + assert len(statuses_list) == 1 + assert statuses_list[0]["status_id"] == status_id + + filter_status_id = statuses_list[0]["id"] + + single_status = api2.filter_status_v2(filter_status_id) + assert single_status["id"] == filter_status_id + assert single_status["status_id"] == status_id + + finally: + statuses_list = api2.filter_statuses_v2(filter_id) + for st in statuses_list: + api2.delete_filter_status_v2(st["id"]) + + api2.delete_filter_v2(filter_id) + + # Clean up the status + if status_id: + api.status_delete(status_id)