From 03c7f600302cde2fef37dd9cda9c3bb014302aef Mon Sep 17 00:00:00 2001 From: MrPowerGamerBR Date: Tue, 12 Nov 2024 18:16:20 -0300 Subject: [PATCH] Add Loritta's daily shop notifications to be relayed to guilds --- ...TrinketsNotificationMessagePlaceholders.kt | 24 + .../placeholders/PlaceholderSectionType.kt | 3 +- .../common/utils/placeholders/Placeholders.kt | 3 + .../utils/placeholders/SectionPlaceholders.kt | 3 +- .../economy/SonhosTransactionsExecutor.kt | 2 +- .../utils/LorittaDailyShopUpdateTask.kt | 31 +- .../morenitta/website/DefaultRoutes.kt | 5 + ...gureDailyShopTrinketsNotificationsRoute.kt | 77 ++++ ...gureDailyShopTrinketsNotificationsRoute.kt | 80 ++++ .../routes/user/dashboard/DailyShopRoute.kt | 2 +- .../dashboard/guild/GuildDashboardView.kt | 3 +- .../guild/LegacyGuildDashboardView.kt | 1 + ...GuildDailyShopTrinketsNotificationsView.kt | 251 +++++++++++ .../websiteinternal/InternalWebServer.kt | 4 +- .../DailyShopRefreshedRoute.kt | 418 ++++++++++++++++++ ...ildDailyShopTrinketsNotificationsConfig.kt | 14 + .../loritta/cinnamon/pudding/Pudding.kt | 5 +- .../LorittaDailyShopNotificationsConfigs.kt | 13 + .../pt/daily-shop-trinkets-relayer.yml | 10 + .../pt/dashboard-daily-shop-trinkets.yml | 16 + resources/languages/pt/invalid-messages.yml | 3 +- .../messages/DiscordMessageRenderer.kt | 2 +- 22 files changed, 954 insertions(+), 16 deletions(-) create mode 100644 common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/DailyShopTrinketsNotificationMessagePlaceholders.kt create mode 100644 loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/ConfigureDailyShopTrinketsNotificationsRoute.kt create mode 100644 loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/PutConfigureDailyShopTrinketsNotificationsRoute.kt create mode 100644 loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/dailyshoptrinkets/GuildDailyShopTrinketsNotificationsView.kt create mode 100644 loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/loriinternalapi/DailyShopRefreshedRoute.kt create mode 100644 loritta-serializable-commons/src/commonMain/kotlin/net/perfectdreams/loritta/serializable/config/GuildDailyShopTrinketsNotificationsConfig.kt create mode 100644 pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/tables/servers/moduleconfigs/LorittaDailyShopNotificationsConfigs.kt create mode 100644 resources/languages/pt/daily-shop-trinkets-relayer.yml create mode 100644 resources/languages/pt/dashboard-daily-shop-trinkets.yml diff --git a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/DailyShopTrinketsNotificationMessagePlaceholders.kt b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/DailyShopTrinketsNotificationMessagePlaceholders.kt new file mode 100644 index 0000000000..97d9b0733d --- /dev/null +++ b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/DailyShopTrinketsNotificationMessagePlaceholders.kt @@ -0,0 +1,24 @@ +package net.perfectdreams.loritta.common.utils.placeholders + +import net.perfectdreams.loritta.i18n.I18nKeysData + +data object DailyShopTrinketsNotificationMessagePlaceholders : SectionPlaceholders { + override val type = PlaceholderSectionType.DAILY_SHOP_TRINKETS_NOTIFICATION_MESSAGE + + sealed interface DailyShopTrinketsNotificationPlaceholder : MessagePlaceholder + + object DailyShopDateShortPlaceholder : GenericPlaceholders.GenericPlaceholder(null), DailyShopTrinketsNotificationPlaceholder { + override val names = listOf(Placeholders.DAILY_SHOP_DATE_SHORT.toVisiblePlaceholder()) + override val renderType = MessagePlaceholder.RenderType.TEXT + } + object GuildNamePlaceholder : GenericPlaceholders.GuildNamePlaceholder(I18nKeysData.Placeholders.Generic.GuildName), DailyShopTrinketsNotificationPlaceholder + object GuildSizePlaceholder : GenericPlaceholders.GuildSizePlaceholder(I18nKeysData.Placeholders.Generic.GuildSize), DailyShopTrinketsNotificationPlaceholder + object GuildIconUrlPlaceholder : GenericPlaceholders.GuildIconUrlPlaceholder(I18nKeysData.Placeholders.Generic.GuildIconUrl), DailyShopTrinketsNotificationPlaceholder + + override val placeholders = listOf( + DailyShopDateShortPlaceholder, + GuildNamePlaceholder, + GuildSizePlaceholder, + GuildIconUrlPlaceholder + ) +} \ No newline at end of file diff --git a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/PlaceholderSectionType.kt b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/PlaceholderSectionType.kt index cca2e9794f..a47b1365bc 100644 --- a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/PlaceholderSectionType.kt +++ b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/PlaceholderSectionType.kt @@ -8,5 +8,6 @@ enum class PlaceholderSectionType { LEAVE_MESSAGE, TWITCH_STREAM_ONLINE_MESSAGE, BLUESKY_POST_MESSAGE, - YOUTUBE_POST_MESSAGE + YOUTUBE_POST_MESSAGE, + DAILY_SHOP_TRINKETS_NOTIFICATION_MESSAGE } \ No newline at end of file diff --git a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/Placeholders.kt b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/Placeholders.kt index b2260dbf78..b4650c7f58 100644 --- a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/Placeholders.kt +++ b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/Placeholders.kt @@ -70,6 +70,9 @@ object Placeholders { // ===[ BLUESKY ]=== val BLUESKY_POST_URL = LorittaPlaceholder("post.url") + // ===[ DAILY SHOP TRINKETS ]=== + val DAILY_SHOP_DATE_SHORT = LorittaPlaceholder("daily-shop.date-short") + object Deprecated { val USER_ID = LorittaPlaceholder("user-id") val USER_DISCRIMINATOR = LorittaPlaceholder("user-discriminator") diff --git a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/SectionPlaceholders.kt b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/SectionPlaceholders.kt index 4b3ec91bec..9cd3174a42 100644 --- a/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/SectionPlaceholders.kt +++ b/common/src/commonMain/kotlin/net/perfectdreams/loritta/common/utils/placeholders/SectionPlaceholders.kt @@ -7,7 +7,8 @@ sealed interface SectionPlaceholders { LeaveMessagePlaceholders, TwitchStreamOnlineMessagePlaceholders, BlueskyPostMessagePlaceholders, - YouTubePostMessagePlaceholders + YouTubePostMessagePlaceholders, + DailyShopTrinketsNotificationMessagePlaceholders ) init { diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/vanilla/economy/SonhosTransactionsExecutor.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/vanilla/economy/SonhosTransactionsExecutor.kt index ba1b17afe2..17f78a703c 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/vanilla/economy/SonhosTransactionsExecutor.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/interactions/vanilla/economy/SonhosTransactionsExecutor.kt @@ -40,7 +40,7 @@ class SonhosTransactionsExecutor(val loritta: LorittaBot) : LorittaSlashCommandE userFacingTransactionTypeFilter: List ): suspend InlineMessage<*>.() -> (Unit) = { content = "" - + // If the list is empty, we will use *all* transaction types in the filter // This makes it easier because you don't need to manually deselect every single filter before you can filter by a specific // transaction type. diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/utils/LorittaDailyShopUpdateTask.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/utils/LorittaDailyShopUpdateTask.kt index e402bec88c..c7c756d8a4 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/utils/LorittaDailyShopUpdateTask.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/utils/LorittaDailyShopUpdateTask.kt @@ -1,14 +1,15 @@ package net.perfectdreams.loritta.morenitta.utils +import io.ktor.client.request.* +import io.ktor.http.* +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mu.KotlinLogging -import net.perfectdreams.loritta.cinnamon.pudding.tables.Backgrounds -import net.perfectdreams.loritta.cinnamon.pudding.tables.ProfileDesigns -import net.perfectdreams.loritta.morenitta.LorittaBot import net.perfectdreams.loritta.cinnamon.pudding.tables.* +import net.perfectdreams.loritta.morenitta.LorittaBot import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.* -import org.jetbrains.exposed.sql.transactions.transaction class LorittaDailyShopUpdateTask(val loritta: LorittaBot) : Runnable { companion object { @@ -23,17 +24,35 @@ class LorittaDailyShopUpdateTask(val loritta: LorittaBot) : Runnable { logger.info { "Generating a new daily shop..." } runBlocking { - loritta.pudding.transaction { + val resultId = loritta.pudding.transaction { val newShop = DailyShops.insertAndGetId { it[generatedAt] = System.currentTimeMillis() } getAndAddRandomBackgroundsToShop(newShop) getAndAddRandomProfileDesignsToShop(newShop) + + newShop + } + + // Notify that it was refreshed + val shards = loritta.config.loritta.clusters.instances + + shards.map { + GlobalScope.launch { + try { + logger.info { "Sending daily shop refresh request to other clusters..." } + loritta.httpWithoutTimeout.post("${it.getUrl(loritta)}/daily-shop-refreshed?dailyShopId=${resultId}") { + userAgent(loritta.lorittaCluster.getUserAgent(loritta)) + } + } catch (e: Exception) { + logger.warn(e) { "Shard ${it.name} ${it.id} offline!" } + } + } } } } - + private fun getAndAddRandomBackgroundsToShop(shopId: EntityID) { val allBackgrounds = Backgrounds.select { Backgrounds.enabled eq true and (Backgrounds.availableToBuyViaDreams eq true) diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/DefaultRoutes.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/DefaultRoutes.kt index a387cf48ad..29d14606fd 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/DefaultRoutes.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/DefaultRoutes.kt @@ -18,6 +18,8 @@ import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.* import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.bluesky.* import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.commands.ConfigureCommandsRoute import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.commands.PutConfigureCommandsRoute +import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.dailyshoptrinkets.ConfigureDailyShopTrinketsNotificationsRoute +import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.dailyshoptrinkets.PutConfigureDailyShopTrinketsNotificationsRoute import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.reactionevents.ConfigureReactionEventsRoute import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.reactionevents.PutConfigureReactionEventsRoute import net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.twitch.* @@ -109,6 +111,9 @@ object DefaultRoutes { ConfigureReactionEventsRoute(loritta), PutConfigureReactionEventsRoute(loritta), + ConfigureDailyShopTrinketsNotificationsRoute(loritta), + PutConfigureDailyShopTrinketsNotificationsRoute(loritta), + // Reps UserReputationRoute(loritta), diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/ConfigureDailyShopTrinketsNotificationsRoute.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/ConfigureDailyShopTrinketsNotificationsRoute.kt new file mode 100644 index 0000000000..6086a5fbad --- /dev/null +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/ConfigureDailyShopTrinketsNotificationsRoute.kt @@ -0,0 +1,77 @@ +package net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.dailyshoptrinkets + +import io.ktor.server.application.* +import net.dv8tion.jda.api.entities.Guild +import net.perfectdreams.i18nhelper.core.I18nContext +import net.perfectdreams.loritta.cinnamon.pudding.tables.servers.moduleconfigs.LorittaDailyShopNotificationsConfigs +import net.perfectdreams.loritta.common.locale.BaseLocale +import net.perfectdreams.loritta.common.utils.UserPremiumPlans +import net.perfectdreams.loritta.morenitta.LorittaBot +import net.perfectdreams.loritta.morenitta.dao.ServerConfig +import net.perfectdreams.loritta.morenitta.website.routes.dashboard.RequiresGuildAuthLocalizedDashboardRoute +import net.perfectdreams.loritta.morenitta.website.utils.extensions.respondHtml +import net.perfectdreams.loritta.morenitta.website.views.dashboard.guild.dailyshoptrinkets.GuildDailyShopTrinketsNotificationsView +import net.perfectdreams.loritta.serializable.ColorTheme +import net.perfectdreams.loritta.serializable.config.GuildDailyShopTrinketsNotificationsConfig +import net.perfectdreams.loritta.temmiewebsession.LorittaJsonWebSession +import net.perfectdreams.temmiediscordauth.TemmieDiscordAuth +import org.jetbrains.exposed.sql.selectAll + +class ConfigureDailyShopTrinketsNotificationsRoute(loritta: LorittaBot) : RequiresGuildAuthLocalizedDashboardRoute(loritta, "/configure/daily-shop-trinkets") { + override suspend fun onDashboardGuildAuthenticatedRequest( + call: ApplicationCall, + locale: BaseLocale, + i18nContext: I18nContext, + discordAuth: TemmieDiscordAuth, + userIdentification: LorittaJsonWebSession.UserIdentification, + guild: Guild, + serverConfig: ServerConfig, + colorTheme: ColorTheme + ) { + val databaseConfig = loritta.transaction { + LorittaDailyShopNotificationsConfigs.selectAll() + .where { + LorittaDailyShopNotificationsConfigs.id eq guild.idLong + } + .firstOrNull() + } + + val config = if (databaseConfig != null) { + GuildDailyShopTrinketsNotificationsConfig( + databaseConfig[LorittaDailyShopNotificationsConfigs.notifyShopTrinkets], + databaseConfig[LorittaDailyShopNotificationsConfigs.shopTrinketsChannelId], + databaseConfig[LorittaDailyShopNotificationsConfigs.shopTrinketsMessage], + + databaseConfig[LorittaDailyShopNotificationsConfigs.notifyNewTrinkets], + databaseConfig[LorittaDailyShopNotificationsConfigs.newTrinketsChannelId], + databaseConfig[LorittaDailyShopNotificationsConfigs.newTrinketsMessage], + ) + } else { + GuildDailyShopTrinketsNotificationsConfig( + false, + null, + GuildDailyShopTrinketsNotificationsView.defaultShopTrinketsTemplate.content, + + false, + null, + GuildDailyShopTrinketsNotificationsView.defaultNewTrinketsTemplate.content + ) + } + + call.respondHtml( + GuildDailyShopTrinketsNotificationsView( + loritta.newWebsite!!, + i18nContext, + locale, + getPathWithoutLocale(call), + loritta.getLegacyLocaleById(locale.id), + userIdentification, + UserPremiumPlans.getPlanFromValue(loritta.getActiveMoneyFromDonations(userIdentification.id.toLong())), + colorTheme, + guild, + "daily_shop_trinkets", + config + ).generateHtml() + ) + } +} \ No newline at end of file diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/PutConfigureDailyShopTrinketsNotificationsRoute.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/PutConfigureDailyShopTrinketsNotificationsRoute.kt new file mode 100644 index 0000000000..c55f02157b --- /dev/null +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/dashboard/configure/dailyshoptrinkets/PutConfigureDailyShopTrinketsNotificationsRoute.kt @@ -0,0 +1,80 @@ +package net.perfectdreams.loritta.morenitta.website.routes.dashboard.configure.dailyshoptrinkets + +import io.ktor.server.application.* +import io.ktor.server.request.* +import net.dv8tion.jda.api.entities.Guild +import net.perfectdreams.i18nhelper.core.I18nContext +import net.perfectdreams.loritta.cinnamon.pudding.tables.servers.moduleconfigs.LorittaDailyShopNotificationsConfigs +import net.perfectdreams.loritta.common.locale.BaseLocale +import net.perfectdreams.loritta.common.utils.UserPremiumPlans +import net.perfectdreams.loritta.morenitta.LorittaBot +import net.perfectdreams.loritta.morenitta.dao.ServerConfig +import net.perfectdreams.loritta.morenitta.website.routes.dashboard.RequiresGuildAuthLocalizedDashboardRoute +import net.perfectdreams.loritta.morenitta.website.utils.EmbeddedSpicyModalUtils.headerHXTrigger +import net.perfectdreams.loritta.morenitta.website.utils.extensions.respondHtml +import net.perfectdreams.loritta.morenitta.website.views.dashboard.guild.dailyshoptrinkets.GuildDailyShopTrinketsNotificationsView +import net.perfectdreams.loritta.serializable.ColorTheme +import net.perfectdreams.loritta.serializable.EmbeddedSpicyToast +import net.perfectdreams.loritta.serializable.config.GuildDailyShopTrinketsNotificationsConfig +import net.perfectdreams.loritta.temmiewebsession.LorittaJsonWebSession +import net.perfectdreams.temmiediscordauth.TemmieDiscordAuth +import org.jetbrains.exposed.sql.upsert + +class PutConfigureDailyShopTrinketsNotificationsRoute(loritta: LorittaBot) : RequiresGuildAuthLocalizedDashboardRoute(loritta, "/configure/daily-shop-trinkets") { + override suspend fun onDashboardGuildAuthenticatedRequest( + call: ApplicationCall, + locale: BaseLocale, + i18nContext: I18nContext, + discordAuth: TemmieDiscordAuth, + userIdentification: LorittaJsonWebSession.UserIdentification, + guild: Guild, + serverConfig: ServerConfig, + colorTheme: ColorTheme + ) { + val params = call.receiveParameters() + + val result = loritta.newSuspendedTransaction { + LorittaDailyShopNotificationsConfigs.upsert(LorittaDailyShopNotificationsConfigs.id) { + it[LorittaDailyShopNotificationsConfigs.id] = guild.idLong + it[LorittaDailyShopNotificationsConfigs.notifyShopTrinkets] = params["notifyShopTrinkets"] == "on" + it[LorittaDailyShopNotificationsConfigs.shopTrinketsChannelId] = params["shopTrinketsChannelId"]?.toLong() + it[LorittaDailyShopNotificationsConfigs.shopTrinketsMessage] = params["shopTrinketsMessage"] + + it[LorittaDailyShopNotificationsConfigs.notifyNewTrinkets] = params["notifyNewTrinkets"] == "on" + it[LorittaDailyShopNotificationsConfigs.newTrinketsChannelId] = params["newTrinketsChannelId"]?.toLong() + it[LorittaDailyShopNotificationsConfigs.newTrinketsMessage] = params["newTrinketsMessage"] + } + } + + val config = GuildDailyShopTrinketsNotificationsConfig( + result[LorittaDailyShopNotificationsConfigs.notifyShopTrinkets], + result[LorittaDailyShopNotificationsConfigs.shopTrinketsChannelId], + result[LorittaDailyShopNotificationsConfigs.shopTrinketsMessage], + + result[LorittaDailyShopNotificationsConfigs.notifyNewTrinkets], + result[LorittaDailyShopNotificationsConfigs.newTrinketsChannelId], + result[LorittaDailyShopNotificationsConfigs.newTrinketsMessage], + ) + + call.response.headerHXTrigger { + playSoundEffect = "config-saved" + showSpicyToast(EmbeddedSpicyToast.Type.SUCCESS, "Configuração salva!") + } + + call.respondHtml( + GuildDailyShopTrinketsNotificationsView( + loritta.newWebsite!!, + i18nContext, + locale, + getPathWithoutLocale(call), + loritta.getLegacyLocaleById(locale.id), + userIdentification, + UserPremiumPlans.getPlanFromValue(loritta.getActiveMoneyFromDonations(userIdentification.id.toLong())), + colorTheme, + guild, + "daily_shop_trinkets", + config + ).generateHtml() + ) + } +} \ No newline at end of file diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/user/dashboard/DailyShopRoute.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/user/dashboard/DailyShopRoute.kt index 274479e011..e3b0146fe8 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/user/dashboard/DailyShopRoute.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/routes/user/dashboard/DailyShopRoute.kt @@ -153,7 +153,7 @@ class DailyShopRoute(loritta: LorittaBot) : RequiresDiscordLoginLocalizedDashboa setMetaProperty("og:site_name", "Loritta") setMetaProperty("og:title", "Loja Diária") setMetaProperty("og:description", "Bem-vind@ a loja diária de itens! O lugar para comprar itens para o seu \"+perfil\" da Loritta!\n\nTodo o dia as 00:00 UTC (21:00 no horário do Brasil) a loja é atualizada com novos itens! Então volte todo o dia para verificar ^-^") - setMetaProperty("og:image", loritta.config.loritta.website.url + "assets/img/loritta_daily_shop.png") + setMetaProperty("og:image", "https://stuff.loritta.website/loritta-daily-shop-nicholas.png") setMetaProperty("og:image:width", "320") setMetaProperty("og:ttl", "660") setMetaProperty("og:image:width", "320") diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/GuildDashboardView.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/GuildDashboardView.kt index 8d3f583906..45efc153d9 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/GuildDashboardView.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/GuildDashboardView.kt @@ -99,7 +99,8 @@ abstract class GuildDashboardView( appendEntry("/guild/${guild.id}/configure/event-log", true, locale["modules.sectionNames.eventLog"], "fa fa-eye", false) appendEntry("/guild/${guild.id}/configure/youtube", true, "YouTube", "fab fa-youtube", false) appendEntry("/guild/${guild.id}/configure/twitch", true, "Twitch", "fab fa-twitch", false) - appendEntry("/guild/${guild.id}/configure/bluesky", true, "Bluesky", "fab fa-bluesky", true) + appendEntry("/guild/${guild.id}/configure/bluesky", true, "Bluesky", "fab fa-bluesky", false) + appendEntry("/guild/${guild.id}/configure/daily-shop-trinkets", true, i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.Title), "fa-solid fa-store", true) hr(classes = "divider") {} diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/LegacyGuildDashboardView.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/LegacyGuildDashboardView.kt index d76727dc99..44c61adeb1 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/LegacyGuildDashboardView.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/LegacyGuildDashboardView.kt @@ -121,6 +121,7 @@ abstract class LegacyGuildDashboardView( appendEntry("/guild/${guild.id}/configure/youtube", false, "YouTube", "fab fa-youtube", "youtube") appendEntry("/guild/${guild.id}/configure/twitch", false, "Twitch", "fab fa-twitch", "twitch") appendEntry("/guild/${guild.id}/configure/bluesky", false, "Bluesky", "fab fa-bluesky", "bluesky") + appendEntry("/guild/${guild.id}/configure/daily-shop-trinkets", false, i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.Title), "fa-solid fa-store", "daily_shop_trinkets") hr(classes = "divider") {} diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/dailyshoptrinkets/GuildDailyShopTrinketsNotificationsView.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/dailyshoptrinkets/GuildDailyShopTrinketsNotificationsView.kt new file mode 100644 index 0000000000..848f751de7 --- /dev/null +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/website/views/dashboard/guild/dailyshoptrinkets/GuildDailyShopTrinketsNotificationsView.kt @@ -0,0 +1,251 @@ +package net.perfectdreams.loritta.morenitta.website.views.dashboard.guild.dailyshoptrinkets + +import kotlinx.html.* +import net.dv8tion.jda.api.entities.Guild +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel +import net.dv8tion.jda.api.utils.TimeFormat +import net.perfectdreams.i18nhelper.core.I18nContext +import net.perfectdreams.loritta.cinnamon.emotes.Emotes +import net.perfectdreams.loritta.common.locale.BaseLocale +import net.perfectdreams.loritta.common.utils.UserPremiumPlans +import net.perfectdreams.loritta.common.utils.placeholders.DailyShopTrinketsNotificationMessagePlaceholders +import net.perfectdreams.loritta.common.utils.placeholders.PlaceholderSectionType +import net.perfectdreams.loritta.i18n.I18nKeysData +import net.perfectdreams.loritta.morenitta.utils.ImageFormat +import net.perfectdreams.loritta.morenitta.utils.extensions.getIconUrl +import net.perfectdreams.loritta.morenitta.utils.locale.LegacyBaseLocale +import net.perfectdreams.loritta.morenitta.website.LorittaWebsite +import net.perfectdreams.loritta.morenitta.website.components.DashboardDiscordMessageEditor +import net.perfectdreams.loritta.morenitta.website.components.DashboardDiscordMessageEditor.lorittaDiscordMessageEditor +import net.perfectdreams.loritta.morenitta.website.components.DashboardSaveBar.lorittaSaveBar +import net.perfectdreams.loritta.morenitta.website.components.DiscordChannelSelectMenu.discordChannelSelectMenu +import net.perfectdreams.loritta.morenitta.website.components.DiscordLikeToggles.toggleableSection +import net.perfectdreams.loritta.morenitta.website.utils.WebsiteUtils +import net.perfectdreams.loritta.morenitta.website.views.dashboard.guild.GuildDashboardView +import net.perfectdreams.loritta.serializable.ColorTheme +import net.perfectdreams.loritta.serializable.config.GuildDailyShopTrinketsNotificationsConfig +import net.perfectdreams.loritta.serializable.messageeditor.TestMessageTargetChannelQuery +import net.perfectdreams.loritta.temmiewebsession.LorittaJsonWebSession +import java.time.Instant + +class GuildDailyShopTrinketsNotificationsView( + loritta: LorittaWebsite, + i18nContext: I18nContext, + locale: BaseLocale, + path: String, + legacyBaseLocale: LegacyBaseLocale, + userIdentification: LorittaJsonWebSession.UserIdentification, + userPremiumPlan: UserPremiumPlans, + colorTheme: ColorTheme, + guild: Guild, + selectedType: String, + private val config: GuildDailyShopTrinketsNotificationsConfig +) : GuildDashboardView( + loritta, + i18nContext, + locale, + path, + legacyBaseLocale, + userIdentification, + userPremiumPlan, + colorTheme, + guild, + selectedType +) { + companion object { + val defaultShopTrinketsTemplate = DashboardDiscordMessageEditor.createMessageTemplate( + "Padrão", + "# ${Emotes.ShoppingBags.asMention} Loja Diária da Loritta ({daily-shop.date-short})" + ) + + val defaultNewTrinketsTemplate = DashboardDiscordMessageEditor.createMessageTemplate( + "Padrão", + "# ${Emotes.ShoppingBags.asMention} Novas Bugigangas na Loja Diária da Loritta ({daily-shop.date-short})" + ) + + private val notifyShopTrinketsTemplates = listOf( + defaultShopTrinketsTemplate + ) + + private val notifyNewTrinketsTemplates = listOf( + defaultNewTrinketsTemplate + ) + } + + override fun DIV.generateRightSidebarContents() { + val serializableGuild = WebsiteUtils.convertJDAGuildToSerializable(guild) + val serializableSelfLorittaUser = WebsiteUtils.convertJDAUserToSerializable(guild.selfMember.user) + + div { + div { + id = "form-stuff-wrapper" + + div(classes = "hero-wrapper") { + img(src = "https://stuff.loritta.website/loritta-daily-shop-nicholas.png", classes = "hero-image") {} + + div(classes = "hero-text") { + h1 { + text(i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.Title)) + } + + for (line in i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.Description)) { + p { + text(line) + } + } + } + } + + hr {} + + div { + id = "module-config-wrapper" + form { + id = "module-config" + attributes["loritta-synchronize-with-save-bar"] = "#save-bar" + + div(classes = "toggleable-sections") { + toggleableSection( + "notifyShopTrinkets", + i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.ShopRefresh.NotifyWhenRefresh), + i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.ShopRefresh.NotifyWhenRefreshDescription), + config.notifyShopTrinkets + ) { + div(classes = "field-wrappers") { + div(classes = "field-wrapper") { + div(classes = "field-title") { + text(i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.ShopRefresh.ChannelWhereTheMessagesWillBeSent)) + } + + discordChannelSelectMenu( + lorittaWebsite, + i18nContext, + "shopTrinketsChannelId", + guild.channels.filterIsInstance(), + config.shopTrinketsChannelId, + null + ) + } + + div(classes = "field-wrapper") { + div(classes = "field-title") { + text(i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.ShopRefresh.MessageWhenTheShopUpdates)) + } + + lorittaDiscordMessageEditor( + i18nContext, + "shopTrinketsMessage", + notifyShopTrinketsTemplates, + PlaceholderSectionType.DAILY_SHOP_TRINKETS_NOTIFICATION_MESSAGE, + DailyShopTrinketsNotificationMessagePlaceholders.placeholders.flatMap { + when (it) { + DailyShopTrinketsNotificationMessagePlaceholders.DailyShopDateShortPlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + TimeFormat.DATE_SHORT.format(Instant.now()) + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildNamePlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.name + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildSizePlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.memberCount.toString() + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildIconUrlPlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.getIconUrl(512, ImageFormat.PNG) ?: "" + ) // TODO: Fix this! + } + }, + serializableGuild, + serializableSelfLorittaUser, + TestMessageTargetChannelQuery.QuerySelector("[name='shopTrinketsChannelId']"), + config.shopTrinketsMessage ?: "" + ) + } + } + } + + toggleableSection( + "notifyNewTrinkets", + i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.NewTrinkets.NotifyWhenNew), + i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.NewTrinkets.NotifyWhenNewDescription), + config.notifyNewTrinkets + ) { + div(classes = "field-wrappers") { + div(classes = "field-wrapper") { + div(classes = "field-title") { + text(i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.NewTrinkets.ChannelWhereTheMessagesWillBeSent)) + } + + discordChannelSelectMenu( + lorittaWebsite, + i18nContext, + "newTrinketsChannelId", + guild.channels.filterIsInstance(), + config.newTrinketsChannelId, + null + ) + } + + div(classes = "field-wrapper") { + div(classes = "field-title") { + text(i18nContext.get(I18nKeysData.Website.Dashboard.DailyShopTrinkets.NewTrinkets.MessageWhenNewTrinkets)) + } + + lorittaDiscordMessageEditor( + i18nContext, + "newTrinketsMessage", + notifyNewTrinketsTemplates, + PlaceholderSectionType.DAILY_SHOP_TRINKETS_NOTIFICATION_MESSAGE, + DailyShopTrinketsNotificationMessagePlaceholders.placeholders.flatMap { + when (it) { + DailyShopTrinketsNotificationMessagePlaceholders.DailyShopDateShortPlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + TimeFormat.DATE_SHORT.format(Instant.now()) + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildNamePlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.name + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildSizePlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.memberCount.toString() + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildIconUrlPlaceholder -> DashboardDiscordMessageEditor.createMessageEditorPlaceholders( + it, + guild.getIconUrl(512, ImageFormat.PNG) ?: "" + ) // TODO: Fix this! + } + }, + serializableGuild, + serializableSelfLorittaUser, + TestMessageTargetChannelQuery.QuerySelector("[name='newTrinketsChannelId']"), + config.newTrinketsMessage ?: "" + ) + } + } + } + } + } + } + + hr {} + + lorittaSaveBar( + i18nContext, + false, + {} + ) { + attributes["hx-put"] = "/${i18nContext.get(I18nKeysData.Website.LocalePathId)}/guild/${guild.idLong}/configure/daily-shop-trinkets" + } + } + } + } +} \ No newline at end of file diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/InternalWebServer.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/InternalWebServer.kt index 8b1a77e761..a1ab696f20 100644 --- a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/InternalWebServer.kt +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/InternalWebServer.kt @@ -32,6 +32,7 @@ import net.perfectdreams.loritta.morenitta.utils.escapeMentions import net.perfectdreams.loritta.morenitta.utils.extensions.await import net.perfectdreams.loritta.morenitta.utils.extensions.getGuildMessageChannelById import net.perfectdreams.loritta.morenitta.website.utils.extensions.respondJson +import net.perfectdreams.loritta.morenitta.websiteinternal.loriinternalapi.DailyShopRefreshedRoute import net.perfectdreams.loritta.morenitta.websiteinternal.loriinternalapi.GuildJsonBenchmarkRoute import net.perfectdreams.loritta.morenitta.websiteinternal.loripublicapi.WebsitePublicAPIException import net.perfectdreams.loritta.morenitta.websiteinternal.loripublicapi.v1.guilds.* @@ -74,7 +75,8 @@ class InternalWebServer(val m: LorittaBot) { PostMusicalChairsRoute(m) ) private val internalAPIRoutes = listOf( - GuildJsonBenchmarkRoute(m) + GuildJsonBenchmarkRoute(m), + DailyShopRefreshedRoute(m) ) @OptIn(ExperimentalCoroutinesApi::class) diff --git a/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/loriinternalapi/DailyShopRefreshedRoute.kt b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/loriinternalapi/DailyShopRefreshedRoute.kt new file mode 100644 index 0000000000..4de1ead657 --- /dev/null +++ b/loritta-bot-discord/src/main/kotlin/net/perfectdreams/loritta/morenitta/websiteinternal/loriinternalapi/DailyShopRefreshedRoute.kt @@ -0,0 +1,418 @@ +package net.perfectdreams.loritta.morenitta.websiteinternal.loriinternalapi + +import dev.minn.jda.ktx.messages.MessageCreate +import io.ktor.server.application.* +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.serialization.json.buildJsonObject +import mu.KotlinLogging +import net.dv8tion.jda.api.entities.Message +import net.dv8tion.jda.api.entities.channel.middleman.GuildMessageChannel +import net.dv8tion.jda.api.interactions.components.buttons.Button +import net.dv8tion.jda.api.interactions.components.buttons.ButtonStyle +import net.dv8tion.jda.api.utils.FileUpload +import net.dv8tion.jda.api.utils.TimeFormat +import net.perfectdreams.galleryofdreams.common.data.DiscordSocialConnection +import net.perfectdreams.loritta.cinnamon.emotes.Emotes +import net.perfectdreams.loritta.cinnamon.pudding.services.fromRow +import net.perfectdreams.loritta.cinnamon.pudding.tables.Backgrounds +import net.perfectdreams.loritta.cinnamon.pudding.tables.DailyProfileShopItems +import net.perfectdreams.loritta.cinnamon.pudding.tables.DailyShopItems +import net.perfectdreams.loritta.cinnamon.pudding.tables.ProfileDesigns +import net.perfectdreams.loritta.cinnamon.pudding.tables.servers.ServerConfigs +import net.perfectdreams.loritta.cinnamon.pudding.tables.servers.moduleconfigs.LorittaDailyShopNotificationsConfigs +import net.perfectdreams.loritta.common.utils.Color +import net.perfectdreams.loritta.common.utils.Rarity +import net.perfectdreams.loritta.common.utils.placeholders.DailyShopTrinketsNotificationMessagePlaceholders +import net.perfectdreams.loritta.i18n.I18nKeysData +import net.perfectdreams.loritta.morenitta.LorittaBot +import net.perfectdreams.loritta.morenitta.profile.ProfileDesignManager +import net.perfectdreams.loritta.morenitta.utils.ImageFormat +import net.perfectdreams.loritta.morenitta.utils.MessageUtils +import net.perfectdreams.loritta.morenitta.utils.extensions.await +import net.perfectdreams.loritta.morenitta.utils.extensions.getGuildMessageChannelById +import net.perfectdreams.loritta.morenitta.utils.extensions.getIconUrl +import net.perfectdreams.loritta.morenitta.utils.extensions.toJDA +import net.perfectdreams.loritta.morenitta.website.utils.WebsiteUtils +import net.perfectdreams.loritta.morenitta.website.utils.extensions.respondJson +import net.perfectdreams.loritta.serializable.* +import net.perfectdreams.sequins.ktor.BaseRoute +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.innerJoin +import org.jetbrains.exposed.sql.or +import org.jetbrains.exposed.sql.selectAll +import java.time.Instant + +class DailyShopRefreshedRoute(val loritta: LorittaBot) : BaseRoute("/daily-shop-refreshed") { + companion object { + private val logger = KotlinLogging.logger {} + } + + override suspend fun onRequest(call: ApplicationCall) { + val dailyShopId = call.parameters["dailyShopId"]?.toLong() + + val result = loritta.transaction { + val backgrounds = DailyShopItems + .innerJoin(Backgrounds) + .selectAll() + .where { + DailyShopItems.shop eq dailyShopId + } + .toList() + .map { + DailyShopBackgroundEntry( + BackgroundWithVariations( + Background.fromRow(it), + loritta.pudding.backgrounds.getBackgroundVariations(it[Backgrounds.internalName]) + ), + it[DailyShopItems.tag] + ) + } + + val profileDesigns = DailyProfileShopItems + .innerJoin(ProfileDesigns) + .selectAll() + .where { + DailyProfileShopItems.shop eq dailyShopId + } + .toList() + .map { + WebsiteUtils.fromProfileDesignToSerializable(loritta, it).also { profile -> + profile.tag = it[DailyProfileShopItems.tag] + } + } + + val configs = LorittaDailyShopNotificationsConfigs.innerJoin(ServerConfigs, { ServerConfigs.id }, { LorittaDailyShopNotificationsConfigs.id }) + .selectAll() + .where { + LorittaDailyShopNotificationsConfigs.notifyNewTrinkets eq true or (LorittaDailyShopNotificationsConfigs.notifyShopTrinkets eq true) + } + .toList() + + Result(backgrounds, profileDesigns, configs) + } + + for (config in result.configs) { + val guild = loritta.lorittaShards.getGuildById(config[ServerConfigs.id].value) ?: continue + try { + val shopTrinketsChannelId = config[LorittaDailyShopNotificationsConfigs.shopTrinketsChannelId] + val newTrinketsChannelId = config[LorittaDailyShopNotificationsConfigs.newTrinketsChannelId] + + val i18nContext = loritta.languageManager.getI18nContextByLegacyLocaleId(config[ServerConfigs.localeId]) + val legacyBaseLocale = loritta.localeManager.getLocaleById(config[ServerConfigs.localeId]) + val selfMemberAsProfileUserInfoData = loritta.profileDesignManager.transformUserToProfileUserInfoData(guild.selfMember.user) + + suspend fun sendDailyShopTrinketsNotification( + channel: GuildMessageChannel, + message: String?, + shopItems: List, + medium: String, + ) { + channel.sendMessage( + MessageUtils.generateMessageOrFallbackIfInvalid( + i18nContext, + message ?: "", + guild, + DailyShopTrinketsNotificationMessagePlaceholders, + { + when (it) { + DailyShopTrinketsNotificationMessagePlaceholders.DailyShopDateShortPlaceholder -> TimeFormat.DATE_SHORT.format( + Instant.now() + ) + + DailyShopTrinketsNotificationMessagePlaceholders.GuildNamePlaceholder -> guild.name + DailyShopTrinketsNotificationMessagePlaceholders.GuildSizePlaceholder -> guild.memberCount.toString() + DailyShopTrinketsNotificationMessagePlaceholders.GuildIconUrlPlaceholder -> guild.getIconUrl( + 512, + ImageFormat.PNG + ) ?: "" + } + }, + I18nKeysData.InvalidMessages.DailyShopTrinketsNotification, + ) + ).await() + + val chunkedItems = shopItems + .sortedWith(compareByDescending(ShopItemWrapper::rarity).thenBy(ShopItemWrapper::internalName)) + .chunked(Message.MAX_EMBED_COUNT) + + for ((index, itemChunk) in chunkedItems.withIndex()) { + val isLast = index == chunkedItems.size - 1 + + channel.sendMessage( + MessageCreate { + for (item in itemChunk) { + embed { + val tag = item.tag + title = buildString { + if (tag != null) { + append("[${legacyBaseLocale[tag].uppercase()}] ") + } + + append(legacyBaseLocale[item.nameKey]) + } + description = legacyBaseLocale[item.descriptionKey] + field( + i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.Rarity), + when (item.rarity) { + Rarity.COMMON -> i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.RaritiesWithSonhos.Common(item.price)) + Rarity.UNCOMMON -> i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.RaritiesWithSonhos.Uncommon(item.price)) + Rarity.RARE -> i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.RaritiesWithSonhos.Rare(item.price)) + Rarity.EPIC -> i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.RaritiesWithSonhos.Epic(item.price)) + Rarity.LEGENDARY -> i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.RaritiesWithSonhos.Legendary(item.price)) + } + ) + if (item.set != null) { + field( + i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.Set), + legacyBaseLocale["sets.${item.set}"] + ) + } + + val createdBy = item.createdBy + if (createdBy.isNotEmpty()) { + val artists = loritta.cachedGalleryOfDreamsDataResponse!!.artists + .filter { it.slug in createdBy } + + if (artists.isNotEmpty()) { + field( + i18nContext.get(I18nKeysData.DailyShopTrinketsRelayer.Creator), + buildString { + var isFirst = true + for (artist in artists) { + if (!isFirst) + append(", ") + append(artist.name) + val discord = + artist.socialConnections.filterIsInstance() + .firstOrNull() + if (discord != null) { + append(" (`${discord.id}`)") + } + isFirst = false + } + } + ) + } + } + + color = when (item.rarity) { + Rarity.COMMON -> Color.fromHex("#e7e7e7").rgb + Rarity.UNCOMMON -> Color.fromHex("#2cff00").rgb + Rarity.RARE -> Color.fromHex("#009fff").rgb + Rarity.EPIC -> Color.fromHex("#b03cff").rgb + Rarity.LEGENDARY -> Color.fromHex("#fadf4b").rgb + } + + image = when (item) { + is BackgroundItemWrapper -> item.backgroundUrl + is ProfileDesignItemWrapper -> "attachment://profile-${item.internalName}.${item.profileResult.imageFormat.extension}" + } + } + + if (item is ProfileDesignItemWrapper) { + files += FileUpload.fromData( + item.profileResult.image, + "profile-${item.internalName}.${item.profileResult.imageFormat.extension}" + ) + } + } + + if (isLast) { + actionRow( + Button.of( + ButtonStyle.LINK, + "${loritta.config.loritta.website.url}dashboard/daily-shop?utm_source=discord&utm_medium=$medium&utm_campaign=daily-item-shop&utm_content=guild-${guild.idLong}", + i18nContext.get(I18nKeysData.Commands.Command.Profileview.LorittaDailyItemShop), + Emotes.ShoppingBags.toJDA() + ) + ) + } + } + ).await() + } + } + + if (shopTrinketsChannelId != null) { + val channel = guild.getGuildMessageChannelById(shopTrinketsChannelId) + + if (channel != null && channel.canTalk()) { + GlobalScope.launch { + try { + // This has "guild-specific" items (like the profile designs) + val shopItems = mutableListOf() + + for (item in result.profileDesigns) { + val profileResult = loritta.profileDesignManager.createProfile( + loritta, + i18nContext, + legacyBaseLocale, + selfMemberAsProfileUserInfoData, + selfMemberAsProfileUserInfoData, + guild.let { loritta.profileDesignManager.transformGuildToProfileGuildInfoData(it) }, + loritta.profileDesignManager.designs.first { + it.internalName == item.internalName + } + ) + + shopItems.add(ProfileDesignItemWrapper(item, profileResult)) + } + + for (item in result.backgrounds) { + val dssNamespace = loritta.dreamStorageService.getCachedNamespaceOrRetrieve() + val backgroundWithVariations = item.backgroundWithVariations + + val variation = + backgroundWithVariations.variations.filterIsInstance() + .first() + + val backgroundUrl = when (variation.storageType) { + BackgroundStorageType.DREAM_STORAGE_SERVICE -> loritta.profileDesignManager.getDreamStorageServiceBackgroundUrlWithCropParameters( + loritta.config.loritta.dreamStorageService.url, + dssNamespace, + variation + ) + + BackgroundStorageType.ETHEREAL_GAMBI -> loritta.profileDesignManager.getEtherealGambiBackgroundUrl( + variation + ) + } + + shopItems.add(BackgroundItemWrapper(item, backgroundUrl)) + } + + if (shopItems.isNotEmpty()) { + sendDailyShopTrinketsNotification( + channel, + config[LorittaDailyShopNotificationsConfigs.shopTrinketsMessage], + shopItems, + "daily-shop-refresh" + ) + } + } catch (e: Exception) { + logger.warn(e) { "Something went wrong while trying to send daily shop trinkets notifications to ${guild.idLong}!" } + } + } + } + } + + if (newTrinketsChannelId != null) { + val channel = guild.getGuildMessageChannelById(newTrinketsChannelId) + + if (channel != null && channel.canTalk()) { + GlobalScope.launch { + try { + // This has "guild-specific" items (like the profile designs) + val shopItems = mutableListOf() + + for (item in result.profileDesigns.filter { it.tag == "website.dailyShop.new" }) { + val profileResult = loritta.profileDesignManager.createProfile( + loritta, + i18nContext, + legacyBaseLocale, + selfMemberAsProfileUserInfoData, + selfMemberAsProfileUserInfoData, + guild.let { + loritta.profileDesignManager.transformGuildToProfileGuildInfoData( + it + ) + }, + loritta.profileDesignManager.designs.first { + it.internalName == item.internalName + } + ) + + shopItems.add(ProfileDesignItemWrapper(item, profileResult)) + } + + for (item in result.backgrounds.filter { it.tag == "website.dailyShop.new" }) { + val dssNamespace = loritta.dreamStorageService.getCachedNamespaceOrRetrieve() + val backgroundWithVariations = item.backgroundWithVariations + + val variation = + backgroundWithVariations.variations.filterIsInstance() + .first() + + val backgroundUrl = when (variation.storageType) { + BackgroundStorageType.DREAM_STORAGE_SERVICE -> loritta.profileDesignManager.getDreamStorageServiceBackgroundUrlWithCropParameters( + loritta.config.loritta.dreamStorageService.url, + dssNamespace, + variation + ) + + BackgroundStorageType.ETHEREAL_GAMBI -> loritta.profileDesignManager.getEtherealGambiBackgroundUrl( + variation + ) + } + + shopItems.add(BackgroundItemWrapper(item, backgroundUrl)) + } + + if (shopItems.isNotEmpty()) + sendDailyShopTrinketsNotification( + channel, + config[LorittaDailyShopNotificationsConfigs.newTrinketsMessage], + shopItems, + "new-trinkets" + ) + } catch (e: Exception) { + logger.warn(e) { "Something went wrong while trying to send daily shop trinkets notifications to ${guild.idLong}!" } + } + } + } + } + } catch (e: Exception) { + logger.warn(e) { "Something went wrong while trying to process daily shop trinkets notifications to ${guild.idLong}!" } + } + } + + // We don't await to avoid timing out, keep it in separate tasks + call.respondJson(buildJsonObject {}) + } + + private data class Result( + val backgrounds: List, + val profileDesigns: List, + val configs: List, + ) + + private sealed class ShopItemWrapper { + abstract val internalName: String + abstract val rarity: Rarity + abstract val tag: String? + abstract val localePrefix: String? + abstract val price: Int + abstract val set: String? + abstract val createdBy: List + abstract val nameKey: String + abstract val descriptionKey: String + } + + private class BackgroundItemWrapper(backgroundEntry: DailyShopBackgroundEntry, val backgroundUrl: String) : ShopItemWrapper() { + val background = backgroundEntry.backgroundWithVariations.background + val variations = backgroundEntry.backgroundWithVariations.variations + override val internalName = background.id + override val rarity = background.rarity + override val tag = backgroundEntry.tag + override val localePrefix = "backgrounds" + override val price = rarity.getBackgroundPrice() + override val set = backgroundEntry.backgroundWithVariations.background.set + override val createdBy = backgroundEntry.backgroundWithVariations.background.createdBy + + override val nameKey = "backgrounds.${internalName}.title" + override val descriptionKey = "backgrounds.${internalName}.description" + } + + private class ProfileDesignItemWrapper(val profileDesign: ProfileDesign, val profileResult: ProfileDesignManager.ProfileCreationResult) : ShopItemWrapper() { + override val internalName = profileDesign.internalName + override val rarity = profileDesign.rarity + override val tag = profileDesign.tag + override val localePrefix = "profileDesigns" + override val price = rarity.getProfilePrice() + override val set = profileDesign.set + override val createdBy = profileDesign.createdBy + + override val nameKey = "profileDesigns.${internalName}.title" + override val descriptionKey = "profileDesigns.${internalName}.description" + } +} \ No newline at end of file diff --git a/loritta-serializable-commons/src/commonMain/kotlin/net/perfectdreams/loritta/serializable/config/GuildDailyShopTrinketsNotificationsConfig.kt b/loritta-serializable-commons/src/commonMain/kotlin/net/perfectdreams/loritta/serializable/config/GuildDailyShopTrinketsNotificationsConfig.kt new file mode 100644 index 0000000000..8337cd0faa --- /dev/null +++ b/loritta-serializable-commons/src/commonMain/kotlin/net/perfectdreams/loritta/serializable/config/GuildDailyShopTrinketsNotificationsConfig.kt @@ -0,0 +1,14 @@ +package net.perfectdreams.loritta.serializable.config + +import kotlinx.serialization.Serializable + +@Serializable +data class GuildDailyShopTrinketsNotificationsConfig( + val notifyShopTrinkets: Boolean, + val shopTrinketsChannelId: Long?, + val shopTrinketsMessage: String?, + + val notifyNewTrinkets: Boolean, + val newTrinketsChannelId: Long?, + val newTrinketsMessage: String? +) \ No newline at end of file diff --git a/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/Pudding.kt b/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/Pudding.kt index 722f1e099e..22b02c748b 100644 --- a/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/Pudding.kt +++ b/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/Pudding.kt @@ -70,7 +70,7 @@ class Pudding( private val DRIVER_CLASS_NAME = "org.postgresql.Driver" private val ISOLATION_LEVEL = IsolationLevel.TRANSACTION_REPEATABLE_READ // We use repeatable read to avoid dirty and non-repeatable reads! Very useful and safe!! - private const val SCHEMA_VERSION = 73 // Bump this every time any table is added/updated! + private const val SCHEMA_VERSION = 74 // Bump this every time any table is added/updated! private val SCHEMA_ID = UUID.fromString("600556aa-2920-41c7-b26c-7717eff2d392") // This is a random unique ID, it is used for upserting the schema version /** @@ -379,7 +379,8 @@ class Pudding( CollectedReactionEventPoints, CraftedReactionEventItems, ReactionEventsConfigs, - ReactionEventFinishedEventUsers + ReactionEventFinishedEventUsers, + LorittaDailyShopNotificationsConfigs ) if (schemas.isNotEmpty()) diff --git a/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/tables/servers/moduleconfigs/LorittaDailyShopNotificationsConfigs.kt b/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/tables/servers/moduleconfigs/LorittaDailyShopNotificationsConfigs.kt new file mode 100644 index 0000000000..418e795ee8 --- /dev/null +++ b/pudding/client/src/main/kotlin/net/perfectdreams/loritta/cinnamon/pudding/tables/servers/moduleconfigs/LorittaDailyShopNotificationsConfigs.kt @@ -0,0 +1,13 @@ +package net.perfectdreams.loritta.cinnamon.pudding.tables.servers.moduleconfigs + +import net.perfectdreams.loritta.cinnamon.pudding.tables.SnowflakeTable + +object LorittaDailyShopNotificationsConfigs : SnowflakeTable() { + val notifyShopTrinkets = bool("notify_shop_trinkets").index() + val shopTrinketsChannelId = long("shop_trinkets_channel").nullable() + val shopTrinketsMessage = text("shop_trinkets_message").nullable() + + val notifyNewTrinkets = bool("notify_new_trinkets").index() + val newTrinketsChannelId = long("new_trinkets_channel").nullable() + val newTrinketsMessage = text("new_trinkets_message").nullable() +} \ No newline at end of file diff --git a/resources/languages/pt/daily-shop-trinkets-relayer.yml b/resources/languages/pt/daily-shop-trinkets-relayer.yml new file mode 100644 index 0000000000..26f1dde307 --- /dev/null +++ b/resources/languages/pt/daily-shop-trinkets-relayer.yml @@ -0,0 +1,10 @@ +dailyShopTrinketsRelayer: + rarity: "Raridade" + set: "Coleção" + creator: "Criador" + raritiesWithSonhos: + common: "Comum ({quantity,plural, =0 {# sonhos} one {# sonho} other {# sonhos}})" + uncommon: "Incomum ({quantity,plural, =0 {# sonhos} one {# sonho} other {# sonhos}})" + rare: "Raro ({quantity,plural, =0 {# sonhos} one {# sonho} other {# sonhos}})" + epic: "Épico ({quantity,plural, =0 {# sonhos} one {# sonho} other {# sonhos}})" + legendary: "Lendário ({quantity,plural, =0 {# sonhos} one {# sonho} other {# sonhos}})" \ No newline at end of file diff --git a/resources/languages/pt/dashboard-daily-shop-trinkets.yml b/resources/languages/pt/dashboard-daily-shop-trinkets.yml new file mode 100644 index 0000000000..581d6e1428 --- /dev/null +++ b/resources/languages/pt/dashboard-daily-shop-trinkets.yml @@ -0,0 +1,16 @@ +website: + dashboard: + dailyShopTrinkets: + title: "Loja Diária da Loritta" + description: + - "Notificações sobre a Loja Diária de Bugigangas da Loritta. A loja diária da Loritta é onde usuários podem comprar itens para personalizar os perfis do usuário na Loritta, para deixar eles com mais frufrus." + shopRefresh: + notifyWhenRefresh: "Notificar quando a loja diária da Loritta atualizar" + notifyWhenRefreshDescription: "A Loritta enviará as bugigangas que estão na rotação diária no canal configurado." + channelWhereTheMessagesWillBeSent: "Canal onde será enviado as mensagens" + messageWhenTheShopUpdates: "Mensagem quando a loja diária da Loritta atualizar" + newTrinkets: + notifyWhenNew: "Notificar quando tiver novas bugigangas na loja diária da Loritta" + notifyWhenNewDescription: "A Loritta enviará cada bugiganga nova no canal configurado. Se não tiver nenhuma bugiganga nova na loja diária, a Loritta não enviará uma mensagem." + channelWhereTheMessagesWillBeSent: "Canal onde será enviado as mensagens" + messageWhenNewTrinkets: "Mensagem quando novas bugigangas forem lançadas na Loritta" \ No newline at end of file diff --git a/resources/languages/pt/invalid-messages.yml b/resources/languages/pt/invalid-messages.yml index f6bbec3bf2..43c9f70a09 100644 --- a/resources/languages/pt/invalid-messages.yml +++ b/resources/languages/pt/invalid-messages.yml @@ -17,4 +17,5 @@ invalidMessages: inviteBlocked: "convite bloqueado na seção de Bloqueador de Convites" youTubeNotification: "notificação de vídeo do YouTube" twitchStreamOnlineNotification: "notificação de livestream da Twitch" - blueskyPostNotification: "notificação de post no Bluesky" \ No newline at end of file + blueskyPostNotification: "notificação de post no Bluesky" + dailyShopTrinketsNotification: "notificação de novas bugigangas na loja diária da Loritta" \ No newline at end of file diff --git a/web/spicy-morenitta/src/jsMain/kotlin/net/perfectdreams/spicymorenitta/components/messages/DiscordMessageRenderer.kt b/web/spicy-morenitta/src/jsMain/kotlin/net/perfectdreams/spicymorenitta/components/messages/DiscordMessageRenderer.kt index 1f14e593b7..f7fabf3ea7 100644 --- a/web/spicy-morenitta/src/jsMain/kotlin/net/perfectdreams/spicymorenitta/components/messages/DiscordMessageRenderer.kt +++ b/web/spicy-morenitta/src/jsMain/kotlin/net/perfectdreams/spicymorenitta/components/messages/DiscordMessageRenderer.kt @@ -839,7 +839,7 @@ data class MessagePlaceholderNode(val placeholder: String) : MessageNode() private fun parseStringToNodes(input: String): List { val nodes = mutableListOf() // YES THE \\ IS NEEDED ON THE END OF THE } TO AVOID "raw bracket is not allowed in regular expression with unicode flag" - val regex = "\\{([@A-z0-9.]+)\\}".toRegex() + val regex = "\\{([@A-z0-9.-]+)\\}".toRegex() var lastIndex = 0 regex.findAll(input).forEach { matchResult ->