diff --git a/README.md b/README.md index 2cafa57..ca52744 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,8 @@ Can search OpenStreetMaps for locations and send Matrix location events from the Commands: * !loc [location] - search for location +* !loc enable - enable location to link translation in this room (must be done as room admin) +* !loc disable - disable location to link translation in this room (must be done as room admin) Example: diff --git a/bot.py b/bot.py index 21e22cb..cdff094 100755 --- a/bot.py +++ b/bot.py @@ -217,7 +217,7 @@ class Bot: msg["org.vranki.hemppa.ignore"] = "true" await self.client.room_send(room.room_id, 'm.room.message', msg) - async def send_location(self, room, body, latitude, longitude, bot_ignore=False): + async def send_location(self, room, body, latitude, longitude, bot_ignore=False, asset='m.pin'): """ :param room: A MatrixRoom the html should be send to @@ -226,12 +226,14 @@ class Bot: :param latitude: Latitude in WGS84 coordinates (float) :param longitude: Longitude in WGS84 coordinates (float) :param bot_ignore: Flag to mark the message to be ignored by the bot + :param asset: Asset string as defined in MSC3488 (such as m.self or m.pin) :return: """ locationmsg = { "body": str(body), "geo_uri": 'geo:' + str(latitude) + ',' + str(longitude), "msgtype": "m.location", + "org.matrix.msc3488.asset": { "type": asset } } await self.client.room_send(room.room_id, 'm.room.message', locationmsg) @@ -555,7 +557,6 @@ class Bot: sys.exit(2) def init(self): - self.matrix_user = os.getenv('MATRIX_USER') matrix_server = os.getenv('MATRIX_SERVER') bot_owners = os.getenv('BOT_OWNERS') diff --git a/modules/loc.py b/modules/loc.py index 04a8f4d..a17e976 100644 --- a/modules/loc.py +++ b/modules/loc.py @@ -5,7 +5,10 @@ from modules.common.module import BotModule class MatrixModule(BotModule): - bot = None + def __init__(self, name): + super().__init__(name) + self.bot = None + self.enabled_rooms = [] def matrix_start(self, bot): super().matrix_start(bot) @@ -16,30 +19,87 @@ class MatrixModule(BotModule): super().matrix_stop(bot) bot.remove_callback(self.unknown_cb) + ''' + Location events are like: https://spec.matrix.org/v1.2/client-server-api/#mlocation + { + "content": { + "body": "geo:61.49342512194717,23.765914658307736", + "geo_uri": "geo:61.49342512194717,23.765914658307736", + "msgtype": "m.location", + "org.matrix.msc1767.text": "geo:61.49342512194717,23.765914658307736", + "org.matrix.msc3488.asset": { + "type": "m.pin" + }, + "org.matrix.msc3488.location": { + "description": "geo:61.49342512194717,23.765914658307736", + "uri": "geo:61.49342512194717,23.765914658307736" + }, + "org.matrix.msc3488.ts": 1653837929839 + }, + "room_id": "!xsBGdLYGrfYhGfLtHG:hacklab.fi", + "type": "m.room.message" + } + + BUT sometimes there's ; separating altitude?? + { + "content": { + "body": "geo:61.4704211,23.4864855;36.900001525878906", + "geo_uri": "geo:61.4704211,23.4864855;36.900001525878906", + "msgtype": "m.location", + "org.matrix.msc1767.text": "geo:61.4704211,23.4864855;36.900001525878906", + "org.matrix.msc3488.asset": { + "type": "m.self" + }, + "org.matrix.msc3488.location": { + "description": "geo:61.4704211,23.4864855;36.900001525878906", + "uri": "geo:61.4704211,23.4864855;36.900001525878906" + }, + "org.matrix.msc3488.ts": 1653931683087 + }, + "origin_server_ts": 1653931683998, + "sender": "@cos:hacklab.fi", + "type": "m.room.message", + "unsigned": { + "age": 70 + }, + "event_id": "$6xXutKF9EppPMMdc4aQLZjHyd8My0rIZuNZEcuSIPws", + "room_id": "!CLofqdurVWZCMpFnqM:hacklab.fi" +} + ''' + async def unknown_cb(self, room, event): if event.msgtype != 'm.location': return + if room.room_id not in self.enabled_rooms: + return location_text = event.content['body'] # Fallback if body is empty - if len(location_text) == 0: + if (len(location_text) == 0) or ('geo:' in location_text): location_text = 'location' sender_response = await self.bot.client.get_displayname(event.sender) sender = sender_response.displayname geo_uri = event.content['geo_uri'] - latlon = geo_uri.split(':')[1].split(',') + try: + geo_uri = geo_uri[4:] # Strip geo: - # Sanity checks to avoid url manipulation - float(latlon[0]) - float(latlon[1]) + if ';' in geo_uri: # Strip altitude, if present + geo_uri = geo_uri.split(';')[0] + latlon = geo_uri.split(',') - osm_link = 'https://www.openstreetmap.org/?mlat=' + \ - latlon[0] + "&mlon=" + latlon[1] + # Sanity checks to avoid url manipulation + float(latlon[0]) + float(latlon[1]) + except Exception: + self.bot.send_text(room, "Error: Invalid location " + geo_uri) + return - plain = sender + ' 🚩 ' + osm_link - html = f'{sender} 🚩 {location_text}' + osm_link = f"https://www.openstreetmap.org/?mlat={latlon[0]}&mlon={latlon[1]}" + + plain = f'{sender} sent {location_text} {osm_link} 🚩' + html = f'{sender} sent {location_text} 🚩' await self.bot.send_html(room, html, plain) @@ -48,16 +108,41 @@ class MatrixModule(BotModule): args.pop(0) if len(args) == 0: await bot.send_text(room, 'Usage: !loc ') + return + elif len(args) == 1: + if args[0] == 'enable': + bot.must_be_admin(room, event) + self.enabled_rooms.append(room.room_id) + self.enabled_rooms = list(dict.fromkeys(self.enabled_rooms)) # Deduplicate + await bot.send_text(room, "Ok, sending locations events here as text versions") + bot.save_settings() + return + if args[0] == 'disable': + bot.must_be_admin(room, event) + self.enabled_rooms.remove(room.room_id) + await bot.send_text(room, "Ok, disabled here") + bot.save_settings() + return + + query = event.body[4:] + geolocator = Nominatim(user_agent=bot.appid) + self.logger.info('loc: looking up %s ..', query) + location = geolocator.geocode(query) + self.logger.info('loc rx %s', location) + if location: + await bot.send_location(room, location.address, location.latitude, location.longitude, "m.pin") else: - query = event.body[4:] - geolocator = Nominatim(user_agent=bot.appid) - self.logger.info('loc: looking up %s ..', query) - location = geolocator.geocode(query) - self.logger.info('loc rx %s', location) - if location: - await bot.send_location(room, location.address, location.latitude, location.longitude) - else: - await bot.send_text(room, "Can't find " + query + " on map!") + await bot.send_text(room, "Can't find " + query + " on map!") def help(self): return 'Search for locations and display Matrix location events as OSM links' + + def get_settings(self): + data = super().get_settings() + data["enabled_rooms"] = self.enabled_rooms + return data + + def set_settings(self, data): + super().set_settings(data) + if data.get("enabled_rooms"): + self.enabled_rooms = data["enabled_rooms"]