From bba6168f2b1a9152dd3b925f4de4c0587bce175b Mon Sep 17 00:00:00 2001 From: hkc Date: Sun, 28 Aug 2022 01:05:14 +0300 Subject: [PATCH] Added a all other filters --- mastoposter/filters/__init__.py | 10 ++++++ mastoposter/filters/media.py | 27 ++++++++++++++++ mastoposter/filters/spoiler.py | 13 ++++++++ mastoposter/filters/text.py | 51 +++++++++++++++++++++++++++++++ mastoposter/filters/visibility.py | 12 ++++++++ mastoposter/types.py | 2 ++ 6 files changed, 115 insertions(+) create mode 100644 mastoposter/filters/media.py create mode 100644 mastoposter/filters/spoiler.py create mode 100644 mastoposter/filters/text.py create mode 100644 mastoposter/filters/visibility.py diff --git a/mastoposter/filters/__init__.py b/mastoposter/filters/__init__.py index 3229002..96d87a4 100644 --- a/mastoposter/filters/__init__.py +++ b/mastoposter/filters/__init__.py @@ -1,4 +1,14 @@ +from typing import List + +from mastoposter.types import Status from .base import BaseFilter # NOQA from mastoposter.filters.boost import BoostFilter # NOQA from mastoposter.filters.combined import CombinedFilter # NOQA from mastoposter.filters.mention import MentionFilter # NOQA +from mastoposter.filters.media import MediaFilter # NOQA +from mastoposter.filters.text import TextFilter # NOQA +from mastoposter.filters.spoiler import SpoilerFilter # NOQA + + +def run_filters(filters: List[BaseFilter], status: Status) -> bool: + return all((fil(status) for fil in filters)) diff --git a/mastoposter/filters/media.py b/mastoposter/filters/media.py new file mode 100644 index 0000000..3aa1557 --- /dev/null +++ b/mastoposter/filters/media.py @@ -0,0 +1,27 @@ +from configparser import SectionProxy +from typing import Set +from mastoposter.filters.base import BaseFilter +from mastoposter.types import Status + + +class MediaFilter(BaseFilter, filter_name="media"): + def __init__(self, section: SectionProxy): + super().__init__(section) + self.valid_media: Set[str] = set(section.get("valid_media").split()) + self.mode = section.get("mode", "include") + if self.mode not in ("include", "exclude", "only"): + raise ValueError(f"{self.mode=} is not valid") + + def __call__(self, status: Status) -> bool: + if not status.media_attachments: + return False + + types: Set[str] = {a.type for a in status.media_attachments} + + if self.mode == "include": + return len(types & self.valid_media) > 0 + elif self.mode == "exclude": + return len(types & self.valid_media) == 0 + elif self.mode == "only": + return len((types ^ self.valid_media) & types) == 0 + raise ValueError(f"{self.mode=} is not valid") diff --git a/mastoposter/filters/spoiler.py b/mastoposter/filters/spoiler.py new file mode 100644 index 0000000..dc6bfae --- /dev/null +++ b/mastoposter/filters/spoiler.py @@ -0,0 +1,13 @@ +from configparser import SectionProxy +from re import Pattern, compile as regexp +from mastoposter.filters.base import BaseFilter +from mastoposter.types import Status + + +class SpoilerFilter(BaseFilter, filter_name="spoiler"): + def __init__(self, section: SectionProxy): + super().__init__(section) + self.regexp: Pattern = regexp(section["regexp"]) + + def __call__(self, status: Status) -> bool: + return self.regexp.match(status.spoiler_text) is not None diff --git a/mastoposter/filters/text.py b/mastoposter/filters/text.py new file mode 100644 index 0000000..a18a235 --- /dev/null +++ b/mastoposter/filters/text.py @@ -0,0 +1,51 @@ +from configparser import SectionProxy +from re import Pattern, compile as regexp +from typing import Optional, Set + +from bs4 import BeautifulSoup, PageElement, Tag +from mastoposter.filters.base import BaseFilter +from mastoposter.types import Status + + +class TextFilter(BaseFilter, filter_name="content"): + def __init__(self, section: SectionProxy): + super().__init__(section) + self.mode = section["mode"] + self.tags: Set[str] = set() + self.regexp: Optional[Pattern] = None + + if self.mode == "regexp": + self.regexp = regexp(section["regexp"]) + elif self.mode == "hashtag": + self.tags = set(section["tags"].split()) + else: + raise ValueError(f"Invalid filter mode {self.mode}") + + @classmethod + def node_to_text(cls, el: PageElement) -> str: + if isinstance(el, Tag): + if el.name == "br": + return "\n" + elif el.name == "p": + return ( + str.join("", map(cls.node_to_text, el.children)) + "\n\n" + ) + return str.join("", map(cls.node_to_text, el.children)) + return str(el) + + @classmethod + def html_to_plain(cls, html: str) -> str: + soup = BeautifulSoup(html, "lxml") + return cls.node_to_text(soup).rstrip() + + def __call__(self, status: Status) -> bool: + source = status.reblog or status + if self.regexp is not None: + return ( + self.regexp.match(self.html_to_plain(source.content)) + is not None + ) + elif self.tags: + return len(self.tags & {t.name for t in source.tags}) > 0 + else: + raise ValueError("Neither regexp or tags were set. Why?") diff --git a/mastoposter/filters/visibility.py b/mastoposter/filters/visibility.py new file mode 100644 index 0000000..160ad63 --- /dev/null +++ b/mastoposter/filters/visibility.py @@ -0,0 +1,12 @@ +from configparser import SectionProxy +from mastoposter.filters.base import BaseFilter +from mastoposter.types import Status + + +class VisibilityFilter(BaseFilter, filter_name="visibility"): + def __init__(self, section: SectionProxy): + super().__init__(section) + self.options = tuple(section["options"].split()) + + def __call__(self, status: Status) -> bool: + return status.visibility in self.options diff --git a/mastoposter/types.py b/mastoposter/types.py index 7815deb..73376e9 100644 --- a/mastoposter/types.py +++ b/mastoposter/types.py @@ -263,6 +263,7 @@ class Status: favourites_count: int replies_count: int mentions: List[Mention] + tags: List[Tag] application: Optional[Application] = None url: Optional[str] = None in_reply_to_id: Optional[str] = None @@ -300,6 +301,7 @@ class Status: language=data.get("language"), text=data.get("text"), mentions=[Mention.from_dict(m) for m in data.get("mentions", [])], + tags=[Tag.from_dict(m) for m in data.get("tags", [])], ) @property