Compare commits

...

54 Commits

Author SHA1 Message Date
ee9f524fc6 Update gradle.properties 2022-09-19 23:32:07 +06:00
950eebea06 fixes 2022-09-19 02:42:10 +06:00
7845b7cc5f small fixes 2022-09-19 01:56:41 +06:00
539515da43 update dependencies 2022-09-19 01:54:44 +06:00
3695ab7936 start 0.5.4 2022-09-19 01:53:51 +06:00
ab802df7d1 Merge pull request #26 from InsanusMokrassar/0.5.3
0.5.3
2022-09-10 19:23:07 +06:00
c85d92c7ba remove tryGetChatAdmins 2022-09-10 19:04:48 +06:00
cea8ba47db fixes and improvements 2022-09-10 18:54:25 +06:00
b2719c0760 AdminsCacheAPI with defaults and customizing opportunity 2022-09-09 20:20:39 +06:00
1c87f64a6d fix 2022-09-09 19:32:21 +06:00
53630d4864 rework of plagubot part of admins plugin 2022-09-09 19:16:19 +06:00
bb7fb985e3 update arguments in activateAdminsChangesListening 2022-09-09 18:57:19 +06:00
18fc3fd1dd update dependencies and add opportunity to listen for admins in chats 2022-09-09 18:55:26 +06:00
5f1f512db4 Merge pull request #25 from InsanusMokrassar/0.5.2
0.5.2
2022-08-31 11:12:48 +06:00
961d7d9afd Update gradle.properties 2022-08-31 11:07:31 +06:00
6a84bcb860 start 0.5.2 2022-08-31 11:04:31 +06:00
cd1ba035ac Update gradle.properties 2022-08-18 17:18:20 +06:00
6f443c1617 Merge pull request #24 from InsanusMokrassar/0.5.0
0.5.0
2022-08-06 09:49:24 +06:00
85a1048b13 fixes 2022-08-06 09:31:11 +06:00
8dc1687b8e Update gradle.properties 2022-08-06 00:43:51 +06:00
2518109290 Update gradle.properties 2022-08-05 22:55:00 +06:00
760e51cabb 0.5.0 2022-08-05 22:23:15 +06:00
3a667946da Merge pull request #23 from InsanusMokrassar/0.4.2
0.4.2
2022-08-03 09:40:46 +06:00
f6df96c405 0.4.2 2022-08-03 09:35:06 +06:00
fd33cbfca3 Merge pull request #22 from InsanusMokrassar/0.4.1
0.4.1
2022-07-30 23:26:01 +06:00
62a10b9042 fixes in build 2022-07-30 23:25:38 +06:00
a7814c61b5 update admins api 2022-07-30 23:24:24 +06:00
4eb27b42ad Merge pull request #21 from InsanusMokrassar/0.4.0
0.4.0
2022-07-30 20:55:38 +06:00
5367ff5629 Update gradle-wrapper.properties 2022-07-30 20:42:47 +06:00
50caa740cc start 0.4.0 2022-07-30 20:42:04 +06:00
6c85e977f1 Merge pull request #20 from InsanusMokrassar/0.3.1
0.3.1
2022-07-23 00:17:35 +06:00
156a8dd8be Update gradle.properties 2022-07-23 00:11:24 +06:00
3728983653 start 0.3.1 2022-07-23 00:08:55 +06:00
8e99cb9e18 Merge pull request #19 from InsanusMokrassar/0.3.0
0.3.0
2022-06-26 16:27:19 +06:00
0f569ead82 Update gradle.properties 2022-06-26 16:07:26 +06:00
71f6710397 Update gradle.properties 2022-06-22 12:48:07 +06:00
243926cd17 Merge pull request #18 from InsanusMokrassar/0.2.1
0.2.1
2022-06-13 00:28:11 +06:00
b2d3ded0da Update gradle.properties 2022-06-13 00:07:30 +06:00
fbbd8e640c update build scripts 2022-06-11 20:27:00 +06:00
15c0f9979a fixes related to the new microutils 2022-06-11 19:45:29 +06:00
e6d04de433 start 0.2.1 and update dependencies 2022-06-11 19:35:59 +06:00
a6b837f633 Merge pull request #17 from InsanusMokrassar/0.2.0
0.2.0
2022-05-22 15:35:00 +06:00
866b016e08 fixes 2022-05-22 11:56:30 +06:00
b67212ec0b update dependencies 2022-05-22 11:49:42 +06:00
dc83bbc9aa start 0.2.0 2022-05-22 11:49:20 +06:00
14b56c659b Merge pull request #16 from InsanusMokrassar/0.1.0
0.1.0
2022-05-17 12:10:37 -04:00
99218de932 update telegram 2022-05-17 20:06:29 +06:00
ce14a78e09 fixes 2022-05-17 20:05:53 +06:00
827d9b43f7 update 2022-05-17 15:56:50 +06:00
6aaa801fe0 Update gradle.properties 2022-05-17 00:30:35 +06:00
5cb7d30431 Merge pull request #13 from InsanusMokrassar/0.0.18
0.0.18
2022-04-21 14:42:28 +06:00
c34d7597b4 start (?) 0.0.18 2022-04-20 00:21:23 +06:00
d5ae880e77 add MediaFileActualityChecker 2022-04-20 00:19:25 +06:00
570568cd26 add actuality checker 2022-04-20 00:11:03 +06:00
21 changed files with 484 additions and 223 deletions

