Add support for bridging multiple channels/rooms

This commit is contained in:
git-bruh 2020-12-12 15:42:05 +05:30
parent 7b97427287
commit d93cbf54eb
No known key found for this signature in database
GPG key ID: E1475C50075ADCE6
2 changed files with 51 additions and 31 deletions

View file

@ -28,3 +28,4 @@ NOTE: [Privileged Intents](https://discordpy.readthedocs.io/en/latest/intents.ht
- [x] Redacting messages - [x] Redacting messages
- [x] Editing messages - [x] Editing messages
- [x] Replies - [x] Replies
- [x] Bridging multiple channels/rooms

81
main.py
View file

@ -9,11 +9,10 @@ import re
def config_gen(config_file): def config_gen(config_file):
config_dict = { config_dict = {
"homeserver": "https://matrix.org", "homeserver": "https://matrix.org",
"room_id": "room:matrix.org",
"username": "@name:matrix.org", "username": "@name:matrix.org",
"password": "my-secret-password", "password": "my-secret-password",
"channel_id": "channel", "token": "my-secret-token",
"token": "my-secret-token" "bridge": {"channel_id": "room_id", }
} }
if not os.path.exists(config_file): if not os.path.exists(config_file):
@ -34,6 +33,7 @@ logging.basicConfig(level=logging.INFO)
matrix_logger = logging.getLogger("matrix_logger") matrix_logger = logging.getLogger("matrix_logger")
message_store = {} message_store = {}
channel_store = {}
class MatrixClient(nio.AsyncClient): class MatrixClient(nio.AsyncClient):
@ -66,7 +66,10 @@ class MatrixClient(nio.AsyncClient):
await self.close() await self.close()
async def message_send(self, message, reply_id=None, edit_id=None): async def message_send(self, message, channel_id,
reply_id=None, edit_id=None):
room_id = config["bridge"][str(channel_id)]
content = { content = {
"msgtype": "m.text", "msgtype": "m.text",
"body": message, "body": message,
@ -74,7 +77,7 @@ class MatrixClient(nio.AsyncClient):
if reply_id: if reply_id:
reply_event = await self.room_get_event( reply_event = await self.room_get_event(
config["room_id"], reply_id room_id, reply_id
) )
reply_event = reply_event.event.source["content"]["body"] reply_event = reply_event.event.source["content"]["body"]
@ -86,7 +89,7 @@ class MatrixClient(nio.AsyncClient):
content["format"] = "org.matrix.custom.html" content["format"] = "org.matrix.custom.html"
content["formatted_body"] = f"""<mx-reply><blockquote> content["formatted_body"] = f"""<mx-reply><blockquote>
<a href="https://matrix.to/#/{config["room_id"]}/{reply_id}">In reply to</a> <a href="https://matrix.to/#/{room_id}/{reply_id}">In reply to</a>
<a href="https://matrix.to/#/{config["username"]}">{config["username"]}</a><br> <a href="https://matrix.to/#/{config["username"]}">{config["username"]}</a><br>
{reply_event}</blockquote></mx-reply>{message}""" {reply_event}</blockquote></mx-reply>{message}"""
@ -104,20 +107,23 @@ class MatrixClient(nio.AsyncClient):
} }
message = await self.room_send( message = await self.room_send(
room_id=config["room_id"], room_id=room_id,
message_type="m.room.message", message_type="m.room.message",
content=content content=content
) )
return message.event_id return message.event_id
async def message_redact(self, message): async def message_redact(self, message, channel_id):
await self.room_redact( await self.room_redact(
room_id=config["room_id"], room_id=config["bridge"][str(channel_id)],
event_id=message event_id=message
) )
async def webhook_send(self, author, avatar, message, event_id): async def webhook_send(self, author, avatar, message,
event_id, channel_id):
channel = channel_store[channel_id]
# Create webhook if it doesn't exist # Create webhook if it doesn't exist
hook_name = "matrix_bridge" hook_name = "matrix_bridge"
hooks = await channel.webhooks() hooks = await channel.webhooks()
@ -135,11 +141,11 @@ class MatrixClient(nio.AsyncClient):
except discord.errors.HTTPException as e: except discord.errors.HTTPException as e:
matrix_logger.warning(f"Failed to send message {event_id}: {e}") matrix_logger.warning(f"Failed to send message {event_id}: {e}")
async def process_message(self, message): async def process_message(self, message, channel_id):
mentions = re.findall(r"(^|\s)(@(\w*))", message) mentions = re.findall(r"(^|\s)(@(\w*))", message)
emotes = re.findall(r":(\w*):", message) emotes = re.findall(r":(\w*):", message)
guild = channel.guild guild = channel_store[channel_id].guild
added_emotes = [] added_emotes = []
for emote in emotes: for emote in emotes:
@ -170,42 +176,44 @@ class DiscordClient(discord.Client):
async def on_ready(self): async def on_ready(self):
print(f"Logged in as {self.user}") print(f"Logged in as {self.user}")
global channel for channel in config["bridge"].keys():
channel = int(config["channel_id"]) channel_store[channel] = self.get_channel(int(channel))
channel = self.get_channel(channel)
async def on_message(self, message): async def on_message(self, message):
if message.author.bot or str(message.channel.id) != \ if message.author.bot or str(message.channel.id) not in \
config["channel_id"]: config["bridge"].keys():
return return
content = await self.process_message(message) content = await self.process_message(message)
matrix_message = await self.matrix_client.message_send( matrix_message = await self.matrix_client.message_send(
content[0], content[1]) content[0], message.channel.id, reply_id=content[1])
message_store[message.id] = matrix_message message_store[message.id] = matrix_message
async def on_message_edit(self, before, after): async def on_message_edit(self, before, after):
if after.author.bot or str(after.channel.id) != \ if after.author.bot or str(after.channel.id) not in \
config["channel_id"]: config["bridge"].keys():
return return
content = await self.process_message(after) content = await self.process_message(after)
await self.matrix_client.message_send( await self.matrix_client.message_send(
content[0], edit_id=message_store[before.id]) content[0], after.channel.id, edit_id=message_store[before.id])
async def on_message_delete(self, message): async def on_message_delete(self, message):
if message.id in message_store: if message.id in message_store:
await self.matrix_client.message_redact(message_store[message.id]) await self.matrix_client.message_redact(
message_store[message.id], message.channel.id)
async def on_typing(self, channel, user, when): async def on_typing(self, channel, user, when):
if user.bot or str(channel.id) != config["channel_id"]: if user.bot or str(channel.id) not in \
config["bridge"].keys():
return return
# Send typing event # Send typing event
await self.matrix_client.room_typing(config["room_id"], timeout=0) await self.matrix_client.room_typing(
config["bridge"][str(channel.id)], timeout=0)
async def process_message(self, message): async def process_message(self, message):
content = message.clean_content content = message.clean_content
@ -236,9 +244,16 @@ class Callbacks(object):
self.client = client self.client = client
self.process_message = process_message self.process_message = process_message
def get_channel(self, room):
channel_id = next(
(channel_id for channel_id, room_id in config["bridge"].items()
if room_id == room.room_id), None)
return channel_id
async def message_callback(self, room, event): async def message_callback(self, room, event):
# Ignore messages from ourselves or other rooms # Ignore messages from ourselves or other rooms
if room.room_id != config["room_id"] or \ if room.room_id not in config["bridge"].values() or \
event.sender == self.client.user: event.sender == self.client.user:
return return
@ -249,6 +264,8 @@ class Callbacks(object):
content_dict = event.source.get("content") content_dict = event.source.get("content")
channel_id = self.get_channel(room)
author = event.sender.split(":")[0][1:] author = event.sender.split(":")[0][1:]
avatar = None avatar = None
@ -259,7 +276,7 @@ class Callbacks(object):
if content_dict["m.relates_to"]["rel_type"] == "m.replace": if content_dict["m.relates_to"]["rel_type"] == "m.replace":
edited_event = content_dict["m.relates_to"]["event_id"] edited_event = content_dict["m.relates_to"]["event_id"]
edited_content = await self.process_message( edited_content = await self.process_message(
content_dict["m.new_content"]["body"]) content_dict["m.new_content"]["body"], channel_id)
webhook_message = message_store[edited_event] webhook_message = message_store[edited_event]
await webhook_message.edit(content=edited_content) await webhook_message.edit(content=edited_content)
return return
@ -276,7 +293,7 @@ class Callbacks(object):
if content_dict["msgtype"] == "m.emote": if content_dict["msgtype"] == "m.emote":
message = f"_{author} {message}_" message = f"_{author} {message}_"
message = await self.process_message(message) message = await self.process_message(message, channel_id)
# Get attachments # Get attachments
try: try:
@ -298,11 +315,11 @@ class Callbacks(object):
break break
await self.client.webhook_send( await self.client.webhook_send(
author, avatar, message, event.event_id) author, avatar, message, event.event_id, channel_id)
async def redaction_callback(self, room, event): async def redaction_callback(self, room, event):
# Ignore messages from ourselves or other rooms # Ignore messages from ourselves or other rooms
if room.room_id != config["room_id"] or \ if room.room_id not in config["bridge"].values() or \
event.sender == self.client.user: event.sender == self.client.user:
return return
@ -315,7 +332,7 @@ class Callbacks(object):
async def typing_callback(self, room, event): async def typing_callback(self, room, event):
# Ignore events from other rooms # Ignore events from other rooms
if room.room_id != config["room_id"]: if room.room_id not in config["bridge"].values():
return return
if room.typing_users: if room.typing_users:
@ -324,8 +341,10 @@ class Callbacks(object):
and room.typing_users[0] == self.client.user: and room.typing_users[0] == self.client.user:
return return
channel_id = self.get_channel(room)
# Send typing event # Send typing event
async with channel.typing(): async with channel_store[channel_id].typing():
pass pass