Version 1.1: access control, commands can require admin or owner status.

This commit is contained in:
Ville Ranki 2019-12-25 20:49:20 +02:00
parent e1c444024f
commit 4017a53e5b
8 changed files with 66 additions and 26 deletions

View File

@ -47,10 +47,10 @@ Howto:
Commands:
* !teamup apikey [apikey] - set api key
* !teamup apikey [apikey] - set api key (Must be done as bot owner)
* !teamup - list upcoming events in calendar
* !teamup add [calendar id] - add calendar to this room
* !teamup del [calendar id] - delete calendar from this room
* !teamup add [calendar id] - add calendar to this room (Must be done as room admin)
* !teamup del [calendar id] - delete calendar from this room (Must be done as room admin)
* !teamup list - list calendars in this room
* !teamup poll - poll now for changes
@ -82,8 +82,8 @@ Commands:
* !googlecal - Show next 10 events in calendar
* !googlecal today - Show today's events
* !googlecal add [calendar id] - Add new calendar to room
* !googlecal del [calendar id] - Delete calendar from room
* !googlecal add [calendar id] - Add new calendar to room (Must be done as room admin)
* !googlecal del [calendar id] - Delete calendar from room (Must be done as room admin)
* !googlecal list - List calendars in this room
### Cron
@ -92,9 +92,9 @@ Can schedule things to be done.
Commands:
* !cron daily [hour] [command] - Run command on start of hour
* !cron daily [hour] [command] - Run command on start of hour (Must be done as room admin)
* !cron list - List commands in this room
* !cron clear - Clear command s in this room
* !cron clear - Clear command s in this room (Must be done as room admin)
Examples:
@ -127,7 +127,8 @@ sudo apt install python3-pip
sudo pip3 install pipenv
pipenv shell
pipenv install --pre
MATRIX_USER="@user:matrix.org" MATRIX_ACCESS_TOKEN="MDAxOGxvYlotofcharacters53CgYAYFgo" MATRIX_SERVER="https://matrix.org" JOIN_ON_INVITE=True python3 bot.py
MATRIX_USER="@user:matrix.org" MATRIX_ACCESS_TOKEN="MDAxOGxvYlotofcharacters53CgYAYFgo" MATRIX_SERVER="https://matrix.org" JOIN_ON_INVITE=True BOT_OWNERS=@botowner:matrix.org
python3 bot.py
```
## Running with Docker
@ -139,6 +140,7 @@ MATRIX_USER=@user:matrix.org
MATRIX_ACCESS_TOKEN=MDAxOGxvYlotofcharacters53CgYAYFgo
MATRIX_SERVER=https://matrix.org
JOIN_ON_INVITE=True
BOT_OWNERS=@user1:matrix.org,@user2:matrix.org
```
Note: without quotes!
@ -156,6 +158,11 @@ join invites automatically.
You can set MATRIX_PASSWORD if you want to get access token. Normally you can use Riot to get it.
BOT_OWNERS is a comma-separated list of matrix id's for the owners of the bot. Some commands require
sender to be bot owner. Typically set your own id into it. Don't include bot itself in BOT_OWNERS if cron
or any other module that can cause bot to send custom commands is used as it could potentially be used to run
owner commands as the bot itself.
## Module API
Just write a python file with desired command name and place it in modules. See current modules for

44
bot.py
View File

