Skip to content

Commit

Permalink
Do notification updates in parallel
Browse files Browse the repository at this point in the history
  • Loading branch information
NyCodeGHG committed Jul 25, 2022
1 parent 11efc02 commit 586df83
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 94 deletions.
5 changes: 4 additions & 1 deletion bot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
}

group = "dev.nycode"
version = "0.5.0"
version = "0.5.1"

repositories {
mavenCentral()
Expand Down Expand Up @@ -47,6 +47,9 @@ tasks {
assembleBot {
bundledPlugins.set(listOf("[email protected]", "[email protected]"))
}
runBot {
environment["DOWNLOAD_PLUGINS"] = "[email protected],[email protected]"
}
}

idea {
Expand Down
13 changes: 12 additions & 1 deletion bot/src/main/kotlin/RegenbogenICEPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,24 @@ import dev.schlaubi.mikbot.plugin.api.Plugin
import dev.schlaubi.mikbot.plugin.api.PluginMain
import dev.schlaubi.mikbot.plugin.api.PluginWrapper
import dev.schlaubi.mikbot.plugin.api.util.AllShardsReadyEvent
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.*
import kotlinx.coroutines.cancel
import org.koin.core.component.inject

@PluginMain
class RegenbogenICEPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {

private val rainbowICE = RainbowICE()
private val rainbowICE = RainbowICE {
httpClient = HttpClient(OkHttp) {
install(HttpRequestRetry) {
exponentialDelay()
maxRetries = 20
retryOnServerErrors(3)
}
}
}
private val marudor = Marudor()

override suspend fun ExtensibleBotBuilder.apply() {
Expand Down
125 changes: 72 additions & 53 deletions bot/src/main/kotlin/notification/NotificationSender.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import dev.schlaubi.hafalsch.rainbow_ice.entity.TrainVehicle
import dev.schlaubi.mikbot.plugin.api.pluginSystem
import dev.schlaubi.mikbot.plugin.api.util.embed
import kotlinx.datetime.*
import org.jetbrains.annotations.PropertyKey

suspend fun checkNotification(allNotifications: List<Notification>): Map<Snowflake, List<UserNotification>> {
val cache = NotificationCache()
Expand Down Expand Up @@ -59,6 +60,20 @@ private val TrainVehicle.Trip.Stop.plannedTime

private val kord by KordExContext.get().inject<Kord>()

@JvmInline
value class UserContext(val userId: Snowflake)

@Suppress("UNCHECKED_CAST")
private suspend fun UserContext.translate(
@PropertyKey(resourceBundle = "translations.regenbogen_ice.strings") key: String,
vararg args: String = arrayOf()
) = pluginSystem.translate(
key,
"regenbogen_ice",
userLocaleCollection.findOneById(userId)?.locale,
args as Array<Any?>
)

suspend fun buildNotificationMessage(
userId: Snowflake,
userNotification: List<UserNotification>
Expand All @@ -68,65 +83,69 @@ suspend fun buildNotificationMessage(
if (userNotification.isEmpty()) {
return null
}
return userNotification.map { it.trip.firstStopDate }.distinct() to userNotification.groupBy(
UserNotification::train
).map { (train, trainNotifications) ->
embed {
title = pluginSystem.translate(
"notification.train_near_you",
"regenbogen_ice",
userLocaleCollection.findOneById(userId)?.locale,
arrayOf(train.displayName)
)
description = buildString {
trainNotifications.groupBy { it.trip }.toList()
.groupBy { it.first.firstStopDate }
.forEach { (date, tripData) ->
append(
date.atStartOfDayIn(TimeZone.UTC).toMessageFormat(
DiscordTimestampStyle.LongDate
with(UserContext(user.id)) {
return userNotification.map { it.trip.firstStopDate }
.distinct() to userNotification.groupBy(
UserNotification::train
).map { (train, trainNotifications) ->
embed {
title = translate("notification.train_near_you", train.displayName)
description = buildString {
trainNotifications.groupBy { it.trip }.toList()
.groupBy { it.first.firstStopDate }
.forEach { (date, tripData) ->
append(
date.atStartOfDayIn(TimeZone.UTC).toMessageFormat(
DiscordTimestampStyle.LongDate
)
)
)
appendLine()
for ((trip, tripNotifications) in tripData.sortedBy { it.first.safeStops.first().actualTime }) {
appendLine()
val nextStop = trip.nextStation
append(trip.linkedDisplayName)
append(' ')
append(Emojis.pushpin)
append(' ')
append(trip.safeStops.last().station)
appendLine()
for (tripNotification in tripNotifications.sortedBy { it.plannedTime }) {
if (nextStop?.station == tripNotification.station) {
append(Emojis.blueCircle)
} else {
append(Emojis.redCircle)
}
append(" - ")
append(
formatTrainTime(
tripNotification.actualTime,
tripNotification.plannedTime
)
)
append(" **")
append(tripNotification.station.strikethroughIf { tripNotification.actualTime.isPast })
append("** ")
for ((trip, tripNotifications) in tripData.sortedBy { it.first.safeStops.first().actualTime }) {
appendLine()
}
if (trip.isActive && tripNotifications.none { it.station == nextStop?.station }) {
appendLine()
append("Nächster Halt: ")
append(formatTrainTime(nextStop?.actualTime, nextStop?.plannedTime))
appendLine()
append("**")
append(nextStop?.station)
append("**")
val nextStop = trip.nextStation
append(trip.linkedDisplayName)
append(' ')
append(Emojis.pushpin)
append(' ')
append(trip.safeStops.last().station)
appendLine()
for (tripNotification in tripNotifications.sortedBy { it.plannedTime }) {
if (nextStop?.station == tripNotification.station) {
append(Emojis.blueCircle)
} else {
append(Emojis.redCircle)
}
append(" - ")
append(
formatTrainTime(
tripNotification.actualTime,
tripNotification.plannedTime
)
)
append(" **")
append(tripNotification.station.strikethroughIf { tripNotification.actualTime.isPast })
append("** ")
appendLine()
}
if (trip.isActive && tripNotifications.none { it.station == nextStop?.station }) {
appendLine()
append(translate("notification.next_stop"))
append(' ')
append(
formatTrainTime(
nextStop?.actualTime,
nextStop?.plannedTime
)
)
appendLine()
append("**")
append(nextStop?.station)
append("**")
appendLine()
}
}
}
}
}
}
}
}
Expand Down
5 changes: 3 additions & 2 deletions bot/src/main/kotlin/presence/PresenceHealthCheck.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package dev.nycode.regenbogenice.presence

import com.kotlindiscord.kord.extensions.koin.KordExKoinComponent
import dev.schlaubi.mikbot.core.health.check.HealthCheck
import kotlinx.coroutines.isActive
import kotlinx.coroutines.ensureActive
import org.koin.core.component.inject
import org.pf4j.Extension

Expand All @@ -12,6 +12,7 @@ class PresenceHealthCheck : HealthCheck, KordExKoinComponent {
private val presence by inject<RailTrackPresence>()

override suspend fun checkHealth(): Boolean {
return presence.isActive
presence.ensureActive()
return presence.isRunning
}
}
85 changes: 48 additions & 37 deletions bot/src/main/kotlin/presence/RailTrackPresence.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import dev.schlaubi.mikbot.plugin.api.io.Database
import dev.schlaubi.mikbot.plugin.api.io.getCollection
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.sync.withPermit
import org.litote.kmongo.and
import org.litote.kmongo.eq
import org.litote.kmongo.`in`
Expand All @@ -24,22 +26,23 @@ import kotlin.time.Duration.Companion.seconds

private const val REGENBOGEN_ICE_TZN = "304"

class RailTrackPresence(private val kord: Kord, private val database: Database) : CoroutineScope {
class RailTrackPresence(private val kord: Kord, database: Database) : CoroutineScope {
override val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob()

private val isRunning = Mutex()
private val runningMutex = Mutex()

val isRunning: Boolean
get() = runningMutex.isLocked

fun start() = launch {
isRunning.withLock {
runningMutex.withLock {
while (isActive) {
val (_, trip) = fetchCurrentTrip(REGENBOGEN_ICE_TZN)
?: continue
kord.editPresence {
watching("${trip.displayName} to ${trip.destinationStation}")
}
launch {
checkNotifications()
}
checkNotifications()
delay(30.seconds)
}
}
Expand All @@ -48,38 +51,46 @@ class RailTrackPresence(private val kord: Kord, private val database: Database)
private val sentNotifications = database.getCollection<SentNotification>("sent_notifications")
private suspend fun checkNotifications() =
sentryTransaction("checkNotifications()", "notifications") {
val allNotifications = childTransaction("notifications", "Resolve notifications") {
checkNotification(notificationCollection.find().toList())
}
allNotifications.forEach { (user, notifications) ->
childTransaction("notifications", "Resolve notification for user") {
val (days, embeds) = buildNotificationMessage(user, notifications)
?: return@forEach
val existingNotification =
sentNotifications.findOne(
and(
SentNotification::user eq user,
SentNotification::days `in` days,
)
)
val channel = kord.getUser(user)?.getDmChannelOrNull() ?: return
if (existingNotification != null) {
channel.getMessageOrNull(existingNotification.messageId)?.edit {
this.embeds = embeds.toMutableList()
}
} else {
val message = channel.createMessage {
this.embeds.addAll(embeds)
coroutineScope {
val allNotifications = childTransaction("notifications", "Resolve notifications") {
checkNotification(notificationCollection.find().toList())
}
val semaphore = Semaphore(6)
allNotifications.forEach { (user, notifications) ->
semaphore.withPermit {
launch {
childTransaction("notifications", "Resolve notification for user") {
val (days, embeds) = buildNotificationMessage(user, notifications)
?: return@launch
val existingNotification =
sentNotifications.findOne(
and(
SentNotification::user eq user,
SentNotification::days `in` days,
)
)
val channel = kord.getUser(user)?.getDmChannelOrNull() ?: return@launch
if (existingNotification != null) {
channel.getMessageOrNull(existingNotification.messageId)?.edit {
this.embeds = embeds.toMutableList()
}
sentNotifications.save(existingNotification.copy(days = days))
} else {
val message = channel.createMessage {
this.embeds.addAll(embeds)
}
sentNotifications.save(
SentNotification(
newId(),
user,
message.id,
message.channelId,
days
)
)
}
}
}
sentNotifications.save(
SentNotification(
newId(),
user,
message.id,
message.channelId,
days
)
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ converter.station.station=Haltestelle
converter.station.not_found=Haltestelle `{0}` nicht gefunden.
converter.station.empty_station=Haltestelle konnte nicht gefunden werden.
notification.train_near_you=Der Zug {0} ist bald in deiner Nähe.
notification.next_stop=Nächster Halt:
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ commands.notification.remove.argument.train.description=The train of the notific
converter.station.station=Station
converter.station.not_found=Station `{0}` not found
notification.train_near_you=The train {0} will soon be near you
notification.next_stop=Next Stop:

0 comments on commit 586df83

Please sign in to comment.