Bridge discord emotes to matrix (#2)

This commit is contained in:
git-bruh 2021-01-10 23:10:11 +05:30 committed by GitHub
parent 5096505308
commit 7a31decfe4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 16 deletions

View file

@ -26,3 +26,4 @@ NOTE: [Privileged Intents](https://discordpy.readthedocs.io/en/latest/intents.ht
- [x] Editing messages - [x] Editing messages
- [x] Replies - [x] Replies
- [x] Bridging multiple channels/rooms - [x] Bridging multiple channels/rooms
- [x] Discord emotes bridged as inline images (Works on Element Web, Fluffychat)

104
main.py
View file

@ -1,10 +1,14 @@
import discord.ext.commands import aiofiles
import aiofiles.os
import aiohttp
import discord import discord
import discord.ext.commands
import json import json
import logging import logging
import nio import nio
import os import os
import re import re
import uuid
def config_gen(config_file): def config_gen(config_file):
@ -14,7 +18,7 @@ def config_gen(config_file):
"password": "my-secret-password", "password": "my-secret-password",
"token": "my-secret-token", "token": "my-secret-token",
"discord_prefix": "my-command-prefix", "discord_prefix": "my-command-prefix",
"bridge": {"channel_id": "room_id", } "bridge": {"channel_id": "room_id"}
} }
if not os.path.exists(config_file): if not os.path.exists(config_file):
@ -35,9 +39,13 @@ message_store, channel_store = {}, {}
class MatrixClient(nio.AsyncClient): class MatrixClient(nio.AsyncClient):
async def start(self, discord_client): def __init__(self, *args, **kwargs):
self.logger = logging.getLogger("matrix_logger") super().__init__(*args, **kwargs)
self.logger = logging.getLogger("matrix_logger")
self.uploaded_emotes = {}
async def start(self, discord_client):
password = config["password"] password = config["password"]
timeout = 30000 timeout = 30000
@ -70,15 +78,71 @@ class MatrixClient(nio.AsyncClient):
await self.close() await self.close()
async def message_send(self, message, channel_id, async def process_emotes(self, message, emotes):
formatted_body = message
async def upload_emote(emote_name, emote_id):
if emote_id in self.uploaded_emotes.keys():
return self.uploaded_emotes[emote_id]
emote_url = f"https://cdn.discordapp.com/emojis/{emote_id}"
emote_file = f"/tmp/{str(uuid.uuid4())}"
try:
async with aiohttp.ClientSession() as session:
async with session.get(emote_url) as resp:
emote = await resp.read()
content_type = resp.content_type
except Exception as e:
self.logger.warning(
f"Failed to download emote {emote}: {e}"
)
return
async with aiofiles.open(emote_file, "wb") as f:
await f.write(emote)
try:
async with aiofiles.open(emote_file, "rb") as f:
resp, maybe_keys = await self.upload(
f, content_type=content_type
)
except Exception as e:
self.logger.warning(
f"Failed to upload emote {emote}: {e}"
)
return
await aiofiles.os.remove(emote_file)
self.uploaded_emotes[emote_id] = resp.content_uri
return resp.content_uri
for emote in emotes.keys():
emote_ = await upload_emote(emote, emotes[emote])
if emote_:
emote = f":{emote}:"
formatted_body = formatted_body.replace(
emote, f"""<img alt=\"{emote}\" title=\"{emote}\"
height=\"32\" src=\"{emote_}\" data-mx-emoticon />"""
)
return formatted_body
async def message_send(self, message, channel_id, emotes,
reply_id=None, edit_id=None): reply_id=None, edit_id=None):
room_id = config["bridge"][str(channel_id)] room_id = config["bridge"][str(channel_id)]
content = { content = {
"msgtype": "m.text", "format": "org.matrix.custom.html",
"body": message, "msgtype": "m.text"
} }
content["body"], content["formatted_body"] = message, await \
self.process_emotes(message, emotes)
if reply_id: if reply_id:
reply_event = await self.room_get_event( reply_event = await self.room_get_event(
room_id, reply_id room_id, reply_id
@ -91,16 +155,18 @@ 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/#/{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/#/{reply_event.sender}">{reply_event.sender}</a><br> <a href="https://matrix.to/#/{reply_event.sender}">{reply_event.sender}</a>\
{reply_event.body}</blockquote></mx-reply>{message}""" <br>{reply_event.body}</blockquote></mx-reply>{content["formatted_body"]}"""
if edit_id: if edit_id:
content["body"] = f" * {message}" content["body"] = f" * {message}"
content["m.new_content"] = { content["m.new_content"] = {
"body": message, "body": message,
"formatted_body": content["formatted_body"],
"format": "org.matrix.custom.html",
"msgtype": "m.text" "msgtype": "m.text"
} }
@ -183,7 +249,8 @@ class DiscordClient(discord.ext.commands.Bot):
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], message.channel.id, reply_id=content[1] content[0], message.channel.id,
reply_id=content[1], emotes=content[2]
) )
message_store[message.id] = matrix_message message_store[message.id] = matrix_message
@ -196,7 +263,8 @@ class DiscordClient(discord.ext.commands.Bot):
if before.id in message_store: if before.id in message_store:
await self.matrix_client.message_send( await self.matrix_client.message_send(
content[0], after.channel.id, edit_id=message_store[before.id] content[0], after.channel.id,
edit_id=message_store[before.id], emotes=content[2]
) )
async def on_message_delete(self, message): async def on_message_delete(self, message):
@ -217,6 +285,12 @@ class DiscordClient(discord.ext.commands.Bot):
async def process_message(self, message): async def process_message(self, message):
content = message.clean_content content = message.clean_content
regex = r"<a?:(\w+):(\d+)>"
emotes = {}
for emote in re.findall(regex, content):
emotes[emote[0]] = emote[1]
replied_event = None replied_event = None
if message.reference: if message.reference:
replied_message = await message.channel.fetch_message( replied_message = await message.channel.fetch_message(
@ -228,7 +302,7 @@ class DiscordClient(discord.ext.commands.Bot):
pass pass
# Replace emote IDs with names # Replace emote IDs with names
content = re.sub(r"<a?(:\w+:)\d*>", r"\g<1>", content) content = re.sub(regex, r":\g<1>:", content)
# Append attachments to message # Append attachments to message
for attachment in message.attachments: for attachment in message.attachments:
@ -236,7 +310,7 @@ class DiscordClient(discord.ext.commands.Bot):
content = f"[{message.author.name}] {content}" content = f"[{message.author.name}] {content}"
return content, replied_event return content, replied_event, emotes
class Callbacks(object): class Callbacks(object):

View file

@ -1,2 +1,2 @@
git+git://github.com/rapptz/discord.py@c793737 discord.py==1.6.0
matrix-nio==0.15.2 matrix-nio==0.15.2