From 9431d7a2679e50724f7758ece771dfbf0a3b49a6 Mon Sep 17 00:00:00 2001 From: Ville Ranki Date: Tue, 10 Dec 2019 18:05:40 +0200 Subject: [PATCH] Google cal seems to work now! --- Pipfile | 1 + README.md | 5 +-- bot.py | 61 +++++++++++++++++++++++++++++++--- modules/googlecal.py | 79 +++++++++++++++++++++----------------------- 4 files changed, 98 insertions(+), 48 deletions(-) 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")