Compare commits

..

4 Commits

Author SHA1 Message Date
b45bd3192a add dsl preview 2021-06-19 13:57:34 +06:00
c4e18ad25f fixes in architecture and adding playable main 2021-06-19 02:20:47 +06:00
6f17a53146 state to revert 2021-06-19 01:16:39 +06:00
f04f065ac5 start working with fsm 2021-06-18 14:09:02 +06:00
43 changed files with 390 additions and 823 deletions

View File

@@ -7,10 +7,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 11
- name: Fix android 32.0.0 dx
continue-on-error: true
run: cd /usr/local/lib/android/sdk/build-tools/32.0.0/ && mv d8 dx && cd lib && mv d8.jar dx.jar
java-version: 1.8
- name: Update version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

1
.gitignore vendored
View File

@@ -11,6 +11,5 @@ out/
secret.gradle
local.properties
kotlin-js-store/
publishing.sh

View File

@@ -1,5 +1,6 @@
buildscript {
repositories {
jcenter()
google()
mavenCentral()
mavenLocal()
@@ -7,7 +8,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath 'com.android.tools.build:gradle:4.1.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
@@ -18,8 +19,10 @@ buildscript {
allprojects {
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url "https://kotlin.bintray.com/kotlinx" }
}
}

View File

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

View File

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

View File

@@ -1,44 +0,0 @@
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

@@ -1,37 +0,0 @@
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,10 +3,9 @@ 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.members.getChatMember
import dev.inmo.tgbotapi.extensions.api.chat.get.getChatAdministrators
import dev.inmo.tgbotapi.types.*
import dev.inmo.tgbotapi.types.chat.ExtendedBot
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.message.abstracts.*
import kotlinx.serialization.Serializable
@@ -29,37 +28,28 @@ class DefaultAdminsCacheAPI(
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>? {
val settings = settingsAPI.getChatSettings(chatId)
val lastUpdate = repo.lastUpdate(chatId)
return when {
settings == null -> null
settings.refreshOnCacheCalls &&
settings.refreshOnRequests &&
(lastUpdate == null || (DateTime.now() - lastUpdate).seconds > settings.refreshSeconds) -> {
bot.updateAdmins(chatId, repo, getBotInfo())
triggerUpdate(chatId)
}
else -> repo.getChatAdmins(chatId) ?: bot.updateAdmins(chatId, repo, getBotInfo())
else -> repo.getChatAdmins(chatId) ?: triggerUpdate(chatId)
}
}
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
@@ -72,4 +62,5 @@ class DefaultAdminsCacheAPI(
}
override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI
}

View File

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

View File

@@ -1,21 +0,0 @@
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

@@ -6,7 +6,7 @@ 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.chat.member.AdministratorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.Channel
import kotlin.coroutines.*
@@ -28,7 +28,7 @@ private class SetChatAdminsRepoAction(
override val toReturn: Continuation<Unit>
) : RepoActions<Unit>()
class DefaultAdminsCacheAPIRepoImpl(
class DefaultAdminsCacheAPIRepo(
private val adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
private val updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
private val scope: CoroutineScope
@@ -51,19 +51,12 @@ class DefaultAdminsCacheAPIRepoImpl(
}
override suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? = suspendCoroutine {
actor.trySend(GetChatAdminsRepoAction(chatId, it))
actor.offer(GetChatAdminsRepoAction(chatId, it))
}
override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> {
actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it))
actor.offer(SetChatAdminsRepoAction(chatId, chatMembers, it))
}
override suspend fun lastUpdate(chatId: ChatId): DateTime? = suspendCoroutine {
actor.trySend(GetUpdateDateTimeRepoAction(chatId, it))
actor.offer(GetUpdateDateTimeRepoAction(chatId, it))
}
}
fun DefaultAdminsCacheAPIRepo(
adminsRepo: KeyValuesRepo<ChatId, AdministratorChatMember>,
updatesRepo: KeyValueRepo<ChatId, MilliSeconds>,
scope: CoroutineScope
) = DefaultAdminsCacheAPIRepoImpl(adminsRepo, updatesRepo, scope)

View File

@@ -1,48 +1,53 @@
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.exposed.onetomany.ExposedOneToManyKeyValueRepo
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.DefaultAdminsCacheAPIRepoImpl
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.*
import dev.inmo.tgbotapi.types.chat.member.*
import dev.inmo.tgbotapi.types.ChatMember.AdministratorChatMemberImpl
import dev.inmo.tgbotapi.types.ChatMember.CreatorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
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
val telegramAdminsSerializationFormat = Json {
private val serializationFormat = Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(AdministratorChatMember::class) {
subclass(AdministratorChatMemberImpl::class, AdministratorChatMemberImpl.serializer())
subclass(OwnerChatMember::class, OwnerChatMember.serializer())
subclass(CreatorChatMember::class, CreatorChatMember.serializer())
}
contextual(AdministratorChatMember::class, PolymorphicSerializer(AdministratorChatMember::class))
}
}
fun BehaviourContext.createAdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this)
fun TelegramBot.createAdminsCacheAPI(
fun AdminsCacheAPI(
bot: TelegramBot,
database: Database,
scope: CoroutineScope,
defaultAdminsCacheAPIRepo: DefaultAdminsCacheAPIRepo = DefaultAdminsCacheAPIRepoImpl(
ExposedKeyValuesRepo(
scope: CoroutineScope
) : AdminsCacheAPI = DefaultAdminsCacheAPI(
bot,
DefaultAdminsCacheAPIRepo(
ExposedOneToManyKeyValueRepo(
database,
{ long("chatId") },
{ text("member") },
"AdminsTable"
).withMapper<ChatId, AdministratorChatMember, Identifier, String>(
keyFromToTo = { chatId },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
valueFromToTo = { serializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() },
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
valueToToFrom = { serializationFormat.decodeFromString(this) }
),
ExposedKeyValueRepo(
database,
@@ -57,7 +62,7 @@ fun TelegramBot.createAdminsCacheAPI(
),
scope
),
adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo(
database,
{ long("chatId") },
@@ -65,19 +70,12 @@ fun TelegramBot.createAdminsCacheAPI(
"DynamicAdminsCacheSettingsAPI"
).withMapper<ChatId, AdminsCacheSettings, Identifier, String>(
keyFromToTo = { chatId },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
valueFromToTo = { serializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() },
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
valueToToFrom = { serializationFormat.decodeFromString(this) }
),
scope
)
) = DefaultAdminsCacheAPI(this, defaultAdminsCacheAPIRepo, adminsCacheSettingsAPI)
fun AdminsCacheAPI(
bot: TelegramBot,
database: Database,
scope: CoroutineScope
) : AdminsCacheAPI = bot.createAdminsCacheAPI(
database,
scope
)
fun BehaviourContext.AdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this)

View File

@@ -1,106 +1,53 @@
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.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 dev.inmo.tgbotapi.types.ChatId
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
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()
val Koin.adminsPlugin: AdminsPlugin?
get() = getOrNull()
val Map<String, Any>.adminsPlugin: AdminsPlugin?
get() = get("admins") as? AdminsPlugin
@Serializable
class AdminsPlugin : Plugin {
class AdminsPlugin(
private val chatsSettings: Map<ChatId, AdminsCacheSettings>? = null
) : Plugin {
@Transient
private val globalAdminsCacheAPI = MutableStateFlow<AdminsCacheAPI?>(null)
@Transient
private val databaseToAdminsCacheAPI = mutableMapOf<Database, MutableStateFlow<AdminsCacheAPI?>>()
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) }
return when (chatsSettings) {
null -> {
val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
}
flow.first { it != null }!!
}
else -> globalAdminsCacheAPI.first { it != null }!!
}
return flow.filterNotNull().first()
}
override fun Module.setupDI(database: Database, params: JsonObject) {
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) {
with(koin) {
activateAdminsChangesListening(
get()
)
override suspend fun BehaviourContext.invoke(database: Database, params: Map<String, Any>) {
when (chatsSettings) {
null -> {
mutex.withLock {
val flow = databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
if (flow.value == null) {
flow.value = AdminsCacheAPI(database)
}
}
}
else -> mutex.withLock {
globalAdminsCacheAPI.value = AdminsCacheAPI(database)
}
}
}
}

View File

@@ -1,122 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.DownloadFileStream
import dev.inmo.tgbotapi.requests.get.GetFile
import dev.inmo.tgbotapi.requests.send.media.*
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.media.*
import dev.inmo.tgbotapi.types.message.content.MediaContent
import dev.inmo.tgbotapi.types.message.content.MessageContent
import dev.inmo.tgbotapi.utils.asInput
import io.ktor.utils.io.core.Input
class DefaultMessageContentCache<K>(
private val bot: TelegramBot,
private val filesRefreshingChatId: ChatId,
private val simpleMessageContentCache: MessagesSimpleCache<K>,
private val mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId)
),
private val messagesFilesCache: MessagesFilesCache<K> = InMemoryMessagesFilesCache()
) : MessageContentCache<K> {
override suspend fun save(k: K, content: MessageContent) {
when (content) {
is MediaContent -> {
val extendedInfo = bot.execute(
GetFile(content.media.fileId)
)
val allocator = bot.execute(
DownloadFileStream(
extendedInfo.filePath
)
)
save(k, content, extendedInfo.fileName) {
allocator.invoke().asInput()
}
}
else -> simpleMessageContentCache.set(k, content)
}
}
override suspend fun save(
k: K,
content: MediaContent,
filename: String,
inputAllocator: suspend () -> Input
) {
simpleMessageContentCache.set(k, content)
runCatching {
messagesFilesCache.set(k, filename, inputAllocator)
}.onFailure {
simpleMessageContentCache.remove(k)
}.onSuccess {
with(mediaFileActualityChecker) {
bot.saved(content)
}
}
}
override suspend fun get(k: K): MessageContent? {
val savedSimpleContent = simpleMessageContentCache.get(k) ?: return null
if (savedSimpleContent is MediaContent && !with(mediaFileActualityChecker) { bot.isActual(savedSimpleContent) }) {
val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)")
val newContent = bot.execute(
when (savedSimpleContent.asTelegramMedia()) {
is TelegramMediaAnimation -> SendAnimation(
filesRefreshingChatId,
savedFileContentAllocator,
disableNotification = true
)
is TelegramMediaAudio -> SendAudio(
filesRefreshingChatId,
savedFileContentAllocator,
disableNotification = true
)
is TelegramMediaVideo -> SendVideo(
filesRefreshingChatId,
savedFileContentAllocator,
disableNotification = true
)
is TelegramMediaDocument -> SendDocument(
filesRefreshingChatId,
savedFileContentAllocator,
disableNotification = true
)
is TelegramMediaPhoto -> SendPhoto(
filesRefreshingChatId,
savedFileContentAllocator,
disableNotification = true
)
}
)
simpleMessageContentCache.update(k, newContent.content)
return newContent.content
}
return savedSimpleContent
}
override suspend fun contains(k: K): Boolean {
return simpleMessageContentCache.contains(k)
}
override suspend fun remove(k: K) {
simpleMessageContentCache.remove(k)
messagesFilesCache.remove(k)
}
companion object {
operator fun invoke(
bot: TelegramBot,
filesRefreshingChatId: ChatId,
simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(),
mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId)
),
messagesFilesCache: MessagesFilesCache<String> = InMemoryMessagesFilesCache()
) = DefaultMessageContentCache(bot, filesRefreshingChatId, simpleMessageContentCache, mediaFileActualityChecker, messagesFilesCache)
}
}

View File

@@ -1,55 +0,0 @@
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,19 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.message.content.MediaContent
import dev.inmo.tgbotapi.types.message.content.MessageContent
import io.ktor.utils.io.core.Input
interface MessageContentCache<K> {
suspend fun save(k: K, content: MessageContent)
suspend fun save(
k: K,
content: MediaContent,
filename: String,
inputAllocator: suspend () -> Input
)
suspend fun get(k: K): MessageContent?
suspend fun contains(k: K): Boolean
suspend fun remove(k: K)
}

View File

@@ -1,41 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import io.ktor.utils.io.core.*
interface MessagesFilesCache<K> {
suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input)
suspend fun get(k: K): MultipartFile?
suspend fun remove(k: K)
suspend fun contains(k: K): Boolean
}
/**
* It is not recommended to use in production realization of [MessagesFilesCache] which has been created for fast
* start of application creation with usage of [MessageContentCache] with aim to replace this realization by some
* disks-oriented one
*/
class InMemoryMessagesFilesCache<K> : MessagesFilesCache<K> {
private val map = mutableMapOf<K, MultipartFile>()
override suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input) {
val input = inputAllocator()
map[k] = MultipartFile(
filename
) {
input
}
}
override suspend fun get(k: K): MultipartFile? {
return map[k]
}
override suspend fun remove(k: K) {
map.remove(k)
}
override suspend fun contains(k: K): Boolean {
return map.contains(k)
}
}

