Other kinds of reaction removals
This commit is contained in:
		
							parent
							
								
									c3040f19c2
								
							
						
					
					
						commit
						c1cbdfee82
					
				
					 5 changed files with 151 additions and 42 deletions
				
			
		| 
						 | 
					@ -9,6 +9,10 @@ const {discord, sync, db, select} = passthrough
 | 
				
			||||||
const api = sync.require("../../matrix/api")
 | 
					const api = sync.require("../../matrix/api")
 | 
				
			||||||
/** @type {import("../converters/emoji-to-key")} */
 | 
					/** @type {import("../converters/emoji-to-key")} */
 | 
				
			||||||
const emojiToKey = sync.require("../converters/emoji-to-key")
 | 
					const emojiToKey = sync.require("../converters/emoji-to-key")
 | 
				
			||||||
 | 
					/** @type {import("../../m2d/converters/utils")} */
 | 
				
			||||||
 | 
					const utils = sync.require("../../m2d/converters/utils")
 | 
				
			||||||
 | 
					/** @type {import("../../m2d/converters/emoji")} */
 | 
				
			||||||
 | 
					const emoji = sync.require("../../m2d/converters/emoji")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveDispatchData} data
 | 
					 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveDispatchData} data
 | 
				
			||||||
| 
						 | 
					@ -18,16 +22,77 @@ async function removeReaction(data) {
 | 
				
			||||||
	if (!roomID) return
 | 
						if (!roomID) return
 | 
				
			||||||
	const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id)
 | 
						const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id)
 | 
				
			||||||
	if (!eventIDForMessage) return
 | 
						if (!eventIDForMessage) return
 | 
				
			||||||
	const mxid = select("sim", "mxid", "WHERE discord_id = ?").pluck().get(data.user_id)
 | 
					 | 
				
			||||||
	if (!mxid) return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/** @type {Ty.Pagination<Ty.Event.Outer<Ty.Event.M_Reaction>>} */
 | 
						/** @type {Ty.Pagination<Ty.Event.Outer<Ty.Event.M_Reaction>>} */
 | 
				
			||||||
	const relations = await api.getRelations(roomID, eventIDForMessage, "m.annotation")
 | 
						const relations = await api.getRelations(roomID, eventIDForMessage, "m.annotation")
 | 
				
			||||||
	const key = await emojiToKey.emojiToKey(data.emoji)
 | 
						const key = await emojiToKey.emojiToKey(data.emoji)
 | 
				
			||||||
	const eventIDForReaction = relations.chunk.find(e => e.sender === mxid && e.content["m.relates_to"].key === key)
 | 
					 | 
				
			||||||
	if (!eventIDForReaction) return
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await api.redactEvent(roomID, eventIDForReaction.event_id, mxid)
 | 
						const wantToRemoveMatrixReaction = data.user_id === discord.application.id
 | 
				
			||||||
 | 
						for (const event of relations.chunk) {
 | 
				
			||||||
 | 
							if (event.content["m.relates_to"].key === key) {
 | 
				
			||||||
 | 
								const lookingAtMatrixReaction = !utils.eventSenderIsFromDiscord(event.sender)
 | 
				
			||||||
 | 
								if (lookingAtMatrixReaction && wantToRemoveMatrixReaction) {
 | 
				
			||||||
 | 
									// We are removing a Matrix user's reaction, so we need to redact from the correct user ID (not @_ooye_matrix_bridge).
 | 
				
			||||||
 | 
									// Even though the bridge bot only reacted once on Discord-side, multiple Matrix users may have
 | 
				
			||||||
 | 
									// reacted on Matrix-side. Semantically, we want to remove the reaction from EVERY Matrix user.
 | 
				
			||||||
 | 
									await api.redactEvent(roomID, event.event_id)
 | 
				
			||||||
 | 
									// Clean up the database
 | 
				
			||||||
 | 
									const hash = utils.getEventIDHash(event.event_id)
 | 
				
			||||||
 | 
									db.prepare("DELETE FROM reaction WHERE hashed_event_id = ?").run(hash)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (!lookingAtMatrixReaction && !wantToRemoveMatrixReaction) {
 | 
				
			||||||
 | 
									// We are removing a Discord user's reaction, so we just make the sim user remove it.
 | 
				
			||||||
 | 
									const mxid = select("sim", "mxid", "WHERE discord_id = ?").pluck().get(data.user_id)
 | 
				
			||||||
 | 
									await api.redactEvent(roomID, event.event_id, mxid)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveEmojiDispatchData} data
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function removeEmojiReaction(data) {
 | 
				
			||||||
 | 
						const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id)
 | 
				
			||||||
 | 
						if (!roomID) return
 | 
				
			||||||
 | 
						const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id)
 | 
				
			||||||
 | 
						if (!eventIDForMessage) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** @type {Ty.Pagination<Ty.Event.Outer<Ty.Event.M_Reaction>>} */
 | 
				
			||||||
 | 
						const relations = await api.getRelations(roomID, eventIDForMessage, "m.annotation")
 | 
				
			||||||
 | 
						const key = await emojiToKey.emojiToKey(data.emoji)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const event of relations.chunk) {
 | 
				
			||||||
 | 
							if (event.content["m.relates_to"].key === key) {
 | 
				
			||||||
 | 
								const mxid = utils.eventSenderIsFromDiscord(event.sender) ? event.sender : undefined
 | 
				
			||||||
 | 
								await api.redactEvent(roomID, event.event_id, mxid)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const discordPreferredEncoding = emoji.encodeEmoji(key, undefined)
 | 
				
			||||||
 | 
						db.prepare("DELETE FROM reaction WHERE message_id = ? AND encoded_emoji = ?").run(data.message_id, discordPreferredEncoding)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveAllDispatchData} data
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function removeAllReactions(data) {
 | 
				
			||||||
 | 
						const roomID = select("channel_room", "room_id", "WHERE channel_id = ?").pluck().get(data.channel_id)
 | 
				
			||||||
 | 
						if (!roomID) return
 | 
				
			||||||
 | 
						const eventIDForMessage = select("event_message", "event_id", "WHERE message_id = ? AND part = 0").pluck().get(data.message_id)
 | 
				
			||||||
 | 
						if (!eventIDForMessage) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/** @type {Ty.Pagination<Ty.Event.Outer<Ty.Event.M_Reaction>>} */
 | 
				
			||||||
 | 
						const relations = await api.getRelations(roomID, eventIDForMessage, "m.annotation")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (const event of relations.chunk) {
 | 
				
			||||||
 | 
							const mxid = utils.eventSenderIsFromDiscord(event.sender) ? event.sender : undefined
 | 
				
			||||||
 | 
							await api.redactEvent(roomID, event.event_id, mxid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						db.prepare("DELETE FROM reaction WHERE message_id = ?").run(data.message_id)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports.removeReaction = removeReaction
 | 
					module.exports.removeReaction = removeReaction
 | 
				
			||||||
 | 
					module.exports.removeEmojiReaction = removeEmojiReaction
 | 
				
			||||||
 | 
					module.exports.removeAllReactions = removeAllReactions
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -158,6 +158,12 @@ const utils = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				} else if (message.t === "MESSAGE_REACTION_REMOVE") {
 | 
									} else if (message.t === "MESSAGE_REACTION_REMOVE") {
 | 
				
			||||||
					await eventDispatcher.onReactionRemove(client, message.d)
 | 
										await eventDispatcher.onReactionRemove(client, message.d)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									} else if (message.t === "MESSAGE_REACTION_REMOVE_EMOJI") {
 | 
				
			||||||
 | 
										await eventDispatcher.onReactionEmojiRemove(client, message.d)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									} else if (message.t === "MESSAGE_REACTION_REMOVE_ALL") {
 | 
				
			||||||
 | 
										await eventDispatcher.onRemoveAllReactions(client, message.d)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} catch (e) {
 | 
								} catch (e) {
 | 
				
			||||||
				// Let OOYE try to handle errors too
 | 
									// Let OOYE try to handle errors too
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -214,13 +214,28 @@ module.exports = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param {import("./discord-client")} client
 | 
						 * @param {import("./discord-client")} client
 | 
				
			||||||
	 * @param {import("discord-api-types/v10").GatewayMessageReactionAddDispatchData} data
 | 
						 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveDispatchData} data
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	async onReactionRemove(client, data) {
 | 
						async onReactionRemove(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.
 | 
					 | 
				
			||||||
		await removeReaction.removeReaction(data)
 | 
							await removeReaction.removeReaction(data)
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param {import("./discord-client")} client
 | 
				
			||||||
 | 
						 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveEmojiDispatchData} data
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						async onReactionEmojiRemove(client, data) {
 | 
				
			||||||
 | 
							await removeReaction.removeEmojiReaction(data)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param {import("./discord-client")} client
 | 
				
			||||||
 | 
						 * @param {import("discord-api-types/v10").GatewayMessageReactionRemoveAllDispatchData} data
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						async onRemoveAllReactions(client, data) {
 | 
				
			||||||
 | 
							await removeReaction.removeAllReactions(data)
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param {import("./discord-client")} client
 | 
						 * @param {import("./discord-client")} client
 | 
				
			||||||
	 * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data
 | 
						 * @param {import("discord-api-types/v10").GatewayMessageDeleteDispatchData} data
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,6 +7,8 @@ const passthrough = require("../../passthrough")
 | 
				
			||||||
const {discord, sync, db, select} = passthrough
 | 
					const {discord, sync, db, select} = passthrough
 | 
				
			||||||
/** @type {import("../converters/utils")} */
 | 
					/** @type {import("../converters/utils")} */
 | 
				
			||||||
const utils = sync.require("../converters/utils")
 | 
					const utils = sync.require("../converters/utils")
 | 
				
			||||||
 | 
					/** @type {import("../converters/emoji")} */
 | 
				
			||||||
 | 
					const emoji = sync.require("../converters/emoji")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @param {Ty.Event.Outer<Ty.Event.M_Reaction>} event
 | 
					 * @param {Ty.Event.Outer<Ty.Event.M_Reaction>} event
 | 
				
			||||||
| 
						 | 
					@ -17,41 +19,9 @@ 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
 | 
						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.
 | 
						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
 | 
						const key = event.content["m.relates_to"].key // TODO: handle custom text or emoji reactions
 | 
				
			||||||
	let discordPreferredEncoding
 | 
						const discordPreferredEncoding = emoji.encodeEmoji(key, event.content.shortcode)
 | 
				
			||||||
	if (emoji.startsWith("mxc://")) {
 | 
						if (!discordPreferredEncoding) return
 | 
				
			||||||
		// Custom emoji
 | 
					 | 
				
			||||||
		let row = select("emoji", ["id", "name"], "WHERE mxc_url = ?").get(emoji)
 | 
					 | 
				
			||||||
		if (!row && event.content.shortcode) {
 | 
					 | 
				
			||||||
			// Use the name to try to find a known emoji with the same name.
 | 
					 | 
				
			||||||
			const name = event.content.shortcode.replace(/^:|:$/g, "")
 | 
					 | 
				
			||||||
			row = select("emoji", ["id", "name"], "WHERE name = ?").get(name)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (!row) {
 | 
					 | 
				
			||||||
			// We don't have this emoji and there's no realistic way to just-in-time upload a new emoji somewhere.
 | 
					 | 
				
			||||||
			// Sucks!
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		// Cool, we got an exact or a candidate emoji.
 | 
					 | 
				
			||||||
		discordPreferredEncoding = encodeURIComponent(`${row.name}:${row.id}`)
 | 
					 | 
				
			||||||
	} 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, "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const forceTrimmedList = [
 | 
					 | 
				
			||||||
			"%F0%9F%91%8D", // 👍
 | 
					 | 
				
			||||||
			"%E2%AD%90" // ⭐
 | 
					 | 
				
			||||||
		]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		discordPreferredEncoding =
 | 
					 | 
				
			||||||
			( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed
 | 
					 | 
				
			||||||
			: encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded
 | 
					 | 
				
			||||||
			: encodedTrimmed)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	await discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding) // acting as the discord bot itself
 | 
						await discord.snow.channel.createReaction(channelID, messageID, discordPreferredEncoding) // acting as the discord bot itself
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										53
									
								
								m2d/converters/emoji.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								m2d/converters/emoji.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					// @ts-check
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const assert = require("assert").strict
 | 
				
			||||||
 | 
					const Ty = require("../../types")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const passthrough = require("../../passthrough")
 | 
				
			||||||
 | 
					const {sync, select} = passthrough
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {string} emoji
 | 
				
			||||||
 | 
					 * @param {string | null | undefined} shortcode
 | 
				
			||||||
 | 
					 * @returns {string?}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function encodeEmoji(emoji, shortcode) {
 | 
				
			||||||
 | 
						let discordPreferredEncoding
 | 
				
			||||||
 | 
						if (emoji.startsWith("mxc://")) {
 | 
				
			||||||
 | 
							// Custom emoji
 | 
				
			||||||
 | 
							let row = select("emoji", ["id", "name"], "WHERE mxc_url = ?").get(emoji)
 | 
				
			||||||
 | 
							if (!row && shortcode) {
 | 
				
			||||||
 | 
								// Use the name to try to find a known emoji with the same name.
 | 
				
			||||||
 | 
								const name = shortcode.replace(/^:|:$/g, "")
 | 
				
			||||||
 | 
								row = select("emoji", ["id", "name"], "WHERE name = ?").get(name)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!row) {
 | 
				
			||||||
 | 
								// We don't have this emoji and there's no realistic way to just-in-time upload a new emoji somewhere.
 | 
				
			||||||
 | 
								// Sucks!
 | 
				
			||||||
 | 
								return null
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Cool, we got an exact or a candidate emoji.
 | 
				
			||||||
 | 
							discordPreferredEncoding = encodeURIComponent(`${row.name}:${row.id}`)
 | 
				
			||||||
 | 
						} 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, "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const forceTrimmedList = [
 | 
				
			||||||
 | 
								"%F0%9F%91%8D", // 👍
 | 
				
			||||||
 | 
								"%E2%AD%90", // ⭐
 | 
				
			||||||
 | 
								"%F0%9F%90%88", // 🐈
 | 
				
			||||||
 | 
							]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							discordPreferredEncoding =
 | 
				
			||||||
 | 
								( forceTrimmedList.includes(encodedTrimmed) ? encodedTrimmed
 | 
				
			||||||
 | 
								: encodedTrimmed !== encoded && [...emoji].length === 2 ? encoded
 | 
				
			||||||
 | 
								: encodedTrimmed)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							console.log("add reaction from matrix:", emoji, encoded, encodedTrimmed, "chosen:", discordPreferredEncoding)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return discordPreferredEncoding
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports.encodeEmoji = encodeEmoji
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue