Compare commits

..

No commits in common. "044ccc08e06f11b48e16c7f7637d3c088008119a" and "c7ddf638dbd658057ec6a7a018a23b0693a97890~" have entirely different histories.

10 changed files with 38 additions and 110 deletions

View file

@ -10,8 +10,6 @@ const api = sync.require("../../matrix/api")
const registerUser = sync.require("./register-user")
/** @type {import("../actions/create-room")} */
const createRoom = sync.require("../actions/create-room")
/** @type {import("../../matrix/file")} */
const file = sync.require("../../matrix/file")
/**
* @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data
@ -19,37 +17,16 @@ const file = sync.require("../../matrix/file")
async function addReaction(data) {
const user = data.member?.user
assert.ok(user && user.username)
const parentID = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id) // 0 = primary
if (!parentID) return // Nothing can be done if the parent message was never bridged.
assert.equal(typeof parentID, "string")
let key
if (data.emoji.id) {
// Custom emoji
const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(data.emoji.id)
if (mxc) {
// The custom emoji is registered and we should send it
key = mxc
} else {
// The custom emoji is not registered. We will register it and then add it.
const mxc = await file.uploadDiscordFileToMxc(file.emoji(data.emoji.id, data.emoji.animated))
db.prepare("INSERT OR IGNORE INTO emoji (id, name, animated, mxc_url) VALUES (?, ?, ?, ?)").run(data.emoji.id, data.emoji.name, +!!data.emoji.animated, mxc)
key = mxc
// TODO: what happens if the matrix user also tries adding this reaction? the bridge bot isn't able to use that emoji...
}
} else {
// Default emoji
key = data.emoji.name
}
const roomID = await createRoom.ensureRoom(data.channel_id)
const senderMxid = await registerUser.ensureSimJoined(user, roomID)
const eventID = await api.sendEvent(roomID, "m.reaction", {
"m.relates_to": {
rel_type: "m.annotation",
event_id: parentID,
key
key: data.emoji.name
}
}, senderMxid)
return eventID

View file

@ -29,7 +29,7 @@ async function emojisToState(emojis) {
},
url
}
db.prepare("INSERT OR IGNORE INTO emoji (id, name, animated, mxc_url) VALUES (?, ?, ?, ?)").run(emoji.id, emoji.name, +!!emoji.animated, url)
db.prepare("INSERT OR IGNORE INTO emoji (emoji_id, animated, mxc_url) VALUES (?, ?, ?)").run(emoji.id, +!!emoji.animated, url)
}).catch(e => {
if (e.data.errcode === "M_TOO_LARGE") { // Very unlikely to happen. Only possible for 3x-series emojis uploaded shortly after animated emojis were introduced, when there was no 256 KB size limit.
return

View file

@ -41,10 +41,10 @@ function getDiscordParseCallbacks(message, useHTML) {
/** @param {{animated: boolean, name: string, id: string, type: "discordEmoji"}} node */
emoji: node => {
if (useHTML) {
const mxc = select("emoji", "mxc_url", "WHERE id = ?").pluck().get(node.id)
const mxc = select("emoji", "mxc_url", "WHERE emoji_id = ?").pluck().get(node.id)
if (mxc) {
return `<img data-mx-emoticon height="32" src="${mxc}" title=":${node.name}:" alt=":${node.name}:">`
} else { // We shouldn't get here since all emojis should have been added ahead of time in the messageToEvent function.
} else {
return `<img src="mxc://cadence.moe/${node.id}" data-mx-emoticon alt=":${node.name}:" title=":${node.name}:" height="24">`
}
} else {
@ -184,26 +184,6 @@ async function messageToEvent(message, guild, options = {}, di) {
}
}
// Handling emojis that we don't know about. The emoji has to be present in the DB for it to be picked up in the emoji markdown converter.
// So we scan the message ahead of time for all its emojis and ensure they are in the DB.
const emojiMatches = [...content.matchAll(/<(a?):([^:>]{2,20}):([0-9]+)>/g)]
const emojiDownloads = []
for (const match of emojiMatches) {
const id = match[3]
const name = match[2]
const animated = +!!match[1]
const row = select("emoji", "id", "WHERE id = ?").pluck().get(id)
if (!row) {
// The custom emoji is not registered. We will register it and then add it.
emojiDownloads.push(
file.uploadDiscordFileToMxc(file.emoji(id, animated)).then(mxc => {
db.prepare("INSERT OR IGNORE INTO emoji (id, name, animated, mxc_url) VALUES (?, ?, ?, ?)").run(id, name, animated, mxc)
})
)
}
}
await Promise.all(emojiDownloads)
// Star * prefix for fallback edits
if (options.includeEditFallbackStar) {
body = "* " + body

View file

@ -207,6 +207,7 @@ module.exports = {
async onReactionAdd(client, data) {
if (data.user_id === client.user.id) return // m2d reactions are added by the discord bot user - do not reflect them back to matrix.
discordCommandHandler.onReactionAdd(data)
if (data.emoji.id !== null) return // TODO: image emoji reactions
await addReaction.addReaction(data)
},

View file

@ -64,10 +64,9 @@ CREATE TABLE IF NOT EXISTS "lottie" (
PRIMARY KEY("id")
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS "emoji" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"emoji_id" TEXT NOT NULL,
"animated" INTEGER NOT NULL,
"mxc_url" TEXT NOT NULL,
PRIMARY KEY("id")
PRIMARY KEY("emoji_id")
) WITHOUT ROWID;
COMMIT;

View file

@ -70,9 +70,9 @@ INSERT INTO file (discord_url, mxc_url) VALUES
('https://cdn.discordapp.com/emojis/230201364309868544.png', 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'),
('https://cdn.discordapp.com/emojis/393635038903926784.gif', 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc');
INSERT INTO emoji (id, name, animated, mxc_url) VALUES
('230201364309868544', 'hippo', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'),
('393635038903926784', 'hipposcope', 1, 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc');
INSERT INTO emoji (emoji_id, animated, mxc_url) VALUES
('230201364309868544', 0, 'mxc://cadence.moe/qWmbXeRspZRLPcjseyLmeyXC'),
('393635038903926784', 1, 'mxc://cadence.moe/WbYqNlACRuicynBfdnPYtmvc');
INSERT INTO member_cache (room_id, mxid, displayname, avatar_url) VALUES
('!kLRqKKUQXcibIMtOpl:cadence.moe', '@cadence:cadence.moe', 'cadence [they]', NULL),

3
db/orm-utils.d.ts vendored
View file

@ -64,8 +64,7 @@ export type Models = {
}
emoji: {
id: string
name: string
emoji_id: string
animated: number
mxc_url: string
}

View file

@ -159,8 +159,8 @@ Can use custom transaction ID (?) to send the original timestamps to Matrix. See
```
pragma case_sensitive_like = 1;
insert into emoji select replace(substr(discord_url, 35), ".gif", "") as id, 1 as animated, mxc_url from file where discord_url like 'https://cdn.discordapp.com/emojis/%.gif';
insert into emoji select replace(substr(discord_url, 35), ".png", "") as id, 0 as animated, mxc_url from file where discord_url like 'https://cdn.discordapp.com/emojis/%.png';
insert into emoji select replace(substr(discord_url, 35), ".gif", "") as emoji_id, 1 as animated, mxc_url from file where discord_url like 'https://cdn.discordapp.com/emojis/%.gif';
insert into emoji select replace(substr(discord_url, 35), ".png", "") as emoji_id, 0 as animated, mxc_url from file where discord_url like 'https://cdn.discordapp.com/emojis/%.png';
```
# Various considerations

View file

@ -15,40 +15,27 @@ async function addReaction(event) {
const messageID = select("event_message", "message_id", "WHERE event_id = ? AND part = 0").pluck().get(event.content["m.relates_to"].event_id) // 0 = primary
if (!messageID) return // Nothing can be done if the parent message was never bridged.
const emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions
let discordPreferredEncoding
if (emoji.startsWith("mxc://")) {
// Custom emoji
const row = select("emoji", ["id", "name"], "WHERE mxc_url = ?").get(emoji)
if (row) {
// Great, we know exactly what this emoji is!
discordPreferredEncoding = encodeURIComponent(`${row.name}:${row.id}`)
} else {
// We don't have this emoji and there's no realistic way to just-in-time upload a new emoji somewhere.
// We can't try using a known emoji with the same name because we don't even know what the name is. We only have the mxc url.
// Sucks!
return
}
} else {
// Default emoji
// https://github.com/discord/discord-api-docs/issues/2723#issuecomment-807022205 ????????????
const encoded = encodeURIComponent(emoji)
const encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "")
// no need to sync the matrix member to the other side. but if I did need to, this is where I'd do it
const forceTrimmedList = [
"%F0%9F%91%8D", // 👍
"%E2%AD%90" // ⭐
]
let emoji = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions
let encoded = encodeURIComponent(emoji)
let encodedTrimmed = encoded.replace(/%EF%B8%8F/g, "")
discordPreferredEncoding =
( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed
: encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded
: encodedTrimmed)
// https://github.com/discord/discord-api-docs/issues/2723#issuecomment-807022205 ????????????
console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding)
}
const forceTrimmedList = [
"%F0%9F%91%8D", // 👍
"%E2%AD%90" // ⭐
]
return discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding) // acting as the discord bot itself
let discordPreferredEncoding =
( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed
: encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded
: encodedTrimmed)
console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding)
return discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding)
}
module.exports.addReaction = addReaction

View file

@ -120,25 +120,9 @@ turndownService.addRule("inlineLink", {
turndownService.addRule("emoji", {
filter: function (node, options) {
if (node.nodeName !== "IMG" || !node.hasAttribute("data-mx-emoticon") || !node.getAttribute("src")) return false
let row = select("emoji", ["id", "name", "animated"], "WHERE mxc_url = ?").get(node.getAttribute("src"))
if (!row) {
// We don't know what this is... but maybe we can guess based on the name?
const guessedName = node.getAttribute("title")?.replace?.(/^:|:$/g, "")
if (!guessedName) return false
for (const guild of discord.guilds.values()) {
/** @type {{name: string, id: string, animated: number}[]} */
// @ts-ignore
const emojis = guild.emojis
const match = emojis.find(e => e.name === guessedName) || emojis.find(e => e.name?.toLowerCase() === guessedName.toLowerCase())
if (match) {
row = match
break
}
}
}
const row = select("emoji", ["emoji_id", "animated"], "WHERE mxc_url = ?").get(node.getAttribute("src"))
if (!row) return false
node.setAttribute("data-emoji-id", row.id)
node.setAttribute("data-emoji-name", row.name)
node.setAttribute("data-emoji-id", row.emoji_id)
node.setAttribute("data-emoji-animated-char", row.animated ? "a" : "")
return true
},
@ -149,7 +133,8 @@ turndownService.addRule("emoji", {
/** @type {string} */
const animatedChar = node.getAttribute("data-emoji-animated-char")
/** @type {string} */
const name = node.getAttribute("data-emoji-name")
const title = node.getAttribute("title") || "__"
const name = title.replace(/^:|:$/g, "")
return `<${animatedChar}:${name}:${id}>`
}
})