Compare commits

...

232 Commits

Author SHA1 Message Date
061bac146c Merge pull request #127 from InsanusMokrassar/0.27.0
0.27.0
2025-10-21 23:20:45 +06:00
00fc2a1e0b update dependencies 2025-10-21 23:20:18 +06:00
1e2e45c404 start 0.27.0 2025-10-21 23:17:55 +06:00
17398781a5 Merge pull request #124 from InsanusMokrassar/0.26.8
0.26.8
2025-09-04 17:44:40 +06:00
f86dbcefce update dependencies 2025-09-04 17:38:47 +06:00
e4aba03cdc start 0.26.8 2025-09-04 17:33:39 +06:00
c8f8608a60 Merge pull request #117 from InsanusMokrassar/0.26.7
0.26.7
2025-08-04 13:47:08 +06:00
19fcee9f3e start 0.26.7 and update dependencies 2025-08-04 13:38:57 +06:00
825e6474cc Merge pull request #111 from InsanusMokrassar/0.26.6
0.26.6
2025-06-27 09:50:49 +06:00
ff99bdd45d update publishing way 2025-06-27 09:50:18 +06:00
2ae7507c01 0.26.6 2025-06-27 09:48:39 +06:00
7b30d95f56 Merge pull request #108 from InsanusMokrassar/0.26.5
0.26.5
2025-04-30 13:42:46 +06:00
bbe9367ef3 update gradle wrapper 2025-04-30 13:37:12 +06:00
6403fe6f5d update dependencies 2025-04-30 13:36:50 +06:00
3cad9ec337 start 0.26.5 2025-04-30 13:34:36 +06:00
78e5da9db7 Merge pull request #105 from InsanusMokrassar/0.26.4
0.26.4
2025-03-26 22:40:12 +06:00
74329a8eb2 small improvements 2025-03-26 20:41:46 +06:00
953df71d9e add MessageContentCache.sendAndSave 2025-03-26 18:14:25 +06:00
c25f6d522f start 0.26.4 2025-03-26 18:10:27 +06:00
d569f46e48 Merge pull request #100 from InsanusMokrassar/0.26.3
0.26.3
2025-02-16 09:24:40 +06:00
f2731f6e7d update dependencies 2025-02-16 09:23:30 +06:00
f10c7412f4 start 0.26.3 2025-02-16 09:23:08 +06:00
a113e6e37e Merge pull request #96 from InsanusMokrassar/0.26.2
0.26.2
2025-01-29 14:33:23 +06:00
7c6005bea3 update dependencies 2025-01-29 14:32:39 +06:00
3f6067b34e start 0.26.2 2025-01-29 14:30:21 +06:00
af72bba116 Merge pull request #84 from InsanusMokrassar/0.26.1
0.26.1
2024-11-11 16:17:26 +06:00
84f04fb1f0 Merge pull request #82 from InsanusMokrassar/renovate/configure
Configure Renovate
2024-11-11 16:15:40 +06:00
5524fc9fa0 start 0.26.1 and update dependencies 2024-11-11 16:08:08 +06:00
d0eebc0ace Merge pull request #83 from InsanusMokrassar/0.26.0
0.26.0
2024-11-04 22:23:30 +06:00
891dab1f4b fixes of build 2024-11-04 22:23:16 +06:00
9f6895a530 update dependencies 2024-11-04 22:14:39 +06:00
49084169d2 start 0.26.0 2024-11-04 22:14:09 +06:00
renovate[bot]
684172cc08 Add renovate.json 2024-10-22 10:46:07 +00:00
1457cfc748 Merge pull request #81 from InsanusMokrassar/0.25.0
0.25.0
2024-09-22 19:40:07 +06:00
6784a3c49a update plagubot 2024-09-22 18:22:07 +06:00
8ef89a32c7 Merge pull request #80 from InsanusMokrassar/0.24.0
0.24.0
2024-09-14 21:24:53 +06:00
d16de6428a start 0.24.0 2024-09-14 20:25:49 +06:00
49475a457d Merge pull request #79 from InsanusMokrassar/0.23.0
0.23.0
2024-09-06 00:16:21 +06:00
b8312873b0 start 0.23.0 && update dependencies 2024-09-06 00:14:42 +06:00
3ede07c2a6 Merge pull request #78 from InsanusMokrassar/0.22.0
0.22.0
2024-08-12 16:44:02 +06:00
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
5cb7d30431 Merge pull request #13 from InsanusMokrassar/0.0.18
0.0.18
2022-04-21 14:42:28 +06:00
c34d7597b4 start (?) 0.0.18 2022-04-20 00:21:23 +06:00
d5ae880e77 add MediaFileActualityChecker 2022-04-20 00:19:25 +06:00
570568cd26 add actuality checker 2022-04-20 00:11:03 +06:00
45 changed files with 851 additions and 419 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: 17
- name: Update version
run: |
branch="`echo "${{ github.ref }}" | grep -o "[^/]*$"`"

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.idea
.kotlin
out/*
*.iml
target

View File

@@ -7,19 +7,35 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.4'
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"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
}
}
plugins {
id "com.gradleup.nmcp.aggregation" version "$nmcp_version"
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
nmcpAggregation {
centralPortal {
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')
validationTimeout = Duration.ofHours(4)
publishingType = System.getenv('PUBLISHING_TYPE') != "" ? System.getenv('PUBLISHING_TYPE') : "USER_MANAGED"
}
publishAllProjectsProbablyBreakingProjectIsolation()
}
}
allprojects {
repositories {
mavenLocal()
mavenCentral()
google()
maven { url "https://nexus.inmo.dev/repository/maven-releases/" }
}
}

View File

@@ -1,10 +1,9 @@
plugins {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJavaWithJsProjectPath"
kotlin {
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.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.Message
interface AdminsCacheAPI {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>?
suspend fun isAdmin(chatId: ChatId, userId: UserId): Boolean = getChatAdmins(chatId) ?.any {
suspend fun getChatAdmins(chatId: IdChatIdentifier): List<AdministratorChatMember>?
suspend fun isAdmin(chatId: IdChatIdentifier, userId: UserId): Boolean = getChatAdmins(chatId) ?.any {
it.user.id == userId
} == true
suspend fun sentByAdmin(groupContentMessage: GroupContentMessage<*>): Boolean

View File

@@ -1,8 +1,9 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import com.soywiz.klock.minutes
import dev.inmo.tgbotapi.types.ChatId
import korlibs.time.minutes
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.Seconds
import korlibs.time.seconds
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.serialization.Serializable
@@ -15,27 +16,30 @@ data class AdminsCacheSettings(
*/
val disableRequestsRefreshMode: Boolean = false
) {
val refreshOnRequests: Boolean
val refreshOnCacheCalls: Boolean
get() = !disableRequestsRefreshMode
@Deprecated("Renamed", ReplaceWith("refreshOnCacheCalls"))
val refreshOnRequests: Boolean
get() = refreshOnCacheCalls
}
interface AdminsCacheSettingsAPI {
suspend fun getChatSettings(chatId: ChatId): AdminsCacheSettings?
suspend fun getChatSettings(chatId: IdChatIdentifier): AdminsCacheSettings?
}
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
@Serializable
class StaticAdminsCacheSettingsAPI(
private val settings: Map<ChatId, AdminsCacheSettings>
private val settings: Map<IdChatIdentifier, AdminsCacheSettings>
) : 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
import com.soywiz.klock.DateTime
import korlibs.time.DateTime
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.extensions.api.chat.members.getChatMember
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 korlibs.time.seconds
import kotlinx.serialization.Serializable
interface DefaultAdminsCacheAPIRepo {
suspend fun getChatAdmins(chatId: ChatId): List<AdministratorChatMember>?
suspend fun setChatAdmins(chatId: ChatId, chatMembers: List<AdministratorChatMember>)
suspend fun lastUpdate(chatId: ChatId): DateTime?
suspend fun getChatAdmins(chatId: IdChatIdentifier): List<AdministratorChatMember>?
suspend fun setChatAdmins(chatId: IdChatIdentifier, chatMembers: List<AdministratorChatMember>)
suspend fun lastUpdate(chatId: IdChatIdentifier): DateTime?
}
@Serializable
@@ -28,28 +30,37 @@ 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>? {
override suspend fun getChatAdmins(chatId: IdChatIdentifier): List<AdministratorChatMember>? {
val settings = settingsAPI.getChatSettings(chatId)
val lastUpdate = repo.lastUpdate(chatId)
return when {
settings == null -> null
settings.refreshOnRequests &&
settings.refreshOnCacheCalls &&
(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 {
return when (groupContentMessage) {
is AnonymousGroupContentMessage -> true
@@ -62,5 +73,4 @@ class DefaultAdminsCacheAPI(
}
override suspend fun settings(): AdminsCacheSettingsAPI = settingsAPI
}

View File

@@ -1,20 +1,43 @@
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.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 AnonymousGroupContentMessage<*> -> true
else -> false
}
suspend fun <R> ContentMessage<*>.doAfterVerification(adminsCacheAPI: AdminsCacheAPI, block: suspend () -> R): R? {
val verified = adminsCacheAPI.verifyMessageFromAdmin(this)
suspend inline fun AdminsCacheAPI.verifyMessageFromAdmin(message: Message) = isAdmin(message)
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) {
block()
} else {
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 {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJavaWithJsProjectPath"
kotlin {
sourceSets {

View File

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

View File

@@ -1,81 +1,83 @@
package dev.inmo.tgbotapi.libraries.cache.admins
import dev.inmo.micro_utils.repos.exposed.keyvalue.ExposedKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedOneToManyKeyValueRepo
import dev.inmo.micro_utils.repos.exposed.onetomany.ExposedKeyValuesRepo
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.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.types.*
import dev.inmo.tgbotapi.types.ChatMember.AdministratorChatMemberImpl
import dev.inmo.tgbotapi.types.ChatMember.CreatorChatMember
import dev.inmo.tgbotapi.types.ChatMember.abstracts.AdministratorChatMember
import dev.inmo.tgbotapi.types.chat.member.*
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
private val serializationFormat = Json {
val telegramAdminsSerializationFormat = Json {
ignoreUnknownKeys = true
serializersModule = SerializersModule {
polymorphic(AdministratorChatMember::class) {
subclass(AdministratorChatMemberImpl::class, AdministratorChatMemberImpl.serializer())
subclass(CreatorChatMember::class, CreatorChatMember.serializer())
subclass(OwnerChatMember::class, OwnerChatMember.serializer())
}
contextual(AdministratorChatMember::class, PolymorphicSerializer(AdministratorChatMember::class))
}
}
fun AdminsCacheAPI(
bot: TelegramBot,
fun BehaviourContext.createAdminsCacheAPI(database: Database) = AdminsCacheAPI(this, database, this)
fun TelegramBot.createAdminsCacheAPI(
database: Database,
scope: CoroutineScope
) : AdminsCacheAPI = DefaultAdminsCacheAPI(
bot,
DefaultAdminsCacheAPIRepo(
ExposedOneToManyKeyValueRepo(
scope: CoroutineScope,
defaultAdminsCacheAPIRepo: DefaultAdminsCacheAPIRepo = DefaultAdminsCacheAPIRepoImpl(
ExposedKeyValuesRepo(
database,
{ long("chatId") },
{ text("member") },
"AdminsTable"
).withMapper<ChatId, AdministratorChatMember, Identifier, String>(
keyFromToTo = { chatId },
valueFromToTo = { serializationFormat.encodeToString(this) },
).withMapper<IdChatIdentifier, AdministratorChatMember, Long, String>(
keyFromToTo = { chatId.long },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(AdministratorChatMember.serializer(), this) },
keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) }
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(AdministratorChatMember.serializer(), this) }
),
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ long("datetime") },
"AdminsUpdatesTimesTable"
).withMapper<ChatId, Long, Identifier, Long>(
keyFromToTo = { chatId },
).withMapper<IdChatIdentifier, Long, Long, Long>(
keyFromToTo = { chatId.long },
valueFromToTo = { this },
keyToToFrom = { toChatId() },
valueToToFrom = { this }
),
scope
),
DynamicAdminsCacheSettingsAPI(
adminsCacheSettingsAPI: AdminsCacheSettingsAPI = DynamicAdminsCacheSettingsAPI(
ExposedKeyValueRepo(
database,
{ long("chatId") },
{ text("settings") },
"DynamicAdminsCacheSettingsAPI"
).withMapper<ChatId, AdminsCacheSettings, Identifier, String>(
keyFromToTo = { chatId },
valueFromToTo = { serializationFormat.encodeToString(this) },
).withMapper<IdChatIdentifier, AdminsCacheSettings, Long, String>(
keyFromToTo = { chatId.long },
valueFromToTo = { telegramAdminsSerializationFormat.encodeToString(AdminsCacheSettings.serializer() , this) },
keyToToFrom = { toChatId() },
valueToToFrom = { serializationFormat.decodeFromString(this) }
valueToToFrom = { telegramAdminsSerializationFormat.decodeFromString(AdminsCacheSettings.serializer() , this) }
),
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 {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJavaWithJsProjectPath"
kotlin {
sourceSets {

View File

@@ -1,53 +1,107 @@
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.database
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.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.*
import kotlinx.serialization.json.JsonObject
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?
get() = get("admins") as? AdminsPlugin
val Scope.adminsPlugin: AdminsPlugin?
get() = getOrNull()
val Koin.adminsPlugin: AdminsPlugin?
get() = getOrNull()
@Serializable
class AdminsPlugin(
private val chatsSettings: Map<ChatId, AdminsCacheSettings>? = null
) : Plugin {
class AdminsPlugin : 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 {
return when (chatsSettings) {
null -> {
val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
}
flow.first { it != null }!!
}
else -> globalAdminsCacheAPI.first { it != null }!!
val flow = mutex.withLock {
databaseToAdminsCacheAPI.getOrPut(database){ MutableStateFlow(null) }
}
return flow.filterNotNull().first()
}
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)
}
override fun Module.setupDI(config: 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<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 {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJavaWithJsProjectPath"
kotlin {
sourceSets {

View File

@@ -1,25 +1,31 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.bot.TelegramBot
import dev.inmo.tgbotapi.requests.DeleteMessage
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.send.media.*
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.InputMedia.*
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.media.*
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.DocumentContent
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 filesRefreshingChatId: IdChatIdentifier,
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(content: MessageContent): K {
return when (content) {
override suspend fun save(k: K, content: MessageContent) {
when (content) {
is MediaContent -> {
val extendedInfo = bot.execute(
GetFile(content.media.fileId)
@@ -30,80 +36,93 @@ class DefaultMessageContentCache<K>(
)
)
save(content, extendedInfo.fileName) {
save(k, content, extendedInfo.fileName) {
allocator.invoke().asInput()
}
}
else -> simpleMessageContentCache.add(content)
else -> simpleMessageContentCache.set(k, content)
}
}
override suspend fun save(
k: K,
content: MediaContent,
filename: String,
inputAllocator: suspend () -> Input
): K {
val key = simpleMessageContentCache.add(content)
) {
simpleMessageContentCache.set(k, content)
runCatching {
messagesFilesCache.set(key, filename, inputAllocator)
messagesFilesCache.set(k, filename, inputAllocator)
}.onFailure {
simpleMessageContentCache.remove(key)
simpleMessageContentCache.remove(k)
}.onSuccess {
with(mediaFileActualityChecker) {
bot.saved(content)
}
}
}
override suspend fun sendAndSave(
k: K,
filename: String,
inputAllocator: () -> Input
): DocumentContent {
val sentDocument = bot.execute(
SendDocument(
filesRefreshingChatId,
MultipartFile(filename, inputAllocator),
)
)
save(k, sentDocument.content, filename, inputAllocator)
runCatching {
bot.execute(
DeleteMessage(
sentDocument.chat.id,
sentDocument.messageId
)
)
}
return key
return sentDocument.content
}
override suspend fun get(k: K): MessageContent? {
val savedSimpleContent = simpleMessageContentCache.get(k) ?: return null
if (savedSimpleContent is MediaContent) {
runCatching {
bot.execute(GetFile(savedSimpleContent.media.fileId))
}.onFailure {
val savedFileContentAllocator = messagesFilesCache.get(k) ?: error("Unexpected absence of $k file for content ($simpleMessageContentCache)")
val newContent = bot.execute(
when (savedSimpleContent.asInputMedia()) {
is InputMediaAnimation -> SendAnimation(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
is InputMediaAudio -> SendAudio(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
is InputMediaVideo -> SendVideo(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
is InputMediaDocument -> SendDocument(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
is InputMediaPhoto -> SendPhoto(
filesRefreshingChatId,
MultipartFile(
savedFileContentAllocator
),
disableNotification = true
)
}
)
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
}
simpleMessageContentCache.update(k, newContent.content)
return newContent.content
}
return savedSimpleContent
}
@@ -120,9 +139,12 @@ class DefaultMessageContentCache<K>(
companion object {
operator fun invoke(
bot: TelegramBot,
filesRefreshingChatId: ChatId,
filesRefreshingChatId: IdChatIdentifier,
simpleMessageContentCache: MessagesSimpleCache<String> = InMemoryMessagesSimpleCache(),
mediaFileActualityChecker: MediaFileActualityChecker = MediaFileActualityChecker.WithDelay(
MediaFileActualityChecker.Default(filesRefreshingChatId)
),
messagesFilesCache: MessagesFilesCache<String> = InMemoryMessagesFilesCache()
) = DefaultMessageContentCache(bot, filesRefreshingChatId, simpleMessageContentCache, messagesFilesCache)
) = DefaultMessageContentCache(bot, filesRefreshingChatId, simpleMessageContentCache, mediaFileActualityChecker, messagesFilesCache)
}
}

View File

@@ -0,0 +1,56 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import korlibs.time.DateTime
import korlibs.time.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.IdChatIdentifier
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: IdChatIdentifier
) : MediaFileActualityChecker {
override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean {
return runCatching {
execute(mediaContent.createResend(checkingChatId)).also { sentMessage ->
execute(DeleteMessage(sentMessage.chat.id, sentMessage.messageId))
}
}.isSuccess
}
}
class WithDelay(
private val underhoodChecker: MediaFileActualityChecker,
private val checkingDelay: MilliSeconds = 24 * 60 * 60 * 1000L // one day
) : MediaFileActualityChecker {
private val fileIdChecksMap = mutableMapOf<FileId, DateTime>()
private val checkingDelayTimeSpan = checkingDelay.milliseconds
override suspend fun TelegramBot.isActual(mediaContent: MediaContent): Boolean {
val now = DateTime.now()
val lastCheck = fileIdChecksMap[mediaContent.media.fileId]
return if (lastCheck == null || now - lastCheck > checkingDelayTimeSpan) {
with(underhoodChecker) {
isActual(mediaContent)
}.also {
if (it) {
fileIdChecksMap[mediaContent.media.fileId] = now
}
}
} else {
true
}
}
override suspend fun TelegramBot.saved(mediaContent: MediaContent) {
fileIdChecksMap[mediaContent.media.fileId] = DateTime.now()
}
}
}

View File

@@ -1,16 +1,25 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.message.content.abstracts.MediaContent
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.types.message.abstracts.ContentMessage
import dev.inmo.tgbotapi.types.message.content.DocumentContent
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(content: MessageContent): K
suspend fun save(k: K, content: MessageContent)
suspend fun save(
k: K,
content: MediaContent,
filename: String,
inputAllocator: suspend () -> Input
): K
)
suspend fun sendAndSave(
k: K,
filename: String,
inputAllocator: () -> Input
): DocumentContent
suspend fun get(k: K): MessageContent?
suspend fun contains(k: K): Boolean
suspend fun remove(k: K)

View File

@@ -1,13 +1,11 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import dev.inmo.tgbotapi.types.ChatId
import dev.inmo.tgbotapi.types.MessageIdentifier
import dev.inmo.tgbotapi.utils.StorageFile
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): StorageFile?
suspend fun get(k: K): MultipartFile?
suspend fun remove(k: K)
suspend fun contains(k: K): Boolean
}
@@ -18,16 +16,18 @@ interface MessagesFilesCache<K> {
* disks-oriented one
*/
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) {
map[k] = StorageFile(
filename,
inputAllocator().readBytes()
)
val input = inputAllocator()
map[k] = MultipartFile(
filename
) {
input
}
}
override suspend fun get(k: K): StorageFile? {
override suspend fun get(k: K): MultipartFile? {
return map[k]
}

View File

@@ -1,10 +1,9 @@
package dev.inmo.tgbotapi.libraries.cache.media.common
import com.benasher44.uuid.uuid4
import dev.inmo.tgbotapi.types.message.content.abstracts.MessageContent
import dev.inmo.tgbotapi.types.message.content.MessageContent
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 get(k: K): MessageContent?
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
* disks-oriented one
*/
class InMemoryMessagesSimpleCache<K>(
private val keyGenerator: () -> K
) : MessagesSimpleCache<K> {
class InMemoryMessagesSimpleCache<K> : MessagesSimpleCache<K> {
private val map = mutableMapOf<K, MessageContent>()
override suspend fun add(
override suspend fun set(
k: K,
content: MessageContent
): K {
val key = keyGenerator()
map[key] = content
return key
) {
map[k] = content
}
override suspend fun update(
@@ -54,10 +50,4 @@ class InMemoryMessagesSimpleCache<K>(
override suspend fun contains(k: K): Boolean {
return map.contains(k)
}
companion object {
operator fun invoke() = InMemoryMessagesSimpleCache {
uuid4().toString()
}
}
}

View File

@@ -1,26 +1,23 @@
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.copyTo
import io.ktor.utils.io.streams.asInput
import io.ktor.utils.io.streams.asOutput
import io.ktor.utils.io.streams.*
import java.io.File
class InFilesMessagesFilesCache<K>(
private val folderFile: File,
private val filePrefixBuilder: (K) -> String
) : MessagesFilesCache<K> {
private val K.storageFile: StorageFile?
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 StorageFile(
StorageFileInfo(storageFileFilename)
) {
return MultipartFile(storageFileFilename) {
file.inputStream().asInput()
}
}
@@ -41,15 +38,15 @@ class InFilesMessagesFilesCache<K>(
val file = File(folderFile, fullFileName).apply {
delete()
}
inputAllocator().use { input ->
file.outputStream().asOutput().use { output ->
inputAllocator().inputStream().use { input ->
file.outputStream().use { output ->
input.copyTo(output)
}
}
}
override suspend fun get(k: K): StorageFile? {
return k.storageFile
override suspend fun get(k: K): MultipartFile? {
return k.multipartFile
}
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 {
id "org.jetbrains.kotlin.multiplatform"
id "org.jetbrains.kotlin.plugin.serialization"
id "com.android.library"
}
apply from: "$mppProjectWithSerializationPresetPath"
apply from: "$mppJavaWithJsProjectPath"
kotlin {
sourceSets {

View File

@@ -1,12 +1,11 @@
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.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.abstracts.MessageContent
import dev.inmo.tgbotapi.types.MessageId
import dev.inmo.tgbotapi.types.message.content.MessageContent
import kotlinx.serialization.*
import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.serializer
@@ -16,14 +15,10 @@ import kotlin.js.JsName
import kotlin.jvm.JvmName
class SimpleKeyValueMessageContentCache<K>(
private val keyValueRepo: KeyValueRepo<K, MessageContent>,
private val keyGenerator: () -> K
private val keyValueRepo: KeyValueRepo<K, MessageContent>
) : MessagesSimpleCache<K> {
override suspend fun add(content: MessageContent): K {
val key = keyGenerator()
keyValueRepo.set(key, content)
return key
override suspend fun set(k: K, content: MessageContent) {
keyValueRepo.set(k, content)
}
override suspend fun update(k: K, content: MessageContent): Boolean {
@@ -48,26 +43,22 @@ class SimpleKeyValueMessageContentCache<K>(
override suspend fun remove(k: K) {
keyValueRepo.unset(k)
}
companion object {
operator fun invoke(
keyValueRepo: KeyValueRepo<String, MessageContent>
) = SimpleKeyValueMessageContentCache(keyValueRepo) { uuid4().toString() }
}
}
val chatIdToMessageIdentifierSerializer = PairSerializer(
ChatId.serializer(),
MessageIdentifier.serializer()
MessageId.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> {
): StandardKeyValueRepo<Pair<ChatId, MessageId>, MessageContent> {
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(messageContentSerializer, this) },
{ serialFormat.decodeFromString(chatIdToMessageIdentifierSerializer, this) },
@@ -79,9 +70,9 @@ inline fun KeyValueRepo<String, String>.asMessageContentCache(
@JsName("stringsKeyValueAsHexMessageContentCache")
inline fun KeyValueRepo<String, String>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {
): StandardKeyValueRepo<Pair<ChatId, MessageId>, MessageContent> {
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(messageContentSerializer, this) },
{ serialFormat.decodeFromHexString(chatIdToMessageIdentifierSerializer, this) },
@@ -93,9 +84,9 @@ inline fun KeyValueRepo<String, String>.asMessageContentCache(
@JsName("bytesKeyValueAsMessageContentCache")
inline fun KeyValueRepo<ByteArray, ByteArray>.asMessageContentCache(
serialFormatCreator: (SerializersModule) -> BinaryFormat
): StandardKeyValueRepo<Pair<ChatId, MessageIdentifier>, MessageContent> {
): StandardKeyValueRepo<Pair<ChatId, MessageId>, MessageContent> {
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(messageContentSerializer, 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)
}
mppProjectWithSerializationPresetPath = "${rootProject.projectDir.absolutePath}/mppProjectWithSerialization.gradle"
mppJavaProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppJavaProject.gradle"
mppAndroidProjectPresetPath = "${rootProject.projectDir.absolutePath}/mppAndroidProject.gradle"
defaultAndroidSettingsPresetPath = "${rootProject.projectDir.absolutePath}/defaultAndroidSettings.gradle"
mppJavaWithJsProjectPath = "${rootProject.projectDir.absolutePath}/mppJavaWithJsProject.gradle"
publishGradlePath = "${rootProject.projectDir.absolutePath}/publish.gradle"
}

View File

@@ -1,37 +1,28 @@
org.gradle.jvmargs=-Xmx512m
kotlin.code.style=official
org.gradle.parallel=true
kotlin.js.generate.externals=true
kotlin.incremental=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.2.20
kotlin_serialisation_core_version=1.9.0
tgbotapi_version=0.38.12
micro_utils_version=0.9.20
exposed_version=0.37.3
plagubot_version=0.5.1
github_release_plugin_version=2.5.2
# ANDROID
tgbotapi_version=30.0.0
micro_utils_version=0.26.6
exposed_version=0.61.0
plagubot_version=10.9.0
android_minSdkVersion=21
android_compileSdkVersion=32
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
nmcp_version=1.2.0
# Dokka
dokka_version=1.6.10
dokka_version=2.1.0
# Project data
group=dev.inmo
version=0.0.17
android_code_version=17
version=0.27.0

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
zipStoreBase=GRADLE_USER_HOME
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"
kotlin {
jvm()
jvm {
compilations.main {
kotlinOptions {
jvmTarget = "17"
}
}
}
js (IR) {
browser()
nodejs()
}
android {
publishAllLibraryVariants()
}
sourceSets {
commonMain {
@@ -34,22 +37,12 @@ kotlin {
jsTest {
dependencies {
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 {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
apply from: "$defaultAndroidSettingsPresetPath"

View File

@@ -1,7 +1,7 @@
apply plugin: 'maven-publish'
task javadocsJar(type: Jar) {
classifier = 'javadoc'
archiveClassifier = 'javadoc'
}
publishing {
@@ -42,20 +42,36 @@ publishing {
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('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)) {
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')
}
}
}
}
@@ -76,4 +92,27 @@ if (project.hasProperty("signing.gnupg.keyName")) {
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"}}}

6
renovate.json Normal file
View File

@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}

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(
toChatId = 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(
toChatId = 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(
toChatId = 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(
toChatId = targetChatId,
fromChatId = it.chat.id,
messageId = 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:micro_utils",
":resender",
]