Skip to content

Commit

Permalink
Add API to request sonhos from a user and to transfer sonhos to an user
Browse files Browse the repository at this point in the history
Currently restricted only for "BOT" tokens
  • Loading branch information
MrPowerGamerBR committed Jan 3, 2025
1 parent aae46cf commit b636861
Show file tree
Hide file tree
Showing 30 changed files with 1,316 additions and 253 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package net.perfectdreams.loritta.common.utils

enum class TokenType {
USER,
BOT
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.cio.*
import io.ktor.server.engine.*
import io.ktor.server.request.*
Expand Down Expand Up @@ -51,6 +50,9 @@ class LoriAPIProxy(
ProxiedRoute(LoriPublicHttpApiEndpoints.EMOJIFIGHT_GUILD_TOP_WINNERS_RANK, ProxiedRoute.ROUTE_BASED_ON_GUILD_ID),
ProxiedRoute(LoriPublicHttpApiEndpoints.EMOJIFIGHT_GUILD_VICTORIES, ProxiedRoute.ROUTE_BASED_ON_GUILD_ID),
ProxiedRoute(LoriPublicHttpApiEndpoints.CREATE_GUILD_MUSICALCHAIRS, ProxiedRoute.ROUTE_BASED_ON_GUILD_ID),
ProxiedRoute(LoriPublicHttpApiEndpoints.TRANSFER_SONHOS, ProxiedRoute.ROUTE_BASED_ON_GUILD_ID),
ProxiedRoute(LoriPublicHttpApiEndpoints.REQUEST_SONHOS, ProxiedRoute.ROUTE_BASED_ON_GUILD_ID),
ProxiedRoute(LoriPublicHttpApiEndpoints.GET_SONHOS_TRANSFER_STATUS, ProxiedRoute.ROUTE_TO_DEFAULT_CLUSTER),
)

fun start() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ object LoriPublicHttpApiEndpoints {
)
val GET_USER_TRANSACTIONS = LoriPublicHttpApiEndpoint(HttpMethod.Get, "/users/{userId}/transactions")
val GET_SONHOS_RANK = LoriPublicHttpApiEndpoint(HttpMethod.Get, "/sonhos/rank")
val GET_SONHOS_TRANSFER_STATUS = LoriPublicHttpApiEndpoint(HttpMethod.Get, "/sonhos/sonhos-transfer/{sonhosTransferId}")

val VERIFY_LORITTA_MESSAGE = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/lori-messages/verify-message")
val SAVE_LORITTA_MESSAGE = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/guilds/{guildId}/channels/{channelId}/messages/{messageId}/save-lori-message")

val CREATE_GUILD_GIVEAWAY = LoriPublicHttpApiEndpoint(HttpMethod.Put, "/guilds/{guildId}/giveaways")
val END_GUILD_GIVEAWAY = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/guilds/{guildId}/giveaways/{giveawayId}/end")
val REROLL_GUILD_GIVEAWAY = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/guilds/{guildId}/giveaways/{giveawayId}/reroll")
val TRANSFER_SONHOS = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/guilds/{guildId}/channels/{channelId}/sonhos/sonhos-transfer")
val REQUEST_SONHOS = LoriPublicHttpApiEndpoint(HttpMethod.Post, "/guilds/{guildId}/channels/{channelId}/sonhos/sonhos-request")

val EMOJIFIGHT_GUILD_TOP_WINNERS_RANK = LoriPublicHttpApiEndpoint(HttpMethod.Get, "/guilds/{guildId}/emojifights/top-winners")
val EMOJIFIGHT_GUILD_VICTORIES = LoriPublicHttpApiEndpoint(HttpMethod.Get, "/guilds/{guildId}/members/{userId}/emojifight/victories")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ suspend fun main() {
is StoredLorittaItemShopComissionBackgroundTransaction -> TODO()
is StoredLorittaItemShopComissionProfileDesignTransaction -> TODO()
is StoredMarriageMarryTransaction -> TODO()
is StoredAPIInitiatedPaymentSonhosTransaction -> TODO()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import kotlinx.datetime.toKotlinInstant
import mu.KotlinLogging
import net.perfectdreams.loritta.cinnamon.discord.utils.RunnableCoroutine
import net.perfectdreams.loritta.cinnamon.pudding.tables.DailyTaxNotifiedUsers
import net.perfectdreams.loritta.cinnamon.pudding.tables.Payments
import net.perfectdreams.loritta.cinnamon.pudding.tables.Profiles
import net.perfectdreams.loritta.cinnamon.pudding.tables.SonhosTransactionsLog
import net.perfectdreams.loritta.cinnamon.pudding.tables.UserLorittaAPITokens
import net.perfectdreams.loritta.cinnamon.pudding.tables.notifications.DailyTaxTaxedUserNotifications
import net.perfectdreams.loritta.cinnamon.pudding.tables.notifications.UserNotifications
import net.perfectdreams.loritta.cinnamon.pudding.tables.transactions.DailyTaxSonhosTransactionsLog
import net.perfectdreams.loritta.cinnamon.pudding.utils.SimpleSonhosTransactionsLogUtils
import net.perfectdreams.loritta.common.utils.TokenType
import net.perfectdreams.loritta.common.utils.TransactionType
import net.perfectdreams.loritta.common.utils.UserPremiumPlans
import net.perfectdreams.loritta.morenitta.LorittaBot
import net.perfectdreams.loritta.serializable.StoredDailyTaxSonhosTransaction
import org.jetbrains.exposed.sql.*
Expand All @@ -24,12 +26,46 @@ import java.time.ZoneOffset
class DailyTaxCollector(val m: LorittaBot) : RunnableCoroutine {
companion object {
private val logger = KotlinLogging.logger {}

/**
* Queries the daily check bypass user IDs list
*
* @return a list containing all the IDs that should not be checked by Loritta
*/
// This is in here because DailyTaxWarner also does the same checks
fun queryDailyBypassList(lorittaBot: LorittaBot): List<Long> {
val bypassDailyTaxUserIds = mutableListOf<Long>()

// lori so cute she doesn't deserve to be hit with the inactive daily tax
bypassDailyTaxUserIds.add(lorittaBot.config.loritta.discord.applicationId)

// Now our precious premium users
val moneySum = Payments.money.sum()

val cheapestPlanWithoutDailyInactivityTaxCost = UserPremiumPlans.getPlansThatDoNotHaveDailyInactivityTax()
.minOf { it.cost }

val usersToBeIgnored = Payments.slice(Payments.userId, moneySum).select {
Payments.expiresAt greaterEq System.currentTimeMillis()
}.groupBy(Payments.userId)
.having { moneySum greaterEq (cheapestPlanWithoutDailyInactivityTaxCost - 10.00).toBigDecimal() } // It is actually 99.99 but shhhhh
.map { it[Payments.userId] }
.toMutableSet()

bypassDailyTaxUserIds.addAll(usersToBeIgnored)

// We expect that all BOT tokens are... well, bots! And they don't be taxed!!
val botTokensIds = UserLorittaAPITokens.selectAll().where {
UserLorittaAPITokens.tokenType eq TokenType.BOT
}.map { it[UserLorittaAPITokens.tokenUserId] }

bypassDailyTaxUserIds.addAll(botTokensIds)

return bypassDailyTaxUserIds
}
}

override suspend fun run() {
// TODO: proper i18n
val i18nContext = m.languageManager.getI18nContextById("pt")

try {
logger.info { "Collecting tax from inactive daily users..." }

Expand All @@ -51,7 +87,12 @@ class DailyTaxCollector(val m: LorittaBot) : RunnableCoroutine {
m.pudding.transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
val notifiedUsers = DailyTaxNotifiedUsers.slice(DailyTaxNotifiedUsers.user).selectAll().map { it[DailyTaxNotifiedUsers.user].value }.toSet()

DailyTaxUtils.getAndProcessInactiveDailyUsers(m.config.loritta.discord.applicationId, 0) { threshold, inactiveDailyUser ->
val bypassDailyTaxUserIds = queryDailyBypassList(m)

DailyTaxUtils.getAndProcessInactiveDailyUsers(
bypassDailyTaxUserIds,
0
) { threshold, inactiveDailyUser ->
if (notifiedUsers.contains(inactiveDailyUser.id)) {
logger.info { "Adding important notification to ${inactiveDailyUser.id} about daily tax taxed" }

Expand Down Expand Up @@ -120,7 +161,9 @@ class DailyTaxCollector(val m: LorittaBot) : RunnableCoroutine {
.toKotlinInstant()

m.pudding.transaction {
DailyTaxUtils.getAndProcessInactiveDailyUsers(m.config.loritta.discord.applicationId, 1) { threshold, inactiveDailyUser ->
val bypassDailyTaxUserIds = queryDailyBypassList(m)

DailyTaxUtils.getAndProcessInactiveDailyUsers(bypassDailyTaxUserIds, 1) { threshold, inactiveDailyUser ->
// Don't warn them about the tax if they were already taxed before
if (inactiveDailyUser.id !in alreadyWarnedThatTheyWereTaxed && inactiveDailyUser.id !in alreadyWarnedThatTheyAreGoingToBeTaxed) {
DailyTaxWarner.processDailyTaxWarning(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package net.perfectdreams.loritta.cinnamon.discord.utils.dailytax

import mu.KotlinLogging
import net.perfectdreams.loritta.cinnamon.pudding.tables.Payments
import net.perfectdreams.loritta.cinnamon.pudding.tables.PendingImportantNotifications
import net.perfectdreams.loritta.common.utils.DailyTaxThresholds
import net.perfectdreams.loritta.common.utils.DailyTaxThresholds.THRESHOLDS
import net.perfectdreams.loritta.common.utils.PendingImportantNotificationState
import net.perfectdreams.loritta.common.utils.UserPremiumPlans
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.sum
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.time.Instant
import java.time.LocalDateTime
Expand All @@ -21,23 +17,14 @@ object DailyTaxUtils {
/**
* Gets and processes inactive daily users
*
* @param lorittaId lori so cute she doesn't deserve to be hit with the inactive daily tax
* @param dayOffset offsets (plusDays) the current day by [dayOffset]
* @param block block that will be executed when a inactive daily user is found
* @param bypassDailyTaxUserIds the IDs of users that can bypass the daily tax
* @param dayOffset offsets (plusDays) the current day by [dayOffset]
* @param block block that will be executed when a inactive daily user is found
*/
fun getAndProcessInactiveDailyUsers(lorittaId: Long, dayOffset: Long, block: (threshold: DailyTaxThresholds.DailyTaxThreshold, inactiveDailyUser: InactiveDailyUser) -> (Unit)) {
val moneySum = Payments.money.sum()

val cheapestPlanWithoutDailyInactivityTaxCost = UserPremiumPlans.getPlansThatDoNotHaveDailyInactivityTax()
.minOf { it.cost }

val usersToBeIgnored = Payments.slice(Payments.userId, moneySum).select {
Payments.expiresAt greaterEq System.currentTimeMillis()
}.groupBy(Payments.userId)
.having { moneySum greaterEq (cheapestPlanWithoutDailyInactivityTaxCost - 10.00).toBigDecimal() } // It is actually 99.99 but shhhhh
.map { it[Payments.userId] }
.toMutableSet()
usersToBeIgnored.add(lorittaId)
fun getAndProcessInactiveDailyUsers(bypassDailyTaxUserIds: List<Long>, dayOffset: Long, block: (threshold: DailyTaxThresholds.DailyTaxThreshold, inactiveDailyUser: InactiveDailyUser) -> (Unit)) {
// This looks weird, but that's because this list is mutated down below
val usersToBeIgnored = mutableListOf<Long>()
usersToBeIgnored.addAll(bypassDailyTaxUserIds)

for (threshold in THRESHOLDS.sortedByDescending { it.minimumSonhosForTrigger }) {
logger.info { "Checking daily inactivity tax threshold $threshold" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import kotlinx.datetime.toJavaInstant
import kotlinx.datetime.toKotlinInstant
import mu.KotlinLogging
import net.perfectdreams.loritta.cinnamon.discord.utils.RunnableCoroutine
import net.perfectdreams.loritta.cinnamon.discord.utils.dailytax.DailyTaxCollector.Companion.queryDailyBypassList
import net.perfectdreams.loritta.cinnamon.pudding.tables.DailyTaxNotifiedUsers
import net.perfectdreams.loritta.cinnamon.pudding.tables.notifications.DailyTaxWarnUserNotifications
import net.perfectdreams.loritta.cinnamon.pudding.tables.notifications.UserNotifications
Expand Down Expand Up @@ -77,7 +78,9 @@ class DailyTaxWarner(val m: LorittaBot) : RunnableCoroutine {
// We need to use Read Commited to avoid "Could not serialize access due to concurrent update"
// This is more "unsafe" because we may make someone be in the negative sonhos, but there isn't another good alterative, so yeah...
m.pudding.transaction(transactionIsolation = Connection.TRANSACTION_READ_COMMITTED) {
DailyTaxUtils.getAndProcessInactiveDailyUsers(m.config.loritta.discord.applicationId, 0) { threshold, inactiveDailyUser ->
val bypassDailyTaxUserIds = queryDailyBypassList(m)

DailyTaxUtils.getAndProcessInactiveDailyUsers(bypassDailyTaxUserIds, 0) { threshold, inactiveDailyUser ->
processDailyTaxWarning(
threshold,
inactiveDailyUser,
Expand Down
Loading

0 comments on commit b636861

Please sign in to comment.