Version 1.1: access control, commands can require admin or owner status.
This commit is contained in:
parent
e1c444024f
commit
4017a53e5b
27
README.md
27
README.md
|
@ -42,15 +42,15 @@ Howto:
|
|||
|
||||
* Create a calendar in Teamup https://teamup.com/
|
||||
* Get api key at https://teamup.com/api-keys/request
|
||||
* !teamup apikey [your api key]
|
||||
* !teamup add [calendar id]
|
||||
* !teamup apikey [your api key]
|
||||
* !teamup add [calendar id]
|
||||
|
||||
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
44
bot.py
|
@ -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!')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}')
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue