mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-01-26 22:08:29 -06:00
add static calendar overview message
Still need to include triggered updates and some other important things
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package org.dreamexposure.discal.client.commands
|
||||
|
||||
import discord4j.core.`object`.command.ApplicationCommandInteractionOption
|
||||
import discord4j.core.`object`.command.ApplicationCommandInteractionOptionValue
|
||||
import discord4j.core.`object`.entity.Member
|
||||
import discord4j.core.`object`.entity.Message
|
||||
import discord4j.core.event.domain.interaction.ChatInputInteractionEvent
|
||||
import discord4j.core.spec.MessageCreateSpec
|
||||
import org.dreamexposure.discal.client.message.embed.CalendarEmbed
|
||||
import org.dreamexposure.discal.core.`object`.GuildSettings
|
||||
import org.dreamexposure.discal.core.`object`.StaticMessage
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.followupEphemeral
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.hasElevatedPermissions
|
||||
import org.dreamexposure.discal.core.utils.getCommonMsg
|
||||
import org.springframework.stereotype.Component
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Component
|
||||
class DisplayCalendarCommand : SlashCommand {
|
||||
override val name = "displaycal"
|
||||
override val ephemeral = true
|
||||
|
||||
override fun handle(event: ChatInputInteractionEvent, settings: GuildSettings): Mono<Message> {
|
||||
val hour = event.getOption("time")
|
||||
.flatMap(ApplicationCommandInteractionOption::getValue)
|
||||
.map(ApplicationCommandInteractionOptionValue::asLong)
|
||||
.get()
|
||||
|
||||
val calendarNumber = event.getOption("calendar")
|
||||
.flatMap(ApplicationCommandInteractionOption::getValue)
|
||||
.map(ApplicationCommandInteractionOptionValue::asLong)
|
||||
.map(Long::toInt)
|
||||
.orElse(1)
|
||||
|
||||
|
||||
|
||||
return Mono.justOrEmpty(event.interaction.member).filterWhen(Member::hasElevatedPermissions).flatMap {
|
||||
event.interaction.guild.flatMap { guild ->
|
||||
guild.getCalendar(calendarNumber).flatMap { cal ->
|
||||
CalendarEmbed.overview(guild, settings, cal, true).flatMap { embed ->
|
||||
event.interaction.channel.flatMap {
|
||||
it.createMessage(MessageCreateSpec.builder()
|
||||
.addEmbed(embed)
|
||||
.build()
|
||||
).flatMap { msg ->
|
||||
val nextUpdate = ZonedDateTime.now(cal.timezone)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.plusHours(hour + 24)
|
||||
.toInstant()
|
||||
|
||||
val staticMsg = StaticMessage(
|
||||
settings.guildID,
|
||||
msg.id,
|
||||
msg.channelId,
|
||||
StaticMessage.Type.CALENDAR_OVERVIEW,
|
||||
Instant.now(),
|
||||
nextUpdate,
|
||||
calendarNumber,
|
||||
)
|
||||
|
||||
DatabaseManager.updateStaticMessage(staticMsg)
|
||||
.then(event.followupEphemeral(getCommonMsg("success.generic", settings)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("notFound.calendar", settings)))
|
||||
}
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.elevated", settings)))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.dreamexposure.discal.client.service
|
||||
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.core.spec.MessageEditSpec
|
||||
import discord4j.rest.http.client.ClientException
|
||||
import org.dreamexposure.discal.Application
|
||||
import org.dreamexposure.discal.Application.Companion.getShardIndex
|
||||
import org.dreamexposure.discal.client.DisCalClient
|
||||
import org.dreamexposure.discal.client.message.embed.CalendarEmbed
|
||||
import org.dreamexposure.discal.core.`object`.StaticMessage
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getSettings
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import org.springframework.boot.ApplicationArguments
|
||||
import org.springframework.boot.ApplicationRunner
|
||||
import org.springframework.stereotype.Component
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.function.TupleUtils
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Component
|
||||
class StaticMessageService: ApplicationRunner {
|
||||
//TODO: use gateway client from DI once available
|
||||
|
||||
override fun run(args: ApplicationArguments?) {
|
||||
Flux.interval(Duration.ofHours(1))
|
||||
.flatMap { doMessageUpdateLogic() }
|
||||
.onBackpressureBuffer()
|
||||
.doOnError { LOGGER.error(GlobalVal.DEFAULT, "!-Static Message Service Error-!", it) }
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
|
||||
private fun doMessageUpdateLogic(): Mono<Void> {
|
||||
if (DisCalClient.client == null) return Mono.empty()
|
||||
|
||||
return DatabaseManager.getStaticMessagesForShard(Application.getShardCount(), getShardIndex().toInt())
|
||||
.flatMapMany { Flux.fromIterable(it) }
|
||||
//We have no interest in updating the message so close to its last update
|
||||
.filter { Duration.between(Instant.now(), it.lastUpdate).abs().toMinutes() >= 30 }
|
||||
// Only update messages in range
|
||||
.filter { Duration.between(Instant.now(), it.scheduledUpdate).toMinutes() <= 60 }
|
||||
.flatMap { data ->
|
||||
DisCalClient.client!!.getMessageById(data.channelId, data.messageId).flatMap { message ->
|
||||
when (data.type) {
|
||||
StaticMessage.Type.CALENDAR_OVERVIEW -> {
|
||||
val guildMono = DisCalClient.client!!.getGuildById(data.guildId).cache()
|
||||
val setMono = guildMono.flatMap(Guild::getSettings)
|
||||
val calMono = guildMono.flatMap { it.getCalendar(data.calendarNumber) }
|
||||
|
||||
Mono.zip(guildMono, setMono, calMono).flatMap(
|
||||
TupleUtils.function { guild, settings, calendar ->
|
||||
CalendarEmbed.overview(guild, settings, calendar, true).flatMap {
|
||||
message.edit(MessageEditSpec.builder()
|
||||
.embedsOrNull(listOf(it))
|
||||
.build()
|
||||
)
|
||||
}.then(DatabaseManager.updateStaticMessage(data.copy(
|
||||
lastUpdate = Instant.now(),
|
||||
scheduledUpdate = data.scheduledUpdate.plus(1, ChronoUnit.DAYS))
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
}.onErrorResume(ClientException.isStatusCode(404)) {
|
||||
//Message or channel was deleted, delete from database
|
||||
DatabaseManager.deleteStaticMessage(data.guildId, data.messageId)
|
||||
}
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "Static message update error", it)
|
||||
}.onErrorResume {
|
||||
Mono.empty()
|
||||
}.then()
|
||||
}
|
||||
}
|
||||
@@ -1206,8 +1206,11 @@ object DatabaseManager {
|
||||
c.createStatement(Queries.INSERT_STATIC_MESSAGE)
|
||||
.bind(0, message.guildId.asLong())
|
||||
.bind(1, message.messageId.asLong())
|
||||
.bind(2, message.type.value)
|
||||
.bind(3, message.lastUpdate)
|
||||
.bind(2, message.channelId.asLong())
|
||||
.bind(3, message.type.value)
|
||||
.bind(4, message.lastUpdate)
|
||||
.bind(5, message.scheduledUpdate)
|
||||
.bind(6, message.calendarNumber)
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
@@ -1228,10 +1231,13 @@ object DatabaseManager {
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val channelId = Snowflake.of(row["channel_id", Long::class.java]!!)
|
||||
val type = StaticMessage.Type.valueOf(row["type", Int::class.java]!!)
|
||||
val lastUpdate = row["last_update", Instant::class.java]!!
|
||||
val scheduledUpdate = row["scheduled_update", Instant::class.java]!!
|
||||
val calNum = row["calendar_number", Int::class.java]!!
|
||||
|
||||
StaticMessage(guildId, messageId, type, lastUpdate)
|
||||
StaticMessage(guildId, messageId, channelId, type, lastUpdate, scheduledUpdate, calNum)
|
||||
}
|
||||
}.next().retryWhen(Retry.max(3)
|
||||
.filter(IllegalStateException::class::isInstance)
|
||||
@@ -1279,7 +1285,38 @@ object DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
fun getStaticMessagesForShard(shardCount: Int, shardIndex: Int): Mono<List<StaticMessage>> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_STATIC_MESSAGES_FOR_SHARD)
|
||||
.bind(0, shardCount)
|
||||
.bind(1, shardIndex)
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val guildId = Snowflake.of(row["guild_id", Long::class.java]!!)
|
||||
val messageId = Snowflake.of(row["message_id", Long::class.java]!!)
|
||||
val channelId = Snowflake.of(row["channel_id", Long::class.java]!!)
|
||||
val type = StaticMessage.Type.valueOf(row["type", Int::class.java]!!)
|
||||
val lastUpdate = row["last_update", Instant::class.java]!!
|
||||
val scheduledUpdate = row["scheduled_update", Instant::class.java]!!
|
||||
val calNum = row["calendar_number", Int::class.java]!!
|
||||
|
||||
StaticMessage(guildId, messageId, channelId, type, lastUpdate, scheduledUpdate, calNum)
|
||||
}
|
||||
}.retryWhen(Retry.max(3)
|
||||
.filter(IllegalStateException::class::isInstance)
|
||||
.filter { it.message != null && it.message!!.contains("Request queue was disposed") }
|
||||
).doOnError {
|
||||
LOGGER.error(DEFAULT, "Failed to get many event data", it)
|
||||
}.onErrorResume {
|
||||
Mono.empty()
|
||||
}.collectList()
|
||||
}
|
||||
}
|
||||
|
||||
/* Event Data */
|
||||
|
||||
fun getEventsData(guildId: Snowflake, eventIds: List<String>): Mono<Map<String, EventData>> {
|
||||
// clean up IDs
|
||||
val idsToUse = mutableListOf<String>()
|
||||
@@ -1477,10 +1514,15 @@ private object Queries {
|
||||
WHERE guild_id = ? AND message_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_STATIC_MESSAGES_FOR_SHARD = """SELECT * FROM ${Tables.STATIC_MESSAGES}
|
||||
WHERE MOD(guild_id >> 22, ?) = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val INSERT_STATIC_MESSAGE = """INSERT INTO ${Tables.STATIC_MESSAGES}
|
||||
(guild_id, message_id, type, last_update)
|
||||
VALUES(?, ?, ?, ?)
|
||||
(guild_id, message_id, channel_id, type, last_update, scheduled_update, calendar_number)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?)
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
|
||||
@@ -16,10 +16,23 @@ data class StaticMessage(
|
||||
@SerialName("message_id")
|
||||
@Serializable(with = SnowflakeAsStringSerializer::class)
|
||||
val messageId: Snowflake,
|
||||
|
||||
@SerialName("channel_id")
|
||||
@Serializable(with = SnowflakeAsStringSerializer::class)
|
||||
val channelId: Snowflake,
|
||||
|
||||
val type: Type,
|
||||
|
||||
@SerialName("last_update")
|
||||
@Serializable(with = InstantAsStringSerializer::class)
|
||||
val lastUpdate: Instant,
|
||||
|
||||
@SerialName("scheduled_update")
|
||||
@Serializable(with = InstantAsStringSerializer::class)
|
||||
val scheduledUpdate: Instant,
|
||||
|
||||
@SerialName("calendar_number")
|
||||
val calendarNumber: Int
|
||||
) {
|
||||
enum class Type(val value: Int) {
|
||||
CALENDAR_OVERVIEW(1);
|
||||
|
||||
117
core/src/main/resources/commands/displayCal.json
Normal file
117
core/src/main/resources/commands/displayCal.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"name": "displaycal",
|
||||
"description": "Displays an auto-updating overview of the calendar",
|
||||
"options": [
|
||||
{
|
||||
"name": "time",
|
||||
"type": 4,
|
||||
"description": "The hour of the day to update the message (in the calendar's timezone)",
|
||||
"required": true,
|
||||
"choices": [
|
||||
{
|
||||
"name": "00",
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"name": "01",
|
||||
"value": 1
|
||||
},
|
||||
{
|
||||
"name": "02",
|
||||
"value": 2
|
||||
},
|
||||
{
|
||||
"name": "03",
|
||||
"value": 3
|
||||
},
|
||||
{
|
||||
"name": "04",
|
||||
"value": 4
|
||||
},
|
||||
{
|
||||
"name": "05",
|
||||
"value": 5
|
||||
},
|
||||
{
|
||||
"name": "06",
|
||||
"value": 6
|
||||
},
|
||||
{
|
||||
"name": "07",
|
||||
"value": 7
|
||||
},
|
||||
{
|
||||
"name": "08",
|
||||
"value": 8
|
||||
},
|
||||
{
|
||||
"name": "09",
|
||||
"value": 9
|
||||
},
|
||||
{
|
||||
"name": "10",
|
||||
"value": 10
|
||||
},
|
||||
{
|
||||
"name": "11",
|
||||
"value": 11
|
||||
},
|
||||
{
|
||||
"name": "12",
|
||||
"value": 12
|
||||
},
|
||||
{
|
||||
"name": "13",
|
||||
"value": 13
|
||||
},
|
||||
{
|
||||
"name": "14",
|
||||
"value": 14
|
||||
},
|
||||
{
|
||||
"name": "15",
|
||||
"value": 15
|
||||
},
|
||||
{
|
||||
"name": "16",
|
||||
"value": 16
|
||||
},
|
||||
{
|
||||
"name": "17",
|
||||
"value": 17
|
||||
},
|
||||
{
|
||||
"name": "18",
|
||||
"value": 18
|
||||
},
|
||||
{
|
||||
"name": "19",
|
||||
"value": 19
|
||||
},
|
||||
{
|
||||
"name": "20",
|
||||
"value": 20
|
||||
},
|
||||
{
|
||||
"name": "21",
|
||||
"value": 21
|
||||
},
|
||||
{
|
||||
"name": "22",
|
||||
"value": 22
|
||||
},
|
||||
{
|
||||
"name": "23",
|
||||
"value": 23
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "calendar",
|
||||
"type": 4,
|
||||
"description": "The calendar to display. Defaults to 1",
|
||||
"required": false,
|
||||
"min_value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,8 +2,10 @@ CREATE TABLE IF NOT EXISTS static_messages
|
||||
(
|
||||
guild_id BIGINT NOT NULL,
|
||||
message_id BIGINT NOT NULL,
|
||||
channel_id BIGINT NOT NULL,
|
||||
type SMALLINT NOT NULL,
|
||||
last_update DATETIME NOT NULL,
|
||||
scheduled_update DATETIME NOT NULL,
|
||||
PRIMARY KEY (guild_id, message_id)
|
||||
) ENGINE = InnoDB
|
||||
DEFAULT CHARSET = utf8;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
bot.name=DisCal
|
||||
|
||||
success.generic=Success
|
||||
|
||||
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