\!room tombstoning support, dont send blank urls (fixes #156), \!loc quotes (fixes #152), \!user (fixes #144 ), better explanation (fixes #151)

This commit is contained in:
Ville Ranki 2021-05-19 23:01:04 +03:00
parent ff281e0e2e
commit c4eeae2e7b
7 changed files with 114 additions and 13 deletions

View File

@ -38,6 +38,7 @@ Bot management commands.
* !bot stats - show statistics on matrix users seen by bot * !bot stats - show statistics on matrix users seen by bot
The following must be done as the bot owner: The following must be done as the bot owner:
* !bot enable [module] - enable module * !bot enable [module] - enable module
* !bot disable [module] - disable module * !bot disable [module] - disable module
* !bot quit - quit the bot process * !bot quit - quit the bot process
@ -53,6 +54,7 @@ The following must be done as the bot owner:
The uri cache prevents the bot from uploading a blob from a url repeatedly The uri cache prevents the bot from uploading a blob from a url repeatedly
* !bot leave - ask bot to leave this room * !bot leave - ask bot to leave this room
* !bot modules - list all modules including enabled status * !bot modules - list all modules including enabled status
* !bot rooms - list rooms the bot is on
### Help ### Help
@ -183,11 +185,15 @@ Example:
This module is for interacting with the room that the commands are being executed on. This module is for interacting with the room that the commands are being executed on.
* !room servers: Lists the servers in the room * !room servers Lists the servers in the room
* !room joined: Responds with the joined members count * !room joined Responds with the joined members count
* !room banned: Lists the banned users and their provided reason * !room banned Lists the banned users and their provided reason
* !room kicked: Lists the kicked users and their provided reason * !room kicked Lists the kicked users and their provided reason
* !room state [event type] [optional state key]: Gets a state event with given event type and optional state key * !room state [event type] [optional state key] Gets a state event with given event type and optional state key
* !room tombstone [target] Creates a tombstone event pointing to target room. Target room can be alias (starting with #) or id (starting with !).
Note on tombstone: If using alias, bot must be present in target room. This is the preferred way. If using id, make sure it's correct, as it's not validated!
Tombstoning requires power level for room upgrade. Make sure bot has it in the room.
### Welcome to Room ### Welcome to Room
@ -602,6 +608,15 @@ by default, but you can set any single instance to search on.
* !pt setinstance [url] - Set instance url, must end with / (example: https://peertube.cpy.re/) * !pt setinstance [url] - Set instance url, must end with / (example: https://peertube.cpy.re/)
* !pt showinstance - Print which instance is used * !pt showinstance - Print which instance is used
### User management
Admin commands to manage users.
#### Usage
* !users list [pattern] - List users matching wildcard pattern
* !users kick [pattern] - Kick users matching wildcard pattern from room
## Bot setup ## Bot setup
* Create a Matrix user * Create a Matrix user
@ -669,8 +684,8 @@ docker-compose up
## Env variables ## Env variables
`MATRIX_USER` is the full MXID (not just username) of the Matrix user. `MATRIX_ACCESS_TOKEN` `MATRIX_USER` is the full MXID (not just username) of the Matrix user. `MATRIX_ACCESS_TOKEN`
and `MATRIX_SERVER` should be self-explanatory. Set `JOIN_ON_INVITE` (default true) to false and `MATRIX_SERVER` should be url to the user's server (non-delegated server). Set `JOIN_ON_INVITE` (default true)
if you don't want the bot automatically joining rooms. to false if you don't want the bot automatically joining rooms.
You can get access token by logging in with Element Android and looking from Settings / Help & About. You can get access token by logging in with Element Android and looking from Settings / Help & About.

4
bot.py
View File

@ -424,8 +424,8 @@ class Bot:
async def memberevent_cb(self, room, event): async def memberevent_cb(self, room, event):
# Automatically leaves rooms where bot is alone. # Automatically leaves rooms where bot is alone.
if room.member_count == 1 and event.membership=='leave': if room.member_count == 1 and event.membership=='leave' and event.sender != self.matrix_user:
self.logger.info(f"membership event in {room.display_name} ({room.room_id}) with {room.member_count} members by '{event.sender}' - leaving room as i don't want to be left alone!") self.logger.info(f"Membership event in {room.display_name} ({room.room_id}) with {room.member_count} members by '{event.sender}' (I am {self.matrix_user})- leaving room as i don't want to be left alone!")
await self.client.room_leave(room.room_id) await self.client.room_leave(room.room_id)
def load_module(self, modulename): def load_module(self, modulename):

View File

@ -57,6 +57,8 @@ class MatrixModule(BotModule):
await self.export_settings(bot, event) await self.export_settings(bot, event)
elif args[1] == 'ping': elif args[1] == 'ping':
await self.get_ping(bot, room, event) await self.get_ping(bot, room, event)
elif args[1] == 'rooms':
await self.rooms(bot, room, event)
elif len(args) == 3: elif len(args) == 3:
if args[1] == 'enable': if args[1] == 'enable':
@ -297,6 +299,14 @@ class MatrixModule(BotModule):
bot.uri_cache = dict() bot.uri_cache = dict()
bot.save_settings() bot.save_settings()
async def rooms(self, bot, room, event):
bot.must_be_owner(event)
rooms = []
for croomid in bot.client.rooms:
roomobj = bot.get_room_by_id(croomid)
rooms.append(roomobj.machine_name)
await bot.send_text(room, f'I\'m in following {len(rooms)} rooms: {rooms}')
def disable(self): def disable(self):
raise ModuleCannotBeDisabled raise ModuleCannotBeDisabled

View File

@ -39,7 +39,7 @@ class MatrixModule(BotModule):
latlon[0] + "&mlon=" + latlon[1] latlon[0] + "&mlon=" + latlon[1]
plain = sender + ' 🚩 ' + osm_link plain = sender + ' 🚩 ' + osm_link
html = f'{sender} 🚩 <a href={osm_link}>{location_text}</a>' html = f'{sender} 🚩 <a href="{osm_link}">{location_text}</a>'
await self.bot.send_html(room, html, plain) await self.bot.send_html(room, html, plain)

View File

@ -43,6 +43,8 @@ class MatrixModule(BotModule):
await MatrixModule.kicked_members(bot, room) await MatrixModule.kicked_members(bot, room)
elif command == 'state': elif command == 'state':
await MatrixModule.get_state_event(bot, room, args) await MatrixModule.get_state_event(bot, room, args)
elif command == 'tombstone':
await MatrixModule.tombstone(bot, room, args, event)
@staticmethod @staticmethod
async def servers_in_room(bot, room: nio.MatrixRoom): async def servers_in_room(bot, room: nio.MatrixRoom):
@ -210,3 +212,35 @@ class MatrixModule(BotModule):
else: else:
# Raise the error and the bot module will handle the rest # Raise the error and the bot module will handle the rest
raise res raise res
@staticmethod
async def tombstone(bot, room, args, event):
bot.must_be_admin(room, event)
body = "This room has been replaced" # Todo: make settable
if len(args) == 2:
target = args[1]
if "#" in target:
targetid = await bot.get_room_by_alias(target)
if not targetid:
await bot.send_text(room, f"Bot is not on room {targetid}?")
return
elif "!" in target:
targetid = target
else:
await bot.send_text(room, f"Give a room alias (starts with #) or room id (starts with !)")
return
tombstone_event = {
"body": body,
"replacement_room": targetid
}
response = await bot.client.room_put_state(room.room_id, 'm.room.tombstone', tombstone_event)
if type(response) == nio.RoomPutStateResponse:
await bot.send_text(room, f"See you in the new room!")
await bot.client.room_leave(room.room_id)
else:
await bot.send_text(room, f"Error creating tombstone event: {response}")
return
await bot.send_text(room, f"Usage: !room tombstone #room:server.org")

View File

@ -128,7 +128,7 @@ class MatrixModule(BotModule):
# failed fetching, give up # failed fetching, give up
continue continue
msg = None msg = ""
if status == "TITLE" and title is not None: if status == "TITLE" and title is not None:
msg = f"Title: {title}" msg = f"Title: {title}"
@ -143,7 +143,7 @@ class MatrixModule(BotModule):
elif status == "BOTH" and description is not None: elif status == "BOTH" and description is not None:
msg = f"Description: {description}" msg = f"Description: {description}"
if msg is not None: if msg.strip(): # Evaluates to true on non-empty strings
await self.bot.send_text(room, msg, msgtype=self.type, bot_ignore=True) await self.bot.send_text(room, msg, msgtype=self.type, bot_ignore=True)
except Exception as e: except Exception as e:
self.logger.warning(f"Unexpected error in url module text_cb: {e}") self.logger.warning(f"Unexpected error in url module text_cb: {e}")

42
modules/users.py Normal file
View File

@ -0,0 +1,42 @@
from modules.common.module import BotModule
import fnmatch
class MatrixModule(BotModule):
async def matrix_message(self, bot, room, event):
args = event.body.split()
args.pop(0)
if len(args) == 2:
if args[0] == 'list':
users = self.search_users(bot, args[1])
if len(users):
await bot.send_text(room, ' '.join(users))
else:
await bot.send_text(room, 'No matching users found!')
return
if args[0] == 'kick':
users = self.search_users(bot, args[1])
if len(users):
for user in users:
self.logger.debug(f"Kicking {user} from {room.room_id} as requested by {event.sender}")
await bot.client.room_kick(room.room_id, user)
else:
await bot.send_text(room, 'No matching users found!')
return
await bot.send_text(room, 'Unknown command - please see readme')
def search_users(self, bot, pattern):
allusers = []
for croomid in bot.client.rooms:
try:
users = bot.client.rooms[croomid].users
except (KeyError, ValueError) as e:
self.logger.warning(f"Couldn't get user list in room with id {croomid}, skipping: {repr(e)}")
continue
for user in users:
allusers.append(user)
allusers = list(dict.fromkeys(allusers)) # Deduplicate
return fnmatch.filter(allusers, pattern)
def help(self):
return 'User management tools'