View File

@@ -2,7 +2,7 @@ package dev.inmo.tgbotapi.libraries.cache.admins
import dev.inmo.tgbotapi.extensions.utils.asGroupContentMessage import dev.inmo.tgbotapi.extensions.utils.asGroupContentMessage
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.message.abstracts.GroupContentMessage import dev.inmo.tgbotapi.types.message.abstracts.GroupContentMessage
import dev.inmo.tgbotapi.types.message.abstracts.Message import dev.inmo.tgbotapi.types.message.abstracts.Message

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,9 +3,10 @@ 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.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.chat.ExtendedBot
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.message.abstracts.* import dev.inmo.tgbotapi.types.message.abstracts.*
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -28,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
@@ -62,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

@@ -1,62 +1,91 @@
package dev.inmo.tgbotapi.libraries.cache.admins.micro_utils package dev.inmo.tgbotapi.libraries.cache.admins.micro_utils
import com.soywiz.klock.DateTime import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.coroutines.actor import dev.inmo.micro_utils.coroutines.*
import dev.inmo.micro_utils.coroutines.safelyWithoutExceptions
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.tgbotapi.libraries.cache.admins.DefaultAdminsCacheAPIRepo import dev.inmo.tgbotapi.libraries.cache.admins.DefaultAdminsCacheAPIRepo
import dev.inmo.tgbotapi.types.* import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.*
import kotlin.coroutines.* import kotlin.coroutines.*
private sealed class RepoActions<T> { private sealed class RepoActions<T> {
abstract val toReturn: Continuation<T> abstract val deferred: CompletableDeferred<T>
} }
private class GetUpdateDateTimeRepoAction( private class GetUpdateDateTimeRepoAction(
val chatId: ChatId, val chatId: ChatId,
override val toReturn: Continuation<DateTime?> override val deferred: CompletableDeferred<DateTime?>
) : RepoActions<DateTime?>() ) : RepoActions<DateTime?>()
private class GetChatAdminsRepoAction( private class GetChatAdminsRepoAction(
val chatId: ChatId, val chatId: ChatId,
override val toReturn: Continuation<List<AdministratorChatMember>?> override val deferred: CompletableDeferred<List<AdministratorChatMember>?>
) : RepoActions<List<AdministratorChatMember>?>() ) : RepoActions<List<AdministratorChatMember>?>()
private class SetChatAdminsRepoAction( private class SetChatAdminsRepoAction(
val chatId: ChatId, val chatId: ChatId,
val newValue: List<AdministratorChatMember>, val newValue: List<AdministratorChatMember>,
override val toReturn: Continuation<Unit> override val deferred: CompletableDeferred<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
) : DefaultAdminsCacheAPIRepo { ) : DefaultAdminsCacheAPIRepo {
private val actor = scope.actor<RepoActions<*>>(Channel.UNLIMITED) { private val actor = scope.actorAsync<RepoActions<*>>(Channel.UNLIMITED) {
safelyWithoutExceptions { safelyWithoutExceptions(
{ e ->
it.deferred.completeExceptionally(e)
}
) {
when (it) { when (it) {
is GetUpdateDateTimeRepoAction -> it.toReturn.resume( is GetUpdateDateTimeRepoAction -> it.deferred.complete(
updatesRepo.get(it.chatId) ?.let { DateTime(it.toDouble()) } updatesRepo.get(it.chatId) ?.let { DateTime(it.toDouble()) }
) )
is GetChatAdminsRepoAction -> it.toReturn.resume(adminsRepo.getAll(it.chatId)) is GetChatAdminsRepoAction -> it.deferred.complete(adminsRepo.getAll(it.chatId))
is SetChatAdminsRepoAction -> { is SetChatAdminsRepoAction -> {
adminsRepo.clear(it.chatId) adminsRepo.clear(it.chatId)
adminsRepo.set(it.chatId, it.newValue) adminsRepo.set(it.chatId, it.newValue)
updatesRepo.set(it.chatId, DateTime.now().unixMillisLong) updatesRepo.set(it.chatId, DateTime.now().unixMillisLong)
it.toReturn.resume(Unit) it.deferred.complete(Unit)
} }
} }
} }
} }
override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? = suspendCoroutine { override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? {
actor.trySend(GetChatAdminsRepoAction(chatId, it)) val deferred = CompletableDeferred<List<AdministratorChatMember>?>()
actor.trySend(
GetChatAdminsRepoAction(chatId, deferred)
).onFailure {
deferred.completeExceptionally(it ?: IllegalStateException("Something went wrong when tried to add getChatAdmins action"))
}
return deferred.await()
} }
override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> {
actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it)) override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) {
val deferred = CompletableDeferred<Unit>()
actor.trySend(
SetChatAdminsRepoAction(chatId, chatMembers, deferred)
).onFailure {
deferred.completeExceptionally(it ?: IllegalStateException("Something went wrong when tried to add setChatAdmins action"))
}
return deferred.await()
} }
override suspend fun lastUpdate(chatId: ChatId): DateTime? = suspendCoroutine { override suspend fun lastUpdate(chatId: ChatId): DateTime? {
actor.trySend(GetUpdateDateTimeRepoAction(chatId, it)) val deferred = CompletableDeferred<DateTime?>()
actor.trySend(
GetUpdateDateTimeRepoAction(chatId, deferred)
).onFailure {
deferred.completeExceptionally(it ?: IllegalStateException("Something went wrong when tried to add lastUpdate action"))
}
return deferred.await()
} }
} }
fun DefaultAdminsCacheAPIRepo(
adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
scope: CoroutineScope
) = DefaultAdminsCacheAPIRepoImpl(adminsRepo, updatesRepo, scope)