@ -13,15 +13,22 @@ import json
import urllib.parse
from nio import (AsyncClient, RoomMessageText, RoomMessageUnknown, JoinError, InviteEvent)
# Couple of custom exceptions
class CommandRequiresAdmin(Exception):
pass
class CommandRequiresOwner(Exception):
pass
class Bot:
appid = 'org.vranki.hemppa'
version = '1.0'
version = '1.1'
client = None
join_on_invite = False
modules = dict()
pollcount = 0
poll_task = None
owners = []
async def send_text(self, room, body):
msg = {
@ -42,6 +49,27 @@ class Bot:
def get_room_by_id(self, room_id):
return self.client.rooms[room_id]
# Throws exception if event sender is not a room admin
def must_be_admin(self, room, event):
if not self.is_admin(room, event):
raise CommandRequiresAdmin
# Throws exception if event sender is not a bot owner
def must_be_owner(self, event):
if not self.is_owner(event):
raise CommandRequiresOwner
# Returns true if event's sender is admin in the room event was sent in
def is_admin(self, room, event):
print(room.power_levels)
if not event.sender in room.power_levels.users:
return False
return room.power_levels.users[event.sender] >= 50
# Returns true if event's sender is owner of the bot
def is_owner(self, event):
return event.sender in self.owners
def save_settings(self):
module_settings = dict()
for modulename, moduleobject in self.modules.items():
@ -81,15 +109,14 @@ class Bot:
if "matrix_message" in dir(moduleobject):
try:
await moduleobject.matrix_message(bot, room, event)
except CommandRequiresAdmin:
await self.send_text(room, f'Sorry, you need admin power level in this room to run that command.')
except CommandRequiresOwner:
await self.send_text(room, f'Sorry, only bot owner can run that command.')
except:
await self.send_text(room, f'Module {command} experienced difficulty: {sys.exc_info()[0]} - see log for details')
traceback.print_exc(file=sys.stderr)
async def unknown_cb(self, room, event):
if event.msgtype != 'm.location':
return
pass
async def invite_cb(self, room, event):
for attempt in range(3):
result = await self.client.join(room.room_id)
@ -154,7 +181,7 @@ class Bot:
self.client = AsyncClient(os.environ['MATRIX_SERVER'], os.environ['MATRIX_USER'])
self.client.access_token = os.getenv('MATRIX_ACCESS_TOKEN')
self.join_on_invite = os.getenv('JOIN_ON_INVITE')
self.owners = os.environ['BOT_OWNERS'].split(',')
self.get_modules()
def stop(self):
@ -188,11 +215,10 @@ class Bot:
if self.client.logged_in:
self.load_settings(self.get_account_data())
self.client.add_event_callback(self.message_cb, RoomMessageText)
self.client.add_event_callback(self.unknown_cb, RoomMessageUnknown)
if self.join_on_invite:
print('Note: Bot will join rooms if invited')
self.client.add_event_callback(self.invite_cb, (InviteEvent,))
print('Bot running')
print('Bot running as', self.client.user, ', owners', self.owners)
await self.client.sync_forever(timeout=30000)
else:
print('Client was not able to log in, check env variables!')

View File

@ -12,6 +12,7 @@ services:
- MATRIX_PASSWORD
- MATRIX_SERVER
- JOIN_ON_INVITE
- BOT_OWNERS
volumes:
- ${PWD}/credentials.json:/bot/credentials.json
- ${PWD}/token.pickle:/bot/token.pickle

View File

@ -6,6 +6,8 @@ class MatrixModule:
last_hour = datetime.now().hour
async def matrix_message(self, bot, room, event):
bot.must_be_admin(room, event)
args = shlex.split(event.body)
args.pop(0)
if len(args) == 3:

View File

@ -1,10 +1,4 @@
class MatrixModule:
def matrix_start(self, bot):
print("Echo started.")
def matrix_stop(self, bot):
print("Echo stopped")
async def matrix_message(self, bot, room, event):
args = event.body.split()
args.pop(0)

View File

@ -79,6 +79,8 @@ class MatrixModule:
await bot.send_text(room, 'Calendars in this room: ' + str(self.calendar_rooms.get(room.room_id)))
elif len(args) == 3:
if args[1] == 'add':
bot.must_be_admin(room, event)
calid = args[2]
print(f'Adding calendar {calid} to room id {room.room_id}')
@ -97,6 +99,8 @@ class MatrixModule:
await bot.send_text(room, 'Added new google calendar to this room')
if args[1] == 'del':
bot.must_be_admin(room, event)
calid = args[2]
print(f'Removing calendar {calid} from room id {room.room_id}')

View File

@ -1,6 +1,6 @@
class MatrixModule:
async def matrix_message(self, bot, room, event):
msg = 'This is Hemppa, a generic Matrix bot. Known commands:\n\n'
msg = f'This is Hemppa {bot.version}, a generic Matrix bot. Known commands:\n\n'
for modulename, moduleobject in bot.modules.items():
msg = msg + '!' + modulename

View File

@ -36,6 +36,8 @@ class MatrixModule:
await self.poll_all_calendars(bot)
elif len(args) == 3:
if args[1] == 'add':
bot.must_be_admin(room, event)
calid = args[2]
print(f'Adding calendar {calid} to room id {room.room_id}')
@ -54,6 +56,8 @@ class MatrixModule:
self.setup_calendars()
await bot.send_text(room, 'Added new teamup calendar to this room')
if args[1] == 'del':
bot.must_be_admin(room, event)
calid = args[2]
print(f'Removing calendar {calid} from room id {room.room_id}')
@ -66,6 +70,8 @@ class MatrixModule:
self.setup_calendars()
await bot.send_text(room, 'Removed teamup calendar from this room')
if args[1] == 'apikey':
bot.must_be_owner(event)
self.api_key = args[2]
bot.save_settings()
self.setup_calendars()