View File

@@ -1,53 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.message.content.MessageContent
interface MessagesSimpleCache<K> {
suspend fun set(k: K, content: MessageContent)
suspend fun update(k: K, content: MessageContent): Boolean
suspend fun get(k: K): MessageContent?
suspend fun remove(k: K)
suspend fun contains(k: K): Boolean
}
/**
* It is not recommended to use in production realization of [MessagesFilesCache] which has been created for fast
* start of application creation with usage of [MessageContentCache] with aim to replace this realization by some
* disks-oriented one
*/
class InMemoryMessagesSimpleCache<K> : MessagesSimpleCache<K> {
private val map = mutableMapOf<K, MessageContent>()
override suspend fun set(
k: K,
content: MessageContent
) {
map[k] = content
}
override suspend fun update(
k: K,
content: MessageContent
): Boolean {
return map.runCatching {
if (contains(k)) {
put(k, content)
true
} else {
false
}
}.getOrDefault(false)
}
override suspend fun get(k: K): MessageContent? {
return map[k]
}
override suspend fun remove(k: K) {
map.remove(k)
}
override suspend fun contains(k: K): Boolean {
return map.contains(k)
}
}

View File

@@ -1,72 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.requests.abstracts.MultipartFile
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.streams.asInput
import io.ktor.utils.io.streams.asOutput
import java.io.File
class InFilesMessagesFilesCache<K>(
private val folderFile: File,
private val filePrefixBuilder: (K) -> String
) : MessagesFilesCache<K> {
private val K.multipartFile: MultipartFile?
get() {
val prefix = filePrefix(this)
val filename = folderFile.list() ?.firstOrNull { it.startsWith(prefix) } ?: return null
val file = File(folderFile, filename)
val storageFileFilename = file.name.removePrefix("$prefix ")
return MultipartFile(storageFileFilename) {
file.inputStream().asInput()
}
}
init {
require(!folderFile.isFile) { "Folder of messages files cache can't be file, but was $folderFile" }
folderFile.mkdirs()
}
private fun filePrefix(k: K): String = filePrefixBuilder(k)
private fun fileName(k: K, filename: String): String {
return "${filePrefix(k)} $filename"
}
override suspend fun set(k: K, filename: String, inputAllocator: suspend () -> Input) {
val fullFileName = fileName(k, filename)
val file = File(folderFile, fullFileName).apply {
delete()
}
inputAllocator().use { input ->
file.outputStream().asOutput().use { output ->
input.copyTo(output)
}
}
}
override suspend fun get(k: K): MultipartFile? {
return k.multipartFile
}
override suspend fun remove(k: K) {
val prefix = filePrefix(k)
folderFile.listFiles() ?.forEach {
if (it.name.startsWith(prefix)) {
it.delete()
}
}
}
override suspend fun contains(k: K): Boolean {
val prefix = filePrefix(k)
return folderFile.list() ?.any { it.startsWith(prefix) } == true
}
companion object {
operator fun invoke(folderFile: File) = InFilesMessagesFilesCache<String>(
folderFile
) { it }
}
}

