Skip to content

Commit

Permalink
v1.2-beta
Browse files Browse the repository at this point in the history
v1.2-beta release
  • Loading branch information
y9san9 authored Apr 4, 2021
2 parents a06f7c2 + 0222b9b commit cca1438
Show file tree
Hide file tree
Showing 95 changed files with 1,241 additions and 278 deletions.
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FROM openjdk:8-jre-alpine3.9

CMD ["./gradlew", "fatJar"]

COPY build/libs/app.jar /app.jar

CMD ["java", "-jar", "/app.jar"]
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ There is no open source telegram bot that can purely raffle prizes, so I decided

The official bot is hosted at [@secure_prize_bot](https://t.me/secure_prize_bot)

# Running
# Running
Dockerfile available for running, please check Dockerfile and prizebot.env

To run this bot by yourself provide the bot token with `BOT_TOKEN` environment variable.

### Database
Expand Down Expand Up @@ -38,6 +40,7 @@ title | TEXT NOT NULL
participateButton | TEXT NOT NULL
languageCode | TEXT DEFAULT NULL
winnerId | BIGINT DEFAULT NULL
raffleDate | TEXT DEFAULT NULL

Table `giveaways_active_messages`:

Expand All @@ -47,6 +50,13 @@ rowId | BIGINT AUTOINCREMENT
giveawayId | BIGINT NOT NULL
inlineMessageId | TEXT NOT NULL

Table `language_codes`:

field | type
---|---
userId | BIGINT NOT NULL
languageCode | TEXT NOT NULL

# Licence
[MIT](https://github.com/y9san9/prizebot/LICENCE)

Expand Down
1 change: 1 addition & 0 deletions bot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ dependencies {
implementation(telegram)
implementation(kds)
implementation(random)
implementation(kotlin("script-runtime"))
}
20 changes: 16 additions & 4 deletions bot/src/main/kotlin/me/y9san9/prizebot/Prizebot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@ package me.y9san9.prizebot

import dev.inmo.micro_utils.coroutines.subscribeSafely
import dev.inmo.tgbotapi.bot.Ktor.telegramBot
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.utils.updates.retrieving.longPolling
import dev.inmo.tgbotapi.types.message.abstracts.PrivateContentMessage
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import me.y9san9.fsm.FSM
import me.y9san9.fsm.statesOf
import me.y9san9.prizebot.actors.giveaway.AutoRaffleActor
import me.y9san9.prizebot.actors.storage.giveaways_active_messages_storage.GiveawaysActiveMessagesStorage
import me.y9san9.prizebot.actors.storage.giveaways_storage.GiveawayStorage
import me.y9san9.prizebot.actors.storage.language_codes_storage.LanguageCodesStorage
import me.y9san9.prizebot.actors.storage.participants_storage.ParticipantsStorage
import me.y9san9.prizebot.actors.storage.states_storage.PrizebotFSMStorage
import me.y9san9.prizebot.handlers.callback_queries.CallbackQueryHandler
import me.y9san9.prizebot.handlers.choosen_inline_result.ChosenInlineResultHandler
import me.y9san9.prizebot.handlers.inline_queries.InlineQueryHandler
import me.y9san9.prizebot.handlers.private_messages.fsm.prizebotPrivateMessages
import me.y9san9.prizebot.handlers.private_messages.fsm.states.MainState
import me.y9san9.prizebot.handlers.private_messages.fsm.states.giveaway.ParticipateTextInputState
import me.y9san9.prizebot.handlers.private_messages.fsm.states.giveaway.TitleInputState
import me.y9san9.prizebot.handlers.private_messages.fsm.states.statesSerializers
import me.y9san9.prizebot.models.DatabaseConfig
import me.y9san9.prizebot.models.di.PrizebotDI
import me.y9san9.prizebot.extensions.telegram.PrizebotPrivateMessageUpdate
import me.y9san9.prizebot.handlers.private_messages.fsm.states.giveaway.*
import me.y9san9.telegram.updates.CallbackQueryUpdate
import me.y9san9.telegram.updates.ChosenInlineResultUpdate
import me.y9san9.telegram.updates.InlineQueryUpdate
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.transactions.transaction


class Prizebot (
Expand All @@ -42,8 +46,10 @@ class Prizebot (
val di = PrizebotDI (
giveawaysStorage = GiveawayStorage(database),
participantsStorage = ParticipantsStorage(database),
giveawaysActiveMessagesStorage = GiveawaysActiveMessagesStorage(database)
giveawaysActiveMessagesStorage = GiveawaysActiveMessagesStorage(database),
languageCodesStorage = LanguageCodesStorage(database)
)
scheduleRaffles(bot, di)

val messages = messageFlow
.mapNotNull { it.data as? PrivateContentMessage<*> }
Expand All @@ -64,11 +70,17 @@ class Prizebot (
.subscribeSafely(scope, ::logException, ChosenInlineResultHandler::handle)
}

private fun scheduleRaffles(bot: TelegramBot, di: PrizebotDI) = scope.launch {
AutoRaffleActor.scheduleAll(bot, di)
}

private fun createFSM(events: Flow<PrizebotPrivateMessageUpdate>) = FSM.prizebotPrivateMessages (
events,
states = statesOf (
initial = MainState,
TitleInputState, ParticipateTextInputState
TitleInputState, ParticipateTextInputState,
RaffleDateInputState, TimezoneInputState,
CustomTimezoneInputState
),
storage = storage,
scope = scope
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package me.y9san9.prizebot.actors.giveaway

import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.api.send.sendMessage
import dev.inmo.tgbotapi.types.ChatId
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import me.y9san9.prizebot.actors.storage.giveaways_active_messages_storage.GiveawaysActiveMessagesStorage
import me.y9san9.prizebot.actors.storage.giveaways_storage.ActiveGiveaway
import me.y9san9.prizebot.actors.storage.giveaways_storage.Giveaway
import me.y9san9.prizebot.actors.storage.giveaways_storage.GiveawaysStorage
import me.y9san9.prizebot.actors.storage.language_codes_storage.LanguageCodesStorage
import me.y9san9.prizebot.actors.storage.participants_storage.ParticipantsStorage
import me.y9san9.prizebot.actors.telegram.updater.GiveawayActiveMessagesUpdater
import me.y9san9.prizebot.resources.locales.Locale
import me.y9san9.telegram.updates.hierarchies.DIBotUpdate
import java.time.Instant


object AutoRaffleActor : CoroutineScope {

/**
* Map of giveaway id to schedule job
*/
private val scheduled = mutableMapOf<Long, Job>()

private val scheduledMutex = Mutex()

suspend fun <T> scheduleAll (
bot: TelegramBot,
di: T
) where T : ParticipantsStorage, T : GiveawaysActiveMessagesStorage,
T : GiveawaysStorage, T : LanguageCodesStorage =
di.getAllGiveaways()
.filterIsInstance<ActiveGiveaway>()
.forEach { schedule(bot, it, di) }

suspend fun <T> schedule (
bot: TelegramBot,
giveaway: ActiveGiveaway,
di: T
) where T : GiveawaysActiveMessagesStorage, T : ParticipantsStorage,
T : GiveawaysStorage, T : LanguageCodesStorage = scheduledMutex.withLock {
val scope = this

if(giveaway.raffleDate != null && giveaway.id !in scheduled) {
scheduled[giveaway.id] = scope.launch {
val delay = giveaway.raffleDate.toInstant().toEpochMilli() - Instant.now().toEpochMilli()
delay(delay.takeIf { it > 0 } ?: 0)

scheduledMutex.withLock {
if (giveaway.id in scheduled && di.getGiveawayById(giveaway.id) != null) {
scheduled.remove(giveaway.id)
handleRaffleResult(bot, di, giveaway, RaffleActor.raffle(giveaway.id, di))
}
}
}
}
}

private suspend fun <T> handleRaffleResult (
bot: TelegramBot, di: T, giveaway: Giveaway, successRaffle: Boolean
) where T : GiveawaysActiveMessagesStorage, T : ParticipantsStorage,
T : GiveawaysStorage, T : LanguageCodesStorage {
if (successRaffle) {
GiveawayActiveMessagesUpdater.update(getEvent(bot, di), giveaway.id)
} else {
di.removeRaffleDate(giveaway.id)
val locale = Locale.with(di.getLanguageCode(giveaway.ownerId))
bot.sendMessage(ChatId(giveaway.ownerId), locale.cannotRaffleGiveaway(giveaway.title))
}
}


private fun <T> getEvent(bot: TelegramBot, di: T) = object : DIBotUpdate<T> {
override val bot = bot
override val di = di
}

override val coroutineContext = GlobalScope.coroutineContext + Job()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package me.y9san9.prizebot.actors.giveaway

import me.y9san9.fsm.FSMStateResult
import me.y9san9.fsm.stateResult
import me.y9san9.prizebot.actors.telegram.sender.GiveawayCreatedSender
import me.y9san9.prizebot.extensions.telegram.PrizebotPrivateMessageUpdate
import me.y9san9.prizebot.handlers.private_messages.fsm.states.MainState
import java.time.OffsetDateTime


object CreateGiveawayActor {
suspend fun create (
update: PrizebotPrivateMessageUpdate,
title: String,
participateText: String,
raffleDate: OffsetDateTime?
): FSMStateResult<*> {

val giveaway = update.di.saveGiveaway (
update.chatId, title, participateText,
languageCode = update.di.getLanguageCode(update.chatId) ?: update.languageCode,
raffleDate
)

GiveawayCreatedSender.send(update, giveaway)

AutoRaffleActor.schedule(update.bot, giveaway, update.di)

return stateResult(MainState)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.y9san9.prizebot.actors.giveaway

import me.y9san9.prizebot.actors.storage.giveaways_storage.GiveawaysStorage
import me.y9san9.prizebot.actors.storage.participants_storage.ParticipantsStorage
import me.y9san9.random.extensions.shuffledRandomOrg


object RaffleActor {
suspend fun <T> raffle (
giveawayId: Long,
di: T
): Boolean where T : ParticipantsStorage, T : GiveawaysStorage {
val winner = chooseWinner(giveawayId, di) ?: return false
di.finishGiveaway(giveawayId, winner)
return true
}

private suspend fun <T> chooseWinner (
giveawayId: Long,
di: T
) where T : ParticipantsStorage =
di.getParticipantsIds(giveawayId)
.shuffledRandomOrg()
.firstOrNull()
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import dev.inmo.tgbotapi.types.InlineMessageIdentifier
import me.y9san9.prizebot.actors.storage.giveaways_active_messages_storage.TableGiveawaysActiveMessagesStorage.Storage.GIVEAWAY_ID
import me.y9san9.prizebot.actors.storage.giveaways_active_messages_storage.TableGiveawaysActiveMessagesStorage.Storage.MESSAGE_ID
import me.y9san9.prizebot.actors.storage.giveaways_active_messages_storage.TableGiveawaysActiveMessagesStorage.Storage.ROW_ID
import me.y9san9.prizebot.extensions.unit
import me.y9san9.prizebot.extensions.any.unit
import me.y9san9.prizebot.resources.ACTIVE_MESSAGES_LIMIT
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package me.y9san9.prizebot.actors.storage.giveaways_storage

import kotlinx.serialization.Serializable
import me.y9san9.prizebot.extensions.offset_date_time.OffsetDateTimeSerializer
import me.y9san9.prizebot.extensions.time.Milliseconds
import me.y9san9.prizebot.resources.locales.Locale
import java.time.OffsetDateTime


@Serializable
Expand All @@ -11,6 +14,8 @@ sealed class Giveaway {
abstract val title: String
abstract val participateText: String
abstract val languageCode: String?
@Serializable(with = OffsetDateTimeSerializer::class)
abstract val raffleDate: OffsetDateTime?
}

val Giveaway.locale get() = Locale.with(languageCode)
Expand All @@ -21,7 +26,9 @@ data class ActiveGiveaway (
override val ownerId: Long,
override val title: String,
override val participateText: String,
override val languageCode: String?
override val languageCode: String?,
@Serializable(with = OffsetDateTimeSerializer::class)
override val raffleDate: OffsetDateTime?
) : Giveaway()

@Serializable
Expand All @@ -31,5 +38,7 @@ data class FinishedGiveaway (
override val title: String,
override val participateText: String,
override val languageCode: String?,
@Serializable(with = OffsetDateTimeSerializer::class)
override val raffleDate: OffsetDateTime?,
val winnerId: Long
) : Giveaway()
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
package me.y9san9.prizebot.actors.storage.giveaways_storage

import org.jetbrains.exposed.sql.Database
import java.time.OffsetDateTime


interface GiveawaysStorage {
fun getGiveawayById(id: Long): Giveaway?
fun saveGiveaway(ownerId: Long, title: String, participateButton: String, languageCode: String?): Giveaway
fun saveGiveaway (
ownerId: Long,
title: String,
participateButton: String,
languageCode: String?,
raffleDate: OffsetDateTime?
): ActiveGiveaway
fun finishGiveaway(giveawayId: Long, winnerId: Long)
fun removeRaffleDate(giveawayId: Long)
fun getUserGiveaways(ownerId: Long, count: Int = 20, offset: Long = 0): List<Giveaway>
fun getAllGiveaways(): List<Giveaway>
fun deleteGiveaway(id: Long)
}

Expand Down
Loading

0 comments on commit cca1438

Please sign in to comment.