Don't delete our reaction on Discord unless we have 0 of that reaction coming from Matrix #85

Open
ellie wants to merge 8 commits from ellie/out-of-your-element:ellie-fix-reacts into main
3 changed files with 1 additions and 144 deletions
Showing only changes of commit 22bebaf064 - Show all commits

View file

@ -1,143 +0,0 @@
// we are going to delete this file before committing
// just here so it's easy to see what our prior code was while programming the new version.
// @ts-check
const {EventEmitter} = require("events")
const passthrough = require("../../passthrough")
const {select, sync} = passthrough
/** @type {import("../../matrix/utils")} */
const utils = sync.require("../../matrix/utils")
/*
Due to Eventual Consistency(TM) an update/delete may arrive before the original message arrives
(or before the it has finished being bridged to an event).
In this case, wait until the original message has finished bridging, then retrigger the passed function.
*/
const DEBUG_RETRIGGER = false
function debugRetrigger(message) {
if (DEBUG_RETRIGGER) {
console.log(message)
}
}
const paused = new Set()
const emitter = new EventEmitter()
/**
* @template {(...args: any[]) => any} T
* @param {string} eventID
* @param {T} fn
* @param {Parameters<T>} rest
* @returns {boolean} false if the event was found and the function will be ignored, true if the event was not found and the function will be retriggered
*/
function eventNotFoundThenRetrigger(eventID, fn, ...rest) {
if (!paused.has(eventID)) {
const messageID = select("event_message", "message_id", {event_id: eventID}).pluck().get()
if (messageID) {
debugRetrigger(`[retrigger] OK eid <-> mid = ${eventID} <-> ${messageID}`)
return false // message was found so don't retrigger
}
}
waitThenRetrigger(eventID, fn, ...rest)
return true
}
/**
* @template {(...args: any[]) => any} T
* @param {string} messageID
* @param {T} fn
* @param {Parameters<T>} rest
* @returns {boolean} false if the event was found and the function will be ignored, true if the event was not found and the function will be retriggered
*/
function messageNotFoundThenRetrigger(messageID, fn, ...rest) {
if (!paused.has(messageID)) {
const eventID = select("event_message", "event_id", {message_id: messageID}).pluck().get()
if (eventID) {
debugRetrigger(`[retrigger] OK mid <-> eid = ${messageID} <-> ${eventID}`)
return false // event was found so don't retrigger
}
}
waitThenRetrigger(messageID, fn, ...rest)
return true
}
/**
* @template {(...args: any[]) => any} T
* @param {string} reactionEventID
* @param {T} fn
* @param {Parameters<T>} rest
* @returns {boolean} false if the event was found and the function will be ignored, true if the event was not found and the function will be retriggered
*/
function reactionNotFoundThenRetrigger(reactionEventID, fn, ...rest){
const reactionEventHash = utils.getEventIDHash(reactionEventID)
const reaction = select("reaction", "encoded_emoji", {hashed_event_id: reactionEventHash})
if (reaction) {
debugRetrigger(`[retrigger] OK eid <-> reaction = ${reactionEventID} <-> ${reactionEventHash}`)
return false
}
waitThenRetrigger(reactionEventID, fn, ...rest)
return true
}
/**
* @template {(...args: any[]) => any} T
* @param {string} id
* @param {T} fn
* @param {Parameters<T>} rest
*/
function waitThenRetrigger(id, fn, ...rest){
debugRetrigger(`[retrigger] WAIT id = ${id}`)
emitter.once(id, () => {
debugRetrigger(`[retrigger] TRIGGER id = ${id}`)
fn(...rest)
})
// if the event never arrives, don't trigger the callback, just clean up
setTimeout(() => {
if (emitter.listeners(id).length) {
debugRetrigger(`[retrigger] EXPIRE id = ${id}`)
}
emitter.removeAllListeners(id)
}, 60 * 1000) // 1 minute
}
/**
* Anything calling retrigger during the callback will be paused and retriggered after the callback resolves.
* @template T
* @param {string} messageID
* @param {Promise<T>} promise
* @returns {Promise<T>}
*/
async function pauseChanges(messageID, promise) {
try {
debugRetrigger(`[retrigger] PAUSE id = ${messageID}`)
paused.add(messageID)
return await promise
} finally {
debugRetrigger(`[retrigger] RESUME id = ${messageID}`)
paused.delete(messageID)
finishedBridging(messageID)
}
}
/**
* Triggers any pending operations that were waiting on the corresponding event ID.
* @param {string} id
*/
function finishedBridging(id) {
if (emitter.listeners(id).length) {
debugRetrigger(`[retrigger] EMIT id = ${id}`)
}
emitter.emit(id)
}
module.exports.eventNotFoundThenRetrigger = eventNotFoundThenRetrigger
module.exports.messageNotFoundThenRetrigger = messageNotFoundThenRetrigger
module.exports.reactionNotFoundThenRetrigger = reactionNotFoundThenRetrigger
module.exports.finishedBridging = finishedBridging
module.exports.pauseChanges = pauseChanges

View file

@ -34,7 +34,6 @@ function removeReaction(data, reactions, key) {
// Even though the bridge bot only reacted once on Discord-side, multiple Matrix users may have // 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. // reacted on Matrix-side. Semantically, we want to remove the reaction from EVERY Matrix user.
// Also need to clean up the database. // Also need to clean up the database.
console.log("ELLIE TEST: we are removing all matrix reactions, because we got a removal from Discord")
const hash = utils.getEventIDHash(eventID) const hash = utils.getEventIDHash(eventID)
removals.push({eventID, mxid: null, hash}) removals.push({eventID, mxid: null, hash})
} }

View file

@ -15,6 +15,7 @@ const mreq = sync.require("./matrix/mreq")
const api = sync.require("./matrix/api") const api = sync.require("./matrix/api")
const file = sync.require("./matrix/file") const file = sync.require("./matrix/file")
const sendEvent = sync.require("./m2d/actions/send-event") const sendEvent = sync.require("./m2d/actions/send-event")
const redact = sync.require("./m2d/actions/redact")
const eventDispatcher = sync.require("./d2m/event-dispatcher") const eventDispatcher = sync.require("./d2m/event-dispatcher")
const updatePins = sync.require("./d2m/actions/update-pins") const updatePins = sync.require("./d2m/actions/update-pins")
const speedbump = sync.require("./d2m/actions/speedbump") const speedbump = sync.require("./d2m/actions/speedbump")