Compare commits

...

3 Commits

Author SHA1 Message Date
959b497280 upgrades 2022-05-22 12:37:13 +06:00
fb9933a1de fixes 2022-05-22 12:05:24 +06:00
012922cd0e update dependencies 2022-05-22 11:57:10 +06:00
3 changed files with 106 additions and 61 deletions

View File

@ -7,10 +7,10 @@ kotlin.incremental=true
kotlin_version=1.6.21 kotlin_version=1.6.21
kotlin_coroutines_version=1.6.1 kotlin_coroutines_version=1.6.1
kotlin_serialisation_runtime_version=1.3.3 kotlin_serialisation_runtime_version=1.3.3
plagubot_version=1.0.1 plagubot_version=1.1.0
micro_utils_version=0.10.5 micro_utils_version=0.10.5
tgbotapi_libraries_version=0.1.0 tgbotapi_libraries_version=0.2.0
project_group=dev.inmo project_group=dev.inmo
project_version=0.1.6 project_version=0.1.6

View File

@ -15,9 +15,7 @@ import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onComman
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onNewChatMembers import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onNewChatMembers
import dev.inmo.tgbotapi.extensions.utils.* import dev.inmo.tgbotapi.extensions.utils.*
import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams import dev.inmo.tgbotapi.extensions.utils.extensions.parseCommandsWithParams
import dev.inmo.tgbotapi.extensions.utils.extensions.sourceChat
import dev.inmo.tgbotapi.libraries.cache.admins.* import dev.inmo.tgbotapi.libraries.cache.admins.*
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.chat.* import dev.inmo.tgbotapi.types.chat.*
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -129,7 +127,7 @@ class CaptchaBotPlugin : Plugin {
val defaultChatPermissions = (getChat(it.chat) as ExtendedGroupChat).permissions val defaultChatPermissions = (getChat(it.chat) as ExtendedGroupChat).permissions
with (settings.captchaProvider) { with (settings.captchaProvider) {
doAction(it.date, chat, newUsers, defaultChatPermissions) doAction(it.date, chat, newUsers, defaultChatPermissions, adminsAPI)
} }
} }

View File

@ -9,17 +9,15 @@ import dev.inmo.tgbotapi.extensions.api.answers.answerCallbackQuery
import dev.inmo.tgbotapi.extensions.api.chat.members.* import dev.inmo.tgbotapi.extensions.api.chat.members.*
import dev.inmo.tgbotapi.extensions.api.deleteMessage import dev.inmo.tgbotapi.extensions.api.deleteMessage
import dev.inmo.tgbotapi.extensions.api.edit.reply_markup.editMessageReplyMarkup import dev.inmo.tgbotapi.extensions.api.edit.reply_markup.editMessageReplyMarkup
import dev.inmo.tgbotapi.extensions.api.send.sendDice import dev.inmo.tgbotapi.extensions.api.send.*
import dev.inmo.tgbotapi.extensions.api.send.sendTextMessage
import dev.inmo.tgbotapi.extensions.behaviour_builder.* import dev.inmo.tgbotapi.extensions.behaviour_builder.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitDataCallbackQuery
import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery import dev.inmo.tgbotapi.extensions.behaviour_builder.expectations.waitMessageDataCallbackQuery
import dev.inmo.tgbotapi.extensions.utils.asSlotMachineReelImage import dev.inmo.tgbotapi.extensions.utils.asSlotMachineReelImage
import dev.inmo.tgbotapi.extensions.utils.calculateSlotMachineResult import dev.inmo.tgbotapi.extensions.utils.calculateSlotMachineResult
import dev.inmo.tgbotapi.extensions.utils.extensions.raw.message
import dev.inmo.tgbotapi.extensions.utils.formatting.* import dev.inmo.tgbotapi.extensions.utils.formatting.*
import dev.inmo.tgbotapi.extensions.utils.shortcuts.executeUnsafe import dev.inmo.tgbotapi.extensions.utils.shortcuts.executeUnsafe
import dev.inmo.tgbotapi.extensions.utils.types.buttons.InlineKeyboardMarkup import dev.inmo.tgbotapi.extensions.utils.types.buttons.*
import dev.inmo.tgbotapi.libraries.cache.admins.AdminsCacheAPI
import dev.inmo.tgbotapi.requests.DeleteMessage import dev.inmo.tgbotapi.requests.DeleteMessage
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton import dev.inmo.tgbotapi.types.buttons.InlineKeyboardButtons.CallbackDataInlineKeyboardButton
@ -28,10 +26,10 @@ import dev.inmo.tgbotapi.types.chat.*
import dev.inmo.tgbotapi.types.chat.User import dev.inmo.tgbotapi.types.chat.User
import dev.inmo.tgbotapi.types.dice.SlotMachineDiceAnimationType import dev.inmo.tgbotapi.types.dice.SlotMachineDiceAnimationType
import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.types.message.textsources.mention
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.toList import kotlinx.coroutines.channels.toList
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -44,10 +42,41 @@ sealed class CaptchaProvider {
eventDateTime: DateTime, eventDateTime: DateTime,
chat: GroupChat, chat: GroupChat,
newUsers: List<User>, newUsers: List<User>,
leftRestrictionsPermissions: ChatPermissions leftRestrictionsPermissions: ChatPermissions,
adminsApi: AdminsCacheAPI?
) )
} }
private const val cancelData = "cancel"
private fun EntitiesBuilder.mention(user: User, defaultName: String = "User"): EntitiesBuilder {
return mention(
listOfNotNull(
user.lastName.takeIf { it.isNotBlank() }, user.firstName.takeIf { it.isNotBlank() }
).takeIf {
it.isNotEmpty()
} ?.joinToString(" ") ?: defaultName,
user
)
}
private suspend fun BehaviourContext.sendAdminCanceledMessage(
chat: Chat,
captchaSolver: User,
admin: User
) {
safelyWithoutExceptions {
sendTextMessage(
chat,
buildEntities {
mention(admin, "Admin")
regular(" cancelled captcha for ")
mention(captchaSolver)
}
)
}
}
private suspend fun BehaviourContext.banUser( private suspend fun BehaviourContext.banUser(
chat: PublicChat, chat: PublicChat,
user: User, user: User,
@ -57,13 +86,7 @@ private suspend fun BehaviourContext.banUser(
sendTextMessage( sendTextMessage(
chat, chat,
buildEntities(" ") { buildEntities(" ") {
user.mention( mention(user)
listOfNotNull(
user.lastName.takeIf { it.isNotBlank() }, user.firstName.takeIf { it.isNotBlank() }
).takeIf {
it.isNotEmpty()
} ?.joinToString(" ") ?: "User"
)
+"failed captcha" +"failed captcha"
} }
) )
@ -89,17 +112,18 @@ data class SlotMachineCaptchaProvider(
eventDateTime: DateTime, eventDateTime: DateTime,
chat: GroupChat, chat: GroupChat,
newUsers: List<User>, newUsers: List<User>,
leftRestrictionsPermissions: ChatPermissions leftRestrictionsPermissions: ChatPermissions,
adminsApi: AdminsCacheAPI?
) { ) {
val userBanDateTime = eventDateTime + checkTimeSpan val userBanDateTime = eventDateTime + checkTimeSpan
val authorized = Channel<User>(newUsers.size) val authorized = Channel<User>(newUsers.size)
val messagesToDelete = Channel<Message>(Channel.UNLIMITED) val messagesToDelete = Channel<Message>(Channel.UNLIMITED)
val subContexts = newUsers.map { val subContexts = newUsers.map { user ->
createSubContextAndDoWithUpdatesFilter (stopOnCompletion = false) { createSubContextAndDoWithUpdatesFilter (stopOnCompletion = false) {
val sentMessage = sendTextMessage( val sentMessage = sendTextMessage(
chat, chat,
buildEntities { buildEntities {
+it.mention(it.firstName) mention(user)
regular(", $captchaText") regular(", $captchaText")
} }
).also { messagesToDelete.send(it) } ).also { messagesToDelete.send(it) }
@ -119,9 +143,7 @@ data class SlotMachineCaptchaProvider(
launch { launch {
val clicked = arrayOf<String?>(null, null, null) val clicked = arrayOf<String?>(null, null, null)
while (leftToClick.isNotEmpty()) { while (leftToClick.isNotEmpty()) {
val userClicked = waitMessageDataCallbackQuery { val userClicked = waitMessageDataCallbackQuery().filter { it.user.id == user.id && it.message.messageId == sentDice.messageId }.first()
if (user.id == it.id && this.message.messageId == sentDice.messageId) this else null
}.first()
if (userClicked.data == leftToClick.first()) { if (userClicked.data == leftToClick.first()) {
clicked[3 - leftToClick.size] = leftToClick.removeAt(0) clicked[3 - leftToClick.size] = leftToClick.removeAt(0)
if (clicked.contains(null)) { if (clicked.contains(null)) {
@ -136,12 +158,12 @@ data class SlotMachineCaptchaProvider(
safelyWithoutExceptions { answerCallbackQuery(userClicked, "Nope") } safelyWithoutExceptions { answerCallbackQuery(userClicked, "Nope") }
} }
} }
authorized.send(it) authorized.send(user)
safelyWithoutExceptions { restrictChatMember(chat, it, permissions = leftRestrictionsPermissions) } safelyWithoutExceptions { restrictChatMember(chat, user, permissions = leftRestrictionsPermissions) }
stop() stop()
} }
this to it this to user
} }
} }
@ -179,22 +201,30 @@ data class SimpleCaptchaProvider(
eventDateTime: DateTime, eventDateTime: DateTime,
chat: GroupChat, chat: GroupChat,
newUsers: List<User>, newUsers: List<User>,
leftRestrictionsPermissions: ChatPermissions leftRestrictionsPermissions: ChatPermissions,
adminsApi: AdminsCacheAPI?
) { ) {
val userBanDateTime = eventDateTime + checkTimeSpan val userBanDateTime = eventDateTime + checkTimeSpan
newUsers.map { newUsers.map { user ->
launchSafelyWithoutExceptions { launchSafelyWithoutExceptions {
createSubContext(this).doInContext(stopOnCompletion = false) { createSubContext(this).doInContext(stopOnCompletion = false) {
val callbackData = uuid4().toString() val callbackData = uuid4().toString()
val sentMessage = sendTextMessage( val sentMessage = sendTextMessage(
chat, chat,
buildEntities { buildEntities {
+it.mention(it.firstName) mention(user)
regular(", $captchaText") regular(", $captchaText")
}, },
replyMarkup = InlineKeyboardMarkup( replyMarkup = inlineKeyboard {
CallbackDataInlineKeyboardButton(buttonText, callbackData) row {
) dataButton(buttonText, callbackData)
}
if (adminsApi != null) {
row {
dataButton("Cancel (Admins only)", cancelData)
}
}
}
) )
suspend fun removeRedundantMessages() { suspend fun removeRedundantMessages() {
@ -204,14 +234,22 @@ data class SimpleCaptchaProvider(
} }
val job = launchSafely { val job = launchSafely {
waitMessageDataCallbackQuery ( waitMessageDataCallbackQuery().filter { query ->
filter = { query -> val baseCheck = query.message.messageId == sentMessage.messageId
query.user.id == it.id && query.data == callbackData && query.message.messageId == sentMessage.messageId val userAnswered = query.user.id == user.id && query.data == callbackData
val adminCanceled = (query.data == cancelData && (adminsApi ?.isAdmin(sentMessage.chat.id, query.user.id)) == true)
if (baseCheck && adminCanceled) {
sendAdminCanceledMessage(
sentMessage.chat,
user,
query.user
)
} }
).first() baseCheck && (adminCanceled || userAnswered)
}.first()
removeRedundantMessages() removeRedundantMessages()
safelyWithoutExceptions { restrictChatMember(chat, it, permissions = leftRestrictionsPermissions) } safelyWithoutExceptions { restrictChatMember(chat, user, permissions = leftRestrictionsPermissions) }
stop() stop()
} }
@ -220,7 +258,7 @@ data class SimpleCaptchaProvider(
if (job.isActive) { if (job.isActive) {
job.cancel() job.cancel()
if (kick) { if (kick) {
banUser(chat, it, leftRestrictionsPermissions) banUser(chat, user, leftRestrictionsPermissions)
} }
} }
stop() stop()
@ -288,7 +326,8 @@ data class ExpressionCaptchaProvider(
eventDateTime: DateTime, eventDateTime: DateTime,
chat: GroupChat, chat: GroupChat,
newUsers: List<User>, newUsers: List<User>,
leftRestrictionsPermissions: ChatPermissions leftRestrictionsPermissions: ChatPermissions,
adminsApi: AdminsCacheAPI?
) { ) {
val userBanDateTime = eventDateTime + checkTimeSpan val userBanDateTime = eventDateTime + checkTimeSpan
newUsers.map { user -> newUsers.map { user ->
@ -308,15 +347,20 @@ data class ExpressionCaptchaProvider(
val sentMessage = sendTextMessage( val sentMessage = sendTextMessage(
chat, chat,
buildEntities { buildEntities {
+user.mention(user.firstName) mention(user)
regular(", $captchaText ") regular(", $captchaText ")
bold(callbackData.second) bold(callbackData.second)
}, },
replyMarkup = dev.inmo.tgbotapi.types.buttons.InlineKeyboardMarkup( replyMarkup = inlineKeyboard {
answers.map { answers.map {
CallbackDataInlineKeyboardButton(it.toString(), it.toString()) CallbackDataInlineKeyboardButton(it.toString(), it.toString())
}.chunked(3) }.chunked(3).forEach(::add)
) if (adminsApi != null) {
row {
dataButton("Cancel (Admins only)", cancelData)
}
}
}
) )
suspend fun removeRedundantMessages() { suspend fun removeRedundantMessages() {
@ -353,25 +397,28 @@ data class ExpressionCaptchaProvider(
} }
var leftAttempts = attempts var leftAttempts = attempts
waitMessageDataCallbackQuery ( waitMessageDataCallbackQuery().takeWhile { leftAttempts > 0 }.filter { query ->
filter = { query -> val baseCheck = query.message.messageId == sentMessage.messageId
query.user.id == user.id && query.message.messageId == sentMessage.messageId val dataCorrect = (query.user.id == user.id && query.data == correctAnswer)
} val adminCanceled = (query.data == cancelData && (adminsApi ?.isAdmin(sentMessage.chat.id, query.user.id)) == true)
) { baseCheck && if (dataCorrect || adminCanceled) {
if (this.data != correctAnswer) {
leftAttempts--
if (leftAttempts < 1) {
this
} else {
answerCallbackQuery(this@waitMessageDataCallbackQuery, leftRetriesText + leftAttempts)
null
}
} else {
this
}
}.take(1)
banJob.cancel() banJob.cancel()
if (adminCanceled) {
sendAdminCanceledMessage(
sentMessage.chat,
user,
query.user
)
}
true
} else {
leftAttempts--
if (leftAttempts > 0) {
answerCallbackQuery(query, leftRetriesText + leftAttempts)
}
false
}
}.firstOrNull()
callback(leftAttempts > 0) callback(leftAttempts > 0)
} }