View File

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

View File

@@ -1,95 +0,0 @@
package dev.inmo.tgbotapi.libraries.cache.media.micro_utils
import dev.inmo.micro_utils.repos.*
import dev.inmo.micro_utils.repos.mappers.withMapper
import dev.inmo.tgbotapi.libraries.cache.media.common.MessagesSimpleCache
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.types.message.content.MessageContent
import kotlinx.serialization.*
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import kotlin.js.JsName
import kotlin.jvm.JvmName
class SimpleKeyValueMessageContentCache<K>(
private val keyValueRepo: KeyValueRepo<K, MessageContent>
) : MessagesSimpleCache<K> {
override suspend fun set(k: K, content: MessageContent) {
keyValueRepo.set(k, content)
}
override suspend fun update(k: K, content: MessageContent): Boolean {
return keyValueRepo.runCatching {
if (contains(k)) {
keyValueRepo.set(k, content)
true
} else {
false
}
}.getOrDefault(false)
}
override suspend fun get(k: K): MessageContent? {
return keyValueRepo.get(k)
}
override suspend fun contains(k: K): Boolean {
return keyValueRepo.contains(k)
}
override suspend fun remove(k: K) {
keyValueRepo.unset(k)
}
}
val chatIdToMessageIdentifierSerializer = PairSerializer(
ChatId.serializer(),
MessageIdentifier.serializer()
)
val messageContentSerializer = PolymorphicSerializer<MessageContent>(MessageContent::class)
inline fun <K> KeyValueRepo<K, MessageContent>.asMessageContentCache() = SimpleKeyValueMessageContentCache(this)
inline fun KeyValueRepo<String, String>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> StringFormat = { Json { serializersModule = it } }
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, String, String>(
{ serialFormat.encodeToString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToString(messageContentSerializer, this) },
{ serialFormat.decodeFromString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.decodeFromString(messageContentSerializer, this) },
)
}
@JvmName("stringsKeyValueAsHexMessageContentCache")
@JsName("stringsKeyValueAsHexMessageContentCache")
inline fun KeyValueRepo<String, String>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, String, String>(
{ serialFormat.encodeToHexString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToHexString(messageContentSerializer, this) },
{ serialFormat.decodeFromHexString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.decodeFromHexString(messageContentSerializer, this) },
)
}
@JvmName("bytesKeyValueAsMessageContentCache")
@JsName("bytesKeyValueAsMessageContentCache")
inline fun KeyValueRepo<ByteArray, ByteArray>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, ByteArray, ByteArray>(
{ serialFormat.encodeToByteArray(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToByteArray(messageContentSerializer, this) },
{ serialFormat.decodeFromByteArray(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.decodeFromByteArray(messageContentSerializer, this) },
)
}

View File

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

8
cache/media/build.gradle vendored Normal file
View File

@@ -0,0 +1,8 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"

View File

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

View File

@@ -10,10 +10,8 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:micro_utils.repos.common:$micro_utils_version"
api project(":tgbotapi.libraries.cache.content.common")
api "dev.inmo:micro_utils.coroutines:$micro_utils_version"
}
}
}
}

View File

