Compare commits

...

195 Commits

Author SHA1 Message Date
Andrea Cavalli
6ea5b8c540 Merge commit 'b03fe98798192840402168e6e422d1b4cee48279'
Conflicts:
	pom.xml
	telegrambots-spring-boot-starter/pom.xml
	telegrambots/pom.xml
	telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultWebhook.java
2020-11-11 15:40:29 +01:00
Ruben Bermudez
b03fe98798
Merge pull request #831 from rubenlagus/dev
Dev
2020-11-08 18:00:40 +00:00
rubenlagus
415d31eaec Updage 5.0.1 2020-11-08 16:19:38 +00:00
rubenlagus
4af620770b Remove old method messed 2020-11-08 11:35:09 +00:00
rubenlagus
f60425820a Fixed #794 2020-11-08 11:29:13 +00:00
rubenlagus
88dd390566 Fix deserialization bug 2020-11-08 11:28:57 +00:00
Ruben Bermudez
0bbe66986b
Merge pull request #830 from Dhina17/dev
Fix unhandled exception type error in examples
2020-11-07 23:41:40 +00:00
Ruben Bermudez
04880cb9ed
Merge pull request #829 from aNNiMON/issue828
Update FAQ.md according to API changes
2020-11-07 23:40:40 +00:00
Dhina17
c42955985e Fix unhandled exception type error in examples 2020-11-08 03:11:19 +05:30
Victor Melnik
bd2aa24e00 Fix FAQ.md according to API changes 2020-11-07 11:03:24 +02:00
rubenlagus
b2fcbaa3c2 Deprecate forgotten setter 2020-11-06 01:16:06 +00:00
rubenlagus
b7ffc57704 Update link 2020-11-04 15:17:27 +00:00
Ruben Bermudez
cd2f746a21
Merge pull request #827 from rubenlagus/dev
Dev Merge
2020-11-04 15:11:50 +00:00
rubenlagus
934e3e8f1a Fix #795 2020-11-04 01:25:03 +00:00
rubenlagus
2f56a05a22 Fix #795 2020-11-04 01:23:37 +00:00
rubenlagus
93bd1e0efe Update changelog and how to update 2020-11-03 21:09:02 +00:00
rubenlagus
95216b90c5 Pom cleanup 2020-11-03 03:04:14 +00:00
Ruben Bermudez
ab00b182e8 Remove Guice 2020-11-03 02:57:36 +00:00
rubenlagus
fb5626de7a CompletableFuture support 2020-11-03 02:24:07 +00:00
rubenlagus
447c9d3f92 Update 5.0 and lombok 2020-11-03 01:32:26 +00:00
rubenlagus
3275c10913 More cleanup 2020-10-31 17:43:32 +00:00
rubenlagus
bea0b95fc7 Update Location fields to Double to avoid roundings 2020-10-31 16:50:18 +00:00
rubenlagus
4fb51ec99c Added lombok 2020-10-31 13:41:05 +00:00
rubenlagus
0a58ef7120 Remove deprecated versions 2020-10-31 13:19:19 +00:00
rubenlagus
6b18fbb545 Remove deprecated versions 2020-10-31 13:04:38 +00:00
Ruben Bermudez
48dea8184c
Merge pull request #823 from rubenlagus/dev
Dev
2020-10-29 00:21:38 +00:00
Ruben Bermudez
f776950240
Update README.md 2020-10-28 22:42:49 +00:00
Ruben Bermudez
48f9bf8105
Add files via upload 2020-10-28 22:39:23 +00:00
Ruben Bermudez
341c330857
Add files via upload 2020-10-28 22:34:50 +00:00
rubenlagus
ac83b5d517 Update version 2020-10-28 22:16:13 +00:00
Ruben Bermudez
b4684b9ec7 Fix bug 2020-10-26 01:25:35 +00:00
Ruben Bermudez
5d058d41c9
Merge pull request #814 from Daniil547/master
Change Ability name check according to Telegram bot API requirements
2020-10-26 01:15:40 +00:00
Ruben Bermudez
3898480a87
Merge pull request #820 from alexengrig/command-registry-lazy-bot-username
CommandRegistry: Replace botUsername with botUsernameSupplier
2020-10-25 11:44:58 +00:00
Grig Alex
cdef29f1ba
Deprecate old constructor 2020-10-24 00:04:33 +03:00
Ruben Bermudez
814955c2a6
Merge pull request #813 from christianblos/feature/ability-extension-list
Be able to add AbilityExtensions in Bot constructor
2020-10-23 00:26:26 +01:00
Ruben Bermudez
aa030fa148
Merge pull request #812 from christianblos/feature/lazy-load-abilities
Feature/lazy load abilities
2020-10-23 00:25:46 +01:00
Ruben Bermudez
8b98ef71ba
Merge pull request #810 from christianblos/feature/bot-in-message-context
Be able to access AbilityBot via MessageContext
2020-10-23 00:25:16 +01:00
Grig Alex
5ea6fc9f09
Add constructor with bot username 2020-10-21 22:16:38 +03:00
Ruben Bermudez
f83e2b3ccb
Merge pull request #792 from costalfy/master
Upgrade Spring Boot dependency in telegrambots-spring-boot-starter
2020-10-21 20:08:11 +01:00
Ruben Bermudez
02674a2a39
Merge pull request #804 from Chase22/configure-longpolling
Add timeout and limit for long-polling updates to DefaultBotOptions
2020-10-21 19:34:23 +01:00
Ruben Bermudez
7336766502
Merge pull request #801 from MouamleH/dev
Webhook bots now print exceptions instead of ignoring them
2020-10-21 19:32:46 +01:00
Grig Alex
323d7607d6
Create tests for CommandRegistry 2020-10-20 22:10:06 +03:00
Grig Alex
30fa92cd8b
Replace botUsername with botUsernameSupplier 2020-10-20 20:41:34 +03:00
Christian Blos
3f07bfdff1 Updated ExtensionTest and wiki 2020-10-08 16:11:45 +02:00
Christian Blos
247ca5f984 Don't expose extensions in BaseAbilityBot 2020-10-08 15:46:31 +02:00
Christian Blos
a1034707e3 Updated wiki 2020-10-08 15:33:30 +02:00
Christian Blos
5b465e79f3 Removed unnecessary onRegister() overrides 2020-10-08 15:07:14 +02:00
Daniil547
5d8d10b02d
Update AbilityUtils.java 2020-10-06 16:08:44 +00:00
Daniil547
fe79de9bb7
Update AbilityUtils.java 2020-10-06 15:49:49 +00:00
Daniil547
1855a27d13
Update Ability.java 2020-10-06 15:46:18 +00:00
Christian Blos
b715f2a154 Be able to add AbilityExtensions in Bot constructor 2020-10-05 18:07:06 +02:00
Christian Blos
74e305c2a7 Load abilities when bot gets registered 2020-10-05 17:08:26 +02:00
Christian Blos
a106e3190c Be able to access AbilityBot via MessageContext 2020-10-02 21:57:57 +02:00
Chase22
2009ef411d Add timeout and update limit for long-polling updates to DefaultBotOptions
Just a little commit to make the long-polling timeout and long-polling limit configurable via the BotOptions. Mainly for Testing
2020-09-21 19:31:50 +02:00
Andrea Cavalli
d5db07d12e java 14 support 2020-09-21 11:59:34 +02:00
Mouamle
13f6b1f336 used slf4j for logging instead of exception.printStackTrace() 2020-09-08 01:18:18 +03:00
Mouamle
6fcd4d85c1 Made webhook bots print exceptions stacktrace to stderr instead of silently swallowing them. 2020-09-05 03:53:42 +03:00
Andy Costanza
f3626b7bf1 feat(spring boot): Upgrade spring boot dependency to 2.3.3.RELEASE 2020-08-25 17:25:36 +02:00
Andrea Cavalli
73b8f90ee2 Merge commit '203b26587f771f9b9bf0f97f8df04e18612a5fe5' 2020-08-21 12:35:54 +02:00
Andy Costanza
3c5163990b feat(spring boot): Upgrade spring boot dependency to 2.3.2.RELEASE
I'll fix UT too because in Spring Boot 2.3.X, ApplicationContextRunner
disables bean overriding by default. To enable it, I set
withAllowBeanDefinitionOverriding to
true.

https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.3-Release-Notes#applicationcontextrunner-disables-bean-overriding-by-default
for more informations
2020-08-11 12:29:35 +02:00
Andy Costanza
b79911b45f fix(javadoc): add missing configuration to generate javadoc
the error message before this fix was:
Failed to execute goal
org.apache.maven.plugins:maven-javadoc-plugin:3.1.0:jar (default) on
project telegrambots-meta: MavenReportException: Error while generating
Javadoc: Unable to find javadoc command: The environment variable
JAVA_HOME is not correctly set.
2020-08-11 12:17:33 +02:00
Ruben Bermudez
203b26587f
Merge pull request #788 from rubenlagus/dev
Dev
2020-08-02 11:38:03 +01:00
Ruben Bermudez
e6b9fd892f
Merge pull request #789 from addo37/fix-longest-match
Fix matching longest ability name on continuous text feature
2020-08-01 16:02:46 +01:00
Abbas Abou Daya
402c36e6b2 Fix matching longest ability name on continuous text feature 2020-08-01 05:56:50 -07:00
rubenlagus
749c6d3b6f Fixes #767, #766, #761, #763, #776, #772, #771, #780 2020-08-01 12:49:46 +01:00
Ruben Bermudez
196d0a3924
Merge pull request #785 from oskov/update-comments-for-AbilityBot
Simple comment update
2020-07-31 01:29:31 +01:00
Ruben Bermudez
280b9f2686
Merge pull request #780 from addo37/improve-token-fetch
Add customization options for command processing
2020-07-31 01:12:55 +01:00
Ruben Bermudez
4a4267768c
Merge pull request #779 from addo37/access-privacy
Ease the access modifier on getPrivacy and other auxiliary methods
2020-07-31 01:12:06 +01:00
Ruben Bermudez
fdb60e7b53
Merge pull request #778 from addo37/safe-get-user
Make getUser a safer method by returning a non-null user
2020-07-31 01:10:40 +01:00
Ruben Bermudez
cec61f95a1
Merge pull request #777 from addo37/prevent-reply-crash
Wrap reply calls with a try-catch clause to avoid dynamic exceptions
2020-07-31 01:09:20 +01:00
oskov
12619422c0 Seems to have forgotten about the comment after refactoring 2020-07-23 23:13:45 +03:00
Abbas Abou Daya
a419a88ce1 Add test 2020-07-18 02:04:57 -07:00
Abbas Abou Daya
4991eef1f1 Add wiki and rename no space feature 2020-07-18 01:50:16 -07:00
Abbas Abou Daya
e6aae3c282 Allow users to customize command prefix, split regex and NoSpaceText 2020-07-18 01:27:39 -07:00
Abbas Abou Daya
709ed0f212 Ease the access modifier on getPrivacy and other auxiliary methods 2020-07-18 01:07:57 -07:00
Abbas Abou Daya
54096d2e85 Add test 2020-07-18 00:43:28 -07:00
Abbas Abou Daya
5281caad39 Make getUser a safer method by returning an EMPTY_USER on null 2020-07-18 00:36:29 -07:00
Abbas Abou Daya
2c1ba312b3 Wrap reply calls with a try-catch clause to avoid dynamic exceptions 2020-07-17 22:55:25 -07:00
2adce4685e Merge commit '1a2e7c9773dbb7d251d30f1f8e3d6538dcd92600' 2020-07-17 18:53:57 +02:00
Ruben Bermudez
023772142a
Merge pull request #763 from FireFrogSEO/master
SEND_DATA_TO_PROVIDER fieldsanf methods
2020-07-11 15:52:58 +01:00
Ruben Bermudez
427f1ee8d2
Merge pull request #761 from b1sar/master
Update README.md
2020-07-11 15:50:37 +01:00
Ruben Bermudez
f6a4489498
Merge pull request #766 from addo37/ability-replyflow
Fix reply flow registration when using ability definitions
2020-07-11 15:47:51 +01:00
Ruben Bermudez
de3246058b
Merge pull request #767 from UnAfraid/patch-2
Update FAQ.md
2020-07-11 15:46:12 +01:00
Rumen Nikiforov
7c993feaeb
Update README.md
Fixed SpringApplicationRun for spring boot starter readme as well
2020-06-15 11:53:37 +03:00
Rumen Nikiforov
129c6d0c2f
Update FAQ.md
Fixed typo in SpringApplicationRun
Fixed inconsistency in sendMessage example
Fixed link to my webhook example
2020-06-15 11:47:34 +03:00
Abbas Abou Daya
fd91b3a2ba Fix reply flow registration when using ability definitions 2020-06-14 18:39:49 -07:00
Vladimir
223c6cccc0 added SEND_PHONE_NUMBER_TO_PROVIDER and SEND_EMAIL_TO_PROVIDER fields and methods for them 2020-06-09 12:48:25 +03:00
Vladimir
03fdafd54f added SEND_PHONE_NUMBER_TO_PROVIDER and SEND_EMAIL_TO_PROVIDER fields and methods for them 2020-06-07 15:28:55 +03:00
Vladimir
504db77a7a renamed PRIVIDER_DATA_FIELD to PROVIDER_DATA_FIELD in SendInvoice.java 2020-06-07 14:59:56 +03:00
Vladimir
8f0b31d3f1
Merge pull request #1 from rubenlagus/master
update to 4.9
2020-06-07 14:17:03 +03:00
Cebrail Yılmaz
4b1783e198
Update README.md
Altough the current version is 4.9, the readme was using version 4.1.2 which made me encounter lots of bugs. Fixed it.
2020-06-07 10:59:05 +03:00
Ruben Bermudez
1a2e7c9773
Merge pull request #758 from rubenlagus/dev
Dev
2020-06-04 23:06:36 +01:00
rubenlagus
72b239e851 Update version 4.9 2020-06-04 22:18:36 +01:00
Ruben Bermudez
28f60e3024
Merge pull request #731 from addo37/ability-stats
Add basic statistics to Abilities and Replies
2020-05-31 19:08:57 +01:00
Ruben Bermudez
765c47b77b
Merge pull request #751 from addo37/fix-poll-user-v2
Fix processing updates with no user info
2020-05-31 19:08:39 +01:00
Ruben Bermudez
05db723802
Merge pull request #752 from UnAfraid/patch-1
Fixed typo in State-Machines wiki page
2020-05-31 19:08:04 +01:00
Ruben Bermudez
39af4ced4c
Merge pull request #753 from UnAfraid/cleanup/idea_project_files
Removed Bots.ipr legacy intellij project file
2020-05-31 19:07:29 +01:00
Abbas Abou Daya
202fb72c3f Handle the poll flag in the getChatId method 2020-05-09 14:02:36 -07:00
1578534726 Ported to java 11 2020-05-05 22:30:22 +02:00
Abbas Abou Daya
9ffc547cdf Add basic statistics to Abilities and Replies 2020-04-27 21:40:47 -07:00
Abbas Abou Daya
e4dbf27c55 Adjust getChatId in utils for the new content 2020-04-27 21:36:33 -07:00
UnAfraid
260d8369eb Removed Bots.ipr legacy intellij project file 2020-04-27 10:27:46 +03:00
UnAfraid
854b336a24 Updated indentation of State-Machines example 2020-04-27 09:58:54 +03:00
Abbas Abou Daya
40c17126ae Spawn the EMPTY_USER and add the proper tests 2020-04-26 13:22:05 -07:00
Ruben Bermudez
6ecc2af3b3
Merge pull request #748 from rubenlagus/dev
Update version Api 4.8
2020-04-26 02:21:50 +01:00
rubenlagus
f84ec0b020 Version 4.8.1 2020-04-26 02:11:41 +01:00
Ruben Bermudez
cd0ec5959f
Merge pull request #744 from petrov9/dev
Update wiki pages
2020-04-26 01:57:21 +01:00
Ruben Bermudez
99e9b31f40
Merge pull request #730 from addo37/replyflow-commit
Prevent loss of DB state after bot termination
2020-04-26 01:57:07 +01:00
Ruben Bermudez
cdfd49e9b5
Merge pull request #750 from addo37/fix-poll-user
Fix handling of poll updates without user information
2020-04-26 01:37:58 +01:00
Abbas Abou Daya
05566742c0 Fix handling of poll updates without user information 2020-04-25 17:32:38 -07:00
Ruben Bermudez
52d69853e3
Merge pull request #687 from SokoMishaLov/starter-upgrade-2.2.0
upgrade spring boot to 2.2.1.RELEASE, replaced Optional bean with ObjectProvider in autoconfiguration
2020-04-26 00:51:49 +01:00
Ruben Bermudez
bd0c96f5e0
Merge pull request #719 from galimru/dev
The fix to workaround OpenJDK bug which throw internal error
2020-04-26 00:48:33 +01:00
Ruben Bermudez
21472d67cd
Merge pull request #735 from Chase22/fix-command-bot-constructors
Revert: Added deprecated constructor with the old style (e526b9db)
2020-04-26 00:37:59 +01:00
Ruben Bermudez
a3781ca0a5
Merge pull request #738 from Chase22/wiki/understanding-the-library
Wiki/understanding the library
2020-04-26 00:36:48 +01:00
Ruben Bermudez
da0aacfa40
Merge pull request #745 from Chase22/fix-gradle-string-in-ability-example
Fix gradle string in ability example
2020-04-26 00:35:02 +01:00
rubenlagus
f351e63307 Fix bug 2020-04-26 00:33:15 +01:00
979d2e1d67 Merge commit '571d58e13f5c462dee449310909c760277476a91' 2020-04-25 16:34:54 +02:00
rubenlagus
571d58e13f Update version Api 4.8 2020-04-24 09:03:23 +01:00
11a5722576 Updated to v4.7
Conflicts:
	pom.xml
2020-04-18 01:03:37 +02:00
59ead1c79c Removed signing, Changed Glassfish Version, Added LibraryVersion, Fixed SpecificBinder of the webhook 2020-04-15 21:59:52 +02:00
Chase22
43082ebeea Fix gradle string in ability example
The current version is 4.7.
Also the compile configuration is deprecated and
was replaced by implementation.

