Compare commits

...

188 Commits

Author SHA1 Message Date
a0e17a664c fix of build 2024-08-12 16:42:37 +06:00
fc3bf19453 start 0.22.0 and update dependencies 2024-08-12 16:37:18 +06:00
8d1c57700e Merge pull request #77 from InsanusMokrassar/0.21.1
0.21.1
2024-07-24 16:43:37 +06:00
00c0f0fd6f start 0.21.1 and update dependencies 2024-07-24 16:41:36 +06:00
551eb293ab Merge pull request #76 from InsanusMokrassar/0.21.0
0.21.0
2024-07-11 23:10:05 +06:00
f1b58a889f update dependencies 2024-07-11 23:08:41 +06:00
36a0f9b65b start 0.21.0 2024-07-11 23:07:47 +06:00
86abe49bc1 Merge pull request #75 from InsanusMokrassar/0.20.0
0.20.0
2024-06-27 23:27:26 +06:00
5b62bfa7d3 update dependencies 2024-06-27 23:26:52 +06:00
d94d847715 start 0.20.0 2024-06-27 23:18:23 +06:00
91013b914d Merge pull request #74 from InsanusMokrassar/0.19.0
0.19.0
2024-04-23 21:21:02 +06:00
cec4c610f3 fixes in build 2024-04-23 21:20:03 +06:00
b9003388b1 Merge pull request #73 from InsanusMokrassar/0.19.0
0.19.0
2024-04-23 18:53:32 +06:00
6d161c2d78 update dependencies 2024-04-23 18:52:47 +06:00
dc63639fee Merge pull request #72 from InsanusMokrassar/0.18.3
0.18.3
2024-02-21 02:25:50 +06:00
66934c823d revert algorithm of messages resender 2024-02-21 02:22:45 +06:00
90870c225c start 0.18.3 2024-02-21 02:00:10 +06:00
106d01775c Merge pull request #71 from InsanusMokrassar/0.18.2
0.18.2
2024-02-18 21:38:49 +06:00
cea2f7dc6e update dependencies and start 0.18.2 2024-02-18 21:37:38 +06:00
7d461edc9b Merge pull request #70 from InsanusMokrassar/0.18.1
0.18.1
2024-02-10 23:48:47 +06:00
c52c6cb633 update dependencies 2024-02-10 23:48:05 +06:00
6db1755ee7 start 0.18.1 2024-02-10 23:46:47 +06:00
ccf60c95ca Merge pull request #69 from InsanusMokrassar/0.18.0
0.18.0
2024-01-13 15:40:35 +06:00
4057d5167f improvements in MessagesResender 2024-01-13 14:37:21 +06:00
f19664da58 0.18.0 2024-01-12 20:35:48 +06:00
646a551b15 Merge pull request #68 from InsanusMokrassar/0.17.2
0.17.2
2023-12-24 23:33:45 +06:00
a08974e76d update gradle wrapper 2023-12-24 23:33:14 +06:00
30e62041c0 0.17.2 2023-12-24 23:30:19 +06:00
be8fe43e76 Merge pull request #67 from InsanusMokrassar/0.17.1
0.17.1
2023-12-10 15:04:02 +06:00
42f3a064ad update repositories 2023-12-10 14:59:50 +06:00
34f9d8c0ab 0.17.1 2023-12-10 14:56:37 +06:00
6e4fc54a23 Merge pull request #66 from InsanusMokrassar/0.17.0
0.17.0
2023-11-26 21:06:55 +06:00
674ee28991 update dependencies 2023-11-26 21:05:17 +06:00
4950fd4ed0 start 0.17.0 2023-11-26 21:04:41 +06:00
e3f5ae0b24 Merge pull request #65 from InsanusMokrassar/0.16.0
0.16.0
2023-11-06 17:37:49 +06:00
3bf2d858eb update publishing script 2023-11-06 17:35:41 +06:00
b51bc97551 update gradle wrapper version 2023-11-06 17:34:25 +06:00
c0680932ab update jdk version everywhere up to 17 2023-11-06 17:34:07 +06:00
849edadab0 start 0.16.0 and update dependencies 2023-11-06 17:33:02 +06:00
f02933a9d0 Merge pull request #64 from InsanusMokrassar/0.15.3
0.15.3
2023-10-11 15:38:07 +06:00
f018b723cb 0.15.3 2023-10-11 15:34:45 +06:00
65d8fd6be1 0.15.2 2023-10-01 16:01:38 +06:00
d44c13bea5 Merge pull request #63 from InsanusMokrassar/0.15.1
0.15.1
2023-09-29 23:00:14 +06:00
50c1e33b52 update plagubot version 2023-09-29 22:52:37 +06:00
41b9001e1f start 0.15.1 2023-09-29 22:23:54 +06:00
2c44a4e580 Merge pull request #62 from InsanusMokrassar/0.15.0
0.15.0
2023-09-26 01:07:46 +06:00
ffa9525c70 Update gradle.properties 2023-09-26 01:04:09 +06:00
4f2df31e5e Merge pull request #61 from InsanusMokrassar/0.14.0
0.14.0
2023-08-20 14:45:58 +06:00
18f9318b63 0.14.0 2023-08-20 14:40:59 +06:00
c97a48dcd4 Merge pull request #60 from InsanusMokrassar/0.13.0
0.13.0
2023-07-01 16:25:41 +06:00
caaba32f9a Update gradle.properties 2023-07-01 16:22:26 +06:00
2734fc0adc Merge pull request #59 from InsanusMokrassar/0.12.2
0.12.2
2023-06-19 23:31:16 +06:00
9c2e271d57 start 0.12.2 and update dependencies 2023-06-19 23:28:29 +06:00
3edb5b1b11 Merge pull request #58 from InsanusMokrassar/0.12.1
0.12.1
2023-06-07 10:07:22 +06:00
82d859272f 0.12.1 2023-06-07 10:06:27 +06:00
64952fb054 Merge pull request #57 from InsanusMokrassar/0.12.0
0.12.0
2023-05-31 07:16:17 +06:00
bd4e3d2380 Update gradle.properties 2023-05-31 07:13:37 +06:00
d77e08631c make migration 2023-05-28 20:57:43 +06:00
90e063a47a update dependencies 2023-05-28 20:56:54 +06:00
a988117037 start 0.12.0 2023-05-28 20:56:15 +06:00
50d8511db5 Merge pull request #56 from InsanusMokrassar/0.11.3
0.11.3
2023-05-20 12:48:22 +06:00
cf5b02057d 0.11.3 2023-05-20 12:43:01 +06:00
64305c9393 Merge pull request #55 from InsanusMokrassar/0.11.2
0.11.2
2023-05-06 14:01:24 +06:00
b4107cff26 0.11.2 2023-05-06 13:34:00 +06:00
23d68c9aa5 Merge pull request #54 from InsanusMokrassar/0.11.1
0.11.1
2023-05-02 10:19:36 +06:00
735e23cadb Update gradle.properties 2023-05-02 01:53:44 +06:00
59b32f9b9a Merge pull request #53 from InsanusMokrassar/0.11.0
0.11.0
2023-04-22 22:33:58 +06:00
b8601d4c90 add opportunity to be called between resender sends 2023-04-22 22:16:14 +06:00
240ee3de6f start 0.11.0 2023-04-22 21:41:05 +06:00
300ff6514b Merge pull request #52 from InsanusMokrassar/0.10.2
0.10.2
2023-04-20 00:28:11 +06:00
ce85622876 start 0.10.2 2023-04-20 00:23:04 +06:00
bd9e6045a3 Merge pull request #51 from InsanusMokrassar/0.10.1
0.10.1
2023-03-16 20:55:36 +06:00
e8a41d97e8 Update gradle.properties 2023-03-16 19:56:58 +06:00
e2e329a757 Merge pull request #50 from InsanusMokrassar/0.10.0
0.10.0
2023-03-12 00:43:55 +06:00
c69dd61eb9 0.10.0 2023-03-12 00:33:20 +06:00
cf13bfadde Merge pull request #49 from InsanusMokrassar/0.9.4
0.9.4
2023-03-08 22:46:08 +06:00
6e33649e6c 0.9.4 2023-03-08 22:41:16 +06:00
40dd4b166f Merge pull request #48 from InsanusMokrassar/0.9.3
0.9.3
2023-03-02 23:54:49 +06:00
95adaef36f 0.9.3 2023-03-02 22:05:52 +06:00
8386690090 Merge pull request #47 from InsanusMokrassar/0.9.2
0.9.2
2023-03-01 15:48:52 +06:00
0f02910766 0.9.2 2023-03-01 15:37:53 +06:00
46b694c72b Merge pull request #46 from InsanusMokrassar/0.9.1
0.9.1
2023-02-28 21:14:32 +06:00
1bf9a1570f 0.9.1 2023-02-28 20:06:44 +06:00
f24825bcbc Merge pull request #45 from InsanusMokrassar/0.9.0
0.9.0
2023-02-28 14:15:36 +06:00
5b560118c0 start 0.9.0 2023-02-28 00:00:54 +06:00
c27b5647d7 Merge pull request #44 from InsanusMokrassar/0.8.2
0.8.2
2023-02-17 16:04:51 +06:00
6a5cd2f469 update dependencies and commit-publish workflow 2023-02-17 14:11:26 +06:00
69c819162d start 0.8.2 2023-02-17 14:10:14 +06:00
7a603c21b8 Merge pull request #43 from InsanusMokrassar/0.8.1
0.8.1
2023-02-12 01:35:57 +06:00
bfebd1de50 Update gradle.properties 2023-02-12 01:35:42 +06:00
cbbe283305 fixes 2023-02-12 01:13:59 +06:00
a2dab361cf MessageMetaInfo now serialize chatId as is instead of ChatId version 2023-02-11 20:43:22 +06:00
ac89551ac4 start 0.8.1 2023-02-11 20:40:44 +06:00
0b0d1e4ea5 Merge pull request #42 from InsanusMokrassar/0.8.0
0.8.0
2023-02-06 15:33:56 +06:00
9b056656e3 start 0.8.0 && update dependencies 2023-02-06 14:14:52 +06:00
9023cc5acc Update gradle.properties 2023-01-19 00:24:50 +06:00
617b8091db small refactor 2023-01-14 23:42:16 +06:00
88a89ff1e7 add resender module 2023-01-14 22:37:56 +06:00
c211002ac1 start 0.7.1 2023-01-14 22:24:00 +06:00
50f592f52c Merge pull request #40 from InsanusMokrassar/0.7.0
0.7.0
2023-01-01 02:35:50 +06:00
efeb15b971 Update gradle.properties 2023-01-01 02:33:26 +06:00
d33ca67c7c Merge pull request #39 from InsanusMokrassar/0.6.8
0.6.8
2022-12-28 12:46:45 +06:00
f59ff6dd3c Update gradle.properties 2022-12-28 12:43:04 +06:00
8f643a7d5b start 0.6.8 2022-12-28 12:42:28 +06:00
980badd275 Merge pull request #38 from InsanusMokrassar/0.6.7
0.6.7
2022-12-20 09:25:56 +06:00
a2e65f7b24 Update gradle-wrapper.properties 2022-12-20 09:21:14 +06:00
f86cef7118 Update gradle.properties 2022-12-20 09:20:43 +06:00
2278572cb2 Merge pull request #37 from InsanusMokrassar/0.6.6
0.6.6
2022-12-08 11:10:22 +06:00
3ce3b03f02 0.6.6 2022-12-08 10:47:51 +06:00
48b5f88359 Merge pull request #36 from InsanusMokrassar/0.6.5
0.6.5
2022-12-05 16:56:47 +06:00
0bb7257ec3 start 0.6.5 2022-12-05 16:44:41 +06:00
2b503786ca Merge pull request #35 from InsanusMokrassar/0.6.4
0.6.4
2022-11-28 18:21:40 +06:00
4622359592 Update gradle.properties 2022-11-28 16:33:30 +06:00
0fa045f6a9 Merge pull request #34 from InsanusMokrassar/0.6.3
0.6.3
2022-11-17 13:29:32 +06:00
7759d5faa5 add own repo 2022-11-17 13:27:10 +06:00
d6896c63b5 update dependencies 2022-11-17 13:25:31 +06:00
262c496d45 Merge pull request #33 from InsanusMokrassar/0.6.2
0.6.2
2022-11-17 11:42:23 +06:00
17072092ae add gitea publication 2022-11-17 11:35:16 +06:00
78c44a2c61 Update gradle.properties 2022-11-17 11:32:39 +06:00
0263fe0862 Update gradle.properties 2022-11-15 09:55:35 +06:00
3baccc6c12 Update gradle.properties 2022-11-15 00:18:55 +06:00
87bd39544d Merge pull request #32 from InsanusMokrassar/0.6.1
0.6.1
2022-11-10 21:27:46 +06:00
4bb1f54ddb build fixes 2022-11-10 21:25:31 +06:00
a4662b084a Update gradle.properties 2022-11-10 20:30:24 +06:00
53fd94f094 Merge pull request #31 from InsanusMokrassar/0.6.0
0.6.0
2022-11-09 01:48:13 +06:00
2b288c43c0 Update gradle.properties 2022-11-09 00:50:14 +06:00
842dcbf4b6 Merge pull request #30 from InsanusMokrassar/0.5.7
0.5.7
2022-11-03 13:22:28 +06:00
653d17827a Update gradle.properties 2022-11-03 13:03:55 +06:00
e4a21fe293 Update gradle.properties 2022-11-03 13:03:03 +06:00
7b75828b2e Merge pull request #29 from InsanusMokrassar/0.5.6
0.5.6
2022-10-22 22:41:41 +06:00
e8cb9556db cleanup 2022-10-22 22:33:45 +06:00
d118a3d060 remove redundant xmls 2022-10-22 22:29:38 +06:00
62fb8854b9 remove android targets in all modules due to the fact that currently there is no any android-specific code there 2022-10-22 22:28:32 +06:00
7163f7d64a update dependencies 2022-10-22 22:09:39 +06:00
8a6925e95c start 0.5.6 2022-10-22 22:08:50 +06:00
fd275ddacf Merge pull request #28 from InsanusMokrassar/0.5.5
0.5.5
2022-10-02 02:29:59 +06:00
8e281f0edc start 0.5.5 2022-10-02 02:22:18 +06:00
f802aa6a99 Merge pull request #27 from InsanusMokrassar/0.5.4
0.5.4
2022-09-19 23:47:09 +06:00
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
43 changed files with 705 additions and 396 deletions

View File

@@ -7,10 +7,7 @@ jobs:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v1 - uses: actions/setup-java@v1
with: with:
java-version: 11 java-version: 17
- 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
- name: Update version - name: Update version
run: | run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`" branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

View File

@@ -7,10 +7,8 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.getkeepsafe.dexcount:dexcount-gradle-plugin:$dexcount_version"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version" classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
} }
} }
@@ -20,6 +18,7 @@ allprojects {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
google() google()
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
} }
} }

View File

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

View File

@@ -2,13 +2,13 @@ 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
interface AdminsCacheAPI { interface AdminsCacheAPI {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? suspend fun getChatAdmins(chatId: IdChatIdentifier): List<AdministratorChatMember>?
suspend fun isAdmin(chatId: ChatId, userId: UserId): Boolean = getChatAdmins(chatId) ?.any { suspend fun isAdmin(chatId: IdChatIdentifier, userId: UserId): Boolean = getChatAdmins(chatId) ?.any {
it.user.id == userId it.user.id == userId
} == true } == true
suspend fun sentByAdmin(groupContentMessage: GroupContentMessage<*>): Boolean suspend fun sentByAdmin(groupContentMessage: GroupContentMessage<*>): Boolean

View File

@@ -1,8 +1,9 @@
package dev.inmo.tgbotapi.libraries.cache.admins package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.minutes import korlibs.time.minutes
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.Seconds import dev.inmo.tgbotapi.types.Seconds
import korlibs.time.seconds
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@@ -15,27 +16,30 @@ 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 {
suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings? suspend fun getChatSettings(chatId: IdChatIdentifier): AdminsCacheSettings?
} }
interface MutableAdminsCacheSettingsAPI : AdminsCacheSettingsAPI { interface MutableAdminsCacheSettingsAPI : AdminsCacheSettingsAPI {
val chatSettingsUpdatedFlow: SharedFlow<Pair<ChatId, AdminsCacheSettings>> val chatSettingsUpdatedFlow: SharedFlow<Pair<IdChatIdentifier, AdminsCacheSettings>>
suspend fun setChatSettings(chatId: ChatId, settings: AdminsCacheSettings) suspend fun setChatSettings(chatId: IdChatIdentifier, settings: AdminsCacheSettings)
} }
fun AdminsCacheSettingsAPI.asMutable(): MutableAdminsCacheSettingsAPI? = this as? MutableAdminsCacheSettingsAPI fun AdminsCacheSettingsAPI.asMutable(): MutableAdminsCacheSettingsAPI? = this as? MutableAdminsCacheSettingsAPI
@Serializable @Serializable
class StaticAdminsCacheSettingsAPI( class StaticAdminsCacheSettingsAPI(
private val settings: Map<ChatId, AdminsCacheSettings> private val settings: Map<IdChatIdentifier, AdminsCacheSettings>
) : AdminsCacheSettingsAPI { ) : AdminsCacheSettingsAPI {
override suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings? = settings[chatId] override suspend fun getChatSettings(chatId: IdChatIdentifier): AdminsCacheSettings? = settings[chatId]
} }

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.IdChatIdentifier
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<IdChatIdentifier>
) = 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.IdChatIdentifier
import dev.inmo.tgbotapi.types.UserId
import dev.inmo.tgbotapi.types.message.abstracts.Message
fun AdminsChecker(
adminsCacheAPI: AdminsCacheAPI
): SimpleFilter<Pair<IdChatIdentifier, UserId>> = SimpleFilter {
adminsCacheAPI.isAdmin(it.first, it.second)
}
fun <T> AdminsChecker(
adminsCacheAPI: AdminsCacheAPI,
mapper: (T) -> Pair<IdChatIdentifier, 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: IdChatIdentifier
) = SimpleFilter<FromUser> {
adminsCacheAPI.isAdmin(chatId, it.from.id)
}

View File

@@ -1,18 +1,20 @@
package dev.inmo.tgbotapi.libraries.cache.admins package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.DateTime import korlibs.time.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 korlibs.time.seconds
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
interface DefaultAdminsCacheAPIRepo { interface DefaultAdminsCacheAPIRepo {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>? suspend fun getChatAdmins(chatId: IdChatIdentifier): List<AdministratorChatMember>?
suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) suspend fun setChatAdmins(chatId: IdChatIdentifier, chatMembers: List<AdministratorChatMember>)
suspend fun lastUpdate(chatId: ChatId): DateTime? suspend fun lastUpdate(chatId: IdChatIdentifier): DateTime?
} }
@Serializable @Serializable
@@ -28,28 +30,37 @@ class DefaultAdminsCacheAPI(
bot.getMe().also { botInfo = it } bot.getMe().also { botInfo = it }
} }
private suspend fun triggerUpdate(chatId: ChatId): List<AdministratorChatMember> { override suspend fun getChatAdmins(chatId: IdChatIdentifier): 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 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: IdChatIdentifier, 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 +73,4 @@ class DefaultAdminsCacheAPI(
} }
override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI
} }

View File

@@ -1,20 +1,43 @@
package dev.inmo.tgbotapi.libraries.cache.admins package dev.inmo.tgbotapi.libraries.cache.admins
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.IdChatIdentifier
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: IdChatIdentifier,
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.IdChatIdentifier
import dev.inmo.tgbotapi.types.chat.ExtendedBot
import dev.inmo.tgbotapi.types.chat.member.AdministratorChatMember
internal suspend fun TelegramBot.updateAdmins(
chatId: IdChatIdentifier,
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 +0,0 @@
<manifest package="dev.inmo.tgbotapi.libraries.cache.admins"/>

View File

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

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 korlibs.time.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: IdChatIdentifier,
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: IdChatIdentifier,
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: IdChatIdentifier,
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<IdChatIdentifier, AdministratorChatMember>,
private val updatesRepo: KeyValueRepo<ChatId, MilliSeconds>, private val updatesRepo: KeyValueRepo<IdChatIdentifier, 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: IdChatIdentifier): 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"))
} }
override suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>) = suspendCoroutine<Unit> { return deferred.await()
actor.trySend(SetChatAdminsRepoAction(chatId, chatMembers, it))
} }
override suspend fun lastUpdate(chatId: ChatId): DateTime? = suspendCoroutine {
actor.trySend(GetUpdateDateTimeRepoAction(chatId, it)) override suspend fun setChatAdmins(chatId: IdChatIdentifier, 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: IdChatIdentifier): DateTime? {
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<IdChatIdentifier, AdministratorChatMember>,
updatesRepo: KeyValueRepo<IdChatIdentifier, MilliSeconds>,
scope: CoroutineScope
) = DefaultAdminsCacheAPIRepoImpl(adminsRepo, updatesRepo, scope)

View File

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

View File

@@ -1,81 +1,83 @@
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<IdChatIdentifier, AdministratorChatMember, Long, String>(
keyFromToTo = { chatId }, keyFromToTo = { chatId.long },
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,
{ long("chatId") }, { long("chatId") },
{ long("datetime") }, { long("datetime") },
"AdminsUpdatesTimesTable" "AdminsUpdatesTimesTable"
).withMapper<ChatId, Long, Identifier, Long>( ).withMapper<IdChatIdentifier, Long, Long, Long>(
keyFromToTo = { chatId }, keyFromToTo = { chatId.long },
valueFromToTo = { this }, valueFromToTo = { this },
keyToToFrom = { toChatId() }, keyToToFrom = { toChatId() },
valueToToFrom = { this } valueToToFrom = { this }
), ),
scope scope
), ),
DynamicAdminsCacheSettingsAPI( adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo( ExposedKeyValueRepo(
database, database,
{ long("chatId") }, { long("chatId") },
{ text("settings") }, { text("settings") },
"DynamicAdminsCacheSettingsAPI" "DynamicAdminsCacheSettingsAPI"
).withMapper<ChatId, AdminsCacheSettings, Identifier, String>( ).withMapper<IdChatIdentifier, AdminsCacheSettings, Long, String>(
keyFromToTo = { chatId }, keyFromToTo = { chatId.long },
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 +0,0 @@
<manifest package="dev.inmo.tgbotapi.libraries.cache.admins.micro_utils"/>

View File

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

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) {
null -> {
val flow = mutex.withLock { val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) } databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
} }
flow.first { it != null }!! return flow.filterNotNull().first()
}
else -> globalAdminsCacheAPI.first { it != null }!!
}
} }
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<IdChatIdentifier, AdministratorChatMember, Long, String>(
} keyFromToTo = { chatId.long },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(this) },
keyToToFrom = { toChatId() },
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(this) }
),
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ long("datetime") },
"AdminsUpdatesTimesTable"
).withMapper<IdChatIdentifier, Long, Long, Long>(
keyFromToTo = { chatId.long },
valueFromToTo = { this },
keyToToFrom = { toChatId() },
valueToToFrom = { this }
),
get(scopeQualifier)
)
}
single<AdminsCacheSettingsAPI> {
DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ text("settings") },
"DynamicAdminsCacheSettingsAPI"
).withMapper<IdChatIdentifier, AdminsCacheSettings, Long, String>(
keyFromToTo = { chatId.long },
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

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

View File

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

View File

@@ -1,29 +1,27 @@
package dev.inmo.tgbotapi.libraries.cache.media.common 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.DeleteMessage
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.IdChatIdentifier
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
class DefaultMessageContentCache<K>( class DefaultMessageContentCache<K>(
private val bot: TelegramBot, private val bot: TelegramBot,
private val filesRefreshingChatId: ChatId, private val filesRefreshingChatId: IdChatIdentifier,
private val simpleMessageContentCache: MessagesSimpleCache<K>, private val simpleMessageContentCache: MessagesSimpleCache<K>,
private val mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay( private val mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId) 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)
@@ -34,31 +32,30 @@ 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 { }.onSuccess {
with(mediaFileActualityChecker) { with(mediaFileActualityChecker) {
bot.saved(content) bot.saved(content)
} }
} }
return key
} }
override suspend fun get(k: K): MessageContent? { override suspend fun get(k: K): MessageContent? {
@@ -67,40 +64,30 @@ class DefaultMessageContentCache<K>(
if (savedSimpleContent is MediaContent && !with(mediaFileActualityChecker) { bot.isActual(savedSimpleContent) }) { if (savedSimpleContent is MediaContent && !with(mediaFileActualityChecker) { bot.isActual(savedSimpleContent) }) {
val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)") val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)")
val newContent = bot.execute( val newContent = bot.execute(
when (savedSimpleContent.asInputMedia()) { when (savedSimpleContent.asTelegramMedia()) {
is InputMediaAnimation -> SendAnimation( is TelegramMediaAnimation -> SendAnimation(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator
),
disableNotification = true disableNotification = true
) )
is InputMediaAudio -> SendAudio( is TelegramMediaAudio -> SendAudio(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator
),
disableNotification = true disableNotification = true
) )
is InputMediaVideo -> SendVideo( is TelegramMediaVideo -> SendVideo(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator
),
disableNotification = true disableNotification = true
) )
is InputMediaDocument -> SendDocument( is TelegramMediaDocument -> SendDocument(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator
),
disableNotification = true disableNotification = true
) )
is InputMediaPhoto -> SendPhoto( is TelegramMediaPhoto -> SendPhoto(
filesRefreshingChatId, filesRefreshingChatId,
MultipartFile( savedFileContentAllocator,
savedFileContentAllocator
),
disableNotification = true disableNotification = true
) )
} }
@@ -124,7 +111,7 @@ class DefaultMessageContentCache<K>(
companion object { companion object {
operator fun invoke( operator fun invoke(
bot: TelegramBot, bot: TelegramBot,
filesRefreshingChatId: ChatId, filesRefreshingChatId: IdChatIdentifier,
simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(), simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(),
mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay( mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId) MediaFileActualityChecker.Default(filesRefreshingChatId)

View File

@@ -1,20 +1,21 @@
package dev.inmo.tgbotapi.libraries.cache.media.common package dev.inmo.tgbotapi.libraries.cache.media.common
import com.soywiz.klock.DateTime import korlibs.time.DateTime
import com.soywiz.klock.milliseconds import korlibs.time.milliseconds
import dev.inmo.tgbotapi.bot.TelegramBot import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.DeleteMessage import dev.inmo.tgbotapi.requests.DeleteMessage
import dev.inmo.tgbotapi.requests.abstracts.FileId import dev.inmo.tgbotapi.requests.abstracts.FileId
import dev.inmo.tgbotapi.types.ChatId import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MilliSeconds import dev.inmo.tgbotapi.types.MilliSeconds
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent import dev.inmo.tgbotapi.types.message.content.MediaContent
fun interface MediaFileActualityChecker { fun interface MediaFileActualityChecker {
suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean
suspend fun TelegramBot.saved(mediaContent: MediaContent) {} suspend fun TelegramBot.saved(mediaContent: MediaContent) {}
class Default( class Default(
private val checkingChatId: ChatId private val checkingChatId: IdChatIdentifier
) : MediaFileActualityChecker { ) : MediaFileActualityChecker {
override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean { override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean {
return runCatching { return runCatching {

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 +0,0 @@
<manifest package="dev.inmo.tgbotapi.libraries.cache.content.common"/>

View File

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

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.MessageId
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,26 +43,22 @@ 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(
ChatId.serializer(), ChatId.serializer(),
MessageIdentifier.serializer() MessageId.serializer()
) )
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, MessageId>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule()) val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, String, String>( return withMapper<Pair<ChatId, MessageId>, MessageContent, String, String>(
{ serialFormat.encodeToString(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.encodeToString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToString(messageContentSerializer, this) }, { serialFormat.encodeToString(messageContentSerializer, this) },
{ serialFormat.decodeFromString(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.decodeFromString(chatIdToMessageIdentifierSerializer, this) },
@@ -79,9 +70,9 @@ inline fun KeyValueRepo<String, String>.asMessageContentCache(
@JsName("stringsKeyValueAsHexMessageContentCache") @JsName("stringsKeyValueAsHexMessageContentCache")
inline fun KeyValueRepo<String, String>.asMessageContentCache( inline fun KeyValueRepo<String, String>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> { ): StandardKeyValueRepo<Pair<ChatId, MessageId>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule()) val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, String, String>( return withMapper<Pair<ChatId, MessageId>, MessageContent, String, String>(
{ serialFormat.encodeToHexString(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.encodeToHexString(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToHexString(messageContentSerializer, this) }, { serialFormat.encodeToHexString(messageContentSerializer, this) },
{ serialFormat.decodeFromHexString(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.decodeFromHexString(chatIdToMessageIdentifierSerializer, this) },
@@ -93,9 +84,9 @@ inline fun KeyValueRepo<String, String>.asMessageContentCache(
@JsName("bytesKeyValueAsMessageContentCache") @JsName("bytesKeyValueAsMessageContentCache")
inline fun KeyValueRepo<ByteArray, ByteArray>.asMessageContentCache( inline fun KeyValueRepo<ByteArray, ByteArray>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> { ): StandardKeyValueRepo<Pair<ChatId, MessageId>, MessageContent> {
val serialFormat = serialFormatCreator(MessageContent.serializationModule()) val serialFormat = serialFormatCreator(MessageContent.serializationModule())
return withMapper<Pair<ChatId, MessageIdentifier>, MessageContent, ByteArray, ByteArray>( return withMapper<Pair<ChatId, MessageId>, MessageContent, ByteArray, ByteArray>(
{ serialFormat.encodeToByteArray(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.encodeToByteArray(chatIdToMessageIdentifierSerializer, this) },
{ serialFormat.encodeToByteArray(messageContentSerializer, this) }, { serialFormat.encodeToByteArray(messageContentSerializer, this) },
{ serialFormat.decodeFromByteArray(chatIdToMessageIdentifierSerializer, this) }, { serialFormat.decodeFromByteArray(chatIdToMessageIdentifierSerializer, this) },

View File

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

View File

@@ -1,40 +0,0 @@
apply plugin: 'com.getkeepsafe.dexcount'
android {
compileSdkVersion "$android_compileSdkVersion".toInteger()
buildToolsVersion "$android_buildToolsVersion"
defaultConfig {
minSdkVersion "$android_minSdkVersion".toInteger()
targetSdkVersion "$android_compileSdkVersion".toInteger()
versionCode "${android_code_version}".toInteger()
versionName "$version"
}
buildTypes {
release {
minifyEnabled false
}
debug {
debuggable true
}
}
packagingOptions {
exclude 'META-INF/kotlinx-serialization-runtime.kotlin_module'
exclude 'META-INF/kotlinx-serialization-cbor.kotlin_module'
exclude 'META-INF/kotlinx-serialization-properties.kotlin_module'
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
}

View File

@@ -13,11 +13,7 @@ allprojects {
projectByName(name) projectByName(name)
} }
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle" mppJavaWithJsProjectPath = "${rootProject.projectDir.absolutePath}/mppJavaWithJsProject.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle" publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
} }

View File

@@ -1,37 +1,26 @@
org.gradle.jvmargs=-Xmx512m
kotlin.code.style=official kotlin.code.style=official
org.gradle.parallel=true org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
kotlin.incremental.js=true kotlin.incremental.js=true
android.useAndroidX=true
android.enableJetifier=true
kotlin_version=1.6.10
kotlin_serialisation_core_version=1.3.2
github_release_plugin_version=2.2.12 kotlin_version=2.0.10
kotlin_serialisation_core_version=1.7.1
tgbotapi_version=0.38.13 github_release_plugin_version=2.5.2
micro_utils_version=0.9.22
exposed_version=0.37.3
plagubot_version=0.5.1
# ANDROID tgbotapi_version=16.0.0
micro_utils_version=0.22.0
android_minSdkVersion=21 exposed_version=0.53.0
android_compileSdkVersion=32 plagubot_version=9.0.0
android_buildToolsVersion=32.0.0
dexcount_version=3.0.1
junit_version=4.12
test_ext_junit_version=1.1.2
espresso_core=3.3.0
# Dokka # Dokka
dokka_version=1.6.10 dokka_version=1.9.20
# Project data # Project data
group=dev.inmo group=dev.inmo
version=0.0.18 version=0.22.0
android_code_version=18

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-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@@ -1,26 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
}
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -1,35 +0,0 @@
project.version = "$version"
project.group = "$group"
apply from: "$publishGradlePath"
kotlin {
jvm {
compilations.main.kotlinOptions.useIR = true
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmTest {
dependencies {
implementation kotlin('test-junit')
}
}
}
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

View File

@@ -4,14 +4,17 @@ project.group = "$group"
apply from: "$publishGradlePath" apply from: "$publishGradlePath"
kotlin { kotlin {
jvm() jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) { js (IR) {
browser() browser()
nodejs() nodejs()
} }
android {
publishAllLibraryVariants()
}
sourceSets { sourceSets {
commonMain { commonMain {
@@ -34,22 +37,12 @@ kotlin {
jsTest { jsTest {
dependencies { dependencies {
implementation kotlin('test-js') implementation kotlin('test-js')
implementation kotlin('test-junit')
}
}
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation "androidx.test.espresso:espresso-core:$espresso_core"
} }
} }
} }
} }
java { java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -1,7 +1,7 @@
apply plugin: 'maven-publish' apply plugin: 'maven-publish'
task javadocsJar(type: Jar) { task javadocsJar(type: Jar) {
classifier = 'javadoc' archiveClassifier = 'javadoc'
} }
publishing { publishing {
@@ -42,20 +42,36 @@ publishing {
maven { maven {
name = "GithubPackages" name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries") url = uri("https://maven.pkg.github.com/InsanusMokrassar/TelegramBotApiLibraries")
credentials { credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER') 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') password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
} }
}
}
if ((project.hasProperty('INMONEXUS_USER') || System.getenv('INMONEXUS_USER') != null) && (project.hasProperty('INMONEXUS_PASSWORD') || System.getenv('INMONEXUS_PASSWORD') != null)) {
maven {
name = "InmoNexus"
url = uri("https://nexus.inmo.dev/repository/maven-releases/")
credentials {
username = project.hasProperty('INMONEXUS_USER') ? project.property('INMONEXUS_USER') : System.getenv('INMONEXUS_USER')
password = project.hasProperty('INMONEXUS_PASSWORD') ? project.property('INMONEXUS_PASSWORD') : System.getenv('INMONEXUS_PASSWORD')
}
} }
} }
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) { if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven { maven {
name = "sonatype" name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/") url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials { credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER') 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') password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
} }
} }
} }
} }
@@ -76,4 +92,27 @@ if (project.hasProperty("signing.gnupg.keyName")) {
dependsOn(it) dependsOn(it)
} }
} }
// Workaround to make android sign operations depend on signing tasks
project.getTasks().withType(AbstractPublishToMaven.class).configureEach {
def signingTasks = project.getTasks().withType(Sign.class)
mustRunAfter(signingTasks)
}
// Workaround to make test tasks use sign
project.getTasks().withType(Sign.class).configureEach { signTask ->
def withoutSign = (signTask.name.startsWith("sign") ? signTask.name.minus("sign") : signTask.name)
def pubName = withoutSign.endsWith("Publication") ? withoutSign.substring(0, withoutSign.length() - "Publication".length()) : withoutSign
// These tasks only exist for native targets, hence findByName() to avoid trying to find them for other targets
// Task ':linkDebugTest<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def debugTestTask = tasks.findByName("linkDebugTest$pubName")
if (debugTestTask != null) {
signTask.mustRunAfter(debugTestTask)
}
// Task ':compileTestKotlin<platform>' uses this output of task ':sign<platform>Publication' without declaring an explicit or implicit dependency
def testTask = tasks.findByName("compileTestKotlin$pubName")
if (testTask != null) {
signTask.mustRunAfter(testTask)
}
}
} }

View File

@@ -1 +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","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"}}} {"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":"InmoNexus","url":"https://nexus.inmo.dev/repository/maven-releases/"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}}}

17
resender/build.gradle Normal file
View File

@@ -0,0 +1,17 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
}
apply from: "$mppJavaWithJsProjectPath"
kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:tgbotapi.core:$tgbotapi_version"
}
}
}
}

View File

@@ -0,0 +1,36 @@
package dev.inmo.tgbotapi.libraries.resender
import dev.inmo.tgbotapi.types.FullChatIdentifierSerializer
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MediaGroupId
import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.abstracts.Message
import dev.inmo.tgbotapi.types.message.abstracts.PossiblyMediaGroupMessage
import dev.inmo.tgbotapi.types.message.content.MediaGroupContent
import kotlinx.serialization.Serializable
@Serializable
data class MessageMetaInfo(
@Serializable(FullChatIdentifierSerializer::class)
val chatId: IdChatIdentifier,
val messageId: MessageId,
val group: MediaGroupId? = null
) {
val metaInfo: Message.MetaInfo
get() = Message.MetaInfo(chatId, messageId)
}
fun Message.asMessageMetaInfos(): List<MessageMetaInfo> {
return if (this is ContentMessage<*>) {
(content as? MediaGroupContent<*>) ?.group ?.map {
MessageMetaInfo(it.sourceMessage.chat.id, it.sourceMessage.messageId, it.sourceMessage.mediaGroupId)
}
} else {
null
} ?: listOf(MessageMetaInfo(chat.id, messageId, (this as? PossiblyMediaGroupMessage<*>) ?.mediaGroupId))
}
operator fun MessageMetaInfo.Companion.invoke(
message: Message
) = MessageMetaInfo(message.chat.id, message.messageId, (message as? PossiblyMediaGroupMessage<*>) ?.mediaGroupId)

View File

@@ -0,0 +1,160 @@
package dev.inmo.tgbotapi.libraries.resender
import dev.inmo.micro_utils.common.applyDiff
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.ForwardMessage
import dev.inmo.tgbotapi.requests.ForwardMessages
import dev.inmo.tgbotapi.requests.send.CopyMessage
import dev.inmo.tgbotapi.requests.send.CopyMessages
import dev.inmo.tgbotapi.requests.send.media.SendMediaGroup
import dev.inmo.tgbotapi.types.ChatIdentifier
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.mediaCountInMediaGroup
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.MediaGroupPartContent
class MessagesResender(
private val bot: TelegramBot,
private val cacheChatId: ChatIdentifier
) {
suspend fun resend(
targetChatId: IdChatIdentifier,
messagesInfo: List<MessageMetaInfo>,
onBetweenMessages: suspend (sent: List<MessageMetaInfo>, toBeSent: List<MessageMetaInfo>) -> Unit
): List<Pair<MessageMetaInfo, MessageMetaInfo>> {
val messagesWithOrders = messagesInfo.mapIndexed { i, messageInfo -> messageInfo to i }.toMap()
val ordersWithMessagesGroups = messagesInfo.groupBy { it.group }.flatMap { (group, list) ->
if (group == null) {
list.map {
messagesWithOrders.getValue(it) to listOf(it)
}
} else {
listOf(messagesWithOrders.getValue(list.first()) to list)
}
}.sortedBy { it.first }
val sent = mutableListOf<MessageMetaInfo>()
val leftToSend = ordersWithMessagesGroups.map { it.second }.toMutableList()
return ordersWithMessagesGroups.flatMap { (_, contents) ->
val sourceMessagesToSentMessages = mutableListOf<Pair<MessageMetaInfo, MessageMetaInfo>>()
onBetweenMessages(sent.toList(), leftToSend.flatten())
when {
contents.size == 1 -> {
val messageInfo = contents.first()
runCatching {
MessageMetaInfo(
targetChatId,
bot.execute(
CopyMessage(
targetChatId,
fromChatId = messageInfo.chatId,
messageId = messageInfo.messageId
)
)
)
}.onFailure { _ ->
runCatching {
bot.execute(
ForwardMessage(
toChatId = targetChatId,
fromChatId = messageInfo.chatId,
messageId = messageInfo.messageId
)
)
}.onSuccess {
MessageMetaInfo(
targetChatId,
bot.execute(
CopyMessage(
targetChatId,
fromChatId = it.chat.id,
messageId = it.messageId
)
)
)
}
}.getOrNull() ?.let {
sourceMessagesToSentMessages.add(messageInfo to it)
}
}
else -> {
val resultContents = contents.mapNotNull {
it to (
bot.execute(
ForwardMessage(
toChatId = cacheChatId,
fromChatId = it.chatId,
messageId = it.messageId
)
) as? ContentMessage<*> ?: return@mapNotNull null)
}.mapNotNull { (src, forwardedMessage) ->
val forwardedMessageAsMediaPartMessage = forwardedMessage.takeIf {
it.content is MediaGroupPartContent
} ?.let {
it as ContentMessage<MediaGroupPartContent>
}
src to (forwardedMessageAsMediaPartMessage ?: null.also { _ ->
sourceMessagesToSentMessages.add(
src to MessageMetaInfo(
targetChatId,
bot.execute(
CopyMessage(
targetChatId,
fromChatId = forwardedMessage.chat.id,
messageId = forwardedMessage.messageId
)
)
)
)
} ?: return@mapNotNull null)
}
resultContents.singleOrNull() ?.also { (src, it) ->
sourceMessagesToSentMessages.add(
src to MessageMetaInfo(
targetChatId,
bot.execute(
CopyMessage(
targetChatId,
it.chat.id,
it.messageId
)
)
)
)
} ?: resultContents.chunked(mediaCountInMediaGroup.last).forEach {
bot.execute(
SendMediaGroup<MediaGroupPartContent>(
targetChatId,
it.map { it.second.content.toMediaGroupMemberTelegramMedia() }
)
).content.group.mapIndexed { i, partWrapper ->
it.getOrNull(i) ?.let {
sourceMessagesToSentMessages.add(
it.first to MessageMetaInfo(
partWrapper.sourceMessage.chat.id,
partWrapper.sourceMessage.messageId,
partWrapper.sourceMessage.mediaGroupId
)
)
}
}
}
}
}
leftToSend.takeIf { it.isNotEmpty() } ?.removeAt(0) ?.also {
sent.addAll(it)
}
sourceMessagesToSentMessages.toList()
}
}
suspend fun resend(
targetChatId: IdChatIdentifier,
messagesInfo: List<MessageMetaInfo>
): List<Pair<MessageMetaInfo, MessageMetaInfo>> = resend(targetChatId, messagesInfo) { _, _ -> }
}

View File

@@ -7,6 +7,8 @@ String[] includes = [
":cache:content:common", ":cache:content:common",
":cache:content:micro_utils", ":cache:content:micro_utils",
":resender",
] ]