users module: add user classification and stats commands.

This commit is contained in:
Ville Ranki 2021-09-11 22:35:41 +03:00
parent 9b5ac99a40
commit 87aa703512
2 changed files with 97 additions and 5 deletions

View File

@ -624,12 +624,27 @@ by default, but you can set any single instance to search on.
### User management ### User management
Admin commands to manage users. Admin commands to manage users and some utilities.
You can classify users based on MXID to get stats on where users come from.
#### Usage #### Usage
* !users list [pattern] - List users matching wildcard pattern (must be owner) * !users list [pattern] - List users matching wildcard pattern in this room (must be owner)
* !users kick [pattern] - Kick users matching wildcard pattern from room (must be admin in room) * !users kick [pattern] - Kick users matching wildcard pattern from room (must be admin in room)
* !users classify add [name] [pattern] - Add a classification pattern (must be owner)
* !users classify list - List classifications
* !users classify del [name] - Delete classification
* !users roomstats - List how many users are in each class in this room
* !users stats - List how many users are in each class globally as seen by bot
Example:
* !users classify add matrix.org @*:matrix.org
* !users classify add libera.chat @*:libera.chat
* !users classify add discord @*discordpuppet*:*
* !users stats
* !users roomstats
### RASP (Gliding Weather forecast) ### RASP (Gliding Weather forecast)

View File

@ -2,10 +2,43 @@ from modules.common.module import BotModule
import fnmatch import fnmatch
class MatrixModule(BotModule): class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.classes = dict() # classname <-> pattern
async def matrix_message(self, bot, room, event): async def matrix_message(self, bot, room, event):
args = event.body.split() args = event.body.split()
args.pop(0) args.pop(0)
if len(args) == 1:
if args[0] == 'stats' or args[0] == 'roomstats':
stats = dict()
for name, pattern in self.classes.items():
stats[name] = 0
if args[0] == 'stats':
allusers = self.get_users(bot)
else:
allusers = self.get_users(bot, room.room_id)
total = len(allusers)
matched = 0
for user in allusers:
for name, pattern in self.classes.items():
match = fnmatch.fnmatch(user, pattern)
if match:
stats[name] = stats[name] + 1
matched = matched + 1
stats['Matrix'] = total - matched
stats = dict(sorted(stats.items(), key=lambda item: item[1], reverse=True))
if args[0] == 'stats':
reply = f'I am seeing total {len(allusers)} users:\n'
else:
reply = f'I am seeing {len(allusers)} users in this room:\n'
for name in stats:
reply = reply + f' - {name}: {stats[name]} ({stats[name] / total * 100}%)\n'
await bot.send_text(room, reply)
return
if len(args) == 2: if len(args) == 2:
if args[0] == 'list': if args[0] == 'list':
bot.must_be_owner(event) bot.must_be_owner(event)
@ -25,20 +58,64 @@ class MatrixModule(BotModule):
else: else:
await bot.send_text(room, 'No matching users found!') await bot.send_text(room, 'No matching users found!')
return return
if args[0] == 'classify':
if args[1] == 'list':
await bot.send_text(room, f'Classes in use: {self.classes}.')
return
elif len(args) == 4:
if args[0] == 'classify':
if args[1] == 'add':
bot.must_be_owner(event)
name = args[2]
pattern = args[3]
self.classes[name] = pattern
await bot.send_text(room, f'Added class {name} pattern {pattern}.')
bot.save_settings()
return
elif len(args) == 3:
if args[0] == 'classify':
if args[1] == 'del':
bot.must_be_owner(event)
name = args[2]
del self.classes[name]
await bot.send_text(room, f'Deleted class {name}.')
bot.save_settings()
return
await bot.send_text(room, 'Unknown command - please see readme') await bot.send_text(room, 'Unknown command - please see readme')
def search_users(self, bot, pattern): def get_users(self, bot, roomid=None):
allusers = [] allusers = []
for croomid in bot.client.rooms: for croomid in self.bot.client.rooms:
if roomid and (roomid != croomid):
break
try: try:
users = bot.client.rooms[croomid].users users = self.bot.client.rooms[croomid].users
except (KeyError, ValueError) as e: except (KeyError, ValueError) as e:
self.logger.warning(f"Couldn't get user list in room with id {croomid}, skipping: {repr(e)}") self.logger.warning(f"Couldn't get user list in room with id {croomid}, skipping: {repr(e)}")
continue continue
for user in users: for user in users:
allusers.append(user) allusers.append(user)
allusers = list(dict.fromkeys(allusers)) # Deduplicate allusers = list(dict.fromkeys(allusers)) # Deduplicate
return allusers
def search_users(self, bot, pattern):
allusers = self.get_users(self, bot)
return fnmatch.filter(allusers, pattern) return fnmatch.filter(allusers, pattern)
def help(self): def help(self):
return 'User management tools' return 'User management tools'
def get_settings(self):
data = super().get_settings()
data["classes"] = self.classes
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("classes"):
self.classes = data["classes"]
def matrix_start(self, bot):
super().matrix_start(bot)
self.bot = bot