1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
|
# 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}')
|