@@ -0,0 +1,17 @@
package dev.inmo.tgbotapi.libraries.fsm.core
sealed interface State {
val context: Any
}
/**
* Use this state as parent of your state in case you want to avoid saving of this state in queue for [context] if this
* queue is not empty
*/
interface ImmediateOrNeverState : State
/**
* Use this state as parent of your state in case you want to keep saving of this state in queue for [context] if this
* queue is not empty
*/
interface QueueableState : State

View File

@@ -0,0 +1,15 @@
package dev.inmo.tgbotapi.libraries.fsm.core
import kotlin.reflect.KClass
class StateHandlerHolder<I : State>(
private val inputKlass: KClass<I>,
private val strict: Boolean = false,
private val delegateTo: StatesHandler<I>
) : StatesHandler<State> {
fun checkHandleable(state: State) = state::class == inputKlass || (!strict && inputKlass.isInstance(state))
override suspend fun StatesMachine.handleState(state: State): State? {
return delegateTo.run { handleState(state as I) }
}
}

View File

@@ -0,0 +1,5 @@
package dev.inmo.tgbotapi.libraries.fsm.core
fun interface StatesHandler<I : State> {
suspend fun StatesMachine.handleState(state: I): State?
}

View File

@@ -0,0 +1,46 @@
package dev.inmo.tgbotapi.libraries.fsm.core
import dev.inmo.micro_utils.coroutines.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.asFlow
private suspend fun <I : State> StatesMachine.launchStateHandling(
state: State,
handlers: List<StateHandlerHolder<out I>>
): State? {
return handlers.firstOrNull { it.checkHandleable(state) } ?.run {
handleState(state)
}
}
class StatesMachine (
private val statesManager: StatesManager,
private val handlers: List<StateHandlerHolder<*>>
) : StatesHandler<State> {
override suspend fun StatesMachine.handleState(state: State): State? = launchStateHandling(state, handlers)
fun start(scope: CoroutineScope): Job = scope.launchSafelyWithoutExceptions {
val statePerformer: suspend (State) -> Unit = { state: State ->
val newState = launchStateHandling(state, handlers)
if (newState != null) {
statesManager.update(state, newState)
} else {
statesManager.endChain(state)
}
}
statesManager.onStartChain.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it) }
}
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
launch { statePerformer(it.second) }
}
statesManager.getActiveStates().forEach {
launch { statePerformer(it) }
}
}
suspend fun startChain(state: State) {
statesManager.startChain(state)
}
}

View File

@@ -0,0 +1,92 @@
package dev.inmo.tgbotapi.libraries.fsm.core
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
interface StatesManager {
val onChainStateUpdated: Flow<Pair<State, State>>
val onStartChain: Flow<State>
val onEndChain: Flow<State>
/**
* Must set current set using [State.context]
*/
suspend fun update(old: State, new: State)
/**
* Starts chain with [state] as first [State]. May returns false in case of [State.context] of [state] is already
* busy by the other [State]
*/
suspend fun startChain(state: State)
/**
* Ends chain with context from [state]. In case when [State.context] of [state] is absent, [state] should be just
* ignored
*/
suspend fun endChain(state: State)
suspend fun getActiveStates(): List<State>
}
/**
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
* new state by using [endChain] with that state
*/
class InMemoryStatesManager(
private val onContextsConflictResolver: suspend (old: State, new: State, currentNew: State) -> Boolean = { _, _, _ -> true }
) : StatesManager {
private val _onChainStateUpdated = MutableSharedFlow<Pair<State, State>>(0)
override val onChainStateUpdated: Flow<Pair<State, State>> = _onChainStateUpdated.asSharedFlow()
private val _onStartChain = MutableSharedFlow<State>(0)
override val onStartChain: Flow<State> = _onStartChain.asSharedFlow()
private val _onEndChain = MutableSharedFlow<State>(0)
override val onEndChain: Flow<State> = _onEndChain.asSharedFlow()
private val contextsToStates = mutableMapOf<Any, State>()
private val mapMutex = Mutex()
override suspend fun update(old: State, new: State) = mapMutex.withLock {
when {
contextsToStates[old.context] != old -> return@withLock
old.context == new.context || !contextsToStates.containsKey(new.context) -> {
contextsToStates[old.context] = new
_onChainStateUpdated.emit(old to new)
}
else -> {
val stateOnNewOneContext = contextsToStates.getValue(new.context)
if (onContextsConflictResolver(old, new, stateOnNewOneContext)) {
endChainWithoutLock(stateOnNewOneContext)
contextsToStates.remove(old.context)
contextsToStates[new.context] = new
_onChainStateUpdated.emit(old to new)
}
}
}
}
override suspend fun startChain(state: State) = mapMutex.withLock {
if (!contextsToStates.containsKey(state.context)) {
contextsToStates[state.context] = state
_onStartChain.emit(state)
}
}
private suspend fun endChainWithoutLock(state: State) {
if (contextsToStates[state.context] == state) {
contextsToStates.remove(state.context)
_onEndChain.emit(state)
}
}
override suspend fun endChain(state: State) {
mapMutex.withLock {
endChainWithoutLock(state)
}
}
override suspend fun getActiveStates(): List<State> = contextsToStates.values.toList()
}

