Use an internal event bus/dispatcher?
This would be a pretty major refactoring, if not a rewrite. The dispatcher pattern would allow us to instrument the bot better and build more functionality around it. For example, if we wanted to add some code which monitors for spammy ban lists to take some other action, that code could simply attach itself to the dispatcher and do its thing.
The instrumentation benefits include improved logging (we can just dump the dispatcher to the log) and faster, more reliable, tests. This test is an example of something which is already pretty close to using a dispatcher: it's relying on a signal from the event emitter to determine that an event was seen, but not redacted. If the protection in question simply did a dispatcher.send("nsfw", eventId, {actioned: false}), then the test could watch for that instead (and the runtime application logs would be clear that the event ID wasn't being processed for whatever reason).
A danger is everything connected to the dispatcher needs to be fast because while NodeJS can do async code, it's still a single thread. Element Web/Desktop has an internal dispatcher and occasionally spammy dispatches can slow down the whole application because it "wakes" every single listener.
Using a central event emitter as an event bus instead of a dispatcher could work to scope the dispatches to things that care, but can create a star-like call pattern instead of a bus pattern, which may be harder to debug/log when issues come up.
This looks a lot like the matrix-protection-suite and Draupnir meow.
I generally dislike dispatcher patterns chiefly due to my time with JS/React SDK. It makes it nigh on impossible to identify control flow: you just see something be emitted and then have to hope like hell that the listeners fire in the right order, don't block too long, etc. Alternatively, you just implement the listener and have to hope that it's called at the precise time you want (a good example being event sending hooks and whether they fire pre-request or post-request). But to really know that you need to see what each listener is doing and what each emitter of that type is doing, so it ends up greatly expanding the number of places you need to look before doing a code change. As opposed to, you know, calling a function.
The dispatcher pattern can work if you're very disciplined about what you do in the listeners: for example, don't emit more stuff in the listener and cause cascading dispatches. React SDK for one, is not disciplined, and the temptation is akin to using global variables. It's easy, it's quick, it works for very small projects. Misuse and overuse it though and you end up with control flow that uses the dispatcher needlessly, all in the name of loosely coupled components. A better solution IMO is traditional dependency injection (your logic explicitly states what things/dependencies it needs to work), though it then becomes a pain to setup and configure your dependencies (which is why things like Spring exist). However, I would much rather wrangle the order I create my dependencies and pass them down the call stack up-front when the program starts than to let the complexity demon ravage the codebase post-startup.
For a comparison to Draupnir’s matrix-protection-suite: we don't use a central dispatcher for events. Instead, we use narrowly scoped revision issuers, which are event emitters that track immutable snapshots of state. While revision issuers can depend on one another, these relationships are explicit and established during construction. Consumers of revisions cannot emit new revisions on the revision issuer, only the issuer itself can do that. So we have a loosely coupled and disciplined system in place already. And they're easy to isolate and test. Cascading in theory could happen only through applying effects on Matrix and from there Matrix events trickling back into the root of the system as new data.
We have some documentation about the pattern here https://the-draupnir-project.github.io/draupnir-documentation/matrix-protection-suite/concepts/revisions