Added relay module for relaybot functionality

This commit is contained in:
Ville Ranki 2020-11-04 21:53:23 +02:00
parent ea8fd74a85
commit 39c1b98f88
4 changed files with 137 additions and 3 deletions

View File

@ -411,6 +411,26 @@ Example commands:
* !md login https://my.instance/ me@email.invalid r00tm3 * !md login https://my.instance/ me@email.invalid r00tm3
* !md roomlogin #myroom:matrix.org https://my.instance/ me@email.invalid r00tm3 * !md roomlogin #myroom:matrix.org https://my.instance/ me@email.invalid r00tm3
### Relay bridge
Bridges two or more Matrix rooms together via relaybot.
Note: Room ID is not same as room alias! Rooms can exist without aliases so ID's are more flexible. Room id is usually in format !123LotOfRandomChars:server.org
To get room ID, it's in Element Web room settings | Advanced | Internal room ID
Before bridging the bot must be present on both rooms.
Commands:
* !relay bridge [roomid] - Bridge room with given ID to this room (must be done as bot owner)
* !relay list - List bridged rooms (and their index numbers) (must be done as bot owner)
* !relay unbridge [number] - Remove the given bridge number (must be done as bot owner)
File uploads, joins, leaves or other special events are not (yet) handled. Contributions welcome.
Relaybots are stupid. Please prefer real Matrix bridges to this. Sometimes there's no alternative.
## Bot setup ## Bot setup
* Create a Matrix user * Create a Matrix user

7
bot.py
View File

@ -142,7 +142,10 @@ class Bot:
self.client.event_callbacks.remove(cb_object) self.client.event_callbacks.remove(cb_object)
def get_room_by_id(self, room_id): def get_room_by_id(self, room_id):
try:
return self.client.rooms[room_id] return self.client.rooms[room_id]
except KeyError:
return None
async def get_room_by_alias(self, alias): async def get_room_by_alias(self, alias):
rar = await self.client.room_resolve_alias(alias) rar = await self.client.room_resolve_alias(alias)
@ -205,7 +208,7 @@ class Bot:
# Ignore if asked to ignore # Ignore if asked to ignore
if self.should_ignore_event(event): if self.should_ignore_event(event):
if self.debug: if self.debug:
print('Ignoring event!') self.logger.debug('Ignoring event!')
return return
body = event.body body = event.body
@ -450,4 +453,4 @@ async def main():
try: try:
asyncio.run(main()) asyncio.run(main())
except Exception as e: except Exception as e:
print(e) traceback.print_exc(file=sys.stderr)

108
modules/relay.py Normal file
View File

@ -0,0 +1,108 @@
from modules.common.module import BotModule
from nio import RoomMessageText
class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.bridges = dict()
self.bot = None
async def message_cb(self, room, event):
if self.bot.should_ignore_event(event):
return
if event.body.startswith('!'):
return
source_id = None
target_id = None
for src_id, tgt_id in self.bridges.items():
if room.room_id == src_id:
source_id = src_id
target_id = tgt_id
elif room.room_id == tgt_id:
source_id = tgt_id
target_id = src_id
if not source_id or not target_id:
return
target_room = self.bot.get_room_by_id(target_id)
if(target_room):
sendernick = target_room.user_name(event.sender)
if not sendernick:
sendernick = event.sender
await self.bot.send_text(target_room, f'<{sendernick}> {event.body}', msgtype="m.text", bot_ignore=True)
else:
self.logger.warning(f"Bot doesn't seem to be in bridged room {target_id}")
def matrix_start(self, bot):
super().matrix_start(bot)
bot.client.add_event_callback(self.message_cb, RoomMessageText)
self.bot = bot
def matrix_stop(self, bot):
super().matrix_stop(bot)
bot.remove_callback(self.message_cb)
self.bot = None
async def matrix_message(self, bot, room, event):
bot.must_be_admin(room, event)
args = event.body.split()
args.pop(0)
if len(args) == 1:
if args[0] == 'list':
i = 1
msg = f"Active relay bridges ({len(self.bridges)}):\n"
for src_id, tgt_id in self.bridges.items():
srcroom = self.bot.get_room_by_id(src_id)
tgtroom = self.bot.get_room_by_id(tgt_id)
if srcroom:
srcroom = srcroom.display_name
else:
srcroom = f'??? {src_id}'
if tgtroom:
tgtroom = tgtroom.display_name
else:
tgtroom = f'??? {tgt_id}'
msg += f'{i}: {srcroom} <-> {tgtroom}'
i = i + 1
await bot.send_text(room, msg)
if len(args) == 2:
if args[0] == 'bridge':
roomid = args[1]
room_to_bridge = bot.get_room_by_id(roomid)
if room_to_bridge:
await bot.send_text(room, f'Bridging {room_to_bridge.display_name} here.')
self.bridges[room.room_id] = roomid
bot.save_settings()
else:
await bot.send_text(room, f'I am not on room with id {roomid} (note: use id, not alias)!')
elif args[0] == 'unbridge':
idx = int(args[1]) - 1
i = 0
for src_id, tgt_id in self.bridges.items():
if i == idx:
del self.bridges[src_id]
await bot.send_text(room, f'Unbridged {src_id} and {tgt_id}.')
bot.save_settings()
return
i = i + 1
def help(self):
return 'Simple relaybot between two Matrix rooms'
def get_settings(self):
data = super().get_settings()
data["bridges"] = self.bridges
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("bridges"):
self.bridges = data["bridges"]

View File

@ -3,6 +3,8 @@ import shlex
from functools import lru_cache from functools import lru_cache
import httpx import httpx
import sys
import traceback
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from nio import RoomMessageText from nio import RoomMessageText
@ -90,6 +92,7 @@ class MatrixModule(BotModule):
title, description = self.get_content_from_url(url) title, description = self.get_content_from_url(url)
except Exception as e: except Exception as e:
self.logger.warning(f"could not fetch url: {e}") self.logger.warning(f"could not fetch url: {e}")
traceback.print_exc(file=sys.stderr)
# failed fetching, give up # failed fetching, give up
continue continue