mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-05-07 17:59:52 -05:00
Implement RSVP waitlist (#127)
* WIP - RSVP waitlist * waitlist DM embed + logic for filling N > 1 slots. Handling of single wait listed user and triggering waitlist in command still needed * Prevent possible CME * Remove `@JVMStatic` annotation as no longer needed * Add rsvp waitlist DMing and triggering Just need to set up adding to waitlist * Cleanup some string list stuff * This should be the last of the work on the waitlist system
This commit is contained in:
@@ -52,15 +52,26 @@ class RsvpCommand : SlashCommand {
|
||||
cal.getEvent(eventId).flatMap { calEvent ->
|
||||
if (!calEvent.isOver()) {
|
||||
val member = event.interaction.member.get()
|
||||
calEvent.getRsvp()
|
||||
.filter { it.hasRoom(member.id.asString()) }
|
||||
.flatMap { it.removeCompletely(member).thenReturn(it) }
|
||||
.flatMap { it.addGoingOnTime(member).thenReturn(it) }
|
||||
.flatMap { calEvent.updateRsvp(it).thenReturn(it) }
|
||||
.flatMap { RsvpEmbed.list(guild, settings, calEvent, it) }
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("onTime.success", settings), it)
|
||||
}.switchIfEmpty(event.followupEphemeral(getMessage("onTime.failure.limit", settings)))
|
||||
calEvent.getRsvp().flatMap { rsvp ->
|
||||
if (rsvp.hasRoom(member.id.asString())) {
|
||||
rsvp.removeCompletely(member)
|
||||
.then(rsvp.addGoingOnTime(member))
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("onTime.success", settings), it)
|
||||
}
|
||||
} else {
|
||||
// No room, add to waitlist instead
|
||||
rsvp.removeCompletely(member)
|
||||
.doOnNext { rsvp.waitlist.add(member.id.asString()) }
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("onTime.failure.limit", settings), it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.followupEphemeral(getCommonMsg("error.event.ended", settings))
|
||||
}
|
||||
@@ -86,15 +97,26 @@ class RsvpCommand : SlashCommand {
|
||||
cal.getEvent(eventId).flatMap { calEvent ->
|
||||
if (!calEvent.isOver()) {
|
||||
val member = event.interaction.member.get()
|
||||
calEvent.getRsvp()
|
||||
.filter { it.hasRoom(member.id.asString()) }
|
||||
.flatMap { it.removeCompletely(member).thenReturn(it) }
|
||||
.flatMap { it.addGoingLate(member).thenReturn(it) }
|
||||
.flatMap { calEvent.updateRsvp(it).thenReturn(it) }
|
||||
.flatMap { RsvpEmbed.list(guild, settings, calEvent, it) }
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("late.success", settings), it)
|
||||
}.switchIfEmpty(event.followupEphemeral(getMessage("late.failure.limit", settings)))
|
||||
calEvent.getRsvp().flatMap { rsvp ->
|
||||
if (rsvp.hasRoom(member.id.asString())) {
|
||||
rsvp.removeCompletely(member)
|
||||
.then(rsvp.addGoingLate(member))
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("late.success", settings), it)
|
||||
}
|
||||
} else {
|
||||
// No room, add to waitlist instead
|
||||
rsvp.removeCompletely(member)
|
||||
.doOnNext { rsvp.waitlist.add(member.id.asString()) }
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("late.failure.limit", settings), it)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.followupEphemeral(getCommonMsg("error.event.ended", settings))
|
||||
}
|
||||
@@ -187,7 +209,8 @@ class RsvpCommand : SlashCommand {
|
||||
if (!calEvent.isOver()) {
|
||||
val member = event.interaction.member.get()
|
||||
calEvent.getRsvp().flatMap { rsvp ->
|
||||
rsvp.removeCompletely(member)
|
||||
// Add next person on waitlist if this user was previously going to attend
|
||||
rsvp.removeCompletely(member, true)
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap { event.followupEphemeral(getMessage("remove.success", settings), it) }
|
||||
@@ -248,6 +271,8 @@ class RsvpCommand : SlashCommand {
|
||||
if (!calEvent.isOver()) {
|
||||
calEvent.getRsvp()
|
||||
.doOnNext { it.limit = limit }
|
||||
// Handle adding other users to going in the event the limit was increased/removed
|
||||
.flatMap { it.fillRemaining(guild, settings) }
|
||||
.flatMap { calEvent.updateRsvp(it).thenReturn(it) }
|
||||
.flatMap { RsvpEmbed.list(guild, settings, calEvent, it) }
|
||||
.flatMap {
|
||||
@@ -283,38 +308,36 @@ class RsvpCommand : SlashCommand {
|
||||
return@function event.followupEphemeral(getCommonMsg("error.patronOnly", settings))
|
||||
}
|
||||
|
||||
Mono.justOrEmpty(event.interaction.member)
|
||||
.filterWhen(Member::hasElevatedPermissions)
|
||||
.flatMap { member ->
|
||||
guild.getCalendar(calendarNumber).flatMap { cal ->
|
||||
cal.getEvent(eventId).flatMap { calEvent ->
|
||||
if (!calEvent.isOver()) {
|
||||
calEvent.getRsvp().flatMap { rsvp ->
|
||||
if (role.isEveryone) {
|
||||
rsvp.clearRole(member.client.rest())
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.flatMap { RsvpEmbed.list(guild, settings, calEvent, rsvp) }
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("role.success.remove", settings), it)
|
||||
}
|
||||
} else {
|
||||
rsvp.setRole(role)
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(
|
||||
getMessage("role.success.set", settings, role.name),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
Mono.justOrEmpty(event.interaction.member).filterWhen(Member::hasElevatedPermissions).flatMap { member ->
|
||||
guild.getCalendar(calendarNumber).flatMap { cal ->
|
||||
cal.getEvent(eventId).flatMap { calEvent ->
|
||||
if (!calEvent.isOver()) {
|
||||
calEvent.getRsvp().flatMap { rsvp ->
|
||||
if (role.isEveryone) {
|
||||
rsvp.clearRole(member.client.rest())
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.flatMap { RsvpEmbed.list(guild, settings, calEvent, rsvp) }
|
||||
.flatMap {
|
||||
event.followupEphemeral(getMessage("role.success.remove", settings), it)
|
||||
}
|
||||
} else {
|
||||
rsvp.setRole(role)
|
||||
.then(calEvent.updateRsvp(rsvp))
|
||||
.then(RsvpEmbed.list(guild, settings, calEvent, rsvp))
|
||||
.flatMap {
|
||||
event.followupEphemeral(
|
||||
getMessage("role.success.set", settings, role.name),
|
||||
it
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
event.followupEphemeral(getCommonMsg("error.event.ended", settings))
|
||||
}
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings)))
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings)))
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.elevated", settings)))
|
||||
} else {
|
||||
event.followupEphemeral(getCommonMsg("error.event.ended", settings))
|
||||
}
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.event", settings)))
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.notFound.calendar", settings)))
|
||||
}.switchIfEmpty(event.followupEphemeral(getCommonMsg("error.perms.elevated", settings)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,8 +47,19 @@ object RsvpEmbed : EmbedMaker {
|
||||
.map(MutableList<String>::asStringList)
|
||||
.map { it.ifEmpty { "N/a" } }
|
||||
|
||||
return Mono.zip(roleMono, onTimeMono, lateMono, undecidedMono, notMono)
|
||||
.map(TupleUtils.function { role, onTime, late, undecided, notGoing ->
|
||||
// Wait list users (show up to 3, with (+X) if there are more)
|
||||
val display = 3
|
||||
val waitListMono = guild.getMembersFromId(rsvp.waitlist.take(display))
|
||||
.map(Member::getUsername)
|
||||
.collectList()
|
||||
.map { list ->
|
||||
if (rsvp.waitlist.size > display) "${list.asStringList()} +${rsvp.waitlist.size - display} more"
|
||||
else if (list.isNotEmpty()) list.asStringList()
|
||||
else "N/a"
|
||||
}
|
||||
|
||||
return Mono.zip(roleMono, onTimeMono, lateMono, undecidedMono, notMono, waitListMono)
|
||||
.map(TupleUtils.function { role, onTime, late, undecided, notGoing, waitList ->
|
||||
val limitValue = if (rsvp.limit < 0) {
|
||||
getMessage("rsvp", "list.field.limit.value", settings, "${rsvp.getCurrentCount()}")
|
||||
} else "${rsvp.getCurrentCount()}/${rsvp.limit}"
|
||||
@@ -63,6 +74,7 @@ object RsvpEmbed : EmbedMaker {
|
||||
.addField(getMessage("rsvp", "list.field.late", settings), late, false)
|
||||
.addField(getMessage("rsvp", "list.field.unsure", settings), undecided, false)
|
||||
.addField(getMessage("rsvp", "list.field.notGoing", settings), notGoing, false)
|
||||
.addField(getMessage("rsvp", "list.field.waitList", settings), waitList, false)
|
||||
.footer(getMessage("rsvp", "list.footer", settings), null)
|
||||
.build()
|
||||
})
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.dreamexposure.discal.core.enums.calendar.CalendarHost
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor.Companion.fromNameOrHexOrId
|
||||
import org.dreamexposure.discal.core.enums.time.TimeFormat
|
||||
import org.dreamexposure.discal.core.extensions.asStringList
|
||||
import org.dreamexposure.discal.core.extensions.setFromString
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.DEFAULT
|
||||
import org.intellij.lang.annotations.Language
|
||||
@@ -392,7 +393,7 @@ object DatabaseManager {
|
||||
if (exists) {
|
||||
val updateCommand = """UPDATE ${Tables.RSVP} SET
|
||||
CALENDAR_NUMBER = ?, EVENT_END = ?, GOING_ON_TIME = ?, GOING_LATE = ?,
|
||||
NOT_GOING = ?, UNDECIDED = ?, RSVP_LIMIT = ?, RSVP_ROLE = ?
|
||||
NOT_GOING = ?, UNDECIDED = ?, waitlist = ?, RSVP_LIMIT = ?, RSVP_ROLE = ?
|
||||
WHERE EVENT_ID = ? AND GUILD_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@@ -404,14 +405,16 @@ object DatabaseManager {
|
||||
.bind(3, data.goingLate.asStringList())
|
||||
.bind(4, data.notGoing.asStringList())
|
||||
.bind(5, data.undecided.asStringList())
|
||||
.bind(6, data.limit)
|
||||
.bind(8, data.eventId)
|
||||
.bind(9, data.guildId.asString())
|
||||
.bind(6, data.waitlist.asStringList())
|
||||
.bind(7, data.limit)
|
||||
//8 deal with nullable role below
|
||||
.bind(9, data.eventId)
|
||||
.bind(10, data.guildId.asString())
|
||||
).doOnNext { statement ->
|
||||
if (data.roleId == null)
|
||||
statement.bindNull(7, Long::class.java)
|
||||
statement.bindNull(8, Long::class.java)
|
||||
else
|
||||
statement.bind(7, data.roleId!!.asString())
|
||||
statement.bind(8, data.roleId!!.asString())
|
||||
}.flatMap {
|
||||
Mono.from(it.execute())
|
||||
}.flatMapMany(Result::getRowsUpdated)
|
||||
@@ -420,8 +423,8 @@ object DatabaseManager {
|
||||
} else if (data.shouldBeSaved()) {
|
||||
val insertCommand = """INSERT INTO ${Tables.RSVP}
|
||||
(GUILD_ID, EVENT_ID, CALENDAR_NUMBER, EVENT_END, GOING_ON_TIME, GOING_LATE,
|
||||
NOT_GOING, UNDECIDED, RSVP_LIMIT, RSVP_ROLE)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
NOT_GOING, UNDECIDED, waitlist, RSVP_LIMIT, RSVP_ROLE)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""".trimMargin()
|
||||
|
||||
Mono.just(
|
||||
@@ -434,12 +437,13 @@ object DatabaseManager {
|
||||
.bind(5, data.goingLate.asStringList())
|
||||
.bind(6, data.notGoing.asStringList())
|
||||
.bind(7, data.undecided.asStringList())
|
||||
.bind(8, data.limit)
|
||||
.bind(8, data.waitlist.asStringList())
|
||||
.bind(9, data.limit)
|
||||
).doOnNext { statement ->
|
||||
if (data.roleId == null)
|
||||
statement.bindNull(9, Long::class.java)
|
||||
statement.bindNull(10, Long::class.java)
|
||||
else
|
||||
statement.bind(9, data.roleId!!.asString())
|
||||
statement.bind(10, data.roleId!!.asString())
|
||||
}.flatMap {
|
||||
Mono.from(it.execute())
|
||||
}.flatMapMany(Result::getRowsUpdated)
|
||||
@@ -552,7 +556,7 @@ object DatabaseManager {
|
||||
lang, prefix, patron, dev, maxCals, branded
|
||||
)
|
||||
|
||||
settings.setDmAnnouncementsString(dmAnnouncementsString)
|
||||
settings.dmAnnouncements.setFromString(dmAnnouncementsString)
|
||||
|
||||
//Store in cache...
|
||||
DiscalCache.guildSettings[guildId] = settings
|
||||
@@ -722,10 +726,11 @@ object DatabaseManager {
|
||||
val data = RsvpData(guildId, eventId, calNumber)
|
||||
|
||||
data.eventEnd = row["EVENT_END", Long::class.java]!!
|
||||
data.setGoingOnTimeFromString(row["GOING_ON_TIME", String::class.java]!!)
|
||||
data.setGoingLateFromString(row["GOING_LATE", String::class.java]!!)
|
||||
data.setNotGoingFromString(row["NOT_GOING", String::class.java]!!)
|
||||
data.setUndecidedFromString(row["UNDECIDED", String::class.java]!!)
|
||||
data.goingOnTime.setFromString(row["GOING_ON_TIME", String::class.java]!!)
|
||||
data.goingLate.setFromString(row["GOING_LATE", String::class.java]!!)
|
||||
data.notGoing.setFromString(row["NOT_GOING", String::class.java]!!)
|
||||
data.undecided.setFromString(row["UNDECIDED", String::class.java]!!)
|
||||
data.waitlist.setFromString(row["waitlist", String::class.java]!!)
|
||||
data.limit = row["RSVP_LIMIT", Int::class.java]!!
|
||||
|
||||
//Handle new rsvp role
|
||||
@@ -755,8 +760,8 @@ object DatabaseManager {
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -791,8 +796,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -828,8 +833,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -864,8 +869,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -901,8 +906,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -937,8 +942,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -973,8 +978,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -1010,8 +1015,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -1047,8 +1052,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
@@ -1480,8 +1485,8 @@ object DatabaseManager {
|
||||
|
||||
val a = Announcement(guildId, announcementId)
|
||||
a.calendarNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
a.setSubscriberRoleIdsFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.setSubscriberUserIdsFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.subscriberRoleIds.setFromString(row["SUBSCRIBERS_ROLE", String::class.java]!!)
|
||||
a.subscriberUserIds.setFromString(row["SUBSCRIBERS_USER", String::class.java]!!)
|
||||
a.announcementChannelId = row["CHANNEL_ID", String::class.java]!!
|
||||
a.type = AnnouncementType.valueOf(row["ANNOUNCEMENT_TYPE", String::class.java]!!)
|
||||
a.modifier = AnnouncementModifier.valueOf(row["MODIFIER", String::class.java]!!)
|
||||
|
||||
@@ -20,6 +20,10 @@ fun MutableList<String>.asStringList(): String {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
fun MutableList<String>.setFromString(strList: String) {
|
||||
this += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
|
||||
fun MutableList<Event>.groupByDate(): Map<ZonedDateTime, List<Event>> {
|
||||
return this.stream()
|
||||
|
||||
@@ -44,10 +44,6 @@ data class GuildSettings(
|
||||
|
||||
fun getDmAnnouncementsString() = this.dmAnnouncements.asStringList()
|
||||
|
||||
fun setDmAnnouncementsString(dm: String) {
|
||||
this.dmAnnouncements += dm.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
//TODO: Remove when old translation system is dropped
|
||||
fun getLocale(): Locale {
|
||||
|
||||
|
||||
+3
-10
@@ -12,6 +12,7 @@ import org.dreamexposure.discal.core.enums.announcement.AnnouncementType
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.serializers.SnowflakeAsStringSerializer
|
||||
import org.dreamexposure.discal.core.utils.getEmbedMessage
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@Serializable
|
||||
data class Announcement(
|
||||
@@ -25,10 +26,10 @@ data class Announcement(
|
||||
override val editing: Boolean = false,
|
||||
|
||||
@SerialName("subscriber_roles")
|
||||
val subscriberRoleIds: MutableList<String> = mutableListOf(),
|
||||
val subscriberRoleIds: MutableList<String> = CopyOnWriteArrayList(),
|
||||
|
||||
@SerialName("subscriber_users")
|
||||
val subscriberUserIds: MutableList<String> = arrayListOf(),
|
||||
val subscriberUserIds: MutableList<String> = CopyOnWriteArrayList(),
|
||||
|
||||
@SerialName("channel_id")
|
||||
var announcementChannelId: String = "N/a",
|
||||
@@ -56,14 +57,6 @@ data class Announcement(
|
||||
var publish: Boolean = false,
|
||||
) : Pre(guildId) {
|
||||
|
||||
fun setSubscriberRoleIdsFromString(subList: String) {
|
||||
this.subscriberRoleIds += subList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun setSubscriberUserIdsFromString(subList: String) {
|
||||
this.subscriberUserIds += subList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun hasRequiredValues(): Boolean {
|
||||
return !((this.type == AnnouncementType.SPECIFIC || this.type == AnnouncementType.RECUR) && this.eventId == "N/a")
|
||||
&& this.announcementChannelId != "N/a"
|
||||
|
||||
@@ -2,14 +2,32 @@ package org.dreamexposure.discal.core.`object`.event
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.DiscordClient
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.core.`object`.entity.Member
|
||||
import discord4j.core.`object`.entity.Role
|
||||
import discord4j.core.spec.EmbedCreateSpec
|
||||
import discord4j.discordjson.json.GuildData
|
||||
import discord4j.discordjson.json.GuildUpdateData
|
||||
import discord4j.discordjson.json.MessageData
|
||||
import discord4j.rest.http.client.ClientException
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.dreamexposure.discal.core.`object`.BotSettings
|
||||
import org.dreamexposure.discal.core.`object`.GuildSettings
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.enums.time.DiscordTimestampFormat.LONG_DATETIME
|
||||
import org.dreamexposure.discal.core.extensions.asDiscordTimestamp
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getSettings
|
||||
import org.dreamexposure.discal.core.extensions.embedFieldSafe
|
||||
import org.dreamexposure.discal.core.extensions.toMarkdown
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.serializers.SnowflakeAsStringSerializer
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import org.dreamexposure.discal.core.utils.getEmbedMessage
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.function.TupleUtils
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@Serializable
|
||||
@@ -46,22 +64,7 @@ data class RsvpData(
|
||||
|
||||
val undecided: MutableList<String> = CopyOnWriteArrayList()
|
||||
|
||||
//List string stuffs
|
||||
fun setGoingOnTimeFromString(strList: String) {
|
||||
this.goingOnTime += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun setGoingLateFromString(strList: String) {
|
||||
this.goingLate += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun setNotGoingFromString(strList: String) {
|
||||
this.notGoing += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun setUndecidedFromString(strList: String) {
|
||||
this.undecided += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
val waitlist: MutableList<String> = CopyOnWriteArrayList()
|
||||
|
||||
fun getCurrentCount() = this.goingOnTime.size + this.goingLate.size
|
||||
|
||||
@@ -71,6 +74,8 @@ data class RsvpData(
|
||||
else goingOnTime.contains(userId) || goingLate.contains(userId)
|
||||
}
|
||||
|
||||
private fun hasRoom() = limit < 0 || getCurrentCount() + 1 <= limit
|
||||
|
||||
fun setRole(id: Snowflake, client: DiscordClient): Mono<Void> {
|
||||
roleId = id
|
||||
|
||||
@@ -108,17 +113,33 @@ data class RsvpData(
|
||||
}
|
||||
|
||||
//Functions
|
||||
fun removeCompletely(userId: String, client: DiscordClient): Mono<Void> {
|
||||
fun removeCompletely(userId: String, client: DiscordClient, doWaitlistOp: Boolean = false): Mono<Void> {
|
||||
// Remove from all lists
|
||||
goingOnTime.removeAll { userId == it }
|
||||
goingLate.removeAll { userId == it }
|
||||
notGoing.removeAll { userId == it }
|
||||
undecided.removeAll { userId == it }
|
||||
waitlist.removeAll { userId == it }
|
||||
|
||||
return if (roleId != null) removeRole(userId, roleId!!, "Removed RSVP to event with ID $eventId", client)
|
||||
else Mono.empty()
|
||||
// Remove role if one is set
|
||||
val roleMono = if (roleId != null) {
|
||||
removeRole(userId, roleId!!, "Removed RSVP to event with ID $eventId", client)
|
||||
} else {
|
||||
Mono.empty()
|
||||
}
|
||||
|
||||
// If there is now room, add the next waiting user as going
|
||||
val waitListMono = if (doWaitlistOp && waitlist.isNotEmpty() && hasRoom(waitlist.first())) {
|
||||
handleWaitListedUser(waitlist.removeFirst(), client)
|
||||
} else {
|
||||
Mono.empty()
|
||||
}
|
||||
|
||||
return roleMono.then(waitListMono)
|
||||
}
|
||||
|
||||
fun removeCompletely(member: Member): Mono<Void> = removeCompletely(member.id.asString(), member.client.rest())
|
||||
fun removeCompletely(member: Member, doWaitlistOp: Boolean = false): Mono<Void> =
|
||||
removeCompletely(member.id.asString(), member.client.rest(), doWaitlistOp)
|
||||
|
||||
fun addGoingOnTime(userId: String, client: DiscordClient): Mono<Void> {
|
||||
return Mono.just(userId)
|
||||
@@ -142,15 +163,59 @@ data class RsvpData(
|
||||
}
|
||||
}
|
||||
|
||||
fun handleWaitListedUser(userId: String, client: DiscordClient): Mono<Void> {
|
||||
val guild = client.getGuildById(guildId)
|
||||
val eventMono = guild.getCalendar(calendarNumber).flatMap { it.getEvent(eventId) }
|
||||
val guildDataMono = guild.data
|
||||
val settingsMono = guild.getSettings()
|
||||
|
||||
val embedMono = Mono.zip(guildDataMono, settingsMono, eventMono).map(
|
||||
TupleUtils.function { data, settings, event ->
|
||||
followupEmbed(data, settings, userId, event)
|
||||
}
|
||||
)
|
||||
|
||||
/* Add the user as attending on time
|
||||
(it would be rude to show up late if other people want to attend the full event)
|
||||
*/
|
||||
return addGoingOnTime(userId, client).then(embedMono).flatMap {
|
||||
dmUser(userId, it, client)
|
||||
}.then()
|
||||
}
|
||||
|
||||
fun handleWaitListedUser(member: Member): Mono<Void> = handleWaitListedUser(member.id.asString(), member.client.rest())
|
||||
|
||||
fun fillRemaining(guild: Guild, settings: GuildSettings): Mono<RsvpData> {
|
||||
val eventMono = guild.getCalendar(calendarNumber).flatMap { it.getEvent(eventId) }.cache()
|
||||
|
||||
return Flux.fromIterable(waitlist)
|
||||
.takeWhile { hasRoom() }
|
||||
.concatMap { userId ->
|
||||
/* Add the user as attending on time
|
||||
(it would be rude to show up late if other people want to attend the full event)
|
||||
*/
|
||||
addGoingOnTime(userId, guild.client.rest()).then(eventMono).flatMap { event ->
|
||||
// Send DM
|
||||
val embed = followupEmbed(guild.data, settings, userId, event)
|
||||
dmUser(userId, embed, guild.client.rest())
|
||||
}
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "RSVP waitlist processing failed", it)
|
||||
}.onErrorResume {
|
||||
Mono.empty()
|
||||
}.then().thenReturn(this)
|
||||
}
|
||||
|
||||
fun addGoingLate(member: Member): Mono<Void> = addGoingLate(member.id.asString(), member.client.rest())
|
||||
|
||||
fun shouldBeSaved(): Boolean {
|
||||
return this.goingOnTime.isNotEmpty()
|
||||
|| this.goingLate.isNotEmpty()
|
||||
|| this.notGoing.isNotEmpty()
|
||||
|| this.undecided.isNotEmpty()
|
||||
|| limit != -1
|
||||
|| roleId != null
|
||||
|| this.goingLate.isNotEmpty()
|
||||
|| this.notGoing.isNotEmpty()
|
||||
|| this.undecided.isNotEmpty()
|
||||
|| this.waitlist.isNotEmpty()
|
||||
|| limit != -1
|
||||
|| roleId != null
|
||||
}
|
||||
|
||||
private fun addRole(userId: String, roleId: Snowflake, reason: String, client: DiscordClient): Mono<Void> {
|
||||
@@ -164,4 +229,78 @@ data class RsvpData(
|
||||
.removeMemberRole(Snowflake.of(userId), roleId, reason)
|
||||
.onErrorResume(ClientException::class.java) { Mono.empty() }
|
||||
}
|
||||
|
||||
private fun followupEmbed(guild: GuildUpdateData, settings: GuildSettings, userId: String, event: Event): EmbedCreateSpec {
|
||||
val iconUrl = if (guild.icon().isPresent)
|
||||
"${GlobalVal.discordCdnUrl}/icons/${guild.id().asString()}/${guild.icon().get()}.png"
|
||||
else GlobalVal.iconUrl
|
||||
|
||||
val builder = EmbedCreateSpec.builder()
|
||||
// Even without branding enabled, we want the user to know what guild this is because it's in DMs
|
||||
.author(guild.name(), BotSettings.BASE_URL.get(), iconUrl)
|
||||
.title(getEmbedMessage("rsvp", "waitlist.title", settings))
|
||||
.description(getEmbedMessage("rsvp", "waitlist.desc", settings, userId, event.name, event.eventId))
|
||||
.addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.start", settings),
|
||||
event.start.asDiscordTimestamp(LONG_DATETIME),
|
||||
true
|
||||
).addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.end", settings),
|
||||
event.end.asDiscordTimestamp(LONG_DATETIME),
|
||||
true
|
||||
).footer(getEmbedMessage("rsvp", "waitlist.footer", settings, event.eventId), null)
|
||||
|
||||
if (event.location.isNotBlank()) builder.addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.location", settings),
|
||||
event.location.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
)
|
||||
|
||||
if (event.image.isNotBlank()) builder.thumbnail(event.image)
|
||||
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun followupEmbed(guild: GuildData, settings: GuildSettings, userId: String, event: Event): EmbedCreateSpec {
|
||||
val iconUrl = if (guild.icon().isPresent)
|
||||
"${GlobalVal.discordCdnUrl}/icons/${guild.id().asString()}/${guild.icon().get()}.png"
|
||||
else GlobalVal.iconUrl
|
||||
|
||||
val builder = EmbedCreateSpec.builder()
|
||||
// Even without branding enabled, we want the user to know what guild this is because it's in DMs
|
||||
.author(guild.name(), BotSettings.BASE_URL.get(), iconUrl)
|
||||
.title(getEmbedMessage("rsvp", "waitlist.title", settings))
|
||||
.description(getEmbedMessage("rsvp", "waitlist.desc", settings, userId, event.name, event.eventId))
|
||||
.addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.start", settings),
|
||||
event.start.asDiscordTimestamp(LONG_DATETIME),
|
||||
true
|
||||
).addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.end", settings),
|
||||
event.end.asDiscordTimestamp(LONG_DATETIME),
|
||||
true
|
||||
).footer(getEmbedMessage("rsvp", "waitlist.footer", settings, event.eventId), null)
|
||||
|
||||
if (event.location.isNotBlank()) builder.addField(
|
||||
getEmbedMessage("rsvp", "waitlist.field.location", settings),
|
||||
event.location.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
)
|
||||
|
||||
if (event.image.isNotBlank()) builder.thumbnail(event.image)
|
||||
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun dmUser(userId: String, embedCreateSpec: EmbedCreateSpec, client: DiscordClient): Mono<MessageData> {
|
||||
return client.getUserById(Snowflake.of(userId)).privateChannel.flatMap { channelData ->
|
||||
client.getChannelById(Snowflake.of(channelData.id())).createMessage(embedCreateSpec.asRequest())
|
||||
}.doOnError {
|
||||
LOGGER.error("Failed to DM user for RSVP Followup", it)
|
||||
}.onErrorResume {
|
||||
Mono.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,7 @@ import java.time.Duration
|
||||
import javax.imageio.ImageIO
|
||||
import javax.imageio.ImageReader
|
||||
|
||||
//TODO: Remove jvm static
|
||||
object ImageValidator {
|
||||
@JvmStatic
|
||||
fun validate(url: String, allowGif: Boolean): Mono<Boolean> {
|
||||
return Mono.fromCallable {
|
||||
val image = ImageIO.read(URL(url))
|
||||
|
||||
@@ -37,3 +37,9 @@ fun getEmbedMessage(embed: String, key: String, settings: GuildSettings, vararg
|
||||
|
||||
return src.getMessage(key, args, settings.getLocale())
|
||||
}
|
||||
|
||||
fun getCmdMessage(cmd: String, key: String, settings: GuildSettings, vararg args: String): String {
|
||||
val src = MessageSourceLoader.getSourceByPath("command/$cmd/$cmd")
|
||||
|
||||
return src.getMessage(key, args, settings.getLocale())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE rsvp
|
||||
ADD COLUMN waitlist LONGTEXT not null default ''
|
||||
after UNDECIDED;
|
||||
@@ -1,19 +1,10 @@
|
||||
meta.description=A set of commands allowing RSVP functionality
|
||||
meta.example=/rsvp <subCommand> <eventId> (args...)
|
||||
meta.description.onTime=RSVPs you as attending the event on time
|
||||
meta.description.late=RSVPs you as attending the event late
|
||||
meta.description.notGoing=RSVPs you as not attending the event
|
||||
meta.description.unsure=RSVPs you as unsure if you will be attending the event
|
||||
meta.description.remove=Removes your previous RSVP status from the event
|
||||
meta.description.list=Lists who has RSVPed to the event
|
||||
meta.description.limit=Sets a limit of how many people may attend the event
|
||||
meta.description.role=Sets the role assigned when a member RSVPs as attending the event. *Not automatically removed
|
||||
|
||||
onTime.success=Confirmed your event attendance as arriving on time.
|
||||
onTime.failure.limit=Sorry, but the maximum amount of people have RSVPed as attending. Consider using `/rsvp unsure`?
|
||||
onTime.failure.limit=Sorry, but the maximum amount of people have RSVPed as attending. \n\n\
|
||||
You have been added to the waitlist and will receive a DM if an opening becomes available.
|
||||
|
||||
late.success=Confirmed your event attendance as arriving late.
|
||||
late.failure.limit=Sorry, but the maximum amount of people have RSVPed as attending. Consider using `/rsvp unsure`?
|
||||
late.failure.limit=Sorry, but the maximum amount of people have RSVPed as attending. \n\n\
|
||||
You have been added to the waitlist and will receive a DM if an opening becomes available.
|
||||
|
||||
notGoing.success=Confirmed that you will not be able to attend the event.
|
||||
|
||||
@@ -24,4 +15,4 @@ remove.success=Successfully removed your response about attending the event.
|
||||
limit.success=Successfully set the max amount of people that can attend the event to `{0}`!
|
||||
|
||||
role.success.set=Members who confirm they will attend the event will receive the `{0}` role!
|
||||
role.success.remove=Members who confirm they will attend the event will not longer receive a special role.
|
||||
role.success.remove=Members who confirm they will attend the event will no longer receive a special role.
|
||||
|
||||
@@ -7,4 +7,14 @@ list.field.onTime=Arriving On Time
|
||||
list.field.late=Arriving Late
|
||||
list.field.unsure=Unsure About Attending
|
||||
list.field.notGoing=Not Attending
|
||||
list.field.waitList=Wait List
|
||||
list.footer=Use `!event view <id>` to view details about this event
|
||||
|
||||
waitlist.title=| Event RSVP Followup |
|
||||
waitlist.desc=<@{0}>, \n\
|
||||
A space has become available in `{1}`, and you have been moved from the waitlist, to attending. \n\n\
|
||||
If you can't make the event, please update your RSVP with `/rsvp not-going event:{2}`, otherwise enjoy the event!
|
||||
waitlist.field.start=Start (Local)
|
||||
waitlist.field.end=End (Local)
|
||||
waitlist.field.location=Location
|
||||
waitlist.footer=Event ID: {0}
|
||||
|
||||
Reference in New Issue
Block a user