mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-05-06 09:10:09 -05:00
Adds all day and multi-day event displays to /displaycal (#128)
* Working on doing multi-day and all events in displaycal * Fixing more stuff for this feature * Properly handle 3+ day events
This commit is contained in:
+27
-5
@@ -15,6 +15,7 @@ import reactor.core.publisher.Mono
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
object CalendarEmbed : EmbedMaker {
|
||||
fun link(guild: Guild, settings: GuildSettings, calNumber: Int, overview: Boolean): Mono<EmbedCreateSpec> {
|
||||
@@ -43,7 +44,7 @@ object CalendarEmbed : EmbedMaker {
|
||||
}
|
||||
|
||||
fun overview(guild: Guild, settings: GuildSettings, calendar: Calendar, showUpdate: Boolean): Mono<EmbedCreateSpec> {
|
||||
return calendar.getUpcomingEvents(15).collectList().map { it.groupByDate() }.map { events ->
|
||||
return calendar.getUpcomingEvents(15).collectList().map { it.groupByDateMulti() }.map { events ->
|
||||
val builder = defaultBuilder(guild, settings)
|
||||
|
||||
//Handle optional fields
|
||||
@@ -62,10 +63,31 @@ object CalendarEmbed : EmbedMaker {
|
||||
|
||||
val content = StringBuilder().append("```\n")
|
||||
sortedEvents.forEach {
|
||||
content.append(it.start.humanReadableTime(it.timezone, settings.timeFormat))
|
||||
.append(" - ")
|
||||
.append(it.end.humanReadableTime(it.timezone, settings.timeFormat))
|
||||
.append(" | ")
|
||||
// determine time length
|
||||
val timeDisplayLen = ("${it.start.humanReadableTime(it.timezone, settings.timeFormat)} -" +
|
||||
" ${it.end.humanReadableTime(it.timezone,settings.timeFormat)} ").length
|
||||
|
||||
// Displaying time
|
||||
if (it.isAllDay()) {
|
||||
content.append(getCommonMsg("generic.time.allDay", settings).padCenter(timeDisplayLen))
|
||||
.append("| ")
|
||||
} else {
|
||||
// Add start text
|
||||
var str = if (it.start.isBefore(date.key.toInstant())) {
|
||||
"${getCommonMsg("generic.time.continued", settings)} - "
|
||||
} else {
|
||||
"${it.start.humanReadableTime(it.timezone, settings.timeFormat)} - "
|
||||
}
|
||||
// Add end text
|
||||
str += if (it.end.isAfter(date.key.toInstant().plus(1, ChronoUnit.DAYS))) {
|
||||
getCommonMsg("generic.time.continued", settings)
|
||||
} else {
|
||||
"${it.end.humanReadableTime(it.timezone, settings.timeFormat)} "
|
||||
}
|
||||
content.append(str.padCenter(timeDisplayLen))
|
||||
.append("| ")
|
||||
}
|
||||
// Display name or ID if not set
|
||||
if (it.name.isNotBlank()) content.append(it.name)
|
||||
else content.append(getMessage("calendar", "link.field.id", settings)).append(" ${it.eventId}")
|
||||
content.append("\n")
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.dreamexposure.discal.core.utils.GlobalVal.JSON_FORMAT
|
||||
import org.json.JSONObject
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
|
||||
@@ -111,15 +112,15 @@ interface Event {
|
||||
*/
|
||||
fun getLinkedAnnouncements(): Flux<Announcement> {
|
||||
return DatabaseManager.getAnnouncements(this.guildId)
|
||||
.flatMapMany { Flux.fromIterable(it) }
|
||||
.filter { ann ->
|
||||
when (ann.type) {
|
||||
AnnouncementType.UNIVERSAL -> return@filter true
|
||||
AnnouncementType.COLOR -> return@filter ann.eventColor == this.color
|
||||
AnnouncementType.SPECIFIC -> return@filter ann.eventId == this.eventId
|
||||
AnnouncementType.RECUR -> return@filter this.eventId.contains(ann.eventId)
|
||||
}
|
||||
.flatMapMany { Flux.fromIterable(it) }
|
||||
.filter { ann ->
|
||||
when (ann.type) {
|
||||
AnnouncementType.UNIVERSAL -> return@filter true
|
||||
AnnouncementType.COLOR -> return@filter ann.eventColor == this.color
|
||||
AnnouncementType.SPECIFIC -> return@filter ann.eventId == this.eventId
|
||||
AnnouncementType.RECUR -> return@filter this.eventId.contains(ann.eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,24 +156,40 @@ interface Event {
|
||||
|
||||
fun isStarted() = start.isBefore(Instant.now())
|
||||
|
||||
/**
|
||||
* Whether the event is 24 hours long.
|
||||
*
|
||||
* @return Whether the event is 24 hours long
|
||||
*/
|
||||
fun is24Hours() = Duration.between(start, end).toHours() == 24L
|
||||
|
||||
/**
|
||||
* Whether the event lasts for a full calendar day (midnight to midnight) or longer.
|
||||
*
|
||||
* @return Whether the event is all day
|
||||
*/
|
||||
fun isAllDay(): Boolean {
|
||||
val start = this.start.atZone(timezone)
|
||||
|
||||
return start.hour == 0 && is24Hours()
|
||||
}
|
||||
|
||||
//Json bullshit
|
||||
fun toJson(): JSONObject {
|
||||
return JSONObject()
|
||||
.put("guild_id", guildId)
|
||||
.put("calendar", calendar.toJson())
|
||||
.put("event_id", eventId)
|
||||
.put("epoch_start", start.toEpochMilli())
|
||||
.put("epoch_end", end.toEpochMilli())
|
||||
.put("name", name)
|
||||
.put("description", description)
|
||||
.put("location", location)
|
||||
.put("is_parent", !eventId.contains("_"))
|
||||
.put("color", color.name)
|
||||
.put("recur", recur)
|
||||
.put("recurrence", JSONObject(JSON_FORMAT.encodeToString(recurrence)))
|
||||
.put("rrule", recurrence.toRRule())
|
||||
.put("image", eventData.imageLink)
|
||||
.put("guild_id", guildId)
|
||||
.put("calendar", calendar.toJson())
|
||||
.put("event_id", eventId)
|
||||
.put("epoch_start", start.toEpochMilli())
|
||||
.put("epoch_end", end.toEpochMilli())
|
||||
.put("name", name)
|
||||
.put("description", description)
|
||||
.put("location", location)
|
||||
.put("is_parent", !eventId.contains("_"))
|
||||
.put("color", color.name)
|
||||
.put("recur", recur)
|
||||
.put("recurrence", JSONObject(JSON_FORMAT.encodeToString(recurrence)))
|
||||
.put("rrule", recurrence.toRRule())
|
||||
.put("image", eventData.imageLink)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.dreamexposure.discal.core.extensions
|
||||
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.time.temporal.TemporalAdjusters
|
||||
@@ -27,9 +29,45 @@ fun MutableList<String>.setFromString(strList: String) {
|
||||
|
||||
fun MutableList<Event>.groupByDate(): Map<ZonedDateTime, List<Event>> {
|
||||
return this.stream()
|
||||
.collect(Collectors.groupingBy {
|
||||
ZonedDateTime.ofInstant(it.start, it.timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
.with(TemporalAdjusters.ofDateAdjuster { identity -> identity })
|
||||
}).toSortedMap()
|
||||
.collect(Collectors.groupingBy {
|
||||
ZonedDateTime.ofInstant(it.start, it.timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
.with(TemporalAdjusters.ofDateAdjuster { identity -> identity })
|
||||
}).toSortedMap()
|
||||
|
||||
}
|
||||
|
||||
// TODO: This could use some optimization, but we'll leave it for now
|
||||
fun MutableList<Event>.groupByDateMulti(): Map<ZonedDateTime, List<Event>> {
|
||||
// Each of the days events start on, their ending is ignored
|
||||
val dates = this.map {
|
||||
ZonedDateTime.ofInstant(it.start, it.timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
.with(TemporalAdjusters.ofDateAdjuster { identity -> identity })
|
||||
}.distinct().sorted()
|
||||
|
||||
val multi: MutableMap<ZonedDateTime, List<Event>> = mutableMapOf()
|
||||
|
||||
dates.forEach {
|
||||
LOGGER.debug("Date: $it")
|
||||
|
||||
val range = LongRange(it.toEpochSecond(), it.plusHours(23).plusMinutes(59).toEpochSecond())
|
||||
LOGGER.debug("Range: ${Instant.ofEpochSecond(range.first)} - ${Instant.ofEpochSecond(range.last)}")
|
||||
|
||||
val events: MutableList<Event> = mutableListOf()
|
||||
|
||||
this.forEach { event ->
|
||||
// When we check event end, we bump it back a second in order to prevent weirdness.
|
||||
if (range.contains(event.start.epochSecond) || range.contains(event.end.epochSecond - 1)) {
|
||||
LOGGER.debug("Event in range? Start: ${event.start} | End: ${event.end} | Name: ${event.name}")
|
||||
events.add(event)
|
||||
} else if (event.start.epochSecond < range.first && event.end.epochSecond > range.last) {
|
||||
// This is a multi-day event that starts before today and ends after today
|
||||
LOGGER.debug("Event extends beyond range? Start: ${event.start} | End: ${event.end} | Name: ${event.name}")
|
||||
events.add(event)
|
||||
}
|
||||
}
|
||||
LOGGER.debug("events in this range: ${events.size}")
|
||||
multi[it] = events
|
||||
}
|
||||
|
||||
return multi.toSortedMap()
|
||||
}
|
||||
|
||||
@@ -29,3 +29,24 @@ fun String.isValidTimezone(): Boolean {
|
||||
}
|
||||
|
||||
fun String.isValidImage(allowGif: Boolean) = ImageValidator.validate(this, allowGif)
|
||||
|
||||
fun String.padCenter(length: Int, padChar: Char = ' '): String {
|
||||
if (this.length >= length) return this
|
||||
|
||||
val chars: CharArray = this.toCharArray()
|
||||
val delta = length - chars.size
|
||||
val a = if (delta % 2 == 0) delta / 2 else delta / 2 + 1
|
||||
val b = a + chars.size
|
||||
|
||||
val output = CharArray(length)
|
||||
for (i in 0 until length) {
|
||||
if (i < a) {
|
||||
output[i] = padChar
|
||||
} else if (i < b) {
|
||||
output[i] = chars[i - a]
|
||||
} else {
|
||||
output[i] = padChar
|
||||
}
|
||||
}
|
||||
return String(output)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ bot.name=DisCal
|
||||
|
||||
success.generic=Success
|
||||
|
||||
generic.time.allDay=All day
|
||||
generic.time.continued=Cont.
|
||||
|
||||
error.unknown=Sorry, an unknown error has occurred.
|
||||
|
||||
error.notFound.event=No event with that ID was found. There may be a typo, or it may have been deleted.
|
||||
|
||||
Reference in New Issue
Block a user