fill welcome plugin

This commit is contained in:
InsanusMokrassar 2022-07-09 23:41:37 +06:00
parent 5861b909cd
commit 84f5b5da75
9 changed files with 266 additions and 13 deletions

View File

@ -25,6 +25,7 @@ dependencies {
implementation libs.plagubot implementation libs.plagubot
implementation project(":introduction") implementation project(":introduction")
implementation project(":welcome")
} }
application { application {

View File

@ -1,12 +1,15 @@
{ {
"botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678", "botToken": "1234567890:ABCDEFGHIJKLMNOP_qrstuvwxyz12345678",
"plugins": [ "plugins": [
"IntroductionPlugin" "dev.inmo.plagubot.plugins.commands.CommandsPlugin",
"IntroductionPlugin",
"WelcomePlugin"
], ],
"introduction": { "introduction": {
"onStartCommandMessage": "Hello World" "onStartCommandMessage": "Hello World"
}, },
"database": { "database": {
"url": "jdbc:sqlite:file:local.db?mode=memory&cache=shared" "url": "jdbc:sqlite:file:local.db"
} }
} }

View File

@ -1,14 +1,16 @@
[versions] [versions]
kotlin = "1.6.21" kotlin = "1.6.21"
plagubot = "1.2.2" plagubot = "1.2.3"
kslog = "0.3.2" kslog = "0.3.2"
plagubot-commands = "0.1.0"
[libraries] [libraries]
kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
plagubot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" } plagubot = { module = "dev.inmo:plagubot.bot", version.ref = "plagubot" }
plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" } plagubot-plugin = { module = "dev.inmo:plagubot.plugin", version.ref = "plagubot" }
plagubot-commands = { module = "dev.inmo:plagubot.plugins.commands", version.ref = "plagubot-commands" }
kslog = { module = "dev.inmo:kslog", version.ref = "kslog" } kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
# Libs for classpath # Libs for classpath

View File

@ -1,3 +1,4 @@
rootProject.name = "tutorial" rootProject.name = "tutorial"
include ":introduction" include ":introduction"
include ":welcome"

View File

@ -23,4 +23,5 @@ dependencies {
implementation libs.kotlin implementation libs.kotlin
api libs.plagubot.plugin api libs.plagubot.plugin
api libs.kslog api libs.kslog
api libs.plagubot.commands
} }

View File

@ -0,0 +1,11 @@
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
import dev.inmo.tgbotapi.types.chat.GroupChat
import dev.inmo.tgbotapi.types.chat.User
suspend fun TelegramBot.userIsAdmin(user: User, chat: GroupChat): Boolean {
val chatAdmins = getChatAdministrators(chat)
val chatAdminsIds = chatAdmins.map { adminMember -> adminMember.user.id }
return user.id in chatAdminsIds
}

View File

@ -1,17 +1,40 @@
import db.WelcomeTable
import dev.inmo.kslog.common.logger import dev.inmo.kslog.common.logger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.tgbotapi.extensions.api.send.reply import dev.inmo.plagubot.plugins.commands.full
import dev.inmo.tgbotapi.extensions.api.send.sendMessage import dev.inmo.tgbotapi.bot.exceptions.RequestException
import dev.inmo.tgbotapi.extensions.api.answers.answer
import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
import dev.inmo.tgbotapi.extensions.api.edit.edit
import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitContentMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onMyChatMemberUpdated import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
import dev.inmo.tgbotapi.types.chat.PrivateChat import dev.inmo.tgbotapi.extensions.behaviour_builder.oneOf
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.*
import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.formatting.*
import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.MilliSeconds
import dev.inmo.tgbotapi.types.chat.GroupChat
import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.message.abstracts.CommonGroupContentMessage
import dev.inmo.tgbotapi.types.message.content.MessageContent
import dev.inmo.tgbotapi.types.message.content.TextContent
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import model.ChatSettings
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
import org.koin.core.qualifier.named
/** /**
* This is template of plugin with preset [log]ger, [Config] and template configurations of [setupDI] and [setupBotPlugin]. * This is template of plugin with preset [log]ger, [Config] and template configurations of [setupDI] and [setupBotPlugin].
@ -30,27 +53,182 @@ class WelcomePlugin : Plugin {
* See realization of [setupDI] to get know how this class will be deserialized from global config * See realization of [setupDI] to get know how this class will be deserialized from global config
* *
* See realization of [setupBotPlugin] to get know how to get access to this class * See realization of [setupBotPlugin] to get know how to get access to this class
*
* @param recheckOfAdmin This parameter will be used before setup of
*/ */
@Serializable @Serializable
private class Config( private class Config(
val recheckOfAdmin: MilliSeconds = 60L
) )
/** /**
* DI configuration of current plugin. Here we are decoding [Config] and put it into [Module] receiver * DI configuration of current plugin. Here we are decoding [Config] and put it into [Module] receiver
*/ */
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { get<Json>().decodeFromJsonElement(Config.serializer(), params[pluginConfigSectionName] ?: return@single null) } single { get<Json>().decodeFromJsonElement(Config.serializer(), params[pluginConfigSectionName] ?: return@single Config()) }
single { WelcomeTable(database) }
single(named("welcome")) { BotCommand("welcome", "Use to setup welcome message").full(BotCommandScope.AllChatAdministrators) }
}
private suspend fun BehaviourContext.handleWelcomeCommand(
welcomeTable: WelcomeTable,
config: Config,
groupMessage: CommonGroupContentMessage<MessageContent>
) {
val user = groupMessage.user
if (userIsAdmin(user, groupMessage.chat)) {
val cancelData = "cancel_${groupMessage.chat.id}"
val unsetData = "unset_${groupMessage.chat.id}"
val sentMessage = sendMessage(
user,
buildEntities {
regular("Ok, send me the message which should be used as welcome message for chat ")
underline(groupMessage.chat.title)
},
replyMarkup = inlineKeyboard {
row {
dataButton("Unset", unsetData)
dataButton("Cancel", cancelData)
}
}
)
oneOf(
async {
val query = waitMessageDataCallbackQuery().filter {
it.data == unsetData
&& it.message.chat.id == sentMessage.chat.id
&& it.message.messageId == sentMessage.messageId
}.first()
if (welcomeTable.unset(groupMessage.chat.id)) {
edit(
sentMessage,
buildEntities {
regular("Welcome message has been removed for chat ")
underline(groupMessage.chat.title)
}
)
} else {
edit(
sentMessage,
buildEntities {
regular("Something went wrong on welcome message unsetting for chat ")
underline(groupMessage.chat.title)
}
)
}
answer(query)
},
async {
val query = waitMessageDataCallbackQuery().filter {
it.data == cancelData
&& it.message.chat.id == sentMessage.chat.id
&& it.message.messageId == sentMessage.messageId
}.first()
edit(
sentMessage,
buildEntities {
regular("You have cancelled change of welcome message for chat ")
underline(groupMessage.chat.title)
}
)
answer(query)
},
async {
val message = waitContentMessage().filter {
it.chat.id == sentMessage.chat.id
}.first()
val success = welcomeTable.set(
ChatSettings(
groupMessage.chat.id,
message.chat.id,
message.messageId
)
)
if (success) {
reply(
message,
buildEntities {
regular("Welcome message has been changed for chat ")
underline(groupMessage.chat.title)
regular(".\n\n")
bold("Please, do not delete this message if you want it to work and don't stop this bot to keep welcome message works right")
}
)
} else {
reply(
message,
buildEntities {
regular("Something went wrong on welcome message changing for chat ")
underline(groupMessage.chat.title)
}
)
}
},
async {
while (isActive) {
delay(config.recheckOfAdmin)
if (!userIsAdmin(user, groupMessage.chat)) {
edit(sentMessage, "Sorry, but you are not admin in chat ${groupMessage.chat.title} more")
break
}
}
}
)
}
} }
/** /**
* Final configuration of bot. Here we are getting [Config] from [koin] * Final configuration of bot. Here we are getting [Config] from [koin]
*/ */
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
val config = koin.getOrNull<Config>() val config = koin.get<Config>()
if (config == null) { val welcomeTable = koin.get<WelcomeTable>()
log.w("Plugin has been disabled due to absence of \"$pluginConfigSectionName\" field in config or some error during configuration loading")
return onCommand(
"welcome",
initialFilter = {
it.chat is GroupChat
}
) {
it.whenCommonGroupContentMessage { groupMessage ->
launch {
handleWelcomeCommand(welcomeTable, config, groupMessage)
}
}
}
onNewChatMembers {
val chatSettings = welcomeTable.get(it.chat.id)
if (chatSettings == null) {
return@onNewChatMembers
}
try {
copyMessage(
it.chat.id,
chatSettings.sourceChatId,
chatSettings.sourceMessageId
)
} catch (e: RequestException) {
welcomeTable.unset(it.chat.id)
}
}
allUpdatesFlow.subscribeSafelyWithoutExceptions(scope) {
println(it)
} }
} }

