Bridge discord emotes to matrix (#2)
This commit is contained in:
parent
5096505308
commit
7a31decfe4
3 changed files with 91 additions and 16 deletions
|
@ -26,3 +26,4 @@ NOTE: [Privileged Intents](https://discordpy.readthedocs.io/en/latest/intents.ht
|
|||
- [x] Editing messages
|
||||
- [x] Replies
|
||||
- [x] Bridging multiple channels/rooms
|
||||
- [x] Discord emotes bridged as inline images (Works on Element Web, Fluffychat)
|
||||
|
|
104
main.py
104
main.py
|
@ -1,10 +1,14 @@
|
|||
import discord.ext.commands
|
||||
import aiofiles
|
||||
import aiofiles.os
|
||||
import aiohttp
|
||||
import discord
|
||||
import discord.ext.commands
|
||||
import json
|
||||
import logging
|
||||
import nio
|
||||
import os
|
||||
import re
|
||||
import uuid
|
||||
|
||||
|
||||
def config_gen(config_file):
|
||||
|
@ -14,7 +18,7 @@ def config_gen(config_file):
|
|||
"password": "my-secret-password",
|
||||
"token": "my-secret-token",
|
||||
"discord_prefix": "my-command-prefix",
|
||||
"bridge": {"channel_id": "room_id", }
|
||||
"bridge": {"channel_id": "room_id"}
|
||||
}
|
||||
|
||||
if not os.path.exists(config_file):
|
||||
|
@ -35,9 +39,13 @@ message_store, channel_store = {}, {}
|
|||
|
||||
|
||||
class MatrixClient(nio.AsyncClient):
|
||||
async def start(self, discord_client):
|
||||
self.logger = logging.getLogger("matrix_logger")
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.logger = logging.getLogger("matrix_logger")
|
||||
self.uploaded_emotes = {}
|
||||
|
||||
async def start(self, discord_client):
|
||||
password = config["password"]
|
||||
timeout = 30000
|
||||
|
||||
|
@ -70,15 +78,71 @@ class MatrixClient(nio.AsyncClient):
|
|||
|
||||
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):
|
||||
room_id = config["bridge"][str(channel_id)]
|
||||
|
||||
content = {
|
||||
"msgtype": "m.text",
|
||||
"body": message,
|
||||
"format": "org.matrix.custom.html",
|
||||
"msgtype": "m.text"
|
||||
}
|
||||
|
||||
content["body"], content["formatted_body"] = message, await \
|
||||
self.process_emotes(message, emotes)
|
||||
|
||||
if reply_id:
|
||||
reply_event = await self.room_get_event(
|
||||
room_id, reply_id
|
||||
|
@ -91,16 +155,18 @@ class MatrixClient(nio.AsyncClient):
|
|||
|
||||
content["format"] = "org.matrix.custom.html"
|
||||
|
||||
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/#/{reply_event.sender}">{reply_event.sender}</a><br>
|
||||
{reply_event.body}</blockquote></mx-reply>{message}"""
|
||||
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/#/{reply_event.sender}">{reply_event.sender}</a>\
|
||||
<br>{reply_event.body}</blockquote></mx-reply>{content["formatted_body"]}"""
|
||||
|
||||
if edit_id:
|
||||
content["body"] = f" * {message}"
|
||||
|
||||
content["m.new_content"] = {
|
||||
"body": message,
|
||||
"formatted_body": content["formatted_body"],
|
||||
"format": "org.matrix.custom.html",
|
||||
"msgtype": "m.text"
|
||||
}
|
||||
|
||||
|
@ -183,7 +249,8 @@ class DiscordClient(discord.ext.commands.Bot):
|
|||
content = await self.process_message(message)
|
||||
|
||||
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
|
||||
|
@ -196,7 +263,8 @@ class DiscordClient(discord.ext.commands.Bot):
|
|||
|
||||
if before.id in message_store:
|
||||
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):
|
||||
|
@ -217,6 +285,12 @@ class DiscordClient(discord.ext.commands.Bot):
|
|||
async def process_message(self, message):
|
||||
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
|
||||
if message.reference:
|
||||
replied_message = await message.channel.fetch_message(
|
||||
|
@ -228,7 +302,7 @@ class DiscordClient(discord.ext.commands.Bot):
|
|||
pass
|
||||
|
||||
# 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
|
||||
for attachment in message.attachments:
|
||||
|
@ -236,7 +310,7 @@ class DiscordClient(discord.ext.commands.Bot):
|
|||
|
||||
content = f"[{message.author.name}] {content}"
|
||||
|
||||
return content, replied_event
|
||||
return content, replied_event, emotes
|
||||
|
||||
|
||||
class Callbacks(object):
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
git+git://github.com/rapptz/discord.py@c793737
|
||||
discord.py==1.6.0
|
||||
matrix-nio==0.15.2
|
||||
|
|
Loading…
Reference in a new issue