initial commit, old activity tracker and create-room plugin merged into one with some fun fixes
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2023 <copyright holders>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Community Bot
|
||||
|
||||
a maubot plugin that attempts to assist administrators of communities on matrix, based on the concept of matrix spaces.
|
||||
this bot will attempt to administer any room that it is in (specifically user activity tracking), so you may want to
|
||||
leverage [join](https://github.com/williamkray/maubot-join) to ensure your bot doesn't end up somewhere it's not
|
||||
supposed to be.
|
||||
|
||||
# features
|
||||
|
||||
## activity tracking and reporting
|
||||
|
||||
tracks the last message timestamp of a user across any room that the bot is in, and generates a simple report. intended
|
||||
to be used to boot people from a matrix space and all space rooms after a period of inactivity (prune inactive users)
|
||||
with the `purge` subcommand.
|
||||
|
||||
supports simple threshold configuration and the option to also track "reaction" activity.
|
||||
|
||||
you can also exempt users from showing as "inactive" in the report by setting their ignore status with the `ignore` and
|
||||
`unignore` subcommands, e.g. `!community ignore @takinabreak:fromthis.group`. this is helpful to avoid accidentally
|
||||
purging admin accounts, backup accounts, rarely used bots, etc.
|
||||
|
||||
`sync` subcommand will actively sync your space member list with the database to track active members properly. new
|
||||
members to the space automatically trigger a sync, as do most other commands. this command is mostly deprecated but you
|
||||
may want to run it just to see what it does.
|
||||
|
||||
generate a report with the `report` subcommand (i.e. `!community report`) to see your inactive users.
|
||||
|
||||
## user management
|
||||
|
||||
purge inactive users with the `purge` subcommand (i.e. `!community purge`).
|
||||
|
||||
kick an individual user from your space and all child rooms, regardless of activity status, with the `kick` subcommand
|
||||
(e.g. `!community kick @malicious:user.here`). this is useful in communities built on the concept of private (invite
|
||||
only) matrix spaces.
|
||||
|
||||
if you want more sever action, use the `ban` and `unban` subcommands to ban users from all rooms in the space (this action
|
||||
will automatically kick them from those rooms as well). if you've made a mistake, use the unban option, but they will
|
||||
need to rejoin all rooms themselves or be re-invited.
|
||||
|
||||
use the `guests` subcommand to see who is in a room but NOT a member of the parent space (invited guests) e.g.
|
||||
`!community guests #myroom:alias.here`.
|
||||
|
||||
## room creation
|
||||
|
||||
use the `createroom` subcommand to create a new room according to your preferences, and join it into the parent space.
|
||||
will attempt to sanitize the room name and assign a room alias automatically. the bot user will be assigned very high
|
||||
power level (1000) and set an admin power level (100) to plugin administrators. this ensures that the bot is still able
|
||||
to manage room admins. the bot will also invite other users to these new rooms as configured.
|
||||
|
||||
# installation
|
||||
|
||||
install this like any other maubot plugin: zip the contents of this repo into a file and upload via the web interface,
|
||||
or use the `mbc` utility to package and upload to your maubot server.
|
||||
|
||||
be sure to give your bot permission to kick people from all rooms, otherwise management features will not work! for
|
||||
more robust management, check out the [welcome](https://github.com/williamkray/maubot-welcome),
|
||||
[join](https://github.com/williamkray/maubot-join) plugins as well.
|
||||
@@ -0,0 +1,31 @@
|
||||
# the room-id of the matrix room or space to use as your "full user list"
|
||||
parent_room: "!somerandomcharacters:server.tld"
|
||||
|
||||
# whether to encrypt rooms when using the room creation commands
|
||||
encrypt: False
|
||||
|
||||
# number of days of inactivity to be considered in the "warning zone"
|
||||
warn_threshold_days: 30
|
||||
|
||||
# number of days of inactivity to be considered in the "danger zone"
|
||||
kick_threshold_days: 60
|
||||
|
||||
|
||||
# track messages? if false, will not track user activity timestamps. enable if you'd like to track
|
||||
# inactive users in your community
|
||||
track_messages: True
|
||||
|
||||
# track reactions? if false, will only track activity based on normal message events, but if true
|
||||
# will update the user's last-active date when they add a reaction to a message
|
||||
track_reactions: True
|
||||
|
||||
# list of users who can use administrative commands, these users will also be made room admins (PL100)
|
||||
# in new rooms created with the create room commands
|
||||
admins:
|
||||
- '@user1:server.tld'
|
||||
- '@user2:server.tld'
|
||||
|
||||
# list of users who should be invited to new rooms immediately (other bots perhaps)
|
||||
invitees:
|
||||
- "@mybot:server.tld"
|
||||
- "@secondaryadmin:server.tld"
|
||||
@@ -0,0 +1 @@
|
||||
from .bot import CommunityBot
|
||||
@@ -0,0 +1,473 @@
|
||||
# kickbot - a maubot plugin to track user activity and remove inactive users from rooms/spaces.
|
||||
|
||||
from typing import Awaitable, Type, Optional, Tuple
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
|
||||
from mautrix.client import Client, InternalEventType, MembershipEventDispatcher
|
||||
from mautrix.types import (Event, StateEvent, EventID, UserID, FileInfo, EventType,
|
||||
MediaMessageEventContent, ReactionEvent, RedactionEvent)
|
||||
from mautrix.errors import MNotFound
|
||||
from mautrix.util.config import BaseProxyConfig, ConfigUpdateHelper
|
||||
from maubot import Plugin, MessageEvent
|
||||
from maubot.handlers import command, event
|
||||
|
||||
# database table related things
|
||||
from .db import upgrade_table
|
||||
|
||||
|
||||
|
||||
class Config(BaseProxyConfig):
|
||||
def do_update(self, helper: ConfigUpdateHelper) -> None:
|
||||
helper.copy("admins")
|
||||
helper.copy("parent_room")
|
||||
helper.copy("track_messages")
|
||||
helper.copy("track_reactions")
|
||||
helper.copy("warn_threshold_days")
|
||||
helper.copy("kick_threshold_days")
|
||||
helper.copy("encrypt")
|
||||
helper.copy("invitees")
|
||||
|
||||
|
||||
class CommunityBot(Plugin):
|
||||
|
||||
async def start(self) -> None:
|
||||
await super().start()
|
||||
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["parent_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:
|
||||
space = self.config["parent_room"]
|
||||
rooms = []
|
||||
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['parent_room']:
|
||||
await self.do_sync()
|
||||
|
||||
@event.on(EventType.ROOM_MESSAGE)
|
||||
async def update_message_timestamp(self, evt: MessageEvent) -> None:
|
||||
if not self.config["track_messages"]:
|
||||
pass
|
||||
else:
|
||||
q = """
|
||||
INSERT INTO user_events(mxid, last_message_timestamp)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT(mxid)
|
||||
DO UPDATE SET last_message_timestamp=$2
|
||||
"""
|
||||
await self.database.execute(q, evt.sender, evt.timestamp)
|
||||
|
||||
@event.on(EventType.REACTION)
|
||||
async def update_reaction_timestamp(self, evt: MessageEvent) -> None:
|
||||
if not self.config["track_reactions"]:
|
||||
pass
|
||||
else:
|
||||
q = """
|
||||
INSERT INTO user_events(mxid, last_message_timestamp)
|
||||
VALUES ($1, $2)
|
||||
ON CONFLICT(mxid)
|
||||
DO UPDATE SET last_message_timestamp=$2
|
||||
"""
|
||||
await self.database.execute(q, evt.sender, evt.timestamp)
|
||||
|
||||
@command.new("community", help="track active/inactive status of members of a space")
|
||||
async def community(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@community.subcommand("sync", help="update the activity tracker with the current space members \
|
||||
in case they are missing")
|
||||
async def sync_space_members(self, evt: MessageEvent) -> None:
|
||||
if evt.sender in self.config["admins"]:
|
||||
results = await self.do_sync()
|
||||
|
||||
added_str = "<br />".join(results['added'])
|
||||
dropped_str = "<br />".join(results['dropped'])
|
||||
await evt.respond(f"Added: {added_str}<br /><br />Dropped: {dropped_str}", allow_html=True)
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to do that")
|
||||
|
||||
|
||||
@community.subcommand("ignore", help="exclude a specific matrix ID from inactivity tracking")
|
||||
@command.argument("mxid", "full matrix ID", required=True)
|
||||
async def ignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||
if evt.sender in self.config["admins"]:
|
||||
try:
|
||||
Client.parse_user_id(mxid)
|
||||
await self.database.execute("UPDATE user_events SET ignore_inactivity = 1 WHERE \
|
||||
mxid = $1", mxid)
|
||||
self.log.info(f"{mxid} set to ignore inactivity")
|
||||
await evt.react("✅")
|
||||
except Exception as e:
|
||||
await evt.respond(f"{e}")
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to set that")
|
||||
|
||||
@community.subcommand("unignore", help="re-enable activity tracking for a specific matrix ID")
|
||||
@command.argument("mxid", "full matrix ID", required=True)
|
||||
async def unignore_inactivity(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||
if evt.sender in self.config["admins"]:
|
||||
try:
|
||||
Client.parse_user_id(mxid)
|
||||
await self.database.execute("UPDATE user_events SET ignore_inactivity = 0 WHERE \
|
||||
mxid = $1", mxid)
|
||||
self.log.info(f"{mxid} set to track inactivity")
|
||||
await evt.react("✅")
|
||||
except Exception as e:
|
||||
await evt.respond(f"{e}")
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to set that")
|
||||
|
||||
@community.subcommand("report", help='generate a list of matrix IDs that have been inactive')
|
||||
async def get_report(self, evt: MessageEvent) -> None:
|
||||
sync_results = await self.do_sync()
|
||||
report = await self.generate_report()
|
||||
await evt.respond(f"<b>Users inactive for at least {self.config['warn_threshold_days']} days:</b><br /> \
|
||||
{'<br />'.join(report['warn_inactive'])} <br />\
|
||||
<b>Users inactive for at least {self.config['kick_threshold_days']} days:</b><br /> \
|
||||
{'<br />'.join(report['kick_inactive'])}", \
|
||||
allow_html=True)
|
||||
|
||||
|
||||
@community.subcommand("purge", help='kick users for excessive inactivity')
|
||||
async def kick_users(self, evt: MessageEvent) -> None:
|
||||
await evt.mark_read()
|
||||
if evt.sender in self.config["admins"]:
|
||||
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["parent_room"])
|
||||
purge_list = {}
|
||||
error_list = {}
|
||||
|
||||
for user in purgeable:
|
||||
purge_list[user] = []
|
||||
for room in roomlist:
|
||||
try:
|
||||
roomname = None
|
||||
roomnamestate = await self.client.get_state_event(room, 'm.room.name')
|
||||
roomname = roomnamestate['name']
|
||||
|
||||
await self.client.get_state_event(room, EventType.ROOM_MEMBER, user)
|
||||
await self.client.kick_user(room, user, reason='inactivity')
|
||||
if roomname:
|
||||
purge_list[user].append(roomname)
|
||||
else:
|
||||
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(roomname or 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()
|
||||
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to do that")
|
||||
|
||||
|
||||
@community.subcommand("kick", help='kick a specific user from the community and all rooms')
|
||||
@command.argument("mxid", "full matrix ID", required=True)
|
||||
async def kick_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||
await evt.mark_read()
|
||||
if evt.sender in self.config["admins"]:
|
||||
user = mxid
|
||||
msg = await evt.respond("starting the purge...")
|
||||
roomlist = await self.get_space_roomlist()
|
||||
# don't forget to kick from the space itself
|
||||
roomlist.append(self.config["parent_room"])
|
||||
purge_list = {}
|
||||
error_list = {}
|
||||
|
||||
purge_list[user] = []
|
||||
for room in roomlist:
|
||||
try:
|
||||
roomname = None
|
||||
roomnamestate = await self.client.get_state_event(room, 'm.room.name')
|
||||
roomname = roomnamestate['name']
|
||||
|
||||
await self.client.get_state_event(room, EventType.ROOM_MEMBER, user)
|
||||
await self.client.kick_user(room, user, reason='kicked')
|
||||
if roomname:
|
||||
purge_list[user].append(roomname)
|
||||
else:
|
||||
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(roomname or room)
|
||||
|
||||
|
||||
results = "the following users were kicked:<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()
|
||||
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to do that")
|
||||
|
||||
|
||||
@community.subcommand("ban", help='kick and ban a specific user from the community and all rooms')
|
||||
@command.argument("mxid", "full matrix ID", required=True)
|
||||
async def ban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||
await evt.mark_read()
|
||||
if evt.sender in self.config["admins"]:
|
||||
user = mxid
|
||||
msg = await evt.respond("starting the ban...")
|
||||
roomlist = await self.get_space_roomlist()
|
||||
# don't forget to kick from the space itself
|
||||
roomlist.append(self.config["parent_room"])
|
||||
ban_list = {}
|
||||
error_list = {}
|
||||
|
||||
ban_list[user] = []
|
||||
for room in roomlist:
|
||||
try:
|
||||
roomname = None
|
||||
roomnamestate = await self.client.get_state_event(room, 'm.room.name')
|
||||
roomname = roomnamestate['name']
|
||||
|
||||
# ban user even if they're not in the room!
|
||||
#await self.client.get_state_event(room, EventType.ROOM_MEMBER, user)
|
||||
await self.client.ban_user(room, user, reason='banned')
|
||||
if roomname:
|
||||
ban_list[user].append(roomname)
|
||||
else:
|
||||
ban_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(roomname or room)
|
||||
|
||||
|
||||
results = "the following users were kicked and banned:<p><code>{ban_list}</code></p>the following errors were \
|
||||
recorded:<p><code>{error_list}</code></p>".format(ban_list=ban_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()
|
||||
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to do that")
|
||||
|
||||
|
||||
@community.subcommand("unban", help='unban a specific user from the community and all rooms')
|
||||
@command.argument("mxid", "full matrix ID", required=True)
|
||||
async def unban_user(self, evt: MessageEvent, mxid: UserID) -> None:
|
||||
await evt.mark_read()
|
||||
if evt.sender in self.config["admins"]:
|
||||
user = mxid
|
||||
msg = await evt.respond("starting the unban...")
|
||||
roomlist = await self.get_space_roomlist()
|
||||
# don't forget to kick from the space itself
|
||||
roomlist.append(self.config["parent_room"])
|
||||
unban_list = {}
|
||||
error_list = {}
|
||||
|
||||
unban_list[user] = []
|
||||
for room in roomlist:
|
||||
try:
|
||||
roomname = None
|
||||
roomnamestate = await self.client.get_state_event(room, 'm.room.name')
|
||||
roomname = roomnamestate['name']
|
||||
|
||||
await self.client.get_state_event(room, EventType.ROOM_MEMBER, user)
|
||||
await self.client.unban_user(room, user, reason='unbanned')
|
||||
if roomname:
|
||||
unban_list[user].append(roomname)
|
||||
else:
|
||||
unban_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(roomname or room)
|
||||
|
||||
|
||||
results = "the following users were unbanned:<p><code>{unban_list}</code></p>the following errors were \
|
||||
recorded:<p><code>{error_list}</code></p>".format(unban_list=unban_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()
|
||||
|
||||
else:
|
||||
await evt.reply("lol you don't have permission to do that")
|
||||
|
||||
|
||||
@community.subcommand("createroom", help="create a new room titled <roomname> and add it to the parent space")
|
||||
@command.argument("roomname", pass_raw=True, required=True)
|
||||
async def create_that_room(self, evt: MessageEvent, roomname: str) -> None:
|
||||
if (roomname == "help") or len(roomname) == 0:
|
||||
self.log.debug(f"DEBUG: {homeserver}")
|
||||
await evt.reply('pass me a room name (like "cool topic") and i will create it and add it to the space')
|
||||
else:
|
||||
if evt.sender in self.config["admins"] or evt.sender in self.config["mods"]:
|
||||
try:
|
||||
sanitized_name = re.sub(r"[^a-zA-Z0-9]", '', roomname).lower()
|
||||
invitees = self.config['invitees']
|
||||
parent_room = self.config['parent_room']
|
||||
## homeserver is derived from maubot's client instance since this is the user that will create the room
|
||||
server = self.client.parse_user_id(self.client.mxid)[1]
|
||||
# set bot PL higher than admin so we can kick old admins if needed
|
||||
pl_override = {"users": {self.client.mxid: 1000}}
|
||||
for u in self.config['admins']:
|
||||
pl_override["users"][u] = 100
|
||||
pl_json = json.dumps(pl_override)
|
||||
|
||||
mymsg = await evt.respond(f"creating {sanitized_name}, give me a minute...")
|
||||
#self.log.info(mymsg)
|
||||
room_id = await self.client.create_room(alias_localpart=sanitized_name, name=roomname,
|
||||
invitees=invitees, power_level_override=pl_override)
|
||||
|
||||
await evt.respond(f"updating room states...", edits=mymsg)
|
||||
parent_event_content = json.dumps({'auto_join': False, 'suggested': False, 'via': [server]})
|
||||
child_event_content = json.dumps({'canonical': True, 'via': [server]})
|
||||
join_rules_content = json.dumps({'join_rule': 'restricted', 'allow': [{'type': 'm.room_membership',
|
||||
'room_id': parent_room}]})
|
||||
|
||||
await self.client.send_state_event(parent_room, 'm.space.child', parent_event_content, state_key=room_id)
|
||||
await self.client.send_state_event(room_id, 'm.space.parent', child_event_content, state_key=parent_room)
|
||||
await self.client.send_state_event(room_id, 'm.room.join_rules', join_rules_content, state_key="")
|
||||
|
||||
if self.config["encrypt"]:
|
||||
encryption_content = json.dumps({"algorithm": "m.megolm.v1.aes-sha2"})
|
||||
|
||||
await self.client.send_state_event(room_id, 'm.room.encryption', encryption_content,
|
||||
state_key="")
|
||||
await evt.respond(f"encrypting room...", edits=mymsg)
|
||||
|
||||
await evt.respond(f"room created and updated, alias is #{sanitized_name}:{server}", edits=mymsg)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
await evt.respond(f"i tried, but something went wrong: \"{e}\"", edits=mymsg)
|
||||
else:
|
||||
await evt.reply("you're not the boss of me!")
|
||||
|
||||
|
||||
#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
|
||||
|
||||
## loop through each room and report people who are "guests" (in the room, but not members of the space)
|
||||
@community.subcommand("guests", help="generate a list of members in a room who are not members of the parent space")
|
||||
@command.argument("room", required=False)
|
||||
async def get_guestlist(self, evt: MessageEvent, room: str) -> None:
|
||||
space_members_obj = await self.client.get_joined_members(self.config["parent_room"])
|
||||
space_members_list = space_members_obj.keys()
|
||||
room_id = None
|
||||
if room:
|
||||
if room.startswith('#'):
|
||||
try:
|
||||
thatroom_id = await self.client.resolve_room_alias(room)
|
||||
room_id = thatroom_id["room_id"]
|
||||
except:
|
||||
evt.reply("i don't recognize that room, sorry")
|
||||
return
|
||||
else:
|
||||
room_id = room
|
||||
else:
|
||||
room_id = evt.room_id
|
||||
room_members_obj = await self.client.get_joined_members(room_id)
|
||||
room_members_list = room_members_obj.keys()
|
||||
|
||||
# find the non-space members in the room member list
|
||||
try:
|
||||
guest_list = set(room_members_list) - set(space_members_list)
|
||||
if len(guest_list) == 0:
|
||||
guest_list = ["None"]
|
||||
await evt.reply(f"<b>Guests in this room are:</b><br /> \
|
||||
{'<br />'.join(guest_list)}", allow_html=True)
|
||||
except Exception as e:
|
||||
await evt.respond(f"something went wrong: {e}")
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_db_upgrade_table(cls) -> None:
|
||||
return upgrade_table
|
||||
|
||||
@classmethod
|
||||
def get_config_class(cls) -> Type[BaseProxyConfig]:
|
||||
return Config
|
||||
@@ -0,0 +1,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mautrix.util.async_db import UpgradeTable, Connection
|
||||
|
||||
upgrade_table = UpgradeTable()
|
||||
|
||||
@upgrade_table.register(description="Table initialization")
|
||||
async def upgrade_v1(conn: Connection) -> None:
|
||||
await conn.execute(
|
||||
"""CREATE TABLE user_events (
|
||||
mxid TEXT PRIMARY KEY,
|
||||
last_message_timestamp BIGINT NOT NULL,
|
||||
ignore_inactivity INT
|
||||
)"""
|
||||
)
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
maubot: 0.1.0
|
||||
id: org.jobmachine.communitybot
|
||||
version: 0.0.1
|
||||
license: MIT
|
||||
modules:
|
||||
- community
|
||||
main_class: CommunityBot
|
||||
extra_files:
|
||||
- base-config.yaml
|
||||
database: true
|
||||
database_type: asyncpg
|
||||
Reference in New Issue
Block a user