View File

@ -0,0 +1,44 @@
package db
import dev.inmo.micro_utils.repos.exposed.ExposedRepo
import dev.inmo.micro_utils.repos.exposed.initTable
import dev.inmo.tgbotapi.types.ChatId
import model.ChatSettings
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.transactions.transaction
internal class WelcomeTable(
override val database: Database
) : Table("welcome"), ExposedRepo {
val targetChatIdColumn = long("targetChatId").uniqueIndex()
val sourceChatIdColumn = long("sourceChatId")
val sourceMessageIdColumn = long("sourceMessageId")
override val primaryKey: PrimaryKey = PrimaryKey(targetChatIdColumn)
init {
initTable()
}
fun get(chatId: ChatId): ChatSettings? = transaction(database) {
select { targetChatIdColumn.eq(chatId.chatId) }.limit(1).firstOrNull() ?.let {
ChatSettings(
ChatId(it[targetChatIdColumn]),
ChatId(it[sourceChatIdColumn]),
it[sourceMessageIdColumn]
)
}
}
fun set(chatSettings: ChatSettings): Boolean = transaction(database) {
deleteWhere { targetChatIdColumn.eq(chatSettings.targetChatId.chatId) }
insert {
it[targetChatIdColumn] = chatSettings.targetChatId.chatId
it[sourceChatIdColumn] = chatSettings.sourceChatId.chatId
it[sourceMessageIdColumn] = chatSettings.sourceMessageId
}.insertedCount > 0
}
fun unset(chatId: ChatId): Boolean = transaction(database) {
deleteWhere { targetChatIdColumn.eq(chatId.chatId) } > 0
}
}

View File

@ -0,0 +1,12 @@
package model
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier
import kotlinx.serialization.Serializable
@Serializable
internal data class ChatSettings(
val targetChatId: ChatId,
val sourceChatId: ChatId,
val sourceMessageId: MessageIdentifier
)