from __future__ import annotations import enum from dataclasses import dataclass from typing import TYPE_CHECKING from files.helpers.config.const import PERMS if TYPE_CHECKING: from files.classes.user import User from files.helpers.content import Submittable class StateMod(enum.Enum): VISIBLE = 0 FILTERED = 1 REMOVED = 2 class StateReport(enum.Enum): UNREPORTED = 0 RESOLVED = 1 REPORTED = 2 IGNORED = 3 @dataclass(frozen=True, kw_only=True, slots=True) class VisibilityState: ''' The full moderation state machine. It holds the moderation state, report state, deleted information, and shadowban information. A decision to show or hide a post or comment should be able to be done with information from this alone. ''' state_mod: StateMod state_mod_set_by: str | None state_report: StateReport state_mod_set_by: str | None deleted: bool op_shadowbanned: bool op_id: int op_name_safe: str @property def removed(self) -> bool: return self.state_mod == StateMod.REMOVED @property def filtered(self) -> bool: return self.state_mod == StateMod.FILTERED @property def reports_ignored(self) -> bool: return self.state_report == StateReport.IGNORED @classmethod def from_submittable(cls, target: Submittable) -> "VisibilityState": return cls( state_mod=target.state_mod, state_mod_set_by=target.state_mod_set_by, # type: ignore state_report=target.state_report, deleted=bool(target.state_user_deleted_utc != None), op_shadowbanned=bool(target.author.shadowbanned), op_id=target.author_id, # type: ignore op_name_safe=target.author_name ) def moderated_body(self, v: User | None, is_blocking: bool=False) -> str | None: if v and (v.admin_level >= PERMS['POST_COMMENT_MODERATION'] \ or v.id == self.op_id): return None if self.deleted: return 'Deleted' if self.appear_removed(v): return 'Removed' if self.filtered: return 'Filtered' if is_blocking: return f'You are blocking @{self.op_name_safe}' return None def visibility_and_message(self, v: User | None, is_blocking: bool) -> tuple[bool, str]: ''' Returns a tuple of whether this content is visible and a publicly visible message to accompany it. The visibility state machine is a slight mess but... this should at least unify the state checks. ''' def can(v: User | None, perm_level: int) -> bool: return v and v.admin_level >= perm_level can_moderate: bool = can(v, PERMS['POST_COMMENT_MODERATION']) can_shadowban: bool = can(v, PERMS['USER_SHADOWBAN']) if v and v.id == self.op_id: return True, "This shouldn't be here, please report it!" if (self.removed and not can_moderate) or \ (self.op_shadowbanned and not can_shadowban): msg: str = 'Removed' if self.state_mod_set_by: msg = f'Removed by @{self.state_mod_set_by}' return False, msg if self.filtered and not can_moderate: return False, 'Filtered' if self.deleted and not can_moderate: return False, 'Deleted by author' if is_blocking: return False, f'You are blocking @{self.op_name_safe}' return True, "This shouldn't be here, please report it!" def is_visible_to(self, v: User | None, is_blocking: bool) -> bool: return self.visibility_and_message(v, is_blocking)[0] def replacement_message(self, v: User | None, is_blocking: bool) -> str: return self.visibility_and_message(v, is_blocking)[1] def appear_removed(self, v: User | None) -> bool: if self.removed: return True if not self.op_shadowbanned: return False return (not v) or bool(v.admin_level < PERMS['USER_SHADOWBAN']) @property def publicly_visible(self) -> bool: return all( not state for state in [self.deleted, self.removed, self.filtered, self.op_shadowbanned] ) @property def explicitly_moderated(self) -> bool: ''' Whether this was removed or filtered and not as the result of a shadowban ''' return self.removed or self.filtered