first version of adminbot

This commit is contained in:
InsanusMokrassar 2021-02-22 18:11:12 +06:00
parent 3fe45e4aec
commit e638aff72a
12 changed files with 295 additions and 1 deletions

View File

@ -6,3 +6,12 @@ plugins {
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:tgbotapi:$tgbotapi_version"
}
}
}
}

View File

@ -0,0 +1,11 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
interface AdminsCacheAPI {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>?
suspend fun isAdmin(userId: UserId, chatId: ChatId): Boolean
suspend fun settings(): AdminsCacheSettingsAPI
}

View File

@ -0,0 +1,41 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.minutes
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.Seconds
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.Serializable
@Serializable
data class AdminsCacheSettings(
val refreshSeconds: Seconds = 10.minutes.seconds.toInt(),
/**
* In case this setting set up to true, will request admins list just from time to time instead of refreshing on
* requests
*/
val disableRequestsRefreshMode: Boolean = false
) {
val refreshOnRequests: Boolean
get() = !disableRequestsRefreshMode
}
interface AdminsCacheSettingsAPI {
suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings?
}
interface MutableAdminsCacheSettingsAPI : AdminsCacheSettingsAPI {
val chatSettingsUpdatedFlow: SharedFlow<Pair<ChatId, AdminsCacheSettings>>
suspend fun setChatSettings(chatId: ChatId, settings: AdminsCacheSettings)
}
fun AdminsCacheSettingsAPI.asMutable(): MutableAdminsCacheSettingsAPI? = this as? MutableAdminsCacheSettingsAPI
@Serializable
class StaticAdminsCacheSettingsAPI(
private val settings: Map<ChatId, AdminsCacheSettings>
) : AdminsCacheSettingsAPI {
override suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings? = settings[chatId]
}

View File

@ -0,0 +1,48 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.DateTime
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.UserId
import kotlinx.serialization.Serializable
interface DefaultAdminsCacheAPIRepo {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>?
suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>)
suspend fun lastUpdate(chatId: ChatId): DateTime?
}
@Serializable
class DefaultAdminsCacheAPI(
private val bot: TelegramBot,
private val repo: DefaultAdminsCacheAPIRepo,
private val settingsAPI: AdminsCacheSettingsAPI
) : AdminsCacheAPI {
private suspend fun triggerUpdate(chatId: ChatId): List<AdministratorChatMember> {
val admins = bot.getChatAdministrators(chatId)
repo.setChatAdmins(chatId, admins)
return admins
}
override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? {
val settings = settingsAPI.getChatSettings(chatId)
val lastUpdate = repo.lastUpdate(chatId)
return when {
settings == null -> null
settings.refreshOnRequests &&
(lastUpdate == null || (DateTime.now() - lastUpdate).seconds > settings.refreshSeconds) -> {
triggerUpdate(chatId)
}
else -> repo.getChatAdmins(chatId) ?: triggerUpdate(chatId)
}
}
override suspend fun isAdmin(userId: UserId, chatId: ChatId): Boolean = getChatAdmins(chatId) ?.any {
it.user.id == userId
} == true
override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI
}

25
cache/admins/micro_utils/build.gradle vendored Normal file
View File

@ -0,0 +1,25 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:micro_utils.repos.common:$micro_utils_version"
api project(":tgbotapi.libraries.cache.admins.common")
}
}
jvmMain {
dependencies {
api "dev.inmo:micro_utils.repos.exposed:$micro_utils_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-cbor:$kotlin_serialisation_core_version"
api "org.jetbrains.exposed:exposed-jdbc:$exposed_version"
}
}
}
}

View File

