Skip to content

Commit

Permalink
Simplify using outbox
Browse files Browse the repository at this point in the history
  • Loading branch information
dfuchss committed Aug 2, 2024
1 parent 2779a9b commit 815cd35
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 83 deletions.
11 changes: 11 additions & 0 deletions src/main/kotlin/org/fuchss/matrix/yarb/Helper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.fuchss.matrix.yarb

import net.folivo.trixnity.client.room.RoomService
import net.folivo.trixnity.core.model.EventId
import org.fuchss.matrix.bots.firstWithTimeout

suspend fun RoomService.getMessageId(transactionId: String): EventId? {
val outboxWithTransaction = this.getOutbox().firstWithTimeout { it[transactionId] != null } ?: return null
val transaction = outboxWithTransaction[transactionId] ?: return null
return transaction.firstWithTimeout { it?.eventId != null }?.eventId
}
3 changes: 2 additions & 1 deletion src/main/kotlin/org/fuchss/matrix/yarb/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,10 @@ fun main() {
matrixBot.subscribeContent { event -> reminderCommand.handleUserDeleteMessage(matrixBot, event) }

val loggedOut = matrixBot.startBlocking()
timer.cancel()

// After Shutdown
timer.cancel()

if (loggedOut) {
// Cleanup database
val databaseFiles = listOf(File(config.dataDirectory + "/database.mv.db"), File(config.dataDirectory + "/database.trace.db"))
Expand Down
87 changes: 20 additions & 67 deletions src/main/kotlin/org/fuchss/matrix/yarb/TimerManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,16 @@ import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import net.folivo.trixnity.client.room.RoomService
import net.folivo.trixnity.client.room.getTimelineEventReactionAggregation
import net.folivo.trixnity.client.room.message.mentions
import net.folivo.trixnity.client.room.message.reply
import net.folivo.trixnity.client.room.message.text
import net.folivo.trixnity.client.store.eventId
import net.folivo.trixnity.client.store.relatesTo
import net.folivo.trixnity.client.store.sender
import net.folivo.trixnity.core.model.EventId
import net.folivo.trixnity.core.model.RoomId
import net.folivo.trixnity.core.model.UserId
import net.folivo.trixnity.core.model.events.m.RelatesTo
import net.folivo.trixnity.core.model.events.m.RelationType
import org.fuchss.matrix.bots.MatrixBot
import org.fuchss.matrix.bots.emoji
import org.fuchss.matrix.bots.matrixTo
Expand Down Expand Up @@ -84,28 +74,20 @@ class TimerManager(private val matrixBot: MatrixBot, javaTimer: Timer, config: C
roomId: RoomId,
requestMessage: EventId,
timeToRemind: LocalTime,
content: String
content: String,
botMessageId: EventId,
botReactionMessageId: EventId
) {
val timer = TimerData(roomId.full, requestMessage.full, timeToRemind, content, null)
val timer = TimerData(roomId.full, requestMessage.full, timeToRemind, content, botMessageId.full, botReactionMessageId.full)
timers.add(timer)
saveTimers()
}

fun addBotMessageToTimer(
requestMessage: EventId,
botMessageId: EventId
): Boolean {
val timer = timers.find { it.requestMessage() == requestMessage } ?: return false
timer.botMessageId = botMessageId.full
saveTimers()
return true
}

fun removeByRequestMessage(eventId: EventId): EventId? {
fun removeByRequestMessage(eventId: EventId): Pair<EventId, EventId>? {
val timerData = timers.find { it.requestMessage() == eventId } ?: return null
timers.remove(timerData)
saveTimers()
return timerData.botMessageId()
return timerData.botMessageId() to timerData.botReactionMessageId()
}

private fun removeTimer(timer: TimerData) {
Expand All @@ -124,17 +106,15 @@ class TimerManager(private val matrixBot: MatrixBot, javaTimer: Timer, config: C
private suspend fun remind(timer: TimerData) {
try {
val roomId = timer.roomId()
val messageId = timer.botMessageId() ?: return
val timelineEvent = matrixBot.getTimelineEvent(roomId, messageId) ?: return

val remainingReactions = removeReactionOfBot(roomId, messageId)
val messageId = timer.botMessageId()

val remainingReactions = removeReactionOfBot(roomId, messageId, timer.botReactionMessageId())
if (remainingReactions.isEmpty()) {
return
}

matrixBot.room().sendMessage(roomId) {
reply(timelineEvent)
reply(messageId, null)
mentions(remainingReactions.toSet())
text("'${timer.content}' ${remainingReactions.joinToString(", ") { it.matrixTo() }}")
}
Expand All @@ -149,57 +129,30 @@ class TimerManager(private val matrixBot: MatrixBot, javaTimer: Timer, config: C
*/
private suspend fun removeReactionOfBot(
roomId: RoomId,
messageId: EventId
messageId: EventId,
reactionId: EventId
): List<UserId> {
val allReactions = matrixBot.room().getTimelineEventReactionAggregationWithIds(roomId, messageId).first()
matrixBot.roomApi().redactEvent(roomId, reactionId).getOrThrow()

val allReactions = matrixBot.room().getTimelineEventReactionAggregation(roomId, messageId).first().reactions
val reactions = allReactions[EMOJI] ?: return emptyList()

val botReaction = reactions.find { it.second == matrixBot.self() }
if (botReaction != null) {
matrixBot.roomApi().redactEvent(roomId, botReaction.first)
} else {
logger.warn("Could not find bot reaction to remove for message $messageId")
}
return reactions.filter { it.second != matrixBot.self() }.map { it.second }
return reactions.filter { it != matrixBot.self() }
}

// Adapted from net/folivo/trixnity/client/room/TimelineEventAggregation.kt
private fun RoomService.getTimelineEventReactionAggregationWithIds(
roomId: RoomId,
eventId: EventId
): Flow<Map<String, Set<Pair<EventId, UserId>>>> =
getTimelineEventRelations(roomId, eventId, RelationType.Annotation)
.map { it?.keys.orEmpty() }
.map { relations ->
coroutineScope {
relations.map { relatedEvent ->
async {
withTimeoutOrNull(1.minutes) { getTimelineEvent(roomId, relatedEvent).first() }
}
}.awaitAll()
}.filterNotNull()
.mapNotNull {
val relatesTo = it.relatesTo as? RelatesTo.Annotation ?: return@mapNotNull null
val key = relatesTo.key ?: return@mapNotNull null
key to (it.eventId to it.sender)
}
.distinct()
.groupBy { it.first }
.mapValues { entry -> entry.value.map { it.second }.toSet() }
}

private data class TimerData(
@JsonProperty val roomId: String,
@JsonProperty val requestMessage: String,
@JsonProperty val timeToRemind: LocalTime,
@JsonProperty val content: String,
@JsonProperty var botMessageId: String?
@JsonProperty val botMessageId: String,
@JsonProperty val botReactionMessageId: String
) {
fun roomId() = RoomId(roomId)

fun requestMessage() = EventId(requestMessage)

fun botMessageId() = botMessageId?.let { EventId(it) }
fun botMessageId() = EventId(botMessageId)

fun botReactionMessageId() = EventId(botReactionMessageId)
}
}
37 changes: 22 additions & 15 deletions src/main/kotlin/org/fuchss/matrix/yarb/commands/ReminderCommand.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import net.folivo.trixnity.core.model.events.roomIdOrNull
import net.folivo.trixnity.core.model.events.senderOrNull
import org.fuchss.matrix.bots.MatrixBot
import org.fuchss.matrix.bots.command.Command
import org.fuchss.matrix.bots.firstWithTimeout
import org.fuchss.matrix.yarb.Config
import org.fuchss.matrix.yarb.TimerManager
import org.fuchss.matrix.yarb.getMessageId
import java.time.LocalTime

class ReminderCommand(private val config: Config, private val timerManager: TimerManager) : Command() {
Expand Down Expand Up @@ -62,24 +62,29 @@ class ReminderCommand(private val config: Config, private val timerManager: Time
return
}

val timelineEvent = matrixBot.getTimelineEvent(roomId, textEventId) ?: return
timerManager.addTimer(roomId, textEventId, time, timeXmessage[1])

val transactionId =
val botMessageTransactionId =
matrixBot.room().sendMessage(roomId) {
reply(timelineEvent)
reply(textEventId, null)
text("I'll remind all people at $time with '${timeXmessage[1]}'. If you want to receive a message please click on $EMOJI")
}

val outboxWithTransaction = matrixBot.room().getOutbox().firstWithTimeout { it[transactionId] != null }
if (outboxWithTransaction == null) {
logger.error("Cannot find outbox")
val botMessageId = matrixBot.room().getMessageId(botMessageTransactionId)
if (botMessageId == null) {
logger.error("Could not send bot message :( -- TransactionId: {}", botMessageTransactionId)
return
}
val botMessageId = outboxWithTransaction[transactionId]?.firstWithTimeout { it?.eventId != null }?.eventId ?: return
matrixBot.room().sendMessage(roomId) {
react(botMessageId, EMOJI)

val botReactionMessageTransactionId =
matrixBot.room().sendMessage(roomId) {
react(botMessageId, EMOJI)
}
val botReactionMessageId = matrixBot.room().getMessageId(botReactionMessageTransactionId)
if (botReactionMessageId == null) {
logger.error("Could not send bot reaction message :( -- TransactionId: {}", botReactionMessageTransactionId)
return
}

timerManager.addTimer(roomId, textEventId, time, timeXmessage[1], botMessageId, botReactionMessageId)
}

suspend fun handleUserDeleteMessage(
Expand All @@ -90,8 +95,9 @@ class ReminderCommand(private val config: Config, private val timerManager: Time
return
}

val botMessage = timerManager.removeByRequestMessage(event.content.redacts) ?: return
val roomId = event.roomIdOrNull ?: return
val (botMessage, botReaction) = timerManager.removeByRequestMessage(event.content.redacts) ?: return
matrixBot.roomApi().redactEvent(roomId, botReaction).getOrThrow()
matrixBot.roomApi().redactEvent(roomId, botMessage).getOrThrow()
}

Expand All @@ -108,9 +114,10 @@ class ReminderCommand(private val config: Config, private val timerManager: Time
return
}

val relatedBotMessage = this.timerManager.removeByRequestMessage(relatesTo.eventId) ?: return
val (botMessage, botReaction) = this.timerManager.removeByRequestMessage(relatesTo.eventId) ?: return

matrixBot.roomApi().redactEvent(roomId, relatedBotMessage).getOrThrow()
matrixBot.roomApi().redactEvent(roomId, botReaction).getOrThrow()
matrixBot.roomApi().redactEvent(roomId, botMessage).getOrThrow()

val replace = (textEvent.relatesTo as? RelatesTo.Replace) ?: return
val newBody = (replace.newContent as? RoomMessageEventContent.TextBased.Text)?.body ?: return
Expand Down

0 comments on commit 815cd35

Please sign in to comment.