mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-01-24 21:08:27 -06:00
Create new RRule implementation
This should allow me to support more complex recurring rules once I figure out how to implement the user-facing input
This commit is contained in:
@@ -9,10 +9,9 @@ import kotlinx.coroutines.reactor.awaitSingleOrNull
|
||||
import org.dreamexposure.discal.client.commands.SlashCommand
|
||||
import org.dreamexposure.discal.core.business.*
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.enums.event.EventFrequency
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.`object`.new.Event
|
||||
import org.dreamexposure.discal.core.`object`.new.EventRecurrence
|
||||
import org.dreamexposure.discal.core.`object`.new.EventWizardState
|
||||
import org.dreamexposure.discal.core.`object`.new.GuildSettings
|
||||
import org.dreamexposure.discal.core.utils.getCommonMsg
|
||||
@@ -484,8 +483,8 @@ class EventCommand(
|
||||
val frequency = event.options[0].getOption("frequency")
|
||||
.flatMap(ApplicationCommandInteractionOption::getValue)
|
||||
.map(ApplicationCommandInteractionOptionValue::asString)
|
||||
.map(EventFrequency.Companion::fromValue)
|
||||
.orElse(EventFrequency.WEEKLY)
|
||||
.map(EventRecurrence.Frequency::valueOf)
|
||||
.orElse(EventRecurrence.Frequency.WEEKLY)
|
||||
val interval = event.options[0].getOption("interval")
|
||||
.flatMap(ApplicationCommandInteractionOption::getValue)
|
||||
.map(ApplicationCommandInteractionOptionValue::asLong)
|
||||
@@ -510,7 +509,7 @@ class EventCommand(
|
||||
.awaitSingle()
|
||||
|
||||
val modifiedWizard = if (shouldRecur)
|
||||
existingWizard.copy(entity = existingWizard.entity.copy(recur = true, recurrence = Recurrence(frequency, interval, count)))
|
||||
existingWizard.copy(entity = existingWizard.entity.copy(recur = true, recurrence = EventRecurrence(frequency, interval, count)))
|
||||
else existingWizard.copy(entity = existingWizard.entity.copy(recur = false, recurrence = null))
|
||||
calendarService.putEventWizard(modifiedWizard)
|
||||
|
||||
|
||||
@@ -400,7 +400,7 @@ class EmbedService(
|
||||
|
||||
if (wizard.entity.recurrence != null) builder.addField(
|
||||
getEmbedMessage("event", "wizard.field.recurrence", settings.locale),
|
||||
wizard.entity.recurrence.toHumanReadable(),
|
||||
wizard.entity.recurrence.asHumanReadable(),
|
||||
true
|
||||
) else if (wizard.editing && wizard.entity.id != null && wizard.entity.id.contains("_")) builder.addField(
|
||||
getEmbedMessage("event", "wizard.field.recurrence", settings.locale),
|
||||
|
||||
@@ -11,11 +11,7 @@ import org.dreamexposure.discal.core.crypto.KeyGenerator
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.exceptions.ApiException
|
||||
import org.dreamexposure.discal.core.extensions.google.asInstant
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.`object`.new.Calendar
|
||||
import org.dreamexposure.discal.core.`object`.new.CalendarMetadata
|
||||
import org.dreamexposure.discal.core.`object`.new.Event
|
||||
import org.dreamexposure.discal.core.`object`.new.EventMetadata
|
||||
import org.dreamexposure.discal.core.`object`.new.*
|
||||
import org.springframework.stereotype.Component
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -197,7 +193,7 @@ class GoogleCalendarProviderService(
|
||||
event.colorId = spec.color.id.toString()
|
||||
|
||||
if (spec.recur && spec.recurrence != null)
|
||||
event.recurrence = listOf(spec.recurrence.toRRule())
|
||||
event.recurrence = listOf(spec.recurrence.asRRule())
|
||||
|
||||
// Create event in google
|
||||
val response = googleCalendarApiWrapper.createEvent(calendar.metadata, event)
|
||||
@@ -240,11 +236,11 @@ class GoogleCalendarProviderService(
|
||||
if (spec.recur != null) {
|
||||
if (spec.recur) {
|
||||
//event now recurs, add the RRUle.
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.toRRule()) }
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.asRRule()) }
|
||||
}
|
||||
} else {
|
||||
//Recur equals null, so it's not changing whether its recurring, so handle if RRule changes only
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.toRRule()) }
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.asRRule()) }
|
||||
}
|
||||
|
||||
// Okay, all values are set, let's patch this event now
|
||||
@@ -320,7 +316,7 @@ class GoogleCalendarProviderService(
|
||||
.atZone(calendar.timezone)
|
||||
.toInstant(),
|
||||
recur = !baseEvent.recurrence.isNullOrEmpty(),
|
||||
recurrence = if (baseEvent.recurrence.isNullOrEmpty()) Recurrence() else Recurrence.fromRRule(baseEvent.recurrence[0]),
|
||||
recurrence = if (baseEvent.recurrence.isNullOrEmpty()) EventRecurrence() else EventRecurrence.fromRRule(baseEvent.recurrence[0]),
|
||||
image = metadata.imageLink,
|
||||
timezone = calendar.timezone,
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.dreamexposure.discal.core.`object`.event
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.dreamexposure.discal.core.enums.event.EventFrequency
|
||||
|
||||
@Deprecated("Use new EventRecurrence class")
|
||||
@Serializable
|
||||
data class Recurrence(
|
||||
val frequency: EventFrequency = EventFrequency.DAILY,
|
||||
@@ -25,14 +26,14 @@ data class Recurrence(
|
||||
val inter = c.replace("INTERVAL=", "")
|
||||
try {
|
||||
recur = recur.copy(interval = inter.toInt())
|
||||
} catch (ignore: NumberFormatException) {
|
||||
} catch (_: NumberFormatException) {
|
||||
}
|
||||
}
|
||||
c.contains("COUNT=") -> {
|
||||
val con = c.replaceAfter("COUNT=", "")
|
||||
try {
|
||||
recur = recur.copy(count = con.toInt())
|
||||
} catch (ignore: NumberFormatException) {
|
||||
} catch (_: NumberFormatException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.dreamexposure.discal.core.`object`.new
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
@@ -20,7 +19,7 @@ data class Event(
|
||||
val start: Instant,
|
||||
val end: Instant,
|
||||
val recur: Boolean,
|
||||
val recurrence: Recurrence,
|
||||
val recurrence: EventRecurrence,
|
||||
val image: String,
|
||||
val timezone: ZoneId,
|
||||
) {
|
||||
@@ -67,7 +66,7 @@ data class Event(
|
||||
val start: Instant?,
|
||||
val end: Instant?,
|
||||
val recur: Boolean,
|
||||
val recurrence: Recurrence?,
|
||||
val recurrence: EventRecurrence?,
|
||||
val image: String?,
|
||||
val timezone: ZoneId,
|
||||
)
|
||||
@@ -81,7 +80,7 @@ data class Event(
|
||||
val location: String?,
|
||||
val image: String?,
|
||||
val recur: Boolean,
|
||||
val recurrence: Recurrence?,
|
||||
val recurrence: EventRecurrence?,
|
||||
)
|
||||
|
||||
data class UpdateSpec(
|
||||
@@ -94,7 +93,7 @@ data class Event(
|
||||
val location: String?,
|
||||
val image: String?,
|
||||
val recur: Boolean?,
|
||||
val recurrence: Recurrence?,
|
||||
val recurrence: EventRecurrence?,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.dreamexposure.discal.core.`object`.new
|
||||
|
||||
import java.time.Month
|
||||
|
||||
data class EventRecurrence(
|
||||
val frequency: Frequency = Frequency.DAILY,
|
||||
val interval: Int = 1,
|
||||
val count: Int? = null,
|
||||
val bySetPos: SetPos? = null,
|
||||
val byDay: List<Day> = emptyList(),
|
||||
val byMonthDay: Int? = null,
|
||||
val byMonth: Month? = null,
|
||||
) {
|
||||
// Companion object to translate from rrule string
|
||||
companion object {
|
||||
fun fromRRule(rrule: String): EventRecurrence {
|
||||
var frequency = Frequency.DAILY
|
||||
var interval = 1
|
||||
var count: Int? = null
|
||||
var bySetPos: SetPos? = null
|
||||
var byDay = emptyList<Day>()
|
||||
var byMonthDay: Int? = null
|
||||
var byMonth: Month? = null
|
||||
|
||||
rrule.replace("RRULE:", "").split(";").forEach {
|
||||
when {
|
||||
it.contains("FREQ=") -> frequency = Frequency.valueOf(it.replace("FREQ=", ""))
|
||||
it.contains("INTERVAL=") -> try {
|
||||
interval = it.replace("INTERVAL=", "").toInt()
|
||||
} catch (_: NumberFormatException) {}
|
||||
it.contains("COUNT=") -> try {
|
||||
count = it.replace("COUNT=", "").toInt()
|
||||
} catch (_: NumberFormatException) {}
|
||||
it.contains("BYSETPOS=") -> try {
|
||||
bySetPos = SetPos.entries.firstOrNull { v -> v.value == it.replace("BYSETPOS=", "").toInt() }
|
||||
} catch (_: NumberFormatException) {}
|
||||
it.contains("BYDAY=") -> byDay = it.replace("BYDAY=", "").split(",").map { dv -> Day.valueOf(dv) }
|
||||
it.contains("BYMONTHDAY=") -> try {
|
||||
byMonthDay = it.replace("BYMONTHDAY=", "").toInt()
|
||||
} catch (_: NumberFormatException) {}
|
||||
it.contains("BYMONTH=") -> try {
|
||||
byMonth = Month.of(it.replace("BYMONTH=", "").toInt())
|
||||
} catch (_: NumberFormatException) {}
|
||||
}
|
||||
}
|
||||
|
||||
return EventRecurrence(frequency, interval, count, bySetPos, byDay, byMonthDay, byMonth)
|
||||
}
|
||||
}
|
||||
|
||||
// Some helpful functions
|
||||
fun asRRule(): String {
|
||||
val rrule = StringBuilder()
|
||||
.append("RRULE:")
|
||||
.append("FREQ=${frequency.name};")
|
||||
.append("INTERVAL=${interval};")
|
||||
|
||||
if (count != null) rrule.append("COUNT=${count};")
|
||||
if (bySetPos != null) rrule.append("BYSETPOS=${bySetPos};")
|
||||
if (byDay.isNotEmpty()) rrule.append("BYDAY=${byDay.joinToString(",")};")
|
||||
if (byMonthDay != null) rrule.append("BYMONTHDAY=${byMonthDay};")
|
||||
if (byMonth != null) rrule.append("BYMONTH=${byMonth.value};")
|
||||
|
||||
return rrule.toString()
|
||||
}
|
||||
|
||||
fun asHumanReadable(): String {
|
||||
val builder = StringBuilder()
|
||||
.append("Repeat ${frequency.name} every $count ")
|
||||
|
||||
when (frequency) {
|
||||
Frequency.DAILY -> builder.append("day(s) ")
|
||||
Frequency.MONTHLY -> builder.append("month(s) ")
|
||||
else -> {}
|
||||
}
|
||||
|
||||
if (byMonth != null && byMonthDay != null) builder.append("on ${byMonth.name} $byMonthDay ")
|
||||
else if (byMonthDay != null) builder.append("$byMonthDay ")
|
||||
|
||||
if (byMonth != null && bySetPos != null && byDay.isNotEmpty()) builder.append("on the ${bySetPos.name} ${byDay.joinToString(",")} of ${byMonth.name} ")
|
||||
else if (bySetPos != null && byDay.isNotEmpty()) builder.append("on the ${bySetPos.name} ${byDay.joinToString(",")} ")
|
||||
else if (byDay.isNotEmpty()) builder.append("on ${byDay.joinToString(",")} ")
|
||||
|
||||
if (count != null) builder.append("End after $count occurrence(s)")
|
||||
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
////////////////////////////
|
||||
////// Nested classes //////
|
||||
////////////////////////////
|
||||
enum class Frequency {
|
||||
DAILY,
|
||||
WEEKLY,
|
||||
MONTHLY,
|
||||
YEARLY,
|
||||
}
|
||||
|
||||
enum class SetPos(val value: Int) {
|
||||
FIRST(1),
|
||||
SECOND(2),
|
||||
THIRD(3),
|
||||
FOURTH(4),
|
||||
LAST(-1),
|
||||
}
|
||||
|
||||
enum class Day(val value: String) {
|
||||
SUNDAY("SU"),
|
||||
MONDAY("MO"),
|
||||
TUESDAY("TU"),
|
||||
WEDNESDAY("WE"),
|
||||
THURSDAY("TH"),
|
||||
FRIDAY("FR"),
|
||||
SATURDAY("SA"),
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,8 @@ data class EventV2Model(
|
||||
isParent = !event.id.contains("_"),
|
||||
color = event.color.name,
|
||||
recur = event.recur,
|
||||
recurrence = event.recurrence,
|
||||
rrule = event.recurrence.toRRule(),
|
||||
recurrence = Recurrence.fromRRule(event.recurrence.asRRule()),
|
||||
rrule = event.recurrence.asRRule(),
|
||||
image = event.image,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user