Add initial flood prevention

This commit is contained in:
Tulir Asokan 2019-06-23 14:14:28 +03:00
parent 8e16575d3b
commit 85a7967888
3 changed files with 39 additions and 2 deletions

View file

@ -29,6 +29,10 @@ templates:
default_flags: default_flags:
- ignorecase - ignorecase
antispam:
max: 2
delay: 60
rules: rules:
twim_cookies: twim_cookies:
rooms: ["!FPUfgzXYWTKgIrwKxW:matrix.org"] rooms: ["!FPUfgzXYWTKgIrwKxW:matrix.org"]

View file

@ -13,9 +13,12 @@
# #
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Type, Tuple from typing import Type, Tuple, Dict
import time
from mautrix.types import EventType, MessageType from attr import dataclass
from mautrix.types import EventType, MessageType, UserID, RoomID
from mautrix.util.config import BaseProxyConfig from mautrix.util.config import BaseProxyConfig
from maubot import Plugin, MessageEvent from maubot import Plugin, MessageEvent
@ -24,8 +27,27 @@ from maubot.handlers import event
from .config import Config, ConfigError from .config import Config, ConfigError
@dataclass
class FloodInfo:
rb: 'ReactBot'
count: int
last_message: int
def bump(self) -> bool:
now = int(time.time())
if self.last_message + self.rb.config["antispam.delay"] < now:
self.count = 0
self.count += 1
if self.count > self.rb.config["antispam.max"]:
return True
self.last_message = now
return False
class ReactBot(Plugin): class ReactBot(Plugin):
allowed_msgtypes: Tuple[MessageType, ...] = (MessageType.TEXT, MessageType.EMOTE) allowed_msgtypes: Tuple[MessageType, ...] = (MessageType.TEXT, MessageType.EMOTE)
user_flood: Dict[UserID, FloodInfo]
room_flood: Dict[RoomID, FloodInfo]
@classmethod @classmethod
def get_config_class(cls) -> Type[BaseProxyConfig]: def get_config_class(cls) -> Type[BaseProxyConfig]:
@ -33,6 +55,8 @@ class ReactBot(Plugin):
async def start(self) -> None: async def start(self) -> None:
await super().start() await super().start()
self.user_flood = {}
self.room_flood = {}
self.on_external_config_update() self.on_external_config_update()
def on_external_config_update(self) -> None: def on_external_config_update(self) -> None:
@ -42,6 +66,11 @@ class ReactBot(Plugin):
except ConfigError: except ConfigError:
self.log.exception("Failed to load config") self.log.exception("Failed to load config")
def is_flood(self, evt: MessageEvent) -> bool:
uf = self.user_flood.setdefault(evt.sender, FloodInfo(rb=self, count=0, last_message=0))
rf = self.room_flood.setdefault(evt.room_id, FloodInfo(rb=self, count=0, last_message=0))
return uf.bump() or rf.bump()
@event.on(EventType.ROOM_MESSAGE) @event.on(EventType.ROOM_MESSAGE)
async def event_handler(self, evt: MessageEvent) -> None: async def event_handler(self, evt: MessageEvent) -> None:
if evt.sender == self.client.mxid or evt.content.msgtype not in self.allowed_msgtypes: if evt.sender == self.client.mxid or evt.content.msgtype not in self.allowed_msgtypes:
@ -49,6 +78,8 @@ class ReactBot(Plugin):
for name, rule in self.config.rules.items(): for name, rule in self.config.rules.items():
match = rule.match(evt) match = rule.match(evt)
if match is not None: if match is not None:
if self.is_flood(evt):
return
try: try:
await rule.execute(evt, match) await rule.execute(evt, match)
except Exception: except Exception:

View file

@ -37,6 +37,8 @@ class Config(BaseProxyConfig):
helper.copy("rules") helper.copy("rules")
helper.copy("templates") helper.copy("templates")
helper.copy("default_flags") helper.copy("default_flags")
helper.copy("antispam.max")
helper.copy("antispam.delay")
def parse_data(self) -> None: def parse_data(self) -> None:
self.default_flags = re.RegexFlag(0) self.default_flags = re.RegexFlag(0)