@ -0,0 +1,62 @@
package dev.inmo.tgbotapi.libraries.cache.admins.micro_utils
import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.coroutines.actor
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.repos.*
import dev.inmo.tgbotapi.libraries.cache.admins.DefaultAdminsCacheAPIRepo
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.*
private sealed class RepoActions<T> {
abstract val toReturn: Continuation<T>
}
private class GetUpdateDateTimeRepoAction(
val chatId: ChatId,
override val toReturn: Continuation<DateTime?>
) : RepoActions<DateTime?>()
private class GetChatAdminsRepoAction(
val chatId: ChatId,
override val toReturn: Continuation<List<AdministratorChatMember>?>
) : RepoActions<List<AdministratorChatMember>?>()
private class SetChatAdminsRepoAction(
val chatId: ChatId,
val newValue: List<AdministratorChatMember>,
override val toReturn: Continuation<Unit>
) : RepoActions<Unit>()
class DefaultAdminsCacheAPIRepo(
private val adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
private val updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
private val scope: CoroutineScope
) : DefaultAdminsCacheAPIRepo {
private val actor = scope.actor<RepoActions<*>>(Channel.UNLIMITED) {
safelyWithoutExceptions {
when (it) {
is GetUpdateDateTimeRepoAction -> it.toReturn.resume(
updatesRepo.get(it.chatId) ?.let { DateTime(it.toDouble()) }
)
is GetChatAdminsRepoAction -> it.toReturn.resume(adminsRepo.getAll(it.chatId))
is SetChatAdminsRepoAction -> {
adminsRepo.clear(it.chatId)
adminsRepo.set(it.chatId, it.newValue)
updatesRepo.set(it.chatId, DateTime.now().unixMillisLong)
it.toReturn.resume(Unit)
}
}
}
}
override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? = suspendCoroutine {
actor.offer(GetChatAdminsRepoAction(chatId, it))
}
override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> {
actor.offer(SetChatAdminsRepoAction(chatId, chatMembers, it))
}
override suspend fun lastUpdate(chatId: ChatId): DateTime? = suspendCoroutine {
actor.offer(GetUpdateDateTimeRepoAction(chatId, it))
}
}

View File

@ -0,0 +1,31 @@
package dev.inmo.tgbotapi.libraries.cache.admins.micro_utils
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.repos.*
import dev.inmo.tgbotapi.libraries.cache.admins.*
import dev.inmo.tgbotapi.types.ChatId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
class DynamicAdminsCacheSettingsAPI(
private val repo: KeyValueRepo<ChatId, AdminsCacheSettings>,
private val scope: CoroutineScope
) : AdminsCacheSettingsAPI, MutableAdminsCacheSettingsAPI {
override val chatSettingsUpdatedFlow: SharedFlow<Pair<ChatId, AdminsCacheSettings>>
get() = repo.onNewValue.shareIn(scope, SharingStarted.Eagerly)
override suspend fun setChatSettings(chatId: ChatId, settings: AdminsCacheSettings) {
repo.set(chatId, settings)
}
override suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings {
val settings = repo.get(chatId)
return if (settings == null) {
val newSettings = AdminsCacheSettings()
setChatSettings(chatId, newSettings)
newSettings
} else {
settings
}
}
}

View File

@ -0,0 +1,64 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DefaultAdminsCacheAPIRepo
import dev.inmo.tgbotapi.libraries.cache.admins.micro_utils.DynamicAdminsCacheSettingsAPI
import dev.inmo.tgbotapi.types.toChatId
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.cbor.Cbor
import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.encodeToByteArray
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.statements.api.ExposedBlob
private val serializationFormat = Cbor
fun AdminsCacheAPI(
bot: TelegramBot,
database: Database,
scope: CoroutineScope,
repo: dev.inmo.tgbotapi.libraries.cache.admins.DefaultAdminsCacheAPIRepo = DefaultAdminsCacheAPIRepo(
ExposedOneToManyKeyValueRepo(
database,
{ long("chatId") },
{ blob("member") },
"AdminsTable"
).withMapper(
keyFromToTo = { chatId },
valueFromToTo = { ExposedBlob(serializationFormat.encodeToByteArray(this)) },
keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromByteArray(bytes) }
),
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ long("datetime") },
"AdminsUpdatesTimesTable"
).withMapper(
keyFromToTo = { chatId },
keyToToFrom = { toChatId() }
),
scope
),
settingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ blob("settings") },
"DynamicAdminsCacheSettingsAPI"
).withMapper(
keyFromToTo = { chatId },
valueFromToTo = { ExposedBlob(serializationFormat.encodeToByteArray(this)) },
keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromByteArray(bytes) }
),
scope
)
) : AdminsCacheAPI = DefaultAdminsCacheAPI(
bot,
repo,
settingsAPI
)

View File

@ -0,0 +1 @@
<manifest package="dev.inmo.tgbotapi.libraries.cache.admins.micro_utils"/>

View File

@ -13,6 +13,7 @@ github_release_plugin_version=2.2.12
tgbotapi_version=0.32.8
micro_utils_version=0.4.26
exposed_version=0.29.1
# ANDROID

View File

@ -1,7 +1,8 @@
rootProject.name = 'tgbotapi.libraries'
String[] includes = [
":cache:admins",
":cache:admins:common",
":cache:admins:micro_utils",
":cache:media"
]