a few extra commands and cleaner code to actually purge people from space rooms

This commit is contained in:
William Kray 2023-06-11 00:03:37 -07:00
parent a4260e3543
commit 161c6f04ad

View File

@ -4,9 +4,10 @@ from typing import Awaitable, Type, Optional, Tuple
import json import json
import time import time
from mautrix.client import Client from mautrix.client import Client, InternalEventType, MembershipEventDispatcher
from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, EventType, from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, EventType,
MediaMessageEventContent, ReactionEvent, RedactionEvent) MediaMessageEventContent, ReactionEvent, RedactionEvent)
from mautrix.errors import MNotFound
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
from maubot import Plugin, MessageEvent from maubot import Plugin, MessageEvent
from maubot.handlers import command, event from maubot.handlers import command, event
@ -30,11 +31,76 @@ class KickBot(Plugin):
async def start(self) -> None: async def start(self) -> None:
await super().start() await super().start()
self.config.load_and_update() self.config.load_and_update()
self.client.add_dispatcher(MembershipEventDispatcher)
async def do_sync(self) -> None:
space_members_obj = await self.client.get_joined_members(self.config["master_room"])
space_members_list = space_members_obj.keys()
table_users = await self.database.fetch("SELECT mxid FROM user_events")
table_user_list = [ row["mxid"] for row in table_users ]
untracked_users = set(space_members_list) - set(table_user_list)
non_space_members = set(table_user_list) - set(space_members_list)
results = {}
results['added'] = []
results['dropped'] = []
try:
for user in untracked_users:
now = int(time.time() * 1000)
q = """
INSERT INTO user_events (mxid, last_message_timestamp)
VALUES ($1, $2)
"""
await self.database.execute(q, user, now)
results['added'].append(user)
self.log.info(f"{user} inserted into activity tracking table")
for user in non_space_members:
await self.database.execute("DELETE FROM user_events WHERE mxid = $1", user)
self.log.info(f"{user} is not a space member, dropped from activity tracking table")
results['dropped'].append(user)
except Exception as e:
self.log.exception(e)
return results
async def get_space_roomlist(self) -> None: async def get_space_roomlist(self) -> None:
space_state = await self.client.get_state(self.config["master_room"]) space = self.config["master_room"]
children = space_state[EventType.SPACE_CHILD] rooms = []
return children state = await self.client.get_state(space)
for evt in state:
if evt.type == EventType.SPACE_CHILD:
# only look for rooms that include a via path, otherwise they
# are not really in the space!
if evt.content and evt.content.via:
rooms.append(evt.state_key)
return rooms
async def generate_report(self) -> None:
now = int(time.time() * 1000)
warn_days_ago = (now - (1000 * 60 * 60 * 24 * self.config["warn_threshold_days"]))
kick_days_ago = (now - (1000 * 60 * 60 * 24 * self.config["kick_threshold_days"]))
warn_q = """
SELECT mxid FROM user_events WHERE last_message_timestamp <= $1 AND
last_message_timestamp >= $2
AND ignore_inactivity = 0
"""
kick_q = """
SELECT mxid FROM user_events WHERE last_message_timestamp <= $1
AND ignore_inactivity = 0
"""
warn_inactive_results = await self.database.fetch(warn_q, warn_days_ago, kick_days_ago)
kick_inactive_results = await self.database.fetch(kick_q, kick_days_ago)
report = {}
report["warn_inactive"] = [ row["mxid"] for row in warn_inactive_results ] or ["none"]
report["kick_inactive"] = [ row["mxid"] for row in kick_inactive_results ] or ["none"]
return report
@event.on(InternalEventType.JOIN)
async def passive_sync(self, evt:StateEvent) -> None:
if evt.room_id == self.config['master_room']:
await self.do_sync()
@event.on(EventType.ROOM_MESSAGE) @event.on(EventType.ROOM_MESSAGE)
async def update_message_timestamp(self, evt: MessageEvent) -> None: async def update_message_timestamp(self, evt: MessageEvent) -> None:
@ -59,45 +125,16 @@ class KickBot(Plugin):
async def activity(self) -> None: async def activity(self) -> None:
pass pass
@activity.subcommand("rooms", help="test command to get rooms in the space")
async def get_rooms(self, evt: MessageEvent) -> None:
msg = await self.get_space_roomlist
await evt.respond(msg)
@activity.subcommand("sync", help="update the activity tracker with the current space members \ @activity.subcommand("sync", help="update the activity tracker with the current space members \
in case they are missing") in case they are missing")
async def sync_space_members(self, evt: MessageEvent) -> None: async def sync_space_members(self, evt: MessageEvent) -> None:
if evt.sender in self.config["admins"]: if evt.sender in self.config["admins"]:
space_members_obj = await self.client.get_joined_members(self.config["master_room"]) results = await self.do_sync()
space_members_list = space_members_obj.keys()
table_users = await self.database.fetch("SELECT mxid FROM user_events")
table_user_list = [ row["mxid"] for row in table_users ]
untracked_users = set(space_members_list) - set(table_user_list)
non_space_members = set(table_user_list) - set(space_members_list)
added = []
dropped = []
try:
for user in untracked_users:
now = int(time.time() * 1000)
q = """
INSERT INTO user_events (mxid, last_message_timestamp)
VALUES ($1, $2)
"""
await self.database.execute(q, user, now)
added.append(user)
self.log.info(f"{user} inserted into activity tracking table")
for user in non_space_members:
await self.database.execute("DELETE FROM user_events WHERE mxid = $1", user)
self.log.info(f"{user} is not a space member, dropped from activity tracking table")
dropped.append(user)
await evt.react("")
added_str = "<br />".join(added) added_str = "<br />".join(results['added'])
dropped_str = "<br />".join(dropped) dropped_str = "<br />".join(results['dropped'])
await evt.respond(f"Added: {added_str}<br /><br />Dropped: {dropped_str}", allow_html=True) await evt.respond(f"Added: {added_str}<br /><br />Dropped: {dropped_str}", allow_html=True)
except Exception as e:
self.log.exception(e)
else: else:
await evt.reply("lol you don't have permission to do that") await evt.reply("lol you don't have permission to do that")
@ -120,7 +157,7 @@ class KickBot(Plugin):
@activity.subcommand("unignore", help="re-enable activity tracking for a specific matrix ID") @activity.subcommand("unignore", help="re-enable activity tracking for a specific matrix ID")
@command.argument("mxid", "full matrix ID", required=True) @command.argument("mxid", "full matrix ID", required=True)
async def ignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None: async def unignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
if evt.sender in self.config["admins"]: if evt.sender in self.config["admins"]:
try: try:
Client.parse_user_id(mxid) Client.parse_user_id(mxid)
@ -133,30 +170,54 @@ class KickBot(Plugin):
else: else:
await evt.reply("lol you don't have permission to set that") await evt.reply("lol you don't have permission to set that")
@activity.subcommand("snitch", help='generate a list of matrix IDs that have been inactive') @activity.subcommand("report", help='generate a list of matrix IDs that have been inactive')
async def generate_report(self, evt: MessageEvent) -> None: async def get_report(self, evt: MessageEvent) -> None:
now = int(time.time() * 1000) sync_results = await self.do_sync()
warn_days_ago = (now - (1000 * 60 * 60 * 24 * self.config["warn_threshold_days"])) report = await self.generate_report()
kick_days_ago = (now - (1000 * 60 * 60 * 24 * self.config["kick_threshold_days"]))
warn_q = """
SELECT mxid FROM user_events WHERE last_message_timestamp <= $1 AND
last_message_timestamp >= $2
AND ignore_inactivity = 0
"""
kick_q = """
SELECT mxid FROM user_events WHERE last_message_timestamp <= $1
AND ignore_inactivity = 0
"""
warn_inactive_results = await self.database.fetch(warn_q, warn_days_ago, kick_days_ago)
kick_inactive_results = await self.database.fetch(kick_q, kick_days_ago)
warn_inactive = [ row["mxid"] for row in warn_inactive_results ] or ["none"]
kick_inactive = [ row["mxid"] for row in kick_inactive_results ] or ["none"]
await evt.respond(f"<b>Users inactive for {self.config['warn_threshold_days']} days:</b><br /> \ await evt.respond(f"<b>Users inactive for {self.config['warn_threshold_days']} days:</b><br /> \
{'<br />'.join(warn_inactive)} <br />\ {'<br />'.join(report['warn_inactive'])} <br />\
<b>Users inactive for {self.config['kick_threshold_days']} days:</b><br /> \ <b>Users inactive for {self.config['kick_threshold_days']} days:</b><br /> \
{'<br />'.join(kick_inactive)}", \ {'<br />'.join(report['kick_inactive'])}", \
allow_html=True) allow_html=True)
@activity.subcommand("purge", help='kick users for excessive inactivity')
async def kick_users(self, evt: MessageEvent) -> None:
await evt.mark_read()
msg = await evt.respond("starting the purge...")
report = await self.generate_report()
purgeable = report['kick_inactive']
roomlist = await self.get_space_roomlist()
# don't forget to kick from the space itself
roomlist.append(self.config["master_room"])
purge_list = {}
error_list = {}
for user in purgeable:
purge_list[user] = []
for room in roomlist:
try:
await self.client.get_state_event(room, EventType.ROOM_MEMBER, user)
await self.client.kick_user(room, user, reason='inactivity')
purge_list[user].append(room)
time.sleep(0.5)
except MNotFound:
pass
except Exception as e:
self.log.warning(e)
error_list[user] = []
error_list[user].append(room)
results = "the following users were purged:<p><code>{purge_list}</code></p>the following errors were \
recorded:<p><code>{error_list}</code></p>".format(purge_list=purge_list, error_list=error_list)
await evt.respond(results, allow_html=True, edits=msg)
# sync our database after we've made changes to room memberships
await self.do_sync()
#need to somehow regularly fetch and update the list of room ids that are associated with a given space #need to somehow regularly fetch and update the list of room ids that are associated with a given space
#to track events within so that we are actually only paying attention to those rooms #to track events within so that we are actually only paying attention to those rooms