View File

@@ -0,0 +1,35 @@
package dev.inmo.tgbotapi.libraries.fsm.core.dsl
import dev.inmo.tgbotapi.libraries.fsm.core.*
import kotlin.reflect.KClass
class FSMBuilder(
var statesManager: StatesManager = InMemoryStatesManager()
) {
private var states = mutableListOf<StateHandlerHolder<*>>()
fun <I : State> add(kClass: KClass<I>, handler: StatesHandler<I>) {
states.add(StateHandlerHolder(kClass, false, handler))
}
fun <I : State> addStrict(kClass: KClass<I>, handler: StatesHandler<I>) {
states.add(StateHandlerHolder(kClass, true, handler))
}
fun build() = StatesMachine(
statesManager,
states.toList()
)
}
inline fun <reified I : State> FSMBuilder.onStateOrSubstate(handler: StatesHandler<I>) {
add(I::class, handler)
}
inline fun <reified I : State> FSMBuilder.strictlyOn(handler: StatesHandler<I>) {
addStrict(I::class, handler)
}
fun buildFSM(
block: FSMBuilder.() -> Unit
): StatesMachine = FSMBuilder().apply(block).build()

View File

@@ -0,0 +1,54 @@
import dev.inmo.tgbotapi.libraries.fsm.core.*
import dev.inmo.tgbotapi.libraries.fsm.core.dsl.buildFSM
import dev.inmo.tgbotapi.libraries.fsm.core.dsl.strictlyOn
import kotlinx.coroutines.*
import kotlin.random.Random
import kotlin.test.Test
sealed interface TrafficLightState : ImmediateOrNeverState {
val trafficLightNumber: Int
override val context: Int
get() = trafficLightNumber
}
data class GreenCommon(override val trafficLightNumber: Int) : TrafficLightState
data class YellowCommon(override val trafficLightNumber: Int) : TrafficLightState
data class RedCommon(override val trafficLightNumber: Int) : TrafficLightState
class PlayableMain {
fun test() {
runBlocking {
val countOfTrafficLights = 10
val initialStates = (0 until countOfTrafficLights).map {
when (0/*Random.nextInt(3)*/) {
0 -> GreenCommon(it)
1 -> YellowCommon(it)
else -> RedCommon(it)
}
}
val statesManager = InMemoryStatesManager()
val machine = buildFSM {
strictlyOn<GreenCommon> {
delay(1000L)
YellowCommon(it.context).also(::println)
}
strictlyOn<YellowCommon> {
delay(1000L)
RedCommon(it.context).also(::println)
}
strictlyOn<RedCommon> {
delay(1000L)
GreenCommon(it.context).also(::println)
}
this.statesManager = statesManager
}
initialStates.forEach { machine.startChain(it) }
val scope = CoroutineScope(Dispatchers.Default)
machine.start(scope).join()
}
}
}

View File

@@ -0,0 +1 @@
<manifest package="dev.inmo.tgbotapi.libraries.fsm.core"/>

View File

@@ -10,9 +10,8 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:tgbotapi.core:$tgbotapi_version"
api project(":tgbotapi.libraries.fsm.core")
}
}
}
}

View File

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

View File

@@ -6,32 +6,33 @@ kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.7.10
kotlin_serialisation_core_version=1.4.0
kotlin_version=1.5.10
kotlin_serialisation_core_version=1.2.1
kotlin_coroutines_version=1.5.0
github_release_plugin_version=2.4.1
github_release_plugin_version=2.2.12
tgbotapi_version=3.2.1
micro_utils_version=0.12.11
exposed_version=0.39.2
plagubot_version=2.3.1
tgbotapi_version=0.35.0
micro_utils_version=0.5.12
exposed_version=0.32.1
plagubot_version=0.3.0
# ANDROID
android_minSdkVersion=21
android_compileSdkVersion=32
android_buildToolsVersion=32.0.0
dexcount_version=3.1.0
android_compileSdkVersion=30
android_buildToolsVersion=30.0.3
dexcount_version=2.0.0
junit_version=4.12
test_ext_junit_version=1.1.3
espresso_core=3.4.0
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka
dokka_version=1.7.10
dokka_version=1.4.32
# Project data
group=dev.inmo
version=0.5.3
android_code_version=30
version=0.0.10
android_code_version=9

View File

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

View File

@@ -5,11 +5,7 @@ apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
compilations.main.kotlinOptions.useIR = true
}
sourceSets {
@@ -32,8 +28,3 @@ kotlin {
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -4,13 +4,7 @@ project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
jvm()
js (IR) {
browser()
nodejs()
@@ -53,9 +47,4 @@ kotlin {
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -38,42 +38,14 @@ publishing {
}
}
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
}

View File

@@ -1 +0,0 @@
{"licenses":[{"id":"MIT","title":"MIT License","url":"https://opensource.org/licenses/MIT"}],"mavenConfig":{"name":"${project.name}","description":"${project.name}","url":"https://github.com/InsanusMokrassar/TelegramBotApiLibraries","vcsUrl":"https://github.com/InsanusMokrassar/TelegramBotApiLibraries.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

1
publish_config.kpsb Normal file
View File

@@ -0,0 +1 @@
{"licenses":[{"id":"MIT","title":"MIT License","url":"https://opensource.org/licenses/MIT"}],"mavenConfig":{"name":"${project.name}","description":"${project.name}","url":"https://github.com/InsanusMokrassar/TelegramBotApiLibraries","vcsUrl":"https://github.com/InsanusMokrassar/TelegramBotApiLibraries.git","includeGpgSigning":false,"developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries"}]}}

View File

@@ -4,15 +4,16 @@ String[] includes = [
":cache:admins:common",
":cache:admins:micro_utils",
":cache:admins:plagubot",
":cache:media",
":cache:content:common",
":cache:content:micro_utils",
":fsm:core",
":fsm:tgbotapi"
]
includes.each { originalName ->
String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replace(":", File.separator)}"
String projectName = "${rootProject.name}${originalName.replace(":", ".")}"
String projectDirectory = "${rootProject.projectDir.getAbsolutePath()}${originalName.replaceAll(":", File.separator)}"
String projectName = "${rootProject.name}${originalName.replaceAll(":", ".")}"
String projectIdentifier = ":${projectName}"
include projectIdentifier
ProjectDescriptor project = project(projectIdentifier)