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] 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
104
main.py
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue