diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheSettingsAPI.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheSettingsAPI.kt index e960565..5bd1cad 100644 --- a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheSettingsAPI.kt +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheSettingsAPI.kt @@ -15,8 +15,11 @@ data class AdminsCacheSettings( */ val disableRequestsRefreshMode: Boolean = false ) { - val refreshOnRequests: Boolean + val refreshOnCacheCalls: Boolean get() = !disableRequestsRefreshMode + @Deprecated("Renamed", ReplaceWith("refreshOnCacheCalls")) + val refreshOnRequests: Boolean + get() = refreshOnCacheCalls } interface AdminsCacheSettingsAPI { diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsChangesListener.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsChangesListener.kt new file mode 100644 index 0000000..1688df0 --- /dev/null +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsChangesListener.kt @@ -0,0 +1,44 @@ +package dev.inmo.tgbotapi.libraries.cache.admins + +import dev.inmo.tgbotapi.extensions.api.bot.getMe +import dev.inmo.tgbotapi.extensions.behaviour_builder.* +import dev.inmo.tgbotapi.extensions.behaviour_builder.filters.ChatMemberUpdatedFilterByChat +import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onChatMemberUpdated +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.ByChatChatMemberUpdatedMarkerFactory +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.marker_factories.MarkerFactory +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember +import dev.inmo.tgbotapi.types.chat.member.ChatMemberUpdated +import dev.inmo.tgbotapi.types.update.abstracts.Update +import kotlinx.coroutines.Job + +suspend fun BehaviourContext.activateAdminsChangesListening( + repo: DefaultAdminsCacheAPIRepo, + initialFilter: SimpleFilter? = null, + markerFactory: MarkerFactory = ByChatChatMemberUpdatedMarkerFactory +): Job { + val me = getMe() + return onChatMemberUpdated(initialFilter, markerFactory = markerFactory) { + when { + it.oldChatMemberState is AdministratorChatMember && it.newChatMemberState !is AdministratorChatMember || + it.newChatMemberState is AdministratorChatMember && it.oldChatMemberState !is AdministratorChatMember -> { + updateAdmins( + it.chat.id, + repo, + me + ) + } + } + } +} + +suspend fun BehaviourContext.activateAdminsChangesListening( + repo: DefaultAdminsCacheAPIRepo, + allowedChats: List +) = activateAdminsChangesListening( + repo, + { + it.chat.id in allowedChats + } +) diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsSimpleFilter.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsSimpleFilter.kt new file mode 100644 index 0000000..6fd5fe9 --- /dev/null +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsSimpleFilter.kt @@ -0,0 +1,37 @@ +package dev.inmo.tgbotapi.libraries.cache.admins + +import dev.inmo.tgbotapi.abstracts.FromUser +import dev.inmo.tgbotapi.extensions.behaviour_builder.utils.SimpleFilter +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.UserId +import dev.inmo.tgbotapi.types.message.abstracts.Message + +fun AdminsChecker( + adminsCacheAPI: AdminsCacheAPI +): SimpleFilter> = SimpleFilter { + adminsCacheAPI.isAdmin(it.first, it.second) +} + +fun AdminsChecker( + adminsCacheAPI: AdminsCacheAPI, + mapper: (T) -> Pair +): SimpleFilter { + val baseChecker = AdminsChecker(adminsCacheAPI) + + return SimpleFilter { + baseChecker(mapper(it)) + } +} + +fun MessageAdminsChecker( + adminsCacheAPI: AdminsCacheAPI +) = SimpleFilter { + adminsCacheAPI.isAdmin(it) +} + +fun AdminsChecker( + adminsCacheAPI: AdminsCacheAPI, + chatId: ChatId +) = SimpleFilter { + adminsCacheAPI.isAdmin(chatId, it.from.id) +} diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/DefaultAdminsCacheAPI.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/DefaultAdminsCacheAPI.kt index 75c6ae0..377f5f9 100644 --- a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/DefaultAdminsCacheAPI.kt +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/DefaultAdminsCacheAPI.kt @@ -3,7 +3,7 @@ package dev.inmo.tgbotapi.libraries.cache.admins import com.soywiz.klock.DateTime import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.api.bot.getMe -import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators +import dev.inmo.tgbotapi.extensions.api.chat.members.getChatMember import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.chat.ExtendedBot import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember @@ -29,28 +29,37 @@ class DefaultAdminsCacheAPI( bot.getMe().also { botInfo = it } } - private suspend fun triggerUpdate(chatId: ChatId): List { - val botInfo = getBotInfo() - val admins = bot.getChatAdministrators(chatId).filter { - botInfo.id != it.user.id - } - repo.setChatAdmins(chatId, admins) - return admins - } - override suspend fun getChatAdmins(chatId: ChatId): List? { val settings = settingsAPI.getChatSettings(chatId) val lastUpdate = repo.lastUpdate(chatId) return when { settings == null -> null - settings.refreshOnRequests && + settings.refreshOnCacheCalls && (lastUpdate == null || (DateTime.now() - lastUpdate).seconds > settings.refreshSeconds) -> { - triggerUpdate(chatId) + bot.updateAdmins(chatId, repo, getBotInfo()) } - else -> repo.getChatAdmins(chatId) ?: triggerUpdate(chatId) + else -> repo.getChatAdmins(chatId) ?: bot.updateAdmins(chatId, repo, getBotInfo()) } } + override suspend fun isAdmin(chatId: ChatId, userId: UserId): Boolean { + val settings = settingsAPI.getChatSettings(chatId) + val lastUpdate = repo.lastUpdate(chatId) + return when { + settings == null -> return false + settings.refreshOnCacheCalls && (lastUpdate == null || (DateTime.now() - lastUpdate).seconds > settings.refreshSeconds) -> { + bot.updateAdmins(chatId, repo, getBotInfo()) + } + else -> { + val chatAdmins = repo.getChatAdmins(chatId) + if (chatAdmins == null) { + return bot.getChatMember(chatId, userId) is AdministratorChatMember + } + chatAdmins + } + }.any { it.user.id == userId } + } + override suspend fun sentByAdmin(groupContentMessage: GroupContentMessage<*>): Boolean { return when (groupContentMessage) { is AnonymousGroupContentMessage -> true @@ -63,5 +72,4 @@ class DefaultAdminsCacheAPI( } override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI - } diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/MessageSenderVerification.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/MessageSenderVerification.kt index e784cdf..a5fe89a 100644 --- a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/MessageSenderVerification.kt +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/MessageSenderVerification.kt @@ -4,17 +4,40 @@ import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.UserId import dev.inmo.tgbotapi.types.message.abstracts.* -suspend fun AdminsCacheAPI.verifyMessageFromAdmin(message: ContentMessage<*>) = when (message) { +suspend inline fun AdminsCacheAPI.isAdmin(message: Message) = when (message) { is CommonGroupContentMessage<*> -> isAdmin(message.chat.id, message.user.id) is AnonymousGroupContentMessage<*> -> true else -> false } -suspend fun ContentMessage<*>.doAfterVerification(adminsCacheAPI: AdminsCacheAPI, block: suspend () -> R): R? { - val verified = adminsCacheAPI.verifyMessageFromAdmin(this) +suspend inline fun AdminsCacheAPI.verifyMessageFromAdmin(message: Message) = isAdmin(message) + +suspend inline fun AdminsCacheAPI.doIfAdmin( + chatId: ChatId, + userId: UserId, + block: () -> R +) = if(isAdmin(chatId, userId)) { + block() +} else { + null +} + +suspend inline fun AdminsCacheAPI.doIfAdmin( + message: Message, + block: () -> R +) = if(isAdmin(message)) { + block() +} else { + null +} + +suspend inline fun ContentMessage<*>.doIfAdmin(adminsCacheAPI: AdminsCacheAPI, block: () -> R): R? { + val verified = adminsCacheAPI.isAdmin(this) return if (verified) { block() } else { null } } + +suspend inline fun ContentMessage<*>.doAfterVerification(adminsCacheAPI: AdminsCacheAPI, block: () -> R) = doIfAdmin(adminsCacheAPI, block) diff --git a/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/TriggerAdminsUpdate.kt b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/TriggerAdminsUpdate.kt new file mode 100644 index 0000000..c241bab --- /dev/null +++ b/cache/admins/common/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/TriggerAdminsUpdate.kt @@ -0,0 +1,21 @@ +package dev.inmo.tgbotapi.libraries.cache.admins + +import dev.inmo.tgbotapi.bot.TelegramBot +import dev.inmo.tgbotapi.extensions.api.bot.getMe +import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators +import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.types.chat.ExtendedBot +import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember + +internal suspend fun TelegramBot.updateAdmins( + chatId: ChatId, + repo: DefaultAdminsCacheAPIRepo, + botInfo: ExtendedBot? = null +): List { + val botInfo = botInfo ?: getMe() + val admins = getChatAdministrators(chatId).filter { + botInfo.id != it.user.id + } + repo.setChatAdmins(chatId, admins) + return admins +} diff --git a/cache/admins/micro_utils/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/micro_utils/DefaultAdminsCacheAPI.kt b/cache/admins/micro_utils/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/micro_utils/DefaultAdminsCacheAPI.kt index 0027b85..2c51f1d 100644 --- a/cache/admins/micro_utils/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/micro_utils/DefaultAdminsCacheAPI.kt +++ b/cache/admins/micro_utils/src/commonMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/micro_utils/DefaultAdminsCacheAPI.kt @@ -28,7 +28,7 @@ private class SetChatAdminsRepoAction( override val toReturn: Continuation ) : RepoActions() -class DefaultAdminsCacheAPIRepo( +class DefaultAdminsCacheAPIRepoImpl( private val adminsRepo: KeyValuesRepo, private val updatesRepo: KeyValueRepo, private val scope: CoroutineScope @@ -53,6 +53,7 @@ class DefaultAdminsCacheAPIRepo( override suspend fun getChatAdmins(chatId: ChatId): List? = suspendCoroutine { actor.trySend(GetChatAdminsRepoAction(chatId, it)) } + override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List) = suspendCoroutine { actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it)) } @@ -60,3 +61,9 @@ class DefaultAdminsCacheAPIRepo( actor.trySend(GetUpdateDateTimeRepoAction(chatId, it)) } } + +fun DefaultAdminsCacheAPIRepo( + adminsRepo: KeyValuesRepo, + updatesRepo: KeyValueRepo, + scope: CoroutineScope +) = DefaultAdminsCacheAPIRepoImpl(adminsRepo, updatesRepo, scope) diff --git a/cache/admins/micro_utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheAPI.kt b/cache/admins/micro_utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheAPI.kt index 5a57304..dbde1f2 100644 --- a/cache/admins/micro_utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheAPI.kt +++ b/cache/admins/micro_utils/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsCacheAPI.kt @@ -5,20 +5,18 @@ import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DefaultAdminsCacheAPIRepo +import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DefaultAdminsCacheAPIRepoImpl import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DynamicAdminsCacheSettingsAPI import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.chat.member.* import kotlinx.coroutines.CoroutineScope import kotlinx.serialization.* -import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.statements.api.ExposedBlob -private val serializationFormat = Json { +val telegramAdminsSerializationFormat = Json { ignoreUnknownKeys = true serializersModule = SerializersModule { polymorphic(AdministratorChatMember::class) { @@ -29,13 +27,12 @@ private val serializationFormat = Json { } } -fun AdminsCacheAPI( - bot: TelegramBot, +fun BehaviourContext.createAdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this) + +fun TelegramBot.createAdminsCacheAPI( database: Database, - scope: CoroutineScope -) : AdminsCacheAPI = DefaultAdminsCacheAPI( - bot, - DefaultAdminsCacheAPIRepo( + scope: CoroutineScope, + defaultAdminsCacheAPIRepo: DefaultAdminsCacheAPIRepo = DefaultAdminsCacheAPIRepoImpl( ExposedKeyValuesRepo( database, { long("chatId") }, @@ -43,9 +40,9 @@ fun AdminsCacheAPI( "AdminsTable" ).withMapper( keyFromToTo = { chatId }, - valueFromToTo = { serializationFormat.encodeToString(this) }, + valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) }, keyToToFrom = { toChatId() }, - valueToToFrom = { serializationFormat.decodeFromString(this) } + valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) } ), ExposedKeyValueRepo( database, @@ -60,7 +57,7 @@ fun AdminsCacheAPI( ), scope ), - DynamicAdminsCacheSettingsAPI( + adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI( ExposedKeyValueRepo( database, { long("chatId") }, @@ -68,12 +65,19 @@ fun AdminsCacheAPI( "DynamicAdminsCacheSettingsAPI" ).withMapper( keyFromToTo = { chatId }, - valueFromToTo = { serializationFormat.encodeToString(this) }, + valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) }, keyToToFrom = { toChatId() }, - valueToToFrom = { serializationFormat.decodeFromString(this) } + valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) } ), scope ) -) +) = DefaultAdminsCacheAPI(this, defaultAdminsCacheAPIRepo, adminsCacheSettingsAPI) -fun BehaviourContext.AdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this) +fun AdminsCacheAPI( + bot: TelegramBot, + database: Database, + scope: CoroutineScope +) : AdminsCacheAPI = bot.createAdminsCacheAPI( + database, + scope +) diff --git a/cache/admins/plagubot/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsPlugin.kt b/cache/admins/plagubot/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsPlugin.kt index a53a14a..c9d5aed 100644 --- a/cache/admins/plagubot/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsPlugin.kt +++ b/cache/admins/plagubot/src/jvmMain/kotlin/dev/inmo/tgbotapi/libraries/cache/admins/AdminsPlugin.kt @@ -1,18 +1,26 @@ package dev.inmo.tgbotapi.libraries.cache.admins +import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo +import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo +import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.plagubot.Plugin import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext -import dev.inmo.tgbotapi.types.ChatId +import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DefaultAdminsCacheAPIRepoImpl +import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DynamicAdminsCacheSettingsAPI +import dev.inmo.tgbotapi.types.* +import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlinx.serialization.Serializable -import kotlinx.serialization.Transient +import kotlinx.serialization.* import kotlinx.serialization.json.JsonObject import org.jetbrains.exposed.sql.Database import org.koin.core.Koin import org.koin.core.module.Module +import org.koin.core.qualifier.named import org.koin.core.scope.Scope +import org.koin.dsl.binds val Scope.adminsPlugin: AdminsPlugin? get() = getOrNull() @@ -28,6 +36,7 @@ class AdminsPlugin : Plugin { private val databaseToAdminsCacheAPI = mutableMapOf>() private val mutex = Mutex() + @Deprecated("Will be removed soon due to its redundancy") suspend fun adminsAPI(database: Database): AdminsCacheAPI { val flow = mutex.withLock { databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) } @@ -37,16 +46,61 @@ class AdminsPlugin : Plugin { override fun Module.setupDI(database: Database, params: JsonObject) { single { this@AdminsPlugin } + val scopeQualifier = named("admins plugin scope") + single(scopeQualifier) { CoroutineScope(Dispatchers.IO + SupervisorJob()) } + single { + DefaultAdminsCacheAPIRepoImpl( + ExposedKeyValuesRepo( + database, + { long("chatId") }, + { text("member") }, + "AdminsTable" + ).withMapper( + keyFromToTo = { chatId }, + valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) }, + keyToToFrom = { toChatId() }, + valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) } + ), + ExposedKeyValueRepo( + database, + { long("chatId") }, + { long("datetime") }, + "AdminsUpdatesTimesTable" + ).withMapper( + keyFromToTo = { chatId }, + valueFromToTo = { this }, + keyToToFrom = { toChatId() }, + valueToToFrom = { this } + ), + get(scopeQualifier) + ) + } + single { + DynamicAdminsCacheSettingsAPI( + ExposedKeyValueRepo( + database, + { long("chatId") }, + { text("settings") }, + "DynamicAdminsCacheSettingsAPI" + ).withMapper( + keyFromToTo = { chatId }, + valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) }, + keyToToFrom = { toChatId() }, + valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) } + ), + get(scopeQualifier) + ) + } + single { DefaultAdminsCacheAPI(get(), get(), get()) } binds arrayOf( + AdminsCacheAPI::class + ) } override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { with(koin) { - mutex.withLock { - val flow = databaseToAdminsCacheAPI.getOrPut(koin.get()){ MutableStateFlow(null) } - if (flow.value == null) { - flow.value = AdminsCacheAPI(koin.get()) - } - } + activateAdminsChangesListening( + get() + ) } } } diff --git a/gradle.properties b/gradle.properties index 330ac00..be1ce12 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,10 +11,10 @@ kotlin_serialisation_core_version=1.4.0 github_release_plugin_version=2.4.1 -tgbotapi_version=3.2.0 -micro_utils_version=0.12.5 +tgbotapi_version=3.2.1 +micro_utils_version=0.12.11 exposed_version=0.39.2 -plagubot_version=2.2.0 +plagubot_version=2.3.1 # ANDROID @@ -33,5 +33,5 @@ dokka_version=1.7.10 # Project data group=dev.inmo -version=0.5.2 -android_code_version=29 +version=0.5.3 +android_code_version=30