mirror of
https://github.com/DreamExposure/DisCal-Discord-Bot.git
synced 2026-04-30 22:29:28 -05:00
Just casually deleting loads of now unused old code. Feels good
This commit is contained in:
@@ -27,7 +27,7 @@ class Cam {
|
||||
.run(*args)
|
||||
LOGGER.info(GlobalVal.STATUS, "CAM is now online")
|
||||
} catch (e: Exception) {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "'Spring error' by PANIC! at the CAM")
|
||||
LOGGER.error(GlobalVal.DEFAULT, "'Spring error' by PANIC! at the CAM", e)
|
||||
exitProcess(4)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package org.dreamexposure.discal.client.config
|
||||
|
||||
import org.dreamexposure.discal.core.`object`.Wizard
|
||||
import org.dreamexposure.discal.core.`object`.event.PreEvent
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
class DisCalConfig {
|
||||
|
||||
@Bean
|
||||
fun eventWizard(): Wizard<PreEvent> = Wizard()
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package org.dreamexposure.discal.client.message.embed
|
||||
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.core.spec.EmbedCreateSpec
|
||||
import discord4j.rest.util.Image
|
||||
import org.dreamexposure.discal.core.config.Config
|
||||
import org.dreamexposure.discal.core.`object`.new.GuildSettings
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import org.dreamexposure.discal.core.utils.getCommonMsg
|
||||
import org.dreamexposure.discal.core.utils.getEmbedMessage
|
||||
|
||||
interface EmbedMaker {
|
||||
fun defaultBuilder(guild: Guild, settings: GuildSettings): EmbedCreateSpec.Builder {
|
||||
val builder = EmbedCreateSpec.builder()
|
||||
|
||||
if (settings.interfaceStyle.branded)
|
||||
builder.author(guild.name, Config.URL_BASE.getString(), guild.getIconUrl(Image.Format.PNG).orElse(GlobalVal.iconUrl))
|
||||
else
|
||||
builder.author(getCommonMsg("bot.name", settings.locale), Config.URL_BASE.getString(), GlobalVal.iconUrl)
|
||||
|
||||
return builder
|
||||
}
|
||||
|
||||
fun getMessage(embed: String, key: String, settings: GuildSettings, vararg args: String) =
|
||||
getEmbedMessage(embed, key, settings.locale, *args)
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package org.dreamexposure.discal.client.message.embed
|
||||
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.core.spec.EmbedCreateSpec
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.enums.time.DiscordTimestampFormat.LONG_DATETIME
|
||||
import org.dreamexposure.discal.core.extensions.*
|
||||
import org.dreamexposure.discal.core.`object`.event.PreEvent
|
||||
import org.dreamexposure.discal.core.`object`.new.GuildSettings
|
||||
import org.dreamexposure.discal.core.utils.getCommonMsg
|
||||
|
||||
object EventEmbed : EmbedMaker {
|
||||
@Deprecated("Prefer to use EmbedService impl")
|
||||
fun getFull(guild: Guild, settings: GuildSettings, event: Event): EmbedCreateSpec {
|
||||
val builder = defaultBuilder(guild, settings)
|
||||
.footer(getMessage("event", "full.footer", settings, event.eventId), null)
|
||||
.color(event.color.asColor())
|
||||
|
||||
if (event.name.isNotBlank())
|
||||
builder.title(event.name.toMarkdown().embedTitleSafe())
|
||||
if (event.description.isNotBlank())
|
||||
builder.description(event.description.toMarkdown().embedDescriptionSafe())
|
||||
|
||||
builder.addField(
|
||||
getMessage("event", "full.field.start", settings),
|
||||
event.start.asDiscordTimestamp(LONG_DATETIME),
|
||||
true)
|
||||
builder.addField(
|
||||
getMessage("event", "full.field.end", settings),
|
||||
event.end.asDiscordTimestamp(LONG_DATETIME),
|
||||
true
|
||||
)
|
||||
|
||||
if (event.location.isNotBlank()) builder.addField(
|
||||
getMessage("event", "full.field.location", settings),
|
||||
event.location.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
)
|
||||
|
||||
builder.addField(getMessage("event", "full.field.cal", settings), "${event.calendar.calendarNumber}", false)
|
||||
|
||||
if (event.image.isNotEmpty())
|
||||
builder.image(event.image)
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Deprecated("Prefer to use EmbedService impl")
|
||||
fun pre(guild: Guild, settings: GuildSettings, event: PreEvent): EmbedCreateSpec {
|
||||
val builder = defaultBuilder(guild, settings)
|
||||
.title(getMessage("event", "wizard.title", settings))
|
||||
.footer(getMessage("event", "wizard.footer", settings), null)
|
||||
.color(event.color.asColor())
|
||||
|
||||
if (!event.name.isNullOrBlank()) builder.addField(
|
||||
getMessage("event", "wizard.field.name", settings),
|
||||
event.name!!.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.name", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
false
|
||||
)
|
||||
|
||||
if (!event.description.isNullOrBlank()) builder.addField(
|
||||
getMessage("event", "wizard.field.desc", settings),
|
||||
event.description!!.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.desc", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
false
|
||||
)
|
||||
|
||||
if (!event.location.isNullOrBlank()) builder.addField(
|
||||
getMessage("event", "wizard.field.location", settings),
|
||||
event.location!!.toMarkdown().embedFieldSafe(),
|
||||
false
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.location", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
false
|
||||
)
|
||||
|
||||
if (event.start != null) builder.addField(
|
||||
getMessage("event", "wizard.field.start", settings),
|
||||
event.start!!.humanReadableFull(event.timezone, settings.interfaceStyle.timeFormat),
|
||||
true
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.start", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
true
|
||||
)
|
||||
|
||||
if (event.end != null) builder.addField(
|
||||
getMessage("event", "wizard.field.end", settings),
|
||||
event.end!!.humanReadableFull(event.timezone, settings.interfaceStyle.timeFormat),
|
||||
true
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.end", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
true
|
||||
)
|
||||
|
||||
if (event.recurrence != null) builder.addField(
|
||||
getMessage("event", "wizard.field.recurrence", settings),
|
||||
event.recurrence!!.toHumanReadable(),
|
||||
true
|
||||
) else if (event.editing && event.eventId != null && event.eventId!!.contains("_")) builder.addField(
|
||||
getMessage("event", "wizard.field.recurrence", settings),
|
||||
getMessage("event", "wizard.field.recurrence.child", settings, event.eventId!!.split("_")[0]),
|
||||
false,
|
||||
) else builder.addField(
|
||||
getMessage("event", "wizard.field.recurrence", settings),
|
||||
getCommonMsg("embed.unset", settings.locale),
|
||||
true
|
||||
)
|
||||
|
||||
builder.addField(getMessage("event", "wizard.field.timezone", settings), event.timezone.id, false)
|
||||
|
||||
if (event.editing)
|
||||
builder.addField(getMessage("event", "wizard.field.id", settings), event.eventId!!, true)
|
||||
builder.addField(getMessage("event", "wizard.field.calendar", settings), event.calNumber.toString(), true)
|
||||
|
||||
if (event.image != null)
|
||||
builder.image(event.image!!)
|
||||
|
||||
val warnings = event.generateWarnings(settings)
|
||||
if (warnings.isNotEmpty()) {
|
||||
val warnText = "```fix\n${warnings.joinToString("\n")}\n```"
|
||||
builder.addField(getMessage("event", "wizard.field.warnings", settings), warnText, false)
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.dreamexposure.discal.core.cache
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import reactor.core.publisher.Flux
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
//TODO: Eventually use redis instead of in-memory so these can be shared across the whole discal network and need less time for eventual consistency.
|
||||
@Deprecated("Use proper caching impl")
|
||||
object DiscalCache {
|
||||
//guild id -> cal num -> calendar
|
||||
private val calendars: MutableMap<Snowflake, ConcurrentHashMap<Int, Calendar>> = ConcurrentHashMap()
|
||||
|
||||
init {
|
||||
//Automatically clear caches every so often...
|
||||
Flux.interval(Duration.ofMinutes(15))
|
||||
.doOnEach { invalidateAll() }
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
|
||||
fun invalidateAll() {
|
||||
calendars.clear()
|
||||
}
|
||||
|
||||
//Functions to stop direct modification
|
||||
fun getCalendar(guildId: Snowflake, calNum: Int): Calendar? = calendars[guildId]?.get(calNum)
|
||||
|
||||
fun getAllCalendars(guildId: Snowflake): Collection<Calendar>? = calendars[guildId]?.values
|
||||
|
||||
fun putCalendar(calendar: Calendar) {
|
||||
if (calendars.containsKey(calendar.guildId))
|
||||
calendars[calendar.guildId]!![calendar.calendarNumber] = calendar
|
||||
else {
|
||||
val map = ConcurrentHashMap<Int, Calendar>()
|
||||
map[calendar.calendarNumber] = calendar
|
||||
|
||||
calendars[calendar.guildId] = map
|
||||
}
|
||||
}
|
||||
|
||||
fun handleCalendarDelete(guildId: Snowflake) {
|
||||
removeCalendars(guildId)
|
||||
//Eventually other cached things will be here, like events, rsvp data, etc
|
||||
}
|
||||
|
||||
fun removeCalendars(guildId: Snowflake) {
|
||||
calendars.remove(guildId)
|
||||
}
|
||||
}
|
||||
@@ -2,25 +2,19 @@
|
||||
|
||||
package org.dreamexposure.discal.core.database
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import io.r2dbc.pool.ConnectionPool
|
||||
import io.r2dbc.pool.ConnectionPoolConfiguration
|
||||
import io.r2dbc.spi.Connection
|
||||
import io.r2dbc.spi.ConnectionFactories
|
||||
import io.r2dbc.spi.ConnectionFactoryOptions.*
|
||||
import io.r2dbc.spi.Result
|
||||
import org.dreamexposure.discal.core.config.Config
|
||||
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.`object`.event.EventData
|
||||
import org.dreamexposure.discal.core.`object`.web.UserAPIAccount
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.DEFAULT
|
||||
import org.intellij.lang.annotations.Language
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.util.retry.Retry
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.function.Function
|
||||
|
||||
object DatabaseManager {
|
||||
@@ -52,131 +46,6 @@ object DatabaseManager {
|
||||
|
||||
fun disconnectFromMySQL() = pool.dispose()
|
||||
|
||||
fun updateCalendar(calData: CalendarData): Mono<Boolean> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_CALENDAR_BY_GUILD)
|
||||
.bind(0, calData.guildId.asLong())
|
||||
.bind(1, calData.calendarNumber)
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ -> row }
|
||||
}.hasElements().flatMap { exists ->
|
||||
if (exists) {
|
||||
val updateCommand = """UPDATE ${Tables.CALENDARS} SET
|
||||
HOST = ?, CALENDAR_ID = ?,
|
||||
CALENDAR_ADDRESS = ?, EXTERNAL = ?, CREDENTIAL_ID = ?,
|
||||
PRIVATE_KEY = ?, ACCESS_TOKEN = ?, REFRESH_TOKEN = ?, EXPIRES_AT = ?
|
||||
WHERE GUILD_ID = ? AND CALENDAR_NUMBER = ?
|
||||
""".trimMargin()
|
||||
|
||||
Mono.from(
|
||||
c.createStatement(updateCommand)
|
||||
.bind(0, calData.host.name)
|
||||
.bind(1, calData.calendarId)
|
||||
.bind(2, calData.calendarAddress)
|
||||
.bind(3, calData.external)
|
||||
.bind(4, calData.credentialId)
|
||||
.bind(5, calData.privateKey)
|
||||
.bind(6, calData.encryptedAccessToken)
|
||||
.bind(7, calData.encryptedRefreshToken)
|
||||
.bind(8, calData.expiresAt.toEpochMilli())
|
||||
.bind(9, calData.guildId.asLong())
|
||||
.bind(10, calData.calendarNumber)
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
} else {
|
||||
val insertCommand = """INSERT INTO ${Tables.CALENDARS}
|
||||
(GUILD_ID, CALENDAR_NUMBER, HOST, CALENDAR_ID,
|
||||
CALENDAR_ADDRESS, EXTERNAL, CREDENTIAL_ID,
|
||||
PRIVATE_KEY, ACCESS_TOKEN, REFRESH_TOKEN, EXPIRES_AT) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""".trimMargin()
|
||||
|
||||
Mono.from(
|
||||
c.createStatement(insertCommand)
|
||||
.bind(0, calData.guildId.asLong())
|
||||
.bind(1, calData.calendarNumber)
|
||||
.bind(2, calData.host.name)
|
||||
.bind(3, calData.calendarId)
|
||||
.bind(4, calData.calendarAddress)
|
||||
.bind(5, calData.external)
|
||||
.bind(6, calData.credentialId)
|
||||
.bind(7, calData.privateKey)
|
||||
.bind(8, calData.encryptedAccessToken)
|
||||
.bind(9, calData.encryptedRefreshToken)
|
||||
.bind(10, calData.expiresAt.toEpochMilli())
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
}
|
||||
}.doOnError {
|
||||
LOGGER.error(DEFAULT, "Failed to update calendar data", it)
|
||||
}.onErrorResume { Mono.just(false) }
|
||||
}
|
||||
}
|
||||
|
||||
fun updateEventData(data: EventData): Mono<Boolean> {
|
||||
val id = if (data.eventId.contains("_"))
|
||||
data.eventId.split("_")[0]
|
||||
else
|
||||
data.eventId
|
||||
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_EVENT_BY_GUILD)
|
||||
.bind(0, data.guildId.asLong())
|
||||
.bind(1, id)
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ -> row }
|
||||
}.hasElements().flatMap { exists ->
|
||||
if (exists) {
|
||||
val updateCommand = """UPDATE ${Tables.EVENTS} SET
|
||||
CALENDAR_NUMBER = ?, IMAGE_LINK = ?, EVENT_END = ?
|
||||
WHERE EVENT_ID = ? AND GUILD_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
Mono.from(
|
||||
c.createStatement(updateCommand)
|
||||
.bind(0, data.calendarNumber)
|
||||
.bind(1, data.imageLink)
|
||||
.bind(2, data.eventEnd)
|
||||
.bind(3, id)
|
||||
.bind(4, data.guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
} else if (data.shouldBeSaved()) {
|
||||
val insertCommand = """INSERT INTO ${Tables.EVENTS}
|
||||
(GUILD_ID, EVENT_ID, CALENDAR_NUMBER, EVENT_END, IMAGE_LINK)
|
||||
VALUES(?, ?, ?, ?, ?)
|
||||
""".trimMargin()
|
||||
|
||||
Mono.from(
|
||||
c.createStatement(insertCommand)
|
||||
.bind(0, data.guildId.asLong())
|
||||
.bind(1, id)
|
||||
.bind(2, data.calendarNumber)
|
||||
.bind(3, data.eventEnd)
|
||||
.bind(4, data.imageLink)
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
} else {
|
||||
Mono.just(false)
|
||||
}.doOnError {
|
||||
LOGGER.error(DEFAULT, "Failed to update event data", it)
|
||||
}.onErrorResume { Mono.just(false) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun getAPIAccount(APIKey: String): Mono<UserAPIAccount> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
@@ -200,279 +69,6 @@ object DatabaseManager {
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
}
|
||||
|
||||
fun getCalendar(guildId: Snowflake, calendarNumber: Int): Mono<CalendarData> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_CALENDAR_BY_GUILD)
|
||||
.bind(0, guildId.asLong())
|
||||
.bind(1, calendarNumber)
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val calId = row["CALENDAR_ID", String::class.java]!!
|
||||
val calNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
val calAddr = row["CALENDAR_ADDRESS", String::class.java]!!
|
||||
val host = CalendarHost.valueOf(row["HOST", String::class.java]!!)
|
||||
val external = row["EXTERNAL", Boolean::class.java]!!
|
||||
val credId = row["CREDENTIAL_ID", Int::class.java]!!
|
||||
val privateKey = row["PRIVATE_KEY", String::class.java]!!
|
||||
val accessToken = row["ACCESS_TOKEN", String::class.java]!!
|
||||
val refreshToken = row["REFRESH_TOKEN", String::class.java]!!
|
||||
val expiresAt = Instant.ofEpochMilli(row["EXPIRES_AT", Long::class.java]!!)
|
||||
|
||||
CalendarData(
|
||||
guildId, calNumber, host, calId, calAddr, external,
|
||||
credId, privateKey, accessToken, refreshToken, expiresAt
|
||||
)
|
||||
}
|
||||
}.next().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 all guild calendars", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
}
|
||||
|
||||
fun getAllCalendars(guildId: Snowflake): Mono<List<CalendarData>> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_ALL_CALENDARS_BY_GUILD)
|
||||
.bind(0, guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val calId = row["CALENDAR_ID", String::class.java]!!
|
||||
val calNumber = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
val calAddr = row["CALENDAR_ADDRESS", String::class.java]!!
|
||||
val host = CalendarHost.valueOf(row["HOST", String::class.java]!!)
|
||||
val external = row["EXTERNAL", Boolean::class.java]!!
|
||||
val credId = row["CREDENTIAL_ID", Int::class.java]!!
|
||||
val privateKey = row["PRIVATE_KEY", String::class.java]!!
|
||||
val accessToken = row["ACCESS_TOKEN", String::class.java]!!
|
||||
val refreshToken = row["REFRESH_TOKEN", String::class.java]!!
|
||||
val expiresAt = Instant.ofEpochMilli(row["EXPIRES_AT", Long::class.java]!!)
|
||||
|
||||
CalendarData(
|
||||
guildId, calNumber, host, calId, calAddr, external,
|
||||
credId, privateKey, accessToken, refreshToken, expiresAt
|
||||
)
|
||||
}
|
||||
}.collectList().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 all guild calendars", it)
|
||||
}.onErrorReturn(mutableListOf())
|
||||
}
|
||||
}
|
||||
|
||||
fun getCalendarCount(guildId: Snowflake): Mono<Int> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_CALENDAR_COUNT_BY_GUILD)
|
||||
.bind(0, guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val calendars = row.get(0, Long::class.java)!!
|
||||
return@map calendars.toInt()
|
||||
}
|
||||
}.next().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 calendar count", it)
|
||||
}.onErrorReturn(-1)
|
||||
}.defaultIfEmpty(0)
|
||||
}
|
||||
|
||||
fun getEventData(guildId: Snowflake, eventId: String): Mono<EventData> {
|
||||
var eventIdLookup = eventId
|
||||
if (eventId.contains("_"))
|
||||
eventIdLookup = eventId.split("_")[0]
|
||||
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.SELECT_EVENT_BY_GUILD)
|
||||
.bind(0, guildId.asLong())
|
||||
.bind(1, eventIdLookup)
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
|
||||
val id = row["EVENT_ID", String::class.java]!!
|
||||
val calNum = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
val end = row["EVENT_END", Long::class.java]!!
|
||||
val img = row["IMAGE_LINK", String::class.java]!!
|
||||
|
||||
EventData(guildId, id, calNum, end, img)
|
||||
}
|
||||
}.next().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 event data", it)
|
||||
}.onErrorResume {
|
||||
Mono.empty()
|
||||
}
|
||||
}.defaultIfEmpty(EventData(guildId, eventId = eventIdLookup))
|
||||
}
|
||||
|
||||
fun deleteAnnouncementsForEvent(guildId: Snowflake, eventId: String): Mono<Boolean> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.DELETE_ANNOUNCEMENTS_FOR_EVENT)
|
||||
.bind(0, eventId)
|
||||
.bind(1, guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
.doOnError {
|
||||
LOGGER.error(DEFAULT, "Failed to delete announcements for event", it)
|
||||
}.onErrorReturn(false)
|
||||
}.defaultIfEmpty(false)
|
||||
}
|
||||
|
||||
fun deleteEventData(eventId: String): Mono<Boolean> {
|
||||
if (eventId.contains("_")) return Mono.empty() // Don't delete if child event of recurring parent.
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.DELETE_EVENT_DATA)
|
||||
.bind(0, eventId)
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
.doOnError {
|
||||
LOGGER.error(DEFAULT, "Failed to delete event data", it)
|
||||
}.onErrorReturn(false)
|
||||
}.defaultIfEmpty(false)
|
||||
}
|
||||
|
||||
/* Utility Deletion Methods */
|
||||
|
||||
fun deleteCalendarAndRelatedData(calendarData: CalendarData): Mono<Boolean> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.FULL_CALENDAR_DELETE) //Monolith 8 statement query
|
||||
// calendar delete bindings
|
||||
.bind(0, calendarData.guildId.asLong())
|
||||
.bind(1, calendarData.calendarNumber)
|
||||
// event delete bindings
|
||||
.bind(2, calendarData.guildId.asLong())
|
||||
.bind(3, calendarData.calendarNumber)
|
||||
// rsvp delete bindings
|
||||
.bind(4, calendarData.guildId.asLong())
|
||||
.bind(5, calendarData.calendarNumber)
|
||||
// announcement delete bindings
|
||||
.bind(6, calendarData.guildId.asLong())
|
||||
.bind(7, calendarData.calendarNumber)
|
||||
// delete static message bindings
|
||||
.bind(8, calendarData.guildId.asLong())
|
||||
.bind(9, calendarData.calendarNumber)
|
||||
// decrement calendar bindings
|
||||
.bind(10, calendarData.calendarNumber)
|
||||
.bind(11, calendarData.guildId.asLong())
|
||||
// decrement event bindings
|
||||
.bind(12, calendarData.calendarNumber)
|
||||
.bind(13, calendarData.guildId.asLong())
|
||||
// decrement rsvp bindings
|
||||
.bind(14, calendarData.calendarNumber)
|
||||
.bind(15, calendarData.guildId.asLong())
|
||||
// decrement announcement bindings
|
||||
.bind(16, calendarData.calendarNumber)
|
||||
.bind(17, calendarData.guildId.asLong())
|
||||
// decrement static message bindings
|
||||
.bind(18, calendarData.calendarNumber)
|
||||
.bind(19, calendarData.guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
.doOnError {
|
||||
LOGGER.error(DEFAULT, "Full calendar delete failed!", it)
|
||||
}.onErrorReturn(false)
|
||||
}.defaultIfEmpty(true) // If nothing was updated and no error was emitted, it's safe to return this worked.
|
||||
}
|
||||
|
||||
fun deleteAllDataForGuild(guildId: Snowflake): Mono<Boolean> {
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
c.createStatement(Queries.DELETE_EVERYTHING_FOR_GUILD) //Monolith 6 statement query
|
||||
// settings delete bindings
|
||||
.bind(0, guildId.asLong())
|
||||
// calendar delete bindings
|
||||
.bind(1, guildId.asLong())
|
||||
// event delete bindings
|
||||
.bind(2, guildId.asLong())
|
||||
// rsvp delete bindings
|
||||
.bind(3, guildId.asLong())
|
||||
// announcement delete bindings
|
||||
.bind(4, guildId.asLong())
|
||||
// static message delete bindings
|
||||
.bind(5, guildId.asLong())
|
||||
.execute()
|
||||
).flatMapMany(Result::getRowsUpdated)
|
||||
.hasElements()
|
||||
.thenReturn(true)
|
||||
.doOnError {
|
||||
LOGGER.error(DEFAULT, "Full data delete failed!", it)
|
||||
}.onErrorReturn(false)
|
||||
}.defaultIfEmpty(true) // If nothing was updated and no error was emitted, it's safe to return this worked.
|
||||
}
|
||||
|
||||
/* Event Data */
|
||||
|
||||
fun getEventsData(guildId: Snowflake, eventIds: List<String>): Mono<Map<String, EventData>> {
|
||||
// clean up IDs
|
||||
val idsToUse = mutableListOf<String>()
|
||||
eventIds.forEach {
|
||||
val id = if (it.contains("_")) it.split("_")[0] else it
|
||||
|
||||
if (!idsToUse.contains(id)) idsToUse.add(id)
|
||||
}
|
||||
|
||||
if (idsToUse.isEmpty()) return Mono.just(emptyMap())
|
||||
|
||||
// Convert our list of IDs to sql escaped string
|
||||
val builder = StringBuilder()
|
||||
idsToUse.withIndex().forEach {
|
||||
if (it.index != idsToUse.size - 1) {
|
||||
builder.append("'${it.value}', ")
|
||||
} else {
|
||||
builder.append("'${it.value}'")
|
||||
}
|
||||
}
|
||||
|
||||
return connect { c ->
|
||||
Mono.from(
|
||||
//Have to do it this way, sql injection is not possible as these IDs are not user input
|
||||
c.createStatement(Queries.SELECT_MANY_EVENT_DATA.replace("?", builder.toString()))
|
||||
.execute()
|
||||
).flatMapMany { res ->
|
||||
res.map { row, _ ->
|
||||
val id = row["EVENT_ID", String::class.java]!!
|
||||
val calNum = row["CALENDAR_NUMBER", Int::class.java]!!
|
||||
val end = row["EVENT_END", Long::class.java]!!
|
||||
val img = row["IMAGE_LINK", String::class.java]!!
|
||||
|
||||
EventData(guildId, id, calNum, end, img)
|
||||
}
|
||||
}.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()
|
||||
}.collectMap {
|
||||
it.eventId
|
||||
}.defaultIfEmpty(emptyMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object Queries {
|
||||
@@ -480,115 +76,6 @@ private object Queries {
|
||||
val SELECT_API_KEY = """SELECT * FROM ${Tables.API}
|
||||
WHERE API_KEY = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_CALENDAR_BY_GUILD = """SELECT * FROM ${Tables.CALENDARS}
|
||||
WHERE GUILD_ID = ? AND CALENDAR_NUMBER = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_ALL_CALENDARS_BY_GUILD = """SELECT * FROM ${Tables.CALENDARS}
|
||||
WHERE GUILD_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_CALENDAR_COUNT_BY_GUILD = """SELECT COUNT(*) FROM ${Tables.CALENDARS}
|
||||
WHERE GUILD_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_EVENT_BY_GUILD = """SELECT * FROM ${Tables.EVENTS}
|
||||
WHERE GUILD_ID = ? AND EVENT_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_ANNOUNCEMENTS_FOR_EVENT = """DELETE FROM ${Tables.ANNOUNCEMENTS}
|
||||
WHERE EVENT_ID = ? AND GUILD_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_EVENT_DATA = """DELETE FROM ${Tables.EVENTS}
|
||||
WHERE EVENT_ID = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_ALL_EVENT_DATA = """DELETE FROM ${Tables.EVENTS}
|
||||
WHERE GUILD_ID = ? AND CALENDAR_NUMBER = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_ALL_ANNOUNCEMENT_DATA = """DELETE FROM ${Tables.ANNOUNCEMENTS}
|
||||
WHERE GUILD_ID = ? AND CALENDAR_NUMBER = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_ALL_RSVP_DATA = """DELETE FROM ${Tables.RSVP}
|
||||
WHERE GUILD_ID = ? AND CALENDAR_NUMBER = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_CALENDAR = """DELETE FROM ${Tables.CALENDARS}
|
||||
WHERE GUILD_ID = ? AND calendar_number = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DECREMENT_CALENDARS = """UPDATE ${Tables.CALENDARS}
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >=? AND guild_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DECREMENT_ANNOUNCEMENTS = """UPDATE ${Tables.ANNOUNCEMENTS}
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >=? AND guild_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DECREMENT_EVENTS = """UPDATE ${Tables.EVENTS}
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >=? AND guild_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DECREMENT_RSVPS = """UPDATE ${Tables.RSVP}
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >=? AND guild_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DECREMENT_STATIC_MESSAGES = """UPDATE ${Tables.STATIC_MESSAGES}
|
||||
SET calendar_number = calendar_number - 1
|
||||
WHERE calendar_number >=? AND guild_id = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_ALL_STATIC_MESSAGES = """DELETE FROM ${Tables.STATIC_MESSAGES}
|
||||
WHERE guild_id = ? AND calendar_number = ?
|
||||
""".trimMargin()
|
||||
|
||||
@Language("MySQL")
|
||||
val FULL_CALENDAR_DELETE = """
|
||||
$DELETE_CALENDAR;$DELETE_ALL_EVENT_DATA;$DELETE_ALL_RSVP_DATA;$DELETE_ALL_ANNOUNCEMENT_DATA;$DELETE_ALL_STATIC_MESSAGES;
|
||||
$DECREMENT_CALENDARS;$DECREMENT_EVENTS;$DECREMENT_RSVPS;$DECREMENT_ANNOUNCEMENTS;$DECREMENT_STATIC_MESSAGES
|
||||
""".trimIndent()
|
||||
|
||||
|
||||
@Language("MySQL")
|
||||
val SELECT_MANY_EVENT_DATA = """SELECT * FROM ${Tables.EVENTS}
|
||||
WHERE event_id in (?)
|
||||
""".trimMargin()
|
||||
|
||||
/* Delete everything */
|
||||
|
||||
@Language("MySQL")
|
||||
val DELETE_EVERYTHING_FOR_GUILD = """
|
||||
delete from ${Tables.GUILD_SETTINGS} where GUILD_ID=?;
|
||||
delete from ${Tables.CALENDARS} where GUILD_ID=?;
|
||||
delete from ${Tables.EVENTS} where GUILD_ID=?;
|
||||
delete from ${Tables.RSVP} where GUILD_ID=?;
|
||||
delete from ${Tables.ANNOUNCEMENTS} where GUILD_ID=?;
|
||||
delete from ${Tables.STATIC_MESSAGES} where guild_id=?;
|
||||
""".trimMargin()
|
||||
}
|
||||
|
||||
private object Tables {
|
||||
@@ -596,22 +83,4 @@ private object Tables {
|
||||
|
||||
@Language("Kotlin")
|
||||
const val API: String = "api"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val GUILD_SETTINGS = "guild_settings"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val CALENDARS = "calendars"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val ANNOUNCEMENTS = "announcements"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val EVENTS = "events"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val RSVP = "rsvp"
|
||||
|
||||
@Language("Kotlin")
|
||||
const val STATIC_MESSAGES = "static_messages"
|
||||
}
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import org.dreamexposure.discal.core.config.Config
|
||||
import org.dreamexposure.discal.core.entities.google.GoogleCalendar
|
||||
import org.dreamexposure.discal.core.entities.response.UpdateCalendarResponse
|
||||
import org.dreamexposure.discal.core.entities.spec.create.CreateEventSpec
|
||||
import org.dreamexposure.discal.core.entities.spec.update.UpdateCalendarSpec
|
||||
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.`object`.web.WebCalendar
|
||||
import org.json.JSONObject
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Deprecated("Prefer to use new.Event via the new CalendarService")
|
||||
interface Calendar {
|
||||
/**
|
||||
* The ID of the [Guild] this calendar belongs to.
|
||||
*/
|
||||
val guildId: Snowflake
|
||||
get() = calendarData.guildId
|
||||
|
||||
/**
|
||||
* The base object in which most of the calendar data comes from
|
||||
*/
|
||||
val calendarData: CalendarData
|
||||
|
||||
/**
|
||||
* The calendar's ID, usually but not always the same as the calendar's [address][calendarAddress].
|
||||
* Use this for unique identification of the calendar
|
||||
*/
|
||||
val calendarId: String
|
||||
get() = calendarData.calendarId
|
||||
|
||||
/**
|
||||
* The calendar's address, usually but not always the same as the calendar's [ID][calendarId].
|
||||
* This property may not be unique, use [calendarId] instead
|
||||
*/
|
||||
val calendarAddress: String
|
||||
get() = calendarData.calendarAddress
|
||||
|
||||
/**
|
||||
* The relative number of the calendar in order it was created in for the [Guild].
|
||||
* Calendar number `1` is the `main` calendar for the [Guild], used as the default.
|
||||
*/
|
||||
val calendarNumber: Int
|
||||
get() = calendarData.calendarNumber
|
||||
|
||||
/**
|
||||
* Whether the calendar is "external" meaning it is owned by a user account.
|
||||
* This does not indicate the service used to host the calendar, but whether it is owned by DisCal, or a user.
|
||||
*/
|
||||
val external: Boolean
|
||||
get() = calendarData.external
|
||||
|
||||
/**
|
||||
* The name of the calendar. Renamed from "summary" to be more user-friendly and clear.
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* A longer form description of the calendar.
|
||||
* If this is not present, an empty string is returned
|
||||
*/
|
||||
val description: String
|
||||
|
||||
/**
|
||||
* The timezone the calendar uses. Normally in its longer name, such as `America/New_York`
|
||||
*/
|
||||
val timezone: ZoneId
|
||||
|
||||
/**
|
||||
* The timezone's name, derived from the ZoneId timezone
|
||||
*/
|
||||
val zoneName: String
|
||||
get() = timezone.id
|
||||
|
||||
/**
|
||||
* A link to view the calendar on the official discal website
|
||||
*/
|
||||
val link: String
|
||||
get() = "${Config.URL_BASE.getString()}/embed/${guildId.asString()}/calendar/$calendarNumber"
|
||||
|
||||
/**
|
||||
* A link to view the calendar on the host's website (e.g. google.com)
|
||||
*/
|
||||
val hostLink: String
|
||||
|
||||
//Reactive - Self
|
||||
/**
|
||||
* Attempts to delete the calendar and returns the result.
|
||||
* If an error occurs, it is emitted through the [Mono].
|
||||
*
|
||||
* @return A [Mono] boolean telling whether the deletion was successful
|
||||
*/
|
||||
fun delete(): Mono<Boolean>
|
||||
|
||||
/**
|
||||
* Attempts to update the calendar with the provided details and return the result.
|
||||
* If an error occurs, it is emitted through the [Mono].
|
||||
*
|
||||
* @param spec The details to update the calendar with
|
||||
* @return A [Mono] whereupon successful completion, returns an [UpdateCalendarResponse] with the new calendar
|
||||
*/
|
||||
fun update(spec: UpdateCalendarSpec): Mono<UpdateCalendarResponse>
|
||||
|
||||
//Reactive - Events
|
||||
/**
|
||||
* Requests to retrieve the [Event] with the ID.
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @return A [Mono] of the [Event], or [Empty][Mono.empty] if not found
|
||||
*/
|
||||
fun getEvent(eventId: String): Mono<Event>
|
||||
|
||||
/**
|
||||
* Requests to retrieve all upcoming [events][Event]
|
||||
* If an error occurs, it is emitted through the [Flux]
|
||||
*
|
||||
* @param amount The upper limit of how many events to retrieve
|
||||
* @return A [Flux] of [events][Event] that are upcoming
|
||||
*/
|
||||
fun getUpcomingEvents(amount: Int): Flux<Event>
|
||||
|
||||
/**
|
||||
* Requests to retrieve all ongoing [events][Event] (starting no more than 2 weeks ago).
|
||||
* If an error occurs, it is emitted through the [Flux]
|
||||
*
|
||||
* @return A [Flux] of [events][Event] that are currently ongoing
|
||||
*/
|
||||
fun getOngoingEvents(): Flux<Event>
|
||||
|
||||
/**
|
||||
* Requests to retrieve all [events][Event] within the supplied time span (inclusive).
|
||||
* If an error occurs, it is emitted through the [Flux]
|
||||
*
|
||||
* @return A [Flux] of [events][Event] that are happening within the supplied time range
|
||||
*/
|
||||
fun getEventsInTimeRange(start: Instant, end: Instant): Flux<Event>
|
||||
|
||||
/**
|
||||
* Requests to retrieve all [events][Event] occurring withing the next 24-hour period from the supplied [Instant]
|
||||
* (inclusive).
|
||||
* If an error occurs, it is emitted through the [Flux]
|
||||
*
|
||||
* @return A [Flux] of [events][Event] that are happening within the next 24-hour period from the start.
|
||||
*/
|
||||
fun getEventsInNext24HourPeriod(start: Instant): Flux<Event> =
|
||||
getEventsInTimeRange(start, start.plus(1, ChronoUnit.DAYS))
|
||||
|
||||
/**
|
||||
* Requests to retrieve all [events][Event] within the month starting at the supplied [Instant].
|
||||
* If an error occurs, it is emitted through the [Flux]
|
||||
*
|
||||
* @return A [Flux] of [events][Event] that are happening in the supplied 1-month period.
|
||||
*/
|
||||
fun getEventsInMonth(start: Instant, daysInMonth: Int): Flux<Event> =
|
||||
getEventsInTimeRange(start, start.plus(daysInMonth.toLong(), ChronoUnit.DAYS))
|
||||
|
||||
fun getEventsInNextNDays(days: Int): Flux<Event> =
|
||||
getEventsInTimeRange(Instant.now(), Instant.now().plus(days.toLong(), ChronoUnit.DAYS))
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Requests to create an event with the supplied information.
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param spec The information to input into the new [Event]
|
||||
* @return A [Mono] containing the newly created [Event]
|
||||
*/
|
||||
fun createEvent(spec: CreateEventSpec): Mono<Event>
|
||||
|
||||
//Convenience
|
||||
|
||||
/**
|
||||
* Converts this entity into a [WebCalendar] object.
|
||||
*
|
||||
* @return A [WebCalendar] containing the information from this entity
|
||||
*/
|
||||
fun toWebCalendar(): WebCalendar {
|
||||
return WebCalendar(
|
||||
this.calendarId,
|
||||
this.calendarAddress,
|
||||
this.calendarNumber,
|
||||
this.calendarData.host,
|
||||
this.link,
|
||||
this.hostLink,
|
||||
this.name,
|
||||
this.description,
|
||||
this.timezone.id.replace("/", "___"),
|
||||
this.external
|
||||
)
|
||||
}
|
||||
|
||||
fun toJson(): JSONObject {
|
||||
return JSONObject()
|
||||
.put("guild_id", guildId.asString())
|
||||
.put("calendar_id", calendarId)
|
||||
.put("calendar_address", calendarAddress)
|
||||
.put("calendar_number", calendarNumber)
|
||||
.put("host", calendarData.host.name)
|
||||
.put("host_link", hostLink)
|
||||
.put("external", external)
|
||||
.put("name", name)
|
||||
.put("description", description)
|
||||
.put("timezone", timezone)
|
||||
.put("link", link)
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Requests to retrieve the [Calendar] from the provided [CalendarData]
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param data The data object for the Calendar to be built with
|
||||
* @return A [Mono] containing the [Calendar], if it does not exist, [empty][Mono.empty] is returned.
|
||||
*/
|
||||
fun from(data: CalendarData): Mono<Calendar> {
|
||||
when (data.host) {
|
||||
CalendarHost.GOOGLE -> {
|
||||
return GoogleCalendar.get(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import kotlinx.serialization.encodeToString
|
||||
import org.dreamexposure.discal.core.entities.response.UpdateEventResponse
|
||||
import org.dreamexposure.discal.core.entities.spec.update.UpdateEventSpec
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.`object`.event.EventData
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.JSON_FORMAT
|
||||
import org.json.JSONObject
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.temporal.ChronoUnit
|
||||
|
||||
@Deprecated("Prefer to use new.Event via the new CalendarService")
|
||||
interface Event {
|
||||
/**
|
||||
* The ID of the event.
|
||||
* In the format eXXXXXXXXXX if generated by DisCal, otherwise it was generated by the 3rd party calendar service
|
||||
*/
|
||||
val eventId: String
|
||||
|
||||
/**
|
||||
* The ID of the [Guild] this event belongs to.
|
||||
*/
|
||||
val guildId: Snowflake
|
||||
get() = calendar.guildId
|
||||
|
||||
/**
|
||||
* The [Calendar] this Event belongs to
|
||||
*/
|
||||
val calendar: Calendar
|
||||
|
||||
/**
|
||||
* The of the event saved to the DisCal database
|
||||
*/
|
||||
val eventData: EventData
|
||||
|
||||
/**
|
||||
* The name of the event, renamed from "summary" to make it more user-friendly and clear.
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* A description of what the event is about.
|
||||
*/
|
||||
val description: String
|
||||
|
||||
/**
|
||||
* The location at which the event occurs, usually a map location.
|
||||
*/
|
||||
val location: String
|
||||
|
||||
/**
|
||||
* The link to view the event at
|
||||
*/
|
||||
val link: String
|
||||
|
||||
/**
|
||||
* The color of the event. Used for visually identifying it in Discord embeds.
|
||||
* If no event color is assigned, it returns [EventColor.NONE] which is DisCal blue.
|
||||
*/
|
||||
val color: EventColor
|
||||
|
||||
/**
|
||||
* The start of the event, as an [Instant] representing the time starting from January 1st 1970.
|
||||
*/
|
||||
val start: Instant
|
||||
|
||||
/**
|
||||
* The end of the event, as an [Instant] representing the time starting from January 1st 1970.
|
||||
*/
|
||||
val end: Instant
|
||||
|
||||
/**
|
||||
* Whether the event is a recurring event.
|
||||
*/
|
||||
val recur: Boolean
|
||||
|
||||
/**
|
||||
* The rules of the recurring event. Contains the RRule an human-readable information on how the event will recur
|
||||
*/
|
||||
val recurrence: Recurrence
|
||||
|
||||
/**
|
||||
* A link to the image, if none is present, returns empty
|
||||
*/
|
||||
val image: String
|
||||
get() = eventData.imageLink
|
||||
|
||||
/**
|
||||
* The timezone that the event takes place in. This is always the same as the [Calendar]'s timezone
|
||||
*/
|
||||
val timezone: ZoneId
|
||||
get() = calendar.timezone
|
||||
|
||||
//Reactive
|
||||
|
||||
|
||||
/**
|
||||
* Attempts to update the event and returns the result.
|
||||
* If an error occurs, it is emitted through the [Mono].
|
||||
*
|
||||
* @param spec The information to update the event with
|
||||
* @return A [Mono] that contains the [UpdateEventResponse] containing information on success and the changes.
|
||||
*/
|
||||
fun update(spec: UpdateEventSpec): Mono<UpdateEventResponse>
|
||||
|
||||
/**
|
||||
* Attempts to delete the event and returns the result.
|
||||
* If an error occurs, it is emitted through the [Mono].
|
||||
*
|
||||
* @return A [Mono] containing whether delete succeeded.
|
||||
*/
|
||||
fun delete(): Mono<Boolean>
|
||||
|
||||
fun isOngoing(): Boolean = start.isBefore(Instant.now()) && end.isAfter(Instant.now())
|
||||
|
||||
fun isOver(): Boolean = end.isBefore(Instant.now())
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the event spans across multiple calendar days (ex Monday 8pm to Tuesday 3am)
|
||||
*
|
||||
* @return Whether the event is multi-day
|
||||
*/
|
||||
fun isMultiDay(): Boolean {
|
||||
if (isAllDay()) return false // All day events should not count as multi-day events
|
||||
|
||||
val start = this.start.atZone(timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
val end = this.end.atZone(timezone).truncatedTo(ChronoUnit.DAYS)
|
||||
|
||||
return when {
|
||||
start.year != end.year -> true
|
||||
start.month != end.month -> true
|
||||
start.dayOfYear != end.dayOfYear -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
//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)
|
||||
}
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.google
|
||||
|
||||
import com.google.api.client.util.DateTime
|
||||
import com.google.api.services.calendar.model.AclRule
|
||||
import com.google.api.services.calendar.model.EventDateTime
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.`object`.event.EventData
|
||||
import org.dreamexposure.discal.core.cache.DiscalCache
|
||||
import org.dreamexposure.discal.core.crypto.KeyGenerator
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.entities.response.UpdateCalendarResponse
|
||||
import org.dreamexposure.discal.core.entities.spec.create.CreateEventSpec
|
||||
import org.dreamexposure.discal.core.entities.spec.update.UpdateCalendarSpec
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.extensions.google.asInstant
|
||||
import org.dreamexposure.discal.core.wrapper.google.AclRuleWrapper
|
||||
import org.dreamexposure.discal.core.wrapper.google.CalendarWrapper
|
||||
import org.dreamexposure.discal.core.wrapper.google.EventWrapper
|
||||
import reactor.core.publisher.Flux
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import com.google.api.services.calendar.model.Calendar as GoogleCalendarModel
|
||||
import com.google.api.services.calendar.model.Event as GoogleEventModel
|
||||
|
||||
class GoogleCalendar internal constructor(
|
||||
override val calendarData: CalendarData,
|
||||
private val baseCalendar: GoogleCalendarModel
|
||||
) : Calendar {
|
||||
|
||||
override val name: String
|
||||
get() = baseCalendar.summary.orEmpty()
|
||||
|
||||
override val description: String
|
||||
get() = baseCalendar.description.orEmpty()
|
||||
|
||||
override val timezone: ZoneId
|
||||
get() = ZoneId.of(baseCalendar.timeZone)
|
||||
|
||||
override val hostLink: String
|
||||
get() = "https://calendar.google.com/calendar/embed?src=$calendarId"
|
||||
|
||||
override fun delete(): Mono<Boolean> {
|
||||
//Delete self from cache
|
||||
DiscalCache.handleCalendarDelete(guildId)
|
||||
|
||||
return CalendarWrapper.deleteCalendar(calendarData)
|
||||
.then(DatabaseManager.deleteCalendarAndRelatedData(calendarData))
|
||||
.thenReturn(true)
|
||||
}
|
||||
|
||||
override fun update(spec: UpdateCalendarSpec): Mono<UpdateCalendarResponse> {
|
||||
val content = GoogleCalendarModel()
|
||||
content.id = this.calendarId
|
||||
|
||||
spec.name?.let { content.summary = it }
|
||||
spec.description?.let { content.description = it }
|
||||
spec.timezone?.let { content.timeZone = it.id }
|
||||
|
||||
return CalendarWrapper.patchCalendar(content, this.calendarData)
|
||||
.timeout(Duration.ofSeconds(30))
|
||||
.flatMap { confirmed ->
|
||||
val rule = AclRule()
|
||||
.setScope(AclRule.Scope().setType("default"))
|
||||
.setRole("reader")
|
||||
|
||||
val new = GoogleCalendar(this.calendarData, confirmed)
|
||||
//Update cache
|
||||
DiscalCache.putCalendar(new)
|
||||
|
||||
return@flatMap AclRuleWrapper.insertRule(rule, this.calendarData)
|
||||
.thenReturn(UpdateCalendarResponse(
|
||||
old = this,
|
||||
new = new,
|
||||
success = true)
|
||||
)
|
||||
}.defaultIfEmpty(UpdateCalendarResponse(old = this, success = false))
|
||||
}
|
||||
|
||||
override fun getEvent(eventId: String): Mono<Event> {
|
||||
return GoogleEvent.get(this, eventId)
|
||||
}
|
||||
|
||||
override fun getUpcomingEvents(amount: Int): Flux<Event> {
|
||||
return EventWrapper.getEvents(calendarData, amount, System.currentTimeMillis())
|
||||
.flatMapMany(this::loadEvents)
|
||||
}
|
||||
|
||||
override fun getOngoingEvents(): Flux<Event> {
|
||||
val start = System.currentTimeMillis() - Duration.ofDays(14).toMillis() // 2 weeks ago
|
||||
val end = System.currentTimeMillis() + Duration.ofDays(1).toMillis() // One day from now
|
||||
|
||||
return EventWrapper.getEvents(calendarData, start, end)
|
||||
.flatMapMany { Flux.fromIterable(it) }
|
||||
.filter { it.start.asInstant(timezone).isBefore(Instant.now()) }
|
||||
.filter { it.end.asInstant(timezone).isAfter(Instant.now()) }
|
||||
.collectList()
|
||||
.flatMapMany(this::loadEvents)
|
||||
}
|
||||
|
||||
override fun getEventsInTimeRange(start: Instant, end: Instant): Flux<Event> {
|
||||
return EventWrapper.getEvents(calendarData, start.toEpochMilli(), end.toEpochMilli())
|
||||
.flatMapMany(this::loadEvents)
|
||||
}
|
||||
|
||||
override fun createEvent(spec: CreateEventSpec): Mono<Event> {
|
||||
val event = GoogleEventModel()
|
||||
event.id = KeyGenerator.generateEventId()
|
||||
event.visibility = "public"
|
||||
|
||||
spec.name?.let { event.summary = it }
|
||||
spec.description?.let { event.description = it }
|
||||
spec.location?.let { event.location = it }
|
||||
|
||||
|
||||
event.start = EventDateTime()
|
||||
.setDateTime(DateTime(spec.start.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
event.end = EventDateTime()
|
||||
.setDateTime(DateTime(spec.end.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
|
||||
if (spec.color != EventColor.NONE)
|
||||
event.colorId = spec.color.id.toString()
|
||||
|
||||
if (spec.recur)
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.toRRule()) }
|
||||
|
||||
//Okay, all values are set, lets create the event now...
|
||||
return EventWrapper.createEvent(this.calendarData, event).flatMap { confirmed ->
|
||||
val data = EventData(
|
||||
this.guildId,
|
||||
confirmed.id,
|
||||
calendarNumber,
|
||||
spec.end.toEpochMilli(),
|
||||
spec.image.orEmpty()
|
||||
)
|
||||
|
||||
return@flatMap DatabaseManager.updateEventData(data)
|
||||
.thenReturn(GoogleEvent(this, data, confirmed))
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadEvents(events: List<GoogleEventModel>): Flux<GoogleEvent> {
|
||||
return DatabaseManager.getEventsData(guildId, events.map { it.id }).flatMapMany { data ->
|
||||
Flux.fromIterable(events).concatMap {
|
||||
val id = if (it.id.contains("_")) it.id.split("_")[0] else it.id
|
||||
|
||||
if (data.containsKey(id)) Mono.just(GoogleEvent(this, data[id]!!, it))
|
||||
else Mono.just(GoogleEvent(this, EventData(guildId, eventId = id), it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
/**
|
||||
* Requests to retrieve the [Calendar] from the provided [CalendarData]
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param calData The data object for the Calendar to be built with
|
||||
* @return A [Mono] containing the [Calendar], if it does not exist, [empty][Mono.empty] is returned.
|
||||
*/
|
||||
fun get(calData: CalendarData): Mono<Calendar> {
|
||||
return CalendarWrapper.getCalendar(calData)
|
||||
.map { GoogleCalendar(calData, it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,184 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.google
|
||||
|
||||
import com.google.api.client.util.DateTime
|
||||
import com.google.api.services.calendar.model.EventDateTime
|
||||
import org.dreamexposure.discal.core.`object`.event.EventData
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.entities.response.UpdateEventResponse
|
||||
import org.dreamexposure.discal.core.entities.spec.update.UpdateEventSpec
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.wrapper.google.EventWrapper
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import com.google.api.services.calendar.model.Event as GoogleEventModel
|
||||
|
||||
class GoogleEvent internal constructor(
|
||||
override val calendar: Calendar,
|
||||
override val eventData: EventData,
|
||||
private val baseEvent: GoogleEventModel,
|
||||
) : Event {
|
||||
override val eventId: String
|
||||
get() = baseEvent.id
|
||||
|
||||
override val name: String
|
||||
get() = baseEvent.summary.orEmpty()
|
||||
|
||||
override val description: String
|
||||
get() = baseEvent.description.orEmpty()
|
||||
|
||||
override val location: String
|
||||
get() = baseEvent.location.orEmpty()
|
||||
|
||||
override val link: String
|
||||
get() = baseEvent.htmlLink.orEmpty()
|
||||
|
||||
override val color: EventColor
|
||||
get() {
|
||||
return if (baseEvent.colorId != null && baseEvent.colorId.isNotEmpty())
|
||||
EventColor.fromNameOrHexOrId(baseEvent.colorId)
|
||||
else
|
||||
EventColor.NONE
|
||||
}
|
||||
|
||||
override val start: Instant
|
||||
get() {
|
||||
return if (baseEvent.start.dateTime != null) {
|
||||
Instant.ofEpochMilli(baseEvent.start.dateTime.value)
|
||||
} else {
|
||||
Instant.ofEpochMilli(baseEvent.start.date.value)
|
||||
.plus(1, ChronoUnit.DAYS)
|
||||
.atZone(timezone)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
.atZone(timezone)
|
||||
.toInstant()
|
||||
}
|
||||
}
|
||||
|
||||
override val end: Instant
|
||||
get() {
|
||||
return if (baseEvent.end.dateTime != null) {
|
||||
Instant.ofEpochMilli(baseEvent.end.dateTime.value)
|
||||
} else {
|
||||
Instant.ofEpochMilli(baseEvent.end.date.value)
|
||||
.plus(1, ChronoUnit.DAYS)
|
||||
.atZone(timezone)
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.toLocalDate()
|
||||
.atStartOfDay()
|
||||
.atZone(timezone)
|
||||
.toInstant()
|
||||
}
|
||||
}
|
||||
|
||||
override val recur: Boolean
|
||||
get() = baseEvent.recurrence != null && baseEvent.recurrence.isNotEmpty()
|
||||
|
||||
override val recurrence: Recurrence
|
||||
get() {
|
||||
return if (recur)
|
||||
Recurrence.fromRRule(baseEvent.recurrence[0])
|
||||
else
|
||||
Recurrence()
|
||||
}
|
||||
|
||||
override fun update(spec: UpdateEventSpec): Mono<UpdateEventResponse> {
|
||||
val event = GoogleEventModel()
|
||||
event.id = this.eventId
|
||||
|
||||
spec.name?.let { event.summary = it }
|
||||
spec.description?.let { event.description = it }
|
||||
spec.location?.let { event.location = it }
|
||||
|
||||
//Always update start/end so that we can safely handle all day events without DateTime by overwriting it
|
||||
if (spec.start != null) {
|
||||
event.start = EventDateTime()
|
||||
.setDateTime(DateTime(spec.start.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
} else {
|
||||
event.start = EventDateTime()
|
||||
.setDateTime(DateTime(this.start.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
}
|
||||
if (spec.end != null) {
|
||||
event.end = EventDateTime()
|
||||
.setDateTime(DateTime(spec.end.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
} else {
|
||||
event.end = EventDateTime()
|
||||
.setDateTime(DateTime(this.end.toEpochMilli()))
|
||||
.setTimeZone(this.timezone.id)
|
||||
}
|
||||
|
||||
spec.color?.let {
|
||||
if (it == EventColor.NONE)
|
||||
event.colorId = null
|
||||
else
|
||||
event.colorId = it.id.toString()
|
||||
}
|
||||
|
||||
//Special recurrence handling
|
||||
if (spec.recur != null) {
|
||||
if (spec.recur) {
|
||||
//event now recurs, add the RRUle.
|
||||
spec.recurrence?.let { event.recurrence = listOf(it.toRRule()) }
|
||||
}
|
||||
} 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()) }
|
||||
}
|
||||
|
||||
//Okay, all values are set, lets patch this event now...
|
||||
return EventWrapper.patchEvent(this.calendar.calendarData, event).flatMap { confirmed ->
|
||||
val data = EventData(
|
||||
this.guildId,
|
||||
confirmed.id,
|
||||
calendar.calendarNumber,
|
||||
confirmed.end.dateTime.value,
|
||||
spec.image ?: this.image
|
||||
)
|
||||
|
||||
return@flatMap DatabaseManager.updateEventData(data)
|
||||
.thenReturn(UpdateEventResponse(true, old = this, GoogleEvent(this.calendar, data, confirmed)))
|
||||
}.defaultIfEmpty(UpdateEventResponse(false, old = this))
|
||||
}
|
||||
|
||||
override fun delete(): Mono<Boolean> {
|
||||
return EventWrapper.deleteEvent(calendar.calendarData, eventId)
|
||||
.flatMap { success ->
|
||||
if (success) {
|
||||
Mono.`when`(
|
||||
DatabaseManager.deleteAnnouncementsForEvent(guildId, eventId),
|
||||
DatabaseManager.deleteEventData(eventId),
|
||||
).thenReturn(true)
|
||||
} else {
|
||||
Mono.just(false)
|
||||
}
|
||||
}.defaultIfEmpty(false)
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
/**
|
||||
* Requests to retrieve the event with the provided ID from the provided [Calendar].
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param calendar The [Calendar] this event exists on.
|
||||
* @param id The ID of the event
|
||||
* @return A [Mono] containing the event, if it does not exist, the mono is empty.
|
||||
*/
|
||||
fun get(calendar: Calendar, id: String): Mono<Event> {
|
||||
return EventWrapper.getEvent(calendar.calendarData, id)
|
||||
.flatMap { event ->
|
||||
DatabaseManager.getEventData(calendar.guildId, id)
|
||||
.map {
|
||||
GoogleEvent(calendar, it, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.response
|
||||
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
|
||||
data class UpdateCalendarResponse(
|
||||
val success: Boolean,
|
||||
val old: Calendar? = null,
|
||||
val new: Calendar? = null,
|
||||
)
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.response
|
||||
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
|
||||
data class UpdateEventResponse(
|
||||
val success: Boolean,
|
||||
val old: Event? = null,
|
||||
val new: Event? = null,
|
||||
)
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.spec.create
|
||||
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import java.time.Instant
|
||||
|
||||
data class CreateEventSpec(
|
||||
val name: String? = null,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val start: Instant,
|
||||
|
||||
val end: Instant,
|
||||
|
||||
val color: EventColor = EventColor.NONE,
|
||||
|
||||
val location: String? = null,
|
||||
|
||||
val image: String? = null,
|
||||
|
||||
val recur: Boolean = false,
|
||||
|
||||
val recurrence: Recurrence? = null,
|
||||
)
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.spec.update
|
||||
|
||||
import java.time.ZoneId
|
||||
|
||||
data class UpdateCalendarSpec(
|
||||
val name: String? = null,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val timezone: ZoneId? = null,
|
||||
)
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
package org.dreamexposure.discal.core.entities.spec.update
|
||||
|
||||
import org.dreamexposure.discal.core.`object`.event.Recurrence
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import java.time.Instant
|
||||
|
||||
data class UpdateEventSpec(
|
||||
val name: String? = null,
|
||||
|
||||
val description: String? = null,
|
||||
|
||||
val start: Instant? = null,
|
||||
|
||||
val end: Instant? = null,
|
||||
|
||||
val color: EventColor? = null,
|
||||
|
||||
val location: String? = null,
|
||||
|
||||
val image: String? = null,
|
||||
|
||||
val recur: Boolean? = null,
|
||||
|
||||
val recurrence: Recurrence? = null
|
||||
)
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
package org.dreamexposure.discal.core.exceptions.google
|
||||
|
||||
class GoogleAuthCancelException : Exception()
|
||||
@@ -19,10 +19,6 @@ fun List<String>.asStringList(): String {
|
||||
return builder.toString()
|
||||
}
|
||||
|
||||
fun MutableList<String>.setFromString(strList: String) {
|
||||
this += strList.split(",").filter(String::isNotBlank)
|
||||
}
|
||||
|
||||
fun List<Event>.groupByDate(): Map<ZonedDateTime, List<Event>> {
|
||||
if (this.isEmpty()) return emptyMap()
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.dreamexposure.discal.core.extensions
|
||||
|
||||
import org.dreamexposure.discal.core.enums.time.BadTimezone
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import org.dreamexposure.discal.core.utils.ImageValidator
|
||||
import org.jsoup.Jsoup
|
||||
import java.time.ZoneId
|
||||
import java.util.*
|
||||
@@ -21,14 +20,6 @@ fun String.toMarkdown(): String = GlobalVal.MARKDOWN_CONVERTER.convert(this.sani
|
||||
|
||||
fun String.unescapeNewLines(): String = this.replace(Regex("([\\\\\\n]+)(n)"), "\n").replace("=0D=0A", "\n")
|
||||
|
||||
fun String.isValidTimezone(): Boolean {
|
||||
return try {
|
||||
ZoneId.getAvailableZoneIds().contains(this) && !BadTimezone.isBad(this)
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toZoneId(): ZoneId? {
|
||||
return try {
|
||||
if (!BadTimezone.isBad(this)) ZoneId.of(this) else null
|
||||
@@ -37,8 +28,6 @@ fun String.toZoneId(): ZoneId? {
|
||||
}
|
||||
}
|
||||
|
||||
fun String.isValidImage(allowGif: Boolean) = ImageValidator.validate(this, allowGif)
|
||||
|
||||
fun String.padCenter(length: Int, padChar: Char = ' '): String {
|
||||
if (this.length >= length) return this
|
||||
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package org.dreamexposure.discal.core.extensions.discord4j
|
||||
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.rest.entity.RestGuild
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
//Calendars
|
||||
|
||||
/**
|
||||
* Attempts to retrieve this [Guild]'s [Calendar] with the supplied index.
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param calNumber The number of the [Calendar]. one-indexed
|
||||
* @return A [Mono] containing the [Calendar] with the supplied index, if it does not exist, [empty][Mono.empty] is
|
||||
* returned.
|
||||
*/
|
||||
@Deprecated("Prefer to use new CalendarService")
|
||||
fun Guild.getCalendar(calNumber: Int): Mono<Calendar> = getRestGuild().getCalendar(calNumber)
|
||||
|
||||
fun Guild.getRestGuild(): RestGuild {
|
||||
return client.rest().restGuild(data)
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
package org.dreamexposure.discal.core.extensions.discord4j
|
||||
|
||||
import discord4j.core.event.domain.interaction.DeferrableInteractionEvent
|
||||
import discord4j.core.`object`.entity.Message
|
||||
import discord4j.core.spec.EmbedCreateSpec
|
||||
import discord4j.core.spec.InteractionFollowupCreateSpec
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followup(embed: EmbedCreateSpec): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.addEmbed(embed)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followup(message: String): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.content(message)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followup(message: String, embed: EmbedCreateSpec): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.content(message)
|
||||
.addEmbed(embed)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followupEphemeral(embed: EmbedCreateSpec): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.addEmbed(embed)
|
||||
.ephemeral(true)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followupEphemeral(message: String): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.content(message)
|
||||
.ephemeral(true)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
|
||||
@Deprecated("lol")
|
||||
fun DeferrableInteractionEvent.followupEphemeral(message: String, embed: EmbedCreateSpec): Mono<Message> {
|
||||
val spec = InteractionFollowupCreateSpec.builder()
|
||||
.content(message)
|
||||
.addEmbed(embed)
|
||||
.ephemeral(true)
|
||||
.build()
|
||||
|
||||
return this.createFollowup(spec)
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package org.dreamexposure.discal.core.extensions.discord4j
|
||||
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import discord4j.rest.entity.RestGuild
|
||||
import org.dreamexposure.discal.core.cache.DiscalCache
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
//Calendars
|
||||
|
||||
/**
|
||||
* Attempts to retrieve this [Guild]'s main [Calendar] (calendar 1, this guild's first/primary calendar)
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @return A [Mono] containing this [Guild]'s main [Calendar], if it does not exist, [empty][Mono.empty] is returned.
|
||||
*/
|
||||
@Deprecated("Prefer to use new CalendarService")
|
||||
fun RestGuild.getMainCalendar(): Mono<Calendar> = this.getCalendar(1)
|
||||
|
||||
/**
|
||||
* Attempts to retrieve this [Guild]'s [Calendar] with the supplied index.
|
||||
* If an error occurs, it is emitted through the [Mono]
|
||||
*
|
||||
* @param calNumber The number of the [Calendar]. one-indexed
|
||||
* @return A [Mono] containing the [Calendar] with the supplied index, if it does not exist, [empty][Mono.empty] is
|
||||
* returned.
|
||||
*/
|
||||
@Deprecated("Prefer to use new CalendarService")
|
||||
fun RestGuild.getCalendar(calNumber: Int): Mono<Calendar> {
|
||||
//Check cache first
|
||||
val cal = DiscalCache.getCalendar(id, calNumber)
|
||||
if (cal != null) return Mono.just(cal)
|
||||
|
||||
return DatabaseManager.getCalendar(this.id, calNumber)
|
||||
.flatMap(Calendar.Companion::from)
|
||||
.doOnNext(DiscalCache::putCalendar)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.dreamexposure.discal.core.`object`.new.GuildSettings
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
abstract class Pre(
|
||||
@Transient
|
||||
open val guildId: Snowflake = Snowflake.of(0),
|
||||
|
||||
@Transient
|
||||
open val editing: Boolean = false,
|
||||
) {
|
||||
@Transient
|
||||
var lastEdit: Instant = Instant.now()
|
||||
|
||||
open fun generateWarnings(settings: GuildSettings): List<String> = Collections.emptyList()
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import reactor.core.publisher.Flux
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.temporal.ChronoUnit
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@Deprecated("This was dumb, what was I doing lmao")
|
||||
class Wizard<T: Pre> {
|
||||
private val active = ConcurrentHashMap<Snowflake, T>()
|
||||
|
||||
init {
|
||||
Flux.interval(Duration.ofMinutes(30))
|
||||
.map { removeOld() }
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
fun get(id: Snowflake): T? {
|
||||
val p = active[id]
|
||||
if (p != null) p.lastEdit = Instant.now()
|
||||
return p
|
||||
}
|
||||
|
||||
fun start(pre: T): T? = active.put(pre.guildId, pre)
|
||||
|
||||
fun remove(id: Snowflake): T? = active.remove(id)
|
||||
|
||||
private fun removeOld() {
|
||||
val toRemove = mutableListOf<Snowflake>()
|
||||
|
||||
active.forEach {
|
||||
if (Instant.now().isAfter(it.value.lastEdit.plus(30, ChronoUnit.MINUTES))) {
|
||||
toRemove.add(it.key)
|
||||
}
|
||||
}
|
||||
toRemove.forEach { active.remove(it) }
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.calendar
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import org.dreamexposure.discal.core.crypto.KeyGenerator
|
||||
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
|
||||
import org.dreamexposure.discal.core.serializers.SnowflakeAsStringSerializer
|
||||
import java.time.Instant
|
||||
|
||||
@Serializable
|
||||
@Deprecated("Use new CalendarMetadata implementation instead")
|
||||
data class CalendarData(
|
||||
@Serializable(with = SnowflakeAsStringSerializer::class)
|
||||
@SerialName("guild_id")
|
||||
val guildId: Snowflake = Snowflake.of(0),
|
||||
@SerialName("calendar_number")
|
||||
val calendarNumber: Int = 1,
|
||||
val host: CalendarHost,
|
||||
@SerialName("calendar_id")
|
||||
val calendarId: String = "primary",
|
||||
@SerialName("calendar_address")
|
||||
val calendarAddress: String = "primary",
|
||||
val external: Boolean = false,
|
||||
|
||||
//secure values that should not be serialized
|
||||
@Transient
|
||||
val credentialId: Int = 0,
|
||||
@Transient
|
||||
var privateKey: String = KeyGenerator.csRandomAlphaNumericString(16),
|
||||
@Transient
|
||||
var encryptedAccessToken: String = "N/a",
|
||||
@Transient
|
||||
var encryptedRefreshToken: String = "N/a",
|
||||
@Transient
|
||||
var expiresAt: Instant = Instant.now()
|
||||
): Comparable<CalendarData> {
|
||||
constructor(guildId: Snowflake, calendarNumber: Int, host: CalendarHost, calendarId: String,
|
||||
calendarAddress: String, credentialId: Int) :
|
||||
this(guildId, calendarNumber, host, calendarId, calendarAddress, false, credentialId)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun empty(guildId: Snowflake, host: CalendarHost) = CalendarData(guildId, host = host)
|
||||
|
||||
fun emptyExternal(guildId: Snowflake, host: CalendarHost) = CalendarData(guildId, external = true, host = host)
|
||||
}
|
||||
|
||||
fun expired() = Instant.now().isAfter(expiresAt)
|
||||
|
||||
override fun compareTo(other: CalendarData): Int {
|
||||
if (this.calendarNumber > other.calendarNumber) return 1
|
||||
if (this.calendarNumber < other.calendarNumber) return -1
|
||||
return 0
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.event
|
||||
|
||||
import com.google.api.services.calendar.model.Event
|
||||
import discord4j.core.`object`.entity.Message
|
||||
|
||||
data class EventCreatorResponse(
|
||||
val successful: Boolean,
|
||||
val event: Event?,
|
||||
val creatorMessage: Message?,
|
||||
val edited: Boolean,
|
||||
)
|
||||
@@ -1,29 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.event
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.dreamexposure.discal.core.serializers.SnowflakeAsStringSerializer
|
||||
|
||||
@Serializable
|
||||
data class EventData(
|
||||
@Serializable(with = SnowflakeAsStringSerializer::class)
|
||||
@SerialName("guild_id")
|
||||
val guildId: Snowflake = Snowflake.of(0),
|
||||
|
||||
@SerialName("event_id")
|
||||
val eventId: String = "",
|
||||
|
||||
@SerialName("calendar_number")
|
||||
val calendarNumber: Int = 1,
|
||||
|
||||
@SerialName("event_end")
|
||||
val eventEnd: Long = 0,
|
||||
|
||||
@SerialName("image_link")
|
||||
val imageLink: String = ""
|
||||
) {
|
||||
fun shouldBeSaved(): Boolean {
|
||||
return this.imageLink.isNotEmpty()
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.event
|
||||
|
||||
import discord4j.common.util.Snowflake
|
||||
import discord4j.core.`object`.entity.Guild
|
||||
import org.dreamexposure.discal.core.entities.Calendar
|
||||
import org.dreamexposure.discal.core.entities.Event
|
||||
import org.dreamexposure.discal.core.entities.spec.create.CreateEventSpec
|
||||
import org.dreamexposure.discal.core.entities.spec.update.UpdateEventSpec
|
||||
import org.dreamexposure.discal.core.enums.event.EventColor
|
||||
import org.dreamexposure.discal.core.extensions.discord4j.getCalendar
|
||||
import org.dreamexposure.discal.core.`object`.Pre
|
||||
import org.dreamexposure.discal.core.`object`.new.GuildSettings
|
||||
import org.dreamexposure.discal.core.utils.getEmbedMessage
|
||||
import reactor.core.publisher.Mono
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
|
||||
@Suppress("DataClassPrivateConstructor")
|
||||
data class PreEvent private constructor(
|
||||
override val guildId: Snowflake,
|
||||
val eventId: String? = null,
|
||||
val calNumber: Int,
|
||||
val timezone: ZoneId,
|
||||
override val editing: Boolean = false,
|
||||
): Pre(guildId, editing) {
|
||||
var name: String? = null
|
||||
|
||||
var description: String? = null
|
||||
|
||||
var start: Instant? = null
|
||||
var end: Instant? = null
|
||||
|
||||
var color: EventColor = EventColor.NONE
|
||||
|
||||
var location: String? = null
|
||||
|
||||
var image: String? = null
|
||||
|
||||
var recurrence: Recurrence? = null
|
||||
|
||||
var event: Event? = null
|
||||
|
||||
|
||||
fun hasRequiredValues(): Boolean {
|
||||
return this.start != null
|
||||
&& this.end != null
|
||||
}
|
||||
|
||||
fun createSpec(): CreateEventSpec {
|
||||
return CreateEventSpec(
|
||||
name = name,
|
||||
description = description,
|
||||
start = start!!,
|
||||
end = end!!,
|
||||
color = color,
|
||||
location = location,
|
||||
image = image,
|
||||
recur = recurrence != null,
|
||||
recurrence = recurrence,
|
||||
)
|
||||
}
|
||||
|
||||
fun updateSpec(): UpdateEventSpec {
|
||||
return UpdateEventSpec(
|
||||
name = name,
|
||||
description = description,
|
||||
start = start,
|
||||
end = end,
|
||||
color = color,
|
||||
location = location,
|
||||
image = image,
|
||||
recur = recurrence != null,
|
||||
recurrence = recurrence,
|
||||
)
|
||||
}
|
||||
|
||||
override fun generateWarnings(settings: GuildSettings): List<String> {
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
if (name.isNullOrBlank()) {
|
||||
warnings.add(getEmbedMessage("event", "warning.wizard.noName", settings.locale))
|
||||
}
|
||||
|
||||
// Checking end time is not needed
|
||||
if (start != null && start!!.isBefore(Instant.now())) {
|
||||
warnings.add(getEmbedMessage("event", "warning.wizard.past", settings.locale))
|
||||
}
|
||||
|
||||
if (this.start != null && this.end != null) {
|
||||
if (Duration.between(start!!, end!!).toDays() > 30) {
|
||||
warnings.add(getEmbedMessage("event", "warning.wizard.veryLong", settings.locale))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return warnings
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun new(calendar: Calendar): PreEvent {
|
||||
return PreEvent(
|
||||
guildId = calendar.guildId,
|
||||
calNumber = calendar.calendarNumber,
|
||||
timezone = calendar.timezone,
|
||||
editing = false,
|
||||
)
|
||||
}
|
||||
|
||||
fun edit(event: Event): PreEvent {
|
||||
val pre = PreEvent(
|
||||
guildId = event.guildId,
|
||||
eventId = event.eventId,
|
||||
calNumber = event.calendar.calendarNumber,
|
||||
timezone = event.timezone,
|
||||
editing = true
|
||||
)
|
||||
|
||||
pre.name = event.name
|
||||
pre.description = event.description
|
||||
pre.start = event.start
|
||||
pre.end = event.end
|
||||
pre.color = event.color
|
||||
pre.location = event.location
|
||||
pre.image = event.image
|
||||
if (event.recur)
|
||||
pre.recurrence = event.recurrence
|
||||
pre.event = event
|
||||
|
||||
return pre
|
||||
}
|
||||
|
||||
fun copy(guild: Guild, event: Event, targetCalNum: Int): Mono<PreEvent> {
|
||||
val calMono: Mono<Calendar> =
|
||||
if (targetCalNum != event.calendar.calendarNumber) {
|
||||
guild.getCalendar(targetCalNum).defaultIfEmpty(event.calendar)
|
||||
} else {
|
||||
Mono.just(event.calendar)
|
||||
}
|
||||
|
||||
return calMono.map { targetCal ->
|
||||
val pre = PreEvent(
|
||||
guildId = event.guildId,
|
||||
calNumber = targetCal.calendarNumber,
|
||||
timezone = targetCal.timezone,
|
||||
)
|
||||
|
||||
pre.name = event.name
|
||||
pre.description = event.description
|
||||
pre.start = event.start
|
||||
pre.end = event.end
|
||||
pre.color = event.color
|
||||
pre.location = event.location
|
||||
pre.image = event.image
|
||||
if (event.recur)
|
||||
pre.recurrence = event.recurrence
|
||||
|
||||
pre
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.google
|
||||
|
||||
import discord4j.core.`object`.entity.User
|
||||
import org.dreamexposure.discal.core.`object`.GuildSettings
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
data class ExternalGoogleAuthPoll(
|
||||
val user: User,
|
||||
val settings: GuildSettings,
|
||||
override var interval: Int,
|
||||
override val expiresIn: Int,
|
||||
override var remainingSeconds: Int,
|
||||
override val deviceCode: String,
|
||||
override val callback: ((poll: GoogleAuthPoll) -> Mono<Void>)
|
||||
): GoogleAuthPoll(interval, expiresIn, remainingSeconds, deviceCode, callback)
|
||||
@@ -1,11 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.google
|
||||
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
open class GoogleAuthPoll(
|
||||
open var interval: Int,
|
||||
open val expiresIn: Int,
|
||||
open var remainingSeconds: Int,
|
||||
open val deviceCode: String,
|
||||
open val callback: ((poll: GoogleAuthPoll) -> Mono<Void>),
|
||||
)
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.google
|
||||
|
||||
import java.time.Instant
|
||||
|
||||
data class GoogleCredentialData(
|
||||
val credentialNumber: Int,
|
||||
var encryptedRefreshToken: String,
|
||||
var encryptedAccessToken: String,
|
||||
var expiresAt: Instant,
|
||||
)
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
package org.dreamexposure.discal.core.`object`.google
|
||||
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
data class InternalGoogleAuthPoll(
|
||||
val credNumber: Int,
|
||||
override var interval: Int,
|
||||
override val expiresIn: Int,
|
||||
override var remainingSeconds: Int,
|
||||
override val deviceCode: String,
|
||||
override val callback: ((poll: GoogleAuthPoll) -> Mono<Void>)
|
||||
): GoogleAuthPoll(interval, expiresIn, remainingSeconds, deviceCode, callback)
|
||||
@@ -1,13 +1,8 @@
|
||||
package org.dreamexposure.discal.core.`object`.rest
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import reactor.core.publisher.Mono
|
||||
|
||||
@Serializable
|
||||
data class GenericResponse(
|
||||
val message: String,
|
||||
|
||||
) {
|
||||
|
||||
fun asMono(): Mono<GenericResponse> = Mono.just(this)
|
||||
}
|
||||
)
|
||||
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package org.dreamexposure.discal.core.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.util.*
|
||||
|
||||
object UUIDasStringSerializer : KSerializer<UUID> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString())
|
||||
|
||||
override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString())
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package org.dreamexposure.discal.core.serializers
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.time.ZoneId
|
||||
|
||||
object ZoneIdAsStringSerializer : KSerializer<ZoneId> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("ZoneId", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: ZoneId) = encoder.encodeString(value.id)
|
||||
|
||||
override fun deserialize(decoder: Decoder): ZoneId = ZoneId.of(decoder.decodeString())
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import io.github.furstenheim.CopyDown
|
||||
import io.github.furstenheim.OptionsBuilder
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.safety.Safelist
|
||||
import org.slf4j.Marker
|
||||
import org.slf4j.MarkerFactory
|
||||
@@ -32,9 +31,6 @@ object GlobalVal {
|
||||
|
||||
val JSON = "application/json; charset=utf-8".toMediaType()
|
||||
|
||||
@Deprecated("Use DI instead")
|
||||
val HTTP_CLIENT: OkHttpClient = OkHttpClient()
|
||||
|
||||
val JSON_FORMAT = Json {
|
||||
encodeDefaults = true
|
||||
ignoreUnknownKeys = true
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
package org.dreamexposure.discal.core.wrapper.google
|
||||
|
||||
import com.google.api.services.calendar.model.AclRule
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
|
||||
@Deprecated("Use the new GoogleCalendarApiWrapper component")
|
||||
object AclRuleWrapper {
|
||||
fun insertRule(rule: AclRule, calData: CalendarData): Mono<AclRule> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.acl()
|
||||
.insert(calData.calendarId, rule)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] ACLRule insert failure", it)
|
||||
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package org.dreamexposure.discal.core.wrapper.google
|
||||
|
||||
import com.google.api.services.calendar.model.Calendar
|
||||
import com.google.api.services.calendar.model.CalendarListEntry
|
||||
import discord4j.common.util.Snowflake
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
|
||||
@Deprecated("User new GoogleCalendarApiWrapper component")
|
||||
object CalendarWrapper {
|
||||
fun createCalendar(calendar: Calendar, credId: Int, guildId: Snowflake): Mono<Calendar> {
|
||||
return GoogleAuthWrapper.getCalendarService(credId).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendars()
|
||||
.insert(calendar)
|
||||
.setQuotaUser(guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Calendar create failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun patchCalendar(calendar: Calendar, calData: CalendarData): Mono<Calendar> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendars()
|
||||
.patch(calendar.id, calendar)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Calendar patch failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun updateCalendar(calendar: Calendar, calData: CalendarData): Mono<Calendar> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendars()
|
||||
.update(calendar.id, calendar)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Calendar update failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun getCalendar(calData: CalendarData): Mono<Calendar> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendars()
|
||||
.get(calData.calendarAddress)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Calendar get failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun deleteCalendar(calData: CalendarData): Mono<Boolean> {
|
||||
return Mono.just(calData)
|
||||
.filter { !it.external }
|
||||
.filter { !it.calendarAddress.equals("primary", true) }
|
||||
.flatMap { GoogleAuthWrapper.getCalendarService(calData) }
|
||||
.flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendars()
|
||||
.delete(calData.calendarAddress)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.thenReturn(true)
|
||||
.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Calendar delete failure", it)
|
||||
}.onErrorReturn(false)
|
||||
}
|
||||
|
||||
fun getUsersExternalCalendars(calData: CalendarData): Mono<List<CalendarListEntry>> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.calendarList()
|
||||
.list()
|
||||
.setMinAccessRole("writer")
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
.items
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] External calendar list failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
}
|
||||
@@ -1,181 +0,0 @@
|
||||
package org.dreamexposure.discal.core.wrapper.google
|
||||
|
||||
import com.google.api.client.googleapis.json.GoogleJsonResponseException
|
||||
import com.google.api.client.util.DateTime
|
||||
import com.google.api.services.calendar.Calendar
|
||||
import com.google.api.services.calendar.model.Event
|
||||
import org.apache.http.HttpStatus
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import java.util.function.Predicate
|
||||
|
||||
@Deprecated("Use new GoogleCalendarApiWrapper component")
|
||||
object EventWrapper {
|
||||
fun createEvent(calData: CalendarData, event: Event): Mono<Event> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.insert(calData.calendarId, event)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event create failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun patchEvent(calData: CalendarData, event: Event): Mono<Event> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.patch(calData.calendarId, event.id, event)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event patch failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun updateEvent(calData: CalendarData, event: Event): Mono<Event> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.update(calData.calendarId, event.id, event)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event update failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun getEvent(calData: CalendarData, id: String): Mono<Event> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.get(calData.calendarId, id)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
}.filterWhen {
|
||||
/*
|
||||
Don't show "deleted" events
|
||||
|
||||
See "status" flag: https://developers.google.com/calendar/api/v3/reference/events#resource
|
||||
*/
|
||||
if (it.status.equals("cancelled", true)) {
|
||||
// Delete any announcements tied to it.
|
||||
DatabaseManager.deleteAnnouncementsForEvent(calData.guildId, id).thenReturn(false)
|
||||
} else Mono.just(true)
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.onErrorResume(GoogleJsonResponseException::class.java) {
|
||||
return@onErrorResume when (it.statusCode) {
|
||||
HttpStatus.SC_GONE -> {
|
||||
// The event is gone. Sometimes google will return this if the event is deleted.
|
||||
DatabaseManager.deleteAnnouncementsForEvent(calData.guildId, id).then(Mono.empty())
|
||||
}
|
||||
HttpStatus.SC_NOT_FOUND -> {
|
||||
// Event not found. Was this ever an event?
|
||||
DatabaseManager.deleteAnnouncementsForEvent(calData.guildId, id).then(Mono.empty())
|
||||
}
|
||||
else -> {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event get failure", it)
|
||||
Mono.empty()
|
||||
}
|
||||
}
|
||||
}.onErrorResume { Mono.empty() } //Can safely ignore this, the event just doesn't exist.
|
||||
}
|
||||
|
||||
fun getEvents(calData: CalendarData, amount: Int, start: Long): Mono<List<Event>> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.list(calData.calendarId)
|
||||
.setMaxResults(amount)
|
||||
.setTimeMin(DateTime(start))
|
||||
.setOrderBy("startTime")
|
||||
.setSingleEvents(true)
|
||||
.setShowDeleted(false)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute()
|
||||
.items
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event list(1) failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun getEvents(calData: CalendarData, amount: Int, start: Long, end: Long): Mono<List<Event>> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service: Calendar ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.list(calData.calendarId)
|
||||
.setMaxResults(amount)
|
||||
.setTimeMin(DateTime(start))
|
||||
.setTimeMax(DateTime(end))
|
||||
.setOrderBy("startTime")
|
||||
.setSingleEvents(true)
|
||||
.setShowDeleted(false)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute().items
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event list(3) failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun getEvents(calData: CalendarData, start: Long, end: Long): Mono<List<Event>> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service: Calendar ->
|
||||
Mono.fromCallable {
|
||||
service.events()
|
||||
.list(calData.calendarId)
|
||||
.setTimeMin(DateTime(start))
|
||||
.setTimeMax(DateTime(end))
|
||||
.setOrderBy("startTime")
|
||||
.setSingleEvents(true)
|
||||
.setShowDeleted(false)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.execute().items
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event list(4) failure", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
fun deleteEvent(calData: CalendarData, id: String?): Mono<Boolean> {
|
||||
return GoogleAuthWrapper.getCalendarService(calData).flatMap { service: Calendar ->
|
||||
Mono.fromCallable {
|
||||
val response = service.events()
|
||||
.delete(calData.calendarAddress, id)
|
||||
.setQuotaUser(calData.guildId.asString())
|
||||
.executeUnparsed()
|
||||
|
||||
//Google sends 4 possible status codes, 200, 204, 404, 410.
|
||||
// First 2 should be treated as successful, and the other 2 as not found.
|
||||
when (response.statusCode) {
|
||||
200, 204 -> {
|
||||
return@fromCallable true
|
||||
}
|
||||
404, 410 -> {
|
||||
return@fromCallable false
|
||||
}
|
||||
else -> {
|
||||
//Log response data and return false as google sent an unexpected response code.
|
||||
LOGGER.debug(GlobalVal.DEFAULT, "Event delete error | ${response.statusCode} | ${response.statusMessage}")
|
||||
return@fromCallable false
|
||||
}
|
||||
}
|
||||
}.subscribeOn(Schedulers.boundedElastic())
|
||||
}.doOnError(GoogleJsonResponseException::class.java) {
|
||||
if (it.statusCode != 410 || it.statusCode != 404) {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event delete failure", it)
|
||||
}
|
||||
}.doOnError(Predicate.not(GoogleJsonResponseException::class.java::isInstance)) {
|
||||
LOGGER.error(GlobalVal.DEFAULT, "[G.Cal] Event delete failure", it)
|
||||
}.onErrorReturn(false)
|
||||
}
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
package org.dreamexposure.discal.core.wrapper.google
|
||||
|
||||
import com.google.api.client.auth.oauth2.Credential
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
|
||||
import com.google.api.client.http.HttpStatusCodes
|
||||
import com.google.api.client.http.javanet.NetHttpTransport
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import discord4j.common.util.Snowflake
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Request
|
||||
import org.dreamexposure.discal.core.config.Config
|
||||
import org.dreamexposure.discal.core.database.DatabaseManager
|
||||
import org.dreamexposure.discal.core.enums.calendar.CalendarHost
|
||||
import org.dreamexposure.discal.core.exceptions.EmptyNotAllowedException
|
||||
import org.dreamexposure.discal.core.logger.LOGGER
|
||||
import org.dreamexposure.discal.core.`object`.calendar.CalendarData
|
||||
import org.dreamexposure.discal.core.`object`.network.discal.CredentialData
|
||||
import org.dreamexposure.discal.core.`object`.rest.RestError
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.DEFAULT
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.HTTP_CLIENT
|
||||
import org.dreamexposure.discal.core.utils.GlobalVal.JSON_FORMAT
|
||||
import reactor.core.publisher.Mono
|
||||
import reactor.core.scheduler.Schedulers
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.random.Random
|
||||
import com.google.api.services.calendar.Calendar as GoogleCalendarService
|
||||
|
||||
@Deprecated("Use GoogleCalendarApiWrapper")
|
||||
object GoogleAuthWrapper {
|
||||
private val discalTokens: MutableMap<Int, CredentialData> = ConcurrentHashMap()
|
||||
private val externalTokens: MutableMap<Snowflake, CredentialData> = ConcurrentHashMap()
|
||||
|
||||
private fun authorize(credentialId: Int): Mono<Credential> {
|
||||
return getAccessToken(credentialId)
|
||||
.map(GoogleCredential()::setAccessToken)
|
||||
.ofType(Credential::class.java) //Cast down to the class it extends
|
||||
.switchIfEmpty(Mono.error(EmptyNotAllowedException()))
|
||||
}
|
||||
|
||||
private fun authorize(calData: CalendarData): Mono<Credential> {
|
||||
return Mono.just(calData).filter { !"N/a".equals(calData.encryptedAccessToken, true) }
|
||||
.flatMap(this::getAccessToken)
|
||||
.map(GoogleCredential()::setAccessToken)
|
||||
.ofType(Credential::class.java) //Cast down to the class it extends
|
||||
.switchIfEmpty(Mono.error(EmptyNotAllowedException()))
|
||||
}
|
||||
|
||||
private fun buildService(credential: Credential): GoogleCalendarService {
|
||||
|
||||
return GoogleCalendarService.Builder(NetHttpTransport(), GsonFactory.getDefaultInstance(), credential)
|
||||
.setApplicationName("DisCal")
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getAccessToken(credentialId: Int): Mono<String> {
|
||||
val token = discalTokens[credentialId]
|
||||
if (token != null && !token.isExpired()) {
|
||||
return Mono.just(token.accessToken)
|
||||
}
|
||||
|
||||
LOGGER.debug("Refreshing local token via CAM | credentialId:$credentialId")
|
||||
|
||||
return Mono.fromCallable {
|
||||
val url = "${Config.URL_CAM.getString()}/v1/token".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("host", CalendarHost.GOOGLE.name)
|
||||
.addQueryParameter("id", credentialId.toString())
|
||||
.build()
|
||||
|
||||
val request = Request.Builder().get()
|
||||
.header("Authorization", "Int ${Config.SECRET_DISCAL_API_KEY.getString()}")
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
HTTP_CLIENT.newCall(request).execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic()).flatMap { response ->
|
||||
when (response.code) {
|
||||
HttpStatusCodes.STATUS_CODE_OK -> {
|
||||
val data = JSON_FORMAT.decodeFromString(CredentialData.serializer(), response.body!!.string())
|
||||
response.body?.close()
|
||||
response.close()
|
||||
|
||||
discalTokens[credentialId] = data
|
||||
Mono.just(data.accessToken)
|
||||
}
|
||||
else -> {
|
||||
val error = JSON_FORMAT.decodeFromString(RestError.serializer(), response.body!!.string())
|
||||
response.body?.close()
|
||||
response.close()
|
||||
|
||||
// Log because this really shouldn't be happening
|
||||
LOGGER.error(DEFAULT, "[Google] Error requesting access token from CAM for Int. | credentialId:$credentialId | error:$error")
|
||||
Mono.empty()
|
||||
}
|
||||
}
|
||||
}.doOnError {
|
||||
LOGGER.error("GoogleAuth | Failed to get access token from CAM | credentialId:$credentialId", it)
|
||||
}.onErrorResume { Mono.empty() }
|
||||
}
|
||||
|
||||
private fun getAccessToken(calData: CalendarData): Mono<String> {
|
||||
val token = externalTokens[calData.guildId]
|
||||
if (token != null && !token.isExpired()) {
|
||||
return Mono.just(token.accessToken)
|
||||
}
|
||||
|
||||
LOGGER.debug("Refreshing local token via CAM | guild:{} | host:{} | calendarId:{} ", calData.guildId, calData.host, calData.calendarNumber)
|
||||
|
||||
return Mono.fromCallable {
|
||||
val url = "${Config.URL_CAM.getString()}/v1/token".toHttpUrlOrNull()!!.newBuilder()
|
||||
.addQueryParameter("host", calData.host.name)
|
||||
.addQueryParameter("guild", calData.guildId.asString())
|
||||
.addQueryParameter("id", calData.calendarNumber.toString())
|
||||
.build()
|
||||
|
||||
val request = Request.Builder().get()
|
||||
.header("Authorization", "Int ${Config.SECRET_DISCAL_API_KEY.getString()}")
|
||||
.url(url)
|
||||
.build()
|
||||
|
||||
HTTP_CLIENT.newCall(request).execute()
|
||||
}.subscribeOn(Schedulers.boundedElastic()).flatMap { response ->
|
||||
when (response.code) {
|
||||
HttpStatusCodes.STATUS_CODE_OK -> {
|
||||
val data = JSON_FORMAT.decodeFromString(CredentialData.serializer(), response.body!!.string())
|
||||
response.body?.close()
|
||||
response.close()
|
||||
|
||||
externalTokens[calData.guildId] = data
|
||||
Mono.just(data.accessToken)
|
||||
}
|
||||
else -> {
|
||||
val error = JSON_FORMAT.decodeFromString(RestError.serializer(), response.body!!.string())
|
||||
response.body?.close()
|
||||
response.close()
|
||||
|
||||
when (error) {
|
||||
RestError.ACCESS_REVOKED -> {
|
||||
// Delete calendar, user MUST reauthorize discal as the refresh token isn't valid.
|
||||
DatabaseManager.deleteCalendarAndRelatedData(calData).then(Mono.empty())
|
||||
}
|
||||
else -> {
|
||||
//An unknown/unsupported error has occurred, log and return empty, upstream can handle this
|
||||
LOGGER.debug(DEFAULT, "[Google] Error requesting access token from CAM for Ext. | {}", error)
|
||||
Mono.empty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCalendarService(calData: CalendarData): Mono<GoogleCalendarService> {
|
||||
return Mono.defer {
|
||||
if (calData.external) {
|
||||
authorize(calData).map(this::buildService)
|
||||
} else {
|
||||
getCalendarService(calData.credentialId)
|
||||
}
|
||||
}.switchIfEmpty(Mono.error(EmptyNotAllowedException()))
|
||||
}
|
||||
|
||||
fun getCalendarService(credentialId: Int): Mono<GoogleCalendarService> {
|
||||
return authorize(credentialId)
|
||||
.map(this::buildService)
|
||||
.switchIfEmpty(Mono.error(EmptyNotAllowedException()))
|
||||
}
|
||||
|
||||
fun randomCredentialId() = Random.nextInt(Config.SECRET_GOOGLE_CREDENTIAL_COUNT.getInt())
|
||||
}
|
||||
Reference in New Issue
Block a user