See:
https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations
2020-04-10 11:08:26 +02:00
Anton Petrov
2083b29ae5 Update wiki pages 2020-04-06 15:29:38 +03:00
Ruben Bermudez
a7aec1e581
Merge pull request #740 from rubenlagus/dev
Update version Api 4.7
2020-04-01 09:15:09 +01:00
Ruben Bermudez
14633b4040 Update version Api 4.7 2020-04-01 09:10:13 +01:00
Lukas Prediger
10ed2b5bf5 Finish first version 2020-03-22 19:55:45 +01:00
Lukas Prediger
2568822d58 Add comments to code 2020-03-22 19:55:42 +01:00
Lukas Prediger
263d690933 Add last updated date 2020-03-22 19:55:40 +01:00
Chase
7b03a2889d Add poll example 2020-03-22 19:55:37 +01:00
Chase
791826fb02 Add mapping section 2020-03-22 19:55:24 +01:00
chase
e4bb479972 Add Introduction and The Libary and the bot section 2020-03-22 19:55:18 +01:00
Lukas Prediger
cc0e0c9771 Revert: Added deprecated constructor with the old style (e526b9db)
This commit causes a bug if using the normal constructors without
overriding getBotUsername and since it's no longer abstract it doesn't force you to implement the method,
even though it is needed in any case aside from the deprecated one
2020-03-05 17:01:06 +01:00
Abbas Abou Daya
21822f7803 Remove redundant commits to DB 2020-02-29 16:27:05 -08:00
Abbas Abou Daya
5120595cb0 Move the commit to the end of the update pipeline 2020-02-29 16:22:48 -08:00
Ruben Bermudez
caf3659de9
Update README.md 2020-02-23 17:41:05 +00:00
galimru
1b18b1ccdb unwrap cause from internal exception and prevent from breaking thread (ref #629) 2020-01-28 12:59:27 +04:00
galimru
55e27c1167 added the fix to workaround openjdk bug which thrown internal error (ref #629) 2020-01-27 21:15:04 +04:00
Ruben Bermudez
bd5c5a2f41
Merge pull request #718 from rubenlagus/dev
Update 4.6
2020-01-23 23:41:32 +00:00
rubenlagus
00f68950aa Fix image 2020-01-23 23:39:45 +00:00
rubenlagus
ce2e1f9c7b Fix image 2020-01-23 23:35:37 +00:00
rubenlagus
d61ade0487 Update 4.6 2020-01-23 23:23:29 +00:00
Ruben Bermudez
32be70c9eb
Merge pull request #711 from rubenlagus/dev
Update 4.5
2019-12-31 15:56:48 +01:00
rubenlagus
581a2f3092 Update 4.5 2019-12-31 12:43:19 +01:00
rubenlagus
2ca6d985fb Update 4.5 2019-12-31 11:40:47 +01:00
rubenlagus
915a3dcd41 Update 4.5 2019-12-31 11:40:34 +01:00
rubenlagus
d8eacfc0b4 Update changelog 2019-12-31 03:44:16 +01:00
rubenlagus
e2bc7b4794 Fix #697 2019-12-31 03:43:55 +01:00
rubenlagus
6b1cea5e82 Update version 4.5 2019-12-31 03:36:57 +01:00
Ruben Bermudez
b27f273ba7
Merge pull request #708 from recursiveribbons/override-equals
Override equals and hashCode for CallbackGame and LoginUrl
2019-12-31 03:29:30 +01:00
Ruben Bermudez
e4b4c07e84
Merge pull request #709 from recursiveribbons/keyboard-constructors
Added constructor for setting keyboard in the ReplyKeyboard types
2019-12-31 03:28:57 +01:00
recursiveribbons
59958c57c0 Added constructor for setting keyboard in the ReplyKeyboard types 2019-12-25 19:18:16 +01:00
recursiveribbons
a0b53d1530 Override equals and hashCode for Callbackgame and LoginUrl 2019-12-25 18:53:58 +01:00
sokomishalov
4f0e5f6af0
Merge branch 'dev' into starter-upgrade-2.2.0 2019-11-25 00:01:11 +05:30
Ruben Bermudez
547013dcd9
Merge pull request #694 from rubenlagus/dev
Dev
2019-11-23 17:03:36 +00:00
Ruben Bermudez
f58def6326 Update changelog 2019-11-23 16:45:03 +00:00
Ruben Bermudez
26f83bd4bb
Merge pull request #692 from kortov/bugfix-691
Move init for TelegramFileDownloader to constructor, fix it for proxy usage
2019-11-23 16:32:50 +00:00
Eugene Kortov
ff96318fef Add DI for TelegramFileDownloader, fix it for proxy usage
- add DI for TelegramFileDownloader
- replace volatile variables with final
2019-11-20 16:45:38 +04:00
Ruben Bermudez
27649a99cf Update dependencies 2019-11-19 01:01:49 +00:00
Ruben Bermudez
030aed2756 Add comment 2019-11-19 00:43:25 +00:00
Ruben Bermudez
0cbc21b142 Update version 4.4.0.2 2019-11-19 00:43:25 +00:00
rubenlagus
44c814256d Bug fixes and pom dependencies upgrades 2019-11-19 00:43:21 +00:00
rubenlagus
7cfcca3ae3 Fix error importing project 2019-11-19 00:42:17 +00:00
Ruben Bermudez
31e5fe6638
Merge pull request #660 from UnAfraid/slf4j
Replacing log4j with slf4j
2019-10-30 23:24:41 +00:00
Ruben Bermudez
777dc843ef
Merge pull request #631 from jugendhacker/patch-jugendhacker-1
Fixed case insensitive usernames
2019-10-30 23:15:00 +00:00
sokomishalov
eb3d159dde upgrade spring boot to 2.2.0.RELEASE
replaced Optional bean with ObjectProvider in autoconfiguration
2019-10-30 12:16:16 +03:00
UnAfraid
2d27f9e60f Post merge fixes 2019-10-14 20:08:16 +03:00
j.r@wiuwiu.de
e526b9db14 Added deprecated constructor with the old style 2019-10-13 15:12:22 +02:00
Ruben Bermudez
15fe637998
Merge branch 'dev' into slf4j 2019-10-12 01:02:15 +01:00
Ruben Bermudez
fc12de81c0
Merge pull request #671 from addo37/add-ability-toggle
Add Abillity toggles and export default abilities to their own class
2019-10-12 01:00:38 +01:00
Ruben Bermudez
1ebbc68485
Merge branch 'dev' into add-ability-toggle 2019-10-12 01:00:28 +01:00
Ruben Bermudez
5d166d3d90
Merge pull request #672 from addo37/add-extensions-wiki
Add wiki for Ability Extensions
2019-10-09 02:06:38 +01:00
Ruben Bermudez
4d3011b89f
Merge pull request #670 from Melancholic/master
Fixed #652
2019-10-09 02:05:41 +01:00
Ruben Bermudez
d1f060fb04
Merge pull request #668 from addo37/add-replyflow
Add state machine capability to AbilityBot via ReplyFlow
2019-10-08 23:08:48 +01:00
Ruben Bermudez
8e97852ee1
Merge pull request #665 from addo37/fix-replies-wiki
Change sender to silent for the usage of forceReply in wiki
2019-10-08 23:07:51 +01:00
Ruben Bermudez
51df90464e
Merge pull request #666 from addo37/fix-execute-async
Fix executeAsync in SilentSender to properly call sender.executeAsync
2019-10-08 23:07:25 +01:00
Ruben Bermudez
2b07b87fdf
Merge pull request #664 from addo37/fix-db-var
Support backup and recovery of db vars
2019-10-08 23:06:30 +01:00
Ruben Bermudez
4c5a25302b
Merge pull request #633 from bernikr/dev
AbilityBots: prevent nullPointerExceptions when using message flags
2019-10-08 23:05:58 +01:00
Abbas Abou Daya
5d60c72a46 Add wiki for Ability Extensions 2019-09-29 23:14:23 -07:00
Abbas Abou Daya
861b7f24f9 Add Abillity toggles and export default abilities to their own class 2019-09-29 23:00:16 -07:00
Abbas Abou Daya
0ff63149f7 Add ReplyFlow implementation, tests, and wiki
This commit also has a some minor test refactoring.
2019-09-25 23:25:11 -07:00
Bernhard
837b4d2360
requested syntax changes 2019-09-17 10:51:14 +03:00
Abbas Abou Daya
ed7333a21e Fix executeAsync in SilentSender to properly call sender.executeAsync 2019-09-16 23:40:20 -07:00
Abbas Abou Daya
7b7478f180 Another sender to silent change 2019-09-16 22:48:22 -07:00
Abbas Abou Daya
b3c6623eaa Change sender to silent for the usage of forceReply in wiki 2019-09-16 22:44:33 -07:00
Abbas Abou Daya
aa3448544e Support backup and recovery of db vars 2019-09-16 22:37:16 -07:00
UnAfraid
ef1bd09a6a Replacing log4j with slf4j 2019-09-09 20:10:26 +03:00
nagorny
afe35b4b14 Fixed #652 2019-08-24 18:57:34 +03:00
Ruben Bermudez
f85409f852
Merge pull request #640 from rubenlagus/dev
Dev
2019-07-30 02:14:10 +01:00
Ruben Bermudez
6f0247232f
Merge pull request #639 from rubenlagus/java11
Java11
2019-07-30 01:27:41 +01:00
Bernhard Kralofsky
727f5a7a3e prevent nullPointerExceptions when using message flags without a MESSAGE flag first 2019-07-09 21:39:02 +02:00
j.r@wiuwiu.de
91508aeebb Removed unnecesarry botUsername argument 2019-06-27 21:11:09 +02:00
j.r@wiuwiu.de
01a7e7dc21 Fixed bot username
Let the programmer of the bot decide where the username should come from
2019-06-27 21:11:09 +02:00
j.r@wiuwiu.de
e616ebbddb Made usernames case insensitive
Fixed because username in Telegram are not case sensitive, eg @fooBar is
the same user as @Foobar
2019-06-27 21:11:09 +02:00
Ruben Bermudez
7fd3d871dc
Merge pull request #627 from rubenlagus/dev
Dev
2019-06-08 20:45:12 +01:00
Ruben Bermudez
c07bfb4903
Merge pull request #624 from rubenlagus/dev
Dev
2019-06-03 21:50:42 +01:00
Ruben Bermudez
d62354915d
Merge pull request #605 from rubenlagus/dev
Api version 4.2
2019-04-16 00:22:09 +01:00
306 changed files with 9966 additions and 14646 deletions

4
.gitignore vendored
View File

@ -35,9 +35,11 @@ hs_err_pid*
.idea/
copyright/
*.iml
*.ipr
*.iws
.classpath
.project
.settings/
#File System specific files
.DS_STORE
.DS_Store

2092
Bots.ipr

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
# Telegram Bot Java Library
[![Telegram](http://trellobot.doomdns.org/telegrambadge.svg)](https://telegram.me/JavaBotsApi)
[![Telegram](/TelegramBots.svg)](https://telegram.me/JavaBotsApi)
[![Build Status](https://travis-ci.org/rubenlagus/TelegramBots.svg?branch=master)](https://travis-ci.org/rubenlagus/TelegramBots)
@ -27,16 +27,16 @@ Just import add the library to your project with one of these options:
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```
```gradle
compile "org.telegram:telegrambots:4.4.0"
compile "org.telegram:telegrambots:5.0.1"
```
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/4.4.0)
3. Download the jar(including all dependencies) from [here](https://mvnrepository.com/artifact/org.telegram/telegrambots/4.4.0)
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/5.0.1)
3. Download the jar(including all dependencies) from [here](https://mvnrepository.com/artifact/org.telegram/telegrambots/5.0.1)
In order to use Long Polling mode, just create your own bot extending `org.telegram.telegrambots.bots.TelegramLongPollingBot`.
@ -50,9 +50,8 @@ Once done, you just need to create a `org.telegram.telegrambots.meta.TelegramBot
// Example taken from https://github.com/rubenlagus/TelegramBotsExample
public class Main {
public static void main(String[] args) {
ApiContextInitializer.init();
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
try {
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
telegramBotsApi.registerBot(new ChannelHandlers());
telegramBotsApi.registerBot(new DirectionsHandlers());
telegramBotsApi.registerBot(new RaeHandlers());
@ -95,7 +94,7 @@ Feel free to create issues [here](https://github.com/rubenlagus/TelegramBots/iss
## Powered by Intellij
<p align="center">
<a href="https://www.jetbrains.com"><img src="jetbrains.png" width="75"></a>
<a href="https://www.jetbrains.com/?from=TelegramBots"><img src="jetbrains.png" width="75"></a>
</p>

22
TelegramBots.svg Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1" />
<stop offset="1" stop-opacity=".1" />
</linearGradient>
<mask id="a">
<rect width="100" height="20" rx="3" fill="#fff" />
</mask>
<g mask="url(#a)">
<path fill="#555" d="M0 0h34v20H0z" />
<path fill="#54a9eb" d="M34 0h68v20H34z" />
<path fill="url(#b)" d="M0 0h92v20H0z" />
</g>
<g fill="#fff" text-anchor="middle" font-family="HelveticaNeue-Light,Helvetica Neue Light, Helvetica Light,Helvetica,Arial,Verdana,sans-serif" font-size="11" color="#fff" font-weight="bold">
<text x="16" y="15" fill="#010101" fill-opacity=".3">chat</text>
<text x="16" y="14">chat</text>
<text x="67" y="15" fill="#010101" fill-opacity=".3">on telegram</text>
<text x="67" y="14">on telegram</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 998 B

View File

@ -1,3 +1,63 @@
### <a id="5.0.1"></a>5.0.1 ###
1. Fixing couple of bugs from 5.0.0
2. Buf fixing: #794
3. Docs updated to reflect usage for version 5.0.0
4. EditMessageText setChatIId(Long) is removed to keep consistency
### <a id="5.0.0"></a>5.0.0 ###
1. Update Api version [5.0](https://core.telegram.org/bots/api-changelog#november-4-2020)
2. Added Builders for many of the API methods and objects (hopefully all of them unless I missed something)
3. Some setters/getters may have change name. They no longer return a reference to itself, use Builder for that.
4. Simplified methods to set files in methods. Only InputFile is available now (this class contains constructors for all the cases)
5. Locations now use Double instead of Float to avoid rounding.
6. When using a TelegramApi for webhook usage, a Webhook instance has to be provided in constructor (i.e. DefaultWebhook class)
6. When registering a Webhook Bot, a SetWebhook object must be provided.
7. When using Webhook with Spring, extends class SpringWebhookBot instead of WebhookBot
8. New Async methods returning CompletableFutures (yes, we still have the existing callback methods)
9. Added new Async methods for missing cases returning CompletableFutures. Like for sendAudio or sendVideo.
10. No more Guice to define custom class
11. Bug fixes: #795
**[[How to update to version 5.0.0|How-To-Update#5.0.0]]**
### <a id="4.9.2"></a>4.9.2 ###
1. Bug fixing: #792, #801, #804, #810, #812, #813, #820 and #814
### <a id="4.9.1"></a>4.9.1 ###
1. Bug fixing: #767, #766, #761, #763, #776, #772, #771, #780
### <a id="4.9"></a>4.9 ###
1. Update Api version [4.9](https://core.telegram.org/bots/api-changelog#june-4-2020)
2. Bug fixing: #731, #749, #752 and #753
### <a id="4.8.1"></a>4.8.1 ###
1. Update Api version [4.8](https://core.telegram.org/bots/api-changelog#april-24-2020)
2. Add stats for Abilities
3. New and updated wiki page
4. Spring-boot support for version 2.2.2
5. Bug fixing: #745, #716, #629, #749, #730
### <a id="4.7"></a>4.7 ###
1. Update Api version [4.7](https://core.telegram.org/bots/api-changelog#march-30-2020)
### <a id="4.6"></a>4.6 ###
1. Update Api version [4.6](https://core.telegram.org/bots/api-changelog#january-23-2020)
### <a id="4.5"></a>4.5 ###
1. Update Api version [4.5](https://core.telegram.org/bots/api-changelog#december-31-2019)
2. Fixes: #697, #710
### <a id="4.4.0.2"></a>4.4.0.2 ###
1. Use SLF4J
2. Support case-insensitive usernames
3. Add Ability toggles and export default abilities to their own class
4. Add state machine capability to AbilityBot via ReplyFlow
5. Support backup and recovery of db vars
6. Fixes: #602, #641, #652, #691
### <a id="4.4.0.1"></a>4.4.0.1 ###
1. Bug fix when importing the project
### <a id="4.4.0"></a>4.4.0 ###
1. Update Api version [4.4](https://core.telegram.org/bots/api-changelog#july-29-2019)
2. Removed BotLogger, replaced with [log4j2](https://logging.apache.org/log4j/2.x/)

View File

@ -4,10 +4,3 @@
## <a id="terminted_by_other"></a>Terminated by other long poll or webhook ##
It means that you have already a running instance of your bot. To solve it, close all running ones and then you can start a new instance.
## <a id="no_implementation_was_bound"></a>No implementation for org.telegram.meta.telegrambots.generics.BotSession was bound ##
Please follow the steps as explained [here](https://github.com/rubenlagus/TelegramBots/wiki/How-To-Update#to-version-243) in "How To Update"
> At the beginning of your program (before creating your TelegramBotsApi instance, add the following line:
```
ApiContextInitializer.init();
```

View File

@ -79,29 +79,29 @@ Quick example here that is showing ChactActions for commands like "/type" or "/r
```java
if (update.hasMessage() && update.getMessage().hasText()) {
String text = update.getMessage().getText();
String text = update.getMessage().getText();
SendChatAction sendChatAction = new SendChatAction();
sendChatAction.setChatId(update.getMessage().getChatId());
SendChatAction sendChatAction = new SendChatAction();
sendChatAction.setChatId(update.getMessage().getChatId());
if (text.equals("/type")) {
// -> "typing"
sendChatAction.setAction(ActionType.TYPING);
// -> "recording a voice message"
} else if (text.equals("/record_audio")) {
sendChatAction.setAction(ActionType.RECORDAUDIO);
} else {
// -> more actions in the Enum ActionType
// For information: https://core.telegram.org/bots/api#sendchataction
sendChatAction.setAction(ActionType.UPLOADDOCUMENT);
}
if (text.equals("/type")) {
// -> "typing"
sendChatAction.setAction(ActionType.TYPING);
// -> "recording a voice message"
} else if (text.equals("/record_audio")) {
sendChatAction.setAction(ActionType.RECORDAUDIO);
} else {
// -> more actions in the Enum ActionType
// For information: https://core.telegram.org/bots/api#sendchataction
sendChatAction.setAction(ActionType.UPLOADDOCUMENT);
}
try {
Boolean wasSuccessfull = execute(sendChatAction);
} catch (TelegramApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Boolean wasSuccessfull = execute(sendChatAction);
} catch (TelegramApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
```
@ -116,7 +116,7 @@ There are several method to send a photo to an user using `sendPhoto` method: Wi
// Set destination chat id
sendPhotoRequest.setChatId(chatId);
// Set the photo url as a simple photo
sendPhotoRequest.setPhoto(url);
sendPhotoRequest.setPhoto(new InputFile(url));
try {
// Execute the method
execute(sendPhotoRequest);
@ -131,7 +131,7 @@ There are several method to send a photo to an user using `sendPhoto` method: Wi
// Set destination chat id
sendPhotoRequest.setChatId(chatId);
// Set the photo url as a simple photo
sendPhotoRequest.setPhoto(fileId);
sendPhotoRequest.setPhoto(new InputFile(fileId));
try {
// Execute the method
execute(sendPhotoRequest);
@ -145,8 +145,8 @@ There are several method to send a photo to an user using `sendPhoto` method: Wi
SendPhoto sendPhotoRequest = new SendPhoto();
// Set destination chat id
sendPhotoRequest.setChatId(chatId);
// Set the photo file as a new photo (You can also use InputStream with a method overload)
sendPhotoRequest.setNewPhoto(new File(filePath));
// Set the photo file as a new photo (You can also use InputStream with a constructor overload)
sendPhotoRequest.setPhoto(new InputFile(new File(filePath)));
try {
// Execute the method
execute(sendPhotoRequest);
@ -162,24 +162,23 @@ In this example we will check if user sends to bot a photo, if it is, get Photo'
```java
// If it is a photo
if (update.hasMessage() && update.getMessage().hasPhoto()) {
// Array with photos
List<PhotoSize> photos = update.getMessage().getPhoto();
// Get largest photo's file_id
String f_id = photos.stream()
.sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
.findFirst()
.orElse(null).getFileId();
// Send photo by file_id we got before
SendPhoto msg = new SendPhoto()
.setChatId(update.getMessage().getChatId())
.setPhoto(f_id)
.setCaption("Photo");
try {
execute(msg); // Call method to send the photo
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
// Array with photos
List<PhotoSize> photos = update.getMessage().getPhoto();
// Get largest photo's file_id
String f_id = photos.stream()
.max(Comparator.comparing(PhotoSize::getFileSize))
.orElseThrow().getFileId();
// Send photo by file_id we got before
SendPhoto msg = new SendPhoto()
.setChatId(update.getMessage().getChatId())
.setPhoto(new InputFile(f_id))
.setCaption("Photo");
try {
execute(msg); // Call method to send the photo
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
```
## <a id="how_to_use_custom_keyboards"></a>How to use custom keyboards? ##
@ -243,16 +242,16 @@ This is just one way, how you can compile it (here with maven). The example belo
Please use ```execute()``` instead.
Example:
```java
SendMessage sn = new SendMessage();
SendMessage message = new SendMessage();
//add chat id and text
execute(sn);
execute(message);
```
If you extend ```TelegramLongPollingCommandBot```, then use ```AbsSender.execute()``` instead.
## <a id="example_webhook"></a>Is there any example for WebHook? ##
Please see the example Bot for https://telegram.me/SnowcrashBot in the [TelegramBotsExample]() repo and also an [example bot for Sping Boot](https://github.com/UnAfraid/SpringTelegramBot) from [UnAfraid](https://github.com/UnAfraid) [here](https://github.com/UnAfraid/SpringTelegramBot/blob/master/src/main/java/com/github/unafraid/spring/bot/TelegramWebhookBot.java)
Please see the example Bot for https://telegram.me/SnowcrashBot in the [TelegramBotsExample]() repo and also an [example bot for Sping Boot](https://github.com/UnAfraid/SpringTelegramBot) from [UnAfraid](https://github.com/UnAfraid) [here](https://github.com/UnAfraid/SpringTelegramBot/blob/master/src/main/java/com/github/unafraid/spring/bot/TelegramWebHookBot.java)
@ -264,12 +263,9 @@ Your main spring boot class should look like this:
@SpringBootApplication
public class YourApplicationMainClass {
public static void main(String[] args) {
//Add this line to initialize bots context
ApiContextInitializer.init();
SpringApplication.run(MusicUploaderApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(YourApplicationMainClass.class, args);
}
}
```

View File

@ -11,13 +11,13 @@ First you need ot get the library and add it to your project. There are few poss
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```
* With **Gradle**:
```groovy
compile group: 'org.telegram', name: 'telegrambots', version: '4.4.0'
compile group: 'org.telegram', name: 'telegrambots', version: '5.0.1'
```
2. Don't like **Maven Central Repository**? It can also be taken from [Jitpack](https://jitpack.io/#rubenlagus/TelegramBots).
@ -98,15 +98,13 @@ Now that we have the library, we can start coding. There are few steps to follow
```
2. **Instantiate `TelegramBotsApi` and register our new bot:**
For this part, we need to actually perform 3 steps: _Initialize Api Context_, _Instantiate Telegram Api_ and _Register our Bot_. In this tutorial, we are going to make it in our `main` method:
For this part, we need to actually perform 2 steps: _Instantiate Telegram Api_ and _Register our Bot_. In this tutorial, we are going to make it in our `main` method:
```java
public class Main {
public static void main(String[] args) {
// TODO Initialize Api Context
// TODO Instantiate Telegram Bots API
// TODO Register our bot
@ -115,33 +113,14 @@ Now that we have the library, we can start coding. There are few steps to follow
```
* **Initialize Api Context**: This can be easily done calling the only method present in `ApiContextInitializer`:
```java
public class Main {
public static void main(String[] args) {
ApiContextInitializer.init();
// TODO Instantiate Telegram Bots API
// TODO Register our bot
}
}
```
* **Instantiate Telegram Bots API**: Easy as well, just create a new instance. Remember that a single instance can handle different bots but each bot can run only once (Telegram doesn't support concurrent calls to `GetUpdates`):
```java
public class Main {
public static void main(String[] args) {
ApiContextInitializer.init();
TelegramBotsApi botsApi = new TelegramBotsApi();
// You can use your own BotSession implementation if needed.
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
// TODO Register our bot
}
@ -156,11 +135,8 @@ Now that we have the library, we can start coding. There are few steps to follow
public class Main {
public static void main(String[] args) {
ApiContextInitializer.init();
TelegramBotsApi botsApi = new TelegramBotsApi();
try {
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
botsApi.registerBot(new MyAmazingBot());
} catch (TelegramApiException e) {
e.printStackTrace();

View File

@ -1,3 +1,114 @@
### <a id="5.0.0"></a>To version 5.0.0 ###
1. ApiContextInitializer.init(); has been removed and is not required anymore, instead:
```java
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
// When using webhook, create your own version of DefaultWebhook with all your parameters set.
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhookInstance);
```
2. For location related class, change from Float to Double type, i.e:
```java
Double latitude = location.getLatitude()
```
3. Instead of chain set method, use builder pattern:
```java
// Before
new SendMessage()
.setChatId("@test")
.setText("Hithere")
.setReplyToMessageId(12)
.setParseMode(ParseMode.HTML)
.setReplyMarkup(new ForceReplyKeyboard())
// After
SendMessage
.builder()
.chatId("@test")
.text("Hithere")
.replyToMessageId(12)
.parseMode(ParseMode.HTML)
.replyMarkup(new ForceReplyKeyboard())
.build();
```
4. Method doesn't accept chatId as Long any more, only as a String. Use Long.toString(...) when needed I.e:
```java
Long chatIdLong = message.getChatId();
SendMessage
.builder()
.chatId(Long.toString(chatIdLong))
.text("Hithere")
.build();
```
5. When registering a Webhook bot, provide the SetWebhook method object:
```java
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhookInstance);
telegramApi.registerBot(myWebhookBot, mySetWebhook);
```
6. When using Spring with a webhook bot, make your bot inherit form SpringWebhookBot instead of WebhookBot and provide your SetWebhook method in the constructor:
```java
// Extend correct class
public class TestSpringWebhookBot extends SpringWebhookBot {
public TestSpringWebhookBot(SetWebhook setWebhook) {
super(setWebhook);
}
public TestSpringWebhookBot(DefaultBotOptions options, SetWebhook setWebhook) {
super(options, setWebhook);
}
@Override
public String getBotUsername() {
return null;
}
@Override
public String getBotToken() {
return null;
}
@Override
public BotApiMethod onWebhookUpdateReceived(Update update) {
return null;
}
@Override
public String getBotPath() {
return null;
}
}
// Create your SetWebhook method
@Bean
public SetWebhook setWebhookInstance() {
return SetWebhook.builder()....build();
}
// Create it as
@Bean
public TestSpringWebhookBot testSpringWebhookBot(SetWebhook setWebhookInstance) {
return new TestSpringWebhookBot(setWebhookInstance);
}
```
7. Use InputFile to set files to upload instead of different setters, i.e:
```java
// With a file
SendDocument
.builder()
.chatId("123456")
.document(new InputFile(new File("Filename.pdf")))
.build()
// With a Stream
SendDocument
.builder()
.chatId("123456")
.document(new InputFile("FileName", new FileInputStream("Filename.pdf")))
.build()
```
### <a id="4.4.0.2"></a>To version 4.4.0.2 ###
1. Logging framework has been replaced by slf4j, so now you'll need to manage your own implementation.
### <a id="4.0.0"></a>To version 4.0.0 ###
1. Replace removed method from AbsSender with `execute` requests.
2. Everything under "Telegrambots-meta" has been moved to package `org.telegram.telegrambots.meta`.

View File

@ -44,14 +44,11 @@ public class Main {
public static void main(String[] args) {
try {
ApiContextInitializer.init();
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi();
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSessioin.class);
// Set up Http proxy
DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class);
DefaultBotOptions botOptions = new DefaultBotOptions());
botOptions.setProxyHost(PROXY_HOST);
botOptions.setProxyPort(PROXY_PORT);
@ -96,13 +93,11 @@ public class Main {
}
});
ApiContextInitializer.init();
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi();
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
// Set up Http proxy
DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class);
DefaultBotOptions botOptions = new DefaultBotOptions();
botOptions.setProxyHost(PROXY_HOST);
botOptions.setProxyPort(PROXY_PORT);

View File

@ -4,11 +4,15 @@
* [[Using HTTP Proxy]]
* [[FAQ]]
* [[Handling Bot Tokens]]
* [[Understanding the Library]]
* AbilityBot
* [[Simple Example]]
* [[Hello Ability]]
* [[Using Replies]]
* [[Ability Toggle]]
* [[State Machines]]
* [[Database Handling]]
* [[Ability Extensions]]
* [[Bot Testing]]
* [[Bot Recovery]]
* [[Advanced]]

View File

@ -0,0 +1,54 @@
# Ability Extensions
You have around 100 abilities in your bot and you're looking for a way to refactor that mess into more modular classes. `AbillityExtension` is here to support just that! It's not a secret that AbilityBot uses refactoring backstage to be able to construct all of your abilities and map them accordingly. However, AbilityBot searches initially for all methods that return an `AbilityExtension` type. Then, those extensions will be used to search for declared abilities. Here's an example.
```java
public class MrGoodGuy implements AbilityExtension {
public Ability () {
return Ability.builder()
.name("nice")
.privacy(PUBLIC)
.locality(ALL)
.action(ctx -> silent.send("You're awesome!", ctx.chatId())
);
}
}
public class MrBadGuy implements AbilityExtension {
public Ability () {
return Ability.builder()
.name("notnice")
.privacy(PUBLIC)
.locality(ALL)
.action(ctx -> silent.send("You're horrible!", ctx.chatId())
);
}
}
public class YourAwesomeBot implements AbilityBot {
// Constructor for your bot
public AbilityExtension goodGuy() {
return new MrGoodGuy();
}
public AbilityExtension badGuy() {
return new MrBadGuy();
}
// Override creatorId
}
```
It's also possible to add extensions in the constructor by using the `addExtension()` or `addExtensions()` method:
```java
public class YourAwesomeBot implements AbilityBot {
public YourAwesomeBot() {
super(/* pass required args ... */);
addExtensions(new MrGoodGuy(), new MrBadGuy());
}
// Override creatorId
}
```

View File

@ -0,0 +1,45 @@
# Ability Toggle
Well, what if you don't like all the default abilities that AbilityBot supplies? Fear not, you are able to disable all of them, even rename them if you will!
You may pass a custom toggle to your abilitybot constructor to customize how these abilities get registered.
## The Barebone Toggle
The barebone toggle is used to **turn off** all the default abilities that come with the bot (ban, unban, demote, promte, etc...). To use the barebone toggle, call your super constructor with:
```java
import org.telegram.abilitybots.api.toggle.BareboneToggle;
public class YourAwesomeBot extends AbilityBot {
private static final BareboneToggle toggle = new BareboneToggle();
public YourAwesomeBot(String token, String username) {
super(token, username, toggle);
}
// Override ceatorId()
}
```
Obviously, you can export this as a parameter that you can pass to your bot's constructor.
## The Custom Toggle
The custom toggle allows you to customize or turn off the names of the abilities that the abilitybot exposes.
```java
import org.telegram.abilitybots.api.toggle.CustomToggle;
public class YourAwesomeBot extends AbilityBot {
private static final CustomToggle toggle = new CustomToggle()
.turnOff("ban")
.toggle("promote", "upgrade");
public YourAwesomeBot(String token, String username) {
super(token, username, toggle);
}
// Override ceatorId()
}
```
With these changes, the ability "ban" is no longer available and the "promote" ability has been renamed to "upgrade".
## The Default Toggle
The default toggle is automatically used if the user does not specify a toggle. The default toggle allows all the abilities to be effective and unchanged.

View File

@ -31,4 +31,48 @@ As an example, if you want to restrict the updates to photos only, then you may
public boolean checkGlobalFlags(Update update) {
return Flag.PHOTO;
}
```
```
## Custom Command Processing
### Command Prefix
Customizing the command prefix is as simple as overriding the `getCommandPrefix` method as shown below.
```java
@Override
protected String getCommandPrefix() {
return "!";
}
```
### Command Regex Split
The method that the bot uses to capture command tokens is through the regex splitters. By default, it's set to `" "`. However, this can be customized. For example,
if you'd like to split on digits and whitespaces, then you may do the following:
```java
@Override
protected String getCommandRegexSplit() {
return "\\s\\d";
}
```
### Commands with Continuous Text
Feeling ambitious? You may allow your bot to process tokens that are technically attached to your command. Imagine you have a command
`/do` and you'd like users to send commands as `/do1` and still trigger the `do` ability. In order to do that, override the `allowContinuousText` function.
```java
@Override
protected boolean allowContinuousText() {
return true;
}
```
Please note that this may cause ability overlap. If multiple abilities can match the same command, the longest match will be taken. For example,
if you have two abilities `do` and `do1`, the command `/do1` will trigger the `do1` ability.
## Statistics
AbilityBot can accrue basic statistics about the usage of your abilities and replies. Simply `enableStats()` on an Ability builder or `enableStats(<name>)` on replies to activate this feature. Once activated, you may call `/stats` and the bot will print a basic list of statistics. At the moment, AbilityBot only tracks hits. In the future, this will be enhanced to track more stats.
## Execute code on bot registration
If you want to execute custom logic to initialize your bot, but you can't do it in the constructor,
you can override the `onRegister()` method:
```
@Override
public void onRegister() {
super.onRegister();
// Execute custom initialize logic here
}
```

View File

@ -54,21 +54,20 @@ public Ability saysHelloWorld() {
The test for this ability would be:
```java
@Test
@Test
public void canSayHelloWorld() {
Update upd = new Update();
// Create a new EndUser - EndUser is a class similar to Telegram User, but contains
// some utility methods like fullName() and shortName() for ease of use
EndUser endUser = EndUser.endUser(USER_ID, "Abbas", "Abou Daya", "addo37");
// Create a new User - User is a class similar to Telegram User
User user = new User(USER_ID, "Abbas", false, "Abou Daya", "addo37", null);
// This is the context that you're used to, it is the necessary conumer item for the ability
MessageContext context = MessageContext.newContext(upd, endUser, CHAT_ID);
MessageContext context = MessageContext.newContext(upd, user, CHAT_ID);
// We consume a context in the lamda declaration, so we pass the context to the action logic
bot.saysHelloWorld().action().accept(context);
// We verify that the sender was called only ONCE and sent Hello World to CHAT_ID
// The sender here is a mock!
Mockito.verify(sender, times(1)).send("Hello World!", CHAT_ID);
// We verify that the silent sender was called only ONCE and sent Hello World to CHAT_ID
// The silent sender here is a mock!
Mockito.verify(silent, times(1)).send("Hello World!", CHAT_ID);
}
```
@ -85,10 +84,10 @@ import org.junit.Test;
import org.mockito.Mockito;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.db.MapDBContext;
import org.telegram.abilitybots.api.objects.EndUser;
import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.abilitybots.api.sender.MessageSender;
import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import static org.mockito.Mockito.*;
@ -98,36 +97,38 @@ public class ExampleBotTest {
// Your bot handle here
private ExampleBot bot;
// Your sender here
private MessageSender sender;
// Your sender here. Also you can create MessageSender
private SilentSender silent;
@Before
public void setUp() {
// Create your bot
bot = new ExampleBot();
// Call onRegister() to initialize abilities etc.
bot.onRegister();
// Create a new sender as a mock
sender = mock(MessageSender.class);
// Set your bot sender to the mocked sender
silent = mock(SilentSender.class);
// Set your bot silent sender to the mocked sender
// THIS is the line that prevents your bot from communicating with Telegram servers when it's running its own abilities
// All method calls will go through the mocked interface -> which would do nothing except logging the fact that you've called this function with the specific arguments
bot.sender = sender;
// Create setter in your bot
bot.setSilentSender(silent);
}
@Test
public void canSayHelloWorld() {
Update upd = new Update();
// Create a new EndUser - EndUser is a class similar to Telegram User, but contains
// some utility methods like fullName() and shortName() for ease of use
EndUser endUser = EndUser.endUser(USER_ID, "Abbas", "Abou Daya", "addo37");
// Create a new User - User is a class similar to Telegram User
User user = new User(USER_ID, "Abbas", false, "Abou Daya", "addo37", null);
// This is the context that you're used to, it is the necessary conumer item for the ability
MessageContext context = MessageContext.newContext(upd, endUser, CHAT_ID);
MessageContext context = MessageContext.newContext(upd, user, CHAT_ID);
// We consume a context in the lamda declaration, so we pass the context to the action logic
bot.saysHelloWorld().action().accept(context);
// We verify that the sender was called only ONCE and sent Hello World to CHAT_ID
// The sender here is a mock!
Mockito.verify(sender, times(1)).send("Hello World!", CHAT_ID);
// We verify that the silent sender was called only ONCE and sent Hello World to CHAT_ID
// The silent sender here is a mock!
Mockito.verify(silent, times(1)).send("Hello World!", CHAT_ID);
}
}
```
@ -157,6 +158,7 @@ public class ExampleBotTest {
// Offline instance will get deleted at JVM shutdown
db = MapDBContext.offlineInstance("test");
bot = new ExampleBot(db);
bot.onRegister();
...
}
@ -178,11 +180,14 @@ public class ExampleBotTest {
private DBContext db;
private MessageSender sender;
@Before
@Before
public void setUp() {
bot = new ExampleBot(db);
bot.onRegister();
sender = mock(MessageSender.class);
bot.silent = new SilentSender(sender);
SilentSender silent = new SilentSender(sender);
// Create setter in your bot
bot.setSilentSender(silent);
...
}

View File

@ -22,7 +22,7 @@ We'll be introducing an ability that maintains a special counter for every user.
// db.getMap takes in a string, this must be unique and the same everytime you want to call the exact same map
// TODO: Using integer as a key in this db map is not recommended, it won't be serialized/deserialized properly if you ever decide to recover/backup db
Map<String, Integer> countMap = db.getMap("COUNTERS");
int userId = ctx.user().id();
int userId = ctx.user().getId();
// Get and increment counter, put it back in the map
Integer counter = countMap.compute(String.valueOf(userId), (id, count) -> count == null ? 1 : ++count);
@ -41,7 +41,7 @@ We'll be introducing an ability that maintains a special counter for every user.
*/
// Send formatted will enable markdown
String message = String.format("%s, your count is now *%d*!", ctx.user().shortName(), counter);
String message = String.format("%s, your count is now *%d*!", ctx.user().getUserName(), counter);
silent.send(message, ctx.chatId());
})
.build();

View File

@ -9,12 +9,12 @@ As with any Java project, you will need to set your dependencies.
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-abilities</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```
* **Gradle**
```groovy
compile group: 'org.telegram', name: 'telegrambots-abilities', version: 'permissions'
implementation group: 'org.telegram', name: 'telegrambots-abilities', version: '5.0.1'
```
* [JitPack](https://jitpack.io/#rubenlagus/TelegramBots)
@ -81,13 +81,10 @@ Running the bot is just like running the regular Telegram bots. Create a Java cl
```java
public class Application {
public static void main(String[] args) {
// Initializes dependencies necessary for the base bot - Guice
ApiContextInitializer.init();
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi();
try {
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
// Register your newly created AbilityBot
botsApi.registerBot(new HelloBot());
} catch (TelegramApiException e) {

View File

@ -0,0 +1,131 @@
# State Machines
AbilityBot supports state machines using ReplyFlows. Internally, they set and transition the state of the user based on their actions so far.
Developers may declare this flow control in either a bottom-up or a top-down approach. If you're already familiar with what a `Reply` is, consider ReplyFlows as the cherry on top.
## Usage
A ReplyFlow can not be directly instantiated; it must be built. First, let's create
some basic replies.
```java
Reply saidLeft = Reply.of(upd ->
silent.send("Sir, I have gone left.", getChatId(upd)),
hasMessageWith("left"));
Reply saidRight = Reply.of(upd ->
silent.send("Sir, I have gone right.", getChatId(upd)),
hasMessageWith("right"));
```
The first `Reply` effectively replies to any message that has the text "left". Once such a message is received, the
bot replies with "Sir, I have gone left". Likewise, the bot acts for when "right" is encountered.
What if now, you'd like to protect those two replies behind one more reply? Let's say, the bot first should ask the user to give it directions.
This means that people can't tell your bot to turn left or right UNLESS the bot asks for directions. Let's trigger that when the user sends "wake up" to the bot!
```java
// We instantiate a ReplyFlow builder with our internal db (DBContext instance) passed
// State is always preserved in the db of the bot and remains even after termination
ReplyFlow.builder(db)
// Just like replies, a ReplyFlow can take an action, here we want to send a
// statement to prompt the user for directions!
.action(upd -> silent.send("Command me to go left or right!", getChatId(upd)))
// We should only trigger this flow when the user says "wake up"
.onlyIf(hasMessageWith("wake up"))
// The next method takes in an object of type Reply.
// Here we chain our replies together
.next(saidLeft)
// We chain one more reply, which is when the user commands your bot to go right
.next(saidRight)
// Finally, we build our ReplyFlow
.build();
```
For the sake of completeness, here's the auxiliary method `hasMessageWith`.
```java
private Predicate<Update> hasMessageWith(String msg) {
return upd -> upd.getMessage().getText().equalsIgnoreCase(msg);
}
```
To run this example in your own AbilityBot, just have a method return that ReplyFlow we just built. Yup, it's that easy, just like how you're used to
building replies and abilities.
## More Complex States
Let's say that your bot becomes naughty when the user asks it to go left. We want the bot to say "I don't know how to go left." when the user commands it to go left. We would also like to chain more commands after this state. Here's
how that's done.
We must create a new ReplyFlow that would be chained to the initial one. Here's what our left flow would look like.
```java
ReplyFlow leftflow = ReplyFlow.builder(db)
.action(upd -> silent.send("I don't know how to go left.", getChatId(upd)))
.onlyIf(hasMessageWith("left"))
.next(saidLeft)
.build();
```
And now, saidLeft reply becomes:
```java
Reply saidLeft = Reply.of(upd -> silent.send("Sir, I have gone left.", getChatId(upd)),
hasMessageWith("go left or else"));
```
Now, after your naughty bot retaliates, the user can say "go left or else" to force the bot to go left. Awesome, our logic now looks like this:
<p align="center">
[[/abilities/img/replyflow_diagram.svg|Diagram]]
</p>
## Complete Example
```java
public class ReplyFlowBot extends AbilityBot {
public ReplyFlowBot(String botToken, String botUsername) {
super(botToken, botUsername);
}
@Override
public int creatorId() {
return <YOUR ID HERE>;
}
public ReplyFlow directionFlow() {
Reply saidLeft = Reply.of(upd -> silent.send("Sir, I have gone left.", getChatId(upd)),
hasMessageWith("go left or else"));
ReplyFlow leftflow = ReplyFlow.builder(db)
.action(upd -> silent.send("I don't know how to go left.", getChatId(upd)))
.onlyIf(hasMessageWith("left"))
.next(saidLeft).build();
Reply saidRight = Reply.of(upd -> silent.send("Sir, I have gone right.", getChatId(upd)),
hasMessageWith("right"));
return ReplyFlow.builder(db)
.action(upd -> silent.send("Command me to go left or right!", getChatId(upd)))
.onlyIf(hasMessageWith("wake up"))
.next(leftflow)
.next(saidRight)
.build();
}
@NotNull
private Predicate<Update> hasMessageWith(String msg) {
return upd -> upd.getMessage().getText().equalsIgnoreCase(msg);
}
}
```
## Inline Declaration
As you can see in the above example, we used a bottom-up approach. We declared the leaf replies before we got to the root reply flow.
If you'd rather have a top-down approach, then you may declare your replies inline to achieve that.
```java
ReplyFlow.builder(db)
.action(upd -> silent.send("Command me to go left or right!", getChatId(upd)))
.onlyIf(hasMessageWith("wake up"))
.next(Reply.of(upd ->
silent.send("Sir, I have gone left.", getChatId(upd)),
hasMessageWith("left")))
.next(Reply.of(upd ->
silent.send("Sir, I have gone right.", getChatId(upd)),
hasMessageWith("right")))
.build();
```
## State Consistency
Under the hood, AbilityBot will generate integers that represent the state of the instigating user. However,
if you add more replies and reply flows, these integers may no longer be consistent. If you'd like to always have consistent state IDs, you
should always pass a unique ID to the ReplyFlow builder like so:
```java
ReplyFlow.builder(db, <ID HERE>)
```

View File

@ -32,7 +32,7 @@ public Ability playWithMe() {
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.action(ctx -> sender.forceReply(playMessage, ctx.chatId()))
.action(ctx -> silent.forceReply(playMessage, ctx.chatId()))
// The signature of a reply is -> (Consumer<Update> action, Predicate<Update>... conditions)
// So, we first declare the action that takes an update (NOT A MESSAGECONTEXT) like the action above
// The reason of that is that a reply can be so versatile depending on the message, context becomes an inefficient wrapping
@ -40,7 +40,7 @@ public Ability playWithMe() {
// Prints to console
System.out.println("I'm in a reply!");
// Sends message
sender.send("It's been nice playing with you!", upd.getMessage().getChatId());
silent.send("It's been nice playing with you!", upd.getMessage().getChatId());
},
// Now we start declaring conditions, MESSAGE is a member of the enum Flag class
// That class contains out-of-the-box predicates for your replies!

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,167 @@
# Understanding the Library
Last updated: 2020-03-04
This little handy guide will not teach you how to do a particular thing in the library.
It will also not teach you how to create bots or anything in the liking of that.
For that take a look at the [Getting Started] guide
This guide will give you a general understanding on how the library function and will answer a lot of frequently
asked questions. I recommend everyone who wants to work with this library more to read this guide.
## Topics
* The library and the bot API
* Understanding how the API is mapped in the library
* The general infrastructure of the library
## The Library and the Bot API
Often in Conversations about the library they talk about the API or Application Programming Interface. Sometimes these
terms are used interchangeably, which is not correct.
To understand the differences between those two things lets take a look at this diagram
![](Telegram-Diagram.png)
As you might have noticed. Our bot never actually talks to the user directly. Actually, Every communication between user
and bot happens on the Telegram Servers (A little disclaimer. I actually don't know the entire infrastructure of telegram.
So take everything between Bot API and Telegram Client with a grain of salt)
Important is that the Library communicates with the Telegram Bot API for everything. Sending Messages, Sending Pictures,
and receiving Updates from Telegram
So we can conclude that the Library is the part of your code that handles all the communication between your bot and the
Bot API. The Bot API is the actual interface telegram offers to implement bots. Everything we can do in the library,
we can also do directly calling the library.
Take this piece of code:
```java
// We initialize our bot in a separate method. See this as the initialization code from the getting started guide
AbsSender ourBot = getOurBot();
// We create the Method class. This one doesn't need any parameters to be able to be send
GetMe getMe = new GetMe();
// At last, we just need to execute the method and get the result
User bot = ourBot.execute(getMe);
```
If we do this for one of our bots this is what theBot will look like:<br>
![](Bot_intellij.png)
(you better be grateful for that picture. Spend an eternity trying to find a username)
We can also go ahead and just call the bot api directly:<br>
![](Bot_curl.png)
We get the same result. The library just handily maps it to an object for us to work with.
So how do we find out how to do things with the library?
## Understanding how the API is mapped in the library
The base for all our operations is the [telegram bot api documentations](https://core.telegram.org/bots/api) . This 220 kB monster of a website will tell us almost everything we need to know about how to use the api.
It is split into multiple sections. We can ignore the sections *Authorizing your bot", "Making Requests" and "Getting Updates". These things are done for us by the library.
So, let's see how we can send a poll for example. First, lets take a look at the *Available Methods*. It already gives us a bunch of send methods
* sendMessage
* sendPhoto
* sendAudio
* sendDocument
* sendVideo
* sendAnimation
* sendVoice
* sendVideoNote
* sendMediaGroup
* sendLocation
* sendVenue
* sendContact
* sendPoll
* sendChatAction
One of them sticks out to us. *sendPoll* looks promising, and when we take a look at the description
`Use this method to send a native poll. On success, the sent Message is returned`
That looks like the thing we need. So let's see what we need to send it
![](poll_params.png)
So as we can see, we need to set 3 things. The id of the chat we want to send it to, a question as a string and an Array of Strings.
This will result in an anonymous regular type poll without mutliselection looking like this when sent:
![](poll_example.png)
We can customize a bunch of other things like create quizzes, send closed polls or reply to a certain message. Now, how is this api object mapped in the library? How *do* we create a poll?
Pretty straight forward. We take our libary, look for the "Go To Class" or "SearchClass" option and type in the name of the Method we are looking for. This will quickly yield a "SendPoll" method.
**Every** Method, listed under *Available Methods* has such a Class. And if we go ahead and scroll down a bit we find a set of fields for the class
```java
@JsonProperty(CHATID_FIELD)
private String chatId; ///< Unique identifier for the target chat or username of the target channel (in the format @channelusername)
@JsonProperty(QUESTION_FIELD)
private String question; ///< Poll question, 1-255 characters
@JsonProperty(OPTIONS_FIELD)
private List<String> options = new ArrayList<>(); ///< List of answer options, 2-10 strings 1-100 characters each
@JsonProperty(ISANONYMOUS_FIELD)
private Boolean isAnonymous; ///< Optional True, if the poll needs to be anonymous, defaults to True
@JsonProperty(TYPE_FIELD)
private String type; ///< Optional Poll type, quiz or regular, defaults to regular
@JsonProperty(ALLOWMULTIPLEANSWERS_FIELD)
private Boolean allowMultipleAnswers; ///< Optional True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False
@JsonProperty(CORRECTOPTIONID_FIELD)
private Integer correctOptionId; ///< Optional 0-based identifier of the correct answer option, required for polls in quiz mode
@JsonProperty(ISCLOSED_FIELD)
private Boolean isClosed; ///< Optional Pass True, if the poll needs to be immediately closed
@JsonProperty(DISABLENOTIFICATION_FIELD)
private Boolean disableNotification; ///< Optional. Sends the message silently. Users will receive a notification with no sound.
@JsonProperty(REPLYTOMESSAGEID_FIELD)
private Integer replyToMessageId; ///< Optional. If the message is a reply, ID of the original message
@JsonProperty(REPLYMARKUP_FIELD)
@JsonDeserialize()
private ReplyKeyboard replyMarkup; ///< Optional. JSON-serialized object for a custom reply keyboard
```
This is a lot at first glance, but if we remember back at the table from the documentation, everything is there. We have the 3 required fields on top (chatId, question and the list of options and everything) else below
Let's reproduce our poll from earlier. We create the Method object and set all required fields. This particular class already allows us to set everything in a constructor, this is normally the case for required fields. All other fields can be set by using the setter method (**Attention** Calling a setter overrides the last value in a field. You can call setter multiple times on a method object, but it's normally not correct to do so)
```java
AbsSender ourBot = getOurBot();
List<String> options = new List<>();
options.add("Yes");
options.add("No");
// Let's just assume we get the chatMessage as a parameter. For example from the message received, or from a database
SendPoll ourPoll = new SendPoll(someChatId, "Some Question", options);
ourBot.execute(ourPoll);
```
This will send the message as expected to telegram and finally to the given chat. But what about return values?
If we go back to the documentation and look at the description it says:
`Use this method to send a native poll. On success, the sent Message is returned`
Message links us to the Message object. An object so big that i wont be showing it's documentation in this guide. It can be found here: [Telegram Bot Api: Message Object](https://core.telegram.org/bots/api#message)
Just the documentation tells us that it returns a Message object. How do we get that object? Easy. Execute() returns it.
```java
Message thePollMessage = ourBot.execute(ourPoll);
```
This again works for any method that returns a value. SendMessage, SendPhoto, SendPoll, GetMe, GetChatMember etc.
## But what about Photos?
Some methods aren't quite as straight forward to use. Let's take a bit of time to look at methods that upload files to telegram. Namely: SendPhoto, SendAnimation, SendSticker, SendDocument, SendVideo, SendVideoNote, SendAudio and SendVoice. All of these require what telegram calls an "InputFile". This can either be a FileId of a previous send file, a url to the file on a public sever or the actual file. In the Library this is mapped using different set methods in the above classes. Currently sending by FileId, sending by java.io.File and sending by InputStream is supported. Sending by URL is not supported as of writing this guide
### FileId
Using a fileId is pretty straight forward. The FileId is returned from telegram upon sending a File. Just set the given FileId and execute the method. Make sure that the FileId actually point to a file of the correct type
### File
The classic approach of creating a java.io.File by Path on your hard drive or by loading a classpath resource.
### InputStream
Sending a file by InputStream will cause the library to read the stream and then converting it into the Request. It's the same as sending a java.io.File, but more convenient some times.
For more information on how to send Files, take a look at the [FAQ: How to send Photos](../FAQ.md). The basic concept will be the same across all Methods.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

136
pom.xml
View File

@ -7,7 +7,7 @@
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<packaging>pom</packaging>
<version>4.4.0</version>
<version>5.0.1</version>
<modules>
<module>telegrambots</module>
@ -26,24 +26,67 @@
</license>
</licenses>
<name>Bots</name>
<url>https://github.com/rubenlagus/TelegramBots</url>
<description>Easy to use library to create Telegram Bots</description>
<issueManagement>
<url>https://github.com/rubenlagus/TelegramBots/issues</url>
<system>GitHub Issues</system>
</issueManagement>
<scm>
<url>https://github.com/rubenlagus/TelegramBots</url>
<connection>scm:git:git://github.com/rubenlagus/TelegramBots.git</connection>
<developerConnection>scm:git:git@github.com:rubenlagus/TelegramBots.git</developerConnection>
</scm>
<developers>
<developer>
<email>rberlopez@gmail.com</email>
<name>Ruben Bermudez</name>
<url>https://github.com/rubenlagus</url>
<id>rubenlagus</id>
</developer>
</developers>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>ossrh</id>
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>
<properties>
<java.version>11</java.version>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.deploy.skip>true</maven.deploy.skip>
<junit.version>5.5.1</junit.version>
<mockito.version>3.0.0</mockito.version>
<mockitojupiter.version>3.0.0</mockitojupiter.version>
<jacksonbase.version>2.9.9</jacksonbase.version>
<jackson.version>2.9.9.1</jackson.version>
<log4j.version>2.12.0</log4j.version>
<junit.version>5.7.0</junit.version>
<mockito.version>3.6.0</mockito.version>
<mockitojupiter.version>3.6.0</mockitojupiter.version>
<jacksonanotation.version>2.11.3</jacksonanotation.version>
<jackson.version>2.11.3</jackson.version>
<json.version>20180813</json.version>
<slf4j.version>1.7.30</slf4j.version>
<jakarta.annotation.version>1.3.5</jakarta.annotation.version>
<lombok.version>1.18.16</lombok.version>
<guava.version>30.0-jre</guava.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
@ -65,12 +108,22 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jacksonbase.version}</version>
<version>${jacksonanotation.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<version>${jacksonbase.version}</version>
<version>${jacksonanotation.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>${jacksonanotation.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
@ -78,9 +131,20 @@
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
<!-- Included to enforce common version-->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta.annotation.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
@ -108,9 +172,43 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4j.version}</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
</project>
<build>
<plugins>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
<nexusUrl>https://oss.sonatype.org/</nexusUrl>
<autoReleaseAfterClose>true</autoReleaseAfterClose>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M2</version>
<executions>
<execution>
<id>enforce-versions</id>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<configuration>
<rules>
<DependencyConvergence />
</rules>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -18,19 +18,19 @@ Usage
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-abilities</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```
**Gradle**
```gradle
compile "org.telegram:telegrambots-abilities:4.4.0"
compile "org.telegram:telegrambots-abilities:5.0.1"
```
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v4.4.0)
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v5.0.1)
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v4.4.0)
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v5.0.1)
Motivation
----------

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</parent>
<artifactId>telegrambots-abilities</artifactId>
@ -69,22 +69,22 @@
<properties>
<java.version>11</java.version>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<commonslang.version>3.9</commonslang.version>
<mapdb.version>3.0.7</mapdb.version>
<commonslang.version>3.11</commonslang.version>
<mapdb.version>3.0.8</mapdb.version>
</properties>
<dependencies>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -116,20 +116,6 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
@ -202,7 +188,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>
@ -263,4 +249,4 @@
</plugins>
</pluginManagement>
</build>
</project>
</project>

View File

@ -1,9 +1,11 @@
package org.telegram.abilitybots.api.bot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
import org.telegram.abilitybots.api.toggle.DefaultToggle;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.generics.LongPollingBot;
import org.telegram.telegrambots.util.WebhookUtils;
@ -11,34 +13,50 @@ import org.telegram.telegrambots.util.WebhookUtils;
import static org.telegram.abilitybots.api.db.MapDBContext.onlineInstance;
/**
* The default AbilityBot class implements {@link LongPollingBot}. It delegates all updates to a {@link TelegramLongPollingBot} instance.
* The default AbilityBot class implements {@link LongPollingBot}. It delegates all updates to a {@link DefaultAbsSender} instance.
*
* @author Abbas Abou Daya
*/
public abstract class AbilityBot extends BaseAbilityBot implements LongPollingBot {
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) {
super(botToken, botUsername, db, botOptions);
}
protected AbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle, DefaultBotOptions botOptions) {
super(botToken, botUsername, db, toggle, botOptions);
}
protected AbilityBot(String botToken, String botUsername, DBContext db) {
this(botToken, botUsername, db, new DefaultBotOptions());
}
protected AbilityBot(String botToken, String botUsername, AbilityToggle toggle, DefaultBotOptions options) {
this(botToken, botUsername, onlineInstance(botUsername), toggle, options);
}
protected AbilityBot(String botToken, String botUsername, DefaultBotOptions botOptions) {
this(botToken, botUsername, onlineInstance(botUsername), botOptions);
}
protected AbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle) {
this(botToken, botUsername, db, toggle, new DefaultBotOptions());
}
protected AbilityBot(String botToken, String botUsername) {
this(botToken, botUsername, onlineInstance(botUsername));
}
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions options) {
this(botToken, botUsername, db, new DefaultToggle(), options);
}
@Override
public void onUpdateReceived(Update update) {
super.onUpdateReceived(update);
}
protected AbilityBot(String botToken, String botUsername, DefaultBotOptions botOptions) {
this(botToken, botUsername, onlineInstance(botUsername), botOptions);
}
@Override
public void clearWebhook() throws TelegramApiRequestException {
WebhookUtils.clearWebhook(this);
}
protected AbilityBot(String botToken, String botUsername, AbilityToggle toggle) {
this(botToken, botUsername, onlineInstance(botUsername), toggle);
}
protected AbilityBot(String botToken, String botUsername, DBContext db) {
this(botToken, botUsername, db, new DefaultToggle());
}
protected AbilityBot(String botToken, String botUsername) {
this(botToken, botUsername, onlineInstance(botUsername));
}
@Override
public void onUpdateReceived(Update update) {
super.onUpdateReceived(update);
}
@Override
public void clearWebhook() throws TelegramApiRequestException {
WebhookUtils.clearWebhook(this);
}
}

View File

@ -1,11 +1,14 @@
package org.telegram.abilitybots.api.bot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
import org.telegram.abilitybots.api.toggle.DefaultToggle;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramWebhookBot;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.generics.WebhookBot;
import org.telegram.telegrambots.util.WebhookUtils;
@ -19,38 +22,54 @@ import static org.telegram.abilitybots.api.db.MapDBContext.onlineInstance;
@SuppressWarnings("WeakerAccess")
public abstract class AbilityWebhookBot extends BaseAbilityBot implements WebhookBot {
private final String botPath;
private final String botPath;
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db, DefaultBotOptions botOptions) {
super(botToken, botUsername, db, botOptions);
this.botPath = botPath;
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db, AbilityToggle toggle, DefaultBotOptions botOptions) {
super(botToken, botUsername, db, toggle, botOptions);
this.botPath = botPath;
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db) {
this(botToken, botUsername, botPath, db, new DefaultBotOptions());
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, AbilityToggle toggle, DefaultBotOptions options) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), toggle, options);
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DefaultBotOptions botOptions) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), botOptions);
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db, AbilityToggle toggle) {
this(botToken, botUsername, botPath, db, toggle, new DefaultBotOptions());
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath) {
this(botToken, botUsername, botPath, onlineInstance(botUsername));
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db, DefaultBotOptions options) {
this(botToken, botUsername, botPath, db, new DefaultToggle(), options);
}
@Override
public BotApiMethod onWebhookUpdateReceived(Update update) {
super.onUpdateReceived(update);
return null;
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DefaultBotOptions botOptions) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), botOptions);
}
@Override
public void setWebhook(String url, String publicCertificatePath) throws TelegramApiRequestException {
WebhookUtils.setWebhook(this, url, publicCertificatePath);
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, AbilityToggle toggle) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), toggle);
}
@Override
public String getBotPath() {
return botPath;
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath, DBContext db) {
this(botToken, botUsername, botPath, db, new DefaultToggle());
}
protected AbilityWebhookBot(String botToken, String botUsername, String botPath) {
this(botToken, botUsername, botPath, onlineInstance(botUsername));
}
@Override
public BotApiMethod onWebhookUpdateReceived(Update update) {
super.onUpdateReceived(update);
return null;
}
@Override
public void setWebhook(SetWebhook setWebhook) throws TelegramApiException {
WebhookUtils.setWebhook(this, setWebhook);
}
@Override
public String getBotPath() {
return botPath;
}
}

View File

@ -1,114 +1,58 @@
package org.telegram.abilitybots.api.bot;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.*;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.io.IOUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import org.telegram.abilitybots.api.objects.Locality;
import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.abilitybots.api.objects.Privacy;
import org.telegram.abilitybots.api.objects.Reply;
import org.telegram.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.sender.DefaultSender;
import org.telegram.abilitybots.api.sender.MessageSender;
import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
import org.telegram.abilitybots.api.util.AbilityExtension;
import org.telegram.abilitybots.api.util.AbilityUtils;
import org.telegram.abilitybots.api.util.Pair;
import org.telegram.abilitybots.api.util.Trio;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.GetFile;
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators;
import org.telegram.telegrambots.meta.api.methods.send.SendDocument;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.MultimapBuilder.hashKeys;
import static com.google.common.collect.Sets.difference;
import static java.lang.String.format;
import static java.time.ZonedDateTime.now;
import static java.util.Arrays.stream;
import static java.util.Comparator.comparing;
import static java.util.Objects.nonNull;
import static java.util.Comparator.comparingInt;
import static java.util.Objects.isNull;
import static java.util.Optional.ofNullable;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static java.util.regex.Pattern.compile;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.telegram.abilitybots.api.objects.Ability.builder;
import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT;
import static org.telegram.abilitybots.api.objects.Flag.MESSAGE;
import static org.telegram.abilitybots.api.objects.Flag.REPLY;
import static org.telegram.abilitybots.api.objects.Locality.ALL;
import static org.telegram.abilitybots.api.objects.Locality.GROUP;
import static org.telegram.abilitybots.api.objects.Locality.USER;
import static java.util.stream.Collectors.toSet;
import static org.telegram.abilitybots.api.objects.Locality.*;
import static org.telegram.abilitybots.api.objects.MessageContext.newContext;
import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
import static org.telegram.abilitybots.api.objects.Privacy.CREATOR;
import static org.telegram.abilitybots.api.objects.Privacy.GROUP_ADMIN;
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_COMMANDS_NOT_FOUND;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_ERROR;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_MESSAGE;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_INPUT_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_LOCALITY_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_PRIVACY_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.USER_NOT_FOUND;
import static org.telegram.abilitybots.api.util.AbilityUtils.addTag;
import static org.telegram.abilitybots.api.util.AbilityUtils.commitTo;
import static org.telegram.abilitybots.api.util.AbilityUtils.getChatId;
import static org.telegram.abilitybots.api.util.AbilityUtils.getLocalizedMessage;
import static org.telegram.abilitybots.api.util.AbilityUtils.isGroupUpdate;
import static org.telegram.abilitybots.api.util.AbilityUtils.isSuperGroupUpdate;
import static org.telegram.abilitybots.api.util.AbilityUtils.isUserMessage;
import static org.telegram.abilitybots.api.util.AbilityUtils.shortName;
import static org.telegram.abilitybots.api.util.AbilityUtils.stripTag;
import static org.telegram.abilitybots.api.objects.Privacy.*;
import static org.telegram.abilitybots.api.objects.Stats.createStats;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.*;
import static org.telegram.abilitybots.api.util.AbilityUtils.*;
/**
* The <b>father</b> of all ability bots. Bots that need to utilize abilities need to extend this bot.
* <p>
* It's important to note that this bot strictly extends {@link TelegramLongPollingBot}.
* It's important to note that this bot strictly extends {@link DefaultAbsSender}.
* <p>
* All bots extending the {@link BaseAbilityBot} get implicit abilities:
* <ul>
@ -139,83 +83,110 @@ import static org.telegram.abilitybots.api.util.AbilityUtils.stripTag;
*/
@SuppressWarnings({"ConfusingArgumentToVarargsMethod", "UnusedReturnValue", "WeakerAccess", "unused", "ConstantConditions"})
public abstract class BaseAbilityBot extends DefaultAbsSender implements AbilityExtension {
private static final Logger log = LogManager.getLogger(BaseAbilityBot.class);
private static final Logger log = LoggerFactory.getLogger(BaseAbilityBot.class);
protected static final String DEFAULT = "default";
// DB objects
public static final String ADMINS = "ADMINS";
public static final String USERS = "USERS";
public static final String USER_ID = "USER_ID";
public static final String BLACKLIST = "BLACKLIST";
// Factory commands
protected static final String DEFAULT = "default";
protected static final String CLAIM = "claim";
protected static final String BAN = "ban";
protected static final String PROMOTE = "promote";
protected static final String DEMOTE = "demote";
protected static final String UNBAN = "unban";
protected static final String BACKUP = "backup";
protected static final String RECOVER = "recover";
protected static final String COMMANDS = "commands";
protected static final String REPORT = "report";
public static final String STATS = "ABILITYBOT_STATS";
// DB and sender
protected final DBContext db;
protected MessageSender sender;
protected SilentSender silent;
// Ability toggle
private final AbilityToggle toggle;
// Bot token and username
private final String botToken;
private final String botUsername;
// Ability registry
private final List<AbilityExtension> extensions = new ArrayList<>();
private Map<String, Ability> abilities;
private Map<String, Stats> stats;
// Reply registry
private List<Reply> replies;
public abstract int creatorId();
protected BaseAbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) {
protected BaseAbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle, DefaultBotOptions botOptions) {
super(botOptions);
this.botToken = botToken;
this.botUsername = botUsername;
this.db = db;
this.toggle = toggle;
this.sender = new DefaultSender(this);
silent = new SilentSender(sender);
}
public void onRegister() {
registerAbilities();
initStats();
}
/**
* @return the database of this bot
*/
public DBContext db() {
return db;
}
/**
* @return the message sender for this bot
*/
public MessageSender sender() {
return sender;
}
/**
* @return the silent sender for this bot
*/
public SilentSender silent() {
return silent;
}
/**
* @return the map of <ID,User>
*/
protected Map<Integer, User> users() {
public Map<Integer, User> users() {
return db.getMap(USERS);
}
/**
* @return the map of <Username,ID>
*/
protected Map<String, Integer> userIds() {
public Map<String, Integer> userIds() {
return db.getMap(USER_ID);
}
/**
* @return a blacklist containing all the IDs of the banned users
*/
protected Set<Integer> blacklist() {
public Set<Integer> blacklist() {
return db.getSet(BLACKLIST);
}
/**
* @return an admin set of all the IDs of bot administrators
*/
protected Set<Integer> admins() {
public Set<Integer> admins() {
return db.getSet(ADMINS);
}
/**
* @return a mapping of ability and reply names to their corresponding statistics
*/
public Map<String, Stats> stats() {
return stats;
}
/**
* @return the immutable map of <String,Ability>
*/
@ -230,6 +201,7 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return replies;
}
/**
* This method contains the stream of actions that are applied on any update.
* <p>
@ -247,6 +219,7 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
.filter(this::checkBlacklist)
.map(this::addUser)
.filter(this::filterReply)
.filter(this::hasUser)
.map(this::getAbility)
.filter(this::validateAbility)
.filter(this::checkPrivacy)
@ -255,8 +228,12 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
.filter(this::checkMessageFlags)
.map(this::getContext)
.map(this::consumeUpdate)
.map(this::updateStats)
.forEach(this::postConsumption);
// Commit to DB now after all the actions have been dealt
db.commit();
long processingTime = System.currentTimeMillis() - millisStarted;
log.info(format("[%s] Processing of update [%s] ended at %s%n---> Processing time: [%d ms] <---%n", botUsername, update.getUpdateId(), now(), processingTime));
}
@ -269,6 +246,32 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return botUsername;
}
public Privacy getPrivacy(Update update, int id) {
return isCreator(id) ?
CREATOR : isAdmin(id) ?
ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ?
GROUP_ADMIN : PUBLIC;
}
public boolean isGroupAdmin(Update update, int id) {
return isGroupAdmin(getChatId(update), id);
}
public boolean isGroupAdmin(long chatId, int id) {
GetChatAdministrators admins = GetChatAdministrators.builder().chatId(Long.toString(chatId)).build();
return silent.execute(admins)
.orElse(new ArrayList<>()).stream()
.anyMatch(member -> member.getUser().getId() == id);
}
public boolean isCreator(int id) {
return id == creatorId();
}
public boolean isAdmin(Integer id) {
return admins().contains(id);
}
/**
* Test the update against the provided global flags. The default implementation is a passthrough to all updates.
* <p>
@ -281,372 +284,28 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return true;
}
/**
* Gets the user with the specified username.
*
* @param username the username of the required user
* @return the user
*/
protected User getUser(String username) {
Integer id = userIds().get(username.toLowerCase());
if (id == null) {
throw new IllegalStateException(format("Could not find ID corresponding to username [%s]", username));
}
return getUser(id);
protected String getCommandPrefix() {
return "/";
}
/**
* Gets the user with the specified ID.
*
* @param id the id of the required user
* @return the user
*/
protected User getUser(int id) {
User user = users().get(id);
if (user == null) {
throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id));
}
return user;
protected String getCommandRegexSplit() {
return " ";
}
/**
* Gets the user with the specified username. If user was not found, the bot will send a message on Telegram.
*
* @param username the username of the required user
* @param ctx the message context with the originating user
* @return the id of the user
*/
protected int getUserIdSendError(String username, MessageContext ctx) {
try {
return getUser(username).getId();
} catch (IllegalStateException ex) {
silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId());
throw ex;
}
protected boolean allowContinuousText() {
return false;
}
/**
* <p>
* Format of the report:
* <p>
* [command1] - [description1]
* <p>
* [command2] - [description2]
* <p>
* ...
* <p>
* Once you invoke it, the bot will send the available commands to the chat. This is a public ability so anyone can invoke it.
* <p>
* Usage: <code>/commands</code>
*
* @return the ability to report commands defined by the child bot.
*/
public Ability reportCommands() {
return builder()
.name(REPORT)
.locality(ALL)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
String commands = abilities.values().stream()
.filter(ability -> nonNull(ability.info()))
.map(ability -> {
String name = ability.name();
String info = ability.info();
return format("%s - %s", name, info);
})
.sorted()
.reduce((a, b) -> format("%s%n%s", a, b))
.orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()));
silent.send(commands, ctx.chatId());
})
.build();
protected void addExtension(AbilityExtension extension) {
this.extensions.add(extension);
}
/**
* Default format:
* <p>
* PUBLIC
* <p>
* [command1] - [description1]
* <p>
* [command2] - [description2]
* <p>
* GROUP_ADMIN
* <p>
* [command1] - [description1]
* <p>
* ...
*
* @return the ability to print commands based on the privacy of the requesting user
*/
public Ability commands() {
return builder()
.name(COMMANDS)
.locality(USER)
.privacy(PUBLIC)
.input(0)
.action(ctx -> {
Privacy privacy = getPrivacy(ctx.update(), ctx.user().getId());
ListMultimap<Privacy, String> abilitiesPerPrivacy = abilities.values().stream()
.map(ability -> {
String name = ability.name();
String info = ability.info();
if (!isEmpty(info))
return Pair.of(ability.privacy(), format("/%s - %s", name, info));
return Pair.of(ability.privacy(), format("/%s", name));
})
.sorted(comparing(Pair::b))
.collect(() -> hashKeys().arrayListValues().build(),
(map, pair) -> map.put(pair.a(), pair.b()),
Multimap::putAll);
String commands = abilitiesPerPrivacy.asMap().entrySet().stream()
.filter(entry -> privacy.compareTo(entry.getKey()) >= 0)
.sorted(comparing(Entry::getKey))
.map(entry ->
entry.getValue().stream()
.reduce(entry.getKey().toString(), (a, b) -> format("%s\n%s", a, b))
)
.collect(joining("\n"));
if (commands.isEmpty())
commands = getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode());
silent.send(commands, ctx.chatId());
})
.build();
protected void addExtensions(AbilityExtension... extensions) {
this.extensions.addAll(Arrays.asList(extensions));
}
/**
* This backup ability returns the object defined by {@link DBContext#backup()} as a message document.
* <p>
* This is a high-profile ability and is restricted to the CREATOR only.
* <p>
* Usage: <code>/backup</code>
*
* @return the ability to back-up the database of the bot
*/
public Ability backupDB() {
return builder()
.name(BACKUP)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
File backup = new File("backup.json");
try (PrintStream printStream = new PrintStream(backup)) {
printStream.print(db.backup());
sender.sendDocument(new SendDocument()
.setDocument(backup)
.setChatId(ctx.chatId())
);
} catch (FileNotFoundException e) {
log.error("Error while fetching backup", e);
} catch (TelegramApiException e) {
log.error("Error while sending document/backup file", e);
}
})
.build();
}
/**
* Recovers the bot database using {@link DBContext#recover(Object)}.
* <p>
* The bot recovery process hugely depends on the implementation of the recovery method of {@link DBContext}.
* <p>
* Usage: <code>/recover</code>
*
* @return the ability to recover the database of the bot
*/
public Ability recoverDB() {
return builder()
.name(RECOVER)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> silent.forceReply(
getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId()))
.reply(update -> {
String replyToMsg = update.getMessage().getReplyToMessage().getText();
String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode());
if (!replyToMsg.equals(recoverMessage))
return;
String fileId = update.getMessage().getDocument().getFileId();
try (FileReader reader = new FileReader(downloadFileWithId(fileId))) {
String backupData = IOUtils.toString(reader);
if (db.recover(backupData)) {
send(ABILITY_RECOVER_SUCCESS, update);
} else {
send(ABILITY_RECOVER_FAIL, update);
}
} catch (Exception e) {
log.error("Could not recover DB from backup", e);
send(ABILITY_RECOVER_ERROR, update);
}
}, MESSAGE, DOCUMENT, REPLY)
.build();
}
/**
* Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link BaseAbilityBot#BLACKLIST}.
* <p>
* Usage: <code>/ban @username</code>
* <p>
* <u>Note that admins who try to ban the creator, get banned.</u>
*
* @return the ability to ban the user from any kind of <b>bot interaction</b>
*/
public Ability banUser() {
return builder()
.name(BAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
int userId = getUserIdSendError(username, ctx);
String bannedUser;
// Protection from abuse
if (userId == creatorId()) {
userId = ctx.user().getId();
bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user());
} else {
bannedUser = addTag(username);
}
Set<Integer> blacklist = blacklist();
if (blacklist.contains(userId))
sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser));
else {
blacklist.add(userId);
sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser));
}
})
.post(commitTo(db))
.build();
}
/**
* Usage: <code>/unban @username</code>
*
* @return the ability to unban a user
*/
public Ability unbanUser() {
return builder()
.name(UNBAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> blacklist = blacklist();
if (!blacklist.remove(userId))
silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
else {
silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
}
})
.post(commitTo(db))
.build();
}
/**
* @return the ability to promote a user to a bot admin
*/
public Ability promoteAdmin() {
return builder()
.name(PROMOTE)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> admins = admins();
if (admins.contains(userId))
sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username));
else {
admins.add(userId);
sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username));
}
}).post(commitTo(db))
.build();
}
/**
* @return the ability to demote an admin to a user
*/
public Ability demoteAdmin() {
return builder()
.name(DEMOTE)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> admins = admins();
if (admins.remove(userId)) {
sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username));
} else {
sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username));
}
})
.post(commitTo(db))
.build();
}
/**
* Regular users and admins who try to claim the bot will get <b>banned</b>.
*
* @return the ability to claim yourself as the master and creator of the bot
*/
public Ability claimCreator() {
return builder()
.name(CLAIM)
.locality(ALL)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
Set<Integer> admins = admins();
int id = creatorId();
if (admins.contains(id))
send(ABILITY_CLAIM_FAIL, ctx);
else {
admins.add(id);
send(ABILITY_CLAIM_SUCCESS, ctx);
}
})
.post(commitTo(db))
.build();
}
private Optional<Message> send(String message, MessageContext ctx, String... args) {
return silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
}
private Optional<Message> sendMd(String message, MessageContext ctx, String... args) {
return silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
}
private Optional<Message> send(String message, Update upd) {
Long chatId = upd.getMessage().getChatId();
return silent.send(getLocalizedMessage(message, AbilityUtils.getUser(upd).getLanguageCode()), chatId);
protected void addExtensions(Collection<AbilityExtension> extensions) {
this.extensions.addAll(extensions);
}
/**
@ -657,19 +316,27 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
private void registerAbilities() {
try {
// Collect all classes that implement AbilityExtension declared in the bot
List<AbilityExtension> extensions = stream(getClass().getMethods())
extensions.addAll(stream(getClass().getMethods())
.filter(checkReturnType(AbilityExtension.class))
.map(returnExtension(this))
.collect(Collectors.toList());
.collect(Collectors.toList()));
// Add the bot itself as it is an AbilityExtension
extensions.add(this);
DefaultAbilities defaultAbs = new DefaultAbilities(this);
Stream<Ability> defaultAbsStream = stream(DefaultAbilities.class.getMethods())
.filter(checkReturnType(Ability.class))
.map(returnAbility(defaultAbs))
.filter(ab -> !toggle.isOff(ab))
.map(toggle::processAbility);
// Extract all abilities from every single extension instance
abilities = extensions.stream()
abilities = Stream.concat(defaultAbsStream,
extensions.stream()
.flatMap(ext -> stream(ext.getClass().getMethods())
.filter(checkReturnType(Ability.class))
.map(returnAbility(ext)))
.map(returnAbility(ext))))
// Abilities are immutable, build it respectively
.collect(ImmutableMap::<String, Ability>builder,
(b, a) -> b.put(a.name(), a),
@ -680,11 +347,13 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
Stream<Reply> extensionReplies = extensions.stream()
.flatMap(ext -> stream(ext.getClass().getMethods())
.filter(checkReturnType(Reply.class))
.map(returnReply(ext)));
.map(returnReply(ext)))
.flatMap(Reply::stream);
// Replies can be standalone or attached to abilities, fetch those too
Stream<Reply> abilityReplies = abilities.values().stream()
.flatMap(ability -> ability.replies().stream());
.flatMap(ability -> ability.replies().stream())
.flatMap(Reply::stream);
// Now create the replies registry (list)
replies = Stream.concat(abilityReplies, extensionReplies).collect(
@ -698,18 +367,31 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
}
}
private void initStats() {
Set<String> enabledStats = Stream.concat(
replies.stream().filter(Reply::statsEnabled).map(Reply::name),
abilities.entrySet().stream()
.filter(entry -> entry.getValue().statsEnabled())
.map(Map.Entry::getKey)).collect(toSet());
stats = db.getMap(STATS);
Set<String> toBeRemoved = difference(stats.keySet(), enabledStats);
toBeRemoved.forEach(stats::remove);
enabledStats.forEach(abName -> stats.computeIfAbsent(abName,
name -> createStats(abName, 0)));
}
/**
* @param clazz the type to be tested
* @return a predicate testing the return type of the method corresponding to the class parameter
*/
private Predicate<Method> checkReturnType(Class<?> clazz) {
private static Predicate<Method> checkReturnType(Class<?> clazz) {
return method -> clazz.isAssignableFrom(method.getReturnType());
}
/**
* Invokes the method and retrieves its return {@link Reply}.
*
* @param obj an bot or extension that this method is invoked with
* @param obj a bot or extension that this method is invoked with
* @return a {@link Function} which returns the {@link Reply} returned by the given method
*/
private Function<? super Method, AbilityExtension> returnExtension(Object obj) {
@ -726,10 +408,10 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
/**
* Invokes the method and retrieves its return {@link Ability}.
*
* @param obj an bot or extension that this method is invoked with
* @param obj a bot or extension that this method is invoked with
* @return a {@link Function} which returns the {@link Ability} returned by the given method
*/
private Function<? super Method, Ability> returnAbility(Object obj) {
private static Function<? super Method, Ability> returnAbility(Object obj) {
return method -> {
try {
return (Ability) method.invoke(obj);
@ -743,10 +425,10 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
/**
* Invokes the method and retrieves its return {@link Reply}.
*
* @param obj an bot or extension that this method is invoked with
* @param obj a bot or extension that this method is invoked with
* @return a {@link Function} which returns the {@link Reply} returned by the given method
*/
private Function<? super Method, Reply> returnReply(Object obj) {
private static Function<? super Method, Reply> returnReply(Object obj) {
return method -> {
try {
return (Reply) method.invoke(obj);
@ -767,16 +449,40 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return pair;
}
Pair<MessageContext, Ability> updateStats(Pair<MessageContext, Ability> pair) {
Ability ab = pair.b();
if (ab.statsEnabled()) {
updateStats(pair.b().name());
}
return pair;
}
private void updateReplyStats(Reply reply) {
if (reply.statsEnabled()) {
updateStats(reply.name());
}
}
void updateStats(String name) {
Stats statsObj = stats.get(name);
statsObj.hit();
stats.put(name, statsObj);
}
Pair<MessageContext, Ability> getContext(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
User user = AbilityUtils.getUser(update);
return Pair.of(newContext(update, user, getChatId(update), trio.c()), trio.b());
return Pair.of(newContext(update, user, getChatId(update), this, trio.c()), trio.b());
}
boolean checkBlacklist(Update update) {
Integer id = AbilityUtils.getUser(update).getId();
User user = getUser(update);
if (isNull(user)) {
return true;
}
int id = user.getId();
return id == creatorId() || !blacklist().contains(id);
}
@ -832,30 +538,6 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return isOk;
}
@NotNull
private Privacy getPrivacy(Update update, int id) {
return isCreator(id) ?
CREATOR : isAdmin(id) ?
ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ?
GROUP_ADMIN : PUBLIC;
}
private boolean isGroupAdmin(Update update, int id) {
GetChatAdministrators admins = new GetChatAdministrators().setChatId(getChatId(update));
return silent.execute(admins)
.orElse(new ArrayList<>()).stream()
.anyMatch(member -> member.getUser().getId() == id);
}
private boolean isCreator(int id) {
return id == creatorId();
}
private boolean isAdmin(Integer id) {
return admins().contains(id);
}
boolean validateAbility(Trio<Update, Ability, String[]> trio) {
return trio.b() != null;
}
@ -867,17 +549,28 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
if (!update.hasMessage() || !msg.hasText())
return Trio.of(update, abilities.get(DEFAULT), new String[]{});
String[] tokens = msg.getText().split(" ");
if (tokens[0].startsWith("/")) {
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
Ability ability = abilities.get(abilityToken);
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
return Trio.of(update, ability, tokens);
Ability ability;
String[] tokens;
if (allowContinuousText()) {
String abName = abilities.keySet().stream()
.filter(name -> msg.getText().startsWith(format("%s%s", getCommandPrefix(), name)))
.max(comparingInt(String::length))
.orElse(DEFAULT);
tokens = msg.getText()
.replaceFirst(getCommandPrefix() + abName, "")
.split(getCommandRegexSplit());
ability = abilities.get(abName);
} else {
Ability ability = abilities.get(DEFAULT);
return Trio.of(update, ability, tokens);
tokens = msg.getText().split(getCommandRegexSplit());
if (tokens[0].startsWith(getCommandPrefix())) {
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
ability = abilities.get(abilityToken);
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
} else {
ability = abilities.get(DEFAULT);
}
}
return Trio.of(update, ability, tokens);
}
private String stripBotUsername(String token) {
@ -888,6 +581,10 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
Update addUser(Update update) {
User endUser = AbilityUtils.getUser(update);
if (endUser.equals(EMPTY_USER)) {
// Can't add an empty user, return the update as is
return update;
}
users().compute(endUser.getId(), (id, user) -> {
if (user == null) {
@ -903,10 +600,15 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return user;
});
db.commit();
return update;
}
private boolean hasUser(Update update) {
// Valid updates without users should return an empty user
// Updates that are not recognized by the getUser method will throw an exception
return !AbilityUtils.getUser(update).equals(EMPTY_USER);
}
private void updateUserId(User oldUser, User newUser) {
if (oldUser != null && oldUser.getUserName() != null) {
// Remove old username -> ID
@ -921,14 +623,25 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
boolean filterReply(Update update) {
return replies.stream()
.filter(reply -> reply.isOkFor(update))
.map(reply -> {
.filter(reply -> runSilently(() -> reply.isOkFor(update), reply.name()))
.map(reply -> runSilently(() -> {
reply.actOn(update);
updateReplyStats(reply);
return false;
})
}, reply.name()))
.reduce(true, Boolean::logicalAnd);
}
boolean runSilently(Callable<Boolean> callable, String name) {
try {
return callable.call();
} catch(Exception ex) {
log.error(format("Reply [%s] failed to check for conditions. " +
"Make sure you're safeguarding against all possible updates.", name));
}
return false;
}
boolean checkMessageFlags(Trio<Update, Ability, String[]> trio) {
Ability ability = trio.b();
Update update = trio.a();
@ -938,13 +651,4 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return ability.flags().stream()
.reduce(true, flagAnd, Boolean::logicalAnd);
}
private File downloadFileWithId(String fileId) throws TelegramApiException {
return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId)));
}
private String escape(String username) {
return username.replace("_", "\\_");
}
}

View File

@ -0,0 +1,479 @@
package org.telegram.abilitybots.api.bot;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.abilitybots.api.objects.Privacy;
import org.telegram.abilitybots.api.util.AbilityExtension;
import org.telegram.abilitybots.api.util.AbilityUtils;
import org.telegram.abilitybots.api.util.Pair;
import org.telegram.telegrambots.meta.api.methods.GetFile;
import org.telegram.telegrambots.meta.api.methods.send.SendDocument;
import org.telegram.telegrambots.meta.api.objects.InputFile;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.PrintStream;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.StringJoiner;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.MultimapBuilder.hashKeys;
import static java.lang.String.format;
import static java.util.Comparator.comparing;
import static java.util.Objects.nonNull;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.telegram.abilitybots.api.objects.Ability.builder;
import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT;
import static org.telegram.abilitybots.api.objects.Flag.MESSAGE;
import static org.telegram.abilitybots.api.objects.Flag.REPLY;
import static org.telegram.abilitybots.api.objects.Locality.ALL;
import static org.telegram.abilitybots.api.objects.Locality.USER;
import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
import static org.telegram.abilitybots.api.objects.Privacy.CREATOR;
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_BAN_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_CLAIM_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_COMMANDS_NOT_FOUND;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_DEMOTE_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_PROMOTE_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_ERROR;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_MESSAGE;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_RECOVER_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_FAIL;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.ABILITY_UNBAN_SUCCESS;
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.USER_NOT_FOUND;
import static org.telegram.abilitybots.api.util.AbilityUtils.addTag;
import static org.telegram.abilitybots.api.util.AbilityUtils.escape;
import static org.telegram.abilitybots.api.util.AbilityUtils.getLocalizedMessage;
import static org.telegram.abilitybots.api.util.AbilityUtils.shortName;
import static org.telegram.abilitybots.api.util.AbilityUtils.stripTag;
public final class DefaultAbilities implements AbilityExtension {
// Default commands
public static final String CLAIM = "claim";
public static final String BAN = "ban";
public static final String PROMOTE = "promote";
public static final String DEMOTE = "demote";
public static final String UNBAN = "unban";
public static final String BACKUP = "backup";
public static final String RECOVER = "recover";
public static final String COMMANDS = "commands";
public static final String REPORT = "report";
public static final String STATS = "stats";
private static final Logger log = LoggerFactory.getLogger(DefaultAbilities.class);
private final BaseAbilityBot bot;
public DefaultAbilities(BaseAbilityBot bot) {
this.bot = bot;
}
/**
* <p>
* Format of the report:
* <p>
* [command1] - [description1]
* <p>
* [command2] - [description2]
* <p>
* ...
* <p>
* Once you invoke it, the bot will send the available commands to the chat. This is a public ability so anyone can invoke it.
* <p>
* Usage: <code>/commands</code>
*
* @return the ability to report commands defined by the child bot.
*/
public Ability reportCommands() {
return builder()
.name(REPORT)
.locality(ALL)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
String commands = bot.abilities().values().stream()
.filter(ability -> nonNull(ability.info()))
.map(ability -> {
String name = ability.name();
String info = ability.info();
return format("%s - %s", name, info);
})
.sorted()
.reduce((a, b) -> format("%s%n%s", a, b))
.orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()));
bot.silent.send(commands, ctx.chatId());
})
.build();
}
/**
* Default format:
* <p>
* PUBLIC
* <p>
* [command1] - [description1]
* <p>
* [command2] - [description2]
* <p>
* GROUP_ADMIN
* <p>
* [command1] - [description1]
* <p>
* ...
*
* @return the ability to print commands based on the privacy of the requesting user
*/
public Ability commands() {
return builder()
.name(COMMANDS)
.locality(USER)
.privacy(PUBLIC)
.input(0)
.action(ctx -> {
Privacy privacy = bot.getPrivacy(ctx.update(), ctx.user().getId());
ListMultimap<Privacy, String> abilitiesPerPrivacy = bot.abilities().values().stream()
.map(ability -> {
String name = ability.name();
String info = ability.info();
if (!isEmpty(info))
return Pair.of(ability.privacy(), format("/%s - %s", name, info));
return Pair.of(ability.privacy(), format("/%s", name));
})
.sorted(comparing(Pair::b))
.collect(() -> hashKeys().arrayListValues().build(),
(map, pair) -> map.put(pair.a(), pair.b()),
Multimap::putAll);
String commands = abilitiesPerPrivacy.asMap().entrySet().stream()
.filter(entry -> privacy.compareTo(entry.getKey()) >= 0)
.sorted(comparing(Map.Entry::getKey))
.map(entry ->
entry.getValue().stream()
.reduce(entry.getKey().toString(), (a, b) -> format("%s\n%s", a, b))
)
.collect(joining("\n"));
if (commands.isEmpty())
commands = getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode());
bot.silent.send(commands, ctx.chatId());
})
.build();
}
/**
* @return the ability to report statistics for abilities and replies.
*/
public Ability reportStats() {
return builder()
.name(STATS)
.locality(ALL)
.privacy(ADMIN)
.input(0)
.action(ctx -> {
String stats = bot.stats().entrySet().stream()
.map(entry -> String.format("%s: %d", entry.getKey(), entry.getValue().hits()))
.reduce(new StringJoiner("\n"), StringJoiner::add, StringJoiner::merge)
.toString();
bot.silent.send(stats, ctx.chatId());
})
.build();
}
/**
* This backup ability returns the object defined by {@link DBContext#backup()} as a message document.
* <p>
* This is a high-profile ability and is restricted to the CREATOR only.
* <p>
* Usage: <code>/backup</code>
*
* @return the ability to back-up the database of the bot
*/
public Ability backupDB() {
return builder()
.name(BACKUP)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
File backup = new File("backup.json");
try (PrintStream printStream = new PrintStream(backup)) {
printStream.print(bot.db.backup());
bot.sender.sendDocument(SendDocument.builder()
.document(new InputFile(backup))
.chatId(ctx.chatId().toString())
.build()
);
} catch (FileNotFoundException e) {
log.error("Error while fetching backup", e);
} catch (TelegramApiException e) {
log.error("Error while sending document/backup file", e);
}
})
.build();
}
/**
* Recovers the bot database using {@link DBContext#recover(Object)}.
* <p>
* The bot recovery process hugely depends on the implementation of the recovery method of {@link DBContext}.
* <p>
* Usage: <code>/recover</code>
*
* @return the ability to recover the database of the bot
*/
public Ability recoverDB() {
return builder()
.name(RECOVER)
.locality(USER)
.privacy(CREATOR)
.input(0)
.action(ctx -> bot.silent.forceReply(
getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId()))
.reply(update -> {
String replyToMsg = update.getMessage().getReplyToMessage().getText();
String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode());
if (!replyToMsg.equals(recoverMessage))
return;
String fileId = update.getMessage().getDocument().getFileId();
try (FileReader reader = new FileReader(downloadFileWithId(fileId))) {
String backupData = IOUtils.toString(reader);
if (bot.db.recover(backupData)) {
send(ABILITY_RECOVER_SUCCESS, update);
} else {
send(ABILITY_RECOVER_FAIL, update);
}
} catch (Exception e) {
log.error("Could not recover DB from backup", e);
send(ABILITY_RECOVER_ERROR, update);
}
}, MESSAGE, DOCUMENT, REPLY)
.build();
}
/**
* Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link BaseAbilityBot#BLACKLIST}.
* <p>
* Usage: <code>/ban @username</code>
* <p>
* <u>Note that admins who try to ban the creator, get banned.</u>
*
* @return the ability to ban the user from any kind of <b>bot interaction</b>
*/
public Ability banUser() {
return builder()
.name(BAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
int userId = getUserIdSendError(username, ctx);
String bannedUser;
// Protection from abuse
if (userId == bot.creatorId()) {
userId = ctx.user().getId();
bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user());
} else {
bannedUser = addTag(username);
}
Set<Integer> blacklist = bot.blacklist();
if (blacklist.contains(userId))
sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser));
else {
blacklist.add(userId);
sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser));
}
})
.build();
}
/**
* Usage: <code>/unban @username</code>
*
* @return the ability to unban a user
*/
public Ability unbanUser() {
return builder()
.name(UNBAN)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> blacklist = bot.blacklist();
if (!blacklist.remove(userId))
bot.silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
else {
bot.silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
}
})
.build();
}
/**
* @return the ability to promote a user to a bot admin
*/
public Ability promoteAdmin() {
return builder()
.name(PROMOTE)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> admins = bot.admins();
if (admins.contains(userId))
sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username));
else {
admins.add(userId);
sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username));
}
})
.build();
}
/**
* @return the ability to demote an admin to a user
*/
public Ability demoteAdmin() {
return builder()
.name(DEMOTE)
.locality(ALL)
.privacy(ADMIN)
.input(1)
.action(ctx -> {
String username = stripTag(ctx.firstArg());
Integer userId = getUserIdSendError(username, ctx);
Set<Integer> admins = bot.admins();
if (admins.remove(userId)) {
sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username));
} else {
sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username));
}
})
.build();
}
/**
* Regular users and admins who try to claim the bot will get <b>banned</b>.
*
* @return the ability to claim yourself as the master and creator of the bot
*/
public Ability claimCreator() {
return builder()
.name(CLAIM)
.locality(ALL)
.privacy(CREATOR)
.input(0)
.action(ctx -> {
Set<Integer> admins = bot.admins();
int id = bot.creatorId();
if (admins.contains(id))
send(ABILITY_CLAIM_FAIL, ctx);
else {
admins.add(id);
send(ABILITY_CLAIM_SUCCESS, ctx);
}
})
.build();
}
/**
* Gets the user with the specified username.
*
* @param username the username of the required user
* @return the user
*/
private User getUser(String username) {
Integer id = bot.userIds().get(username.toLowerCase());
if (id == null) {
throw new IllegalStateException(format("Could not find ID corresponding to username [%s]", username));
}
return getUser(id);
}
/**
* Gets the user with the specified ID.
*
* @param id the id of the required user
* @return the user
*/
private User getUser(int id) {
User user = bot.users().get(id);
if (user == null) {
throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id));
}
return user;
}
/**
* Gets the user with the specified username. If user was not found, the bot will send a message on Telegram.
*
* @param username the username of the required user
* @param ctx the message context with the originating user
* @return the id of the user
*/
private int getUserIdSendError(String username, MessageContext ctx) {
try {
return getUser(username).getId();
} catch (IllegalStateException ex) {
bot.silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId());
throw ex;
}
}
private Optional<Message> send(String message, MessageContext ctx, String... args) {
return bot.silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
}
private Optional<Message> sendMd(String message, MessageContext ctx, String... args) {
return bot.silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
}
private Optional<Message> send(String message, Update upd) {
Long chatId = upd.getMessage().getChatId();
return bot.silent.send(getLocalizedMessage(message, AbilityUtils.getUser(upd).getLanguageCode()), chatId);
}
protected File downloadFileWithId(String fileId) throws TelegramApiException {
return bot.sender.downloadFile(bot.sender.execute(GetFile.builder().fileId(fileId).build()));
}
}

View File

@ -0,0 +1,45 @@
package org.telegram.abilitybots.api.db;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import java.util.Objects;
public class BackupVar<T> {
@JsonProperty("var")
private final T var;
private BackupVar(T var) {
this.var = var;
}
@JsonCreator
public static <R> BackupVar<R> createVar(@JsonProperty("var") R var) {
return new BackupVar<>(var);
}
public T var() {
return var;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BackupVar<?> backupVar = (BackupVar<?>) o;
return Objects.equals(var, backupVar.var);
}
@Override
public int hashCode() {
return Objects.hash(var);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("var", var)
.toString();
}
}

View File

@ -3,21 +3,16 @@ package org.telegram.abilitybots.api.db;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.mapdb.Atomic;
import org.mapdb.DB;
import org.mapdb.DBMaker;
import org.mapdb.Serializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.abilitybots.api.util.Pair;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.*;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
@ -35,7 +30,7 @@ import static org.mapdb.Serializer.JAVA;
*/
@SuppressWarnings({"unchecked", "WeakerAccess"})
public class MapDBContext implements DBContext {
private static final Logger log = LogManager.getLogger(MapDBContext.class);
private static final Logger log = LoggerFactory.getLogger(MapDBContext.class);
private final DB db;
private final ObjectMapper objectMapper;
@ -188,15 +183,15 @@ public class MapDBContext implements DBContext {
return Pair.of(entry.getKey(), newArrayList((List) struct));
else if (struct instanceof Map)
return Pair.of(entry.getKey(), new BackupMap((Map) struct));
else
return Pair.of(entry.getKey(), struct);
else if (struct instanceof Atomic.Var)
return Pair.of(entry.getKey(), BackupVar.createVar(((Atomic.Var) struct).get()));
return Pair.of(entry.getKey(), struct);
}).collect(toMap(pair -> (String) pair.a(), Pair::b));
}
private void doRecover(Map<String, Object> backupData) {
clear();
backupData.forEach((name, value) -> {
if (value instanceof Set) {
Set entrySet = (Set) value;
getSet(name).addAll(entrySet);
@ -206,6 +201,8 @@ public class MapDBContext implements DBContext {
} else if (value instanceof List) {
List entryList = (List) value;
getList(name).addAll(entryList);
} else if (value instanceof BackupVar) {
getVar(name).set(((BackupVar) value).var());
} else {
log.error(format("Unable to identify object type during DB recovery, entry name: %s", name));
}

View File

@ -2,8 +2,8 @@ package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.Arrays;
@ -17,7 +17,7 @@ import static com.google.common.collect.Lists.newArrayList;
import static java.lang.String.format;
import static java.util.Objects.hash;
import static java.util.Optional.ofNullable;
import static org.apache.commons.lang3.StringUtils.*;
import static org.telegram.abilitybots.api.util.AbilityUtils.isValidCommandName;
/**
* An ability is a fully-fledged bot action that contains all the necessary information to process:
@ -35,23 +35,23 @@ import static org.apache.commons.lang3.StringUtils.*;
* @author Abbas Abou Daya
*/
public final class Ability {
private static final Logger log = LogManager.getLogger(Ability.class);
private static final Logger log = LoggerFactory.getLogger(Ability.class);
private final String name;
private final String info;
private final Locality locality;
private final Privacy privacy;
private final int argNum;
private final boolean statsEnabled;
private final Consumer<MessageContext> action;
private final Consumer<MessageContext> postAction;
private final List<Reply> replies;
private final List<Predicate<Update>> flags;
@SafeVarargs
private Ability(String name, String info, Locality locality, Privacy privacy, int argNum, Consumer<MessageContext> action, Consumer<MessageContext> postAction, List<Reply> replies, Predicate<Update>... flags) {
checkArgument(!isEmpty(name), "Method name cannot be empty");
checkArgument(!containsWhitespace(name), "Method name cannot contain spaces");
checkArgument(isAlphanumeric(name), "Method name can only be alpha-numeric", name);
private Ability(String name, String info, Locality locality, Privacy privacy, int argNum, boolean statsEnabled, Consumer<MessageContext> action, Consumer<MessageContext> postAction, List<Reply> replies, Predicate<Update>... flags) {
checkArgument(isValidCommandName(name), "Method name can only contain alpha-numeric characters and underscores," +
" cannot be longer than 31 characters, empty or null", name);
this.name = name;
this.info = info;
@ -70,6 +70,7 @@ public final class Ability {
this.postAction = postAction;
this.replies = replies;
this.statsEnabled = statsEnabled;
}
public static AbilityBuilder builder() {
@ -96,6 +97,10 @@ public final class Ability {
return argNum;
}
public boolean statsEnabled() {
return statsEnabled;
}
public Consumer<MessageContext> action() {
return action;
}
@ -147,17 +152,19 @@ public final class Ability {
private Privacy privacy;
private Locality locality;
private int argNum;
private Consumer<MessageContext> consumer;
private Consumer<MessageContext> postConsumer;
private boolean statsEnabled;
private Consumer<MessageContext> action;
private Consumer<MessageContext> postAction;
private List<Reply> replies;
private Predicate<Update>[] flags;
private AbilityBuilder() {
statsEnabled = false;
replies = newArrayList();
}
public AbilityBuilder action(Consumer<MessageContext> consumer) {
this.consumer = consumer;
this.action = consumer;
return this;
}
@ -186,13 +193,18 @@ public final class Ability {
return this;
}
public AbilityBuilder enableStats() {
statsEnabled = true;
return this;
}
public AbilityBuilder privacy(Privacy privacy) {
this.privacy = privacy;
return this;
}
public AbilityBuilder post(Consumer<MessageContext> postConsumer) {
this.postConsumer = postConsumer;
public AbilityBuilder post(Consumer<MessageContext> postAction) {
this.postAction = postAction;
return this;
}
@ -202,8 +214,26 @@ public final class Ability {
return this;
}
public final AbilityBuilder reply(Reply reply) {
replies.add(reply);
return this;
}
public AbilityBuilder basedOn(Ability ability) {
replies.clear();
replies.addAll(ability.replies());
return name(ability.name())
.info(ability.info())
.input(ability.tokens())
.locality(ability.locality())
.privacy(ability.privacy())
.action(ability.action())
.post(ability.postAction());
}
public Ability build() {
return new Ability(name, info, locality, privacy, argNum, consumer, postConsumer, replies, flags);
return new Ability(name, info, locality, privacy, argNum, statsEnabled, action, postAction, replies, flags);
}
}
}

View File

@ -25,14 +25,18 @@ public enum Flag implements Predicate<Update> {
EDITED_MESSAGE(Update::hasEditedMessage),
INLINE_QUERY(Update::hasInlineQuery),
CHOSEN_INLINE_QUERY(Update::hasChosenInlineQuery),
SHIPPING_QUERY(Update::hasShippingQuery),
PRECHECKOUT_QUERY(Update::hasPreCheckoutQuery),
POLL(Update::hasPoll),
POLL_ANSWER(Update::hasPollAnswer),
// Message Flags
REPLY(update -> update.getMessage().isReply()),
DOCUMENT(upd -> upd.getMessage().hasDocument()),
TEXT(upd -> upd.getMessage().hasText()),
PHOTO(upd -> upd.getMessage().hasPhoto()),
LOCATION(upd -> upd.getMessage().hasLocation()),
CAPTION(upd -> nonNull(upd.getMessage().getCaption()));
REPLY(upd -> MESSAGE.test(upd) && upd.getMessage().isReply()),
DOCUMENT(upd -> MESSAGE.test(upd) && upd.getMessage().hasDocument()),
TEXT(upd -> MESSAGE.test(upd) && upd.getMessage().hasText()),
PHOTO(upd -> MESSAGE.test(upd) && upd.getMessage().hasPhoto()),
LOCATION(upd -> MESSAGE.test(upd) && upd.getMessage().hasLocation()),
CAPTION(upd -> MESSAGE.test(upd) && nonNull(upd.getMessage().getCaption()));
private final Predicate<Update> predicate;

View File

@ -2,6 +2,7 @@ package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
@ -19,16 +20,18 @@ public class MessageContext {
private final Long chatId;
private final String[] arguments;
private final Update update;
private final BaseAbilityBot bot;
private MessageContext(Update update, User user, Long chatId, String[] arguments) {
private MessageContext(Update update, User user, Long chatId, BaseAbilityBot bot, String[] arguments) {
this.user = user;
this.chatId = chatId;
this.update = update;
this.bot = bot;
this.arguments = arguments;
}
public static MessageContext newContext(Update update, User user, Long chatId, String... arguments) {
return new MessageContext(update, user, chatId, arguments);
public static MessageContext newContext(Update update, User user, Long chatId, BaseAbilityBot bot, String... arguments) {
return new MessageContext(update, user, chatId, bot, arguments);
}
/**
@ -45,6 +48,13 @@ public class MessageContext {
return chatId;
}
/**
* @return the bot in which this message is executed
*/
public BaseAbilityBot bot() {
return bot;
}
/**
* If there's no message in the update, then this will an empty array.
*

View File

@ -1,6 +1,7 @@
package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.List;
@ -8,8 +9,9 @@ import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.util.Arrays.asList;
import static com.google.common.collect.Lists.newArrayList;
/**
* A reply consists of update conditionals and an action to be applied on the update.
@ -18,18 +20,34 @@ import static java.util.Arrays.asList;
*
* @author Abbas Abou Daya
*/
public final class Reply {
public class Reply {
public final List<Predicate<Update>> conditions;
public final Consumer<Update> action;
private boolean statsEnabled;
private String name;
private Reply(List<Predicate<Update>> conditions, Consumer<Update> action) {
this.conditions = conditions;
Reply(List<Predicate<Update>> conditions, Consumer<Update> action) {
this.conditions = ImmutableList.<Predicate<Update>>builder()
.addAll(conditions)
.build();
this.action = action;
statsEnabled = false;
}
Reply(List<Predicate<Update>> conditions, Consumer<Update> action, String name) {
this(conditions, action);
if (Objects.nonNull(name)) {
enableStats(name);
}
}
public static Reply of(Consumer<Update> action, List<Predicate<Update>> conditions) {
return new Reply(conditions, action);
}
@SafeVarargs
public static Reply of(Consumer<Update> action, Predicate<Update>... conditions) {
return new Reply(asList(conditions), action);
return Reply.of(action, newArrayList(conditions));
}
public boolean isOkFor(Update update) {
@ -42,6 +60,32 @@ public final class Reply {
action.accept(update);
}
public List<Predicate<Update>> conditions() {
return conditions;
}
public Consumer<Update> action() {
return action;
}
public Stream<Reply> stream(){
return Stream.of(this);
}
public Reply enableStats(String name) {
this.name = name;
statsEnabled = true;
return this;
}
public boolean statsEnabled() {
return statsEnabled;
}
public String name() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o)

View File

@ -0,0 +1,132 @@
package org.telegram.abilitybots.api.objects;
import org.jetbrains.annotations.NotNull;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.util.AbilityUtils;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.google.common.collect.Lists.newArrayList;
public class ReplyFlow extends Reply {
private final Set<Reply> nextReplies;
private ReplyFlow(List<Predicate<Update>> conditions, Consumer<Update> action, Set<Reply> nextReplies, String name) {
super(conditions, action, name);
this.nextReplies = nextReplies;
}
public static ReplyFlowBuilder builder(DBContext db) {
return new ReplyFlowBuilder(db);
}
public static ReplyFlowBuilder builder(DBContext db, int id) {
return new ReplyFlowBuilder(db, id);
}
public Set<Reply> nextReplies() {
return nextReplies;
}
@Override
public Stream<Reply> stream() {
return Stream.concat(Stream.of(this), nextReplies.stream().flatMap(Reply::stream));
}
public static class ReplyFlowBuilder {
public static final String STATES = "user_state_replies";
private static AtomicInteger replyCounter = new AtomicInteger();
private final DBContext db;
private final int id;
private List<Predicate<Update>> conds;
private Consumer<Update> action;
private Set<Reply> nextReplies;
private String name;
private ReplyFlowBuilder(DBContext db, int id) {
conds = new ArrayList<>();
nextReplies = new HashSet<>();
this.db = db;
this.id = id;
}
private ReplyFlowBuilder(DBContext db) {
this(db, replyCounter.getAndIncrement());
}
public ReplyFlowBuilder action(Consumer<Update> action) {
this.action = action;
return this;
}
public ReplyFlowBuilder enableStats(String name) {
this.name = name;
return this;
}
public ReplyFlowBuilder onlyIf(Predicate<Update> pred) {
conds.add(pred);
return this;
}
public ReplyFlowBuilder next(Reply nextReply) {
List<Predicate<Update>> statefulConditions = toStateful(nextReply.conditions());
Consumer<Update> statefulAction = nextReply.action().andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).remove(chatId);
});
Reply statefulReply = new Reply(statefulConditions, statefulAction, nextReply.name());
nextReplies.add(statefulReply);
return this;
}
public ReplyFlowBuilder next(ReplyFlow nextReplyFlow) {
List<Predicate<Update>> statefulConditions = toStateful(nextReplyFlow.conditions());
ReplyFlow statefulReplyFlow = new ReplyFlow(statefulConditions, nextReplyFlow.action(), nextReplyFlow.nextReplies(), nextReplyFlow.name());
nextReplies.add(statefulReplyFlow);
return this;
}
public ReplyFlow build() {
if (action == null)
action = upd -> {};
Consumer<Update> statefulAction;
if (nextReplies.size() > 0) {
statefulAction = action.andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).put(chatId, id);
});
} else {
statefulAction = action.andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).remove(chatId);
});
}
return new ReplyFlow(conds, statefulAction, nextReplies, name);
}
@NotNull
private List<Predicate<Update>> toStateful(List<Predicate<Update>> conditions) {
List<Predicate<Update>> statefulConditions = newArrayList(conditions);
statefulConditions.add(0, upd -> {
Long chatId = AbilityUtils.getChatId(upd);
int stateId = db.<Long, Integer>getMap(STATES).getOrDefault(chatId, -1);
return id == stateId;
});
return statefulConditions;
}
}
}

View File

@ -0,0 +1,64 @@
package org.telegram.abilitybots.api.objects;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import org.json.JSONPropertyIgnore;
import java.io.Serializable;
import java.util.Objects;
/**
* Basic POJO to track ability and reply hits. The current implementation is NOT thread safe.
*
* @author Abbas Abou Daya
*/
public final class Stats implements Serializable {
@JsonProperty
private final String name;
@JsonProperty
private long hits;
private Stats(String name) {
this.name = name;
}
@JsonCreator
public static Stats createStats(@JsonProperty("name") String name, @JsonProperty("hits") long hits) {
return new Stats(name);
}
public String name() {
return name;
}
public long hits() {
return hits;
}
public void hit() {
hits++;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Stats that = (Stats) o;
return hits == that.hits &&
Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(name, hits);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("hits", hits)
.toString();
}
}

View File

@ -1,12 +1,13 @@
package org.telegram.abilitybots.api.sender;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.ForceReplyKeyboard;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.updateshandlers.SentCallback;
import java.io.Serializable;
import java.util.Optional;
@ -18,7 +19,7 @@ import java.util.Optional;
* @author Abbas Abou Daya
*/
public class SilentSender {
private static final Logger log = LogManager.getLogger(SilentSender.class);
private static final Logger log = LoggerFactory.getLogger(SilentSender.class);
private final MessageSender sender;
@ -37,7 +38,7 @@ public class SilentSender {
public Optional<Message> forceReply(String message, long id) {
SendMessage msg = new SendMessage();
msg.setText(message);
msg.setChatId(id);
msg.setChatId(Long.toString(id));
msg.setReplyMarkup(new ForceReplyKeyboard());
return execute(msg);
@ -52,18 +53,18 @@ public class SilentSender {
}
}
public <T extends Serializable, Method extends BotApiMethod<T>> Optional<T> executeAsync(Method method) {
public <T extends Serializable, Method extends BotApiMethod<T>, Callback extends SentCallback<T>> void
executeAsync(Method method, Callback callable) {
try {
return Optional.ofNullable(sender.execute(method));
sender.executeAsync(method, callable);
} catch (TelegramApiException e) {
log.error("Could not execute bot API method", e);
return Optional.empty();
}
}
private Optional<Message> doSendMessage(String txt, long groupId, boolean format) {
SendMessage smsg = new SendMessage();
smsg.setChatId(groupId);
smsg.setChatId(Long.toString(groupId));
smsg.setText(txt);
smsg.enableMarkdown(format);

View File

@ -0,0 +1,21 @@
package org.telegram.abilitybots.api.toggle;
import org.telegram.abilitybots.api.objects.Ability;
/**
* This interface can be used to toggle or customize unwanted default abilities by the user.
*/
public interface AbilityToggle {
/**
* @param ab the target ability
* @return true if the ability has been turned off
*/
boolean isOff(Ability ab);
/**
* Abilities that are ON (and have failed the {@link AbilityToggle#isOff} condition) will be processed by this method.
* @param ab the target ability
* @return the processed ability
*/
Ability processAbility(Ability ab);
}

View File

@ -0,0 +1,20 @@
package org.telegram.abilitybots.api.toggle;
import org.telegram.abilitybots.api.objects.Ability;
/**
* This toggle can be used as-is to turn off ALL the default abilities supplied by the library.
* This is for users who are interested in the barebone functionality of AbilityBot.
*/
public class BareboneToggle implements AbilityToggle {
@Override
public boolean isOff(Ability ability) {
return true;
}
@Override
public Ability processAbility(Ability ab) {
// Should never hit this
throw new RuntimeException("Should not process any ability in a vanilla toggle");
}
}

View File

@ -0,0 +1,56 @@
package org.telegram.abilitybots.api.toggle;
import org.telegram.abilitybots.api.objects.Ability;
import java.util.HashMap;
import java.util.Map;
/**
* This custom toggle can be used to customize default abilities supplied by the library. Users can call {@link CustomToggle#toggle} to
* rename the default abilites or {@link CustomToggle#turnOff} to simply turn off the said ability.
*/
public class CustomToggle implements AbilityToggle {
public static final String OFF = "turn_off_base_ability";
private final Map<String, String> baseMapping;
public CustomToggle() {
baseMapping = new HashMap<>();
}
@Override
public boolean isOff(Ability ability) {
return OFF.equalsIgnoreCase(baseMapping.get(ability.name()));
}
@Override
public Ability processAbility(Ability ability) {
if (baseMapping.containsKey(ability.name())) {
return Ability.builder()
.basedOn(ability)
.name(baseMapping.get(ability.name()))
.build();
}
return ability;
}
/**
* @param abilityName the ability you want to change
* @param targetName the final name for this ability
* @return the toggle instance
*/
public CustomToggle toggle(String abilityName, String targetName) {
baseMapping.put(abilityName, targetName);
return this;
}
/**
* @param ability the ability name you would like turned off
* @return the toggle instance
*/
public CustomToggle turnOff(String ability) {
baseMapping.put(ability, OFF);
return this;
}
}

View File

@ -0,0 +1,19 @@
package org.telegram.abilitybots.api.toggle;
import org.telegram.abilitybots.api.objects.Ability;
/**
* If the user does not supply a toggle to their constructor, the default toggle will be instantiated.
* This default toggle allows all the default abilities to be registered.
*/
public class DefaultToggle implements AbilityToggle {
@Override
public boolean isOff(Ability ability) {
return false;
}
@Override
public Ability processAbility(Ability ab) {
return ab;
}
}

View File

@ -17,6 +17,7 @@ import java.util.function.Predicate;
import static java.util.ResourceBundle.Control.FORMAT_PROPERTIES;
import static java.util.ResourceBundle.Control.getNoFallbackControl;
import static java.util.ResourceBundle.getBundle;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.telegram.abilitybots.api.objects.Flag.*;
@ -24,6 +25,8 @@ import static org.telegram.abilitybots.api.objects.Flag.*;
* Helper and utility methods
*/
public final class AbilityUtils {
public static User EMPTY_USER = new User(0, "", false);
private AbilityUtils() {
}
@ -34,7 +37,7 @@ public final class AbilityUtils {
*/
public static String stripTag(String username) {
String lowerCase = username.toLowerCase();
return lowerCase.startsWith("@") ? lowerCase.substring(1, lowerCase.length()) : lowerCase;
return lowerCase.startsWith("@") ? lowerCase.substring(1) : lowerCase;
}
/**
@ -55,6 +58,10 @@ public final class AbilityUtils {
* @throws IllegalStateException if the user could not be found
*/
public static User getUser(Update update) {
return defaultIfNull(getUserElseThrow(update), EMPTY_USER);
}
private static User getUserElseThrow(Update update) {
if (MESSAGE.test(update)) {
return update.getMessage().getFrom();
} else if (CALLBACK_QUERY.test(update)) {
@ -69,6 +76,14 @@ public final class AbilityUtils {
return update.getEditedMessage().getFrom();
} else if (CHOSEN_INLINE_QUERY.test(update)) {
return update.getChosenInlineQuery().getFrom();
} else if (SHIPPING_QUERY.test(update)) {
return update.getShippingQuery().getFrom();
} else if (PRECHECKOUT_QUERY.test(update)) {
return update.getPreCheckoutQuery().getFrom();
} else if (POLL_ANSWER.test(update)) {
return update.getPollAnswer().getUser();
} else if (POLL.test(update)) {
return EMPTY_USER;
} else {
throw new IllegalStateException("Could not retrieve originating user from update");
}
@ -140,6 +155,14 @@ public final class AbilityUtils {
return update.getEditedMessage().getChatId();
} else if (CHOSEN_INLINE_QUERY.test(update)) {
return (long) update.getChosenInlineQuery().getFrom().getId();
} else if (SHIPPING_QUERY.test(update)) {
return (long) update.getShippingQuery().getFrom().getId();
} else if (PRECHECKOUT_QUERY.test(update)) {
return (long) update.getPreCheckoutQuery().getFrom().getId();
} else if (POLL_ANSWER.test(update)) {
return (long) update.getPollAnswer().getUser().getId();
} else if (POLL.test(update)) {
return (long) EMPTY_USER.getId();
} else {
throw new IllegalStateException("Could not retrieve originating chat ID from update");
}
@ -160,10 +183,8 @@ public final class AbilityUtils {
return update.getEditedChannelPost().isUserMessage();
} else if (EDITED_MESSAGE.test(update)) {
return update.getEditedMessage().isUserMessage();
} else if (CHOSEN_INLINE_QUERY.test(update) || INLINE_QUERY.test(update)) {
return true;
} else {
throw new IllegalStateException("Could not retrieve update context origin (user/group)");
return true;
}
}
@ -183,16 +204,16 @@ public final class AbilityUtils {
return update -> update.getMessage().getReplyToMessage().getText().equals(msg);
}
public static String getLocalizedMessage(String messageCode, Locale locale, Object...arguments) {
public static String getLocalizedMessage(String messageCode, Locale locale, Object... arguments) {
ResourceBundle bundle;
if (locale == null) {
bundle = getBundle("messages", Locale.ROOT);
} else {
try {
bundle = getBundle(
"messages",
locale,
getNoFallbackControl(FORMAT_PROPERTIES));
"messages",
locale,
getNoFallbackControl(FORMAT_PROPERTIES));
} catch (MissingResourceException e) {
bundle = getBundle("messages", Locale.ROOT);
}
@ -201,7 +222,7 @@ public final class AbilityUtils {
return MessageFormat.format(message, arguments);
}
public static String getLocalizedMessage(String messageCode, String languageCode, Object...arguments){
public static String getLocalizedMessage(String messageCode, String languageCode, Object... arguments) {
Locale locale = Strings.isNullOrEmpty(languageCode) ? null : Locale.forLanguageTag(languageCode);
return getLocalizedMessage(messageCode, locale, arguments);
}
@ -231,8 +252,8 @@ public final class AbilityUtils {
* The full name is identified as the concatenation of the first and last name, separated by a space.
* This method can return an empty name if both first and last name are empty.
*
* @param user User to use
* @return the full name of the user
* @param user
*/
public static String fullName(User user) {
StringJoiner name = new StringJoiner(" ");
@ -244,4 +265,33 @@ public final class AbilityUtils {
return name.toString();
}
}
public static String escape(String username) {
return username.replace("_", "\\_");
}
/**
* Checks if the passed string is a valid bot command according to the requirements of Telegram Bot API:
* "A command must always start with the '/' symbol and may not be longer than 32 characters.
* Commands can use latin letters, numbers and underscores."
* (https://core.telegram.org/bots#commands)
*
* @param command String representation of a command to be checked for validity
* @return whether the command is valid
*/
public static boolean isValidCommand(String command){
if (command == null || command.length() > 32) return false;
return command.matches("/[A-Za-z_0-9]+");
}
/**
* Checks if the passed String is a valid command name. Command name is text of a command without leading '/'
*
* @param commandName the command name to be checked for validity
* @return whether the command name is valid
*/
public static boolean isValidCommandName(String commandName){
if (commandName == null || commandName.length() > 31) return false;
return commandName.matches("[A-Za-z_0-9]+");
}
}

View File

@ -15,15 +15,16 @@ import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static org.telegram.abilitybots.api.bot.AbilityBotTest.mockContext;
import static org.telegram.abilitybots.api.bot.TestUtils.mockContext;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
class AbilityBotI18nTest {
private static final User NO_LANGUAGE_USER = new User(1, "first", false, "last", "username", null);
private static final User ITALIAN_USER = new User(2, "first", false, "last", "username", "it-IT");
private static final User NO_LANGUAGE_USER = new User(1, "first", false, "last", "username", null, false, false, false);
private static final User ITALIAN_USER = new User(2, "first", false, "last", "username", "it-IT", false, false, false);
private DBContext db;
private NoPublicCommandsBot bot;
private DefaultAbilities defaultAbs;
private MessageSender sender;
private SilentSender silent;
@ -32,6 +33,8 @@ class AbilityBotI18nTest {
void setUp() {
db = offlineInstance("db");
bot = new NoPublicCommandsBot(EMPTY, EMPTY, db);
bot.onRegister();
defaultAbs = new DefaultAbilities(bot);
sender = mock(MessageSender.class);
silent = mock(SilentSender.class);
@ -50,7 +53,7 @@ class AbilityBotI18nTest {
void missingPublicCommandsLocalizedInEnglishByDefault() {
MessageContext context = mockContext(NO_LANGUAGE_USER);
bot.reportCommands().action().accept(context);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1))
.send("No available commands found.", NO_LANGUAGE_USER.getId());
@ -60,7 +63,7 @@ class AbilityBotI18nTest {
void missingPublicCommandsLocalizedInItalian() {
MessageContext context = mockContext(ITALIAN_USER);
bot.reportCommands().action().accept(context);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1))
.send("Non sono presenti comandi disponibile.", ITALIAN_USER.getId());

View File

@ -8,19 +8,16 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.sender.MessageSender;
import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.abilitybots.api.util.AbilityUtils;
import org.telegram.abilitybots.api.util.Pair;
import org.telegram.abilitybots.api.util.Trio;
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators;
import org.telegram.telegrambots.meta.api.objects.ChatMember;
import org.telegram.telegrambots.meta.api.objects.Document;
import org.telegram.telegrambots.meta.api.objects.File;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.*;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.io.BufferedWriter;
@ -30,6 +27,7 @@ import java.util.Arrays;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newHashSet;
@ -39,15 +37,12 @@ import static java.util.Optional.empty;
import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.apache.commons.lang3.ArrayUtils.addAll;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static org.telegram.abilitybots.api.bot.DefaultBot.getDefaultBuilder;
import static org.telegram.abilitybots.api.bot.TestUtils.CREATOR;
import static org.telegram.abilitybots.api.bot.TestUtils.*;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT;
import static org.telegram.abilitybots.api.objects.Flag.MESSAGE;
@ -65,10 +60,9 @@ public class AbilityBotTest {
private static final long GROUP_ID = 10L;
private static final String TEST = "test";
private static final String[] TEXT = {TEST};
public static final User USER = new User(1, "first", false, "last", "username", null);
public static final User CREATOR = new User(1337, "creatorFirst", false, "creatorLast", "creatorUsername", null);
private DefaultBot bot;
private DefaultAbilities defaultAbs;
private DBContext db;
private MessageSender sender;
private SilentSender silent;
@ -77,6 +71,8 @@ public class AbilityBotTest {
void setUp() {
db = offlineInstance("db");
bot = new DefaultBot(EMPTY, EMPTY, db);
bot.onRegister();
defaultAbs = new DefaultAbilities(bot);
sender = mock(MessageSender.class);
silent = mock(SilentSender.class);
@ -93,7 +89,7 @@ public class AbilityBotTest {
@Test
void sendsPrivacyViolation() {
Update update = mockFullUpdate(USER, "/admin");
Update update = mockFullUpdate(bot, USER, "/admin");
bot.onUpdateReceived(update);
@ -102,7 +98,7 @@ public class AbilityBotTest {
@Test
void sendsLocalityViolation() {
Update update = mockFullUpdate(USER, "/group");
Update update = mockFullUpdate(bot, USER, "/group");
bot.onUpdateReceived(update);
@ -112,7 +108,7 @@ public class AbilityBotTest {
@Test
void sendsInputArgsViolation() {
Update update = mockFullUpdate(USER, "/count 1 2 3");
Update update = mockFullUpdate(bot, USER, "/count 1 2 3");
bot.onUpdateReceived(update);
@ -121,23 +117,90 @@ public class AbilityBotTest {
@Test
void canProcessRepliesIfSatisfyRequirements() {
Update update = mockFullUpdate(USER, "must reply");
Update update = mockFullUpdate(bot, USER, "must reply");
// False means the update was not pushed down the stream since it has been consumed by the reply
assertFalse(bot.filterReply(update));
verify(silent, times(1)).send("reply", USER.getId());
}
@Test
void canProcessUpdatesWithoutUserInfo() {
Update update = mock(Update.class);
// At the moment, only poll updates carry no user information
when(update.hasPoll()).thenReturn(true);
bot.onUpdateReceived(update);
}
@Test
void getUserHasAllMethodsDefined() {
Arrays.stream(Update.class.getMethods())
// filter to all these methods of hasXXX (hasPoll, hasMessage, etc...)
.filter(method -> method.getName().startsWith("has"))
// Gotta filter out hashCode
.filter(method -> method.getReturnType().getName().equals("boolean"))
.forEach(method -> {
Update update = mock(Update.class);
try {
// Mock the method and make sure it returns true so that it gets processed by the following method
when(method.invoke(update)).thenReturn(true);
// Call the getUser function, throws an IllegalStateException if there's an update that can't be processed
AbilityUtils.getUser(update);
} catch (IllegalStateException e) {
throw new RuntimeException(
format("Found an update variation that is not handled by the getUser util method [%s]", method.getName()), e);
} catch (NullPointerException | ReflectiveOperationException e) {
// This is fine, the mock isn't complete and we're only
// looking for IllegalStateExceptions thrown by the method
}
});
}
@Test
void getChatIdCanHandleAllKindsOfUpdates() {
handlesAllUpdates(AbilityUtils::getUser);
}
@Test
void getUserCanHandleAllKindsOfUpdates() {
handlesAllUpdates(AbilityUtils::getChatId);
}
@Test
void canBackupDB() throws TelegramApiException {
MessageContext context = defaultContext();
bot.backupDB().action().accept(context);
defaultAbs.backupDB().action().accept(context);
deleteQuietly(new java.io.File("backup.json"));
verify(sender, times(1)).sendDocument(any());
}
@Test
void canReportStatistics() {
MessageContext context = defaultContext();
defaultAbs.reportStats().action().accept(context);
verify(silent, times(1)).send("count: 0\nmustreply: 0", GROUP_ID);
}
@Test
void canReportUpdatedStatistics() {
Update upd1 = mockFullUpdate(bot, CREATOR, "/count 1 2 3 4");
bot.onUpdateReceived(upd1);
Update upd2 = mockFullUpdate(bot, CREATOR, "must reply");
bot.onUpdateReceived(upd2);
Mockito.reset(silent);
Update statUpd = mockFullUpdate(bot, CREATOR, "/stats");
bot.onUpdateReceived(statUpd);
verify(silent, times(1)).send("count: 1\nmustreply: 1", CREATOR.getId());
}
@Test
void canRecoverDB() throws TelegramApiException, IOException {
Update update = mockBackupUpdate();
@ -147,7 +210,7 @@ public class AbilityBotTest {
// Support for null parameter matching since due to mocking API changes
when(sender.downloadFile(ArgumentMatchers.<File>isNull())).thenReturn(backupFile);
bot.recoverDB().replies().get(0).actOn(update);
defaultAbs.recoverDB().replies().get(0).actOn(update);
verify(silent, times(1)).send(RECOVER_SUCCESS, GROUP_ID);
assertEquals(db.getSet(TEST), newHashSet(TEST), "Bot recovered but the DB is still not in sync");
@ -169,7 +232,7 @@ public class AbilityBotTest {
MessageContext context = defaultContext();
bot.demoteAdmin().action().accept(context);
defaultAbs.demoteAdmin().action().accept(context);
Set<Integer> actual = bot.admins();
Set<Integer> expected = emptySet();
@ -182,7 +245,7 @@ public class AbilityBotTest {
MessageContext context = defaultContext();
bot.promoteAdmin().action().accept(context);
defaultAbs.promoteAdmin().action().accept(context);
Set<Integer> actual = bot.admins();
Set<Integer> expected = newHashSet(USER.getId());
@ -194,7 +257,7 @@ public class AbilityBotTest {
addUsers(USER);
MessageContext context = defaultContext();
bot.banUser().action().accept(context);
defaultAbs.banUser().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet(USER.getId());
@ -208,7 +271,7 @@ public class AbilityBotTest {
MessageContext context = defaultContext();
bot.unbanUser().action().accept(context);
defaultAbs.unbanUser().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet();
@ -225,7 +288,7 @@ public class AbilityBotTest {
addUsers(USER, CREATOR);
MessageContext context = mockContext(USER, GROUP_ID, CREATOR.getUserName());
bot.banUser().action().accept(context);
defaultAbs.banUser().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet(USER.getId());
@ -243,7 +306,7 @@ public class AbilityBotTest {
void creatorCanClaimBot() {
MessageContext context = mockContext(CREATOR, GROUP_ID);
bot.claimCreator().action().accept(context);
defaultAbs.claimCreator().action().accept(context);
Set<Integer> actual = bot.admins();
Set<Integer> expected = newHashSet(CREATOR.getId());
@ -288,7 +351,7 @@ public class AbilityBotTest {
String newFirstName = USER.getFirstName() + "-test";
String newLastName = USER.getLastName() + "-test";
int sameId = USER.getId();
User changedUser = new User(sameId, newFirstName, false, newLastName, newUsername, null);
User changedUser = new User(sameId, newFirstName, false, newLastName, newUsername, "en", false, false, false);
mockAlternateUser(update, message, changedUser);
@ -312,7 +375,7 @@ public class AbilityBotTest {
@Test
void canCheckInput() {
Update update = mockFullUpdate(USER, "/something");
Update update = mockFullUpdate(bot, USER, "/something");
Ability abilityWithOneInput = getDefaultBuilder()
.build();
Ability abilityWithZeroInput = getDefaultBuilder()
@ -441,7 +504,7 @@ public class AbilityBotTest {
mockUser(update, message, USER);
Pair<MessageContext, Ability> actualPair = bot.getContext(trio);
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, USER, GROUP_ID, TEXT), ability);
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, USER, GROUP_ID, bot, TEXT), ability);
assertEquals(expectedPair, actualPair, "Unexpected result when fetching for context");
}
@ -452,6 +515,7 @@ public class AbilityBotTest {
assertTrue(bot.checkGlobalFlags(update), "Unexpected result when checking for the default global flags");
}
@SuppressWarnings({"NumericOverflow", "divzero"})
@Test
void canConsumeUpdate() {
Ability ability = getDefaultBuilder()
@ -544,30 +608,11 @@ public class AbilityBotTest {
void canReportCommands() {
MessageContext context = mockContext(USER, GROUP_ID);
bot.reportCommands().action().accept(context);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1)).send("default - dis iz default command", GROUP_ID);
}
@NotNull
static MessageContext mockContext(User user) {
return mockContext(user, user.getId());
}
@NotNull
static MessageContext mockContext(User user, long groupId, String... args) {
Update update = mock(Update.class);
Message message = mock(Message.class);
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(message.getFrom()).thenReturn(user);
when(message.hasText()).thenReturn(true);
return newContext(update, user, groupId, args);
}
@Test
void canPrintCommandsBasedOnPrivacy() {
Update update = mock(Update.class);
@ -576,11 +621,11 @@ public class AbilityBotTest {
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(message.hasText()).thenReturn(true);
MessageContext creatorCtx = newContext(update, CREATOR, GROUP_ID);
MessageContext creatorCtx = newContext(update, CREATOR, GROUP_ID, bot);
bot.commands().action().accept(creatorCtx);
defaultAbs.commands().action().accept(creatorCtx);
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test\nADMIN\n/admin\n/ban\n/demote\n/promote\n/unban\nCREATOR\n/backup\n/claim\n/recover\n/report";
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test\nADMIN\n/admin\n/ban\n/demote\n/promote\n/stats\n/unban\nCREATOR\n/backup\n/claim\n/recover\n/report";
verify(silent, times(1)).send(expected, GROUP_ID);
}
@ -593,33 +638,50 @@ public class AbilityBotTest {
when(update.getMessage()).thenReturn(message);
when(message.hasText()).thenReturn(true);
MessageContext userCtx = newContext(update, USER, GROUP_ID);
MessageContext userCtx = newContext(update, USER, GROUP_ID, bot);
bot.commands().action().accept(userCtx);
defaultAbs.commands().action().accept(userCtx);
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test";
verify(silent, times(1)).send(expected, GROUP_ID);
}
@NotNull
private Update mockFullUpdate(User user, String args) {
bot.users().put(USER.getId(), USER);
bot.users().put(CREATOR.getId(), CREATOR);
bot.userIds().put(CREATOR.getUserName(), CREATOR.getId());
bot.userIds().put(USER.getUserName(), USER.getId());
bot.admins().add(CREATOR.getId());
@Test
void canProcessChannelPosts() {
Update update = mock(Update.class);
when(update.hasMessage()).thenReturn(true);
Message message = mock(Message.class);
when(message.getFrom()).thenReturn(user);
when(message.getText()).thenReturn(args);
when(message.hasText()).thenReturn(true);
when(message.isUserMessage()).thenReturn(true);
when(message.getChatId()).thenReturn((long) user.getId());
when(update.getMessage()).thenReturn(message);
return update;
when(message.getChatId()).thenReturn(1L);
when(update.getChannelPost()).thenReturn(message);
when(update.hasChannelPost()).thenReturn(true);
bot.onUpdateReceived(update);
String expected = "test channel post";
verify(silent, times(1)).send(expected, 1);
}
private void handlesAllUpdates(Consumer<Update> utilMethod) {
Arrays.stream(Update.class.getMethods())
// filter to all these methods of hasXXX (hasPoll, hasMessage, etc...)
.filter(method -> method.getName().startsWith("has"))
// Gotta filter out hashCode
.filter(method -> method.getReturnType().getName().equals("boolean"))
.forEach(method -> {
Update update = mock(Update.class);
try {
// Mock the method and make sure it returns true so that it gets processed by the following method
when(method.invoke(update)).thenReturn(true);
// Call the function, throws an IllegalStateException if there's an update that can't be processed
utilMethod.accept(update);
} catch (IllegalStateException e) {
throw new RuntimeException(
format("Found an update variation that is not handled by the getChatId util method [%s]", method.getName()), e);
} catch (NullPointerException | ReflectiveOperationException e) {
// This is fine, the mock isn't complete and we're only
// looking for IllegalStateExceptions thrown by the method
}
});
}
private void mockUser(Update update, Message message, User user) {
@ -640,6 +702,7 @@ public class AbilityBotTest {
Message botMessage = mock(Message.class);
Document document = mock(Document.class);
when(document.getFileId()).thenReturn("FAKEFILEID");
when(message.getFrom()).thenReturn(CREATOR);
when(update.getMessage()).thenReturn(message);
when(message.getDocument()).thenReturn(document);

View File

@ -0,0 +1,103 @@
package org.telegram.abilitybots.api.bot;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import java.io.IOException;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static org.telegram.abilitybots.api.bot.TestUtils.mockFullUpdate;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
import static org.telegram.abilitybots.api.objects.Ability.builder;
import static org.telegram.abilitybots.api.objects.Locality.ALL;
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
public class ContinuousTextTest {
private static final User USER = new User(1, "first", false);
private DBContext db;
private SilentSender silent;
private ContinuousTextBot bot;
@BeforeEach
void setUp() {
db = offlineInstance("db");
bot = new ContinuousTextBot(EMPTY, EMPTY, db);
bot.onRegister();
silent = mock(SilentSender.class);
bot.silent = silent;
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
void processesContinuousText() {
Update update = mockFullUpdate(bot, USER, "/do2");
bot.onUpdateReceived(update);
verify(silent, times(1))
.send("2", USER.getId());
}
@Test
void matchesLongestAbilityName() {
Update update = mockFullUpdate(bot, USER, "/do1");
bot.onUpdateReceived(update);
verify(silent, times(1))
.send("longer ability name", USER.getId());
}
public static class ContinuousTextBot extends AbilityBot {
public ContinuousTextBot(String token, String username, DBContext db) {
super(token, username, db);
}
@Override
public int creatorId() {
return 1337;
}
@Override
protected boolean allowContinuousText() {
return true;
}
public Ability continuousTextAbility() {
return builder()
.name("do")
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.action(ctx -> silent.send(ctx.firstArg(), ctx.chatId()))
.build();
}
public Ability continuousTextSimilarAbility() {
return builder()
.name("do1")
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.action(ctx -> silent.send("longer ability name", ctx.chatId()))
.build();
}
}
}

View File

@ -3,6 +3,9 @@ package org.telegram.abilitybots.api.bot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import org.telegram.abilitybots.api.objects.Ability.AbilityBuilder;
import org.telegram.abilitybots.api.objects.Flag;
import org.telegram.abilitybots.api.objects.Reply;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
import static org.telegram.abilitybots.api.objects.Ability.builder;
import static org.telegram.abilitybots.api.objects.Flag.CALLBACK_QUERY;
@ -17,6 +20,10 @@ public class DefaultBot extends AbilityBot {
super(token, username, db);
}
public DefaultBot(String token, String username, DBContext db, AbilityToggle toggle) {
super(token, username, db, toggle);
}
public static AbilityBuilder getDefaultBuilder() {
return builder()
.name("test")
@ -36,7 +43,7 @@ public class DefaultBot extends AbilityBot {
return getDefaultBuilder()
.name(DEFAULT)
.info("dis iz default command")
.reply(upd -> silent.send("reply", upd.getMessage().getChatId()), MESSAGE, update -> update.getMessage().getText().equals("must reply"))
.reply(Reply.of(upd -> silent.send("reply", upd.getMessage().getChatId()), MESSAGE, update -> update.getMessage().getText().equals("must reply")).enableStats("mustreply"))
.reply(upd -> silent.send("reply", upd.getCallbackQuery().getMessage().getChatId()), CALLBACK_QUERY)
.build();
}
@ -62,9 +69,16 @@ public class DefaultBot extends AbilityBot {
.privacy(PUBLIC)
.locality(USER)
.input(4)
.enableStats()
.build();
}
public Reply channelPostReply() {
return Reply.of(
upd -> silent.send("test channel post", upd.getChannelPost().getChatId()), Flag.CHANNEL_POST
);
}
public Ability testAbility() {
return getDefaultBuilder().build();
}

View File

@ -19,6 +19,7 @@ class ExtensionTest {
@BeforeEach
void setUp() {
bot = new ExtensionUsingBot();
bot.onRegister();
}
@AfterEach
@ -32,6 +33,7 @@ class ExtensionTest {
assertTrue(hasAbilityNamed("direct"), "Failed to find Ability in directly declared in root extension/bot");
assertTrue(hasAbilityNamed("returningSuperClass0abc"), "Failed to find Ability in directly declared in extension returned by method returning the AbilityExtension class");
assertTrue(hasAbilityNamed("returningSubClass0abc"), "Failed to find Ability in directly declared in extension returned by method returning the AbilityExtension subclass");
assertTrue(hasAbilityNamed("addedInConstructor0abc"), "Failed to find Ability in directly declared in extension added in the constructor");
}
private boolean hasAbilityNamed(String name) {
@ -41,6 +43,7 @@ class ExtensionTest {
public static class ExtensionUsingBot extends AbilityBot {
ExtensionUsingBot() {
super("", "", offlineInstance("testing"));
addExtension(new AbilityBotExtension("addedInConstructor"));
}
@Override

View File

@ -0,0 +1,227 @@
package org.telegram.abilitybots.api.bot;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.sender.MessageSender;
import org.telegram.abilitybots.api.sender.SilentSender;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.polls.Poll;
import java.io.IOException;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
import static org.telegram.abilitybots.api.bot.TestUtils.USER;
import static org.telegram.abilitybots.api.bot.TestUtils.mockFullUpdate;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
import static org.telegram.abilitybots.api.objects.ReplyFlow.ReplyFlowBuilder.STATES;
import static org.telegram.abilitybots.api.util.AbilityUtils.getChatId;
public class ReplyFlowTest {
private static final int INITIAL_STATE = 1;
private static final int INTERIM_STATE = 2;
private DBContext db;
private ReplyFlowBot bot;
private MessageSender sender;
private SilentSender silent;
@BeforeEach
void setUp() {
db = offlineInstance("db");
bot = new ReplyFlowBot(EMPTY, EMPTY, db);
bot.onRegister();
sender = mock(MessageSender.class);
silent = mock(SilentSender.class);
bot.sender = sender;
bot.silent = silent;
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
void doesNotReplyIfFirstReplyFlowDoesNotMatch() {
Update update = mockFullUpdate(bot, USER, "this is not supported");
long chatId = getChatId(update);
assertTrue(bot.filterReply(update));
verify(silent, never()).send("Command me to go left or right!", chatId);
}
@Test
void doesNotReplyIfLaterRepliesAreAttemptedButUserNotInRightState() {
Update update = mockFullUpdate(bot, USER, "left");
long chatId = getChatId(update);
db.<Long, Integer>getMap(STATES).put(chatId, INTERIM_STATE);
assertTrue(bot.filterReply(update));
verify(silent, never()).send("Sir, I have gone left.", chatId);
}
@Test
void repliesIfFirstReplyFlowMatches() {
Update update = mockFullUpdate(bot, USER, "wake up");
long chatId = getChatId(update);
assertFalse(bot.filterReply(update));
verify(silent, only()).send("Command me to go left or right!", chatId);
assertEquals(INITIAL_STATE, db.<Long, Integer>getMap(STATES).get(chatId), "User is not in the proper initial state");
}
@Test
void stateIsNotResetOnFaultyReply() {
Update update = mockFullUpdate(bot, USER, "leffffft");
long chatId = getChatId(update);
db.<Long, Integer>getMap(STATES).put(chatId, INITIAL_STATE);
assertTrue(bot.filterReply(update));
verify(silent, never()).send("I don't know how to go left.", chatId);
assertEquals(INITIAL_STATE, db.<Long, Integer>getMap(STATES).get(chatId), "User is no longer in the initial state after faulty reply");
}
@Test
void terminalRepliesResetState() {
Update update = mockFullUpdate(bot, USER, "go left or else");
long chatId = getChatId(update);
db.<Long, Integer>getMap(STATES).put(chatId, INTERIM_STATE);
assertFalse(bot.filterReply(update));
verify(silent, only()).send("Sir, I have gone left.", chatId);
assertFalse(db.<Long, Integer>getMap(STATES).containsKey(chatId), "User still has state after terminal reply");
}
@Test
void repliesHandlePollResponse() {
Update update = mock(Update.class);
when(update.hasPoll()).thenReturn(true);
when(update.hasMessage()).thenReturn(false);
Poll poll = mock(Poll.class);
when(poll.getId()).thenReturn("1");
when(update.getPoll()).thenReturn(poll);
// This should not be processed as a reply, so we wouldn't filter out (true)
assertTrue(bot.filterReply(update));
}
@Test
void replyFlowsAreWorkingWhenDefinedInAbilities() {
Update update1 = mockFullUpdate(bot, USER, "one");
Update update2 = mockFullUpdate(bot, USER, "two");
long chatId = getChatId(update1);
// Trigger and verify first reply stage
assertFalse(bot.filterReply(update1));
verify(silent, only()).send("First reply", chatId);
assertTrue(db.<Long, Integer>getMap(STATES).containsKey(chatId), "User is not in initial state");
// Resetting the mock now helps with verification later
reset(silent);
// Trigger and verify second reply stage
assertFalse(bot.filterReply(update2));
verify(silent, only()).send("Second reply", chatId);
assertFalse(db.<Long, Integer>getMap(STATES).containsKey(chatId), "User is still in a state");
}
@Test
void replyFlowsPertainNames() {
Set<String> replyNames = bot.replies().stream().map(Reply::name).collect(Collectors.toSet());
assertTrue(replyNames.containsAll(newHashSet("FIRST", "SECOND")));
}
public static class ReplyFlowBot extends AbilityBot {
private ReplyFlowBot(String botToken, String botUsername, DBContext db) {
super(botToken, botUsername, db);
}
@Override
public int creatorId() {
return 0;
}
public ReplyFlow directionFlow() {
Reply saidLeft = Reply.of(upd -> silent.send("Sir, I have gone left.", getChatId(upd)),
hasMessageWith("go left or else"));
ReplyFlow leftflow = ReplyFlow.builder(db, 2)
.action(upd -> silent.send("I don't know how to go left.", getChatId(upd)))
.onlyIf(hasMessageWith("left"))
.next(saidLeft).build();
Reply saidRight = Reply.of(upd -> silent.send("Sir, I have gone right.", getChatId(upd)),
hasMessageWith("right"));
return ReplyFlow.builder(db, 1)
.action(upd -> silent.send("Command me to go left or right!", getChatId(upd)))
.onlyIf(hasMessageWith("wake up"))
.next(leftflow)
.next(saidRight)
.build();
}
public Reply errantReply() {
return Reply.of(
upd -> {
throw new RuntimeException("Throwing an exception inside the update consumer");
},
upd -> {
throw new RuntimeException("Throwing an exception inside the reply conditions (flags)");
});
}
public Ability replyFlowsWithAbility() {
Reply replyWithVk = ReplyFlow.builder(db, 2)
.enableStats("SECOND")
.action(upd -> {
silent.send("Second reply", upd.getMessage().getChatId());
})
.onlyIf(hasMessageWith("two"))
.build();
Reply replyWithNickname = ReplyFlow.builder(db, 1)
.enableStats("FIRST")
.action(upd -> {
silent.send("First reply", upd.getMessage().getChatId());
})
.onlyIf(hasMessageWith("one"))
.next(replyWithVk)
.build();
return Ability.builder()
.name("trigger")
.privacy(Privacy.PUBLIC)
.locality(Locality.ALL)
.action(ctx -> silent.send("I'm in an ability", ctx.chatId()))
.reply(replyWithNickname)
.build();
}
@NotNull
private Predicate<Update> hasMessageWith(String msg) {
return upd -> Flag.MESSAGE.test(upd) && upd.getMessage().getText().equalsIgnoreCase(msg);
}
}
}

View File

@ -0,0 +1,61 @@
package org.telegram.abilitybots.api.bot;
import org.jetbrains.annotations.NotNull;
import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.telegram.abilitybots.api.objects.MessageContext.newContext;
public final class TestUtils {
public static final User USER = new User(1, "first", false, "last", "username", null, false, false, false);
public static final User CREATOR = new User(1337, "creatorFirst", false, "creatorLast", "creatorUsername", null, false, false, false);
private TestUtils() {
}
@NotNull
static Update mockFullUpdate(AbilityBot bot, User user, String args) {
bot.users().put(USER.getId(), USER);
bot.users().put(CREATOR.getId(), CREATOR);
bot.userIds().put(CREATOR.getUserName(), CREATOR.getId());
bot.userIds().put(USER.getUserName(), USER.getId());
bot.admins().add(CREATOR.getId());
Update update = mock(Update.class);
when(update.hasMessage()).thenReturn(true);
Message message = mock(Message.class);
when(message.getFrom()).thenReturn(user);
when(message.getText()).thenReturn(args);
when(message.hasText()).thenReturn(true);
when(message.isUserMessage()).thenReturn(true);
when(message.getChatId()).thenReturn((long) user.getId());
when(update.getMessage()).thenReturn(message);
return update;
}
@NotNull
static MessageContext mockContext(User user, long groupId, String... args) {
Update update = mock(Update.class);
Message message = mock(Message.class);
BaseAbilityBot bot = mock(BaseAbilityBot.class);
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(message.getFrom()).thenReturn(user);
when(message.hasText()).thenReturn(true);
return newContext(update, user, groupId, bot, args);
}
@NotNull
static MessageContext mockContext(User user) {
return mockContext(user, user.getId());
}
}

View File

@ -12,12 +12,13 @@ import java.util.Set;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.collect.Sets.toImmutableEnumSet;
import static java.lang.String.format;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.telegram.abilitybots.api.bot.AbilityBotTest.CREATOR;
import static org.telegram.abilitybots.api.bot.AbilityBotTest.USER;
import static org.telegram.abilitybots.api.bot.TestUtils.CREATOR;
import static org.telegram.abilitybots.api.bot.TestUtils.USER;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
class MapDBContextTest {
@ -38,6 +39,24 @@ class MapDBContextTest {
db.close();
}
@Test
void canRecoverVar() {
Var<String> test = db.getVar(TEST);
String val = "abilitybot";
test.set(val);
Object backup = db.backup();
db.clear();
// db.clear does not clear atomic variables
// TODO: get clear to remove all non-collection variables in DB
test.set("somevalue");
boolean recovered = db.recover(backup);
String recoveredVal = db.<String>getVar(TEST).get();
assertTrue(recovered, "Could not recover JSON backup file");
assertEquals(val, recoveredVal, "Could not properly recover val from Var in DB");
}
@Test
void canRecoverDB() {
Map<Integer, User> users = db.getMap(USERS);

View File

@ -2,15 +2,19 @@ package org.telegram.abilitybots.api.sender;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.updateshandlers.SentCallback;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
class SilentSenderTest {
private SilentSender silent;
@ -40,4 +44,34 @@ class SilentSenderTest {
assertEquals(data, execute.get(), "Silent execution resulted in a different object");
}
@Test
void callsAsyncVariantOfExecute() throws TelegramApiException {
SendMessage methodObject = new SendMessage();
NoOpCallback callback = new NoOpCallback();
silent.executeAsync(methodObject, callback);
verify(sender, only()).executeAsync(methodObject, callback);
}
private class NoOpCallback implements SentCallback<Message> {
@Override
public void onResult(BotApiMethod<Message> method, Message response) {
}
@Override
public void onError(BotApiMethod<Message> method, TelegramApiRequestException apiException) {
}
@Override
public void onException(BotApiMethod<Message> method, Exception exception) {
}
}
;
}

View File

@ -0,0 +1,48 @@
package org.telegram.abilitybots.api.toggle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.abilitybots.api.bot.DefaultAbilities;
import org.telegram.abilitybots.api.bot.DefaultBot;
import org.telegram.abilitybots.api.db.DBContext;
import java.io.IOException;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.junit.jupiter.api.Assertions.*;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
public class BareboneToggleTest {
private DBContext db;
private AbilityToggle toggle;
private DefaultBot bareboneBot;
private DefaultAbilities defaultAbs;
@BeforeEach
void setUp() {
db = offlineInstance("db");
toggle = new BareboneToggle();
bareboneBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
bareboneBot.onRegister();
defaultAbs = new DefaultAbilities(bareboneBot);
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
public void turnsOffAllAbilities() {
assertFalse(bareboneBot.abilities().containsKey(DefaultAbilities.CLAIM));
}
@Test
public void throwsOnProcessingAbility() {
Assertions.assertThrows(RuntimeException.class, () -> toggle.processAbility(defaultAbs.claimCreator()));
}
}

View File

@ -0,0 +1,50 @@
package org.telegram.abilitybots.api.toggle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.abilitybots.api.bot.DefaultAbilities;
import org.telegram.abilitybots.api.bot.DefaultBot;
import org.telegram.abilitybots.api.db.DBContext;
import java.io.IOException;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.junit.jupiter.api.Assertions.*;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
class CustomToggleTest {
private DBContext db;
private AbilityToggle toggle;
private DefaultBot customBot;
@BeforeEach
void setUp() {
db = offlineInstance("db");
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
public void canTurnOffAbilities() {
toggle = new CustomToggle().turnOff(DefaultAbilities.CLAIM);
customBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
customBot.onRegister();
assertFalse(customBot.abilities().containsKey(DefaultAbilities.CLAIM));
}
@Test
public void canProcessAbilities() {
String targetName = DefaultAbilities.CLAIM + "1toggle";
toggle = new CustomToggle().toggle(DefaultAbilities.CLAIM, targetName);
customBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
customBot.onRegister();
assertTrue(customBot.abilities().containsKey(targetName));
}
}

View File

@ -0,0 +1,69 @@
package org.telegram.abilitybots.api.toggle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.telegram.abilitybots.api.bot.DefaultBot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import java.io.IOException;
import java.util.Set;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.junit.jupiter.api.Assertions.*;
import static org.telegram.abilitybots.api.bot.DefaultAbilities.*;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
class DefaultToggleTest {
private DBContext db;
private AbilityToggle toggle;
private DefaultBot defaultBot;
@BeforeEach
void setUp() {
db = offlineInstance("db");
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
public void claimsEveryAbilityIsOn() {
Ability random = DefaultBot.getDefaultBuilder()
.name("randomsomethingrandom").build();
toggle = new DefaultToggle();
defaultBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
defaultBot.onRegister();
assertFalse(toggle.isOff(random));
}
@Test
public void passedSameAbilityRefOnProcess() {
Ability random = DefaultBot.getDefaultBuilder()
.name("randomsomethingrandom").build();
toggle = new DefaultToggle();
defaultBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
defaultBot.onRegister();
assertSame(random, toggle.processAbility(random), "Toggle returned a different ability");
}
@Test
public void allAbilitiesAreRegistered() {
toggle = new DefaultToggle();
defaultBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
defaultBot.onRegister();
Set<String> defaultNames = newHashSet(
CLAIM, BAN, UNBAN,
PROMOTE, DEMOTE, RECOVER,
BACKUP, REPORT, COMMANDS);
assertTrue(defaultBot.abilities().keySet().containsAll(defaultNames), "Toggle returned a different ability");
}
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_PATTERN">%d{yyyy-MM-dd'T'HH:mm:ss.SSSZ} [%t] %-5level %logger{36} - %msg%n</Property>
<Property name="APP">App</Property>
</Properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT" follow="true">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
<ThresholdFilter level="OFF" onMatch="ACCEPT" onMismatch="DENY"/>
</Console>
</Appenders>
<Loggers>
<Root level="error">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>

View File

@ -15,7 +15,7 @@ Usage
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-chat-session-bot</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</parent>
<artifactId>telegrambots-chat-session-bot</artifactId>
@ -69,14 +69,14 @@
<properties>
<java.version>11</java.version>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<shiro.version>1.4.1</shiro.version>
<shiro.version>1.7.0</shiro.version>
</properties>
<dependencies>
@ -84,7 +84,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
@ -107,20 +107,6 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
@ -193,7 +179,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>
@ -254,4 +240,4 @@
</plugins>
</pluginManagement>
</build>
</project>
</project>

View File

@ -5,6 +5,7 @@ import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionContext;
import org.apache.shiro.session.mgt.eis.AbstractSessionDAO;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
@ -22,6 +23,10 @@ public abstract class TelegramLongPollingSessionBot extends TelegramLongPollingB
}
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter){
this(chatIdConverter, new DefaultBotOptions());
}
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter, DefaultBotOptions defaultBotOptions){
this.setSessionManager(new DefaultSessionManager());
this.setChatIdConverter(chatIdConverter);
AbstractSessionDAO sessionDAO = (AbstractSessionDAO) sessionManager.getSessionDAO();

View File

@ -16,12 +16,12 @@ Just import add the library to your project with one of these options:
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambotsextensions</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
```
2. Using Gradle:
```gradle
compile "org.telegram:telegrambotsextensions:4.4.0"
compile "org.telegram:telegrambotsextensions:5.0.1"
```

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</parent>
<artifactId>telegrambotsextensions</artifactId>
@ -63,7 +63,7 @@
<properties>
<java.version>11</java.version>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
@ -75,7 +75,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</dependency>
</dependencies>
@ -91,20 +91,6 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
@ -177,7 +163,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>
@ -238,4 +224,4 @@
</plugins>
</pluginManagement>
</build>
</project>
</project>

View File

@ -1,7 +1,6 @@
package org.telegram.telegrambots.extensions.bots.commandbot;
import org.telegram.telegrambots.meta.ApiContext;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.bots.AbsSender;
@ -22,16 +21,14 @@ import java.util.function.BiConsumer;
*/
public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingBot implements ICommandRegistry {
private final CommandRegistry commandRegistry;
private String botUsername;
/**
* Creates a TelegramLongPollingCommandBot using default options
* Use ICommandRegistry's methods on this bot to register commands
*
* @param botUsername Username of the bot
*/
public TelegramLongPollingCommandBot(String botUsername) {
this(ApiContext.getInstance(DefaultBotOptions.class), botUsername);
public TelegramLongPollingCommandBot() {
this(new DefaultBotOptions());
}
/**
@ -40,10 +37,9 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
* Use ICommandRegistry's methods on this bot to register commands
*
* @param options Bot options
* @param botUsername Username of the bot
*/
public TelegramLongPollingCommandBot(DefaultBotOptions options, String botUsername) {
this(options, true, botUsername);
public TelegramLongPollingCommandBot(DefaultBotOptions options) {
this(options, true);
}
/**
@ -53,12 +49,10 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
* @param options Bot options
* @param allowCommandsWithUsername true to allow commands with parameters (default),
* false otherwise
* @param botUsername bot username of this bot
*/
public TelegramLongPollingCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername, String botUsername) {
public TelegramLongPollingCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername) {
super(options);
this.botUsername = botUsername;
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, botUsername);
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, this::getBotUsername);
}
@Override
@ -143,9 +137,7 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
* @return Bot username
*/
@Override
public final String getBotUsername() {
return botUsername;
}
public abstract String getBotUsername();
/**
* Process all updates, that are not commands.

View File

@ -7,7 +7,10 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* This class manages all the commands for a bot. You can register and deregister commands on demand
@ -18,17 +21,17 @@ public final class CommandRegistry implements ICommandRegistry {
private final Map<String, IBotCommand> commandRegistryMap = new HashMap<>();
private final boolean allowCommandsWithUsername;
private final String botUsername;
private final Supplier<String> botUsernameSupplier;
private BiConsumer<AbsSender, Message> defaultConsumer;
/**
* Creates a Command registry
* @param allowCommandsWithUsername True to allow commands with username, false otherwise
* @param botUsername Bot username
* @param botUsernameSupplier Bot username supplier
*/
public CommandRegistry(boolean allowCommandsWithUsername, String botUsername) {
public CommandRegistry(boolean allowCommandsWithUsername, Supplier<String> botUsernameSupplier) {
this.allowCommandsWithUsername = allowCommandsWithUsername;
this.botUsername = botUsername;
this.botUsernameSupplier = botUsernameSupplier;
}
@Override
@ -85,7 +88,7 @@ public final class CommandRegistry implements ICommandRegistry {
/**
* Executes a command action if the command is registered.
*
* @note If the command is not registered and there is a default consumer,
* @apiNote If the command is not registered and there is a default consumer,
* that action will be performed
*
* @param absSender absSender
@ -119,10 +122,13 @@ public final class CommandRegistry implements ICommandRegistry {
* the command
* @param command Command to simplify
* @return Simplified command
* @throws java.lang.NullPointerException if {@code allowCommandsWithUsername} is {@code true}
* and {@code botUsernameSupplier} returns {@code null}
*/
private String removeUsernameFromCommandIfNeeded(String command) {
if (allowCommandsWithUsername) {
return command.replace("@" + botUsername, "").trim();
String botUsername = Objects.requireNonNull(botUsernameSupplier.get(), "Bot username must not be null");
return command.replaceAll("(?i)@" + Pattern.quote(botUsername), "").trim();
}
return command;
}

View File

@ -98,14 +98,14 @@ public class HelpCommand extends ManCommand {
IBotCommand command = registry.getRegisteredCommand(arguments[0]);
String reply = getManText(command);
try {
absSender.execute(new SendMessage(chat.getId(), reply).setParseMode("HTML"));
absSender.execute(SendMessage.builder().chatId(chat.getId().toString()).text(reply).parseMode("HTML").build());
} catch (TelegramApiException e) {
e.printStackTrace();
}
} else {
String reply = getHelpText(registry);
try {
absSender.execute(new SendMessage(chat.getId(), reply).setParseMode("HTML"));
absSender.execute(SendMessage.builder().chatId(chat.getId().toString()).text(reply).parseMode("HTML").build());
} catch (TelegramApiException e) {
e.printStackTrace();
}

View File

@ -11,9 +11,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
* Created by Daniil Nikanov aka JetCoder
*/
@SuppressWarnings("unused")
public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot
{
private static final long MANY_CHATS_SEND_INTERVAL = 33;
private static final long ONE_CHAT_SEND_INTERVAL = 1000;
private static final long CHAT_INACTIVE_INTERVAL = 1000 * 60 * 10;
private final Timer mSendTimer = new Timer(true);
@ -21,29 +21,85 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot
private final ArrayList<MessageQueue> mSendQueues = new ArrayList<>();
private final AtomicBoolean mSendRequested = new AtomicBoolean(false);
private final class MessageSenderTask extends TimerTask
{
@Override
public void run()
{
//mSendRequested used for optimisation to not traverse all mMessagesMap 30 times per second all the time
if (!mSendRequested.getAndSet(false))
return;
long currentTime = System.currentTimeMillis();
mSendQueues.clear();
boolean processNext = false;
//First step - find all chats in which already allowed to send message (passed more than 1000 ms from previuos send)
Iterator<Map.Entry<Long,MessageQueue>> it = mMessagesMap.entrySet().iterator();
while (it.hasNext())
{
MessageQueue queue = it.next().getValue();
int state = queue.getCurrentState(currentTime); //Actual check here
if (state == MessageQueue.GET_MESSAGE)
{
mSendQueues.add(queue);
processNext = true;
}
else if (state == MessageQueue.WAIT)
{
processNext = true;
}
else if (state == MessageQueue.DELETE)
{
it.remove();
}
}
//If any of chats are in state WAIT or GET_MESSAGE, request another iteration
if (processNext)
mSendRequested.set(true);
//Second step - find oldest waiting queue and peek it's message
MessageQueue sendQueue = null;
long oldestPutTime = Long.MAX_VALUE;
for (MessageQueue queue : mSendQueues) {
long putTime = queue.getPutTime();
if (putTime < oldestPutTime) {
oldestPutTime = putTime;
sendQueue = queue;
}
}
if (sendQueue == null) //Possible if on first step wasn't found any chats in state GET_MESSAGE
return;
//Invoke the send callback. ChatId is passed for possible additional processing
sendMessageCallback(sendQueue.getChatId(), sendQueue.getMessage(currentTime));
}
}
private static class MessageQueue
{
static final int EMPTY = 0; //Queue is empty
static final int WAIT = 1; //Queue has message(s) but not yet allowed to send
static final int DELETE = 2; //No one message of given queue was sent longer than CHAT_INACTIVE_INTERVAL, delete for optimisation
static final int GET_MESSAGE = 3; //Queue has message(s) and ready to send
public static final int EMPTY = 0; //Queue is empty
public static final int WAIT = 1; //Queue has message(s) but not yet allowed to send
public static final int DELETE = 2; //No one message of given queue was sent longer than CHAT_INACTIVE_INTERVAL, delete for optimisation
public static final int GET_MESSAGE = 3; //Queue has message(s) and ready to send
private final ConcurrentLinkedQueue<Object> mQueue = new ConcurrentLinkedQueue<>();
private final Long mChatId;
private long mLastSendTime; //Time of last peek from queue
private volatile long mLastPutTime; //Time of last put into queue
MessageQueue(Long chatId)
public MessageQueue(Long chatId)
{
mChatId = chatId;
}
synchronized void putMessage(Object msg)
public synchronized void putMessage(Object msg)
{
mQueue.add(msg);
mLastPutTime = System.currentTimeMillis();
}
synchronized int getCurrentState(long currentTime)
public synchronized int getCurrentState(long currentTime)
{
//currentTime is passed as parameter for optimisation to do not recall currentTimeMillis() many times
long interval = currentTime - mLastSendTime;
@ -58,23 +114,29 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot
return WAIT;
}
synchronized Object getMessage(long currentTime)
public synchronized Object getMessage(long currentTime)
{
mLastSendTime = currentTime;
return mQueue.poll();
}
long getPutTime()
public long getPutTime()
{
return mLastPutTime;
}
Long getChatId()
public Long getChatId()
{
return mChatId;
}
}
//Constructor
protected TimedSendLongPollingBot()
{
mSendTimer.schedule(new MessageSenderTask(), MANY_CHATS_SEND_INTERVAL, MANY_CHATS_SEND_INTERVAL);
}
//Something like destructor
public void finish()
{
@ -149,5 +211,5 @@ public abstract class TimedSendLongPollingBot extends TelegramLongPollingBot
}
}
*/
abstract void sendMessageCallback(Long chatId, Object messageRequest);
public abstract void sendMessageCallback(Long chatId, Object messageRequest);
}

View File

@ -0,0 +1,42 @@
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.bots.AbsSender;
class CommandRegistryTest {
@Test
void should_executes_commandWithBotUsername() {
CommandRegistry registry = new CommandRegistry(true, () -> "BotUsername");
IBotCommand command = Mockito.mock(IBotCommand.class);
Mockito.when(command.getCommandIdentifier()).thenReturn("command");
registry.register(command);
AbsSender absSender = Mockito.mock(AbsSender.class);
Message message = Mockito.mock(Message.class);
Mockito.when(message.hasText()).thenReturn(true);
Mockito.when(message.getText()).thenReturn("/command@BotUsername I'll be test!");
Assertions.assertTrue(registry.executeCommand(absSender, message), "Command must be executed");
Mockito.verify(message).hasText();
Mockito.verify(message).getText();
Mockito.verify(command).processMessage(
Mockito.same(absSender), Mockito.same(message), Mockito.eq(new String[]{"I'll", "be", "test!"}));
}
@Test
void should_throws_NPE_on_executes_commandWithNullableBotUsername() {
CommandRegistry registry = new CommandRegistry(true, () -> null);
IBotCommand command = Mockito.mock(IBotCommand.class);
Mockito.when(command.getCommandIdentifier()).thenReturn("command");
registry.register(command);
AbsSender absSender = Mockito.mock(AbsSender.class);
Message message = Mockito.mock(Message.class);
Mockito.when(message.hasText()).thenReturn(true);
Mockito.when(message.getText()).thenReturn("/command@BotUsername ignore");
NullPointerException exception = Assertions.assertThrows(
NullPointerException.class, () -> registry.executeCommand(absSender, message), "Bot username is null");
Assertions.assertEquals("Bot username must not be null", exception.getMessage(), "Invalid exception message");
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.4.0</version>
<version>5.0.1</version>
</parent>
<artifactId>telegrambots-meta</artifactId>
@ -63,35 +63,18 @@
<properties>
<java.version>11</java.version>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<guice.version>4.2.2</guice.version>
<jackson.version>2.9.9</jackson.version>
<jacksonanotation.version>2.9.9</jacksonanotation.version>
<json.version>20180813</json.version>
<guava.version>28.0-jre</guava.version>
</properties>
<dependencies>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice.version}</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
@ -102,18 +85,21 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jacksonanotation.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
@ -123,20 +109,6 @@
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.6</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
@ -209,7 +181,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.4</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>

View File

@ -1,67 +0,0 @@
package org.telegram.telegrambots.meta;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* @author Ruben Bermudez
* @version 1.0
*/
public class ApiContext {
private static final Logger log = LogManager.getLogger(ApiContext.class);
private static final Object lock = new Object();
private static Injector INJECTOR;
private static Map<Class, Class> bindings = new HashMap<>();
private static Map<Class, Class> singletonBindings = new HashMap<>();
public static <T> T getInstance(Class<T> type) {
return getInjector().getInstance(type);
}
public static <T, S extends T> void register(Class<T> type, Class<S> implementation) {
if (bindings.containsKey(type)) {
log.debug(MessageFormat.format("Class {0} already registered", type.getName()));
}
bindings.put(type, implementation);
}
public static <T, S extends T> void registerSingleton(Class<T> type, Class<S> implementation) {
if (singletonBindings.containsKey(type)) {
log.debug(MessageFormat.format("Class {0} already registered", type.getName()));
}
singletonBindings.put(type, implementation);
}
private static Injector getInjector() {
if (INJECTOR == null) {
synchronized (lock) {
if (INJECTOR == null) {
INJECTOR = Guice.createInjector(new ApiModule());
}
}
}
return INJECTOR;
}
@SuppressWarnings("unchecked")
private static class ApiModule extends AbstractModule {
@Override
protected void configure() {
for (Map.Entry<Class, Class> binding : bindings.entrySet()) {
bind(binding.getKey()).to(binding.getValue());
}
for (Map.Entry<Class, Class> binding : singletonBindings.entrySet()) {
bind(binding.getKey()).to(binding.getValue()).in(Singleton.class);
}
}
}
}

View File

@ -1,124 +1,68 @@
package org.telegram.telegrambots.meta;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.api.methods.updates.SetWebhook;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.generics.BotSession;
import org.telegram.telegrambots.meta.generics.LongPollingBot;
import org.telegram.telegrambots.meta.generics.Webhook;
import org.telegram.telegrambots.meta.generics.WebhookBot;
import java.text.MessageFormat;
import java.lang.reflect.InvocationTargetException;
/**
* @author Ruben Bermudez
* @version 1.0
* @brief Bots manager
* @date 14 of January of 2016
* Bots manager
*/
public class TelegramBotsApi {
private static final String webhookUrlFormat = "{0}callback/";
Class<? extends BotSession> botSessionClass;
private boolean useWebhook; ///< True to enable webhook usage
private Webhook webhook; ///< Webhook instance
private String externalUrl; ///< External url of the bots
private String pathToCertificate; ///< Path to public key certificate
/**
*
*/
public TelegramBotsApi() {
public TelegramBotsApi(Class<? extends BotSession> botSessionClass) throws TelegramApiException {
if (botSessionClass == null) {
throw new TelegramApiException("Parameter botSessionClass can not be null or empty");
}
this.botSessionClass = botSessionClass;
}
/**
* Creates an HTTP server to receive webhook request
* @param externalUrl External base url for the webhook
* @param internalUrl Internal base url for the webhook
* @param webhook Webhook class
*
* @implSpec This option requires externally handled HTTPS support (i.e. via proxy)
* @implSpec This option may require externally handled HTTPS support (i.e. via proxy)
*/
public TelegramBotsApi(String externalUrl, String internalUrl) throws TelegramApiRequestException {
if (externalUrl == null || externalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter externalUrl can not be null or empty");
public TelegramBotsApi(Class<? extends BotSession> botSessionClass, Webhook webhook) throws TelegramApiException {
if (botSessionClass == null) {
throw new TelegramApiException("Parameter botSessionClass can not be null or empty");
}
if (internalUrl == null || internalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter internalUrl can not be null or empty");
if (webhook == null) {
throw new TelegramApiException("Parameter webhook can not be null or empty");
}
this.botSessionClass = botSessionClass;
this.useWebhook = true;
this.externalUrl = fixExternalUrl(externalUrl);
webhook = ApiContext.getInstance(Webhook.class);
webhook.setInternalUrl(internalUrl);
webhook.startServer();
}
/**
* Creates an HTTPS server to receive webhook request
* @param keyStore KeyStore for the server
* @param keyStorePassword Key store password for the server
* @param externalUrl External base url for the webhook
* @param internalUrl Internal base url for the webhook
*/
public TelegramBotsApi(String keyStore, String keyStorePassword, String externalUrl, String internalUrl) throws TelegramApiRequestException {
if (externalUrl == null || externalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter externalUrl can not be null or empty");
}
if (internalUrl == null || internalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter internalUrl can not be null or empty");
}
if (keyStore == null || keyStore.isEmpty()) {
throw new TelegramApiRequestException("Parameter keyStore can not be null or empty");
}
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
throw new TelegramApiRequestException("Parameter keyStorePassword can not be null or empty");
}
this.useWebhook = true;
this.externalUrl = fixExternalUrl(externalUrl);
webhook = ApiContext.getInstance(Webhook.class);
webhook.setInternalUrl(internalUrl);
webhook.setKeyStore(keyStore, keyStorePassword);
webhook.startServer();
}
/**
* Creates an HTTPS server with self-signed certificate to receive webhook request
* @param keyStore KeyStore for the server
* @param keyStorePassword Key store password for the server
* @param externalUrl External base url for the webhook
* @param internalUrl Internal base url for the webhook
* @param pathToCertificate Full path until .pem public certificate keys
*/
public TelegramBotsApi(String keyStore, String keyStorePassword, String externalUrl, String internalUrl, String pathToCertificate) throws TelegramApiRequestException {
if (externalUrl == null || externalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter externalUrl can not be null or empty");
}
if (internalUrl == null || internalUrl.isEmpty()) {
throw new TelegramApiRequestException("Parameter internalUrl can not be null or empty");
}
if (keyStore == null || keyStore.isEmpty()) {
throw new TelegramApiRequestException("Parameter keyStore can not be null or empty");
}
if (keyStorePassword == null || keyStorePassword.isEmpty()) {
throw new TelegramApiRequestException("Parameter keyStorePassword can not be null or empty");
}
if (pathToCertificate == null || pathToCertificate.isEmpty()) {
throw new TelegramApiRequestException("Parameter pathToCertificate can not be null or empty");
}
this.useWebhook = true;
this.externalUrl = fixExternalUrl(externalUrl);
this.pathToCertificate = pathToCertificate;
webhook = ApiContext.getInstance(Webhook.class);
webhook.setInternalUrl(internalUrl);
webhook.setKeyStore(keyStore, keyStorePassword);
webhook.startServer();
this.webhook = webhook;
this.webhook.startServer();
}
/**
* Register a bot. The Bot Session is started immediately, and may be disconnected by calling close.
* @param bot the bot to register
*/
public BotSession registerBot(LongPollingBot bot) throws TelegramApiRequestException {
public BotSession registerBot(LongPollingBot bot) throws TelegramApiException {
bot.onRegister();
bot.clearWebhook();
BotSession session = ApiContext.getInstance(BotSession.class);
BotSession session;
try {
session = botSessionClass.getConstructor().newInstance();
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw new TelegramApiException(e);
}
session.setToken(bot.getBotToken());
session.setOptions(bot.getOptions());
session.setCallback(bot);
@ -129,18 +73,19 @@ public class TelegramBotsApi {
/**
* Register a bot in the api that will receive updates using webhook method
* @param bot Bot to register
* @param setWebhook Set webhook request to initialize the bot
*/
public void registerBot(WebhookBot bot) throws TelegramApiRequestException {
public void registerBot(WebhookBot bot, SetWebhook setWebhook) throws TelegramApiException {
if (setWebhook == null) {
throw new TelegramApiException("Parameter setWebhook can not be null or empty");
}
if (useWebhook) {
if (webhook == null) {
throw new TelegramApiException("This instance doesn't support Webhook bot, use correct constructor");
}
bot.onRegister();
webhook.registerWebhook(bot);
bot.setWebhook(externalUrl + bot.getBotPath(), pathToCertificate);
bot.setWebhook(setWebhook);
}
}
private static String fixExternalUrl(String externalUrl) {
if (externalUrl != null && !externalUrl.endsWith("/")) {
externalUrl = externalUrl + "/";
}
return MessageFormat.format(webhookUrlFormat, externalUrl);
}
}

View File

@ -1,17 +0,0 @@
package org.telegram.telegrambots.meta.api.interfaces;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* @author Ruben Bermudez
* @version 1.0
* An object used in the Bots API to answer updates
*
* @deprecated Please, use BotApiObject directly
*/
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
@Deprecated
public interface InputBotApiObject extends BotApiObject {
}

View File

@ -2,7 +2,15 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
@ -16,10 +24,18 @@ import java.io.IOException;
* will be displayed to the user as a notification at the top of the chat screen or as an alert. On
* success, True is returned.
*
* @note Alternatively, the user can be redirected to the specified URL. For this option to work,
* @apiNote Alternatively, the user can be redirected to the specified URL. For this option to work,
* you must enable /setcustomurls for your bot via BotFather and accept the terms.
*
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class AnswerCallbackQuery extends BotApiMethod<Boolean> {
public static final String PATH = "answercallbackquery";
@ -30,6 +46,7 @@ public class AnswerCallbackQuery extends BotApiMethod<Boolean> {
private static final String CACHETIME_FIELD = "cache_time";
@JsonProperty(CALLBACKQUERYID_FIELD)
@NonNull
private String callbackQueryId; ///< Unique identifier for the query to be answered
@JsonProperty(TEXT_FIELD)
private String text; ///< Optional Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters
@ -47,60 +64,11 @@ public class AnswerCallbackQuery extends BotApiMethod<Boolean> {
* Optional The maximum amount of time in seconds that the result of the callback query
* may be cached client-side.
*
* @note Telegram apps will support caching starting in version 3.14. Defaults to 0.
* @apiNote Telegram apps will support caching starting in version 3.14. Defaults to 0.
*/
@JsonProperty(CACHETIME_FIELD)
private Integer cacheTime;
public AnswerCallbackQuery() {
super();
}
public String getCallbackQueryId() {
return this.callbackQueryId;
}
public AnswerCallbackQuery setCallbackQueryId(String callbackQueryId) {
this.callbackQueryId = callbackQueryId;
return this;
}
public String getText() {
return this.text;
}
public AnswerCallbackQuery setText(String text) {
this.text = text;
return this;
}
public Boolean getShowAlert() {
return this.showAlert;
}
public AnswerCallbackQuery setShowAlert(Boolean showAlert) {
this.showAlert = showAlert;
return this;
}
public String getUrl() {
return url;
}
public AnswerCallbackQuery setUrl(String url) {
this.url = url;
return this;
}
public Integer getCacheTime() {
return cacheTime;
}
public AnswerCallbackQuery setCacheTime(Integer cacheTime) {
this.cacheTime = cacheTime;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -127,15 +95,4 @@ public class AnswerCallbackQuery extends BotApiMethod<Boolean> {
throw new TelegramApiValidationException("CallbackQueryId can't be null", this);
}
}
@Override
public String toString() {
return "AnswerCallbackQuery{" +
"callbackQueryId='" + callbackQueryId + '\'' +
", text='" + text + '\'' +
", showAlert=" + showAlert +
", url='" + url + '\'' +
", cacheTime=" + cacheTime +
'}';
}
}

View File

@ -1,16 +1,23 @@
package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.inlinequery.result.InlineQueryResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Singular;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.inlinequery.result.InlineQueryResult;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
@ -19,6 +26,14 @@ import java.util.regex.Pattern;
* @version 1.0
* Use this method to send answers to an inline query. On success, True is returned.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class AnswerInlineQuery extends BotApiMethod<Boolean> {
public static final String PATH = "answerInlineQuery";
@ -31,8 +46,11 @@ public class AnswerInlineQuery extends BotApiMethod<Boolean> {
private static final String SWITCH_PM_PARAMETER_FIELD = "switch_pm_parameter";
@JsonProperty(INLINEQUERYID_FIELD)
@NonNull
private String inlineQueryId; ///< Unique identifier for answered query
@JsonProperty(RESULTS_FIELD)
@Singular
@NonNull
private List<InlineQueryResult> results; ///< A JSON-serialized array of results for the inline query
@JsonProperty(CACHETIME_FIELD)
private Integer cacheTime; ///< Optional The maximum amount of time the result of the inline query may be cached on the server
@ -45,79 +63,6 @@ public class AnswerInlineQuery extends BotApiMethod<Boolean> {
@JsonProperty(SWITCH_PM_PARAMETER_FIELD)
private String switchPmParameter; ///< Optional. Parameter for the start message sent to the bot when user presses the switch button
public AnswerInlineQuery() {
super();
}
public String getInlineQueryId() {
return inlineQueryId;
}
public AnswerInlineQuery setInlineQueryId(String inlineQueryId) {
this.inlineQueryId = inlineQueryId;
return this;
}
public List<InlineQueryResult> getResults() {
return results;
}
public AnswerInlineQuery setResults(List<InlineQueryResult> results) {
this.results = results;
return this;
}
@JsonIgnore
public AnswerInlineQuery setResults(InlineQueryResult... results) {
this.results = Arrays.asList(results);
return this;
}
public Integer getCacheTime() {
return cacheTime;
}
public AnswerInlineQuery setCacheTime(Integer cacheTime) {
this.cacheTime = cacheTime;
return this;
}
public Boolean isPersonal() {
return isPersonal;
}
public AnswerInlineQuery setPersonal(Boolean personal) {
isPersonal = personal;
return this;
}
public String getNextOffset() {
return nextOffset;
}
public AnswerInlineQuery setNextOffset(String nextOffset) {
this.nextOffset = nextOffset;
return this;
}
public String getSwitchPmText() {
return switchPmText;
}
public AnswerInlineQuery setSwitchPmText(String switchPmText) {
this.switchPmText = switchPmText;
return this;
}
public String getSwitchPmParameter() {
return switchPmParameter;
}
public AnswerInlineQuery setSwitchPmParameter(String switchPmParameter) {
this.switchPmParameter = switchPmParameter;
return this;
}
@Override
public void validate() throws TelegramApiValidationException {
if (inlineQueryId == null || inlineQueryId.isEmpty()) {
@ -164,17 +109,4 @@ public class AnswerInlineQuery extends BotApiMethod<Boolean> {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public String toString() {
return "AnswerInlineQuery{" +
"inlineQueryId='" + inlineQueryId + '\'' +
", results=" + results +
", cacheTime=" + cacheTime +
", isPersonal=" + isPersonal +
", switchPmText=" + switchPmText +
", switchPmParameter=" + switchPmParameter +
", nextOffset='" + nextOffset + '\'' +
'}';
}
}

View File

@ -2,14 +2,21 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
* @version 1.0
@ -21,6 +28,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
*
* @apiNote The Bot API must receive an answer within 10 seconds after the pre-checkout query was sent.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class AnswerPreCheckoutQuery extends BotApiMethod<Boolean> {
public static final String PATH = "answerPreCheckoutQuery";
@ -29,56 +44,14 @@ public class AnswerPreCheckoutQuery extends BotApiMethod<Boolean> {
private static final String ERROR_MESSAGE_FIELD = "error_message";
@JsonProperty(PRE_CHECKOUT_QUERY_ID_FIELD)
@NonNull
private String preCheckoutQueryId; ///< Unique identifier for the query to be answered
@JsonProperty(OK_FIELD)
@NonNull
private Boolean ok; ///< Specify True if everything is alright (goods are available, etc.) and the bot is ready to proceed with the order. Use False if there are any problems.
@JsonProperty(ERROR_MESSAGE_FIELD)
private String errorMessage; ///< Optional. Required if ok is False. Error message in human readable form that explains the reason for failure to proceed with the checkout
/**
* Creates an empty answer pre-checkout query
*/
public AnswerPreCheckoutQuery() {
super();
}
/**
* Creates an answer pre-checkout query with mandatory parameters
* @param preCheckoutQueryId Unique identifier for the query to be answered
* @param ok Specify True if delivery to the specified address is possible and False if there are any problems
*/
public AnswerPreCheckoutQuery(String preCheckoutQueryId, Boolean ok) {
this.preCheckoutQueryId = checkNotNull(preCheckoutQueryId);
this.ok = checkNotNull(ok);
}
public String getPreCheckoutQueryId() {
return preCheckoutQueryId;
}
public AnswerPreCheckoutQuery setPreCheckoutQueryId(String preCheckoutQueryId) {
this.preCheckoutQueryId = checkNotNull(preCheckoutQueryId);
return this;
}
public Boolean getOk() {
return ok;
}
public AnswerPreCheckoutQuery setOk(Boolean ok) {
this.ok = checkNotNull(ok);
return this;
}
public String getErrorMessage() {
return errorMessage;
}
public AnswerPreCheckoutQuery setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
@Override
public void validate() throws TelegramApiValidationException {
if (preCheckoutQueryId == null || preCheckoutQueryId.isEmpty()) {
@ -113,13 +86,4 @@ public class AnswerPreCheckoutQuery extends BotApiMethod<Boolean> {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public String toString() {
return "AnswerPreCheckoutQuery{" +
"preCheckoutQueryId='" + preCheckoutQueryId + '\'' +
", ok=" + ok +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}

View File

@ -2,16 +2,23 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.payments.ShippingOption;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.payments.ShippingOption;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
* @version 1.0
@ -22,6 +29,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
*
* On success, True is returned
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class AnswerShippingQuery extends BotApiMethod<Boolean> {
public static final String PATH = "answerShippingQuery";
@ -31,67 +46,16 @@ public class AnswerShippingQuery extends BotApiMethod<Boolean> {
private static final String ERROR_MESSAGE_FIELD = "error_message";
@JsonProperty(SHIPPING_QUERY_ID_FIELD)
@NonNull
private String shippingQueryId; ///< Unique identifier for the query to be answered
@JsonProperty(OK_FIELD)
@NonNull
private Boolean ok; ///< Specify True if delivery to the specified address is possible and False if there are any problems
@JsonProperty(SHIPPING_OPTIONS_FIELD)
private List<ShippingOption> shippingOptions; ///< Optional. Required if ok is True. A JSON-serialized array of available shipping options.
@JsonProperty(ERROR_MESSAGE_FIELD)
private String errorMessage; ///< Optional. Required if ok is False. Error message in human readable form that explains why it is impossible to complete the order (e.g. "Sorry, delivery to your desired address is unavailable').
/**
* Creates an empty answer shipping query
*/
public AnswerShippingQuery() {
super();
}
/**
* Creates an answer shipping query with mandatory parameters
* @param shippingQueryId Unique identifier for the query to be answered
* @param ok Specify True if delivery to the specified address is possible and False if there are any problems
*/
public AnswerShippingQuery(String shippingQueryId, Boolean ok) {
this.shippingQueryId = checkNotNull(shippingQueryId);
this.ok = checkNotNull(ok);
}
public String getShippingQueryId() {
return shippingQueryId;
}
public AnswerShippingQuery setShippingQueryId(String shippingQueryId) {
this.shippingQueryId = checkNotNull(shippingQueryId);
return this;
}
public Boolean getOk() {
return ok;
}
public AnswerShippingQuery setOk(Boolean ok) {
this.ok = checkNotNull(ok);
return this;
}
public List<ShippingOption> getShippingOptions() {
return shippingOptions;
}
public AnswerShippingQuery setShippingOptions(List<ShippingOption> shippingOptions) {
this.shippingOptions = shippingOptions;
return this;
}
public String getErrorMessage() {
return errorMessage;
}
public AnswerShippingQuery setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
return this;
}
@Override
public void validate() throws TelegramApiValidationException {
if (shippingQueryId == null || shippingQueryId.isEmpty()) {
@ -133,14 +97,4 @@ public class AnswerShippingQuery extends BotApiMethod<Boolean> {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public String toString() {
return "AnswerShippingQuery{" +
"shippingQueryId='" + shippingQueryId + '\'' +
", ok=" + ok +
", shippingOptions=" + shippingOptions +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}

View File

@ -0,0 +1,158 @@
package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.MessageEntity;
import org.telegram.telegrambots.meta.api.objects.MessageId;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.ReplyKeyboard;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.List;
/**
* @author Ruben Bermudez
* @version 1.0
* Use this method to copy messages of any kind. The method is analogous to the method forwardMessages,
* but the copied message doesn't have a link to the original message.
* Returns the MessageId of the sent message on success.
*/
@SuppressWarnings("unused")
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@RequiredArgsConstructor
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CopyMessage extends BotApiMethod<MessageId> {
public static final String PATH = "copyMessage";
private static final String CHATID_FIELD = "chat_id";
private static final String FROMCHATID_FIELD = "from_chat_id";
private static final String MESSAGEID_FIELD = "message_id";
private static final String CAPTION_FIELD = "caption";
private static final String PARSEMODE_FIELD = "parse_mode";
private static final String CAPTIONENTITIES_FIELD = "caption_entities";
private static final String DISABLENOTIFICATION_FIELD = "disable_notification";
private static final String REPLYTOMESSAGEID_FIELD = "reply_to_message_id";
private static final String ALLOWSENDINGWITHOUTREPLY_FIELD = "allow_sending_without_reply";
private static final String REPLYMARKUP_FIELD = "reply_markup";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the target chat or username of the target channel (in the format @channelusername)
@JsonProperty(FROMCHATID_FIELD)
@NonNull
private String fromChatId; ///< Unique identifier for the chat where the original message was sent (or channel username in the format @channelusername)
@JsonProperty(MESSAGEID_FIELD)
@NonNull
private Integer messageId; ///< Message identifier in the chat specified in from_chat_id
@JsonProperty(CAPTION_FIELD)
private String caption; ///< Optional. New caption for media, 0-1024 characters after entities parsing. If not specified, the original caption is kept
@JsonProperty(PARSEMODE_FIELD)
private String parseMode; ///< Optional. Mode for parsing entities in the new caption. See formatting options for more details.
@JsonProperty(CAPTIONENTITIES_FIELD)
private List<MessageEntity> captionEntities; ///< Optional. List of special entities that appear in the new caption, which can be specified instead of parse_mode
@JsonProperty(DISABLENOTIFICATION_FIELD)
private Boolean disableNotification; ///< Optional. Sends the message silently. Users will receive a notification with no sound.
@JsonProperty(REPLYTOMESSAGEID_FIELD)
private Integer replyToMessageId; ///< Optional. If the message is a reply, ID of the original message
@JsonProperty(ALLOWSENDINGWITHOUTREPLY_FIELD)
private Boolean allowSendingWithoutReply; ///< Optional. Pass True, if the message should be sent even if the specified replied-to message is not found
/**
* Optional.
*
* Additional interface options.
* A JSON-serialized object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or
* to force a reply from the user.
*/
@JsonProperty(REPLYMARKUP_FIELD)
@JsonDeserialize()
private ReplyKeyboard replyMarkup;
public void enableNotification() {
this.disableNotification = null;
}
public void disableNotification() {
this.disableNotification = true;
}
public void enableMarkdown(boolean enable) {
if (enable) {
this.parseMode = ParseMode.MARKDOWN;
} else {
this.parseMode = null;
}
}
public void enableHtml(boolean enable) {
if (enable) {
this.parseMode = ParseMode.HTML;
} else {
this.parseMode = null;
}
}
public void enableMarkdownV2(boolean enable) {
if (enable) {
this.parseMode = ParseMode.MARKDOWNV2;
} else {
this.parseMode = null;
}
}
@Override
public String getMethod() {
return PATH;
}
@Override
public MessageId deserializeResponse(String answer) throws TelegramApiRequestException {
try {
ApiResponse<MessageId> result = OBJECT_MAPPER.readValue(answer,
new TypeReference<ApiResponse<MessageId>>(){});
if (result.getOk()) {
return result.getResult();
} else {
throw new TelegramApiRequestException("Error copying message", result);
}
} catch (IOException e) {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public void validate() throws TelegramApiValidationException {
if (chatId == null) {
throw new TelegramApiValidationException("ChatId parameter can't be empty", this);
}
if (fromChatId == null) {
throw new TelegramApiValidationException("FromChatId parameter can't be empty", this);
}
if (messageId == null) {
throw new TelegramApiValidationException("MessageId parameter can't be empty", this);
}
if (parseMode != null && (captionEntities != null && !captionEntities.isEmpty()) ) {
throw new TelegramApiValidationException("Parse mode can't be enabled if Entities are provided", this);
}
if (replyMarkup != null) {
replyMarkup.validate();
}
}
}

View File

@ -2,20 +2,35 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.Message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
/**
* @author Ruben Bermudez
* @version 1.0
* Use this method to send text messages. On success, the sent Message is returned.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class ForwardMessage extends BotApiMethod<Message> {
public static final String PATH = "forwardmessage";
@ -25,10 +40,13 @@ public class ForwardMessage extends BotApiMethod<Message> {
private static final String DISABLENOTIFICATION_FIELD = "disable_notification";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (or username for channels)
@JsonProperty(FROMCHATID_FIELD)
@NonNull
private String fromChatId; ///< Unique identifier for the chat where the original message was sent User or GroupChat id
@JsonProperty(MESSAGEID_FIELD)
@NonNull
private Integer messageId; ///< Unique message identifier
/**
* Optional. Sends the message silently.
@ -39,99 +57,6 @@ public class ForwardMessage extends BotApiMethod<Message> {
@JsonProperty(DISABLENOTIFICATION_FIELD)
private Boolean disableNotification;
public ForwardMessage() {
super();
}
public ForwardMessage(String chatId, String fromChatId, Integer messageId) {
this();
Objects.requireNonNull(chatId);
Objects.requireNonNull(fromChatId);
this.chatId = chatId;
this.fromChatId = fromChatId;
this.messageId = messageId;
}
public ForwardMessage(String chatId, Long fromChatId, Integer messageId) {
this();
Objects.requireNonNull(chatId);
Objects.requireNonNull(fromChatId);
this.chatId = chatId;
this.fromChatId = fromChatId.toString();
this.messageId = messageId;
}
public ForwardMessage(Long chatId, String fromChatId, Integer messageId) {
this();
Objects.requireNonNull(chatId);
Objects.requireNonNull(fromChatId);
this.chatId = chatId.toString();
this.fromChatId = fromChatId;
this.messageId = messageId;
}
public ForwardMessage(Long chatId, Long fromChatId, Integer messageId) {
this();
Objects.requireNonNull(chatId);
Objects.requireNonNull(fromChatId);
this.chatId = chatId.toString();
this.fromChatId = fromChatId.toString();
this.messageId = messageId;
}
public String getChatId() {
return chatId;
}
public ForwardMessage setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public ForwardMessage setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
public String getFromChatId() {
return fromChatId;
}
public ForwardMessage setFromChatId(String fromChatId) {
this.fromChatId = fromChatId;
return this;
}
public ForwardMessage setFromChatId(Long fromChatId) {
Objects.requireNonNull(fromChatId);
this.fromChatId = fromChatId.toString();
return this;
}
public Integer getMessageId() {
return messageId;
}
public ForwardMessage setMessageId(Integer messageId) {
this.messageId = messageId;
return this;
}
public Boolean getDisableNotification() {
return disableNotification;
}
public ForwardMessage enableNotification() {
this.disableNotification = false;
return this;
}
public ForwardMessage disableNotification() {
this.disableNotification = true;
return this;
}
@Override
public void validate() throws TelegramApiValidationException {
if (chatId == null || chatId.isEmpty()) {
@ -164,14 +89,4 @@ public class ForwardMessage extends BotApiMethod<Message> {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public String toString() {
return "ForwardMessage{" +
"chatId='" + chatId + '\'' +
", fromChatId='" + fromChatId + '\'' +
", messageId=" + messageId +
", disableNotification=" + disableNotification +
'}';
}
}

View File

@ -2,9 +2,16 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.File;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.File;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
@ -21,27 +28,22 @@ import java.io.IOException;
* It is guaranteed that the link will be valid for at least 1 hour.
* When the link expires, a new one can be requested by calling getFile again.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetFile extends BotApiMethod<File> {
public static final String PATH = "getFile";
private static final String FILEID_FIELD = "file_id";
@JsonProperty(FILEID_FIELD)
@NonNull
private String fileId; ///< File identifier to get info about
public GetFile() {
super();
}
public String getFileId() {
return fileId;
}
public GetFile setFileId(String fileId) {
this.fileId = fileId;
return this;
}
@Override
public void validate() throws TelegramApiValidationException {
if (fileId == null) {
@ -68,11 +70,4 @@ public class GetFile extends BotApiMethod<File> {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public String toString() {
return "GetFile{" +
"fileId='" + fileId + '\'' +
'}';
}
}

View File

@ -1,9 +1,14 @@
package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.User;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
@ -15,13 +20,15 @@ import java.io.IOException;
* A simple method for testing your bot's auth token. Requires no parameters.
* Returns basic information about the bot in form of a User object
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@AllArgsConstructor
@Builder
public class GetMe extends BotApiMethod<User> {
public static final String PATH = "getme";
public GetMe() {
super();
}
@Override
public String getMethod() {
return PATH;

View File

@ -3,6 +3,15 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.UserProfilePhotos;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
@ -15,6 +24,14 @@ import java.io.IOException;
* @version 1.0
* Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos object.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class GetUserProfilePhotos extends BotApiMethod<UserProfilePhotos> {
public static final String PATH = "getuserprofilephotos";
@ -23,9 +40,10 @@ public class GetUserProfilePhotos extends BotApiMethod<UserProfilePhotos> {
private static final String LIMIT_FIELD = "limit";
@JsonProperty(USERID_FIELD)
@NonNull
private Integer userId; ///< Unique identifier of the target user
/**
* Sequential number of the first photo to be returned. By default, all photos are returned.
* Optional. Sequential number of the first photo to be returned. By default, all photos are returned.
*/
@JsonProperty(OFFSET_FIELD)
private Integer offset;
@ -35,37 +53,6 @@ public class GetUserProfilePhotos extends BotApiMethod<UserProfilePhotos> {
@JsonProperty(LIMIT_FIELD)
private Integer limit;
public GetUserProfilePhotos() {
super();
}
public Integer getUserId() {
return userId;
}
public GetUserProfilePhotos setUserId(Integer userId) {
this.userId = userId;
return this;
}
public Integer getOffset() {
return offset;
}
public GetUserProfilePhotos setOffset(Integer offset) {
this.offset = offset;
return this;
}
public Integer getLimit() {
return limit;
}
public GetUserProfilePhotos setLimit(Integer limit) {
this.limit = limit;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -92,13 +79,4 @@ public class GetUserProfilePhotos extends BotApiMethod<UserProfilePhotos> {
throw new TelegramApiValidationException("UserId parameter can't be empty", this);
}
}
@Override
public String toString() {
return "GetUserProfilePhotos{" +
"userId=" + userId +
", offset=" + offset +
", limit=" + limit +
'}';
}
}

View File

@ -3,10 +3,10 @@ package org.telegram.telegrambots.meta.api.methods;
/**
* @author Ruben Bermudez
* @version 1.0
* @brief Type of parse modes for text in messages
* @date 10 of April of 2016
* Type of parse modes for text in messages
*/
public class ParseMode {
public static final String MARKDOWN = "Markdown";
public static final String MARKDOWNV2 = "MarkdownV2";
public static final String HTML = "html";
}

View File

@ -2,17 +2,23 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.passport.dataerror.PassportElementError;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.Singular;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.passport.dataerror.PassportElementError;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
* @version 4.0.0
@ -20,6 +26,13 @@ import static com.google.common.base.Preconditions.checkNotNull;
* Informs a user that some Telegram Passport data contains errors.
* The user will not be able to resend data, until the errors are fixed
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SetPassportDataErrors extends BotApiMethod<Boolean> {
public static final String PATH = "setPassportDataErrors";
@ -27,47 +40,13 @@ public class SetPassportDataErrors extends BotApiMethod<Boolean> {
private static final String ERRORS_FIELD = "errors";
@JsonProperty(USERID_FIELD)
@NonNull
private Integer userId; ///< User identifier
@JsonProperty(ERRORS_FIELD)
@NonNull
@Singular
private List<PassportElementError> errors; ///< A JSON-serialized array describing the errors
public SetPassportDataErrors(Integer userId, List<PassportElementError> errors) {
super();
this.userId = checkNotNull(userId);
this.errors = checkNotNull(errors);
}
public SetPassportDataErrors() {
super();
}
public Integer getUserId() {
return userId;
}
public SetPassportDataErrors setUserId(Integer userId) {
this.userId = userId;
return this;
}
public List<PassportElementError> getErrors() {
return errors;
}
public SetPassportDataErrors setErrors(List<PassportElementError> errors) {
this.errors = errors;
return this;
}
public SetPassportDataErrors addError(PassportElementError error) {
error = checkNotNull(error);
if (this.errors == null) {
this.errors = new ArrayList<>();
}
this.errors.add(error);
return this;
}
@Override
public String getMethod() {
return PATH;

View File

@ -2,8 +2,15 @@ package org.telegram.telegrambots.meta.api.methods;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import org.telegram.telegrambots.meta.api.objects.Message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.replykeyboard.InlineKeyboardMarkup;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
@ -18,6 +25,13 @@ import java.io.Serializable;
* before live_period expires. On success, if the message was sent by the bot, the sent Message is returned,
* otherwise True is returned.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class StopMessageLiveLocation extends BotApiMethod<Serializable> {
public static final String PATH = "stopMessageLiveLocation";
@ -45,51 +59,6 @@ public class StopMessageLiveLocation extends BotApiMethod<Serializable> {
@JsonProperty(REPLYMARKUP_FIELD)
private InlineKeyboardMarkup replyMarkup; ///< Optional. A JSON-serialized object for an inline keyboard.
public StopMessageLiveLocation() {
super();
}
public String getChatId() {
return chatId;
}
public StopMessageLiveLocation setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public StopMessageLiveLocation setChatId(Long chatId) {
this.chatId = chatId.toString();
return this;
}
public Integer getMessageId() {
return messageId;
}
public StopMessageLiveLocation setMessageId(Integer messageId) {
this.messageId = messageId;
return this;
}
public String getInlineMessageId() {
return inlineMessageId;
}
public StopMessageLiveLocation setInlineMessageId(String inlineMessageId) {
this.inlineMessageId = inlineMessageId;
return this;
}
public InlineKeyboardMarkup getReplyMarkup() {
return replyMarkup;
}
public StopMessageLiveLocation setReplyMarkup(InlineKeyboardMarkup replyMarkup) {
this.replyMarkup = replyMarkup;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -142,14 +111,4 @@ public class StopMessageLiveLocation extends BotApiMethod<Serializable> {
replyMarkup.validate();
}
}
@Override
public String toString() {
return "StopMessageLiveLocation{" +
"chatId='" + chatId + '\'' +
", messageId=" + messageId +
", inlineMessageId='" + inlineMessageId + '\'' +
", replyMarkup=" + replyMarkup +
'}';
}
}

View File

@ -0,0 +1,58 @@
package org.telegram.telegrambots.meta.api.methods.commands;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.commands.BotCommand;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.ArrayList;
/**
* @author Ruben Bermudez
* @version 4.7
* Use this method to get the current list of the bot's commands.
* Requires no parameters.
* Returns Array of BotCommand on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@AllArgsConstructor
@Builder
public class GetMyCommands extends BotApiMethod<ArrayList<BotCommand>> {
public static final String PATH = "getMyCommands";
@Override
public String getMethod() {
return PATH;
}
@Override
public ArrayList<BotCommand> deserializeResponse(String answer) throws TelegramApiRequestException {
try {
ApiResponse<ArrayList<BotCommand>> result = OBJECT_MAPPER.readValue(answer,
new TypeReference<ApiResponse<ArrayList<BotCommand>>>(){});
if (result.getOk()) {
return result.getResult();
} else {
throw new TelegramApiRequestException("Error sending commands", result);
}
} catch (IOException e) {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public void validate() throws TelegramApiValidationException {
}
}

View File

@ -0,0 +1,81 @@
package org.telegram.telegrambots.meta.api.methods.commands;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.Singular;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.commands.BotCommand;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.List;
/**
* @author Ruben Bermudez
* @version 4.7
* Use this method to change the list of the bot's commands. Returns True on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SetMyCommands extends BotApiMethod<Boolean> {
public static final String PATH = "setMyCommands";
private static final String COMMANDS_FIELD = "commands";
/**
* A JSON-serialized list of bot commands to be set as the list of the bot's commands.
* At most 100 commands can be specified.
*/
@JsonProperty(COMMANDS_FIELD)
@Singular
@NonNull
private List<BotCommand> commands;
@Override
public String getMethod() {
return PATH;
}
@Override
public Boolean deserializeResponse(String answer) throws TelegramApiRequestException {
try {
ApiResponse<Boolean> result = OBJECT_MAPPER.readValue(answer,
new TypeReference<ApiResponse<Boolean>>(){});
if (result.getOk()) {
return result.getResult();
} else {
throw new TelegramApiRequestException("Error sending commands", result);
}
} catch (IOException e) {
throw new TelegramApiRequestException("Unable to deserialize response", e);
}
}
@Override
public void validate() throws TelegramApiValidationException {
if (commands == null) {
throw new TelegramApiValidationException("Commands parameter can't be empty", this);
}
if (commands.size() > 100) {
throw new TelegramApiValidationException("No more than 100 commands are allowed", this);
}
for (BotCommand command : commands) {
command.validate();
}
}
}

View File

@ -19,6 +19,15 @@ package org.telegram.telegrambots.meta.api.methods.games;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.games.GameHighScore;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
@ -35,12 +44,20 @@ import java.util.ArrayList;
* Will return the score of the specified user and several of his neighbors in a game.
* On success, returns an Array of GameHighScore objects.
*
* @note This method will currently return scores for the target user,
* @apiNote This method will currently return scores for the target user,
* plus two of his closest neighbors on each side. Will also return the top three users
* if the user and his neighbors are not among them.
* Please note that this behavior is subject to change.
*
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class GetGameHighScores extends BotApiMethod<ArrayList<GameHighScore>> {
public static final String PATH = "getGameHighScores";
@ -56,53 +73,9 @@ public class GetGameHighScores extends BotApiMethod<ArrayList<GameHighScore>> {
@JsonProperty(INLINE_MESSAGE_ID_FIELD)
private String inlineMessageId; ///< Optional Required if chat_id and message_id are not specified. Identifier of the inline message
@JsonProperty(USER_ID_FIELD)
@NonNull
private Integer userId; ///<Target user id
public GetGameHighScores() {
super();
}
public String getChatId() {
return chatId;
}
public Integer getMessageId() {
return messageId;
}
public String getInlineMessageId() {
return inlineMessageId;
}
public Integer getUserId() {
return userId;
}
public GetGameHighScores setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public GetGameHighScores setChatId(Long chatId) {
this.chatId = chatId.toString();
return this;
}
public GetGameHighScores setMessageId(Integer messageId) {
this.messageId = messageId;
return this;
}
public GetGameHighScores setInlineMessageId(String inlineMessageId) {
this.inlineMessageId = inlineMessageId;
return this;
}
public GetGameHighScores setUserId(Integer userId) {
this.userId = userId;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -144,14 +117,4 @@ public class GetGameHighScores extends BotApiMethod<ArrayList<GameHighScore>> {
}
}
}
@Override
public String toString() {
return "GetGameHighScores{" +
"chatId='" + chatId + '\'' +
", messageId=" + messageId +
", inlineMessageId='" + inlineMessageId + '\'' +
", userId=" + userId +
'}';
}
}

View File

@ -20,6 +20,15 @@ package org.telegram.telegrambots.meta.api.methods.games;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
@ -39,6 +48,14 @@ import java.io.Serializable;
* Returns an error, if the new score is not greater than the user's current score in
* the chat and force is False.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class SetGameScore extends BotApiMethod<Serializable> {
public static final String PATH = "setGameScore";
@ -59,84 +76,14 @@ public class SetGameScore extends BotApiMethod<Serializable> {
@JsonProperty(DISABLEEDITMESSAGE_FIELD)
private Boolean disableEditMessage; ///< Optional Pass True, if the game message should not be automatically edited to include the current scoreboard. Defaults to False
@JsonProperty(USER_ID_FIELD)
@NonNull
private Integer userId; ///< User identifier
@JsonProperty(SCORE_FIELD)
@NonNull
private Integer score; ///< New score, must be positive
@JsonProperty(FORCE_FIELD)
private Boolean force; ///< Optional. Pass True, if the high score is allowed to decrease. This can be useful when fixing mistakes or banning cheaters
public SetGameScore() {
super();
}
public String getChatId() {
return chatId;
}
public Integer getMessageId() {
return messageId;
}
public String getInlineMessageId() {
return inlineMessageId;
}
public Boolean getDisableEditMessage() {
return disableEditMessage;
}
public Integer getUserId() {
return userId;
}
public Integer getScore() {
return score;
}
public Boolean getForce() {
return force;
}
public SetGameScore setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public SetGameScore setChatId(Long chatId) {
this.chatId = chatId.toString();
return this;
}
public SetGameScore setMessageId(Integer messageId) {
this.messageId = messageId;
return this;
}
public SetGameScore setInlineMessageId(String inlineMessageId) {
this.inlineMessageId = inlineMessageId;
return this;
}
public SetGameScore setDisableEditMessage(Boolean disableEditMessage) {
this.disableEditMessage = disableEditMessage;
return this;
}
public SetGameScore setUserId(Integer userId) {
this.userId = userId;
return this;
}
public SetGameScore setScore(Integer score) {
this.score = score;
return this;
}
public SetGameScore setForce(Boolean force) {
this.force = force;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -192,17 +139,4 @@ public class SetGameScore extends BotApiMethod<Serializable> {
}
}
}
@Override
public String toString() {
return "SetGameScore{" +
"chatId='" + chatId + '\'' +
", messageId=" + messageId +
", inlineMessageId='" + inlineMessageId + '\'' +
", disableEditMessage=" + disableEditMessage +
", userId=" + userId +
", score=" + score +
", force=" + force +
'}';
}
}

View File

@ -2,15 +2,20 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
@ -21,43 +26,22 @@ import static com.google.common.base.Preconditions.checkNotNull;
*
* @apiNote In regular groups (non-supergroups), this method will only work if the All Members Are Admins setting is off in the target group.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DeleteChatPhoto extends BotApiMethod<Boolean> {
public static final String PATH = "deleteChatPhoto";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the target chat or username of the target channel (in the format @channelusername)
public DeleteChatPhoto() {
super();
}
public DeleteChatPhoto(String chatId) {
super();
this.chatId = checkNotNull(chatId);
}
public DeleteChatPhoto(Long chatId) {
super();
this.chatId = checkNotNull(chatId).toString();
}
public String getChatId() {
return chatId;
}
public DeleteChatPhoto setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public DeleteChatPhoto setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -84,11 +68,4 @@ public class DeleteChatPhoto extends BotApiMethod<Boolean> {
throw new TelegramApiValidationException("ChatId can't be null", this);
}
}
@Override
public String toString() {
return "DeleteChatPhoto{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -2,15 +2,20 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
@ -20,43 +25,22 @@ import static com.google.common.base.Preconditions.checkNotNull;
* Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method.
* Returns True on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class DeleteChatStickerSet extends BotApiMethod<Boolean> {
public static final String PATH = "deleteChatStickerSet";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
public DeleteChatStickerSet() {
super();
}
public DeleteChatStickerSet(String chatId) {
super();
this.chatId = checkNotNull(chatId);
}
public DeleteChatStickerSet(Long chatId) {
super();
this.chatId = checkNotNull(chatId).toString();
}
public String getChatId() {
return chatId;
}
public DeleteChatStickerSet setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public DeleteChatStickerSet setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -83,11 +67,4 @@ public class DeleteChatStickerSet extends BotApiMethod<Boolean> {
throw new TelegramApiValidationException("ChatId can't be empty", this);
}
}
@Override
public String toString() {
return "DeleteChatStickerSet{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -1,14 +0,0 @@
package org.telegram.telegrambots.meta.api.methods.groupadministration;
/**
* @author Ruben Bermudez
* @version 3.4
* Use this method to delete a group sticker set from a supergroup.
* The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
* Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method.
* Returns True on success.
* @deprecated Replaced by {@link DeleteChatStickerSet}
*/
@Deprecated
public class DeleteStickerSetName extends DeleteChatStickerSet {
}

View File

@ -2,15 +2,20 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
@ -18,43 +23,22 @@ import static com.google.common.base.Preconditions.checkNotNull;
* Use this method to export an invite link to a supergroup or a channel. The bot must be an administrator in the
* chat for this to work and must have the appropriate admin rights. Returns exported invite link as String on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ExportChatInviteLink extends BotApiMethod<String> {
public static final String PATH = "exportChatInviteLink";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
public ExportChatInviteLink() {
super();
}
public ExportChatInviteLink(String chatId) {
super();
this.chatId = checkNotNull(chatId);
}
public ExportChatInviteLink(Long chatId) {
super();
this.chatId = checkNotNull(chatId).toString();
}
public String getChatId() {
return chatId;
}
public ExportChatInviteLink setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public ExportChatInviteLink setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -81,11 +65,4 @@ public class ExportChatInviteLink extends BotApiMethod<String> {
throw new TelegramApiValidationException("ChatId can't be empty", this);
}
}
@Override
public String toString() {
return "ExportChatInviteLink{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -2,60 +2,43 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.Chat;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.Chat;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
* @version 1.0
* Use this method to get information about the chat. Returns Chat object on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetChat extends BotApiMethod<Chat> {
public static final String PATH = "getChat";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
public GetChat() {
super();
}
public GetChat(String chatId) {
super();
this.chatId = checkNotNull(chatId);
}
public GetChat(Long chatId) {
super();
this.chatId = checkNotNull(chatId).toString();
}
public String getChatId() {
return chatId;
}
public GetChat setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public GetChat setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -82,11 +65,4 @@ public class GetChat extends BotApiMethod<Chat> {
throw new TelegramApiValidationException("ChatId can't be empty", this);
}
}
@Override
public String toString() {
return "GetChat{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -2,16 +2,22 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ChatMember;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.ChatMember;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Objects;
/**
* @author Ruben Bermudez
@ -22,33 +28,22 @@ import java.util.Objects;
* If the chat is a group or a supergroup and no administrators were appointed,
* only the creator will be returned.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetChatAdministrators extends BotApiMethod<ArrayList<ChatMember>> {
public static final String PATH = "getChatAdministrators";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
public GetChatAdministrators() {
super();
}
public String getChatId() {
return chatId;
}
public GetChatAdministrators setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public GetChatAdministrators setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -75,11 +70,4 @@ public class GetChatAdministrators extends BotApiMethod<ArrayList<ChatMember>> {
throw new TelegramApiValidationException("ChatId can't be empty", this);
}
}
@Override
public String toString() {
return "GetChatAdministrators{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -2,15 +2,21 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ChatMember;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.api.objects.ChatMember;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
/**
* @author Ruben Bermudez
@ -18,6 +24,13 @@ import java.util.Objects;
* Use this method to get information about a member of a chat.
* Returns a ChatMember object on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetChatMember extends BotApiMethod<ChatMember> {
public static final String PATH = "getChatMember";
@ -25,38 +38,12 @@ public class GetChatMember extends BotApiMethod<ChatMember> {
private static final String USERID_FIELD = "user_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
@JsonProperty(USERID_FIELD)
@NonNull
private Integer userId; ///< Unique identifier of the target user
public GetChatMember() {
super();
}
public String getChatId() {
return chatId;
}
public GetChatMember setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public GetChatMember setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
public Integer getUserId() {
return userId;
}
public GetChatMember setUserId(Integer userId) {
this.userId = userId;
return this;
}
@Override
public String getMethod() {
return PATH;
@ -86,12 +73,4 @@ public class GetChatMember extends BotApiMethod<ChatMember> {
throw new TelegramApiValidationException("UserId can't be null", this);
}
}
@Override
public String toString() {
return "GetChatMember{" +
"chatId='" + chatId + '\'' +
", userId='" + userId + '\'' +
'}';
}
}

View File

@ -1,11 +0,0 @@
package org.telegram.telegrambots.meta.api.methods.groupadministration;
/**
* @author Ruben Bermudez
* @version 1.0
* Use this method to get the number of members in a chat. Returns Int on success.
* @deprecated Replaced by {@link GetChatMembersCount}
*/
@Deprecated
public class GetChatMemberCount extends GetChatMembersCount {
}

View File

@ -2,46 +2,42 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.exceptions.TelegramApiValidationException;
import java.io.IOException;
import java.util.Objects;
/**
* @author Ruben Bermudez
* @version 1.0
* Use this method to get the number of members in a chat. Returns Int on success.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class GetChatMembersCount extends BotApiMethod<Integer> {
public static final String PATH = "getChatMembersCount";
private static final String CHATID_FIELD = "chat_id";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels)
public GetChatMembersCount() {
super();
}
public String getChatId() {
return chatId;
}
public GetChatMembersCount setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public GetChatMembersCount setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
@Override
public String getMethod() {
return PATH;
@ -68,11 +64,4 @@ public class GetChatMembersCount extends BotApiMethod<Integer> {
throw new TelegramApiValidationException("ChatId can't be empty", this);
}
}
@Override
public String toString() {
return "GetChatMembersCount{" +
"chatId='" + chatId + '\'' +
'}';
}
}

View File

@ -3,7 +3,15 @@ package org.telegram.telegrambots.meta.api.methods.groupadministration;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.objects.ApiResponse;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
@ -13,9 +21,6 @@ import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ruben Bermudez
@ -28,6 +33,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
* group. Otherwise members may only be removed by the group's creator or by the member that added
* them.
*/
@EqualsAndHashCode(callSuper = false)
@Getter
@Setter
@ToString
@NoArgsConstructor
@RequiredArgsConstructor
@AllArgsConstructor
@Builder
public class KickChatMember extends BotApiMethod<Boolean> {
public static final String PATH = "kickchatmember";
@ -36,72 +49,27 @@ public class KickChatMember extends BotApiMethod<Boolean> {
private static final String UNTILDATE_FIELD = "until_date";
@JsonProperty(CHATID_FIELD)
@NonNull
private String chatId; ///< Required. Unique identifier for the chat to send the message to (Or username for channels)
@JsonProperty(USER_ID_FIELD)
@NonNull
private Integer userId; ///< Required. Unique identifier of the target user
@JsonProperty(UNTILDATE_FIELD)
private Integer untilDate; ///< Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever
private Integer untilDate; ///< Optional. Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever
public KickChatMember() {
super();
}
public KickChatMember(String chatId, Integer userId) {
this.chatId = checkNotNull(chatId);
this.userId = checkNotNull(userId);
}
public KickChatMember(Long chatId, Integer userId) {
this.chatId = checkNotNull(chatId).toString();
this.userId = checkNotNull(userId);
}
public String getChatId() {
return chatId;
}
public KickChatMember setChatId(String chatId) {
this.chatId = chatId;
return this;
}
public KickChatMember setChatId(Long chatId) {
Objects.requireNonNull(chatId);
this.chatId = chatId.toString();
return this;
}
public Integer getUserId() {
return userId;
}
public KickChatMember setUserId(Integer userId) {
this.userId = userId;
return this;
}
public Integer getUntilDate() {
return untilDate;
}
public KickChatMember setUntilDate(Integer untilDateInSeconds) {
this.untilDate = untilDateInSeconds;
return this;
@JsonIgnore
public void setUntilDateInstant(Instant instant) {
setUntilDate((int) instant.getEpochSecond());
}
@JsonIgnore
public KickChatMember setUntilDate(Instant instant) {
return setUntilDate((int) instant.getEpochSecond());
public void setUntilDateDateTime(ZonedDateTime date) {
setUntilDateInstant(date.toInstant());
}
@JsonIgnore
public KickChatMember setUntilDate(ZonedDateTime date) {
return setUntilDate(date.toInstant());
}
public KickChatMember forTimePeriod(Duration duration) {
return setUntilDate(Instant.now().plusMillis(duration.toMillis()));
public void forTimePeriodDuration(Duration duration) {
setUntilDateInstant(Instant.now().plusMillis(duration.toMillis()));
}
@Override
@ -133,13 +101,4 @@ public class KickChatMember extends BotApiMethod<Boolean> {
throw new TelegramApiValidationException("UserId can't be null", this);
}
}
@Override
public String toString() {
return "KickChatMember{" +
"chatId='" + chatId + '\'' +
", userId=" + userId +
", untilDate=" + untilDate +
'}';
}
}

Some files were not shown because too many files have changed in this diff Show More