Skip to content

Commit

Permalink
Save reminders in JSON
Browse files Browse the repository at this point in the history
  • Loading branch information
dfuchss committed Jul 27, 2024
1 parent 47cc5e8 commit 62bff0c
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 97 deletions.
7 changes: 3 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,13 @@

<dependencies>
<dependency>
<groupId>com.vdurmont</groupId>
<artifactId>emoji-java</artifactId>
<version>5.1.1</version>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>org.fuchss</groupId>
<artifactId>matrix-bot-base</artifactId>
<version>0.11.0</version>
<version>0.11.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand Down
11 changes: 0 additions & 11 deletions src/main/kotlin/org/fuchss/matrix/yarb/Extensions.kt

This file was deleted.

12 changes: 7 additions & 5 deletions src/main/kotlin/org/fuchss/matrix/yarb/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ private lateinit var commands: List<Command>
fun main() {
runBlocking {
val config = Config.load()

val matrixClient = getMatrixClient(config)
val matrixBot = MatrixBot(matrixClient, config)

val timer = Timer(true)
val reminderCommand = ReminderCommand(config, timer)
val timerManager = TimerManager(matrixBot, timer, config)

val reminderCommand = ReminderCommand(config, timerManager)
commands =
listOf(
HelpCommand(config, "YARB") {
Expand All @@ -41,10 +47,6 @@ fun main() {
reminderCommand
)

val matrixClient = getMatrixClient(config)

val matrixBot = MatrixBot(matrixClient, config)

// Command Handling
matrixBot.subscribeContent { event -> handleCommand(commands, event, matrixBot, config, ReminderCommand.COMMAND_NAME) }
matrixBot.subscribeContent { encEvent -> handleEncryptedCommand(commands, encEvent, matrixBot, config, ReminderCommand.COMMAND_NAME) }
Expand Down
144 changes: 144 additions & 0 deletions src/main/kotlin/org/fuchss/matrix/yarb/TimerManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package org.fuchss.matrix.yarb

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import net.folivo.trixnity.client.room.getTimelineEventReactionAggregation
import net.folivo.trixnity.client.room.message.reply
import net.folivo.trixnity.client.room.message.text
import net.folivo.trixnity.core.model.EventId
import net.folivo.trixnity.core.model.RoomId
import org.fuchss.matrix.bots.MatrixBot
import org.fuchss.matrix.bots.emoji
import org.fuchss.matrix.bots.matrixTo
import org.slf4j.LoggerFactory
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.time.LocalTime
import java.util.Timer
import java.util.TimerTask
import kotlin.time.Duration.Companion.minutes

class TimerManager(private val matrixBot: MatrixBot, javaTimer: Timer, config: Config) {
companion object {
val EMOJI = ":+1:".emoji()
private val logger = LoggerFactory.getLogger(TimerManager::class.java)
}

private val objectMapper = ObjectMapper().registerKotlinModule().registerModule(JavaTimeModule())
private val timerFileLocation = config.dataDirectory + "/timers.json"
private val timers = mutableListOf<TimerData>()

init {
val timerFile = File(timerFileLocation)
if (timerFile.exists()) {
val timersFromFile: List<TimerData> = objectMapper.readValue(timerFile)
timers.addAll(timersFromFile)
}
}

init {
val millisecondsToNextMinute = (60 - LocalTime.now().second) * 1000L
javaTimer.schedule(
object : TimerTask() {
override fun run() {
runBlocking {
logger.debug("Reminders: {}", timers)
val timerCopy = timers.toList()
val now = LocalTime.now()
for (timer in timerCopy) {
if (timer.timeToRemind.isAfter(now)) {
continue
}
removeTimer(timer)
remind(timer)
}
}
}
},
millisecondsToNextMinute,
1.minutes.inWholeMilliseconds
)
}

fun addTimer(
roomId: RoomId,
requestMessage: EventId,
timeToRemind: LocalTime,
content: String
) {
val timer = TimerData(roomId.full, requestMessage.full, timeToRemind, content, null)
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? {
val timerData = timers.find { it.requestMessage() == eventId } ?: return null
timers.remove(timerData)
saveTimers()
return timerData.botMessageId()
}

private fun removeTimer(timer: TimerData) {
timers.remove(timer)
saveTimers()
}

@Synchronized
private fun saveTimers() {
val tempFile = File(timerFileLocation + ".tmp")
objectMapper.writeValue(tempFile, timers)
val timerFile = File(timerFileLocation)
Files.move(tempFile.toPath(), timerFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
}

private suspend fun remind(timer: TimerData) {
try {
val roomId = timer.roomId()
val messageId = timer.botMessageId() ?: return

val reactions = matrixBot.room().getTimelineEventReactionAggregation(roomId, messageId).first().reactions
val peopleToRemind = reactions[EMOJI]?.filter { it != matrixBot.self() }?.map { it.matrixTo() }
if (peopleToRemind.isNullOrEmpty()) {
return
}

val timelineEvent = matrixBot.getTimelineEvent(roomId, messageId) ?: return
matrixBot.room().sendMessage(roomId) {
reply(timelineEvent)
text("'${timer.content}' ${peopleToRemind.joinToString(", ")}")
}
} catch (e: Exception) {
logger.error("Error during remind: ${e.message}", e)
}
}

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

fun requestMessage() = EventId(requestMessage)

fun botMessageId() = botMessageId?.let { EventId(it) }
}
}
87 changes: 10 additions & 77 deletions src/main/kotlin/org/fuchss/matrix/yarb/commands/ReminderCommand.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package org.fuchss.matrix.yarb.commands

import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import net.folivo.trixnity.client.room.getTimelineEventReactionAggregation
import net.folivo.trixnity.client.room.message.react
import net.folivo.trixnity.client.room.message.reply
import net.folivo.trixnity.client.room.message.text
Expand All @@ -14,51 +11,21 @@ import net.folivo.trixnity.core.model.events.m.RelationType
import net.folivo.trixnity.core.model.events.m.room.RoomMessageEventContent
import org.fuchss.matrix.bots.MatrixBot
import org.fuchss.matrix.bots.command.Command
import org.fuchss.matrix.bots.matrixTo
import org.fuchss.matrix.yarb.Config
import org.fuchss.matrix.yarb.emoji
import org.fuchss.matrix.yarb.TimerManager
import java.time.LocalTime
import java.util.Timer
import java.util.TimerTask
import kotlin.time.Duration.Companion.minutes

class ReminderCommand(private val config: Config, private val timer: Timer) : Command() {
class ReminderCommand(private val config: Config, private val timerManager: TimerManager) : Command() {
companion object {
const val COMMAND_NAME = "new"
private val TIME_REGEX = Regex("^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$")
private val EMOJI = ":+1:".emoji()
private val EMOJI = TimerManager.EMOJI
}

override val help: String = "Set a reminder for a specific time."
override val params: String = "<time|11:30> <message|Time for Lunch!>"
override val name: String = COMMAND_NAME

private val timers = mutableListOf<TimerData>()

init {
val millisecondsToNextMinute = (60 - LocalTime.now().second) * 1000L
timer.schedule(
object : TimerTask() {
override fun run() {
runBlocking {
logger.debug("Reminders: {}", timers)
val timerCopy = timers.toList()
val now = LocalTime.now()
for (timer in timerCopy) {
if (timer.timeToRemind.isAfter(now)) {
continue
}
timers.remove(timer)
remind(timer)
}
}
}
},
millisecondsToNextMinute,
1.minutes.inWholeMilliseconds
)
}

override suspend fun execute(
matrixBot: MatrixBot,
sender: UserId,
Expand Down Expand Up @@ -90,9 +57,7 @@ class ReminderCommand(private val config: Config, private val timer: Timer) : Co
}

val timelineEvent = matrixBot.getTimelineEvent(roomId, textEventId) ?: return

val timerData = TimerData(matrixBot, roomId, textEventId, time, timeXmessage[1], null)
timers.add(timerData)
timerManager.addTimer(roomId, textEventId, time, timeXmessage[1])

matrixBot.room().sendMessage(roomId) {
reply(timelineEvent)
Expand All @@ -117,9 +82,11 @@ class ReminderCommand(private val config: Config, private val timer: Timer) : Co
return
}

val timerData = this.timers.find { it.roomId == roomId && it.requestMessage == relatesTo.eventId } ?: return
val isRelated = timerManager.addBotMessageToTimer(relatesTo.eventId, eventId)
if (!isRelated) {
return
}

timerData.botMessageId = eventId
matrixBot.room().sendMessage(roomId) {
react(eventId, EMOJI)
}
Expand All @@ -138,12 +105,9 @@ class ReminderCommand(private val config: Config, private val timer: Timer) : Co
return
}

val timerData = this.timers.find { it.roomId == roomId && it.requestMessage == relatesTo.eventId } ?: return
timers.remove(timerData)
val relatedBotMessage = this.timerManager.removeByRequestMessage(relatesTo.eventId) ?: return

if (timerData.botMessageId != null) {
matrixBot.roomApi().redactEvent(roomId, timerData.botMessageId!!).getOrThrow()
}
matrixBot.roomApi().redactEvent(roomId, relatedBotMessage).getOrThrow()

val replace = (textEvent.relatesTo as? RelatesTo.Replace) ?: return
val newBody = (replace.newContent as? RoomMessageEventContent.TextBased.Text)?.body ?: return
Expand All @@ -154,35 +118,4 @@ class ReminderCommand(private val config: Config, private val timer: Timer) : Co
}
execute(matrixBot, senderId, roomId, parameters, replace.eventId, textEvent)
}

private suspend fun remind(timer: TimerData) {
try {
val matrixBot = timer.matrixBot
val roomId = timer.roomId
val messageId = timer.botMessageId ?: return

val reactions = matrixBot.room().getTimelineEventReactionAggregation(roomId, messageId).first().reactions
val peopleToRemind = reactions[EMOJI]?.filter { it != matrixBot.self() }?.map { it.matrixTo() }
if (peopleToRemind.isNullOrEmpty()) {
return
}

val timelineEvent = matrixBot.getTimelineEvent(roomId, messageId) ?: return
matrixBot.room().sendMessage(roomId) {
reply(timelineEvent)
text("'${timer.content}' ${peopleToRemind.joinToString(", ")}")
}
} catch (e: Exception) {
logger.error("Error during remind: ${e.message}", e)
}
}

private data class TimerData(
val matrixBot: MatrixBot,
val roomId: RoomId,
val requestMessage: EventId,
val timeToRemind: LocalTime,
val content: String,
var botMessageId: EventId?
)
}

0 comments on commit 62bff0c

Please sign in to comment.