Mastodon.py/mastodon/filters.py

268 wiersze
11 KiB
Python

# filters.py - Filter-related endpoints
import re
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, FilterKeyword, FilterStatus
from mastodon.types_base import PaginatableList, NonPaginatableList, IdType
from typing import Union, Optional, List, Dict
class Mastodon(Internals):
###
# Reading data: Keyword filters
###
@api_version("2.4.3", "2.4.3")
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, 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}')
@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]]:
"""
Helper function: Applies a list of filters to a list of either statuses
or notifications and returns only those matched by none. This function will
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 = []
for keyword_filter in filters:
if not context in keyword_filter["context"]:
continue
filter_string = re.escape(keyword_filter["phrase"])
if keyword_filter["whole_word"]:
filter_string = "\\b" + filter_string + "\\b"
filter_strings.append(filter_string)
filter_re = re.compile("|".join(filter_strings), flags=re.IGNORECASE)
# Apply
filter_results = []
for filter_object in objects:
filter_status = filter_object
if "status" in filter_object:
filter_status = filter_object["status"]
filter_text = filter_status["content"]
filter_text = re.sub(r"<.*?>", " ", filter_text)
filter_text = re.sub(r"\s+", " ", filter_text).strip()
if not filter_re.search(filter_text):
filter_results.append(filter_object)
return filter_results
###
# 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) -> Filter:
"""
Creates a new keyword filter. `phrase` is the phrase that should be
filtered out, `context` specifies from where to filter the keywords.
Valid contexts are 'home', 'notifications', 'public' and 'thread'.
Set `irreversible` to True if you want the filter to just delete statuses
server side. This works only for the 'home' and 'notifications' contexts.
Set `whole_word` to False if you want to allow filter matches to
start or end within a word, not only at word boundaries.
Set `expires_in` to specify for how many seconds the filter should be
kept around.
Returns the newly created filter.
"""
params = self.__generate_params(locals())
for context_val in context:
if not context_val in ['home', 'notifications', 'public', 'thread']:
raise MastodonIllegalArgumentError('Invalid filter context.')
return self.__api_request('POST', '/api/v1/filters', params)
@api_version("2.4.3", "2.4.3")
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()`.
Returns the updated filter.
"""
id = self.__unpack_id(id)
params = self.__generate_params(locals(), ['id'])
return self.__api_request('PUT', f'/api/v1/filters/{id}', params)
@api_version("2.4.3", "2.4.3")
def filter_delete(self, id: Union[Filter, FilterV2, IdType]):
"""
Deletes the filter with the given `id`.
"""
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}')