Merge pull request #26 from InsanusMokrassar/0.5.3

0.5.3
This commit is contained in:
InsanusMokrassar 2022-09-10 19:23:07 +06:00 committed by GitHub
commit ab802df7d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 251 additions and 50 deletions

View File

@ -15,8 +15,11 @@ data class AdminsCacheSettings(
*/ */
val disableRequestsRefreshMode: Boolean = false val disableRequestsRefreshMode: Boolean = false
) { ) {
val refreshOnRequests: Boolean val refreshOnCacheCalls: Boolean
get() = !disableRequestsRefreshMode get() = !disableRequestsRefreshMode
@Deprecated("Renamed", ReplaceWith("refreshOnCacheCalls"))
val refreshOnRequests: Boolean
get() = refreshOnCacheCalls
} }
interface AdminsCacheSettingsAPI { interface AdminsCacheSettingsAPI {

View File

@ -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<ChatMemberUpdated>? = null,
markerFactory: MarkerFactory<ChatMemberUpdated, Any> = 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<ChatId>
) = activateAdminsChangesListening(
repo,
{
it.chat.id in allowedChats
}
)

View File

@ -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<Pair<ChatId, UserId>> = SimpleFilter {
adminsCacheAPI.isAdmin(it.first, it.second)
}
fun <T> AdminsChecker(
adminsCacheAPI: AdminsCacheAPI,
mapper: (T) -> Pair<ChatId, UserId>
): SimpleFilter<T> {
val baseChecker = AdminsChecker(adminsCacheAPI)
return SimpleFilter<T> {
baseChecker(mapper(it))
}
}
fun MessageAdminsChecker(
adminsCacheAPI: AdminsCacheAPI
) = SimpleFilter<Message> {
adminsCacheAPI.isAdmin(it)
}
fun AdminsChecker(
adminsCacheAPI: AdminsCacheAPI,
chatId: ChatId
) = SimpleFilter<FromUser> {
adminsCacheAPI.isAdmin(chatId, it.from.id)
}

View File

@ -3,7 +3,7 @@ package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.api.bot.getMe 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.*
import dev.inmo.tgbotapi.types.chat.ExtendedBot import dev.inmo.tgbotapi.types.chat.ExtendedBot
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
@ -29,28 +29,37 @@ class DefaultAdminsCacheAPI(
bot.getMe().also { botInfo = it } bot.getMe().also { botInfo = it }
} }
private suspend fun triggerUpdate(chatId: ChatId): List<AdministratorChatMember> {
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<AdministratorChatMember>? { override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? {
val settings = settingsAPI.getChatSettings(chatId) val settings = settingsAPI.getChatSettings(chatId)
val lastUpdate = repo.lastUpdate(chatId) val lastUpdate = repo.lastUpdate(chatId)
return when { return when {
settings == null -> null settings == null -> null
settings.refreshOnRequests && settings.refreshOnCacheCalls &&
(lastUpdate == null || (DateTime.now() - lastUpdate).seconds > settings.refreshSeconds) -> { (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 { override suspend fun sentByAdmin(groupContentMessage: GroupContentMessage<*>): Boolean {
return when (groupContentMessage) { return when (groupContentMessage) {
is AnonymousGroupContentMessage -> true is AnonymousGroupContentMessage -> true
@ -63,5 +72,4 @@ class DefaultAdminsCacheAPI(
} }
override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI
} }

View File

@ -4,17 +4,40 @@ import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.UserId import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.message.abstracts.* 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 CommonGroupContentMessage<*> -> isAdmin(message.chat.id, message.user.id)
is AnonymousGroupContentMessage<*> -> true is AnonymousGroupContentMessage<*> -> true
else -> false else -> false
} }
suspend fun <R> ContentMessage<*>.doAfterVerification(adminsCacheAPI: AdminsCacheAPI, block: suspend () -> R): R? { suspend inline fun AdminsCacheAPI.verifyMessageFromAdmin(message: Message) = isAdmin(message)
val verified = adminsCacheAPI.verifyMessageFromAdmin(this)
suspend inline fun <R : Any> AdminsCacheAPI.doIfAdmin(
chatId: ChatId,
userId: UserId,
block: () -> R
) = if(isAdmin(chatId, userId)) {
block()
} else {
null
}
suspend inline fun <R : Any> AdminsCacheAPI.doIfAdmin(
message: Message,
block: () -> R
) = if(isAdmin(message)) {
block()
} else {
null
}
suspend inline fun <R> ContentMessage<*>.doIfAdmin(adminsCacheAPI: AdminsCacheAPI, block: () -> R): R? {
val verified = adminsCacheAPI.isAdmin(this)
return if (verified) { return if (verified) {
block() block()
} else { } else {
null null
} }
} }
suspend inline fun <R> ContentMessage<*>.doAfterVerification(adminsCacheAPI: AdminsCacheAPI, block: () -> R) = doIfAdmin(adminsCacheAPI, block)

View File

@ -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<AdministratorChatMember> {
val botInfo = botInfo ?: getMe()
val admins = getChatAdministrators(chatId).filter {
botInfo.id != it.user.id
}
repo.setChatAdmins(chatId, admins)
return admins
}

View File

@ -28,7 +28,7 @@ private class SetChatAdminsRepoAction(
override val toReturn: Continuation<Unit> override val toReturn: Continuation<Unit>
) : RepoActions<Unit>() ) : RepoActions<Unit>()
class DefaultAdminsCacheAPIRepo( class DefaultAdminsCacheAPIRepoImpl(
private val adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>, private val adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
private val updatesRepo: KeyValueRepo<ChatId, MilliSeconds>, private val updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
private val scope: CoroutineScope private val scope: CoroutineScope
@ -53,6 +53,7 @@ class DefaultAdminsCacheAPIRepo(
override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? = suspendCoroutine { override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? = suspendCoroutine {
actor.trySend(GetChatAdminsRepoAction(chatId, it)) actor.trySend(GetChatAdminsRepoAction(chatId, it))
} }
override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> { override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> {
actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it)) actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it))
} }
@ -60,3 +61,9 @@ class DefaultAdminsCacheAPIRepo(
actor.trySend(GetUpdateDateTimeRepoAction(chatId, it)) actor.trySend(GetUpdateDateTimeRepoAction(chatId, it))
} }
} }
fun DefaultAdminsCacheAPIRepo(
adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
scope: CoroutineScope
) = DefaultAdminsCacheAPIRepoImpl(adminsRepo, updatesRepo, scope)

View File

@ -5,20 +5,18 @@ import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo
import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext 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.libraries.cache.admins.micro_utils.DynamicAdminsCacheSettingsAPI
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.member.* import dev.inmo.tgbotapi.types.chat.member.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic import kotlinx.serialization.modules.polymorphic
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
private val serializationFormat = Json { val telegramAdminsSerializationFormat = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
serializersModule = SerializersModule { serializersModule = SerializersModule {
polymorphic(AdministratorChatMember::class) { polymorphic(AdministratorChatMember::class) {
@ -29,13 +27,12 @@ private val serializationFormat = Json {
} }
} }
fun AdminsCacheAPI( fun BehaviourContext.createAdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this)
bot: TelegramBot,
fun TelegramBot.createAdminsCacheAPI(
database: Database, database: Database,
scope: CoroutineScope scope: CoroutineScope,
) : AdminsCacheAPI = DefaultAdminsCacheAPI( defaultAdminsCacheAPIRepo: DefaultAdminsCacheAPIRepo = DefaultAdminsCacheAPIRepoImpl(
bot,
DefaultAdminsCacheAPIRepo(
ExposedKeyValuesRepo( ExposedKeyValuesRepo(
database, database,
{ long("chatId") }, { long("chatId") },
@ -43,9 +40,9 @@ fun AdminsCacheAPI(
"AdminsTable" "AdminsTable"
).withMapper<ChatId, AdministratorChatMember, Identifier, String>( ).withMapper<ChatId, AdministratorChatMember, Identifier, String>(
keyFromToTo = { chatId }, keyFromToTo = { chatId },
valueFromToTo = { serializationFormat.encodeToString(this) }, valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() }, keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) } valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
), ),
ExposedKeyValueRepo( ExposedKeyValueRepo(
database, database,
@ -60,7 +57,7 @@ fun AdminsCacheAPI(
), ),
scope scope
), ),
DynamicAdminsCacheSettingsAPI( adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo( ExposedKeyValueRepo(
database, database,
{ long("chatId") }, { long("chatId") },
@ -68,12 +65,19 @@ fun AdminsCacheAPI(
"DynamicAdminsCacheSettingsAPI" "DynamicAdminsCacheSettingsAPI"
).withMapper<ChatId, AdminsCacheSettings, Identifier, String>( ).withMapper<ChatId, AdminsCacheSettings, Identifier, String>(
keyFromToTo = { chatId }, keyFromToTo = { chatId },
valueFromToTo = { serializationFormat.encodeToString(this) }, valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() }, keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) } valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
), ),
scope 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
)

View File

@ -1,18 +1,26 @@
package dev.inmo.tgbotapi.libraries.cache.admins 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.plagubot.Plugin
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext 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.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.*
import kotlinx.serialization.Transient
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
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
import org.koin.core.scope.Scope import org.koin.core.scope.Scope
import org.koin.dsl.binds
val Scope.adminsPlugin: AdminsPlugin? val Scope.adminsPlugin: AdminsPlugin?
get() = getOrNull() get() = getOrNull()
@ -28,6 +36,7 @@ class AdminsPlugin : Plugin {
private val databaseToAdminsCacheAPI = mutableMapOf<Database, MutableStateFlow<AdminsCacheAPI?>>() private val databaseToAdminsCacheAPI = mutableMapOf<Database, MutableStateFlow<AdminsCacheAPI?>>()
private val mutex = Mutex() private val mutex = Mutex()
@Deprecated("Will be removed soon due to its redundancy")
suspend fun adminsAPI(database: Database): AdminsCacheAPI { suspend fun adminsAPI(database: Database): AdminsCacheAPI {
val flow = mutex.withLock { val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) } databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
@ -37,16 +46,61 @@ class AdminsPlugin : Plugin {
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { this@AdminsPlugin } single { this@AdminsPlugin }
val scopeQualifier = named("admins plugin scope")
single(scopeQualifier) { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
single<DefaultAdminsCacheAPIRepo> {
DefaultAdminsCacheAPIRepoImpl(
ExposedKeyValuesRepo(
database,
{ long("chatId") },
{ text("member") },
"AdminsTable"
).withMapper<ChatId, AdministratorChatMember, Identifier, String>(
keyFromToTo = { chatId },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() },
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
),
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ long("datetime") },
"AdminsUpdatesTimesTable"
).withMapper<ChatId, Long, Identifier, Long>(
keyFromToTo = { chatId },
valueFromToTo = { this },
keyToToFrom = { toChatId() },
valueToToFrom = { this }
),
get(scopeQualifier)
)
}
single<AdminsCacheSettingsAPI> {
DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ text("settings") },
"DynamicAdminsCacheSettingsAPI"
).withMapper<ChatId, AdminsCacheSettings, Identifier, String>(
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) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
with(koin) { with(koin) {
mutex.withLock { activateAdminsChangesListening(
val flow = databaseToAdminsCacheAPI.getOrPut(koin.get()){ MutableStateFlow(null) } get()
if (flow.value == null) { )
flow.value = AdminsCacheAPI(koin.get())
}
}
} }
} }
} }

View File

@ -11,10 +11,10 @@ kotlin_serialisation_core_version=1.4.0
github_release_plugin_version=2.4.1 github_release_plugin_version=2.4.1
tgbotapi_version=3.2.0 tgbotapi_version=3.2.1
micro_utils_version=0.12.5 micro_utils_version=0.12.11
exposed_version=0.39.2 exposed_version=0.39.2
plagubot_version=2.2.0 plagubot_version=2.3.1
# ANDROID # ANDROID
@ -33,5 +33,5 @@ dokka_version=1.7.10
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.5.2 version=0.5.3
android_code_version=29 android_code_version=30