View File

@@ -1,53 +1,48 @@
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.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo 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.ChatMember.AdministratorChatMemberImpl import dev.inmo.tgbotapi.types.chat.member.*
import dev.inmo.tgbotapi.types.ChatMember.CreatorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
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) {
subclass(AdministratorChatMemberImpl::class, AdministratorChatMemberImpl.serializer()) subclass(AdministratorChatMemberImpl::class, AdministratorChatMemberImpl.serializer())
subclass(CreatorChatMember::class, CreatorChatMember.serializer()) subclass(OwnerChatMember::class, OwnerChatMember.serializer())
} }
contextual(AdministratorChatMember::class, PolymorphicSerializer(AdministratorChatMember::class)) contextual(AdministratorChatMember::class, PolymorphicSerializer(AdministratorChatMember::class))
} }
} }
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, ExposedKeyValuesRepo(
DefaultAdminsCacheAPIRepo(
ExposedOneToManyKeyValueRepo(
database, database,
{ long("chatId") }, { long("chatId") },
{ text("member") }, { text("member") },
"AdminsTable" "AdminsTable"
).withMapper<ChatId, AdministratorChatMember, Identifier, String>( ).withMapper<ChatId, AdministratorChatMember, Identifier, String>(
keyFromToTo = { chatId }, keyFromToTo = { chatId },
valueFromToTo = { serializationFormat.encodeToString(this) }, valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(AdministratorChatMember.serializer(), this) },
keyToToFrom = { toChatId() }, keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) } valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(AdministratorChatMember.serializer(), this) }
), ),
ExposedKeyValueRepo( ExposedKeyValueRepo(
database, database,
@@ -62,7 +57,7 @@ fun AdminsCacheAPI(
), ),
scope scope
), ),
DynamicAdminsCacheSettingsAPI( adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo( ExposedKeyValueRepo(
database, database,
{ long("chatId") }, { long("chatId") },
@@ -70,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(AdminsCacheSettings.serializer() , this) },
keyToToFrom = { toChatId() }, keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) } valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(AdminsCacheSettings.serializer() , 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,53 +1,106 @@
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 org.jetbrains.exposed.sql.Database 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 Map<String, Any>.adminsPlugin: AdminsPlugin? val Scope.adminsPlugin: AdminsPlugin?
get() = get("admins") as? AdminsPlugin get() = getOrNull()
val Koin.adminsPlugin: AdminsPlugin?
get() = getOrNull()
@Serializable @Serializable
class AdminsPlugin( class AdminsPlugin : Plugin {
private val chatsSettings: Map<ChatId, AdminsCacheSettings>? = null
) : Plugin {
@Transient @Transient
private val globalAdminsCacheAPI = MutableStateFlow<AdminsCacheAPI?>(null) private val globalAdminsCacheAPI = MutableStateFlow<AdminsCacheAPI?>(null)
@Transient @Transient
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 {
return when (chatsSettings) { val flow = mutex.withLock {
null -> { databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
}
flow.first { it != null }!!
}
else -> globalAdminsCacheAPI.first { it != null }!!
} }
return flow.filterNotNull().first()
} }
override suspend fun BehaviourContext.invoke(database: Database, params: Map<String, Any>) { override fun Module.setupDI(database: Database, params: JsonObject) {
when (chatsSettings) { single { this@AdminsPlugin }
null -> { val scopeQualifier = named("admins plugin scope")
mutex.withLock { single(scopeQualifier) { CoroutineScope(Dispatchers.IO + SupervisorJob()) }
val flow = databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) } single<DefaultAdminsCacheAPIRepo> {
if (flow.value == null) { DefaultAdminsCacheAPIRepoImpl(
flow.value = AdminsCacheAPI(database) ExposedKeyValuesRepo(
} database,
} { long("chatId") },
} { text("member") },
else -> mutex.withLock { "AdminsTable"
globalAdminsCacheAPI.value = AdminsCacheAPI(database) ).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) {
with(koin) {
activateAdminsChangesListening(
get()
)
} }
} }
} }

View File

@@ -2,13 +2,12 @@ package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.DownloadFileStream import dev.inmo.tgbotapi.requests.DownloadFileStream
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.requests.get.GetFile import dev.inmo.tgbotapi.requests.get.GetFile
import dev.inmo.tgbotapi.requests.send.media.* import dev.inmo.tgbotapi.requests.send.media.*
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.InputMedia.* import dev.inmo.tgbotapi.types.media.*
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.MediaContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent import dev.inmo.tgbotapi.types.message.content.MessageContent
import dev.inmo.tgbotapi.utils.asInput import dev.inmo.tgbotapi.utils.asInput
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
@@ -16,10 +15,13 @@ class DefaultMessageContentCache<K>(
private val bot: TelegramBot, private val bot: TelegramBot,
private val filesRefreshingChatId: ChatId, private val filesRefreshingChatId: ChatId,
private val simpleMessageContentCache: MessagesSimpleCache<K>, private val simpleMessageContentCache: MessagesSimpleCache<K>,
private val mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId)
),
private val messagesFilesCache: MessagesFilesCache<K> = InMemoryMessagesFilesCache() private val messagesFilesCache: MessagesFilesCache<K> = InMemoryMessagesFilesCache()
) : MessageContentCache<K> { ) : MessageContentCache<K> {
override suspend fun save(content: MessageContent): K { override suspend fun save(k: K, content: MessageContent) {
return when (content) { when (content) {
is MediaContent -> { is MediaContent -> {
val extendedInfo = bot.execute( val extendedInfo = bot.execute(
GetFile(content.media.fileId) GetFile(content.media.fileId)
@@ -30,80 +32,69 @@ class DefaultMessageContentCache<K>(
) )
) )
save(content, extendedInfo.fileName) { save(k, content, extendedInfo.fileName) {
allocator.invoke().asInput() allocator.invoke().asInput()
} }
} }
else -> simpleMessageContentCache.add(content) else -> simpleMessageContentCache.set(k, content)
} }
} }
override suspend fun save( override suspend fun save(
k: K,
content: MediaContent, content: MediaContent,
filename: String, filename: String,
inputAllocator: suspend () -> Input inputAllocator: suspend () -> Input
): K { ) {
val key = simpleMessageContentCache.add(content) simpleMessageContentCache.set(k, content)
runCatching { runCatching {
messagesFilesCache.set(key, filename, inputAllocator) messagesFilesCache.set(k, filename, inputAllocator)
}.onFailure { }.onFailure {
simpleMessageContentCache.remove(key) simpleMessageContentCache.remove(k)
}.onSuccess {
with(mediaFileActualityChecker) {
bot.saved(content)
}
} }
return key
} }
override suspend fun get(k: K): MessageContent? { override suspend fun get(k: K): MessageContent? {
val savedSimpleContent = simpleMessageContentCache.get(k) ?: return null val savedSimpleContent = simpleMessageContentCache.get(k) ?: return null
if (savedSimpleContent is MediaContent) { if (savedSimpleContent is MediaContent && !with(mediaFileActualityChecker) { bot.isActual(savedSimpleContent) }) {
runCatching { val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)")
bot.execute(GetFile(savedSimpleContent.media.fileId)) val newContent = bot.execute(
}.onFailure { when (savedSimpleContent.asTelegramMedia()) {
val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)") is TelegramMediaAnimation -> SendAnimation(
val newContent = bot.execute( filesRefreshingChatId,
when (savedSimpleContent.asInputMedia()) { savedFileContentAllocator,
is InputMediaAnimation -> SendAnimation( disableNotification = true
filesRefreshingChatId, )
MultipartFile( is TelegramMediaAudio -> SendAudio(
savedFileContentAllocator filesRefreshingChatId,
), savedFileContentAllocator,
disableNotification = true disableNotification = true
) )
is InputMediaAudio -> SendAudio( is TelegramMediaVideo -> SendVideo(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator disableNotification = true
), )
disableNotification = true is TelegramMediaDocument -> SendDocument(
) filesRefreshingChatId,
is InputMediaVideo -> SendVideo( savedFileContentAllocator,
filesRefreshingChatId, disableNotification = true
MultipartFile( )
savedFileContentAllocator is TelegramMediaPhoto -> SendPhoto(
), filesRefreshingChatId,
disableNotification = true savedFileContentAllocator,
) disableNotification = true
is InputMediaDocument -> SendDocument( )
filesRefreshingChatId, }
MultipartFile( )
savedFileContentAllocator
),
disableNotification = true
)
is InputMediaPhoto -> SendPhoto(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
}
)
simpleMessageContentCache.update(k, newContent.content) simpleMessageContentCache.update(k, newContent.content)
return newContent.content return newContent.content
}
} }
return savedSimpleContent return savedSimpleContent
} }
@@ -122,7 +113,10 @@ class DefaultMessageContentCache<K>(
bot: TelegramBot, bot: TelegramBot,
filesRefreshingChatId: ChatId, filesRefreshingChatId: ChatId,
simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(), simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(),
mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId)
),
messagesFilesCache: MessagesFilesCache<String> = InMemoryMessagesFilesCache() messagesFilesCache: MessagesFilesCache<String> = InMemoryMessagesFilesCache()
) = DefaultMessageContentCache(bot, filesRefreshingChatId, simpleMessageContentCache, messagesFilesCache) ) = DefaultMessageContentCache(bot, filesRefreshingChatId, simpleMessageContentCache, mediaFileActualityChecker, messagesFilesCache)
} }
} }

View File

@@ -0,0 +1,55 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import com.soywiz.klock.DateTime
import com.soywiz.klock.milliseconds
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.DeleteMessage
import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MilliSeconds
import dev.inmo.tgbotapi.types.message.content.MediaContent
fun interface MediaFileActualityChecker {
suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean
suspend fun TelegramBot.saved(mediaContent: MediaContent) {}
class Default(
private val checkingChatId: ChatId
) : MediaFileActualityChecker {
override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean {
return runCatching {
execute(mediaContent.createResend(checkingChatId)).also { sentMessage ->
execute(DeleteMessage(sentMessage.chat.id, sentMessage.messageId))
}
}.isSuccess
}
}
class WithDelay(
private val underhoodChecker: MediaFileActualityChecker,
private val checkingDelay: MilliSeconds = 24 * 60 * 60 * 1000L // one day
) : MediaFileActualityChecker {
private val fileIdChecksMap = mutableMapOf<FileId, DateTime>()
private val checkingDelayTimeSpan = checkingDelay.milliseconds
override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean {
val now = DateTime.now()
val lastCheck = fileIdChecksMap[mediaContent.media.fileId]
return if (lastCheck == null || now - lastCheck > checkingDelayTimeSpan) {
with(underhoodChecker) {
isActual(mediaContent)
}.also {
if (it) {
fileIdChecksMap[mediaContent.media.fileId] = now
}
}
} else {
true
}
}
override suspend fun TelegramBot.saved(mediaContent: MediaContent) {
fileIdChecksMap[mediaContent.media.fileId] = DateTime.now()
}
}
}

View File

@@ -1,16 +1,18 @@
package dev.inmo.tgbotapi.libraries.cache.media.common package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.MediaContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent import dev.inmo.tgbotapi.types.message.content.MessageContent
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
interface MessageContentCache<K> { interface MessageContentCache<K> {
suspend fun save(content: MessageContent): K suspend fun save(k: K, content: MessageContent)
suspend fun save( suspend fun save(
k: K,
content: MediaContent, content: MediaContent,
filename: String, filename: String,
inputAllocator: suspend () -> Input inputAllocator: suspend () -> Input
): K )
suspend fun get(k: K): MessageContent? suspend fun get(k: K): MessageContent?
suspend fun contains(k: K): Boolean suspend fun contains(k: K): Boolean
suspend fun remove(k: K) suspend fun remove(k: K)

View File

@@ -1,13 +1,11 @@
package dev.inmo.tgbotapi.libraries.cache.media.common package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.utils.StorageFile
import io.ktor.utils.io.core.* import io.ktor.utils.io.core.*
interface MessagesFilesCache<K> { interface MessagesFilesCache<K> {
suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input) suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input)
suspend fun get(k: K): StorageFile? suspend fun get(k: K): MultipartFile?
suspend fun remove(k: K) suspend fun remove(k: K)
suspend fun contains(k: K): Boolean suspend fun contains(k: K): Boolean
} }
@@ -18,16 +16,18 @@ interface MessagesFilesCache<K> {
* disks-oriented one * disks-oriented one
*/ */
class InMemoryMessagesFilesCache<K> : MessagesFilesCache<K> { class InMemoryMessagesFilesCache<K> : MessagesFilesCache<K> {
private val map = mutableMapOf<K, StorageFile>() private val map = mutableMapOf<K, MultipartFile>()
override suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input) { override suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input) {
map[k] = StorageFile( val input = inputAllocator()
filename, map[k] = MultipartFile(
inputAllocator().readBytes() filename
) ) {
input
}
} }
override suspend fun get(k: K): StorageFile? { override suspend fun get(k: K): MultipartFile? {
return map[k] return map[k]
} }

View File

@@ -1,10 +1,9 @@
package dev.inmo.tgbotapi.libraries.cache.media.common package dev.inmo.tgbotapi.libraries.cache.media.common
import com.benasher44.uuid.uuid4 import dev.inmo.tgbotapi.types.message.content.MessageContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
interface MessagesSimpleCache<K> { interface MessagesSimpleCache<K> {
suspend fun add(content: MessageContent): K suspend fun set(k: K, content: MessageContent)
suspend fun update(k: K, content: MessageContent): Boolean suspend fun update(k: K, content: MessageContent): Boolean
suspend fun get(k: K): MessageContent? suspend fun get(k: K): MessageContent?
suspend fun remove(k: K) suspend fun remove(k: K)
@@ -16,17 +15,14 @@ interface MessagesSimpleCache<K> {
* start of application creation with usage of [MessageContentCache] with aim to replace this realization by some * start of application creation with usage of [MessageContentCache] with aim to replace this realization by some
* disks-oriented one * disks-oriented one
*/ */
class InMemoryMessagesSimpleCache<K>( class InMemoryMessagesSimpleCache<K> : MessagesSimpleCache<K> {
private val keyGenerator: () -> K
) : MessagesSimpleCache<K> {
private val map = mutableMapOf<K, MessageContent>() private val map = mutableMapOf<K, MessageContent>()
override suspend fun add( override suspend fun set(
k: K,
content: MessageContent content: MessageContent
): K { ) {
val key = keyGenerator() map[k] = content
map[key] = content
return key
} }
override suspend fun update( override suspend fun update(
@@ -54,10 +50,4 @@ class InMemoryMessagesSimpleCache<K>(
override suspend fun contains(k: K): Boolean { override suspend fun contains(k: K): Boolean {
return map.contains(k) return map.contains(k)
} }
companion object {
operator fun invoke() = InMemoryMessagesSimpleCache {
uuid4().toString()
}
}
} }

View File

@@ -1,6 +1,6 @@
package dev.inmo.tgbotapi.libraries.cache.media.common package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.utils.* import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asInput import io.ktor.utils.io.streams.asInput
@@ -11,16 +11,14 @@ class InFilesMessagesFilesCache<K>(
private val folderFile: File, private val folderFile: File,
private val filePrefixBuilder: (K) -> String private val filePrefixBuilder: (K) -> String
) : MessagesFilesCache<K> { ) : MessagesFilesCache<K> {
private val K.storageFile: StorageFile? private val K.multipartFile: MultipartFile?
get() { get() {
val prefix = filePrefix(this) val prefix = filePrefix(this)
val filename = folderFile.list() ?.firstOrNull { it.startsWith(prefix) } ?: return null val filename = folderFile.list() ?.firstOrNull { it.startsWith(prefix) } ?: return null
val file = File(folderFile, filename) val file = File(folderFile, filename)
val storageFileFilename = file.name.removePrefix("$prefix ") val storageFileFilename = file.name.removePrefix("$prefix ")
return StorageFile( return MultipartFile(storageFileFilename) {
StorageFileInfo(storageFileFilename)
) {
file.inputStream().asInput() file.inputStream().asInput()
} }
} }
@@ -48,8 +46,8 @@ class InFilesMessagesFilesCache<K>(
} }
} }
override suspend fun get(k: K): StorageFile? { override suspend fun get(k: K): MultipartFile? {
return k.storageFile return k.multipartFile
} }
override suspend fun remove(k: K) { override suspend fun remove(k: K) {

View File

@@ -1,12 +1,11 @@
package dev.inmo.tgbotapi.libraries.cache.media.micro_utils package dev.inmo.tgbotapi.libraries.cache.media.micro_utils
import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.repos.* import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.mappers.withMapper import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.tgbotapi.libraries.cache.media.common.MessagesSimpleCache import dev.inmo.tgbotapi.libraries.cache.media.common.MessagesSimpleCache
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent import dev.inmo.tgbotapi.types.message.content.MessageContent
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.PairSerializer import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
@@ -16,14 +15,10 @@ import kotlin.js.JsName
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
class SimpleKeyValueMessageContentCache<K>( class SimpleKeyValueMessageContentCache<K>(
private val keyValueRepo: KeyValueRepo<K, MessageContent>, private val keyValueRepo: KeyValueRepo<K, MessageContent>
private val keyGenerator: () -> K
) : MessagesSimpleCache<K> { ) : MessagesSimpleCache<K> {
override suspend fun add(content: MessageContent): K { override suspend fun set(k: K, content: MessageContent) {
val key = keyGenerator() keyValueRepo.set(k, content)
keyValueRepo.set(key, content)
return key
} }
override suspend fun update(k: K, content: MessageContent): Boolean { override suspend fun update(k: K, content: MessageContent): Boolean {
@@ -48,12 +43,6 @@ class SimpleKeyValueMessageContentCache<K>(
override suspend fun remove(k: K) { override suspend fun remove(k: K) {
keyValueRepo.unset(k) keyValueRepo.unset(k)
} }
companion object {
operator fun invoke(
keyValueRepo: KeyValueRepo<String, MessageContent>
) = SimpleKeyValueMessageContentCache(keyValueRepo) { uuid4().toString() }
}
} }
val chatIdToMessageIdentifierSerializer = PairSerializer( val chatIdToMessageIdentifierSerializer = PairSerializer(
@@ -63,6 +52,8 @@ val chatIdToMessageIdentifierSerializer = PairSerializer(
val messageContentSerializer = PolymorphicSerializer<MessageContent>(MessageContent::class) val messageContentSerializer = PolymorphicSerializer<MessageContent>(MessageContent::class)
inline fun <K> KeyValueRepo<K, MessageContent>.asMessageContentCache() = SimpleKeyValueMessageContentCache(this)
inline fun KeyValueRepo<String, String>.asMessageContentCache( inline fun KeyValueRepo<String, String>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> StringFormat = { Json { serializersModule = it } } serialFormatCreator: (SerializersModule) -> StringFormat = { Json { serializersModule = it } }
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> { ): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {

View File

@@ -6,32 +6,32 @@ kotlin.incremental.js=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
kotlin_version=1.6.10 kotlin_version=1.7.10
kotlin_serialisation_core_version=1.3.2 kotlin_serialisation_core_version=1.4.0
github_release_plugin_version=2.2.12 github_release_plugin_version=2.4.1
tgbotapi_version=0.38.12 tgbotapi_version=3.2.6
micro_utils_version=0.9.20 micro_utils_version=0.12.13
exposed_version=0.37.3 exposed_version=0.39.2
plagubot_version=0.5.1 plagubot_version=2.3.3
# ANDROID # ANDROID
android_minSdkVersion=21 android_minSdkVersion=21
android_compileSdkVersion=32 android_compileSdkVersion=32
android_buildToolsVersion=32.0.0 android_buildToolsVersion=32.0.0
dexcount_version=3.0.1 dexcount_version=3.1.0
junit_version=4.12 junit_version=4.12
test_ext_junit_version=1.1.2 test_ext_junit_version=1.1.3
espresso_core=3.3.0 espresso_core=3.4.0
# Dokka # Dokka
dokka_version=1.6.10 dokka_version=1.7.10
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.0.17 version=0.5.4
android_code_version=17 android_code_version=31

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -5,7 +5,11 @@ apply from: "$publishGradlePath"
kotlin { kotlin {
jvm { jvm {
compilations.main.kotlinOptions.useIR = true compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
} }
sourceSets { sourceSets {

View File

@@ -4,7 +4,13 @@ project.group = "$group"
apply from: "$publishGradlePath" apply from: "$publishGradlePath"
kotlin { kotlin {
jvm() jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
js (IR) { js (IR) {
browser() browser()
nodejs() nodejs()