\!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:
parent
ff281e0e2e
commit
c4eeae2e7b
29
README.md
29
README.md
|
@ -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
4
bot.py
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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}")
|
||||||
|
|
|
@ -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'
|
Loading…
Reference in New Issue