Add support for bridging multiple channels/rooms
This commit is contained in:
parent
7b97427287
commit
d93cbf54eb
2 changed files with 51 additions and 31 deletions
|
@ -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
81
main.py
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue