Version 1.1: access control, commands can require admin or owner status.
This commit is contained in:
parent
e1c444024f
commit
4017a53e5b
23
README.md
23
README.md
|
@ -47,10 +47,10 @@ Howto:
|
||||||
|
|
||||||
Commands:
|
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 - list upcoming events in calendar
|
||||||
* !teamup add [calendar id] - add calendar to 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
|
* !teamup del [calendar id] - delete calendar from this room (Must be done as room admin)
|
||||||
* !teamup list - list calendars in this room
|
* !teamup list - list calendars in this room
|
||||||
* !teamup poll - poll now for changes
|
* !teamup poll - poll now for changes
|
||||||
|
|
||||||
|
@ -82,8 +82,8 @@ Commands:
|
||||||
|
|
||||||
* !googlecal - Show next 10 events in calendar
|
* !googlecal - Show next 10 events in calendar
|
||||||
* !googlecal today - Show today's events
|
* !googlecal today - Show today's events
|
||||||
* !googlecal add [calendar id] - Add new calendar to room
|
* !googlecal add [calendar id] - Add new calendar to room (Must be done as room admin)
|
||||||
* !googlecal del [calendar id] - Delete calendar from room
|
* !googlecal del [calendar id] - Delete calendar from room (Must be done as room admin)
|
||||||
* !googlecal list - List calendars in this room
|
* !googlecal list - List calendars in this room
|
||||||
|
|
||||||
### Cron
|
### Cron
|
||||||
|
@ -92,9 +92,9 @@ Can schedule things to be done.
|
||||||
|
|
||||||
Commands:
|
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 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:
|
Examples:
|
||||||
|
|
||||||
|
@ -127,7 +127,8 @@ sudo apt install python3-pip
|
||||||
sudo pip3 install pipenv
|
sudo pip3 install pipenv
|
||||||
pipenv shell
|
pipenv shell
|
||||||
pipenv install --pre
|
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
|
## Running with Docker
|
||||||
|
@ -139,6 +140,7 @@ MATRIX_USER=@user:matrix.org
|
||||||
MATRIX_ACCESS_TOKEN=MDAxOGxvYlotofcharacters53CgYAYFgo
|
MATRIX_ACCESS_TOKEN=MDAxOGxvYlotofcharacters53CgYAYFgo
|
||||||
MATRIX_SERVER=https://matrix.org
|
MATRIX_SERVER=https://matrix.org
|
||||||
JOIN_ON_INVITE=True
|
JOIN_ON_INVITE=True
|
||||||
|
BOT_OWNERS=@user1:matrix.org,@user2:matrix.org
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: without quotes!
|
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.
|
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
|
## Module API
|
||||||
|
|
||||||
Just write a python file with desired command name and place it in modules. See current modules for
|
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
|
import urllib.parse
|
||||||
from nio import (AsyncClient, RoomMessageText, RoomMessageUnknown, JoinError, InviteEvent)
|
from nio import (AsyncClient, RoomMessageText, RoomMessageUnknown, JoinError, InviteEvent)
|
||||||
|
|
||||||
|
# Couple of custom exceptions
|
||||||
|
class CommandRequiresAdmin(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CommandRequiresOwner(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class Bot:
|
class Bot:
|
||||||
appid = 'org.vranki.hemppa'
|
appid = 'org.vranki.hemppa'
|
||||||
version = '1.0'
|
version = '1.1'
|
||||||
client = None
|
client = None
|
||||||
join_on_invite = False
|
join_on_invite = False
|
||||||
modules = dict()
|
modules = dict()
|
||||||
pollcount = 0
|
pollcount = 0
|
||||||
poll_task = None
|
poll_task = None
|
||||||
|
owners = []
|
||||||
|
|
||||||
async def send_text(self, room, body):
|
async def send_text(self, room, body):
|
||||||
msg = {
|
msg = {
|
||||||
|
@ -42,6 +49,27 @@ class Bot:
|
||||||
def get_room_by_id(self, room_id):
|
def get_room_by_id(self, room_id):
|
||||||
return self.client.rooms[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):
|
def save_settings(self):
|
||||||
module_settings = dict()
|
module_settings = dict()
|
||||||
for modulename, moduleobject in self.modules.items():
|
for modulename, moduleobject in self.modules.items():
|
||||||
|
@ -81,15 +109,14 @@ class Bot:
|
||||||
if "matrix_message" in dir(moduleobject):
|
if "matrix_message" in dir(moduleobject):
|
||||||
try:
|
try:
|
||||||
await moduleobject.matrix_message(bot, room, event)
|
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:
|
except:
|
||||||
await self.send_text(room, f'Module {command} experienced difficulty: {sys.exc_info()[0]} - see log for details')
|
await self.send_text(room, f'Module {command} experienced difficulty: {sys.exc_info()[0]} - see log for details')
|
||||||
traceback.print_exc(file=sys.stderr)
|
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):
|
async def invite_cb(self, room, event):
|
||||||
for attempt in range(3):
|
for attempt in range(3):
|
||||||
result = await self.client.join(room.room_id)
|
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 = AsyncClient(os.environ['MATRIX_SERVER'], os.environ['MATRIX_USER'])
|
||||||
self.client.access_token = os.getenv('MATRIX_ACCESS_TOKEN')
|
self.client.access_token = os.getenv('MATRIX_ACCESS_TOKEN')
|
||||||
self.join_on_invite = os.getenv('JOIN_ON_INVITE')
|
self.join_on_invite = os.getenv('JOIN_ON_INVITE')
|
||||||
|
self.owners = os.environ['BOT_OWNERS'].split(',')
|
||||||
self.get_modules()
|
self.get_modules()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
@ -188,11 +215,10 @@ class Bot:
|
||||||
if self.client.logged_in:
|
if self.client.logged_in:
|
||||||
self.load_settings(self.get_account_data())
|
self.load_settings(self.get_account_data())
|
||||||
self.client.add_event_callback(self.message_cb, RoomMessageText)
|
self.client.add_event_callback(self.message_cb, RoomMessageText)
|
||||||
self.client.add_event_callback(self.unknown_cb, RoomMessageUnknown)
|
|
||||||
if self.join_on_invite:
|
if self.join_on_invite:
|
||||||
print('Note: Bot will join rooms if invited')
|
print('Note: Bot will join rooms if invited')
|
||||||
self.client.add_event_callback(self.invite_cb, (InviteEvent,))
|
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)
|
await self.client.sync_forever(timeout=30000)
|
||||||
else:
|
else:
|
||||||
print('Client was not able to log in, check env variables!')
|
print('Client was not able to log in, check env variables!')
|
||||||
|
|
|
@ -12,6 +12,7 @@ services:
|
||||||
- MATRIX_PASSWORD
|
- MATRIX_PASSWORD
|
||||||
- MATRIX_SERVER
|
- MATRIX_SERVER
|
||||||
- JOIN_ON_INVITE
|
- JOIN_ON_INVITE
|
||||||
|
- BOT_OWNERS
|
||||||
volumes:
|
volumes:
|
||||||
- ${PWD}/credentials.json:/bot/credentials.json
|
- ${PWD}/credentials.json:/bot/credentials.json
|
||||||
- ${PWD}/token.pickle:/bot/token.pickle
|
- ${PWD}/token.pickle:/bot/token.pickle
|
||||||
|
|
|
@ -6,6 +6,8 @@ class MatrixModule:
|
||||||
last_hour = datetime.now().hour
|
last_hour = datetime.now().hour
|
||||||
|
|
||||||
async def matrix_message(self, bot, room, event):
|
async def matrix_message(self, bot, room, event):
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
args = shlex.split(event.body)
|
args = shlex.split(event.body)
|
||||||
args.pop(0)
|
args.pop(0)
|
||||||
if len(args) == 3:
|
if len(args) == 3:
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
class MatrixModule:
|
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):
|
async def matrix_message(self, bot, room, event):
|
||||||
args = event.body.split()
|
args = event.body.split()
|
||||||
args.pop(0)
|
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)))
|
await bot.send_text(room, 'Calendars in this room: ' + str(self.calendar_rooms.get(room.room_id)))
|
||||||
elif len(args) == 3:
|
elif len(args) == 3:
|
||||||
if args[1] == 'add':
|
if args[1] == 'add':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
calid = args[2]
|
calid = args[2]
|
||||||
print(f'Adding calendar {calid} to room id {room.room_id}')
|
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')
|
await bot.send_text(room, 'Added new google calendar to this room')
|
||||||
if args[1] == 'del':
|
if args[1] == 'del':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
calid = args[2]
|
calid = args[2]
|
||||||
print(f'Removing calendar {calid} from room id {room.room_id}')
|
print(f'Removing calendar {calid} from room id {room.room_id}')
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
class MatrixModule:
|
class MatrixModule:
|
||||||
async def matrix_message(self, bot, room, event):
|
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():
|
for modulename, moduleobject in bot.modules.items():
|
||||||
msg = msg + '!' + modulename
|
msg = msg + '!' + modulename
|
||||||
|
|
|
@ -36,6 +36,8 @@ class MatrixModule:
|
||||||
await self.poll_all_calendars(bot)
|
await self.poll_all_calendars(bot)
|
||||||
elif len(args) == 3:
|
elif len(args) == 3:
|
||||||
if args[1] == 'add':
|
if args[1] == 'add':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
calid = args[2]
|
calid = args[2]
|
||||||
print(f'Adding calendar {calid} to room id {room.room_id}')
|
print(f'Adding calendar {calid} to room id {room.room_id}')
|
||||||
|
|
||||||
|
@ -54,6 +56,8 @@ class MatrixModule:
|
||||||
self.setup_calendars()
|
self.setup_calendars()
|
||||||
await bot.send_text(room, 'Added new teamup calendar to this room')
|
await bot.send_text(room, 'Added new teamup calendar to this room')
|
||||||
if args[1] == 'del':
|
if args[1] == 'del':
|
||||||
|
bot.must_be_admin(room, event)
|
||||||
|
|
||||||
calid = args[2]
|
calid = args[2]
|
||||||
print(f'Removing calendar {calid} from room id {room.room_id}')
|
print(f'Removing calendar {calid} from room id {room.room_id}')
|
||||||
|
|
||||||
|
@ -66,6 +70,8 @@ class MatrixModule:
|
||||||
self.setup_calendars()
|
self.setup_calendars()
|
||||||
await bot.send_text(room, 'Removed teamup calendar from this room')
|
await bot.send_text(room, 'Removed teamup calendar from this room')
|
||||||
if args[1] == 'apikey':
|
if args[1] == 'apikey':
|
||||||
|
bot.must_be_owner(event)
|
||||||
|
|
||||||
self.api_key = args[2]
|
self.api_key = args[2]
|
||||||
bot.save_settings()
|
bot.save_settings()
|
||||||
self.setup_calendars()
|
self.setup_calendars()
|
||||||
|
|
Loading…
Reference in New Issue