diff --git a/Pipfile b/Pipfile
index f536374..4da48af 100644
--- a/Pipfile
+++ b/Pipfile
@@ -9,6 +9,7 @@ geopy = "*"
google-api-python-client = "*"
google-auth-httplib2 = "*"
google-auth-oauthlib = "*"
+requests = "*"
[dev-packages]
pylint = "*"
diff --git a/README.md b/README.md
index 0625140..205d822 100644
--- a/README.md
+++ b/README.md
@@ -27,9 +27,9 @@ Aviation weather TAF service access.
Prints bot uptime.
-### Google Calendar (WIP)
+### Google Calendar
-Displays changes and daily report of a google calendar to a room. This is a bit pain to set up, sorry.
+Can access a google calendar in a room. This is a bit pain to set up, sorry.
To set up, you'll need to generate credentials.json file - see https://console.developers.google.com/apis/credentials
@@ -51,6 +51,7 @@ 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 calendars - List calendars in this room
## Bot setup
diff --git a/bot.py b/bot.py
index 6a20a33..18ba0bf 100755
--- a/bot.py
+++ b/bot.py
@@ -8,10 +8,15 @@ import traceback
import importlib
import sys
import re
+import requests
+import json
+import urllib.parse
from nio import (AsyncClient, RoomMessageText, RoomMessageUnknown, JoinError, InviteEvent)
class Bot:
+ appid = 'org.vranki.hemppa'
+ version = '1.0'
client = None
join_on_invite = False
modules = dict()
@@ -25,11 +30,12 @@ class Bot:
}
await self.client.room_send(self.get_room_id(room), 'm.room.message', msg)
- async def send_html(self, room, html):
+ async def send_html(self, room, html, plaintext):
msg = {
"msgtype": "m.text",
"format": "org.matrix.custom.html",
- "formatted_body": html
+ "formatted_body": html,
+ "body": plaintext
}
await self.client.room_send(self.get_room_id(room), 'm.room.message', msg)
@@ -40,6 +46,32 @@ class Bot:
print('Cannot find id for room', room.named_room_name(), ' - is the bot on it?')
return None
+ def get_room_by_id(self, room_id):
+ return self.client.rooms[room_id]
+
+ def save_settings(self):
+ module_settings = dict()
+ for modulename, moduleobject in self.modules.items():
+ if "get_settings" in dir(moduleobject):
+ try:
+ module_settings[modulename] = moduleobject.get_settings()
+ except:
+ traceback.print_exc(file=sys.stderr)
+ print('Collected module settings:', module_settings)
+ data = { self.appid: self.version, 'module_settings': module_settings}
+ self.set_account_data(data)
+
+ def load_settings(self, data):
+ if not data.get('module_settings'):
+ return
+ for modulename, moduleobject in self.modules.items():
+ if data['module_settings'].get(modulename):
+ if "set_settings" in dir(moduleobject):
+ try:
+ moduleobject.set_settings(data['module_settings'][modulename])
+ except:
+ traceback.print_exc(file=sys.stderr)
+
async def message_cb(self, room, event):
# Figure out the command
body = event.body
@@ -104,6 +136,26 @@ class Bot:
traceback.print_exc(file=sys.stderr)
await asyncio.sleep(10)
+ def set_account_data(self, data):
+ userid = urllib.parse.quote(os.environ['MATRIX_USER'])
+
+ ad_url = f"{self.client.homeserver}/_matrix/client/r0/user/{userid}/account_data/{self.appid}?access_token={self.client.access_token}"
+
+ response = requests.put(ad_url, json.dumps(data))
+ if response.status_code != 200:
+ print('Setting account data failed:', response, response.json())
+
+ def get_account_data(self):
+ userid = urllib.parse.quote(os.environ['MATRIX_USER'])
+
+ ad_url = f"{self.client.homeserver}/_matrix/client/r0/user/{userid}/account_data/{self.appid}?access_token={self.client.access_token}"
+
+ response = requests.get(ad_url)
+ if response.status_code == 200:
+ return response.json()
+ elif response.status_code != 200:
+ print('Getting account data failed:', response, response.json())
+
def init(self):
self.client = AsyncClient(os.environ['MATRIX_SERVER'], os.environ['MATRIX_USER'])
self.client.access_token = os.getenv('MATRIX_ACCESS_TOKEN')
@@ -114,7 +166,7 @@ class Bot:
print(f'Starting {len(self.modules)} modules..')
for modulename, moduleobject in self.modules.items():
- print(modulename + '..')
+ print('Starting', modulename, '..')
if "matrix_start" in dir(moduleobject):
try:
moduleobject.matrix_start(bot)
@@ -124,7 +176,7 @@ class Bot:
def stop(self):
print(f'Stopping {len(self.modules)} modules..')
for modulename, moduleobject in self.modules.items():
- print(modulename + '..')
+ print('Stopping', modulename, '..')
if "matrix_stop" in dir(moduleobject):
try:
moduleobject.matrix_stop(bot)
@@ -140,6 +192,7 @@ class Bot:
self.poll_task = asyncio.get_event_loop().create_task(self.poll_timer())
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:
diff --git a/modules/googlecal.py b/modules/googlecal.py
index 3385430..d80ad1b 100644
--- a/modules/googlecal.py
+++ b/modules/googlecal.py
@@ -33,9 +33,7 @@ class MatrixModule:
if os.getenv("GCAL_CREDENTIALS"):
self.credentials_file = os.getenv("GCAL_CREDENTIALS")
self.service = None
- self.report_time = 8
- self.last_report_date = None
- self.calendar_rooms = dict() # Contains rooms -> [calid, calid] ..
+ self.calendar_rooms = dict() # Contains room_id -> [calid, calid] ..
creds = None
@@ -72,7 +70,7 @@ class MatrixModule:
return
args = event.body.split()
events = []
- calendars = self.calendar_rooms.get(room) or []
+ calendars = self.calendar_rooms.get(room.room_id) or []
if len(args) == 2:
if args[1] == 'today':
@@ -80,20 +78,38 @@ class MatrixModule:
print('Listing events in cal', calid)
events = events + self.list_today(calid)
if args[1] == 'calendars':
- await bot.send_text(room, 'Calendars in this room: ' + str(self.calendar_rooms.get(room)))
+ 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':
calid = args[2]
- print(f'Adding calendar {calid} to room {room}')
+ print(f'Adding calendar {calid} to room id {room.room_id}')
- if self.calendar_rooms.get(room):
- self.calendar_rooms[room].append(calid)
+ if self.calendar_rooms.get(room.room_id):
+ if calid not in self.calendar_rooms[room.room_id]:
+ self.calendar_rooms[room.room_id].append(calid)
+ else:
+ await bot.send_text(room, 'This google calendar already added in this room!')
+ return
else:
- self.calendar_rooms[room] = [calid]
+ self.calendar_rooms[room.room_id] = [calid]
- print(f'Calendars now for this room {self.calendar_rooms[room]}')
+ print(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
+
+ bot.save_settings()
await bot.send_text(room, 'Added new google calendar to this room')
+ if args[1] == 'del':
+ calid = args[2]
+ print(f'Removing calendar {calid} from room id {room.room_id}')
+
+ if self.calendar_rooms.get(room.room_id):
+ self.calendar_rooms[room.room_id].remove(calid)
+
+ print(f'Calendars now for this room {self.calendar_rooms.get(room.room_id)}')
+
+ bot.save_settings()
+
+ await bot.send_text(room, 'Removed google calendar from this room')
else:
for calid in calendars:
print('Listing events in cal', calid)
@@ -108,9 +124,9 @@ class MatrixModule:
async def send_events(self, bot, events, room):
for event in events:
start = event['start'].get('dateTime', event['start'].get('date'))
- await bot.send_text(room, f"{self.parseDate(start)} {event['summary']}")
- # await bot.send_text(room, f"{self.parseDate(start)} {event['summary']} {event['htmlLink']}")
- # await bot.send_html(room, self.parseDate(start) + " " + event['summary'] + "")
+ # await bot.send_text(room, f"{self.parse_date(start)} {event['summary']}")
+ # await bot.send_text(room, f"{self.parse_date(start)} {event['summary']} {event['htmlLink']}")
+ await bot.send_html(room, f'{self.parse_date(start)} {event["summary"]}', f'{self.parse_date(start)} {event["summary"]}')
def list_upcoming(self, calid):
startTime = datetime.datetime.utcnow()
@@ -133,38 +149,17 @@ class MatrixModule:
events = events_result.get('items', [])
return events
- async def matrix_poll(self, bot, pollcount):
- if not self.service:
- return
-
- if pollcount % (6 * 5) == 0: # Poll every 5 min
- pass # Not implemented yet
-
- needs_send = False
-
- today = datetime.datetime.now()
- since_last = 999
-
- # Bot's been started
- if self.last_report_date:
- since_last = (today - self.last_report_date).total_seconds() / 60 / 60
-
- if since_last > 20 and today.hour >= self.report_time:
- needs_send = True
-
- if needs_send:
- self.last_report_date = today
-
- for room in self.calendar_rooms:
- events = []
- for calid in self.calendar_rooms.get(room):
- events = events + self.list_today(calid)
- await self.send_events(bot, events, room)
-
def help(self):
return('Google calendar. Lists 10 next events by default. today = list today\'s events.')
- def parseDate(self, start):
+ def get_settings(self):
+ return { 'calendar_rooms': self.calendar_rooms }
+
+ def set_settings(self, data):
+ if data.get('calendar_rooms'):
+ self.calendar_rooms = data['calendar_rooms']
+
+ def parse_date(self, start):
try:
dt = datetime.datetime.strptime(start, '%Y-%m-%dT%H:%M:%S%z')
return dt.strftime("%d.%m %H:%M")