Compare commits

...

488 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
rubenlagus
97c8011972 Reduce maven verbosity for travis 2019-07-30 00:33:36 +01:00
rubenlagus
0d0fea0f54 Reduce maven verbosity for travis 2019-07-30 00:05:36 +01:00
rubenlagus
e7e369472b Update changelog 2019-07-29 23:48:13 +01:00
rubenlagus
378b1e3eb8 Update version libraries versions 2019-07-29 23:45:23 +01:00
rubenlagus
5cee266478 Update version link 2019-07-29 23:39:49 +01:00
rubenlagus
b19ae64899 Fix text to Junit5 2019-07-29 00:24:30 +01:00
Ruben Bermudez
d7875bba34 Merge branch 'Chase22-Feature/Refactor-file-download' into java11 2019-07-28 23:47:04 +01:00
Ruben Bermudez
e551c64eea Merge branch 'Feature/Refactor-file-download' of https://github.com/Chase22/TelegramBots into Chase22-Feature/Refactor-file-download 2019-07-28 23:38:50 +01:00
rubenlagus
87bd7aefd2 Update version in poms 2019-07-28 21:27:12 +01:00
rubenlagus
fd342ad7a7 Update to Api Layer 4.4 2019-07-28 21:13:58 +01:00
rubenlagus
830b873776 Remove BotLogger 2019-07-27 13:19:54 +01:00
rubenlagus
08bcf2bdac Fix remaining tests 2019-07-09 20:50:50 +01:00
Ruben Bermudez
0448bd82bc
Merge pull request #632 from addo37/java11
Modified tests to accommodate JUnit5 and Mockito API changes
2019-07-09 21:46:01 +02:00
Bernhard Kralofsky
727f5a7a3e prevent nullPointerExceptions when using message flags without a MESSAGE flag first 2019-07-09 21:39:02 +02:00
Abbas Abou Daya
c633f1fcc2 Modified tests to accommodate JUnit5 and Mockito API changes 2019-07-08 23:18:08 -07:00
rubenlagus
251d19f456 Update travis 2019-07-08 20:55:14 +01:00
rubenlagus
37eea6c2be Update travis 2019-07-08 20:44:41 +01:00
rubenlagus
04248e64a7 Update pom to support earlier versions as well 2019-07-08 20:42:42 +01:00
rubenlagus
7b5acf0987 Update travis yalm 2019-07-08 20:37:04 +01:00
rubenlagus
679ad618b9 Java 11, initial version 2019-07-08 20:22:51 +01: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
7c11293d07 Fix #628 2019-06-15 18:10:27 +01:00
Ruben Bermudez
7fd3d871dc
Merge pull request #627 from rubenlagus/dev
Dev
2019-06-08 20:45:12 +01:00
Ruben Bermudez
7c64bf5f1b Update version 2019-06-08 20:39:08 +01:00
Ruben Bermudez
c89ee9b07c Merge branch 'dev' of https://github.com/rubenlagus/TelegramBots into dev 2019-06-08 20:34:04 +01:00
Ruben Bermudez
25e69f8055 Closes #625 2019-06-08 20:33:28 +01:00
Ruben Bermudez
c07bfb4903
Merge pull request #624 from rubenlagus/dev
Dev
2019-06-03 21:50:42 +01:00
Ruben Bermudez
14652bdd21
Merge pull request #617 from UnAfraid/dev
Adding mvnw (maven wrapper)
2019-06-01 12:51:23 +01:00
Rubenlagus
b3914c5e76 Api version 4.3 2019-06-01 12:50:17 +01:00
Ruben Bermudez
06dad7c262
Merge pull request #620 from recursiveribbons/message-poll-field
Added Poll field to Message
2019-05-31 00:00:28 +01:00
Ruben Bermudez
c125b103e6
Merge pull request #622 from recursiveribbons/profile-photos-offset-optional
Allow offset to be null in GetUserProfilePhotos
2019-05-30 23:58:29 +01:00
iamvincentliu
54383aa626 Allow offset to be null in GetUserProfilePhotos 2019-05-27 03:52:06 +02:00
UnAfraid
ba3555fefc Adding mvnw (maven wrapper) 2019-05-21 13:13:04 +03:00
iamvincentliu
59f2f8e9cd Added Poll field to Message 2019-05-17 21:31:09 +02:00
Ruben Bermudez
6e689fbf88
Merge pull request #606 from aNNiMON/stoppoll-getter
Add getter and setter for messageId in StopPoll
2019-05-08 23:56:47 +01:00
Ruben Bermudez
ee89128adb
Merge pull request #608 from recursiveribbons/jar-link
Link to download jar from mvnrepository
2019-05-08 23:54:30 +01:00
iamvincentliu
4a3ebed6ac Link to download jar from mvnrepository 2019-04-24 18:36:54 +02:00
Victor Melnik
c578d47e9c
Add missing getter in StopPoll 2019-04-16 11:22:44 +03:00
Ruben Bermudez
d62354915d
Merge pull request #605 from rubenlagus/dev
Api version 4.2
2019-04-16 00:22:09 +01:00
Ruben Bermudez
b76d4da5bf
Merge pull request #599 from bernikr/dev
Back up maps in the database as a collection of key value pairs
2019-04-16 00:12:06 +01:00
Ruben Bermudez
20f45bd604 Api version 4.2 2019-04-16 00:11:11 +01:00
Rubenlagus
64b0be3154 Api version 4.2 2019-04-08 01:43:46 +01:00
Bernhard
506329e51a apply the minor changes requested in the pull request 2019-04-07 15:54:43 +02:00
Ruben Bermudez
d78acbdda0 Update poms and fix guava 2019-04-05 00:52:35 +01:00
Ruben Bermudez
3861b29b25 Improves #498 2019-04-03 02:24:15 +01:00
Ruben Bermudez
140574f54e Closes #578 2019-04-03 02:18:53 +01:00
Ruben Bermudez
d20cf9d4bd
Merge pull request #575 from taksoidet/master
Change stream in FAQ
2019-04-03 01:34:28 +01:00
Ruben Bermudez
16308c1be7
Merge pull request #597 from Clevero/dev
Fix typo in ability gradle instruction
2019-04-03 01:33:32 +01:00
Ruben Bermudez
a4c7d73f0a
Merge pull request #583 from robatipoor/master
add hasAudio and hasVoice method in Message class
2019-04-03 01:20:31 +01:00
Bernhard
a80ee81ba4 back up maps in the database as a collection of key value pairs
this allows all objects to be used as keys and not only strings
2019-03-29 15:58:35 +01:00
Clevero
0c72769976
fix typo in ability gradle instruction 2019-03-27 23:44:56 +01:00
chase
ed18c2c265 Add test for TelegramFileDownloader 2019-03-26 22:39:54 +01:00
chase
ef0d78eb4c Move TelegramFileDownloader and finish implementation
The fileDownloader now offers all necessary methods. Still needs some
polishing
2019-03-26 22:39:36 +01:00
chase
a317001d4b Add TelegramFileDownloader
Add a seperate file downloader to look at the download code more
isolated. Might be merged back into DefaultAbsSender later
2019-03-24 20:22:55 +01:00
robatioor
21a308079c add method hasAudio and method hasVoice to Message class 2019-03-02 16:44:12 +03:30
taksoidet
b4f63905c5
Update FAQ.md 2019-01-31 02:44:52 +03:00
taksoidet
935b75d685
Change stream 2019-01-31 02:33:30 +03:00
Ruben Bermudez
b099cb6f4e
Merge pull request #573 from rubenlagus/dev
Dev
2019-01-27 23:41:38 +00:00
Rubenlagus
1883320445 Update versions 2019-01-27 23:37:46 +00:00
Ruben Bermudez
261384b9d7 Update more unsafe dependencies 2019-01-27 23:36:57 +00:00
Ruben Bermudez
da5e7a776f
Merge pull request #572 from rubenlagus/dev
Dev
2019-01-27 23:33:43 +00:00
Rubenlagus
f96c91cd48 Update wiki 2019-01-27 23:26:38 +00:00
Ruben Bermudez
390808c804
Merge pull request #569 from Chase22/FIX/annotation-for-bot-session
Use stream to scan for annotations and to execute the methods
2019-01-27 22:57:34 +00:00
Ruben Bermudez
18412d9c98
Merge pull request #545 from romangraef/dev
Extensions for AbilityBots
2019-01-27 22:54:54 +00:00
chase
05c92d4037 Use stream to scan for annotations and to execute the methods 2019-01-19 09:49:44 +01:00
Roman Gräf
d37ea25fa6
Merge pull request #1 from addo37/dev
Consolidated ExtensionTest objects and minor refactoring
2019-01-16 17:34:50 +01:00
Ruben Bermudez
61d1eb3319
Merge pull request #566 from addo37/additional-examples
Added additional examples section for AbillityBot wiki, addresses #482
2019-01-04 18:39:27 +01:00
Abbas Abou Daya
c1597058f4 Add additional examples to sidebar 2019-01-03 06:12:06 +02:00
Abbas Abou Daya
fed36f60f8 Added additional examples section for AbillityBot wiki, addresses #482 2019-01-03 06:09:55 +02:00
Abbas Abou Daya
2f05976d0a Consolidated ExtensionTest objects and minor refactoring 2019-01-03 05:46:09 +02:00
Rubenlagus
f998cc2509 Update versions 2019-01-02 16:19:43 +01:00
Rubenlagus
e203abfab5 Update versions 2019-01-02 16:12:05 +01:00
Ruben Bermudez
293d903083 Fix #563 2019-01-02 15:56:45 +01:00
Ruben Bermudez
702285e631 Merge branch 'aNNiMON-typo-fix' into dev 2019-01-02 15:16:52 +01:00
Rubenlagus
97e0aaa10f Fix inheritance of deprecated methods 2019-01-02 15:16:03 +01:00
Ruben Bermudez
885a2b69ff Merge branch 'typo-fix' of https://github.com/aNNiMON/TelegramBots into aNNiMON-typo-fix 2019-01-02 15:04:53 +01:00
Ruben Bermudez
e746a99188 Merge branch 'aNNiMON-timeperiod-improvements' into dev 2019-01-02 15:03:10 +01:00
Ruben Bermudez
4ea1651b28 Merge branch 'timeperiod-improvements' of https://github.com/aNNiMON/TelegramBots into aNNiMON-timeperiod-improvements 2019-01-02 15:00:28 +01:00
Ruben Bermudez
c06aa5344c Merge branch 'fbascheper-dev' into dev 2019-01-02 14:57:41 +01:00
Rubenlagus
880c8a2087 Fix guava dependency 2019-01-02 14:57:27 +01:00
Ruben Bermudez
8e8156646c Merge branch 'dev' of https://github.com/fbascheper/TelegramBots into fbascheper-dev 2019-01-02 14:55:47 +01:00
Ruben Bermudez
6b0a738dac
Merge pull request #565 from Chase22/FIX/Missing-Field-disableNotifications-in-SendMessge-toString
Add missing "disableNotification" field to toString() of SendMessage class
2019-01-02 14:20:09 +01:00
Ruben Bermudez
72464edd2b
Merge pull request #561 from Chase22/FEATURE/Annotation-for-bot-session
Add annotation to allow bot to get botSession
2019-01-02 14:19:46 +01:00
chase
4c86a3994d Add missing "disableNotification" field to toString() of SendMessage class 2019-01-02 14:04:01 +01:00
chase
ad195d8d59 Add annotation to allow bot to get botSession
A method annotated with @AfterBotRegistration will be invoked after
the bot is registered.

Optionally the method can have a parameter of
type BotSession to get passed the BotSession the bot was created with.
2018-12-19 22:19:22 +01:00
Victor
d091480f71 Ability to set time periods with Instant, Duration and ZonedDateTime 2018-12-12 19:18:09 +02:00
Victor
1099ea07a3 Fix typo in package 2018-12-12 17:28:25 +02:00
Victor
9a6682688e Merge branch 'dev' into typo-fix 2018-12-12 17:10:18 +02:00
Ruben Bermudez
f7bde89ec8
Merge pull request #544 from aNNiMON/maskposition-fix
Fix wrong type in Sticker maskPosition field
2018-12-11 00:50:25 +00:00
Ruben Bermudez
5fec33f9ea
Merge pull request #540 from aNNiMON/actiontype-videonote
Video note support in ActionType
2018-12-11 00:49:58 +00:00
Ruben Bermudez
c71c2a3c29
Merge pull request #539 from aNNiMON/switchpmparameter-fix
Allow hyphen character in switchPmParameter
2018-12-11 00:49:43 +00:00
Ruben Bermudez
d420625e7b
Merge pull request #538 from aNNiMON/stickerpositioninset-fix
Fix setStickerPositionInSet
2018-12-11 00:49:30 +00:00
Erik-Berndt Scheper
28740da0df Update dependencies to fix https://github.com/rubenlagus/TelegramBots/issues/547 2018-11-14 07:09:41 +01:00
romangraef
ee78c70164
removed more accidental configs 2018-11-10 22:35:50 +01:00
romangraef
ff01673f8f
removed accidental tests 2018-11-10 22:33:39 +01:00
romangraef
159de38b29
added tests for ability extension functionality 2018-11-10 22:07:41 +01:00
romangraef
a05988f5b4
merge upstream 2018-11-10 00:51:39 +01:00
romangraef
d515d8e44f
Added the extensions to abilities 2018-11-10 00:51:00 +01:00
Victor
4916e9f853 Fix wrong type in Sticker maskPosition field 2018-11-08 13:52:31 +02:00
Victor
ce05d68e81 Fix wrong class and method names 2018-11-07 12:34:50 +02:00
Victor
f133572a3a Fix test case 2018-11-06 16:40:50 +02:00
Victor
aa3e1fcf63 Fix typos and error messages 2018-11-06 16:35:55 +02:00
Victor
0f611bfd17 Add record/upload video note to ActionType 2018-11-06 14:33:50 +02:00
Victor
1e764dfaf2 Allow hyphen character in switchPmParameter 2018-11-06 14:12:13 +02:00
Victor
cc5d365aed Fix setStickerPositionInSet method 2018-11-06 13:14:21 +02:00
Ruben Bermudez
bdc8db3c87
Merge pull request #528 from d35h/master
Add addAll method for keyboardrow class
2018-10-31 20:14:34 +00:00
Ruben Bermudez
2c24058d5e
Merge pull request #526 from Chase22/master
Created Page on how to use Enviroment Variables for Tokens
2018-10-31 20:13:30 +00:00
Ruben Bermudez
ea6345b26f
Merge pull request #529 from Clevero/dev
change deprecated URL to MonsterDeveloper's book
2018-10-31 20:12:56 +00:00
Ruben Bermudez
7c33c8f454
Merge pull request #533 from Saberos/dev
Update Wiki FAQ Spring Boot starter
2018-10-31 20:12:35 +00:00
Ruben Bermudez
698a9eec76
Merge pull request #530 from Clevero/master
add example for ChatActions to FAQ
2018-10-31 20:12:15 +00:00
Stephan Groenewold
60d5634938 Update Wiki FAQ Spring Boot starter 2018-10-24 16:46:41 +02:00
Clevero
87fdde7fe1
Update FAQ.md 2018-10-17 09:54:24 +02:00
Clevero
4a244ce4da
change deprecated URL to MonsterDeveloper's book
The URL https://legacy.gitbook.com/book/monsterdeveloper/writing-telegram-bots-on-java/details seems to be deprecated

https://monsterdeveloper.gitbooks.io/writing-telegram-bots-on-java/content/
2018-10-17 08:56:39 +02:00
d35h
dc62f9ce63 Add tests 2018-10-14 22:11:04 +02:00
d35h
4efaa253b8 Add addAll method for keyboardrow 2018-10-14 21:45:49 +02:00
Ruben Bermudez
b4f710beaf
Merge pull request #524 from ezachateyskiy/patch-1
Fix wrong path for SendAnimation command
2018-09-28 22:33:23 +01:00
chase
77a3c635b5 Revert: Fix headers in Handling Bot Tokens page 2018-09-28 23:29:08 +02:00
chase
d7c5d28d45 Fix headers in Handling Bot Tokens page again 2018-09-28 22:14:00 +02:00
chase
8aa77f54eb Fix headers in Handling Bot Tokens page 2018-09-28 22:11:33 +02:00
chase
ce82e627a6 Created page on how to handle bot tokens 2018-09-28 22:09:48 +02:00
ezachateyskiy
584b9d5f98
Fix wrong path for SendAnimation command 2018-09-15 02:07:17 +03:00
Ruben Bermudez
6cb8b7905d
Merge pull request #519 from davyvanroy/patch-1
typo
2018-09-09 12:13:36 +01:00
Ruben Bermudez
ffc1f713dc
Merge pull request #518 from rubenlagus/dev
Dev
2018-08-30 19:29:25 +01:00
Davy Van Roy
1728a23b00
typo 2018-08-30 14:31:26 +02:00
Ruben Bermudez
01d3de4945 Update changelog 2018-08-29 21:36:11 +01:00
Ruben Bermudez
a4b7abce78 Update changelog 2018-08-21 21:29:33 +01:00
Ruben Bermudez
f415afb568 #Fix 507 2018-08-21 21:27:54 +01:00
Ruben Bermudez
3e5f12a4c4 Fix #512 2018-08-21 21:19:31 +01:00
Ruben Bermudez
7f8df47f0b Update version 4.1 2018-08-21 20:59:55 +01:00
Ruben Bermudez
27e0751b7e
Merge pull request #503 from rubenlagus/dev
Dev
2018-08-09 02:58:31 +02:00
Ruben Bermudez
b0bc5ea5b7 Update wiki 2018-08-09 02:51:41 +02:00
Ruben Bermudez
e52b7c48a7 Update version 2018-08-09 02:50:04 +02:00
Ruben Bermudez
2bc07613bb Closes #499 2018-08-09 02:49:48 +02:00
Ruben Bermudez
996a11064b Update changelog 2018-08-09 02:49:48 +02:00
Ruben Bermudez
df1cf5a43b
Merge pull request #497 from Clevero/patch-4
Update library version for gradle to 4.0
2018-07-27 20:18:48 +02:00
Clevero
542a93015f
Update library version for gradle to 4.0
Don't using gradle, so don't have tested if it's already available there
Just saw it while looking through
2018-07-27 18:16:42 +02:00
Ruben Bermudez
01765d07a8
Merge pull request #496 from rubenlagus/dev
Dev
2018-07-27 01:52:02 +02:00
Ruben Bermudez
ed1df4402b Merge branch 'addo37-webhook' into dev 2018-07-27 01:06:31 +02:00
Ruben Bermudez
4ea716cfea Merge branch 'webhook' of https://github.com/addo37/TelegramBots into addo37-webhook
# Conflicts:
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramWebhookBot.java
2018-07-27 01:06:23 +02:00
Ruben Bermudez
7daebe1318 Merge branch 'webhook' of https://github.com/addo37/TelegramBots into addo37-webhook
# Conflicts:
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramWebhookBot.java
2018-07-27 01:04:51 +02:00
Ruben Bermudez
904dc39a97 Fix docs 2018-07-27 00:51:45 +02:00
Ruben Bermudez
777da2eccd Merge branch 'EgorBochkarev-dev' into dev 2018-07-27 00:49:02 +02:00
Ruben Bermudez
1c1f340dc9 Update to version 4.0.0 2018-07-27 00:48:40 +02:00
Ruben Bermudez
fe10bc8c03 Merge branch 'dev' of https://github.com/EgorBochkarev/TelegramBots into EgorBochkarev-dev 2018-07-27 00:37:32 +02:00
Ruben Bermudez
a75ca5f08d Version 4.0.0 2018-07-27 00:27:26 +02:00
Ruben Bermudez
6c24155bdf
Merge pull request #491 from ChmilevFA/java9-support
Java9+ support
2018-07-26 20:01:22 +02:00
chmilevfa
753d1d2ed8 Fixes after merge 2018-07-16 19:54:26 +02:00
chmilevfa
0357fdb137 Properly update version to 3.6.2 2018-07-16 19:43:45 +02:00
chmilevfa
ad3dbfdd58 Merge branch 'dev' into java9-support
# Conflicts:
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/EndUser.java
#	telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/MessageContext.java
#	telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.java
#	telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultBotOptions.java
2018-07-16 19:37:30 +02:00
Chmliev Fedor
036032a39c
Merge pull request #1 from rubenlagus/dev
Dev
2018-07-16 18:53:26 +02:00
Ruben Bermudez
dd5d14df6c
Merge pull request #451 from AzZureman/proxy-feature
Socks proxy feature
2018-07-16 13:28:44 +03:00
Ruben Bermudez
1356a27e1d
Merge pull request #486 from rubenlagus/rubenlagus-jetbrains
Rubenlagus jetbrains
2018-07-16 13:20:39 +03:00
Ruben Bermudez
1e90c0c0e4
Merge pull request #477 from luksrn/spring_boot_starter_fix_and_improving
Spring Boot autoconfiguration improvements.
2018-07-16 13:18:53 +03:00
chmilevfa
c0b903e689 telegrambots-extensions java 9 supported 2018-07-08 01:46:26 +02:00
chmilevfa
db9ae4f586 telegrambots-meta java 9 supported 2018-07-08 01:41:21 +02:00
Ruben Bermudez
fa4193617a
Update README.md 2018-06-21 16:41:16 +02:00
Ruben Bermudez
52d385fe86
Add files via upload 2018-06-21 16:22:34 +02:00
Lucas Oliveira
493568649a Spring Boot autoconfiguration improvements.
Unit test refactoring, now with autoconfiguration integration testing.
Bug fix that did not initialize the bots, even with the deprecated @EnableTelegramBots
* [Removed - If users wish to disable, just inform telegrambots.enabled = false].
Upgrade Spring boot 2.0.2
2018-06-09 15:06:38 -03:00
egorbochkarev
5f64fb90ed Support callback query 2018-06-08 23:12:34 +03:00
egorbochkarev
80d4a65c30 Add TelegramLongPollingSessionBot with session support 2018-06-07 20:43:41 +03:00
Abbas Abou Daya
e1e829151b Remove anonymous bot instantiation in ABs, refactor webhook functions 2018-06-04 04:47:21 -04:00
Ruben Bermudez
c4e13e25b3
Merge pull request #470 from addo37/enhance-db
Enhance DBContext with a single variable getter, pushes forward #425
2018-06-03 14:55:36 +02:00
Abbas Abou Daya
b5bfaced7e Separate AbilityBot logic and implement Webhook 2018-05-28 18:12:31 -04:00
Abbas Abou Daya
afebcaeb76 Fix javadoc 2018-05-27 04:20:04 -04:00
Abbas Abou Daya
714857555d Enhance DBContext with a single variable getter, pushes forward #425 2018-05-27 04:16:19 -04:00
Ruben Bermudez
ce1b0402ac
Merge pull request #466 from addo37/dev
Expose abilities and replies, add report command and reformat /commands, closes #436
2018-05-26 12:30:14 +02:00
Abbas Abou Daya
7bee69c68d Fix wiki doc and javadoc 2018-05-23 19:11:26 -04:00
Abbas Abou Daya
889fd46834 Expose abilities and replies, add report command and reformat /commands, closes #436 2018-05-23 18:59:03 -04:00
Ruben Bermudez
b0b2504e01
Merge pull request #449 from davioooh/abilities-i18n
Abilities i18n
2018-05-23 20:03:59 +02:00
Ruben Bermudez
05e65309d5
Merge pull request #464 from Chase22/dev
Added Documentation for deprecated methods
2018-05-23 20:02:07 +02:00
Chase
f78fd046e9 Added Documentation for deprecated methods 2018-05-23 09:17:45 +02:00
Abbas Abou Daya
7d216a1fae Fix todos, all ability bot messages are now properly localized 2018-05-22 03:12:47 -04:00
Abbas Abou Daya
a819d7f178 Cleanup and refactoring 2018-05-21 07:01:45 -04:00
Abbas Abou Daya
308aec54c2 Rebase on dev 2018-05-21 06:05:44 -04:00
Abbas Abou Daya
8729271d34 Fix tests 2018-05-21 05:57:43 -04:00
davioooh
9347d064c1 Refactor localized messages retrievement 2018-05-21 05:57:43 -04:00
davioooh
ab86947cc3 Fix issues
- export message codes
- standardize message codes
- format/spacing
2018-05-21 05:57:43 -04:00
davioooh
bd7092921a Remove locale retrieval to avoid exception 2018-05-21 05:57:43 -04:00
davioooh
aecfe7693a Add basic unit tests 2018-05-21 05:57:43 -04:00
davioooh
c8f1c69fb2 Complete externalization of messages 2018-05-21 05:57:43 -04:00
davioooh
7ff5be3a72 Refactor localized message helper method 2018-05-21 05:57:43 -04:00
davioooh
97a4c0031b Refactor EndUser to serialize locale 2018-05-21 05:57:43 -04:00
davioooh
45ea5af127 Update default messages 2018-05-21 05:57:43 -04:00
davioooh
d77887fd2c Add basic internationalization support 2018-05-21 05:57:43 -04:00
Abbas Abou Daya
c713f9da48 Fix tests 2018-05-18 04:40:27 -04:00
Ruben Bermudez
bcb9d7505b
Merge pull request #452 from addo37/dev
Fix group update check for GROUP_ADMIN
2018-05-17 18:37:22 +02:00
Ruben Bermudez
e2bdf7ff73
Merge pull request #454 from addo37/augment-user
Add detailed constructor, equals and hashCode to User object
2018-05-17 18:36:53 +02:00
Ruben Bermudez
570895ac11
Merge pull request #456 from valinz/spring-boot-start-config
Spring boot start config
2018-05-17 18:36:31 +02:00
Ruben Bermudez
c4f0500d25
Merge pull request #460 from azhohov/dev
fix field name pending_update_count of WebhookInfo
2018-05-17 18:36:11 +02:00
Alexey Zhohov
9357883cf3 fix field name pending_update_count of WebhookInfo 2018-05-17 17:21:55 +03:00
zhaoyi
d0651e60b4 Add unit test for spring boot start configuration 2018-05-14 15:17:07 +08:00
davioooh
31f5c64058 Refactor localized messages retrievement 2018-05-13 11:16:51 +02:00
zhaoyi
dcc4f29ddd Make longPollingBots or webHookBots in spring boot configuration 2018-05-12 14:52:57 +08:00
Abbas Abou Daya
9a436910ff Add detailed constructor, equals and hashCode to User object 2018-05-10 01:27:57 +03:00
davioooh
288a4ab17a Fix issues
- export message codes
- standardize message codes
- format/spacing
2018-05-08 11:10:37 +02:00
Abbas Abou Daya
677b401fc6 Check if the message is a group or super group message for GROUP_ADMIN, #390 2018-05-08 03:39:21 +03:00
Abbas Abou Daya
b9da279fdf Remove group update check for GROUP_ADMIN
GetChatAdministrators only returns admins when there IS a group, there's no need to explicitly check for groups here.
2018-05-08 03:18:34 +03:00
AzZu
ff73a43337 Update version to 3.6.2
Cause new version does not support the old methods of enabling a proxy
2018-05-06 23:32:33 +03:00
AzZu
0c1cb1e05a update proxy usage page on wiki 2018-05-06 23:32:18 +03:00
AzZu
6087b37483 remove unused imports 2018-05-06 23:32:06 +03:00
AzZu
346a9dc045 added proxy feature
So now, it's support http, socks4 and socks5 proxys
2018-05-06 23:31:48 +03:00
AzZu
95dd4e3a3f remove http proxy feature
Cause apache.http package does not provide socks proxy feature
2018-05-06 23:31:33 +03:00
davioooh
02fbb67b59 Remove locale retrieval to avoid exception 2018-05-04 18:16:04 +02:00
davioooh
c97476f05d Add basic unit tests 2018-05-04 18:15:02 +02:00
davioooh
494f21040f Complete externalization of messages 2018-05-02 12:19:12 +02:00
davioooh
f64d556610 Refactor localized message helper method 2018-04-30 19:50:44 +02:00
davioooh
16fa704a17 Refactor EndUser to serialize locale 2018-04-30 15:39:32 +02:00
davioooh
9f1aa6664f Update default messages 2018-04-30 14:33:10 +02:00
davioooh
49cc3fb896 Add basic internationalization support 2018-04-27 18:00:04 +02:00
Ruben Bermudez
431d945486
Merge pull request #441 from rubenlagus/dev
Dev
2018-04-20 13:07:36 +02:00
Ruben Bermudez
28a9cf1fdb Version 3.6.1 2018-04-20 01:44:04 +02:00
Ruben Bermudez
afd84cde05
Merge pull request #395 from MarijnKoesen/add_ibot_interface
Add IBotCommand to create Commands that get whole Message
2018-04-19 21:36:56 +02:00
Ruben Bermudez
1aa886b6e3
Merge pull request #437 from homich1991/dev
Added spring boot starter module
2018-04-19 21:00:25 +02:00
Ruben Bermudez
a276b0bf35 Merge branch 'bvn13-dev' into dev 2018-04-19 20:57:38 +02:00
Ruben Bermudez
9733f35ef2 Merge branch 'dev' of https://github.com/bvn13/TelegramBots into bvn13-dev 2018-04-19 20:52:08 +02:00
Ruben Bermudez
1bb72980f8 Merge branch 'addo37-dev' into dev 2018-04-19 20:50:58 +02:00
Ruben Bermudez
888cc7b82a Merge branch 'dev' of https://github.com/addo37/TelegramBots into addo37-dev 2018-04-19 20:50:35 +02:00
Ruben Bermudez
25d09fcb99
Merge pull request #387 from gekoramy/dev
Fixed Bot API methods
2018-04-19 20:45:48 +02:00
Ruben Bermudez
9f26a60e27
Merge pull request #391 from Baspla/patch-1
Update Using-Replies.md
2018-04-19 20:45:16 +02:00
Vyacheslav N. Boyko
2930f5091b updating wiki with page containing instance of http proxy using 2018-04-19 15:13:40 +03:00
Roman_Meerson
01e046632c Added FAQ 2018-04-19 15:51:30 +04:00
Roman_Meerson
6b76e79f4d Added spring boot starter module 2018-04-17 14:01:48 +04:00
Vyacheslav N. Boyko
28f80e1bcd reworked proxy using 2018-04-17 11:46:48 +03:00
Vyacheslav N. Boyko
b9c32c55d8 implemented authorization via http proxy 2018-04-16 18:01:00 +03:00
Vyacheslav N. Boyko
378c8aaddc implemented authorization via http proxy 2018-04-16 17:58:51 +03:00
Ruben Bermudez
9adb3921e5
Merge pull request #427 from Relecto/dev
Added hasVideo() method to Message
2018-04-02 20:22:49 +02:00
Relecto
3534fa789d Added hasVideo() method to Message 2018-04-02 22:38:29 +06:00
Ruben Bermudez
7a13821e51 Closes #413 2018-04-01 19:28:40 +02:00
Ruben Bermudez
e53f0bd9ef Closes #368 2018-04-01 12:49:39 +02:00
Ruben Bermudez
025be6eefd Closes #364 2018-04-01 12:40:58 +02:00
Ruben Bermudez
b057a98ada Closes #361 2018-04-01 12:37:28 +02:00
Ruben Bermudez
7a83b45a91 Closes #319 2018-04-01 12:20:55 +02:00
Marijn Koesen
422b26d661
Add IBotCommand to create Commands that get whole Message 2018-02-27 06:50:27 +01:00
Baspla
adced55fdc
Update Using-Replies.md
The Consumer has the variable name of "action" not "upd".
2018-02-23 20:23:04 +01:00
gekoramy
2bc7ebc055 Fixed Bot API methods
## AnswerCallbackQuery
`setCacheTime(...)`  now returns `AnswerCallbackQuery`

## EditMessageLiveLocation
`setLongitud(...)`  corrected requireNonNull check
`setLatitude(...)`  corrected requireNonNull check

## Suggestion
Removed `Objects.requireNonNull(chatId)` check from methods which can have `inline_message_id` instead of `chat_id`
2018-02-15 15:19:34 +01:00
Ruben Bermudez
7e369ea2fa
Merge pull request #386 from rubenlagus/dev
Dev
2018-02-14 20:59:10 +01:00
Ruben Bermudez
6db6c4bf0d Update version 3.6 2018-02-14 20:36:22 +01:00
Abbas Abou Daya
f0f616c63c Fix abilities Maven dependency typo 2018-02-13 21:24:20 -05:00
Ruben Bermudez
014f17818b
Merge pull request #340 from myplacedk/SendMessageEqualsHashcode
#325 Implement equals() and hashCode() in SendMessage
2018-02-13 21:56:49 +01:00
Ruben Bermudez
09c2b36559 Merge branch 'myplacedk-SocketException' into dev 2018-02-13 21:51:38 +01:00
Ruben Bermudez
7d6fdcbf69 Merge branch 'SocketException' of https://github.com/myplacedk/TelegramBots into myplacedk-SocketException 2018-02-13 21:51:07 +01:00
Ruben Bermudez
488711a249 Merge branch 'Chase22-dev' into dev 2018-02-13 21:47:53 +01:00
Ruben Bermudez
84a2189439 Merge branch 'dev' of https://github.com/Chase22/TelegramBots into Chase22-dev 2018-02-13 21:47:25 +01:00
Ruben Bermudez
cac157ff1d
Merge pull request #376 from addo37/dev
Make abilities case-insensitive, fix msg markdown bug and add group-admin privacy
2018-02-13 21:39:09 +01:00
Ruben Bermudez
7ed25b2243
Merge pull request #342 from javver/dev
#336 Shutdown polling bot executor service on close
2018-02-13 21:34:52 +01:00
Ruben Bermudez
1d7d2f81ac
Merge pull request #377 from gorshkov/dev
Typo
2018-02-13 21:33:59 +01:00
Ruben Bermudez
65d1454ffc
Merge pull request #379 from eitland/patch-1
Change getBotUsermane() to getBotUsername()
2018-02-13 21:33:39 +01:00
Erik VĂĄrdal Itland
eba3fe737a
Change getBotUsermane() to getBotUsername()
Fix minor typo
2018-02-10 21:02:29 +01:00
Evgeny Gorshkov
c2338c564b Typo 2018-02-08 15:30:24 +07:00
Abbas Abou Daya
dc9f34196e Make abilities case-insensitive, fix msg markdown bug and add group-admin privacy 2018-02-04 03:23:41 -05:00
Chase
f687ca1ceb changed getDescription() to toString() in getManText(BotCommand command) 2017-12-21 15:23:15 +01:00
Chase
6e4c785c9d New command that provides help like the Linux man command 2017-12-21 15:10:57 +01:00
Javier Cardona
a1a8c460e2 #336 Added shutdown call for execution service to default long polling bot 2017-11-21 13:14:12 -06:00
Niels Ulrik Andersen
19b0582322 #325 Implement equals() and hashCode() in reply keyboards 2017-11-20 13:29:17 +01:00
Niels Ulrik Andersen
e2aed55b11 Clean up a bit in DefaultBotSession 2017-11-19 16:59:23 +01:00
Niels Ulrik Andersen
4cf29bd75f Clean up some thread usage, seems to close #168. 2017-11-19 12:41:38 +01:00
Niels Ulrik Andersen
4121a6f8c9 #325 Implement equals() and hashCode() in SendMessage 2017-11-19 10:44:05 +01:00
Ruben Bermudez
6b17b2c9d8
Merge pull request #339 from rubenlagus/dev
Dev
2017-11-17 15:54:29 +01:00
Ruben Bermudez
49c0fef3c5 Update version 3.5 2017-11-17 15:47:22 +01:00
Ruben Bermudez
083f020a35 Closes #335 2017-11-15 18:39:27 +01:00
Ruben Bermudez
91eaccd8ad
Merge pull request #333 from ng1905/dev
Do not log stack trace when stopping bot
2017-11-15 18:34:55 +01:00
Ruben Bermudez
470b13bc5f
Merge pull request #337 from malta895/dev
Added processInvalidCommandUpdate()
2017-11-15 18:34:25 +01:00
Ruben Bermudez
41be63aed0
Merge pull request #324 from addo37/dev
Update AbilityBot and write tutorial
2017-11-15 18:32:49 +01:00
Ruben Bermudez
8cbe3cbcb5
Merge pull request #326 from Clevero/patch-1
add "no implementation for [...]" to Errors Handling
2017-11-15 18:31:56 +01:00
Ruben Bermudez
cd99c85884
Merge pull request #327 from Clevero/patch-2
update FAQ
2017-11-15 18:31:23 +01:00
Ruben Bermudez
135602b5b7
Merge pull request #330 from vadimgoroshevsky/dev
Add DefaultBotCommand, with message ID in execute method
2017-11-15 18:30:50 +01:00
malta895
ff1ef8586f Added processInvalidCommandUpdate(), to handle invalid command sent by user 2017-11-15 11:44:16 +01:00
ng1905
0132f25178
Fix if statement
Missing ')'
2017-11-06 07:19:11 +01:00
ng1905
216e910411
Do not log stack trace when stopping bot
When stopping a LongPollingBot, no exception should be logged due to closing the socket.
2017-11-06 07:11:22 +01:00
Abbas Abou Daya
066ad7f675 Added information about fast DB accessors 2017-11-05 11:31:46 -05:00
Vadim Goroshevsky
27d6d930cf
Add DefaultBotCommand, with message ID in execute method 2017-11-02 21:51:44 +02:00
Abbas Abou Daya
b53d252fb4 Update FAQ on the global flag 2017-10-31 23:08:23 -04:00
Abbas Abou Daya
1f5706b525 Change global flag to always return true and make ability flags as predicates 2017-10-31 23:06:03 -04:00
Clevero
3df2a35a46
Update FAQ.md 2017-10-31 20:31:29 +01:00
Clevero
64390bc6e7
update FAQ.md
- change getFile() to execute()
- add sendMessage is deprecated explanation
- add links for webhook examples
- add link to MonsterDevelopers book for deeper instructions for deploying on DigitalOcean
2017-10-31 20:29:21 +01:00
Clevero
ebf93add88
add no implementation for [...]
add "No implementation for org.telegram.telegrambots.generics.BotSession was bound" error to "Errors-Handling"
2017-10-31 19:50:32 +01:00
Ruben Bermudez
10d0934439
Merge pull request #320 from Clevero/dev
change sendMessage to execute in "Getting Started"
2017-10-31 11:06:10 +01:00
Abbas Abou Daya
3414d8c9f5 Added advanced use case FAQ 2017-10-30 21:45:08 -04:00
Abbas Abou Daya
7689a7bcca Proofreading 2017-10-30 21:32:34 -04:00
Abbas Abou Daya
c67d68dcf7 Tutorial fixes 2017-10-30 21:21:13 -04:00
Abbas Abou Daya
f5cbe8b953 Fix sidebar 2017-10-30 20:56:42 -04:00
Abbas Abou Daya
673506417c Write tutorial and implement SilentTest 2017-10-30 20:45:43 -04:00
Abbas Abou Daya
adddadfba5 Refreshed MessageSender and added a silent sender 2017-10-28 22:15:53 -04:00
Clevero
a05ec52f5b Update FAQ.md 2017-10-24 17:49:59 +02:00
Clevero
a5942de946 change sendMessage to execute in "Getting Started" 2017-10-24 17:09:57 +02:00
Ruben Bermudez
4d41ad8ba9 Merge pull request #316 from rubenlagus/dev
Dev
2017-10-11 20:08:44 +02:00
Ruben Bermudez
babb9d8be5 Update API version 3.4 2017-10-11 19:27:12 +02:00
Ruben Bermudez
9317c6f5a9 Merge pull request #284 from vantuz-subhuman/#269
#269 Fixed update queue polling order and added `TelegramBatchLongPollingBot`
2017-10-06 18:23:10 +02:00
Ruben Bermudez
193834e023 Merge pull request #309 from dschulz/dev
Use a regular expression to split command parameters
2017-10-06 18:20:20 +02:00
vantuz-subhuman
cb92eca883 #269: Deleted TelegramBatchLongPollingBot, added onUpdatesReceived method to basic LongPollingBot interface 2017-09-21 00:46:41 +03:00
Diego Schulz
4a9b0726e7 Update BotCommand.java 2017-09-18 15:39:45 -04:00
Diego Schulz
cf2632b6cc Use a regular expression to split parameters 2017-09-18 15:36:02 -04:00
Ruben Bermudez
d5c8a32361 Merge pull request #307 from rubenlagus/revert-305-dschulz-patch-1
Revert "Use a regular expression to split parameters for BotCommands"
2017-09-17 00:10:50 +02:00
Ruben Bermudez
c63117cdc1 Merge pull request #280 from McPringle/dev
Adding API context initialization to example code snippet
2017-09-17 00:07:02 +02:00
vsubhuman
0fb556518a #269 Extracted UpdatesSupplier insede DefaultBotSession and created tests 2017-08-03 13:29:02 +03:00
vsubhuman
fefe0cff05 #269 Added TelegramBatchLongPollingBotTest 2017-08-03 12:19:19 +03:00
vsubhuman
5741e4ba97 #269 Fixed update queue polling order and added TelegramBatchLongPollingBot 2017-08-03 00:30:51 +03:00
Marcus Fihlon
b83d77c78d
đź“ť Adding API context initialization to example code snippet 2017-07-25 14:44:41 +02:00
448 changed files with 23498 additions and 18105 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

117
.mvn/wrapper/MavenWrapperDownloader.java vendored Normal file
View File

@ -0,0 +1,117 @@
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.5";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}

2
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar

View File

@ -1,11 +1,18 @@
language: java
jdk:
- oraclejdk8
install: mvn install -Dgpg.skip
script: mvn clean compile test
- oraclejdk11
- oraclejdk-ea
- openjdk11
- openjdk-ea
install: mvn -q install -Dgpg.skip
script: mvn -q clean compile test
after_success:
- bash <(curl -s https://codecov.io/bash)
notifications:
webhooks:
secure: "jC7dK/x67ONWQoeLZg4HfW0mHhcjDerJjsLLkrbcpltiqAbw2p7XfY8Pk4zHoD72a+5o6WKu5WvYvZ4OdldnjP8Y6ZUbliQ5RG3olg3gFDoe0+sc3geeb4HRYVcdI20O0z4Bup/qO0ZihxPBc0D5IpHmFxlaqlZG0WeST4CicU8PNnBh6aX9/VMrwXhkMb2vfzmjmIhMbx/uK5+93bnk/vR5Uwu00/Yd2cTAAWMaqK1MRdtR0WLbxlUNsprEfCjYiH3n9XZnlKXs6cLC8EOU436Wx7aepiAszW0wWFMe/7nVqOqztrQiKNvL0qXYwlQf0BLechJdt458EopL9QCu687TNDFYvg1yERAmCRiaayYZcX3PbUSMr6H5Q+Odntjs3XKyzfgSqqlkgf/SAND5jny1/1uteVoplZmFXuZFIiK4H8Rl2ezy1/8pnbp+JD3YEfiA2NuRjlou1BZXyMhiqqVXbrJqk/tXF6yZSkDlYJfNsWzRCGfra4B6JjEvUP927chIFm1ii3dgNstXDo1evV46+OQQO4HKvMPdtU2FPvWpPlkTxnmpZRZjB+bjmybluJdWT3E+e1C3wm7YbRe3vporhpfNPlnod6M0G10y9CKzl9Fbcku6X1FtM+IoPO/aqZ8S4/CBZoYEuR/Nk6bcvsYouxtyIl6PSuF9E8YjpJE="
email: false
matrix:
allow_failures:
- jdk: openjdk-ea
- jdk: oraclejdk-ea

986
Bots.ipr
View File

@ -1,986 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="telegrambots" />
<module name="telegrambots-abilities" />
<module name="telegrambots-extensions" />
<module name="telegrambots-meta" />
</profile>
<profile name="Annotation profile for Bots" enabled="true">
<sourceOutputDir name="../telegrambots/target/generated-sources/annotations" />
<sourceTestOutputDir name="../telegrambots/target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="telegrambots (1)" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="Bots" target="1.5" />
<module name="telegrambots" target="1.8" />
<module name="telegrambots-abilities" target="1.8" />
<module name="telegrambots-extensions" target="1.8" />
<module name="telegrambots-meta" target="1.8" />
</bytecodeTargetLevel>
</component>
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
<file url="file://$PROJECT_DIR$/telegrambots" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/telegrambots-abilities" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/telegrambots-extensions" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/telegrambots-meta" charset="UTF-8" />
<file url="PROJECT" charset="UTF-8" />
</component>
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<option name="myLocal" value="true" />
<inspection_tool class="JSHint" enabled="true" level="ERROR" enabled_by_default="true" />
<inspection_tool class="JSLint" enabled="true" level="ERROR" enabled_by_default="true" />
</profile>
<version value="1.0" />
</component>
<component name="JSHintConfiguration" version="2.6.3" use-config-file="false">
<option asi="false" />
<option bitwise="false" />
<option boss="false" />
<option browser="true" />
<option camelcase="true" />
<option couch="false" />
<option curly="true" />
<option debug="false" />
<option devel="false" />
<option dojo="false" />
<option eqeqeq="true" />
<option eqnull="false" />
<option es3="false" />
<option esnext="false" />
<option evil="false" />
<option expr="false" />
<option forin="true" />
<option freeze="false" />
<option funcscope="false" />
<option gcl="false" />
<option globalstrict="false" />
<option immed="false" />
<option iterator="false" />
<option jquery="false" />
<option lastsemic="false" />
<option latedef="true" />
<option laxbreak="false" />
<option laxcomma="false" />
<option loopfunc="false" />
<option maxerr="50" />
<option maxlen="80" />
<option mootools="false" />
<option moz="false" />
<option multistr="false" />
<option newcap="false" />
<option noarg="true" />
<option node="false" />
<option noempty="true" />
<option nomen="false" />
<option nonbsp="true" />
<option nonew="true" />
<option nonstandard="false" />
<option notypeof="false" />
<option noyield="false" />
<option onevar="false" />
<option passfail="false" />
<option phantom="false" />
<option plusplus="false" />
<option proto="false" />
<option prototypejs="false" />
<option quotmark="false" />
<option rhino="false" />
<option scripturl="false" />
<option shadow="false" />
<option smarttabs="false" />
<option strict="true" />
<option sub="false" />
<option supernew="false" />
<option trailing="true" />
<option undef="true" />
<option unused="true" />
<option validthis="false" />
<option white="false" />
<option worker="false" />
<option wsh="false" />
<option yui="false" />
</component>
<component name="JSLintConfiguration" json="true">
<option maxerr="50" />
<option maxlen="80" />
<option white="true" />
</component>
<component name="JsBowerSettings">
<exe-path>/usr/local/bin/bower</exe-path>
<config-path />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="coroutinesWarn" value="false" />
</component>
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value />
</option>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="GoogleStyle" />
</component>
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Bots.iml" filepath="$PROJECT_DIR$/Bots.iml" />
<module fileurl="file://$PROJECT_DIR$/telegrambots/telegrambots.iml" filepath="$PROJECT_DIR$/telegrambots/telegrambots.iml" />
<module fileurl="file://$PROJECT_DIR$/telegrambots-abilities/telegrambots-abilities.iml" filepath="$PROJECT_DIR$/telegrambots-abilities/telegrambots-abilities.iml" />
<module fileurl="file://$PROJECT_DIR$/telegrambots-extensions/telegrambots-extensions.iml" filepath="$PROJECT_DIR$/telegrambots-extensions/telegrambots-extensions.iml" />
<module fileurl="file://$PROJECT_DIR$/telegrambots-meta/telegrambots-meta.iml" filepath="$PROJECT_DIR$/telegrambots-meta/telegrambots-meta.iml" />
</modules>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
<component name="PropertiesComponent">
<property name="GoToClass.includeLibraries" value="false" />
<property name="GoToClass.toSaveIncludeLibraries" value="false" />
<property name="GoToFile.includeJavaFiles" value="false" />
<property name="MemberChooser.sorted" value="false" />
<property name="MemberChooser.showClasses" value="true" />
<property name="MemberChooser.copyJavadoc" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$/.." />
<property name="options.lastSelected" value="File.Encoding" />
<property name="options.splitter.main.proportions" value="0.3" />
<property name="options.splitter.details.proportions" value="0.2" />
<property name="settings.editor.selected.configurable" value="configurable.group.appearance" />
<property name="settings.editor.splitter.proportion" value="0.2" />
</component>
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
<component name="libraryTable">
<library name="com.google.inject:guice:4.1.0" type="repository">
<properties maven-id="com.google.inject:guice:4.1.0" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/19.0/guava-19.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/19.0/guava-19.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: aopalliance:aopalliance:1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/aopalliance/aopalliance/1.0/aopalliance-1.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.8.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-annotations/2.8.0/jackson-annotations-2.8.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.core:jackson-core:2.8.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.8.7/jackson-core-2.8.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.8.7/jackson-core-2.8.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-core/2.8.7/jackson-core-2.8.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.core:jackson-databind:2.8.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.8.7/jackson-databind-2.8.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.8.7/jackson-databind-2.8.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/core/jackson-databind/2.8.7/jackson-databind-2.8.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.8.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-base/2.8.7/jackson-jaxrs-base-2.8.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-base/2.8.7/jackson-jaxrs-base-2.8.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-base/2.8.7/jackson-jaxrs-base-2.8.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.8.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-json-provider/2.8.7/jackson-jaxrs-json-provider-2.8.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-json-provider/2.8.7/jackson-jaxrs-json-provider-2.8.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/jaxrs/jackson-jaxrs-json-provider/2.8.7/jackson-jaxrs-json-provider-2.8.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.8.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/module/jackson-module-jaxb-annotations/2.8.7/jackson-module-jaxb-annotations-2.8.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/module/jackson-module-jaxb-annotations/2.8.7/jackson-module-jaxb-annotations-2.8.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/fasterxml/jackson/module/jackson-module-jaxb-annotations/2.8.7/jackson-module-jaxb-annotations-2.8.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.google.guava:guava:19.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/19.0/guava-19.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/19.0/guava-19.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/guava/guava/19.0/guava-19.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: com.google.inject:guice:4.1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/com/google/inject/guice/4.1.0/guice-4.1.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: commons-codec:commons-codec:1.9">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.9/commons-codec-1.9.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.9/commons-codec-1.9-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/commons-codec/commons-codec/1.9/commons-codec-1.9-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: commons-io:commons-io:2.5">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.5/commons-io-2.5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.5/commons-io-2.5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/commons-io/commons-io/2.5/commons-io-2.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: commons-logging:commons-logging:1.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.2/commons-logging-1.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.2/commons-logging-1.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/commons-logging/commons-logging/1.2/commons-logging-1.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.annotation:javax.annotation-api:1.2">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/annotation/javax.annotation-api/1.2/javax.annotation-api-1.2-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.inject:javax.inject:1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/inject/javax.inject/1/javax.inject-1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.servlet:javax.servlet-api:3.0.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.validation:validation-api:1.1.0.Final">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/validation/validation-api/1.1.0.Final/validation-api-1.1.0.Final-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: javax.ws.rs:javax.ws.rs-api:2.0.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/javax/ws/rs/javax.ws.rs-api/2.0.1/javax.ws.rs-api-2.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/javax/ws/rs/javax.ws.rs-api/2.0.1/javax.ws.rs-api-2.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/javax/ws/rs/javax.ws.rs-api/2.0.1/javax.ws.rs-api-2.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: junit:junit:4.11">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.11/junit-4.11-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: junit:junit:4.12">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/junit/junit/4.12/junit-4.12-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: net.jcip:jcip-annotations:1.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/net/jcip/jcip-annotations/1.0/jcip-annotations-1.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: net.jpountz.lz4:lz4:1.3.0">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/net/jpountz/lz4/lz4/1.3.0/lz4-1.3.0.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/net/jpountz/lz4/lz4/1.3.0/lz4-1.3.0-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/net/jpountz/lz4/lz4/1.3.0/lz4-1.3.0-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.apache.commons:commons-lang3:3.5">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/commons/commons-lang3/3.5/commons-lang3-3.5-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.apache.httpcomponents:httpclient:4.5.3">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpclient/4.5.3/httpclient-4.5.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.apache.httpcomponents:httpcore:4.4.6">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpcore/4.4.6/httpcore-4.4.6-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.apache.httpcomponents:httpmime:4.5.3">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpmime/4.5.3/httpmime-4.5.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpmime/4.5.3/httpmime-4.5.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/apache/httpcomponents/httpmime/4.5.3/httpmime-4.5.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.eclipse.collections:eclipse-collections-api:7.1.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-api/7.1.1/eclipse-collections-api-7.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-api/7.1.1/eclipse-collections-api-7.1.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-api/7.1.1/eclipse-collections-api-7.1.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.eclipse.collections:eclipse-collections-forkjoin:7.1.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-forkjoin/7.1.1/eclipse-collections-forkjoin-7.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-forkjoin/7.1.1/eclipse-collections-forkjoin-7.1.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections-forkjoin/7.1.1/eclipse-collections-forkjoin-7.1.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.eclipse.collections:eclipse-collections:7.1.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections/7.1.1/eclipse-collections-7.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections/7.1.1/eclipse-collections-7.1.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/collections/eclipse-collections/7.1.1/eclipse-collections-7.1.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.grizzly:grizzly-framework:2.3.28">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-framework/2.3.28/grizzly-framework-2.3.28.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-framework/2.3.28/grizzly-framework-2.3.28-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-framework/2.3.28/grizzly-framework-2.3.28-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.grizzly:grizzly-http-server:2.3.28">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-server/2.3.28/grizzly-http-server-2.3.28.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-server/2.3.28/grizzly-http-server-2.3.28-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-server/2.3.28/grizzly-http-server-2.3.28-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.grizzly:grizzly-http-servlet:2.3.28">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-servlet/2.3.28/grizzly-http-servlet-2.3.28.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-servlet/2.3.28/grizzly-http-servlet-2.3.28-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http-servlet/2.3.28/grizzly-http-servlet-2.3.28-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.grizzly:grizzly-http:2.3.28">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http/2.3.28/grizzly-http-2.3.28.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http/2.3.28/grizzly-http-2.3.28-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/grizzly/grizzly-http/2.3.28/grizzly-http-2.3.28-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2.external:aopalliance-repackaged:2.5.0-b32">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/aopalliance-repackaged/2.5.0-b32/aopalliance-repackaged-2.5.0-b32.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/aopalliance-repackaged/2.5.0-b32/aopalliance-repackaged-2.5.0-b32-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/aopalliance-repackaged/2.5.0-b32/aopalliance-repackaged-2.5.0-b32-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2.external:javax.inject:2.5.0-b32">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/javax.inject/2.5.0-b32/javax.inject-2.5.0-b32.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/javax.inject/2.5.0-b32/javax.inject-2.5.0-b32-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/external/javax.inject/2.5.0-b32/javax.inject-2.5.0-b32-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2:hk2-api:2.5.0-b32">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-api/2.5.0-b32/hk2-api-2.5.0-b32.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-api/2.5.0-b32/hk2-api-2.5.0-b32-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-api/2.5.0-b32/hk2-api-2.5.0-b32-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2:hk2-locator:2.5.0-b32">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-locator/2.5.0-b32/hk2-locator-2.5.0-b32.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-locator/2.5.0-b32/hk2-locator-2.5.0-b32-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-locator/2.5.0-b32/hk2-locator-2.5.0-b32-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2:hk2-utils:2.5.0-b32">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-utils/2.5.0-b32/hk2-utils-2.5.0-b32.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-utils/2.5.0-b32/hk2-utils-2.5.0-b32-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/hk2-utils/2.5.0-b32/hk2-utils-2.5.0-b32-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.hk2:osgi-resource-locator:1.0.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/osgi-resource-locator/1.0.1/osgi-resource-locator-1.0.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/osgi-resource-locator/1.0.1/osgi-resource-locator-1.0.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/hk2/osgi-resource-locator/1.0.1/osgi-resource-locator-1.0.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.bundles.repackaged:jersey-guava:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/bundles/repackaged/jersey-guava/2.25.1/jersey-guava-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/bundles/repackaged/jersey-guava/2.25.1/jersey-guava-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/bundles/repackaged/jersey-guava/2.25.1/jersey-guava-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.containers:jersey-container-grizzly2-http:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-http/2.25.1/jersey-container-grizzly2-http-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-http/2.25.1/jersey-container-grizzly2-http-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-http/2.25.1/jersey-container-grizzly2-http-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.containers:jersey-container-grizzly2-servlet:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-servlet/2.25.1/jersey-container-grizzly2-servlet-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-servlet/2.25.1/jersey-container-grizzly2-servlet-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-grizzly2-servlet/2.25.1/jersey-container-grizzly2-servlet-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.containers:jersey-container-servlet-core:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet-core/2.25.1/jersey-container-servlet-core-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet-core/2.25.1/jersey-container-servlet-core-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet-core/2.25.1/jersey-container-servlet-core-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.containers:jersey-container-servlet:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet/2.25.1/jersey-container-servlet-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet/2.25.1/jersey-container-servlet-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/containers/jersey-container-servlet/2.25.1/jersey-container-servlet-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.core:jersey-client:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-client/2.25.1/jersey-client-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-client/2.25.1/jersey-client-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-client/2.25.1/jersey-client-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.core:jersey-common:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-common/2.25.1/jersey-common-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-common/2.25.1/jersey-common-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-common/2.25.1/jersey-common-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.core:jersey-server:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-server/2.25.1/jersey-server-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-server/2.25.1/jersey-server-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/core/jersey-server/2.25.1/jersey-server-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.ext:jersey-entity-filtering:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/ext/jersey-entity-filtering/2.25.1/jersey-entity-filtering-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/ext/jersey-entity-filtering/2.25.1/jersey-entity-filtering-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/ext/jersey-entity-filtering/2.25.1/jersey-entity-filtering-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.media:jersey-media-jaxb:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-jaxb/2.25.1/jersey-media-jaxb-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-jaxb/2.25.1/jersey-media-jaxb-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-jaxb/2.25.1/jersey-media-jaxb-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.media:jersey-media-json-jackson:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-json-jackson/2.25.1/jersey-media-json-jackson-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-json-jackson/2.25.1/jersey-media-json-jackson-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/media/jersey-media-json-jackson/2.25.1/jersey-media-json-jackson-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/providers/jersey-test-framework-provider-grizzly2/2.25.1/jersey-test-framework-provider-grizzly2-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/providers/jersey-test-framework-provider-grizzly2/2.25.1/jersey-test-framework-provider-grizzly2-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/providers/jersey-test-framework-provider-grizzly2/2.25.1/jersey-test-framework-provider-grizzly2-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.glassfish.jersey.test-framework:jersey-test-framework-core:2.25.1">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/jersey-test-framework-core/2.25.1/jersey-test-framework-core-2.25.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/jersey-test-framework-core/2.25.1/jersey-test-framework-core-2.25.1-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/jersey/test-framework/jersey-test-framework-core/2.25.1/jersey-test-framework-core-2.25.1-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.hamcrest:hamcrest-core:1.3">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.javassist:javassist:3.20.0-GA">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/javassist/javassist/3.20.0-GA/javassist-3.20.0-GA-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.jetbrains.kotlin:kotlin-runtime:1.0.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-runtime/1.0.7/kotlin-runtime-1.0.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-runtime/1.0.7/kotlin-runtime-1.0.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-runtime/1.0.7/kotlin-runtime-1.0.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.jetbrains.kotlin:kotlin-stdlib:1.0.7">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.0.7/kotlin-stdlib-1.0.7.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.0.7/kotlin-stdlib-1.0.7-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/1.0.7/kotlin-stdlib-1.0.7-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.json:json:20160810">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/json/json/20160810/json-20160810.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/json/json/20160810/json-20160810-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/json/json/20160810/json-20160810-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mapdb:elsa:3.0.0-M5">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/elsa/3.0.0-M5/elsa-3.0.0-M5.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/elsa/3.0.0-M5/elsa-3.0.0-M5-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/elsa/3.0.0-M5/elsa-3.0.0-M5-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mapdb:mapdb:3.0.4">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/mapdb/3.0.4/mapdb-3.0.4.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/mapdb/3.0.4/mapdb-3.0.4-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mapdb/mapdb/3.0.4/mapdb-3.0.4-sources.jar!/" />
</SOURCES>
</library>
<library name="Maven: org.mockito:mockito-all:2.0.2-beta">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-all/2.0.2-beta/mockito-all-2.0.2-beta.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-all/2.0.2-beta/mockito-all-2.0.2-beta-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/mockito/mockito-all/2.0.2-beta/mockito-all-2.0.2-beta-sources.jar!/" />
</SOURCES>
</library>
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

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,32 +27,31 @@ Just import add the library to your project with one of these options:
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>3.3</version>
<version>5.0.1</version>
</dependency>
```
```gradle
compile "org.telegram:telegrambots:3.3"
compile "org.telegram:telegrambots:5.0.1"
```
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/3.3)
3. Download the jar(including all dependencies) from [here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.3)
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`.
If you like to use Webhook, extend `org.telegram.telegrambots.bots.TelegramWebhookBot`
Once done, you just need to create a `org.telegram.telegrambots.TelegramBotsApi`and register your bots:
Once done, you just need to create a `org.telegram.telegrambots.meta.TelegramBotsApi`and register your bots:
```java
// Example taken from https://github.com/rubenlagus/TelegramBotsExample
public class Main {
public static void main(String[] args) {
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
try {
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
telegramBotsApi.registerBot(new ChannelHandlers());
telegramBotsApi.registerBot(new DirectionsHandlers());
telegramBotsApi.registerBot(new RaeHandlers());
@ -93,6 +92,12 @@ This library use [Telegram bot API](https://core.telegram.org/bots), you can fin
## Questions or Suggestions
Feel free to create issues [here](https://github.com/rubenlagus/TelegramBots/issues) as you need or join the [chat](https://telegram.me/JavaBotsApi)
## Powered by Intellij
<p align="center">
<a href="https://www.jetbrains.com/?from=TelegramBots"><img src="jetbrains.png" width="75"></a>
</p>
## License
MIT License

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,5 +1,133 @@
### <a id="3.2"></a>3.2 ###
1. Support for Api Version [3.3](https://core.telegram.org/bots/api-changelog#july-21-2017)
### <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/)
3. Library is now built using [Java11](https://www.oracle.com/technetwork/java/javase/overview/index.html)
4. Updated dependencies to use last versions
5. Files can be downloaded into a stream. Allowing it to be processed immediately.
6. A java.io.File can be passed into the methods. The downloaded file is copied into that file instead of a temp file then (does not work with the async methods)
### <a id="4.3.1"></a>4.3.1 ###
1. Fix bug #625
2. Moved ApiResponse to different package, deprecated old one (will be removed in next mayor version)
3. Deprecated InputBotApiObject (It will be removed in next mayor update). And all usages moved to basic BotApiObject type.
4. Updated jackson dependency to avoid security bug
### <a id="4.3"></a>4.3 ###
1. Update to Api version [4.3](https://core.telegram.org/bots/api-changelog#may-31-2019)
2. Fixed: #615, #621
### <a id="4.2"></a>4.2 ###
1. Update to Api version [4.2](https://core.telegram.org/bots/api-changelog#april-14-2019)
2. Fixed: #498, #578
### <a id="4.1.2"></a>4.1.2 ###
1. Removed unsafe dependencies
2. Fix bugs: #535, #524, #563, #562 and #557
### <a id="4.1"></a>4.1 ###
1. Support for Api Version [4.1](https://core.telegram.org/bots/api-changelog#august-27-2018)
2. Fix #507 and #512
### <a id="4.0.1"></a>4.0.1 ###
1. Fix bug #499
### <a id="4.0.0"></a>4.0.0 ###
1. Support for Api Version [4.0](https://core.telegram.org/bots/api-changelog#july-26-2018)
2. Abilities: Internationalization
3. Socks 5 support
4. Improved spring boot start configuration
5. Removed previously deprecated methods
6. Support usage in Java 9 (library is still using java 8)
7. Added chat-session bot module
**[[How to update to version 4.0.0|How-To-Update#4.0.0]]**
### <a id="3.6.1"></a>3.6.1 ###
1. Support for proxy connections
2. New module for Spring
3. Bug fixing
### <a id="3.6"></a>3.6 ###
1. Support for Api Version [3.6](https://core.telegram.org/bots/api-changelog#february-13-2018)
2. Bug fixing and other improvements
### <a id="3.5"></a>3.5 ###
1. Support for Api Version [3.5](https://core.telegram.org/bots/api-changelog#november-17-2017)
2. Bug fixing: #168, #329 and #335
3. Added processInvalidCommandUpdate (#337)
4. AbilitiyBot update and tutorial (#324)
5. Add DefaultBotCommand with message ID (#330)
6. New wiki content (#326 and #327)
### <a id="3.4"></a>3.4 ###
1. Support for Api Version [3.4](https://core.telegram.org/bots/api-changelog#october-11-2017)
2. Use regular expressions to split parameters in `TelegramLongPollingCommandBot` (#309)
3. Option to handle bunch of updates at a time via `onUpdatesReceived` in `TelegramLongPollingBot` (#284)
4. Fix characters encoding (#275)
### <a id="3.3"></a>3.3 ###
1. Support for Api Version [3.3](https://core.telegram.org/bots/api-changelog#august-23-2017)
### <a id="3.2"></a>3.2 ###

View File

@ -1,5 +1,6 @@
* [Terminated by other long poll or webhook](#terminted_by_other)
* ["No implementation for org.telegram.telegrambots.meta.generics.BotSession was bound"](#no_implementation_was_bound)
## <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.
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.

View File

@ -1,10 +1,13 @@
* [How to get picture?](#how_to_get_picture)
* [How to get picture?](#how_to_get_picture)
* [How to display ChatActions like "typing" or "recording a voice message"?](#how_to_sendchataction)
* [How to send photos?](#how_to_send_photos)
* [How do I send photos by file_id?](#how_to_send_photos_file_id)
* [How to use custom keyboards?](#how_to_use_custom_keyboards)
* [How can I run my bot?](#how_to_host)
* [How can I run my bot?](#how_to_host)
* [How can I compile my project?](#how_to_compile)
* [Method ```sendMessage()``` (or other) is deprecated, what should I do?](#sendmessage_deprecated)
* [Is there any example for WebHook?](#example_webhook)
* [How to use spring boot starter?](#spring_boot_starter)
## <a id="how_to_get_picture"></a>How to download photo? ##
@ -19,9 +22,7 @@ public PhotoSize getPhoto(Update update) {
// We fetch the bigger photo
return photos.stream()
.sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
.findFirst()
.orElse(null);
.max(Comparator.comparing(PhotoSize::getFileSize)).orElse(null);
}
// Return null if not found
@ -42,8 +43,8 @@ public String getFilePath(PhotoSize photo) {
GetFile getFileMethod = new GetFile();
getFileMethod.setFileId(photo.getFileId());
try {
// We execute the method using AbsSender::getFile method.
File file = getFile(getFileMethod);
// We execute the method using AbsSender::execute method.
File file = execute(getFileMethod);
// We now have the file_path
return file.getFilePath();
} catch (TelegramApiException e) {
@ -72,6 +73,38 @@ public java.io.File downloadPhotoByFilePath(String filePath) {
The returned `java.io.File` object will be your photo
## <a id="how_to_sendchataction"></a>How to display ChatActions like "typing" or "recording a voice message"? ##
Quick example here that is showing ChactActions for commands like "/type" or "/record_audio"
```java
if (update.hasMessage() && update.getMessage().hasText()) {
String text = update.getMessage().getText();
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);
}
try {
Boolean wasSuccessfull = execute(sendChatAction);
} catch (TelegramApiException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
```
## <a id="how_to_send_photos"></a>How to send photos? ##
There are several method to send a photo to an user using `sendPhoto` method: With a `file_id`, with an `url` or uploading the file. In this example, we assume that we already have the *chat_id* where we want to send the photo:
@ -83,10 +116,10 @@ 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
sendPhoto(sendPhotoRequest);
execute(sendPhotoRequest);
} catch (TelegramApiException e) {
e.printStackTrace();
}
@ -98,10 +131,10 @@ 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
sendPhoto(sendPhotoRequest);
execute(sendPhotoRequest);
} catch (TelegramApiException e) {
e.printStackTrace();
}
@ -112,11 +145,11 @@ 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
sendPhoto(sendPhotoRequest);
execute(sendPhotoRequest);
} catch (TelegramApiException e) {
e.printStackTrace();
}
@ -129,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 {
sendPhoto(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? ##
@ -186,7 +218,7 @@ Custom keyboards can be appended to messages using the `setReplyMarkup`. In this
try {
// Send the message
sendMessage(message);
execute(message);
} catch (TelegramApiException e) {
e.printStackTrace();
}
@ -199,8 +231,50 @@ You don't need to spend a lot of money into hosting your own telegram bot. Basic
1. Hosting on your own hardware. It can be a Mini-PC like a Raspberry Pi. The costs for the hardware (~35€) and annual costs for power (~7-8€) are low. Keep in mind that your internet connection might be limited and a Mini-Pc is not ideal for a large users base.
2. Run your bot in a Virtual Server/dedicated root server. There are many hosters out there that are providing cheap servers that fit your needs. The cheapest one should be openVZ-Containers or a KVM vServer. Example providers are [Hetzner](https://www.hetzner.de/ot/), [DigitalOcean](https://www.digitalocean.com/), (are providing systems that have a high availability but cost's a bit more) and [OVH](https://ovh.com)
For a deeper explanation for deploying your bot on DigitalOcean please see the [Lesson 5. Deploy your bot](https://monsterdeveloper.gitbooks.io/writing-telegram-bots-on-java/content/lesson-5.-deploy-your-bot.html) chapter in [MonsterDeveloper](https://github.com/MonsterDeveloper)'s book
## <a id="how_to_compile"></a>How can I compile my project? ##
This is just one way, how you can compile it (here with maven). The example below below is compiling the TelegramBotsExample repo.
[![asciicast](https://asciinema.org/a/4np9i2u9onuitkg287ism23kj.png)](https://asciinema.org/a/4np9i2u9onuitkg287ism23kj)
## <a id="sendmessage_deprecated"></a>Method ```sendMessage()``` (or other) is deprecated, what should I do? ##
Please use ```execute()``` instead.
Example:
```java
SendMessage message = new SendMessage();
//add chat id and text
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)
## <a id="spring_boot_starter"></a>How to use spring boot starter ##
----------
Your main spring boot class should look like this:
```java
@SpringBootApplication
public class YourApplicationMainClass {
public static void main(String[] args) {
SpringApplication.run(YourApplicationMainClass.class, args);
}
}
```
After that your bot will look like:
```java
//Standard Spring component annotation
@Component
public class YourBotClassName extends TelegramLongPollingBot {
//Bot body.
}
```
Also you could just implement LongPollingBot or WebHookBot interfaces. All this bots will be registered in context and connected to Telegram api.

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>3.3</version>
<version>5.0.1</version>
</dependency>
```
* With **Gradle**:
```groovy
compile group: 'org.telegram', name: 'telegrambots', version: '3.3'
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).
@ -53,7 +53,7 @@ Now that we have the library, we can start coding. There are few steps to follow
```
* `getBotUsermane()`: This method must always return your **Bot username**. May look like:
* `getBotUsername()`: This method must always return your **Bot username**. May look like:
```java
@ -88,7 +88,7 @@ Now that we have the library, we can start coding. There are few steps to follow
.setChatId(update.getMessage().getChatId())
.setText(update.getMessage().getText());
try {
sendMessage(message); // Call method to send the message
execute(message); // Call method to send the message
} catch (TelegramApiException e) {
e.printStackTrace();
}
@ -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();
@ -171,4 +147,4 @@ Now that we have the library, we can start coding. There are few steps to follow
```
3. **Play with your bot:**
Done, now you just need to run this `main` method and your Bot should start working.
Done, now you just need to run this `main` method and your Bot should start working.

View File

@ -0,0 +1,88 @@
* [Bot Token Dont's](#bot-token-donts)
* [Using Enviroment Variables](#using-environment-variables)
* [Setting Enviroment Variables](#setting-environment-variables)
* [Accessing Enviroment Variables](#accessing-enviroment-variables)
* [Using Command Line Arguments](#using-command-line-arguments)
# <a id="bot-token-donts"></a> Bot Token Dont's ##
* Tokens should not be hardcoded into the bot code
* Tokens should never be published
* Tokens should not be pushed into Repositorys
# <a id="using-environment-variables"></a> Using Environment Variables ###
One convenient way to inject your bot token into the application is by using Environment Variables. Environment Variables are Values that are set in the Environment the Bot is running.
Those Values are not defined in the Application and therefore are not visible in the code.
## <a id="setting-environment-variables"></a> Setting Environment Variables ###
### Windows
Enviroment Variables in Windows can be set using the Console (CMD) using
```batchfile
SETX [VARIABLE_NAME] [YOUR_BOT_TOKEN]
```
It can also be set using the Windows GUI
* From the desktop, right click the Computer icon.
* Choose Properties from the context menu.
* Click the Advanced system settings link.
* Click Environment Variables...
* In the 'User Variables for X' click New and enter a Name and your Token as the Value
### Linux & Mac
* Open the '~/.bash_profile' File
* Append the following to it:
```bash
export VARIABLE_NAME = {YOUR_BOT_TOKEN}
```
* Save the file
* Either reboot your system or run the command above in your terminal
### IntelliJ
* Go to Run->Edit Configuratuions...
* Navigate to your Java Run Configuration
* Under Enviroment->Enviroment Variables click the Folder Icon
* Click the Plus Icon to add a new Variable
* Enter a Name and your Token as the Value
###Heroku Cloud
* Naviage to your App
* In the Settings Tab under Config Vars, click "Reveal Config Vars"
* Enter a Name and your Token as the Value
* Click the "Add" button
## <a id="accessing-enviroment-variables"></a> Accessing Enviroment Variables ##
### Java
You can access the Enviroment Variables by using System.getEnv()
```java
String BOT_TOKEN = System.getenv("VARIABLE_NAME");
```
### Spring
In Spring the @Value annotation allows you to inject the Value into your class
```java
public class Bot extends TelegramLongPollingBot {
public Bot(@Value("${VARIABLE_NAME") String botToken) {
this.botToken = botToken;
}
}
```
# <a id="sing-command-line-arguments"></a> Using Command Line Arguments
An easier but not Recommended way of injecting the Bottoken is by utilizing Command Line Arguments when starting the Application
In this case your main Method is responsible for taking in the Token
```java
public static void main(String[] args) {
String botToken = args[0];
}
```
You now have to call your jar by using
```
java -jar myBot.jar [BOT_TOKEN]
```

View File

@ -1,3 +1,3 @@
Welcome to the TelegramBots wiki. Use the sidebar on the right. If you're not sure what to look at, why not take a look at the [[Getting Started|Getting-Started]] guide?
If you want more detailed explanations, you can also visit this [gitbook by MonsterDeveloper's](https://www.gitbook.com/book/monsterdeveloper/writing-telegram-bots-on-java/details)
If you want more detailed explanations, you can also visit this [gitbook by MonsterDeveloper's](https://monsterdeveloper.gitbooks.io/writing-telegram-bots-on-java/content/)

View File

@ -1,6 +1,123 @@
### <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`.
3. `close` method has been removed from `BotSession`, use `stop` instead.
4. All methods that are intended to upload files are using now `InputMedia` and `InputFile`.
### <a id="2.4.3"></a>To version 2.4.3 ###
1. Replace `BotOptions` by `DefaultBotOptions`.
2. At the beginning of your program (before creating your `TelegramBotsApi` instance, add the following line:
2. At the beginning of your program (before creating your `TelegramBotsApi` or `Bot` instance, add the following line:
```java
ApiContextInitializer.init();
```

View File

@ -0,0 +1,119 @@
### Using HTTP proxy
HTTP proxy support implemented since version 3.6.1
First of all you need to override constructor with `DefaultBotOptions` argument while inheriting from `AbilityBot` or `TelegramLongPollingBot`
```java
public class MyBot extends AbilityBot {
protected MyBot(String botToken, String botUsername, DefaultBotOptions botOptions) {
super(botToken, botUsername, botOptions);
}
public int creatorId() {
return 0;
}
public Ability pingPong() {
return Ability
.builder()
.name("ping")
.info("ping pong")
.locality(ALL)
.privacy(PUBLIC)
.action(ctx -> silent.send("pong", ctx.chatId()))
.build();
}
}
```
Now you are able to set up your proxy
#### Without authentication
```java
public class Main {
private static String BOT_NAME = "My test bot";
private static String BOT_TOKEN = "..." /* your bot's token here */;
private static String PROXY_HOST = "..." /* proxy host */;
private static Integer PROXY_PORT = 3128 /* proxy port */;
public static void main(String[] args) {
try {
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSessioin.class);
// Set up Http proxy
DefaultBotOptions botOptions = new DefaultBotOptions());
botOptions.setProxyHost(PROXY_HOST);
botOptions.setProxyPort(PROXY_PORT);
// Select proxy type: [HTTP|SOCKS4|SOCKS5] (default: NO_PROXY)
botOptions.setProxyType(DefaultBotOptions.ProxyType.SOCKS5);
// Register your newly created AbilityBot
MyBot bot = new MyBot(BOT_TOKEN, BOT_NAME, botOptions);
botsApi.registerBot(bot);
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
}
```
#### With authentication
```java
public class Main {
private static String BOT_NAME = "My test bot";
private static String BOT_TOKEN = "..." /* your bot's token here */;
private static String PROXY_HOST = "..." /* proxy host */;
private static Integer PROXY_PORT = 3128 /* proxy port */;
private static String PROXY_USER = "..." /* proxy user */;
private static String PROXY_PASSWORD = "..." /* proxy password */;
public static void main(String[] args) {
try {
// Create the Authenticator that will return auth's parameters for proxy authentication
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(PROXY_USER, PROXY_PASSWORD.toCharArray());
}
});
// Create the TelegramBotsApi object to register your bots
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
// Set up Http proxy
DefaultBotOptions botOptions = new DefaultBotOptions();
botOptions.setProxyHost(PROXY_HOST);
botOptions.setProxyPort(PROXY_PORT);
// Select proxy type: [HTTP|SOCKS4|SOCKS5] (default: NO_PROXY)
botOptions.setProxyType(DefaultBotOptions.ProxyType.SOCKS5);
// Register your newly created AbilityBot
MyBot bot = new MyBot(BOT_TOKEN, BOT_NAME, botOptions);
botsApi.registerBot(bot);
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
}
```
If you need something more complex than one proxy, then you can create more complex Authenticator that will check host and other parameters of proxy and return auth values based on them (for more information see code of java.net.Authenticator class)

View File

@ -1,6 +1,21 @@
* Users guide
* [[Getting Started]]
* [[Errors Handling]]
* [[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]]
* [[Additional Examples]]
* [[Changelog]]
* [[How To Update]]
* [[How To Update]]

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

@ -0,0 +1,5 @@
# Additional Examples
The following are nifty links to projects and examples that leverage the AbilityBot module. If you do have a project that you would like to share, please reach out!
[FitnessBot](https://craftcodecrew.com/getting-started-with-the-telegram-abilitybot/) -
A fully fledged guide that walks you through building a fitness bot from A to Z

View File

@ -0,0 +1,78 @@
# Advanced
This will be more of a FAQ on some important notes before you embark on your next big bot project!
## Default Abilities
It is possible to declare "DEFAULT" abilities that process non-command messages. This is quite close to a reply. If a user says "Hey there" and the default ability is implemented, it will process this input.
```java
/**
* This ability has an extra "flag". It needs a photo to activate! This feature is activated by default if there is no /command given.
*/
public Ability sayNiceToPhoto() {
return Ability.builder()
.name(DEFAULT) // DEFAULT ability is executed if user did not specify a command -> Bot needs to have access to messages (check FatherBot)
.flag(PHOTO)
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.action(ctx -> silent.send("Daaaaang, what a nice photo!", ctx.chatId()))
.build();
}
```
This ability will send a *"Daaaaang, what a nice photo!"* whenever the bot receives a photo. This is one use case where replies and abilities are interchangeable.
## The Global Flag
There is a global flag in AbilityBot that restricts the kind of "updates" it can process. The default implementation is passthrough - it allows all updates to be processed.
As an example, if you want to restrict the updates to photos only, then you may do:
```java
@Override
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

@ -0,0 +1,8 @@
# Bot Recovery
With recovery, we specifically mean recovering the DB in-case of false data being committed. This is a neat feature supported by DBContext, you can /backup and /recover your bot whenever needed.
Once you /backup, the bot will respond back with a valid JSON object that represents all the data in the DB.
On /recover, the bot will ask for the JSON file. A reply to the message with the file attached will recover the bot with the previous state DB.
Try to experiment using the counter ability introduced in [[Database Handling|Database-Handling]]!

View File

@ -0,0 +1,198 @@
# Testing
It is super important to be able to test your bot prior to "release". In this case, release would mean that you're presenting the bot to your designated audience. Nobody likes bots that are buggy, faulty and do clumsy actions.
As developers, we appreciate frameworks that provide an ease in testing. Of course, you might no tbe able to catch all bugs that can occur in production, but you'd be far more comfortable in releasing a bot that is well-tested.
## Limitations
The issue with the basic API is that all DefaultAbsSender methods (the bot methods you use to send message) are statically defined without interfacing. If you declare your bot and try to do some testing, you won't be able to know that you've executed a method... unless you actually execute it! As an example:
```java
public void sayHello() {
SendMessage snd = new SendMessage();
snd.setText("Hello!");
snd.setChatId(123);
try {
// We want to test that we actually sent out this message with the contents "Hello!"
execute(snd);
} catch (TelegramApiException e) {}
}
```
This is how you would define a method that says hello in the basic API. How do you go around testing it? If you do attempt to Junit test this method, what will you be testing? If you change the method signature to return the string sent, then you can test the hello message content. However, can you test that you've actually `executed` the command?
## Mock Testing
*This section assumes you're familiar with mock testing. Mock testing is basically replacing a real object X with a fake object Y (a mock) of the same type. By doing that, you're able to test whether certain functions were executed.*
Obviously, you can't, but there's a twist to it. You can always mock the whole bot, but with that you're also mocking the method `sayHello` when you actually need its contents and code! We need to extract the bot-sending-specific-methods into their own interface and try to mock that interface instead.
## MessageSender Interface
All ability bots declare two utility objects.
### The Sender Object
The `sender` object is an implementation of the [MessageSender](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/MessageSender.java) interface. The interface mirrors
all the bot sending methods. A user can supply his own MessageSender, but the AbilityBot module specifies a [DefaultSender](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/DefaultSender.java) As you might guess, the default sender is simply a proxy for the bot API methods.
### The Silent Object
The `silent` object is exactly like the sender object, but silent. Its methods return `Optional<T>`. On exception, it will be an empty optional. The sender object is provided to reduce verboseness of the code (reducing try-catch blocks with something more elegant).
## AbilityBot Testing
Let's suppose that you have an ability that says "Hello World!" declared as such:
```java
public Ability saysHelloWorld() {
return Ability.builder()
.name("hello")
.info("Says hello world!")
.privacy(PUBLIC)
.locality(ALL)
.action(ctx -> {
try{
sender.execute(new SendMessage().setChatId(ctx.getChatId()).setText("Hello World!"));
} catch (TelgramApiException e){}
})
.build();
}
```
The test for this ability would be:
```java
@Test
public void canSayHelloWorld() {
Update upd = new Update();
// 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, 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 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);
}
```
The comments explain every step in the test. In a single assertion with Mockito, we assert that:
* We've sent the message once
* The message content was "Hello World!"
* The message was sent to a specific chat ID
There are some preparations involved before we can perform such a test. Here's the full code snippet for running this test:
```java
import org.junit.After;
import org.junit.Before;
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.MessageContext;
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.*;
public class ExampleBotTest {
public static final int USER_ID = 1337;
public static final long CHAT_ID = 1337L;
// Your bot handle here
private ExampleBot bot;
// 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
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
// Create setter in your bot
bot.setSilentSender(silent);
}
@Test
public void canSayHelloWorld() {
Update upd = new Update();
// 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, 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 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);
}
}
```
## DB Abilities
What if the ability performs a DB interaction? We don't want testing procedures to modify the database of the bot.
This is where we differentiate between an online DB and an offline DB. The online DB is the default DB when the bot is instantiated. However, AbilityBot supplies a constructor that reveals a DBContext argument. We can supply another instance of a DB (an offline one) so that the tests don't modify our online DB.
In ExampleBot, we do:
```java
public ExampleBot(DBContext db) {
super(BOT_TOKEN, BOT_USERNAME, db);
}
```
In ExampleBotTest:
```java
public class ExampleBotTest {
...
private DBContext db;
private MessageSender sender;
@Before
public void setUp() {
// Offline instance will get deleted at JVM shutdown
db = MapDBContext.offlineInstance("test");
bot = new ExampleBot(db);
bot.onRegister();
...
}
...
// We should clear the DB after every test as such
@After
public void tearDown() {
db.clear();
}
}
```
## Silent Testing
As mentioned before, we also have another object that is able to send messages silently called `silent`. The constructor of the silent sender requires a MessageSender object. If your abilities use the `silent` object, be sure to:
```java
public class ExampleBotTest {
...
private DBContext db;
private MessageSender sender;
@Before
public void setUp() {
bot = new ExampleBot(db);
bot.onRegister();
sender = mock(MessageSender.class);
SilentSender silent = new SilentSender(sender);
// Create setter in your bot
bot.setSilentSender(silent);
...
}
...
}
```
Do note that in your test assertions, don't use the silent object. Mocked assertion require the mock object. If you recall, the silent object uses the sender object, so your tests will still be correct if you're asserting on the `sender` object rather than the silent one.

View File

@ -0,0 +1,57 @@
# Database Handling
AbilityBots come with an embedded DB. Users are free to implement their own databases via implementing the [DBContext](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java) class.
The abstraction has multiple constructors to accommodate user-defined implementations of [DBContext](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java) and [MessageSender](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/sender/MessageSender.java). We'll talk about the message sender interface in the [[Bot Testing|Bot-Testing]] section.
## Example
We'll be introducing an ability that maintains a special counter for every user. At every /increment, the user will receive a message with the previous number + 1. We'll initially start from zero and increment upwards.
```java
/**
* Use the database to fetch a count per user and increments.
* <p>
* Use /count to experiment with this ability.
*/
public Ability useDatabaseToCountPerUser() {
return Ability.builder()
.name("count")
.info("Increments a counter per user")
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.action(ctx -> {
// 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().getId();
// Get and increment counter, put it back in the map
Integer counter = countMap.compute(String.valueOf(userId), (id, count) -> count == null ? 1 : ++count);
/*
Without lambdas implementation of incrementing
int counter;
if (countMap.containsKey(userId))
counter = countMap.get(userId) + 1;
else
counter = 1;
countMap.put(userId, counter);
*/
// Send formatted will enable markdown
String message = String.format("%s, your count is now *%d*!", ctx.user().getUserName(), counter);
silent.send(message, ctx.chatId());
})
.build();
}
```
After successfully adding that ability to your bot, try to /count and watch as the number increases at every message.
Other important functions in the `db` object:
* getSet - gets a set of data
* getList - gets a list of data
* summary - gets a summary of the present structs
Be sure to check out [DBContext](../../telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java) for all the implemented methods.

View File

@ -0,0 +1,117 @@
Motivation
----------
After implementing your own bot via the basic API, you'll quickly realize how verbose it is. Once you get multiple commands up and running, your routing logic and handling per command start to become cumbersome.
Dealing with a basic API has its advantages and disadvantages. Obviously, there's nothing hidden. If it's there on Telegram, it's here in the Java API. However, can we do better than just a basic API?
When you want to implement a feature in your bot, you start asking these questions:
* The **WHO**?
* Who is going to use this feature? Should they be allowed to use all the features?
* The **WHAT**?
* Under what conditions should I allow this feature?
* Should the message have a photo? A document? Oh, maybe a callback query?
* The **HOW**?
* If my bot crashes, how can I resume my operation?
* Should I utilize a DB?
* How can I separate logic execution of different features?
* How can I unit-test my feature outside of Telegram?
Every time you write a command or a feature, you will need to answer these questions and ensure that your feature logic works.
Ability
----------------------
Simply put, the abilities module was developed to make your life easier. Whether you're counting numbers, fetching images or handling large data, AbilityBot is here to augment your development.
The AbilityBot abstraction intends to provide the following:
* New feature is a new **Ability**, a new method - no fuss, zero overhead, no cross-code with other features
* Argument length on a command is as easy as changing a single integer
* Privacy settings per Ability - access levels to Abilities! User | Admin | Creator
* Embedded database - available for every declared ability
* Proxy sender interface - enhances testability; accurate results pre-release
Alongside these exciting core features of the AbilityBot, the following have been introduced:
* The bot automatically maintains an up-to-date set of all the users who have contacted the bot
* Backup and recovery for the DB
* Ban and unban users from accessing your bots
* Promote and demote users as bot administrators
Abstraction
--------------
The AbilityBot abstraction defines a new object, named **Ability**. An ability combines conditions, flags, action, post-action and replies.
As an example, here is a code-snippet of an ability that creates a ***/hello*** command:
```java
public Ability sayHelloWorld() {
return Ability
.builder()
.name("hello")
.info("says hello world!")
.input(0)
.locality(USER)
.privacy(ADMIN)
.action(ctx -> silent.send("Hello world!", ctx.chatId()))
.post(ctx -> silent.send("Bye world!", ctx.chatId()))
.build();
}
```
Here is a breakdown of the above code snippet:
* *.name()* - the name of the ability (essentially, this is the command)
* *.info()* - provides information for the command
* More on this later, but it basically centralizes command information in-code.
* *.input()* - the number of input arguments needed, 0 is for do-not-care
* *.locality()* - this answers where you want the ability to be available
* In GROUP, USER private chats or ALL (both)
* *.privacy()* - this answers who you want to access your ability
* CREATOR, ADMIN, or everyone as PUBLIC
* *.action()* - the feature logic resides here (a lambda function that takes a *MessageContext*)
* *MessageContext* provides fast accessors for the **chatId**, **user** and the underlying **update**. It also conforms to the specifications of the basic API.
* *.post()* - the logic executed **after** your main action finishes execution
The `silent` object is created with every AbilityBot. It provides helper and utility functions that execute "silently". In this context, silent execution of bot API methods are ones that don't throw an exception. However, all methods in silent return an Optional object. If an exception occurs, the optional would be empty. The developer would still be able to
manage errors by checking for the presence of the optional `.isPresent()`. This decreases verboseness while still being able to execute routines correctly.
Do note that:
* You can still access the bot's methods and functions inside the lambda function in your action definition. That includes all the DefaultAbsSender methods execute, executeAsync, setChatPhoto, etc....
* `silent` uses another accessible object named `sender`. Refer to [[Bot Testing|Bot-Testing]] for the main use case of sender as an interface to all bot methods.
With abilities, you can specify the context of the feature. If you only want the command to be available for groups, then you can set `.locality(GROUP)`. If it is a very sensitive command that only admins should have access to, then set `.privacy(ADMIN)`.
This allows for abilities with protection guarantees on who can use it and where it can be used.
All abilities have access to the following important methods.
* `users()` - Returns a map of ID -> User
* `userIds()` - Returns a map of Username -> ID
* `blacklist()` - Returns a set of IDs of banned users
* `admins()` - Returns a set of IDs of bot administrators
`users()` and `userIds()` accumulate data of all the users who have contacted your bot. Even when a user changes some information (like his or her nickname), the bot will be able to detect the change and update its DB accordingly.
The following is a snippet of how this ability would look like with the plain basic API.
```java
@Override
public void onUpdateReceived(Update update) {
// Global checks...
// Switch, if, logic to route to hello world method
// Execute method
}
public void sayHelloWorld(Update update) {
if (!update.hasMessage() || !update.getMessage().isUserMessage() || !update.getMessage().hasText() || update.getMessage.getText().isEmpty())
return;
User maybeAdmin = update.getMessage().getFrom();
/* Query DB for if the user is an admin, can be SQL, Reddis, Ignite, etc...
If user is not an admin, then return here.
*/
SendMessage snd = new SendMessage();
snd.setChatId(update.getMessage().getChatId());
snd.setText("Hello world!");
try {
execute(snd);
} catch (TelegramApiException e) {
BotLogger.error("Could not send message", TAG, e);
}
}
```
I will leave you the choice to decide between the two snippets as to which is more **readable**, **writable** and **testable**.

View File

@ -0,0 +1,115 @@
# AbilityBot
This section of the tutorial will present a barebone example on creating your first AbilityBot! It is highly recommended to write your very first bot via the [[Getting Started|Getting-Started]]. That will give you a sense of how the basic API allows you to handle commands and features.
## Dependencies
As with any Java project, you will need to set your dependencies.
* **Maven**
```xml
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-abilities</artifactId>
<version>5.0.1</version>
</dependency>
```
* **Gradle**
```groovy
implementation group: 'org.telegram', name: 'telegrambots-abilities', version: '5.0.1'
```
* [JitPack](https://jitpack.io/#rubenlagus/TelegramBots)
* [Plain Imports/Jars](https://github.com/rubenlagus/TelegramBots/releases)
## Bot Declaration
To use the abilities module, you will need to extend AbilityBot.
```java
import org.telegram.abilitybots.api.bot.AbilityBot;
public class HelloBot extends AbilityBot {
...
}
```
## Bot Implementation
Bot token and nickname are passed via the constructor and don't require an override.
```java
public HelloBot(String token, String username) {
super(token, username);
}
```
However, since the token and username of a bot are usually constants, you can do the following:
```java
public static String BOT_TOKEN = "...";
public static String BOT_USERNAME = "...";
public HelloBot() {
super(BOT_TOKEN, BOT_USERNAME);
}
```
AbilityBot forces a single implementation of creator ID. This ID corresponds to you, the bot developer. The bot needs to know its master since it has sensitive commands that only the master can use.
So, if your Telegram ID Is 123456789, then add the following method:
```java
@Override
public int creatorId() {
return 123456789;
}
```
That's it to have a valid, compilable and ready to be deployed bot. However, your bot doesn't have a single command to use. Let's declare one!
## Hello Ability
To add a feature to your bot, you add an ability. That's it! No routing from onUpdateReceived, no separate checks and no crossovers. Let's write our first ability that simply says hello!
```java
public Ability sayHelloWorld() {
return Ability
.builder()
.name("hello")
.info("says hello world!")
.locality(ALL)
.privacy(PUBLIC)
.action(ctx -> silent.send("Hello world!", ctx.chatId()))
.build();
}
```
Save your questions for later! Abilities are described in detail in the following sections of the tutorial.
## Running Your Bot
Running the bot is just like running the regular Telegram bots. Create a Java class similar to the one below.
```java
public class Application {
public static void main(String[] args) {
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) {
e.printStackTrace();
}
}
}
```
If you're in doubt that you're missing some code, the full code example can be inspected [here](https://github.com/addo37/ExampleBots/tree/master/src/main/java/org/telegram/examplebots).
## Testing Your Bot
Go ahead and "/hello" to your bot. It should respond back with "Hello World!".
Since you've implemented an AbilityBot, you get **factory abilities** as well. Try:
* /report - Prints all user-defined commands supported by the bot
* This will essentially print "hello - says hello world!". Yes! This is the information we supplied to the ability. The bot prints the commands in the format accepted by BotFather. So, whenever you change, add or remove commands, you can simply /report and forward that message to BotFather.
* /commands - Prints all commands exposed by the bot (factory and user-defined, with and without info)
* /claim - Claims this bot
* /backup - returns a backup of the bot database
* /recover - recovers the database
* /promote @username - promotes user to bot admin
* /demote @username - demotes bot admin to user
* /ban @username - bans the user from accessing your bot commands and features
* /unban @username - lifts the ban from the user
## Conclusion
Congratulation on creating your first AbilityBot. What's next? So far we've only considered the case of commands, but what about images and inline replies? AbilityBots can also handle that! Oh and, did you know that all ability bots have an embedded database that you can use?
The following sections of the tutorial will describe in detail **abilities** and **replies**. It will also bring into attention how to effectively in-code test your bot, handle the embedded DB and administer your user access levels.

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

@ -0,0 +1,77 @@
# Replies
A reply is AbilityBot's swiss army knife. It comes in two variants and is able to handle all possible use cases.
## Standalone Reply
Standalone replies are replies declared on their own without being attached to an ability. Here's an example of a possible reply declaration:
```java
/**
* A reply that says "yuck" to all images sent to the bot.
*/
public Reply sayYuckOnImage() {
// getChatId is a public utility function in rg.telegram.abilitybots.api.util.AbilityUtils
Consumer<Update> action = upd -> silent.send("Yuck", getChatId(upd));
return Reply.of(action, Flag.PHOTO)
}
```
Let's break this down. Replies require a lambda function (consumer) that is able to consume our update. In this case, our consumer simply fetches the chatId
from the update and sends a "Yuck" message. `Reply.of(upd)` would be enough. However, replies accept a var-arg of type `Predicate<Update>`. These predicates are the necessary conditions so that the bot acts the reply. We specify Flag.PHOTO to let the bot know
that we only want the reply to act on images only! The Flag is a public enum at your disposal. It contains other conditionals like checking for videos, messages, voice, documents, etc...
## Ability Reply
In exactly the same manner, you are able to attach replies to abilities. This way you can localize replies that relate to the same ability.
```java
public Ability playWithMe() {
String playMessage = "Play with me!";
return Ability.builder()
.name("play")
.info("Do you want to play with me?")
.privacy(PUBLIC)
.locality(ALL)
.input(0)
.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
.reply(upd -> {
// Prints to console
System.out.println("I'm in a reply!");
// Sends message
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!
// MESSAGE means that the update must have a message
// This is imported statically, Flag.MESSAGE
MESSAGE,
// REPLY means that the update must be a reply, Flag.REPLY
REPLY,
// A new predicate user-defined
// The reply must be to the bot
isReplyToBot(),
// If we process similar logic in other abilities, then we have to make this reply specific to this message
// The reply is to the playMessage
isReplyToMessage(playMessage)
)
// You can add more replies by calling .reply(...)
.build();
}
private Predicate<Update> isReplyToMessage(String message) {
return upd -> {
Message reply = upd.getMessage().getReplyToMessage();
return reply.hasText() && reply.getText().equalsIgnoreCase(message);
};
}
private Predicate<Update> isReplyToBot() {
return upd -> upd.getMessage().getReplyToMessage().getFrom().getUserName().equalsIgnoreCase(getBotUsername());
}
```
In this example, we showcase how we can supply our own predicates. The two new predicates are `isReplyToMessage` and `isReplyToBot`.
The checks are made so that, once you execute your logic there is no need to check for the validity of the reply.
They were all made once the action logic is being executed.

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

BIN
jetbrains.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

310
mvnw vendored Executable file
View File

@ -0,0 +1,310 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven2 Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

182
mvnw.cmd vendored Normal file
View File

@ -0,0 +1,182 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven2 Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%

191
pom.xml
View File

@ -7,13 +7,15 @@
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<packaging>pom</packaging>
<version>3.3</version>
<version>5.0.1</version>
<modules>
<module>telegrambots</module>
<module>telegrambots-meta</module>
<module>telegrambots-extensions</module>
<module>telegrambots-abilities</module>
<module>telegrambots-spring-boot-starter</module>
<module>telegrambots-chat-session-bot</module>
</modules>
<licenses>
@ -24,8 +26,189 @@
</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>
<maven.deploy.skip>true</maven.deploy.skip>
<bots.version>3.3</bots.version>
<java.version>11</java.version>
<maven.compiler.release>11</maven.compiler.release>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<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>
</project>
<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>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<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>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jaxb-annotations</artifactId>
<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>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<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>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockitojupiter.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
</dependencies>
<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>3.3</version>
<version>5.0.1</version>
</dependency>
```
**Gradle**
```gradle
compile "org.telegram:telegrambots-abilities:3.3"
compile "org.telegram:telegrambots-abilities:5.0.1"
```
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v3.3)
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v5.0.1)
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.3)
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v5.0.1)
Motivation
----------

View File

@ -3,9 +3,14 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.telegram</groupId>
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>5.0.1</version>
</parent>
<artifactId>telegrambots-abilities</artifactId>
<version>3.3</version>
<packaging>jar</packaging>
<name>Telegram Ability Bot</name>
@ -63,19 +68,23 @@
</distributionManagement>
<properties>
<java.version>11</java.version>
<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>
<bots.version>3.3</bots.version>
<commonslang.version>3.5</commonslang.version>
<mapdb.version>3.0.4</mapdb.version>
<guava.version>19.0</guava.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>${bots.version}</version>
<version>5.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
@ -86,24 +95,12 @@
<groupId>org.mapdb</groupId>
<artifactId>mapdb</artifactId>
<version>${mapdb.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>2.0.2-beta</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
@ -116,22 +113,13 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.3</version>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
@ -141,7 +129,7 @@
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<id>clean-project</id>
@ -154,7 +142,7 @@
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
@ -173,7 +161,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<goals>
@ -185,14 +173,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
@ -200,7 +188,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.7.201606060606</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>
@ -219,7 +207,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<version>3.0.0-M2</version>
<executions>
<execution>
<id>enforce-versions</id>
@ -237,7 +225,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.4</version>
<version>3.1.1</version>
<executions>
<execution>
<id>copy</id>
@ -251,13 +239,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
</project>

View File

@ -1,743 +1,62 @@
package org.telegram.abilitybots.api.bot;
import org.apache.commons.io.IOUtils;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.*;
import org.telegram.abilitybots.api.sender.DefaultMessageSender;
import org.telegram.abilitybots.api.sender.MessageSender;
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.api.methods.GetFile;
import org.telegram.telegrambots.api.methods.send.SendDocument;
import org.telegram.telegrambots.api.objects.Message;
import org.telegram.telegrambots.api.objects.Update;
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.exceptions.TelegramApiException;
import org.telegram.telegrambots.logging.BotLogger;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.generics.LongPollingBot;
import org.telegram.telegrambots.util.WebhookUtils;
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.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.String.format;
import static java.time.ZonedDateTime.now;
import static java.util.Arrays.stream;
import static java.util.Objects.nonNull;
import static java.util.Optional.ofNullable;
import static java.util.function.Function.identity;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static java.util.regex.Pattern.compile;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static jersey.repackaged.com.google.common.base.Throwables.propagate;
import static org.telegram.abilitybots.api.db.MapDBContext.onlineInstance;
import static org.telegram.abilitybots.api.objects.Ability.builder;
import static org.telegram.abilitybots.api.objects.EndUser.fromUser;
import static org.telegram.abilitybots.api.objects.Flag.*;
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.*;
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}.
* <p>
* All bots extending the {@link AbilityBot} get implicit abilities:
* <ul>
* <li>/claim - Claims this bot</li>
* <ul>
* <li>Sets the user as the {@link Privacy#CREATOR} of the bot</li>
* <li>Only the user with the ID returned by {@link AbilityBot#creatorId()} can genuinely claim the bot</li>
* </ul>
* <li>/commands - reports all user-defined commands (abilities)</li>
* <ul>
* <li>The same format acceptable by BotFather</li>
* </ul>
* <li>/backup - returns a backup of the bot database</li>
* <li>/recover - recovers the database</li>
* <li>/promote <code>@username</code> - promotes user to bot admin</li>
* <li>/demote <code>@username</code> - demotes bot admin to user</li>
* <li>/ban <code>@username</code> - bans the user from accessing your bot commands and features</li>
* <li>/unban <code>@username</code> - lifts the ban from the user</li>
* </ul>
* <p>
* Additional information of the implicit abilities are present in the methods that declare them.
* <p>
* The two most important handles in the AbilityBot are the {@link DBContext} <b><code>db</code></b> and the {@link MessageSender} <b><code>sender</code></b>.
* All bots extending AbilityBot can use both handles in their update consumers.
* 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 TelegramLongPollingBot {
private static final String TAG = AbilityBot.class.getSimpleName();
// 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";
// Messages
protected static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached.";
protected static final String RECOVER_SUCCESS = "I have successfully recovered.";
// DB and sender
protected final DBContext db;
protected MessageSender sender;
// Bot token and username
private final String botToken;
private final String botUsername;
// Ability registry
private Map<String, Ability> abilities;
// Reply registry
private List<Reply> replies;
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) {
super(botOptions);
this.botToken = botToken;
this.botUsername = botUsername;
this.db = db;
this.sender = new DefaultMessageSender(this);
registerAbilities();
}
protected AbilityBot(String botToken, String botUsername, DBContext db) {
this(botToken, botUsername, db, new DefaultBotOptions());
}
protected AbilityBot(String botToken, String botUsername, DefaultBotOptions botOptions) {
this(botToken, botUsername, onlineInstance(botUsername), botOptions);
}
protected AbilityBot(String botToken, String botUsername) {
this(botToken, botUsername, onlineInstance(botUsername));
}
public abstract int creatorId();
/**
* @return the map of ID -> EndUser
*/
protected Map<Integer, EndUser> users() {
return db.getMap(USERS);
}
/**
* @return the map of Username -> ID
*/
protected Map<String, Integer> userIds() {
return db.getMap(USER_ID);
}
/**
* @return a blacklist containing all the IDs of the banned users
*/
protected Set<Integer> blacklist() {
return db.getSet(BLACKLIST);
}
/**
* @return an admin set of all the IDs of bot administrators
*/
protected Set<Integer> admins() {
return db.getSet(ADMINS);
}
/**
* This method contains the stream of actions that are applied on any update.
* <p>
* It will correctly handle addition of users into the DB and the execution of abilities and replies.
*
* @param update the update received by Telegram's API
*/
@Override
public void onUpdateReceived(Update update) {
BotLogger.info(format("New update [%s] received at %s", update.getUpdateId(), now()), format("%s - %s", TAG, botUsername));
BotLogger.info(update.toString(), TAG);
long millisStarted = System.currentTimeMillis();
Stream.of(update)
.filter(this::checkGlobalFlags)
.filter(this::checkBlacklist)
.map(this::addUser)
.filter(this::filterReply)
.map(this::getAbility)
.filter(this::validateAbility)
.filter(this::checkPrivacy)
.filter(this::checkLocality)
.filter(this::checkInput)
.filter(this::checkMessageFlags)
.map(this::getContext)
.map(this::consumeUpdate)
.forEach(this::postConsumption);
long processingTime = System.currentTimeMillis() - millisStarted;
BotLogger.info(format("Processing of update [%s] ended at %s%n---> Processing time: [%d ms] <---%n", update.getUpdateId(), now(), processingTime), format("%s - %s", TAG, botUsername));
}
@Override
public String getBotToken() {
return botToken;
}
@Override
public String getBotUsername() {
return botUsername;
}
/**
* Test the update against the provided global flags. The default implementation requires a {@link Flag#MESSAGE}.
* <p>
* This method should be <b>overridden</b> if the user wants updates that don't require a MESSAGE to pass through.
*
* @param update a Telegram {@link Update}
* @return <tt>true</tt> if the update satisfies the global flags
*/
protected boolean checkGlobalFlags(Update update) {
return MESSAGE.test(update);
}
/**
* Gets the user with the specified username.
*
* @param username the username of the required user
* @return the user
*/
protected EndUser 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));
public abstract class AbilityBot extends BaseAbilityBot implements LongPollingBot {
protected AbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle, DefaultBotOptions botOptions) {
super(botToken, botUsername, db, toggle, botOptions);
}
return getUser(id);
}
/**
* Gets the user with the specified ID.
*
* @param id the id of the required user
* @return the user
*/
protected EndUser getUser(int id) {
EndUser endUser = users().get(id);
if (endUser == null) {
throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id));
protected AbilityBot(String botToken, String botUsername, AbilityToggle toggle, DefaultBotOptions options) {
this(botToken, botUsername, onlineInstance(botUsername), toggle, options);
}
return endUser;
}
/**
* 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
* @return the id of the user
*/
protected int getUserIdSendError(String username, long chatId) {
try {
return getUser(username).id();
} catch (IllegalStateException ex) {
sender.send(format("Sorry, I could not find the user [%s].", username), chatId);
throw propagate(ex);
}
}
/**
* <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(COMMANDS)
.locality(ALL)
.privacy(PUBLIC)
.input(0)
.action(ctx -> {
String commands = abilities.entrySet().stream()
.filter(entry -> nonNull(entry.getValue().info()))
.map(entry -> {
String name = entry.getValue().name();
String info = entry.getValue().info();
return format("%s - %s", name, info);
})
.sorted()
.reduce((a, b) -> format("%s%n%s", a, b))
.orElse("No public commands found.");
sender.send(commands, 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(db.backup());
sender.sendDocument(new SendDocument()
.setNewDocument(backup)
.setChatId(ctx.chatId())
);
} catch (FileNotFoundException e) {
BotLogger.error("Error while fetching backup", TAG, e);
} catch (TelegramApiException e) {
BotLogger.error("Error while sending document/backup file", TAG, 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 -> sender.forceReply(RECOVERY_MESSAGE, ctx.chatId()))
.reply(update -> {
Long chatId = update.getMessage().getChatId();
String fileId = update.getMessage().getDocument().getFileId();
try (FileReader reader = new FileReader(downloadFileWithId(fileId))) {
String backupData = IOUtils.toString(reader);
if (db.recover(backupData)) {
sender.send(RECOVER_SUCCESS, chatId);
} else {
sender.send("Oops, something went wrong during recovery.", chatId);
}
} catch (Exception e) {
BotLogger.error("Could not recover DB from backup", TAG, e);
sender.send("I have failed to recover.", chatId);
}
}, MESSAGE, DOCUMENT, REPLY, isReplyTo(RECOVERY_MESSAGE))
.build();
}
/**
* Banned users are accumulated in the blacklist. Use {@link DBContext#getSet(String)} with name specified by {@link AbilityBot#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.chatId());
String bannedUser;
// Protection from abuse
if (userId == creatorId()) {
userId = ctx.user().id();
bannedUser = isNullOrEmpty(ctx.user().username()) ? addTag(ctx.user().username()) : ctx.user().shortName();
} else {
bannedUser = addTag(username);
}
Set<Integer> blacklist = blacklist();
if (blacklist.contains(userId))
sender.sendMd(format("%s is already *banned*.", bannedUser), ctx.chatId());
else {
blacklist.add(userId);
sender.sendMd(format("%s is now *banned*.", bannedUser), ctx.chatId());
}
})
.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.chatId());
Set<Integer> blacklist = blacklist();
if (!blacklist.remove(userId))
sender.sendMd(format("@%s is *not* on the *blacklist*.", username), ctx.chatId());
else {
sender.sendMd(format("@%s, your ban has been *lifted*.", 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.chatId());
Set<Integer> admins = admins();
if (admins.contains(userId))
sender.sendMd(format("@%s is already an *admin*.", username), ctx.chatId());
else {
admins.add(userId);
sender.sendMd(format("@%s has been *promoted*.", username), ctx.chatId());
}
}).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.chatId());
Set<Integer> admins = admins();
if (admins.remove(userId)) {
sender.sendMd(format("@%s has been *demoted*.", username), ctx.chatId());
} else {
sender.sendMd(format("@%s is *not* an *admin*.", username), ctx.chatId());
}
})
.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(PUBLIC)
.input(0)
.action(ctx -> {
if (ctx.user().id() == creatorId()) {
Set<Integer> admins = admins();
int id = creatorId();
long chatId = ctx.chatId();
if (admins.contains(id))
sender.send("You're already my master.", chatId);
else {
admins.add(id);
sender.send("You're now my master.", chatId);
}
} else {
// This is not a joke
abilities.get(BAN).action().accept(newContext(ctx.update(), ctx.user(), ctx.chatId(), ctx.user().username()));
}
})
.post(commitTo(db))
.build();
}
/**
* Registers the declared abilities using method reflection. Also, replies are accumulated using the built abilities and standalone methods that return a Reply.
* <p>
* <b>Only abilities and replies with the <u>public</u> accessor are registered!</b>
*/
private void registerAbilities() {
try {
abilities = stream(this.getClass().getMethods())
.filter(method -> method.getReturnType().equals(Ability.class))
.map(this::returnAbility)
.collect(toMap(Ability::name, identity()));
Stream<Reply> methodReplies = stream(this.getClass().getMethods())
.filter(method -> method.getReturnType().equals(Reply.class))
.map(this::returnReply);
Stream<Reply> abilityReplies = abilities.values().stream()
.flatMap(ability -> ability.replies().stream());
replies = Stream.concat(methodReplies, abilityReplies).collect(toList());
} catch (IllegalStateException e) {
BotLogger.error(TAG, "Duplicate names found while registering abilities. Make sure that the abilities declared don't clash with the reserved ones.", e);
throw propagate(e);
protected AbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle) {
this(botToken, botUsername, db, toggle, new DefaultBotOptions());
}
}
/**
* Invokes the method and retrieves its return {@link Ability}.
*
* @param method a method that returns an ability
* @return the ability returned by the method
*/
private Ability returnAbility(Method method) {
try {
return (Ability) method.invoke(this);
} catch (IllegalAccessException | InvocationTargetException e) {
BotLogger.error("Could not add ability", TAG, e);
throw propagate(e);
}
}
/**
* Invokes the method and retrieves its returned Reply.
*
* @param method a method that returns a reply
* @return the reply returned by the method
*/
private Reply returnReply(Method method) {
try {
return (Reply) method.invoke(this);
} catch (IllegalAccessException | InvocationTargetException e) {
BotLogger.error("Could not add reply", TAG, e);
throw propagate(e);
}
}
private void postConsumption(Pair<MessageContext, Ability> pair) {
ofNullable(pair.b().postAction())
.ifPresent(consumer -> consumer.accept(pair.a()));
}
Pair<MessageContext, Ability> consumeUpdate(Pair<MessageContext, Ability> pair) {
pair.b().action().accept(pair.a());
return pair;
}
Pair<MessageContext, Ability> getContext(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
EndUser user = fromUser(AbilityUtils.getUser(update));
return Pair.of(newContext(update, user, getChatId(update), trio.c()), trio.b());
}
boolean checkBlacklist(Update update) {
Integer id = AbilityUtils.getUser(update).getId();
return id == creatorId() || !blacklist().contains(id);
}
boolean checkInput(Trio<Update, Ability, String[]> trio) {
String[] tokens = trio.c();
int abilityTokens = trio.b().tokens();
boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens);
if (!isOk)
sender.send(String.format("Sorry, this feature requires %d additional %s.", abilityTokens, abilityTokens == 1 ? "input" : "inputs"), getChatId(trio.a()));
return isOk;
}
boolean checkLocality(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
Locality locality = isUserMessage(update) ? USER : GROUP;
Locality abilityLocality = trio.b().locality();
boolean isOk = abilityLocality == ALL || locality == abilityLocality;
if (!isOk)
sender.send(String.format("Sorry, %s-only feature.", abilityLocality.toString().toLowerCase()), getChatId(trio.a()));
return isOk;
}
boolean checkPrivacy(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
EndUser user = fromUser(AbilityUtils.getUser(update));
Privacy privacy;
int id = user.id();
privacy = isCreator(id) ? CREATOR : isAdmin(id) ? ADMIN : PUBLIC;
boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0;
if (!isOk)
sender.send(String.format("Sorry, %s-only feature.", trio.b().privacy().toString().toLowerCase()), getChatId(trio.a()));
return isOk;
}
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;
}
Trio<Update, Ability, String[]> getAbility(Update update) {
// Handle updates without messages
// Passing through this function means that the global flags have passed
Message msg = update.getMessage();
if (!update.hasMessage() || !msg.hasText())
return Trio.of(update, abilities.get(DEFAULT), new String[]{});
// Priority goes to text before captions
String[] tokens = msg.getText().split(" ");
if (tokens[0].startsWith("/")) {
String abilityToken = stripBotUsername(tokens[0].substring(1));
Ability ability = abilities.get(abilityToken);
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
return Trio.of(update, ability, tokens);
} else {
Ability ability = abilities.get(DEFAULT);
return Trio.of(update, ability, tokens);
}
}
private String stripBotUsername(String token) {
return compile(format("@%s", botUsername), CASE_INSENSITIVE)
.matcher(token)
.replaceAll("");
}
Update addUser(Update update) {
EndUser endUser = fromUser(AbilityUtils.getUser(update));
users().compute(endUser.id(), (id, user) -> {
if (user == null) {
updateUserId(user, endUser);
return endUser;
}
if (!user.equals(endUser)) {
updateUserId(user, endUser);
return endUser;
}
return user;
});
db.commit();
return update;
}
private void updateUserId(EndUser oldUser, EndUser newUser) {
if (oldUser != null && oldUser.username() != null) {
// Remove old username -> ID
userIds().remove(oldUser.username());
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions options) {
this(botToken, botUsername, db, new DefaultToggle(), options);
}
if (newUser.username() != null) {
// Add new mapping with the new username
userIds().put(newUser.username().toLowerCase(), newUser.id());
protected AbilityBot(String botToken, String botUsername, DefaultBotOptions botOptions) {
this(botToken, botUsername, onlineInstance(botUsername), botOptions);
}
}
boolean filterReply(Update update) {
return replies.stream()
.filter(reply -> reply.isOkFor(update))
.map(reply -> {
reply.actOn(update);
return false;
})
.reduce(true, Boolean::logicalAnd);
}
protected AbilityBot(String botToken, String botUsername, AbilityToggle toggle) {
this(botToken, botUsername, onlineInstance(botUsername), toggle);
}
boolean checkMessageFlags(Trio<Update, Ability, String[]> trio) {
Ability ability = trio.b();
Update update = trio.a();
// The following variable is required to avoid bug #JDK-8044546
BiFunction<Boolean, Predicate<Update>, Boolean> flagAnd = (flag, nextFlag) -> flag && nextFlag.test(update);
return ability.flags().stream()
.reduce(true, flagAnd, Boolean::logicalAnd);
}
protected AbilityBot(String botToken, String botUsername, DBContext db) {
this(botToken, botUsername, db, new DefaultToggle());
}
private File downloadFileWithId(String fileId) throws TelegramApiException {
return sender.downloadFile(sender.getFile(new GetFile().setFileId(fileId)));
}
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

@ -0,0 +1,75 @@
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.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramWebhookBot;
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;
import static org.telegram.abilitybots.api.db.MapDBContext.onlineInstance;
/**
* A {@link WebhookBot}-flavor AbilityBot. It delegates all updates to a {@link TelegramWebhookBot} instance.
*
* @author Abbas Abou Daya
*/
@SuppressWarnings("WeakerAccess")
public abstract class AbilityWebhookBot extends BaseAbilityBot implements WebhookBot {
private final String 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, AbilityToggle toggle, DefaultBotOptions options) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), toggle, options);
}
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, DBContext db, DefaultBotOptions options) {
this(botToken, botUsername, botPath, db, new DefaultToggle(), 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, AbilityToggle toggle) {
this(botToken, botUsername, botPath, onlineInstance(botUsername), toggle);
}
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

@ -0,0 +1,654 @@
package org.telegram.abilitybots.api.bot;
import com.google.common.collect.*;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.abilitybots.api.db.DBContext;
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.meta.api.methods.groupadministration.GetChatAdministrators;
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 java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
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.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.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.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.*;
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 DefaultAbsSender}.
* <p>
* All bots extending the {@link BaseAbilityBot} get implicit abilities:
* <ul>
* <li>/claim - Claims this bot</li>
* <ul>
* <li>Sets the user as the {@link Privacy#CREATOR} of the bot</li>
* <li>Only the user with the ID returned by {@link BaseAbilityBot#creatorId()} can genuinely claim the bot</li>
* </ul>
* <li>/report - reports all user-defined commands (abilities)</li>
* <ul>
* <li>The same format acceptable by BotFather</li>
* </ul>
* <li>/commands - returns a list of all possible bot commands based on the privacy of the requesting user</li>
* <li>/backup - returns a backup of the bot database</li>
* <li>/recover - recovers the database</li>
* <li>/promote <code>@username</code> - promotes user to bot admin</li>
* <li>/demote <code>@username</code> - demotes bot admin to user</li>
* <li>/ban <code>@username</code> - bans the user from accessing your bot commands and features</li>
* <li>/unban <code>@username</code> - lifts the ban from the user</li>
* </ul>
* <p>
* Additional information of the implicit abilities are present in the methods that declare them.
* <p>
* The two most important handles in the BaseAbilityBot are the {@link DBContext} <b><code>db</code></b> and the {@link MessageSender} <b><code>sender</code></b>.
* All bots extending BaseAbilityBot can use both handles in their update consumers.
*
* @author Abbas Abou Daya
*/
@SuppressWarnings({"ConfusingArgumentToVarargsMethod", "UnusedReturnValue", "WeakerAccess", "unused", "ConstantConditions"})
public abstract class BaseAbilityBot extends DefaultAbsSender implements AbilityExtension {
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";
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, 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>
*/
public Map<Integer, User> users() {
return db.getMap(USERS);
}
/**
* @return the map of <Username,ID>
*/
public Map<String, Integer> userIds() {
return db.getMap(USER_ID);
}
/**
* @return a blacklist containing all the IDs of the banned users
*/
public Set<Integer> blacklist() {
return db.getSet(BLACKLIST);
}
/**
* @return an admin set of all the IDs of bot administrators
*/
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>
*/
public Map<String, Ability> abilities() {
return abilities;
}
/**
* @return the immutable list carrying the embedded replies
*/
public List<Reply> replies() {
return replies;
}
/**
* This method contains the stream of actions that are applied on any update.
* <p>
* It will correctly handle addition of users into the DB and the execution of abilities and replies.
*
* @param update the update received by Telegram's API
*/
public void onUpdateReceived(Update update) {
log.info(format("[%s] New update [%s] received at %s", botUsername, update.getUpdateId(), now()));
log.info(update.toString());
long millisStarted = System.currentTimeMillis();
Stream.of(update)
.filter(this::checkGlobalFlags)
.filter(this::checkBlacklist)
.map(this::addUser)
.filter(this::filterReply)
.filter(this::hasUser)
.map(this::getAbility)
.filter(this::validateAbility)
.filter(this::checkPrivacy)
.filter(this::checkLocality)
.filter(this::checkInput)
.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));
}
public String getBotToken() {
return botToken;
}
public String getBotUsername() {
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>
* This method should be <b>overridden</b> if the user wants to restrict bot usage to only certain updates.
*
* @param update a Telegram {@link Update}
* @return <tt>true</tt> if the update satisfies the global flags
*/
protected boolean checkGlobalFlags(Update update) {
return true;
}
protected String getCommandPrefix() {
return "/";
}
protected String getCommandRegexSplit() {
return " ";
}
protected boolean allowContinuousText() {
return false;
}
protected void addExtension(AbilityExtension extension) {
this.extensions.add(extension);
}
protected void addExtensions(AbilityExtension... extensions) {
this.extensions.addAll(Arrays.asList(extensions));
}
protected void addExtensions(Collection<AbilityExtension> extensions) {
this.extensions.addAll(extensions);
}
/**
* Registers the declared abilities using method reflection. Also, replies are accumulated using the built abilities and standalone methods that return a Reply.
* <p>
* <b>Only abilities and replies with the <u>public</u> accessor are registered!</b>
*/
private void registerAbilities() {
try {
// Collect all classes that implement AbilityExtension declared in the bot
extensions.addAll(stream(getClass().getMethods())
.filter(checkReturnType(AbilityExtension.class))
.map(returnExtension(this))
.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 = Stream.concat(defaultAbsStream,
extensions.stream()
.flatMap(ext -> stream(ext.getClass().getMethods())
.filter(checkReturnType(Ability.class))
.map(returnAbility(ext))))
// Abilities are immutable, build it respectively
.collect(ImmutableMap::<String, Ability>builder,
(b, a) -> b.put(a.name(), a),
(b1, b2) -> b1.putAll(b2.build()))
.build();
// Extract all replies from every single extension instance
Stream<Reply> extensionReplies = extensions.stream()
.flatMap(ext -> stream(ext.getClass().getMethods())
.filter(checkReturnType(Reply.class))
.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(Reply::stream);
// Now create the replies registry (list)
replies = Stream.concat(abilityReplies, extensionReplies).collect(
ImmutableList::<Reply>builder,
Builder::add,
(b1, b2) -> b1.addAll(b2.build()))
.build();
} catch (IllegalStateException e) {
log.error("Duplicate names found while registering abilities. Make sure that the abilities declared don't clash with the reserved ones.", e);
throw new RuntimeException(e);
}
}
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 static Predicate<Method> checkReturnType(Class<?> clazz) {
return method -> clazz.isAssignableFrom(method.getReturnType());
}
/**
* Invokes the method and retrieves its return {@link Reply}.
*
* @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) {
return method -> {
try {
return (AbilityExtension) method.invoke(obj);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("Could not add ability extension", e);
throw new RuntimeException(e);
}
};
}
/**
* Invokes the method and retrieves its return {@link Ability}.
*
* @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 static Function<? super Method, Ability> returnAbility(Object obj) {
return method -> {
try {
return (Ability) method.invoke(obj);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("Could not add ability", e);
throw new RuntimeException(e);
}
};
}
/**
* Invokes the method and retrieves its return {@link Reply}.
*
* @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 static Function<? super Method, Reply> returnReply(Object obj) {
return method -> {
try {
return (Reply) method.invoke(obj);
} catch (IllegalAccessException | InvocationTargetException e) {
log.error("Could not add reply", e);
throw new RuntimeException(e);
}
};
}
private void postConsumption(Pair<MessageContext, Ability> pair) {
ofNullable(pair.b().postAction())
.ifPresent(consumer -> consumer.accept(pair.a()));
}
Pair<MessageContext, Ability> consumeUpdate(Pair<MessageContext, Ability> pair) {
pair.b().action().accept(pair.a());
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), this, trio.c()), trio.b());
}
boolean checkBlacklist(Update update) {
User user = getUser(update);
if (isNull(user)) {
return true;
}
int id = user.getId();
return id == creatorId() || !blacklist().contains(id);
}
boolean checkInput(Trio<Update, Ability, String[]> trio) {
String[] tokens = trio.c();
int abilityTokens = trio.b().tokens();
boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens);
if (!isOk)
silent.send(
getLocalizedMessage(
CHECK_INPUT_FAIL,
AbilityUtils.getUser(trio.a()).getLanguageCode(),
abilityTokens, abilityTokens == 1 ? "input" : "inputs"),
getChatId(trio.a()));
return isOk;
}
boolean checkLocality(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
Locality locality = isUserMessage(update) ? USER : GROUP;
Locality abilityLocality = trio.b().locality();
boolean isOk = abilityLocality == ALL || locality == abilityLocality;
if (!isOk)
silent.send(
getLocalizedMessage(
CHECK_LOCALITY_FAIL,
AbilityUtils.getUser(trio.a()).getLanguageCode(),
abilityLocality.toString().toLowerCase()),
getChatId(trio.a()));
return isOk;
}
boolean checkPrivacy(Trio<Update, Ability, String[]> trio) {
Update update = trio.a();
User user = AbilityUtils.getUser(update);
Privacy privacy;
int id = user.getId();
privacy = getPrivacy(update, id);
boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0;
if (!isOk)
silent.send(
getLocalizedMessage(
CHECK_PRIVACY_FAIL,
AbilityUtils.getUser(trio.a()).getLanguageCode()),
getChatId(trio.a()));
return isOk;
}
boolean validateAbility(Trio<Update, Ability, String[]> trio) {
return trio.b() != null;
}
Trio<Update, Ability, String[]> getAbility(Update update) {
// Handle updates without messages
// Passing through this function means that the global flags have passed
Message msg = update.getMessage();
if (!update.hasMessage() || !msg.hasText())
return Trio.of(update, abilities.get(DEFAULT), new String[]{});
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 {
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) {
return compile(format("@%s", botUsername), CASE_INSENSITIVE)
.matcher(token)
.replaceAll("");
}
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) {
updateUserId(user, endUser);
return endUser;
}
if (!user.equals(endUser)) {
updateUserId(user, endUser);
return endUser;
}
return user;
});
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
userIds().remove(oldUser.getUserName());
}
if (newUser.getUserName() != null) {
// Add new mapping with the new username
userIds().put(newUser.getUserName().toLowerCase(), newUser.getId());
}
}
boolean filterReply(Update update) {
return replies.stream()
.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();
// The following variable is required to avoid bug #JDK-8044546
BiFunction<Boolean, Predicate<Update>, Boolean> flagAnd = (flag, nextFlag) -> flag && nextFlag.test(update);
return ability.flags().stream()
.reduce(true, flagAnd, Boolean::logicalAnd);
}
}

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,36 @@
package org.telegram.abilitybots.api.db;
import org.telegram.abilitybots.api.util.Pair;
import java.util.*;
final class BackupMap<K, V> extends AbstractCollection<Pair<K, V>> implements Collection<Pair<K, V>> {
private Collection<Pair<K, V>> entries = new HashSet<>();
public BackupMap(){}
public BackupMap(Map<K, V> map) {
map.forEach((key, value) -> entries.add(Pair.of(key, value)));
}
public Map<K, V> toMap() {
Map<K, V> map = new HashMap<>();
entries.forEach(e -> map.put(e.a(), e.b()));
return map;
}
@Override
public Iterator<Pair<K, V>> iterator() {
return entries.iterator();
}
@Override
public int size() {
return entries.size();
}
@Override
public boolean add(Pair<K, V> kvPair) {
return entries.add(kvPair);
}
}

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

@ -1,7 +1,7 @@
package org.telegram.abilitybots.api.db;
import org.telegram.abilitybots.api.bot.AbilityBot;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.io.Closeable;
import java.util.List;
@ -11,8 +11,8 @@ import java.util.Set;
/**
* This interface represents the high-level methods exposed to the user when handling an {@link Update}.
* Example usage:
* <p><code>Ability.builder().action(ctx -> {db.getSet(USERS); doSomething();})* </code></p>
* {@link AbilityBot} contains a handle on the <code>db</code> that the user can use inside his declared abilities.
* <p><code>Ability.builder().action(ctx -> {db.getSet(USERS); doSomething();})</code></p>
* {@link BaseAbilityBot} contains a handle on the <code>db</code> that the user can use inside his declared abilities.
*
* @author Abbas Abou Daya
*/
@ -39,6 +39,13 @@ public interface DBContext extends Closeable {
*/
<T> Set<T> getSet(String name);
/**
* @param name the unique name of the {@link Var}
* @param <T> the type that the variable holds
* @return the variable with the specified name
*/
<T> Var<T> getVar(String name);
/**
* @return a high-level summary of the database structures (Sets, Lists, Maps, ...) present.
*/

View File

@ -3,24 +3,24 @@ 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.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 org.telegram.telegrambots.logging.BotLogger;
import java.io.IOException;
import java.util.*;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static java.lang.String.format;
import static java.util.Objects.isNull;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.StreamSupport.stream;
import static org.mapdb.Serializer.JAVA;
import static org.telegram.abilitybots.api.bot.AbilityBot.USERS;
/**
* An implementation of {@link DBContext} that relies on a {@link DB}.
@ -28,9 +28,9 @@ import static org.telegram.abilitybots.api.bot.AbilityBot.USERS;
* @author Abbas Abou Daya
* @see <a href="https://github.com/jankotek/mapdb">MapDB project</a>
*/
@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "WeakerAccess"})
public class MapDBContext implements DBContext {
private static final String TAG = DBContext.class.getSimpleName();
private static final Logger log = LoggerFactory.getLogger(MapDBContext.class);
private final DB db;
private final ObjectMapper objectMapper;
@ -70,7 +70,6 @@ public class MapDBContext implements DBContext {
.fileDB(name)
.fileMmapEnableIfSupported()
.closeOnJvmShutdown()
.cleanerHackEnable()
.transactionEnable()
.fileDeleteAfterClose()
.make();
@ -93,6 +92,11 @@ public class MapDBContext implements DBContext {
return (Set<T>) db.<T>hashSet(name, JAVA).createOrOpen();
}
@Override
public <T> Var<T> getVar(String name) {
return new MapDBVar<>((Atomic.Var<T>) db.atomicVar(name).createOrOpen());
}
@Override
public String summary() {
return stream(db.getAllNames().spliterator(), false)
@ -117,7 +121,7 @@ public class MapDBContext implements DBContext {
doRecover(backupData);
return true;
} catch (IOException e) {
BotLogger.error(format("Could not recover DB data from file with String representation %s", backup), TAG, e);
log.error(format("Could not recover DB data from file with String representation %s", backup), e);
// Attempt to fallback to data snapshot before recovery
doRecover(snapshot);
return false;
@ -163,7 +167,7 @@ public class MapDBContext implements DBContext {
}
@Override
public void close() throws IOException {
public void close() {
db.close();
}
@ -178,36 +182,29 @@ public class MapDBContext implements DBContext {
else if (struct instanceof List)
return Pair.of(entry.getKey(), newArrayList((List) struct));
else if (struct instanceof Map)
return Pair.of(entry.getKey(), newHashMap((Map) struct));
else
return Pair.of(entry.getKey(), struct);
return Pair.of(entry.getKey(), new BackupMap((Map) 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);
} else if (value instanceof Map) {
Map<Object, Object> entryMap = (Map) value;
// TODO: This is ugly
// Special handling of USERS since the key is an integer. JSON by default considers a map a JSONObject.
// Keys are serialized and deserialized as String
if (name.equals(USERS))
entryMap = entryMap.entrySet().stream()
.map(entry -> Pair.of(Integer.parseInt(entry.getKey().toString()), entry.getValue()))
.collect(toMap(Pair::a, Pair::b));
} else if (value instanceof BackupMap) {
Map<Object, Object> entryMap = ((BackupMap) value).toMap();
getMap(name).putAll(entryMap);
} 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 {
BotLogger.error(TAG, format("Unable to identify object type during DB recovery, entry name: %s", name));
log.error(format("Unable to identify object type during DB recovery, entry name: %s", name));
}
});
commit();
@ -217,7 +214,7 @@ public class MapDBContext implements DBContext {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
BotLogger.info(format("Failed to read the JSON representation of object: %s", obj), TAG, e);
log.info(format("Failed to read the JSON representation of object: %s", obj), e);
return "Error reading required data...";
}
}

View File

@ -0,0 +1,49 @@
package org.telegram.abilitybots.api.db;
import com.google.common.base.MoreObjects;
import org.mapdb.Atomic;
import java.util.Objects;
/**
* The MapDB variant for {@link DBContext#getVar(String)}.
*
* @param <T> the type of the inner variable
*/
public final class MapDBVar<T> implements Var<T> {
private Atomic.Var<T> var;
public MapDBVar(Atomic.Var<T> var) {
this.var = var;
}
@Override
public T get() {
return var.get();
}
@Override
public void set(T var) {
this.var.set(var);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MapDBVar<?> mapDBVar = (MapDBVar<?>) o;
return Objects.equals(var, mapDBVar.var);
}
@Override
public int hashCode() {
return Objects.hash(var);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("var", var)
.toString();
}
}

View File

@ -0,0 +1,19 @@
package org.telegram.abilitybots.api.db;
/**
* The interface governing a variable for abstract getters and setters.
* @param <T> the type of the variable
*
* @author Abbas Abou Daya
*/
public interface Var<T> {
/**
* @return the variable contained
*/
T get();
/**
* @param var the new variable value
*/
void set(T var);
}

View File

@ -2,8 +2,9 @@ package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.logging.BotLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.Arrays;
import java.util.List;
@ -16,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:
@ -34,23 +35,23 @@ import static org.apache.commons.lang3.StringUtils.*;
* @author Abbas Abou Daya
*/
public final class Ability {
private static final String TAG = Ability.class.getSimpleName();
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;
@ -63,12 +64,13 @@ public final class Ability {
this.action = checkNotNull(action, "Method action can't be empty. Please assign a function by using .action() method");
if (postAction == null)
BotLogger.info(TAG, format("No post action was detected for method with name [%s]", name));
log.info(format("No post action was detected for method with name [%s]", name));
this.flags = ofNullable(flags).map(Arrays::asList).orElse(newArrayList());
this.postAction = postAction;
this.replies = replies;
this.statsEnabled = statsEnabled;
}
public static AbilityBuilder builder() {
@ -95,6 +97,10 @@ public final class Ability {
return argNum;
}
public boolean statsEnabled() {
return statsEnabled;
}
public Consumer<MessageContext> action() {
return action;
}
@ -146,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 Flag[] flags;
private Predicate<Update>[] flags;
private AbilityBuilder() {
statsEnabled = false;
replies = newArrayList();
}
public AbilityBuilder action(Consumer<MessageContext> consumer) {
this.consumer = consumer;
this.action = consumer;
return this;
}
@ -170,7 +178,7 @@ public final class Ability {
return this;
}
public AbilityBuilder flag(Flag... flags) {
public AbilityBuilder flag(Predicate<Update>... flags) {
this.flags = flags;
return this;
}
@ -185,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;
}
@ -201,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

@ -1,138 +0,0 @@
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.telegram.telegrambots.api.objects.User;
import java.io.Serializable;
import java.util.Objects;
import java.util.StringJoiner;
import static org.apache.commons.lang3.StringUtils.isEmpty;
/**
* This class serves the purpose of separating the basic Telegram {@link User} and the augmented {@link EndUser}.
* <p>
* It adds proper hashCode, equals, toString as well as useful utility methods such as {@link EndUser#shortName} and {@link EndUser#fullName}.
*
* @author Abbas Abou Daya
*/
public final class EndUser implements Serializable {
@JsonProperty("id")
private final Integer id;
@JsonProperty("firstName")
private final String firstName;
@JsonProperty("lastName")
private final String lastName;
@JsonProperty("username")
private final String username;
private EndUser(Integer id, String firstName, String lastName, String username) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.username = username;
}
@JsonCreator
public static EndUser endUser(@JsonProperty("id") Integer id,
@JsonProperty("firstName") String firstName,
@JsonProperty("lastName") String lastName,
@JsonProperty("username") String username) {
return new EndUser(id, firstName, lastName, username);
}
/**
* Constructs an {@link EndUser} from a {@link User}.
*
* @param user the Telegram user
* @return an augmented end-user
*/
public static EndUser fromUser(User user) {
return new EndUser(user.getId(), user.getFirstName(), user.getLastName(), user.getUserName());
}
public int id() {
return id;
}
public String firstName() {
return firstName;
}
public String lastName() {
return lastName;
}
public String username() {
return username;
}
/**
* 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.
*
* @return the full name of the user
*/
public String fullName() {
StringJoiner name = new StringJoiner(" ");
if (!isEmpty(firstName))
name.add(firstName);
if (!isEmpty(lastName))
name.add(lastName);
return name.toString();
}
/**
* The short name is one of the following:
* <ol>
* <li>First name</li>
* <li>Last name</li>
* <li>Username</li>
* </ol>
* The method will try to return the first valid name in the specified order.
*
* @return the short name of the user
*/
public String shortName() {
if (!isEmpty(firstName))
return firstName;
if (!isEmpty(lastName))
return lastName;
return username;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
EndUser endUser = (EndUser) o;
return Objects.equals(id, endUser.id) &&
Objects.equals(firstName, endUser.firstName) &&
Objects.equals(lastName, endUser.lastName) &&
Objects.equals(username, endUser.username);
}
@Override
public int hashCode() {
return Objects.hash(id, firstName, lastName, username);
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("id", id)
.add("firstName", firstName)
.add("lastName", lastName)
.add("username", username)
.toString();
}
}

View File

@ -1,7 +1,7 @@
package org.telegram.abilitybots.api.objects;
import org.telegram.abilitybots.api.objects.Ability.AbilityBuilder;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.function.Consumer;
import java.util.function.Predicate;
@ -11,7 +11,7 @@ import static java.util.Objects.nonNull;
/**
* Flags are an conditions that are applied on an {@link Update}.
* <p>
* They can be used on {@link AbilityBuilder#flag(Flag...)} and on the post conditions in {@link AbilityBuilder#reply(Consumer, Predicate[])}.
* They can be used on {@link AbilityBuilder#flag(Predicate[])} and on the post conditions in {@link AbilityBuilder#reply(Consumer, Predicate[])}.
*
* @author Abbas Abou Daya
*/
@ -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,7 +2,9 @@ package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.abilitybots.api.bot.BaseAbilityBot;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import java.util.Arrays;
@ -14,26 +16,28 @@ import java.util.Arrays;
* @author Abbas Abou Daya
*/
public class MessageContext {
private final EndUser user;
private final User user;
private final Long chatId;
private final String[] arguments;
private final Update update;
private final BaseAbilityBot bot;
private MessageContext(Update update, EndUser 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, EndUser 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);
}
/**
* @return the originating Telegram user of this update
*/
public EndUser user() {
public User user() {
return user;
}
@ -44,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

@ -10,6 +10,10 @@ public enum Privacy {
* Anybody who is not a bot admin or its creator will be considered as a public user.
*/
PUBLIC,
/**
* Only group admins would get to initiate this command.
*/
GROUP_ADMIN,
/**
* A global admin of the bot, regardless of the group the bot is in.
*/

View File

@ -1,35 +1,53 @@
package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import org.telegram.telegrambots.api.objects.Update;
import com.google.common.collect.ImmutableList;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.List;
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.
* <p>
* If an update satisfies the {@link Reply#conditions}set by the reply, then it's safe to {@link Reply#actOn(Update)}.
* If an update satisfies the {@link Reply#conditions} set by the reply, then it's safe to {@link Reply#actOn(Update)}.
*
* @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,493 +0,0 @@
package org.telegram.abilitybots.api.sender;
import org.telegram.telegrambots.api.methods.*;
import org.telegram.telegrambots.api.methods.games.GetGameHighScores;
import org.telegram.telegrambots.api.methods.games.SetGameScore;
import org.telegram.telegrambots.api.methods.groupadministration.*;
import org.telegram.telegrambots.api.methods.pinnedmessages.PinChatMessage;
import org.telegram.telegrambots.api.methods.pinnedmessages.UnpinChatMessage;
import org.telegram.telegrambots.api.methods.send.*;
import org.telegram.telegrambots.api.methods.updates.DeleteWebhook;
import org.telegram.telegrambots.api.methods.updatingmessages.DeleteMessage;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageCaption;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageReplyMarkup;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageText;
import org.telegram.telegrambots.api.objects.*;
import org.telegram.telegrambots.api.objects.games.GameHighScore;
import org.telegram.telegrambots.api.objects.replykeyboard.ForceReplyKeyboard;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.exceptions.TelegramApiException;
import org.telegram.telegrambots.logging.BotLogger;
import org.telegram.telegrambots.updateshandlers.DownloadFileCallback;
import org.telegram.telegrambots.updateshandlers.SentCallback;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import static java.util.Optional.empty;
import static java.util.Optional.ofNullable;
/**
* The default implementation of the {@link MessageSender}. This serves as a proxy to the {@link DefaultAbsSender} methods.
* <p>Most of the methods below will be directly calling the bot's similar functions. However, there are some methods introduced to ease sending messages such as:</p>
* <ol>
* <li>{@link DefaultMessageSender#sendMd(String, long)} - with markdown</li>
* <li>{@link DefaultMessageSender#send(String, long)} - without markdown</li>
* </ol>
*
* @author Abbas Abou Daya
*/
public class DefaultMessageSender implements MessageSender {
private static final String TAG = MessageSender.class.getName();
private DefaultAbsSender bot;
public DefaultMessageSender(DefaultAbsSender bot) {
this.bot = bot;
}
@Override
public Optional<Message> send(String message, long id) {
return doSendMessage(message, id, false);
}
@Override
public Optional<Message> sendMd(String message, long id) {
return doSendMessage(message, id, true);
}
@Override
public Optional<Message> forceReply(String message, long id) {
SendMessage msg = new SendMessage();
msg.setText(message);
msg.setChatId(id);
msg.setReplyMarkup(new ForceReplyKeyboard());
return optionalSendMessage(msg);
}
@Override
public Boolean answerInlineQuery(AnswerInlineQuery answerInlineQuery) throws TelegramApiException {
return bot.execute(answerInlineQuery);
}
@Override
public Boolean sendChatAction(SendChatAction sendChatAction) throws TelegramApiException {
return bot.execute(sendChatAction);
}
@Override
public Message forwardMessage(ForwardMessage forwardMessage) throws TelegramApiException {
return bot.execute(forwardMessage);
}
@Override
public Message sendLocation(SendLocation sendLocation) throws TelegramApiException {
return bot.execute(sendLocation);
}
@Override
public Message sendVenue(SendVenue sendVenue) throws TelegramApiException {
return bot.execute(sendVenue);
}
@Override
public Message sendContact(SendContact sendContact) throws TelegramApiException {
return bot.execute(sendContact);
}
@Override
public Boolean kickMember(KickChatMember kickChatMember) throws TelegramApiException {
return bot.execute(kickChatMember);
}
@Override
public Boolean unbanMember(UnbanChatMember unbanChatMember) throws TelegramApiException {
return bot.execute(unbanChatMember);
}
@Override
public Boolean leaveChat(LeaveChat leaveChat) throws TelegramApiException {
return bot.execute(leaveChat);
}
@Override
public Chat getChat(GetChat getChat) throws TelegramApiException {
return bot.execute(getChat);
}
@Override
public List<ChatMember> getChatAdministrators(GetChatAdministrators getChatAdministrators) throws TelegramApiException {
return bot.execute(getChatAdministrators);
}
@Override
public ChatMember getChatMember(GetChatMember getChatMember) throws TelegramApiException {
return bot.execute(getChatMember);
}
@Override
public Integer getChatMemberCount(GetChatMemberCount getChatMemberCount) throws TelegramApiException {
return bot.execute(getChatMemberCount);
}
@Override
public Boolean setChatPhoto(SetChatPhoto setChatPhoto) throws TelegramApiException {
return bot.setChatPhoto(setChatPhoto);
}
@Override
public Boolean deleteChatPhoto(DeleteChatPhoto deleteChatPhoto) throws TelegramApiException {
return bot.execute(deleteChatPhoto);
}
@Override
public void deleteChatPhoto(DeleteChatPhoto deleteChatPhoto, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(deleteChatPhoto, sentCallback);
}
@Override
public Boolean pinChatMessage(PinChatMessage pinChatMessage) throws TelegramApiException {
return bot.execute(pinChatMessage);
}
@Override
public void pinChatMessage(PinChatMessage pinChatMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(pinChatMessage, sentCallback);
}
@Override
public Boolean unpinChatMessage(UnpinChatMessage unpinChatMessage) throws TelegramApiException {
return bot.execute(unpinChatMessage);
}
@Override
public void unpinChatMessage(UnpinChatMessage unpinChatMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(unpinChatMessage, sentCallback);
}
@Override
public Boolean promoteChatMember(PromoteChatMember promoteChatMember) throws TelegramApiException {
return bot.execute(promoteChatMember);
}
@Override
public void promoteChatMember(PromoteChatMember promoteChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(promoteChatMember, sentCallback);
}
@Override
public Boolean restrictChatMember(RestrictChatMember restrictChatMember) throws TelegramApiException {
return bot.execute(restrictChatMember);
}
@Override
public void restrictChatMember(RestrictChatMember restrictChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(restrictChatMember, sentCallback);
}
@Override
public Boolean setChatDescription(SetChatDescription setChatDescription) throws TelegramApiException {
return bot.execute(setChatDescription);
}
@Override
public void setChatDescription(SetChatDescription setChatDescription, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(setChatDescription, sentCallback);
}
@Override
public Boolean setChatTite(SetChatTitle setChatTitle) throws TelegramApiException {
return bot.execute(setChatTitle);
}
@Override
public void setChatTite(SetChatTitle setChatTitle, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(setChatTitle, sentCallback);
}
@Override
public String exportChatInviteLink(ExportChatInviteLink exportChatInviteLink) throws TelegramApiException {
return bot.execute(exportChatInviteLink);
}
@Override
public void exportChatInviteLinkAsync(ExportChatInviteLink exportChatInviteLink, SentCallback<String> sentCallback) throws TelegramApiException {
bot.executeAsync(exportChatInviteLink, sentCallback);
}
@Override
public Boolean deleteMessage(DeleteMessage deleteMessage) throws TelegramApiException {
return bot.execute(deleteMessage);
}
@Override
public void deleteMessageAsync(DeleteMessage deleteMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(deleteMessage, sentCallback);
}
@Override
public Serializable editMessageText(EditMessageText editMessageText) throws TelegramApiException {
return bot.execute(editMessageText);
}
@Override
public Serializable editMessageCaption(EditMessageCaption editMessageCaption) throws TelegramApiException {
return bot.execute(editMessageCaption);
}
@Override
public Serializable editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup) throws TelegramApiException {
return bot.execute(editMessageReplyMarkup);
}
@Override
public Boolean answerCallbackQuery(AnswerCallbackQuery answerCallbackQuery) throws TelegramApiException {
return bot.execute(answerCallbackQuery);
}
@Override
public UserProfilePhotos getUserProfilePhotos(GetUserProfilePhotos getUserProfilePhotos) throws TelegramApiException {
return bot.execute(getUserProfilePhotos);
}
@Override
public java.io.File downloadFile(String path) throws TelegramApiException {
return bot.downloadFile(path);
}
@Override
public void downloadFileAsync(String path, DownloadFileCallback<String> callback) throws TelegramApiException {
bot.downloadFileAsync(path, callback);
}
@Override
public java.io.File downloadFile(File file) throws TelegramApiException {
return bot.downloadFile(file);
}
@Override
public void downloadFileAsync(File file, DownloadFileCallback<File> callback) throws TelegramApiException {
bot.downloadFileAsync(file, callback);
}
@Override
public File getFile(GetFile getFile) throws TelegramApiException {
return bot.execute(getFile);
}
@Override
public User getMe() throws TelegramApiException {
return bot.getMe();
}
@Override
public WebhookInfo getWebhookInfo() throws TelegramApiException {
return bot.getWebhookInfo();
}
@Override
public Serializable setGameScore(SetGameScore setGameScore) throws TelegramApiException {
return bot.execute(setGameScore);
}
@Override
public Serializable getGameHighScores(GetGameHighScores getGameHighScores) throws TelegramApiException {
return bot.execute(getGameHighScores);
}
@Override
public Message sendGame(SendGame sendGame) throws TelegramApiException {
return bot.execute(sendGame);
}
@Override
public Boolean deleteWebhook(DeleteWebhook deleteWebhook) throws TelegramApiException {
return bot.execute(deleteWebhook);
}
@Override
public Message sendMessage(SendMessage sendMessage) throws TelegramApiException {
return bot.execute(sendMessage);
}
@Override
public void sendMessageAsync(SendMessage sendMessage, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(sendMessage, sentCallback);
}
@Override
public void answerInlineQueryAsync(AnswerInlineQuery answerInlineQuery, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(answerInlineQuery, sentCallback);
}
@Override
public void sendChatActionAsync(SendChatAction sendChatAction, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(sendChatAction, sentCallback);
}
@Override
public void forwardMessageAsync(ForwardMessage forwardMessage, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(forwardMessage, sentCallback);
}
@Override
public void sendLocationAsync(SendLocation sendLocation, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(sendLocation, sentCallback);
}
@Override
public void sendVenueAsync(SendVenue sendVenue, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(sendVenue, sentCallback);
}
@Override
public void sendContactAsync(SendContact sendContact, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(sendContact, sentCallback);
}
@Override
public void kickMemberAsync(KickChatMember kickChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(kickChatMember, sentCallback);
}
@Override
public void unbanMemberAsync(UnbanChatMember unbanChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(unbanChatMember, sentCallback);
}
@Override
public void leaveChatAsync(LeaveChat leaveChat, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(leaveChat, sentCallback);
}
@Override
public void getChatAsync(GetChat getChat, SentCallback<Chat> sentCallback) throws TelegramApiException {
bot.executeAsync(getChat, sentCallback);
}
@Override
public void getChatAdministratorsAsync(GetChatAdministrators getChatAdministrators, SentCallback<ArrayList<ChatMember>> sentCallback) throws TelegramApiException {
bot.executeAsync(getChatAdministrators, sentCallback);
}
@Override
public void getChatMemberAsync(GetChatMember getChatMember, SentCallback<ChatMember> sentCallback) throws TelegramApiException {
bot.executeAsync(getChatMember, sentCallback);
}
@Override
public void getChatMemberCountAsync(GetChatMemberCount getChatMemberCount, SentCallback<Integer> sentCallback) throws TelegramApiException {
bot.executeAsync(getChatMemberCount, sentCallback);
}
@Override
public void editMessageTextAsync(EditMessageText editMessageText, SentCallback<Serializable> sentCallback) throws TelegramApiException {
bot.executeAsync(editMessageText, sentCallback);
}
@Override
public void editMessageCaptionAsync(EditMessageCaption editMessageCaption, SentCallback<Serializable> sentCallback) throws TelegramApiException {
bot.executeAsync(editMessageCaption, sentCallback);
}
@Override
public void editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup, SentCallback<Serializable> sentCallback) throws TelegramApiException {
bot.executeAsync(editMessageReplyMarkup, sentCallback);
}
@Override
public void answerCallbackQueryAsync(AnswerCallbackQuery answerCallbackQuery, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(answerCallbackQuery, sentCallback);
}
@Override
public void getUserProfilePhotosAsync(GetUserProfilePhotos getUserProfilePhotos, SentCallback<UserProfilePhotos> sentCallback) throws TelegramApiException {
bot.executeAsync(getUserProfilePhotos, sentCallback);
}
@Override
public void getFileAsync(GetFile getFile, SentCallback<File> sentCallback) throws TelegramApiException {
bot.executeAsync(getFile, sentCallback);
}
@Override
public void getMeAsync(SentCallback<User> sentCallback) throws TelegramApiException {
bot.getMeAsync(sentCallback);
}
@Override
public void getWebhookInfoAsync(SentCallback<WebhookInfo> sentCallback) throws TelegramApiException {
bot.getWebhookInfoAsync(sentCallback);
}
@Override
public void setGameScoreAsync(SetGameScore setGameScore, SentCallback<Serializable> sentCallback) throws TelegramApiException {
bot.executeAsync(setGameScore, sentCallback);
}
@Override
public void getGameHighScoresAsync(GetGameHighScores getGameHighScores, SentCallback<ArrayList<GameHighScore>> sentCallback) throws TelegramApiException {
bot.executeAsync(getGameHighScores, sentCallback);
}
@Override
public void sendGameAsync(SendGame sendGame, SentCallback<Message> sentCallback) throws TelegramApiException {
bot.executeAsync(sendGame, sentCallback);
}
@Override
public void deleteWebhook(DeleteWebhook deleteWebhook, SentCallback<Boolean> sentCallback) throws TelegramApiException {
bot.executeAsync(deleteWebhook, sentCallback);
}
@Override
public Message sendDocument(SendDocument sendDocument) throws TelegramApiException {
return bot.sendDocument(sendDocument);
}
@Override
public Message sendPhoto(SendPhoto sendPhoto) throws TelegramApiException {
return bot.sendPhoto(sendPhoto);
}
@Override
public Message sendVideo(SendVideo sendVideo) throws TelegramApiException {
return bot.sendVideo(sendVideo);
}
@Override
public Message sendSticker(SendSticker sendSticker) throws TelegramApiException {
return bot.sendSticker(sendSticker);
}
@Override
public Message sendAudio(SendAudio sendAudio) throws TelegramApiException {
return bot.sendAudio(sendAudio);
}
@Override
public Message sendVoice(SendVoice sendVoice) throws TelegramApiException {
return bot.sendVoice(sendVoice);
}
private Optional<Message> doSendMessage(String txt, long groupId, boolean format) {
SendMessage smsg = new SendMessage();
smsg.setChatId(groupId);
smsg.setText(txt);
smsg.enableMarkdown(format);
return optionalSendMessage(smsg);
}
private Optional<Message> optionalSendMessage(SendMessage smsg) {
try {
return ofNullable(sendMessage(smsg));
} catch (TelegramApiException e) {
BotLogger.error("Could not send message", TAG, e);
return empty();
}
}
}

View File

@ -0,0 +1,139 @@
package org.telegram.abilitybots.api.sender;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.groupadministration.SetChatPhoto;
import org.telegram.telegrambots.meta.api.methods.send.*;
import org.telegram.telegrambots.meta.api.methods.stickers.AddStickerToSet;
import org.telegram.telegrambots.meta.api.methods.stickers.CreateNewStickerSet;
import org.telegram.telegrambots.meta.api.methods.stickers.UploadStickerFile;
import org.telegram.telegrambots.meta.api.objects.File;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.WebhookInfo;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.updateshandlers.DownloadFileCallback;
import org.telegram.telegrambots.meta.updateshandlers.SentCallback;
import java.io.Serializable;
/**
* The default implementation of the {@link MessageSender}. This serves as a proxy to the {@link DefaultAbsSender} methods.
*
* @author Abbas Abou Daya
*/
public class DefaultSender implements MessageSender {
private static final String TAG = MessageSender.class.getName();
private DefaultAbsSender bot;
public DefaultSender(DefaultAbsSender bot) {
this.bot = bot;
}
@Override
public <T extends Serializable, Method extends BotApiMethod<T>, Callback extends SentCallback<T>> void executeAsync(Method method, Callback callback) throws TelegramApiException {
bot.executeAsync(method, callback);
}
@Override
public <T extends Serializable, Method extends BotApiMethod<T>> T execute(Method method) throws TelegramApiException {
return bot.execute(method);
}
@Override
public Boolean addStickerToSet(AddStickerToSet addStickerToSet) throws TelegramApiException {
return bot.execute(addStickerToSet);
}
@Override
public Boolean createNewStickerSet(CreateNewStickerSet createNewStickerSet) throws TelegramApiException {
return bot.execute(createNewStickerSet);
}
@Override
public File uploadStickerFile(UploadStickerFile uploadStickerFile) throws TelegramApiException {
return bot.execute(uploadStickerFile);
}
@Override
public Boolean setChatPhoto(SetChatPhoto setChatPhoto) throws TelegramApiException {
return bot.execute(setChatPhoto);
}
@Override
public java.io.File downloadFile(String path) throws TelegramApiException {
return bot.downloadFile(path);
}
@Override
public void downloadFileAsync(String path, DownloadFileCallback<String> callback) throws TelegramApiException {
bot.downloadFileAsync(path, callback);
}
@Override
public java.io.File downloadFile(File file) throws TelegramApiException {
return bot.downloadFile(file);
}
@Override
public void downloadFileAsync(File file, DownloadFileCallback<File> callback) throws TelegramApiException {
bot.downloadFileAsync(file, callback);
}
@Override
public User getMe() throws TelegramApiException {
return bot.getMe();
}
@Override
public WebhookInfo getWebhookInfo() throws TelegramApiException {
return bot.getWebhookInfo();
}
@Override
public void getMeAsync(SentCallback<User> sentCallback) throws TelegramApiException {
bot.getMeAsync(sentCallback);
}
@Override
public void getWebhookInfoAsync(SentCallback<WebhookInfo> sentCallback) throws TelegramApiException {
bot.getWebhookInfoAsync(sentCallback);
}
@Override
public Message sendDocument(SendDocument sendDocument) throws TelegramApiException {
return bot.execute(sendDocument);
}
@Override
public Message sendPhoto(SendPhoto sendPhoto) throws TelegramApiException {
return bot.execute(sendPhoto);
}
@Override
public Message sendVideo(SendVideo sendVideo) throws TelegramApiException {
return bot.execute(sendVideo);
}
@Override
public Message sendSticker(SendSticker sendSticker) throws TelegramApiException {
return bot.execute(sendSticker);
}
@Override
public Message sendAudio(SendAudio sendAudio) throws TelegramApiException {
return bot.execute(sendAudio);
}
@Override
public Message sendVoice(SendVoice sendVoice) throws TelegramApiException {
return bot.execute(sendVoice);
}
@Override
public Message sendVideoNote(SendVideoNote sendVideoNote) {
return null;
}
}

View File

@ -1,28 +1,21 @@
package org.telegram.abilitybots.api.sender;
import org.telegram.telegrambots.api.methods.*;
import org.telegram.telegrambots.api.methods.games.GetGameHighScores;
import org.telegram.telegrambots.api.methods.games.SetGameScore;
import org.telegram.telegrambots.api.methods.groupadministration.*;
import org.telegram.telegrambots.api.methods.pinnedmessages.PinChatMessage;
import org.telegram.telegrambots.api.methods.pinnedmessages.UnpinChatMessage;
import org.telegram.telegrambots.api.methods.send.*;
import org.telegram.telegrambots.api.methods.updates.DeleteWebhook;
import org.telegram.telegrambots.api.methods.updatingmessages.DeleteMessage;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageCaption;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageReplyMarkup;
import org.telegram.telegrambots.api.methods.updatingmessages.EditMessageText;
import org.telegram.telegrambots.api.objects.*;
import org.telegram.telegrambots.api.objects.games.GameHighScore;
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
import org.telegram.telegrambots.meta.api.methods.groupadministration.SetChatPhoto;
import org.telegram.telegrambots.meta.api.methods.send.*;
import org.telegram.telegrambots.meta.api.methods.stickers.AddStickerToSet;
import org.telegram.telegrambots.meta.api.methods.stickers.CreateNewStickerSet;
import org.telegram.telegrambots.meta.api.methods.stickers.UploadStickerFile;
import org.telegram.telegrambots.meta.api.objects.File;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.WebhookInfo;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.exceptions.TelegramApiException;
import org.telegram.telegrambots.updateshandlers.DownloadFileCallback;
import org.telegram.telegrambots.updateshandlers.SentCallback;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.updateshandlers.DownloadFileCallback;
import org.telegram.telegrambots.meta.updateshandlers.SentCallback;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* A sender interface that replicates {@link DefaultAbsSender} methods.
@ -30,86 +23,19 @@ import java.util.Optional;
* @author Abbas Abou Daya
*/
public interface MessageSender {
Optional<Message> send(String message, long id);
Optional<Message> sendMd(String message, long id);
<T extends Serializable, Method extends BotApiMethod<T>, Callback extends SentCallback<T>> void executeAsync(Method method, Callback callback) throws TelegramApiException;
Optional<Message> forceReply(String message, long id);
<T extends Serializable, Method extends BotApiMethod<T>> T execute(Method method) throws TelegramApiException;
Boolean answerInlineQuery(AnswerInlineQuery answerInlineQuery) throws TelegramApiException;
Boolean addStickerToSet(AddStickerToSet addStickerToSet) throws TelegramApiException;
Boolean sendChatAction(SendChatAction sendChatAction) throws TelegramApiException;
public Boolean createNewStickerSet(CreateNewStickerSet createNewStickerSet) throws TelegramApiException;
Message forwardMessage(ForwardMessage forwardMessage) throws TelegramApiException;
Message sendLocation(SendLocation sendLocation) throws TelegramApiException;
Message sendVenue(SendVenue sendVenue) throws TelegramApiException;
Message sendContact(SendContact sendContact) throws TelegramApiException;
Boolean kickMember(KickChatMember kickChatMember) throws TelegramApiException;
Boolean unbanMember(UnbanChatMember unbanChatMember) throws TelegramApiException;
Boolean leaveChat(LeaveChat leaveChat) throws TelegramApiException;
Chat getChat(GetChat getChat) throws TelegramApiException;
List<ChatMember> getChatAdministrators(GetChatAdministrators getChatAdministrators) throws TelegramApiException;
ChatMember getChatMember(GetChatMember getChatMember) throws TelegramApiException;
Integer getChatMemberCount(GetChatMemberCount getChatMemberCount) throws TelegramApiException;
public File uploadStickerFile(UploadStickerFile uploadStickerFile) throws TelegramApiException;
Boolean setChatPhoto(SetChatPhoto setChatPhoto) throws TelegramApiException;
Boolean deleteChatPhoto(DeleteChatPhoto deleteChatPhoto) throws TelegramApiException;
void deleteChatPhoto(DeleteChatPhoto deleteChatPhoto, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean pinChatMessage(PinChatMessage pinChatMessage) throws TelegramApiException;
void pinChatMessage(PinChatMessage pinChatMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean unpinChatMessage(UnpinChatMessage unpinChatMessage) throws TelegramApiException;
void unpinChatMessage(UnpinChatMessage unpinChatMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean promoteChatMember(PromoteChatMember promoteChatMember) throws TelegramApiException;
void promoteChatMember(PromoteChatMember promoteChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean restrictChatMember(RestrictChatMember restrictChatMember) throws TelegramApiException;
void restrictChatMember(RestrictChatMember restrictChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean setChatDescription(SetChatDescription setChatDescription) throws TelegramApiException;
void setChatDescription(SetChatDescription setChatDescription, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Boolean setChatTite(SetChatTitle setChatTitle) throws TelegramApiException;
void setChatTite(SetChatTitle setChatTitle, SentCallback<Boolean> sentCallback) throws TelegramApiException;
String exportChatInviteLink(ExportChatInviteLink exportChatInviteLink) throws TelegramApiException;
void exportChatInviteLinkAsync(ExportChatInviteLink exportChatInviteLink, SentCallback<String> sentCallback) throws TelegramApiException;
Boolean deleteMessage(DeleteMessage deleteMessage) throws TelegramApiException;
void deleteMessageAsync(DeleteMessage deleteMessage, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Serializable editMessageText(EditMessageText editMessageText) throws TelegramApiException;
Serializable editMessageCaption(EditMessageCaption editMessageCaption) throws TelegramApiException;
Serializable editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup) throws TelegramApiException;
Boolean answerCallbackQuery(AnswerCallbackQuery answerCallbackQuery) throws TelegramApiException;
UserProfilePhotos getUserProfilePhotos(GetUserProfilePhotos getUserProfilePhotos) throws TelegramApiException;
java.io.File downloadFile(String path) throws TelegramApiException;
void downloadFileAsync(String path, DownloadFileCallback<String> callback) throws TelegramApiException;
@ -118,83 +44,25 @@ public interface MessageSender {
void downloadFileAsync(File file, DownloadFileCallback<File> callback) throws TelegramApiException;
File getFile(GetFile getFile) throws TelegramApiException;
User getMe() throws TelegramApiException;
WebhookInfo getWebhookInfo() throws TelegramApiException;
Serializable setGameScore(SetGameScore setGameScore) throws TelegramApiException;
Serializable getGameHighScores(GetGameHighScores getGameHighScores) throws TelegramApiException;
Message sendGame(SendGame sendGame) throws TelegramApiException;
Boolean deleteWebhook(DeleteWebhook deleteWebhook) throws TelegramApiException;
Message sendMessage(SendMessage sendMessage) throws TelegramApiException;
void sendMessageAsync(SendMessage sendMessage, SentCallback<Message> sentCallback) throws TelegramApiException;
void answerInlineQueryAsync(AnswerInlineQuery answerInlineQuery, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void sendChatActionAsync(SendChatAction sendChatAction, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void forwardMessageAsync(ForwardMessage forwardMessage, SentCallback<Message> sentCallback) throws TelegramApiException;
void sendLocationAsync(SendLocation sendLocation, SentCallback<Message> sentCallback) throws TelegramApiException;
void sendVenueAsync(SendVenue sendVenue, SentCallback<Message> sentCallback) throws TelegramApiException;
void sendContactAsync(SendContact sendContact, SentCallback<Message> sentCallback) throws TelegramApiException;
void kickMemberAsync(KickChatMember kickChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void unbanMemberAsync(UnbanChatMember unbanChatMember, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void leaveChatAsync(LeaveChat leaveChat, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void getChatAsync(GetChat getChat, SentCallback<Chat> sentCallback) throws TelegramApiException;
void getChatAdministratorsAsync(GetChatAdministrators getChatAdministrators, SentCallback<ArrayList<ChatMember>> sentCallback) throws TelegramApiException;
void getChatMemberAsync(GetChatMember getChatMember, SentCallback<ChatMember> sentCallback) throws TelegramApiException;
void getChatMemberCountAsync(GetChatMemberCount getChatMemberCount, SentCallback<Integer> sentCallback) throws TelegramApiException;
void editMessageTextAsync(EditMessageText editMessageText, SentCallback<Serializable> sentCallback) throws TelegramApiException;
void editMessageCaptionAsync(EditMessageCaption editMessageCaption, SentCallback<Serializable> sentCallback) throws TelegramApiException;
void editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup, SentCallback<Serializable> sentCallback) throws TelegramApiException;
void answerCallbackQueryAsync(AnswerCallbackQuery answerCallbackQuery, SentCallback<Boolean> sentCallback) throws TelegramApiException;
void getUserProfilePhotosAsync(GetUserProfilePhotos getUserProfilePhotos, SentCallback<UserProfilePhotos> sentCallback) throws TelegramApiException;
void getFileAsync(GetFile getFile, SentCallback<File> sentCallback) throws TelegramApiException;
void getMeAsync(SentCallback<User> sentCallback) throws TelegramApiException;
void getWebhookInfoAsync(SentCallback<WebhookInfo> sentCallback) throws TelegramApiException;
void setGameScoreAsync(SetGameScore setGameScore, SentCallback<Serializable> sentCallback) throws TelegramApiException;
void getGameHighScoresAsync(GetGameHighScores getGameHighScores, SentCallback<ArrayList<GameHighScore>> sentCallback) throws TelegramApiException;
void sendGameAsync(SendGame sendGame, SentCallback<Message> sentCallback) throws TelegramApiException;
void deleteWebhook(DeleteWebhook deleteWebhook, SentCallback<Boolean> sentCallback) throws TelegramApiException;
Message sendDocument(SendDocument sendDocument) throws TelegramApiException;
Message sendPhoto(SendPhoto sendPhoto) throws TelegramApiException;
Message sendVideo(SendVideo sendVideo) throws TelegramApiException;
Message sendSticker(SendSticker sendSticker) throws TelegramApiException;
Message sendAudio(SendAudio sendAudio) throws TelegramApiException;
Message sendVoice(SendVoice sendVoice) throws TelegramApiException;
Message sendVideoNote(SendVideoNote sendVideoNote) throws TelegramApiException;
Message sendSticker(SendSticker sendSticker) throws TelegramApiException;
}

View File

@ -0,0 +1,73 @@
package org.telegram.abilitybots.api.sender;
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;
/**
* A silent sender that returns {@link Optional} objects upon execution. Mainly used to decrease verboseness of exception handling.
*
* @author Abbas Abou Daya
*/
public class SilentSender {
private static final Logger log = LoggerFactory.getLogger(SilentSender.class);
private final MessageSender sender;
public SilentSender(MessageSender sender) {
this.sender = sender;
}
public Optional<Message> send(String message, long id) {
return doSendMessage(message, id, false);
}
public Optional<Message> sendMd(String message, long id) {
return doSendMessage(message, id, true);
}
public Optional<Message> forceReply(String message, long id) {
SendMessage msg = new SendMessage();
msg.setText(message);
msg.setChatId(Long.toString(id));
msg.setReplyMarkup(new ForceReplyKeyboard());
return execute(msg);
}
public <T extends Serializable, Method extends BotApiMethod<T>> Optional<T> execute(Method method) {
try {
return Optional.ofNullable(sender.execute(method));
} catch (TelegramApiException e) {
log.error("Could not execute bot API method", e);
return Optional.empty();
}
}
public <T extends Serializable, Method extends BotApiMethod<T>, Callback extends SentCallback<T>> void
executeAsync(Method method, Callback callable) {
try {
sender.executeAsync(method, callable);
} catch (TelegramApiException e) {
log.error("Could not execute bot API method", e);
}
}
private Optional<Message> doSendMessage(String txt, long groupId, boolean format) {
SendMessage smsg = new SendMessage();
smsg.setChatId(Long.toString(groupId));
smsg.setText(txt);
smsg.enableMarkdown(format);
return execute(smsg);
}
}

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

@ -0,0 +1,7 @@
package org.telegram.abilitybots.api.util;
/**
* An interface to mark a class as an extension. Similar to when a method returns an Ability, it is added to the bot, a method which returns an AbilityExtension will add all Abilities or Replies from this Extension to the bot.
*/
public interface AbilityExtension {
}

View File

@ -0,0 +1,31 @@
package org.telegram.abilitybots.api.util;
public final class AbilityMessageCodes {
public static String USER_NOT_FOUND = "userNotFound";
public static String CHECK_INPUT_FAIL = "checkInput.fail";
public static String CHECK_LOCALITY_FAIL = "checkLocality.fail";
public static String CHECK_PRIVACY_FAIL = "checkPrivacy.fail";
public static String ABILITY_COMMANDS_NOT_FOUND = "ability.commands.notFound";
public static String ABILITY_RECOVER_SUCCESS = "ability.recover.success";
public static String ABILITY_RECOVER_FAIL = "ability.recover.fail";
public static String ABILITY_RECOVER_MESSAGE = "ability.recover.message";
public static String ABILITY_RECOVER_ERROR = "ability.recover.error";
public static String ABILITY_BAN_SUCCESS = "ability.ban.success";
public static String ABILITY_BAN_FAIL = "ability.ban.fail";
public static String ABILITY_UNBAN_SUCCESS = "ability.unban.success";
public static String ABILITY_UNBAN_FAIL = "ability.unban.fail";
public static String ABILITY_PROMOTE_SUCCESS = "ability.promote.success";
public static String ABILITY_PROMOTE_FAIL = "ability.promote.fail";
public static String ABILITY_DEMOTE_SUCCESS = "ability.demote.success";
public static String ABILITY_DEMOTE_FAIL = "ability.demote.fail";
public static String ABILITY_CLAIM_SUCCESS = "ability.claim.success";
public static String ABILITY_CLAIM_FAIL = "ability.claim.fail";
}

View File

@ -1,19 +1,32 @@
package org.telegram.abilitybots.api.util;
import com.google.common.base.Strings;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.MessageContext;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.api.objects.User;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.api.objects.User;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringJoiner;
import java.util.function.Consumer;
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.*;
/**
* Helper and utility methods
*/
public final class AbilityUtils {
public static User EMPTY_USER = new User(0, "", false);
private AbilityUtils() {
}
@ -24,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;
}
/**
@ -45,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)) {
@ -59,11 +76,63 @@ 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");
}
}
/**
* A "best-effort" boolean stating whether the update is a group message or not.
*
* @param update a Telegram {@link Update}
* @return whether the update is linked to a group
*/
public static boolean isGroupUpdate(Update update) {
if (MESSAGE.test(update)) {
return update.getMessage().isGroupMessage();
} else if (CALLBACK_QUERY.test(update)) {
return update.getCallbackQuery().getMessage().isGroupMessage();
} else if (CHANNEL_POST.test(update)) {
return update.getChannelPost().isGroupMessage();
} else if (EDITED_CHANNEL_POST.test(update)) {
return update.getEditedChannelPost().isGroupMessage();
} else if (EDITED_MESSAGE.test(update)) {
return update.getEditedMessage().isGroupMessage();
} else {
return false;
}
}
/**
* A "best-effort" boolean stating whether the update is a super-group message or not.
*
* @param update a Telegram {@link Update}
* @return whether the update is linked to a group
*/
public static boolean isSuperGroupUpdate(Update update) {
if (MESSAGE.test(update)) {
return update.getMessage().isSuperGroupMessage();
} else if (CALLBACK_QUERY.test(update)) {
return update.getCallbackQuery().getMessage().isSuperGroupMessage();
} else if (CHANNEL_POST.test(update)) {
return update.getChannelPost().isSuperGroupMessage();
} else if (EDITED_CHANNEL_POST.test(update)) {
return update.getEditedChannelPost().isSuperGroupMessage();
} else if (EDITED_MESSAGE.test(update)) {
return update.getEditedMessage().isSuperGroupMessage();
} else {
return false;
}
}
/**
* Fetches the direct chat ID of the specified update.
*
@ -86,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");
}
@ -106,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;
}
}
@ -128,4 +203,95 @@ public final class AbilityUtils {
public static Predicate<Update> isReplyTo(String msg) {
return update -> update.getMessage().getReplyToMessage().getText().equals(msg);
}
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));
} catch (MissingResourceException e) {
bundle = getBundle("messages", Locale.ROOT);
}
}
String message = bundle.getString(messageCode);
return MessageFormat.format(message, 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);
}
/**
* The short name is one of the following:
* <ol>
* <li>First name</li>
* <li>Last name</li>
* <li>Username</li>
* </ol>
* The method will try to return the first valid name in the specified order.
*
* @return the short name of the user
*/
public static String shortName(User user) {
if (!isEmpty(user.getFirstName()))
return user.getFirstName();
if (!isEmpty(user.getLastName()))
return user.getLastName();
return user.getUserName();
}
/**
* 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
*/
public static String fullName(User user) {
StringJoiner name = new StringJoiner(" ");
if (!isEmpty(user.getFirstName()))
name.add(user.getFirstName());
if (!isEmpty(user.getLastName()))
name.add(user.getLastName());
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

@ -0,0 +1,27 @@
ability.commands.notFound=No available commands found.
ability.recover.success=I have successfully recovered.
ability.recover.fail=Oops, something went wrong during recovery.
ability.recover.message=I am ready to receive the backup file. Please reply to this message with the backup file attached.
ability.recover.error=I have failed to recover.
ability.ban.success={0} is now *banned*.
ability.ban.fail={0} is already *banned*.
ability.unban.success=@{0}, your ban has been *lifted*.
ability.unban.fail=@{0} is *not* on the *blacklist*.
ability.promote.success=@{0} has been *promoted*.
ability.promote.fail=@{0} is already an *admin*.
ability.demote.success=@{0} has been *demoted*.
ability.demote.fail=@{0} is *not* an *admin*.
ability.claim.success=You''re now my master.
ability.claim.fail=You''re already my master.
checkInput.fail=Sorry, this feature requires {0,number,integer} additional {1}.
checkLocality.fail=Sorry, {0}-only feature.
checkPrivacy.fail=Sorry, you don''t have the required access level to do that.
userNotFound=Sorry, I could not find the user [{0}].

View File

@ -0,0 +1,83 @@
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.MessageContext;
import org.telegram.abilitybots.api.sender.MessageSender;
import org.telegram.abilitybots.api.sender.SilentSender;
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.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, 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;
@BeforeEach
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);
bot.sender = sender;
bot.silent = silent;
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
void missingPublicCommandsLocalizedInEnglishByDefault() {
MessageContext context = mockContext(NO_LANGUAGE_USER);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1))
.send("No available commands found.", NO_LANGUAGE_USER.getId());
}
@Test
void missingPublicCommandsLocalizedInItalian() {
MessageContext context = mockContext(ITALIAN_USER);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1))
.send("Non sono presenti comandi disponibile.", ITALIAN_USER.getId());
}
public static class NoPublicCommandsBot extends AbilityBot {
NoPublicCommandsBot(String botToken, String botUsername, DBContext db) {
super(botToken, botUsername, db);
}
@Override
public int creatorId() {
return 1;
}
}
}

View File

@ -3,128 +3,222 @@ package org.telegram.abilitybots.api.bot;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
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.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.api.objects.*;
import org.telegram.telegrambots.exceptions.TelegramApiException;
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators;
import org.telegram.telegrambots.meta.api.objects.*;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.Charset;
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;
import static java.lang.String.format;
import static java.util.Collections.emptySet;
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.Assert.*;
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.AbilityBot.RECOVERY_MESSAGE;
import static org.telegram.abilitybots.api.bot.AbilityBot.RECOVER_SUCCESS;
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.EndUser.endUser;
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.Locality.ALL;
import static org.telegram.abilitybots.api.objects.Locality.GROUP;
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.PUBLIC;
import static org.telegram.abilitybots.api.objects.Privacy.*;
public class AbilityBotTest {
// Messages
private static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached.";
private static final String RECOVER_SUCCESS = "I have successfully recovered.";
private static final String[] EMPTY_ARRAY = {};
private static final long GROUP_ID = 10L;
private static final String TEST = "test";
private static final String[] TEXT = {TEST};
public static final EndUser MUSER = endUser(1, "first", "last", "username");
public static final EndUser CREATOR = endUser(1337, "creatorFirst", "creatorLast", "creatorUsername");
private DefaultBot bot;
private DefaultAbilities defaultAbs;
private DBContext db;
private MessageSender sender;
private SilentSender silent;
@Before
public void setUp() {
@BeforeEach
void setUp() {
db = offlineInstance("db");
bot = new DefaultBot(EMPTY, EMPTY, db);
bot.onRegister();
defaultAbs = new DefaultAbilities(bot);
sender = mock(MessageSender.class);
bot.setSender(sender);
silent = mock(SilentSender.class);
bot.sender = sender;
bot.silent = silent;
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
public void sendsPrivacyViolation() {
Update update = mockFullUpdate(MUSER, "/admin");
void sendsPrivacyViolation() {
Update update = mockFullUpdate(bot, USER, "/admin");
bot.onUpdateReceived(update);
verify(sender, times(1)).send(format("Sorry, %s-only feature.", "admin"), MUSER.id());
verify(silent, times(1)).send("Sorry, you don't have the required access level to do that.", USER.getId());
}
@Test
public void sendsLocalityViolation() {
Update update = mockFullUpdate(MUSER, "/group");
void sendsLocalityViolation() {
Update update = mockFullUpdate(bot, USER, "/group");
bot.onUpdateReceived(update);
verify(sender, times(1)).send(format("Sorry, %s-only feature.", "group"), MUSER.id());
verify(silent, times(1)).send(format("Sorry, %s-only feature.", "group"), USER.getId());
}
@Test
public void sendsInputArgsViolation() {
Update update = mockFullUpdate(MUSER, "/count 1 2 3");
void sendsInputArgsViolation() {
Update update = mockFullUpdate(bot, USER, "/count 1 2 3");
bot.onUpdateReceived(update);
verify(sender, times(1)).send(format("Sorry, this feature requires %d additional inputs.", 4), MUSER.id());
verify(silent, times(1)).send(format("Sorry, this feature requires %d additional inputs.", 4), USER.getId());
}
@Test
public void canProcessRepliesIfSatisfyRequirements() {
Update update = mockFullUpdate(MUSER, "must reply");
void canProcessRepliesIfSatisfyRequirements() {
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(sender, times(1)).send("reply", MUSER.id());
verify(silent, times(1)).send("reply", USER.getId());
}
@Test
public void canBackupDB() throws TelegramApiException {
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
public void canRecoverDB() throws TelegramApiException, IOException {
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();
Object backup = getDbBackup();
java.io.File backupFile = createBackupFile(backup);
when(sender.downloadFile(Matchers.any(File.class))).thenReturn(backupFile);
bot.recoverDB().replies().get(0).actOn(update);
// Support for null parameter matching since due to mocking API changes
when(sender.downloadFile(ArgumentMatchers.<File>isNull())).thenReturn(backupFile);
verify(sender, times(1)).send(RECOVER_SUCCESS, GROUP_ID);
assertEquals("Bot recovered but the DB is still not in sync", db.getSet(TEST), newHashSet(TEST));
assertTrue("Could not delete backup file", backupFile.delete());
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");
assertTrue(backupFile.delete(), "Could not delete backup file");
}
@Test
public void canFilterOutReplies() {
void canFilterOutReplies() {
Update update = mock(Update.class);
when(update.hasMessage()).thenReturn(false);
@ -132,119 +226,96 @@ public class AbilityBotTest {
}
@Test
public void canDemote() {
addUsers(MUSER);
bot.admins().add(MUSER.id());
void canDemote() {
addUsers(USER);
bot.admins().add(USER.getId());
MessageContext context = defaultContext();
bot.demoteAdmin().action().accept(context);
defaultAbs.demoteAdmin().action().accept(context);
Set<Integer> actual = bot.admins();
Set<Integer> expected = emptySet();
assertEquals("Could not sudont super-admin", expected, actual);
assertEquals(expected, actual, "Could not sudont super-admin");
}
@Test
public void canPromote() {
addUsers(MUSER);
void canPromote() {
addUsers(USER);
MessageContext context = defaultContext();
bot.promoteAdmin().action().accept(context);
defaultAbs.promoteAdmin().action().accept(context);
Set<Integer> actual = bot.admins();
Set<Integer> expected = newHashSet(MUSER.id());
assertEquals("Could not sudo user", expected, actual);
Set<Integer> expected = newHashSet(USER.getId());
assertEquals(expected, actual, "Could not sudo user");
}
@Test
public void canBanUser() {
addUsers(MUSER);
void canBanUser() {
addUsers(USER);
MessageContext context = defaultContext();
bot.banUser().action().accept(context);
defaultAbs.banUser().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet(MUSER.id());
assertEquals("The ban was not emplaced", expected, actual);
Set<Integer> expected = newHashSet(USER.getId());
assertEquals(expected, actual, "The ban was not emplaced");
}
@Test
public void canUnbanUser() {
addUsers(MUSER);
bot.blacklist().add(MUSER.id());
void canUnbanUser() {
addUsers(USER);
bot.blacklist().add(USER.getId());
MessageContext context = defaultContext();
bot.unbanUser().action().accept(context);
defaultAbs.unbanUser().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet();
assertEquals("The ban was not lifted", expected, actual);
assertEquals(expected, actual, "The ban was not lifted");
}
@NotNull
private MessageContext defaultContext() {
MessageContext context = mock(MessageContext.class);
when(context.user()).thenReturn(CREATOR);
when(context.firstArg()).thenReturn(MUSER.username());
return context;
return mockContext(CREATOR, GROUP_ID, USER.getUserName());
}
@Test
public void cannotBanCreator() {
addUsers(MUSER, CREATOR);
MessageContext context = mock(MessageContext.class);
when(context.user()).thenReturn(MUSER);
when(context.firstArg()).thenReturn(CREATOR.username());
void cannotBanCreator() {
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(MUSER.id());
assertEquals("Impostor was not added to the blacklist", expected, actual);
Set<Integer> expected = newHashSet(USER.getId());
assertEquals(expected, actual, "Impostor was not added to the blacklist");
}
private void addUsers(EndUser... users) {
private void addUsers(User... users) {
Arrays.stream(users).forEach(user -> {
bot.users().put(user.id(), user);
bot.userIds().put(user.username().toLowerCase(), user.id());
bot.users().put(user.getId(), user);
bot.userIds().put(user.getUserName().toLowerCase(), user.getId());
});
}
@Test
public void creatorCanClaimBot() {
MessageContext context = mock(MessageContext.class);
when(context.user()).thenReturn(CREATOR);
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.id());
assertEquals("Creator was not properly added to the super admins set", expected, actual);
Set<Integer> expected = newHashSet(CREATOR.getId());
assertEquals(expected, actual, "Creator was not properly added to the super admins set");
}
@Test
public void userGetsBannedIfClaimsBot() {
addUsers(MUSER);
MessageContext context = mock(MessageContext.class);
when(context.user()).thenReturn(MUSER);
bot.claimCreator().action().accept(context);
Set<Integer> actual = bot.blacklist();
Set<Integer> expected = newHashSet(MUSER.id());
assertEquals("Could not find user on the blacklist", expected, actual);
actual = bot.admins();
expected = emptySet();
assertEquals("Admins set is not empty", expected, actual);
}
@Test
public void bannedCreatorPassesBlacklistCheck() {
bot.blacklist().add(CREATOR.id());
void bannedCreatorPassesBlacklistCheck() {
bot.blacklist().add(CREATOR.getId());
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
@ -252,61 +323,59 @@ public class AbilityBotTest {
mockUser(update, message, user);
boolean notBanned = bot.checkBlacklist(update);
assertTrue("Creator is banned", notBanned);
assertTrue(notBanned, "Creator is banned");
}
@Test
public void canAddUser() {
void canAddUser() {
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
mockAlternateUser(update, message, user, MUSER);
mockAlternateUser(update, message, USER);
bot.addUser(update);
Map<String, Integer> expectedUserIds = ImmutableMap.of(MUSER.username(), MUSER.id());
Map<Integer, EndUser> expectedUsers = ImmutableMap.of(MUSER.id(), MUSER);
assertEquals("User was not added", expectedUserIds, bot.userIds());
assertEquals("User was not added", expectedUsers, bot.users());
Map<String, Integer> expectedUserIds = ImmutableMap.of(USER.getUserName(), USER.getId());
Map<Integer, User> expectedUsers = ImmutableMap.of(USER.getId(), USER);
assertEquals(expectedUserIds, bot.userIds(), "User was not added");
assertEquals(expectedUsers, bot.users(), "User was not added");
}
@Test
public void canEditUser() {
addUsers(MUSER);
void canEditUser() {
addUsers(USER);
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
String newUsername = MUSER.username() + "-test";
String newFirstName = MUSER.firstName() + "-test";
String newLastName = MUSER.lastName() + "-test";
int sameId = MUSER.id();
EndUser changedUser = endUser(sameId, newFirstName, newLastName, newUsername);
String newUsername = USER.getUserName() + "-test";
String newFirstName = USER.getFirstName() + "-test";
String newLastName = USER.getLastName() + "-test";
int sameId = USER.getId();
User changedUser = new User(sameId, newFirstName, false, newLastName, newUsername, "en", false, false, false);
mockAlternateUser(update, message, user, changedUser);
mockAlternateUser(update, message, changedUser);
bot.addUser(update);
Map<String, Integer> expectedUserIds = ImmutableMap.of(changedUser.username(), changedUser.id());
Map<Integer, EndUser> expectedUsers = ImmutableMap.of(changedUser.id(), changedUser);
assertEquals("User was not properly edited", bot.userIds(), expectedUserIds);
assertEquals("User was not properly edited", expectedUsers, expectedUsers);
Map<String, Integer> expectedUserIds = ImmutableMap.of(changedUser.getUserName(), changedUser.getId());
Map<Integer, User> expectedUsers = ImmutableMap.of(changedUser.getId(), changedUser);
assertEquals(bot.userIds(), expectedUserIds, "User was not properly edited");
assertEquals(expectedUsers, expectedUsers, "User was not properly edited");
}
@Test
public void canValidateAbility() {
void canValidateAbility() {
Trio<Update, Ability, String[]> invalidPair = Trio.of(null, null, null);
Ability validAbility = getDefaultBuilder().build();
Trio<Update, Ability, String[]> validPair = Trio.of(null, validAbility, null);
assertEquals("Bot can't validate ability properly", false, bot.validateAbility(invalidPair));
assertEquals("Bot can't validate ability properly", true, bot.validateAbility(validPair));
assertFalse(bot.validateAbility(invalidPair), "Bot can't validate ability properly");
assertTrue(bot.validateAbility(validPair), "Bot can't validate ability properly");
}
@Test
public void canCheckInput() {
Update update = mockFullUpdate(MUSER, "/something");
void canCheckInput() {
Update update = mockFullUpdate(bot, USER, "/something");
Ability abilityWithOneInput = getDefaultBuilder()
.build();
Ability abilityWithZeroInput = getDefaultBuilder()
@ -316,54 +385,95 @@ public class AbilityBotTest {
Trio<Update, Ability, String[]> trioOneArg = Trio.of(update, abilityWithOneInput, TEXT);
Trio<Update, Ability, String[]> trioZeroArg = Trio.of(update, abilityWithZeroInput, TEXT);
assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioOneArg));
assertTrue(bot.checkInput(trioOneArg), "Unexpected result when applying token filter");
trioOneArg = Trio.of(update, abilityWithOneInput, addAll(TEXT, TEXT));
assertEquals("Unexpected result when applying token filter", false, bot.checkInput(trioOneArg));
assertFalse(bot.checkInput(trioOneArg), "Unexpected result when applying token filter");
assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioZeroArg));
assertTrue(bot.checkInput(trioZeroArg), "Unexpected result when applying token filter");
trioZeroArg = Trio.of(update, abilityWithZeroInput, EMPTY_ARRAY);
assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioZeroArg));
assertTrue(bot.checkInput(trioZeroArg), "Unexpected result when applying token filter");
}
@Test
public void canCheckPrivacy() {
void canCheckPrivacy() {
Update update = mock(Update.class);
Message message = mock(Message.class);
org.telegram.telegrambots.api.objects.User user = mock(User.class);
User user = mock(User.class);
Ability publicAbility = getDefaultBuilder().privacy(PUBLIC).build();
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
Ability adminAbility = getDefaultBuilder().privacy(ADMIN).build();
Ability creatorAbility = getDefaultBuilder().privacy(Privacy.CREATOR).build();
Trio<Update, Ability, String[]> publicTrio = Trio.of(update, publicAbility, TEXT);
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
Trio<Update, Ability, String[]> adminTrio = Trio.of(update, adminAbility, TEXT);
Trio<Update, Ability, String[]> creatorTrio = Trio.of(update, creatorAbility, TEXT);
mockUser(update, message, user);
assertEquals("Unexpected result when checking for privacy", true, bot.checkPrivacy(publicTrio));
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(adminTrio));
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(creatorTrio));
assertTrue(bot.checkPrivacy(publicTrio), "Unexpected result when checking for privacy");
assertFalse(bot.checkPrivacy(groupAdminTrio), "Unexpected result when checking for privacy");
assertFalse(bot.checkPrivacy(adminTrio), "Unexpected result when checking for privacy");
assertFalse(bot.checkPrivacy(creatorTrio), "Unexpected result when checking for privacy");
}
@Test
public void canBlockAdminsFromCreatorAbilities() {
void canValidateGroupAdminPrivacy() {
Update update = mock(Update.class);
Message message = mock(Message.class);
org.telegram.telegrambots.api.objects.User user = mock(User.class);
User user = mock(User.class);
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
mockUser(update, message, user);
when(message.isGroupMessage()).thenReturn(true);
ChatMember member = mock(ChatMember.class);
when(member.getUser()).thenReturn(user);
when(member.getUser()).thenReturn(user);
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(Optional.of(newArrayList(member)));
assertTrue(bot.checkPrivacy(groupAdminTrio), "Unexpected result when checking for privacy");
}
@Test
void canRestrictNormalUsersFromGroupAdminAbilities() {
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
Ability groupAdminAbility = getDefaultBuilder().privacy(GROUP_ADMIN).build();
Trio<Update, Ability, String[]> groupAdminTrio = Trio.of(update, groupAdminAbility, TEXT);
mockUser(update, message, user);
when(message.isGroupMessage()).thenReturn(true);
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(empty());
assertFalse(bot.checkPrivacy(groupAdminTrio), "Unexpected result when checking for privacy");
}
@Test
void canBlockAdminsFromCreatorAbilities() {
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
Ability creatorAbility = getDefaultBuilder().privacy(Privacy.CREATOR).build();
Trio<Update, Ability, String[]> creatorTrio = Trio.of(update, creatorAbility, TEXT);
bot.admins().add(MUSER.id());
bot.admins().add(USER.getId());
mockUser(update, message, user);
assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(creatorTrio));
assertFalse(bot.checkPrivacy(creatorTrio), "Unexpected result when checking for privacy");
}
@Test
public void canCheckLocality() {
void canCheckLocality() {
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
@ -378,40 +488,36 @@ public class AbilityBotTest {
mockUser(update, message, user);
when(message.isUserMessage()).thenReturn(true);
assertEquals("Unexpected result when checking for locality", true, bot.checkLocality(publicTrio));
assertEquals("Unexpected result when checking for locality", true, bot.checkLocality(userTrio));
assertEquals("Unexpected result when checking for locality", false, bot.checkLocality(groupTrio));
assertTrue(bot.checkLocality(publicTrio), "Unexpected result when checking for locality");
assertTrue(bot.checkLocality(userTrio), "Unexpected result when checking for locality");
assertFalse(bot.checkLocality(groupTrio), "Unexpected result when checking for locality");
}
@Test
public void canRetrieveContext() {
void canRetrieveContext() {
Update update = mock(Update.class);
Message message = mock(Message.class);
User user = mock(User.class);
Ability ability = getDefaultBuilder().build();
Trio<Update, Ability, String[]> trio = Trio.of(update, ability, TEXT);
when(message.getChatId()).thenReturn(GROUP_ID);
mockUser(update, message, user);
mockUser(update, message, USER);
Pair<MessageContext, Ability> actualPair = bot.getContext(trio);
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, MUSER, GROUP_ID, TEXT), ability);
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, USER, GROUP_ID, bot, TEXT), ability);
assertEquals("Unexpected result when fetching for context", expectedPair, actualPair);
assertEquals(expectedPair, actualPair, "Unexpected result when fetching for context");
}
@Test
public void canCheckGlobalFlags() {
void defaultGlobalFlagIsTrue() {
Update update = mock(Update.class);
Message message = mock(Message.class);
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
assertEquals("Unexpected result when checking for locality", true, bot.checkGlobalFlags(update));
assertTrue(bot.checkGlobalFlags(update), "Unexpected result when checking for the default global flags");
}
@Test(expected = ArithmeticException.class)
public void canConsumeUpdate() {
@SuppressWarnings({"NumericOverflow", "divzero"})
@Test
void canConsumeUpdate() {
Ability ability = getDefaultBuilder()
.action((context) -> {
int x = 1 / 0;
@ -420,11 +526,11 @@ public class AbilityBotTest {
Pair<MessageContext, Ability> pair = Pair.of(context, ability);
bot.consumeUpdate(pair);
Assertions.assertThrows(ArithmeticException.class, () -> bot.consumeUpdate(pair));
}
@Test
public void canFetchAbility() {
void canFetchAbility() {
Update update = mock(Update.class);
Message message = mock(Message.class);
@ -439,11 +545,30 @@ public class AbilityBotTest {
Ability expected = bot.testAbility();
Ability actual = trio.b();
assertEquals("Wrong ability was fetched", expected, actual);
assertEquals(expected, actual, "Wrong ability was fetched");
}
@Test
public void canFetchDefaultAbility() {
void canFetchAbilityCaseInsensitive() {
Update update = mock(Update.class);
Message message = mock(Message.class);
String text = "/tESt";
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(update.getMessage().hasText()).thenReturn(true);
when(message.getText()).thenReturn(text);
Trio<Update, Ability, String[]> trio = bot.getAbility(update);
Ability expected = bot.testAbility();
Ability actual = trio.b();
assertEquals(expected, actual, "Wrong ability was fetched");
}
@Test
void canFetchDefaultAbility() {
Update update = mock(Update.class);
Message message = mock(Message.class);
@ -456,11 +581,11 @@ public class AbilityBotTest {
Ability expected = bot.defaultAbility();
Ability actual = trio.b();
assertEquals("Wrong ability was fetched", expected, actual);
assertEquals(expected, actual, "Wrong ability was fetched");
}
@Test
public void canCheckAbilityFlags() {
void canCheckAbilityFlags() {
Update update = mock(Update.class);
Message message = mock(Message.class);
@ -475,80 +600,97 @@ public class AbilityBotTest {
Trio<Update, Ability, String[]> docTrio = Trio.of(update, documentAbility, TEXT);
Trio<Update, Ability, String[]> textTrio = Trio.of(update, textAbility, TEXT);
assertEquals("Unexpected result when checking for message flags", false, bot.checkMessageFlags(docTrio));
assertEquals("Unexpected result when checking for message flags", true, bot.checkMessageFlags(textTrio));
assertFalse(bot.checkMessageFlags(docTrio), "Unexpected result when checking for message flags");
assertTrue(bot.checkMessageFlags(textTrio), "Unexpected result when checking for message flags");
}
@Test
public void canReportCommands() {
void canReportCommands() {
MessageContext context = mockContext(USER, GROUP_ID);
defaultAbs.reportCommands().action().accept(context);
verify(silent, times(1)).send("default - dis iz default command", GROUP_ID);
}
@Test
void canPrintCommandsBasedOnPrivacy() {
Update update = mock(Update.class);
Message message = mock(Message.class);
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(message.hasText()).thenReturn(true);
MessageContext context = mock(MessageContext.class);
when(context.chatId()).thenReturn(GROUP_ID);
MessageContext creatorCtx = newContext(update, CREATOR, GROUP_ID, bot);
bot.reportCommands().action().accept(context);
defaultAbs.commands().action().accept(creatorCtx);
verify(sender, times(1)).send("default - dis iz default command", GROUP_ID);
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);
}
@After
public void tearDown() throws IOException {
db.clear();
db.close();
}
private User mockUser(EndUser fromUser) {
User user = mock(User.class);
when(user.getId()).thenReturn(fromUser.id());
when(user.getUserName()).thenReturn(fromUser.username());
when(user.getFirstName()).thenReturn(fromUser.firstName());
when(user.getLastName()).thenReturn(fromUser.lastName());
return user;
}
@NotNull
private Update mockFullUpdate(EndUser fromUser, String args) {
bot.users().put(MUSER.id(), MUSER);
bot.users().put(CREATOR.id(), CREATOR);
bot.userIds().put(CREATOR.username(), CREATOR.id());
bot.userIds().put(MUSER.username(), MUSER.id());
bot.admins().add(CREATOR.id());
User user = mockUser(fromUser);
@Test
void printsOnlyPublicCommandsForNormalUser() {
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) fromUser.id());
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
return update;
when(message.hasText()).thenReturn(true);
MessageContext userCtx = newContext(update, USER, GROUP_ID, bot);
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);
}
@Test
void canProcessChannelPosts() {
Update update = mock(Update.class);
Message message = mock(Message.class);
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) {
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
when(message.getFrom()).thenReturn(user);
when(user.getFirstName()).thenReturn(MUSER.firstName());
when(user.getLastName()).thenReturn(MUSER.lastName());
when(user.getId()).thenReturn(MUSER.id());
when(user.getUserName()).thenReturn(MUSER.username());
}
private void mockAlternateUser(Update update, Message message, User user, EndUser changedUser) {
when(user.getId()).thenReturn(changedUser.id());
when(user.getFirstName()).thenReturn(changedUser.firstName());
when(user.getLastName()).thenReturn(changedUser.lastName());
when(user.getUserName()).thenReturn(changedUser.username());
private void mockAlternateUser(Update update, Message message, User user) {
when(message.getFrom()).thenReturn(user);
when(update.hasMessage()).thenReturn(true);
when(update.getMessage()).thenReturn(message);
@ -560,10 +702,13 @@ 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);
when(botMessage.getText()).thenReturn(RECOVERY_MESSAGE);
when(message.isReply()).thenReturn(true);
when(update.hasMessage()).thenReturn(true);
when(message.hasDocument()).thenReturn(true);
when(message.getReplyToMessage()).thenReturn(botMessage);
when(message.getChatId()).thenReturn(GROUP_ID);

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

@ -1,10 +1,11 @@
package org.telegram.abilitybots.api.bot;
import com.google.common.annotations.VisibleForTesting;
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.sender.MessageSender;
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;
@ -19,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")
@ -38,8 +43,8 @@ public class DefaultBot extends AbilityBot {
return getDefaultBuilder()
.name(DEFAULT)
.info("dis iz default command")
.reply(upd -> sender.send("reply", upd.getMessage().getChatId()), MESSAGE, update -> update.getMessage().getText().equals("must reply"))
.reply(upd -> sender.send("reply", upd.getCallbackQuery().getMessage().getChatId()), CALLBACK_QUERY)
.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();
}
@ -64,15 +69,17 @@ 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();
}
@VisibleForTesting
void setSender(MessageSender sender) {
this.sender = sender;
}
}

View File

@ -0,0 +1,92 @@
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.objects.Ability;
import org.telegram.abilitybots.api.util.AbilityExtension;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
import static org.telegram.abilitybots.api.objects.Locality.ALL;
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
class ExtensionTest {
private ExtensionUsingBot bot;
@BeforeEach
void setUp() {
bot = new ExtensionUsingBot();
bot.onRegister();
}
@AfterEach
void tearDown() throws IOException {
bot.db.clear();
bot.db.close();
}
@Test
void methodReturningAbilities() {
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) {
return bot.abilities().values().stream().map(Ability::name).anyMatch(name::equals);
}
public static class ExtensionUsingBot extends AbilityBot {
ExtensionUsingBot() {
super("", "", offlineInstance("testing"));
addExtension(new AbilityBotExtension("addedInConstructor"));
}
@Override
public int creatorId() {
return 0;
}
public AbilityBotExtension methodReturningExtensionSubClass() {
return new AbilityBotExtension("returningSubClass");
}
public AbilityExtension methodReturningExtensionSuperClass() {
return new AbilityBotExtension("returningSuperClass");
}
public Ability methodReturningAbility() {
return Ability.builder()
.name("direct")
.info("Test ability")
.locality(ALL)
.privacy(PUBLIC)
.action(messageContext -> {
})
.build();
}
}
public static class AbilityBotExtension implements AbilityExtension {
private String name;
AbilityBotExtension(String name) {
this.name = name;
}
public Ability abc() {
return Ability.builder()
.name(name + "0abc")
.info("Test ability")
.locality(ALL)
.privacy(PUBLIC)
.action(ctx -> {
})
.build();
}
}
}

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

@ -1,9 +1,10 @@
package org.telegram.abilitybots.api.db;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.telegram.abilitybots.api.objects.EndUser;
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.telegrambots.meta.api.objects.User;
import java.io.IOException;
import java.util.Map;
@ -11,69 +12,95 @@ 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.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.telegram.abilitybots.api.bot.AbilityBot.USERS;
import static org.telegram.abilitybots.api.bot.AbilityBot.USER_ID;
import static org.telegram.abilitybots.api.bot.AbilityBotTest.CREATOR;
import static org.telegram.abilitybots.api.bot.AbilityBotTest.MUSER;
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.TestUtils.CREATOR;
import static org.telegram.abilitybots.api.bot.TestUtils.USER;
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
public class MapDBContextTest {
class MapDBContextTest {
private static final String USERS = "USERS";
private static final String USER_ID = "USER_ID";
private static final String TEST = "TEST";
private DBContext db;
@Before
public void setUp() {
@BeforeEach
void setUp() {
db = offlineInstance("db");
}
@AfterEach
void tearDown() throws IOException {
db.clear();
db.close();
}
@Test
public void canRecoverDB() throws IOException {
Map<Integer, EndUser> users = db.getMap(USERS);
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);
Map<String, Integer> userIds = db.getMap(USER_ID);
users.put(CREATOR.id(), CREATOR);
users.put(MUSER.id(), MUSER);
userIds.put(CREATOR.username(), CREATOR.id());
userIds.put(MUSER.username(), MUSER.id());
users.put(CREATOR.getId(), CREATOR);
users.put(USER.getId(), USER);
userIds.put(CREATOR.getUserName(), CREATOR.getId());
userIds.put(USER.getUserName(), USER.getId());
db.getSet("AYRE").add(123123);
Map<Integer, EndUser> originalUsers = newHashMap(users);
Map<Integer, User> originalUsers = newHashMap(users);
String beforeBackupInfo = db.info(USERS);
Object jsonBackup = db.backup();
db.clear();
boolean recovered = db.recover(jsonBackup);
Map<Integer, EndUser> recoveredUsers = db.getMap(USERS);
Map<Integer, User> recoveredUsers = db.getMap(USERS);
String afterRecoveryInfo = db.info(USERS);
assertTrue("Could not recover database successfully", recovered);
assertEquals("Map info before and after recovery is different", beforeBackupInfo, afterRecoveryInfo);
assertEquals("Map before and after recovery are not equal", originalUsers, recoveredUsers);
assertTrue(recovered, "Could not recover database successfully");
assertEquals(beforeBackupInfo, afterRecoveryInfo, "Map info before and after recovery is different");
assertEquals(originalUsers, recoveredUsers, "Map before and after recovery are not equal");
}
@Test
public void canFallbackDBIfRecoveryFails() throws IOException {
Set<EndUser> users = db.getSet(USERS);
void canFallbackDBIfRecoveryFails() {
Set<User> users = db.getSet(USERS);
users.add(CREATOR);
users.add(MUSER);
users.add(USER);
Set<EndUser> originalSet = newHashSet(users);
Set<User> originalSet = newHashSet(users);
Object jsonBackup = db.backup();
String corruptBackup = "!@#$" + String.valueOf(jsonBackup);
String corruptBackup = "!@#$" + jsonBackup;
boolean recovered = db.recover(corruptBackup);
Set<EndUser> recoveredSet = db.getSet(USERS);
Set<User> recoveredSet = db.getSet(USERS);
assertEquals("Recovery was successful from a CORRUPT backup", false, recovered);
assertEquals("Set before and after corrupt recovery are not equal", originalSet, recoveredSet);
assertFalse(recovered, "Recovery was successful from a CORRUPT backup");
assertEquals(originalSet, recoveredSet, "Set before and after corrupt recovery are not equal");
}
@Test
public void canGetSummary() throws IOException {
void canGetSummary() {
String anotherTest = TEST + 1;
db.getSet(TEST).add(TEST);
db.getSet(anotherTest).add(anotherTest);
@ -82,28 +109,39 @@ public class MapDBContextTest {
// Name - Type - Number of "rows"
String expectedSummary = format("%s - Set - 1\n%s - Set - 1", TEST, anotherTest);
assertEquals("Actual DB summary does not match that of the expected", expectedSummary, actualSummary);
assertEquals(expectedSummary, actualSummary, "Actual DB summary does not match that of the expected");
}
@Test
public void canGetInfo() throws IOException {
void canGetInfo() {
db.getSet(TEST).add(TEST);
String actualInfo = db.info(TEST);
// JSON
String expectedInfo = "TEST - Set - 1";
assertEquals("Actual DB structure info does not match that of the expected", expectedInfo, actualInfo);
assertEquals(expectedInfo, actualInfo, "Actual DB structure info does not match that of the expected");
}
@Test(expected = IllegalStateException.class)
public void cantGetInfoFromNonexistentDBStructureName() throws IOException {
db.info(TEST);
@Test
void cantGetInfoFromNonexistentDBStructureName() {
Assertions.assertThrows(IllegalStateException.class, () -> db.info(TEST));
}
@After
public void tearDown() throws IOException {
db.clear();
db.close();
@Test
void canGetAndSetVariables() {
String varName = "somevar";
Var<User> var = db.getVar(varName);
var.set(CREATOR);
db.commit();
var = db.getVar(varName);
assertEquals(var.get(), CREATOR);
var.set(USER);
db.commit();
Var<User> changedVar = db.getVar(varName);
assertEquals(changedVar.get(), USER);
}
}

View File

@ -1,58 +1,59 @@
package org.telegram.abilitybots.api.objects;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.telegram.abilitybots.api.bot.DefaultBot.getDefaultBuilder;
public class AbilityTest {
@Test(expected = IllegalArgumentException.class)
public void argumentsCannotBeNegative() {
getDefaultBuilder().input(-4).build();
}
@Test(expected = IllegalArgumentException.class)
public void nameCannotBeEmpty() {
getDefaultBuilder().name("").build();
}
@Test(expected = IllegalArgumentException.class)
public void nameCannotBeNull() {
getDefaultBuilder().name(null).build();
}
@Test(expected = NullPointerException.class)
public void consumerCannotBeNull() {
getDefaultBuilder().action(null).build();
}
@Test(expected = NullPointerException.class)
public void localityCannotBeNull() {
getDefaultBuilder().locality(null).build();
}
@Test(expected = NullPointerException.class)
public void privacyCannotBeNull() {
getDefaultBuilder().privacy(null).build();
}
@Test(expected = IllegalArgumentException.class)
public void nameCannotContainSpaces() {
getDefaultBuilder().name("test test").build();
class AbilityTest {
@Test
void argumentsCannotBeNegative() {
Assertions.assertThrows(IllegalArgumentException.class, () -> getDefaultBuilder().input(-4).build());
}
@Test
public void abilityEqualsMethod() {
void nameCannotBeEmpty() {
Assertions.assertThrows(IllegalArgumentException.class, () -> getDefaultBuilder().name("").build());
}
@Test
void nameCannotBeNull() {
Assertions.assertThrows(IllegalArgumentException.class, () -> getDefaultBuilder().name(null).build());
}
@Test
void consumerCannotBeNull() {
Assertions.assertThrows(NullPointerException.class, () -> getDefaultBuilder().action(null).build());
}
@Test
void localityCannotBeNull() {
Assertions.assertThrows(NullPointerException.class, () -> getDefaultBuilder().locality(null).build());
}
@Test
void privacyCannotBeNull() {
Assertions.assertThrows(NullPointerException.class, () -> getDefaultBuilder().privacy(null).build());
}
@Test
void nameCannotContainSpaces() {
Assertions.assertThrows(IllegalArgumentException.class, () -> getDefaultBuilder().name("test test").build());
}
@Test
void abilityEqualsMethod() {
Ability ability1 = getDefaultBuilder().build();
Ability ability2 = getDefaultBuilder().build();
Ability ability3 = getDefaultBuilder().name("anotherconsumer").build();
Ability ability4 = getDefaultBuilder().action((context) -> {
}).build();
assertEquals("Abilities should not be equal", ability1, ability2);
assertEquals("Abilities should not be equal", ability1, ability4);
assertNotEquals("Abilities should be equal", ability1, ability3);
assertEquals(ability1, ability2, "Abilities should not be equal");
assertEquals(ability1, ability4, "Abilities should not be equal");
assertNotEquals(ability1, ability3, "Abilities should be equal");
}
}

View File

@ -0,0 +1,77 @@
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.*;
class SilentSenderTest {
private SilentSender silent;
private MessageSender sender;
@BeforeEach
void setUp() {
sender = mock(MessageSender.class);
silent = new SilentSender(sender);
}
@Test
void returnsEmptyOnError() throws TelegramApiException {
when(sender.execute(any())).thenThrow(TelegramApiException.class);
Optional execute = silent.execute(null);
assertFalse(execute.isPresent(), "Execution of a bot API method with execption results in a nonempty optional");
}
@Test
void returnOptionalOnSuccess() throws TelegramApiException {
String data = "data";
when(sender.execute(any())).thenReturn(data);
Optional execute = silent.execute(null);
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

@ -0,0 +1 @@
ability.commands.notFound=Non sono presenti comandi disponibile.

View File

@ -0,0 +1,55 @@
<div align="center">
[![Build Status](https://travis-ci.org/rubenlagus/TelegramBots.svg?branch=master)](https://travis-ci.org/rubenlagus/TelegramBots)
[![Jitpack](https://jitpack.io/v/rubenlagus/TelegramBots.svg)](https://jitpack.io/#rubenlagus/TelegramBots)
[![Telegram](http://trellobot.doomdns.org/telegrambadge.svg)](https://telegram.me/JavaBotsApi)
</div>
Usage
-----
**Maven**
```xml
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-chat-session-bot</artifactId>
<version>5.0.1</version>
</dependency>
```
Motivation
----------
Implementation of bot dialogs require saving some data about current state of conversation.
That brings us to idea of chat session management.
How to use
----------
`Chat session bot` was implemented by using [`Shiro Apache`](https://shiro.apache.org/) session manager.
That allow to manage and store sessions.
To create default Long Polling Session Bot with in-memory store,
you need simply implement `TelegramLongPollingSessionBot`
```java
public class ExampleBotWithSession extends TelegramLongPollingSessionBot {
@Override
public void onUpdateReceived(Update update, Optional<Session> optionalSession) {
//Do some action with update and session
}
@Override
public String getBotUsername() {
return "ExampleBotWithSessionBot";
}
@Override
public String getBotToken() {
return "1234";
}
}
```
Where session is implementation of `org.apache.shiro.session.Session`

View File

@ -0,0 +1,243 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>5.0.1</version>
</parent>
<artifactId>telegrambots-chat-session-bot</artifactId>
<packaging>jar</packaging>
<name>Telegram Bots Chat Session Bot</name>
<url>https://github.com/rubenlagus/TelegramBots</url>
<description>Telegram bot with chat session support</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>
<ciManagement>
<url>https://travis-ci.org/rubenlagus/TelegramBots</url>
<system>Travis</system>
</ciManagement>
<developers>
<developer>
<email>rberlopez@gmail.com</email>
<name>Ruben Bermudez</name>
<url>https://github.com/rubenlagus</url>
<id>rubenlagus</id>
</developer>
<developer>
<email>bochkarevei@gmail.com</email>
<name>Egor Bochkarev</name>
<url>https://github.com/EgorBochkarev</url>
<id>EgorBochkarev</id>
</developer>
</developers>
<licenses>
<license>
<name>MIT License</name>
<url>http://www.opensource.org/licenses/mit-license.php</url>
<distribution>repo</distribution>
</license>
</licenses>
<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>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.7.0</shiro.version>
</properties>
<dependencies>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>5.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
</dependencies>
<build>
<directory>${project.basedir}/target</directory>
<outputDirectory>${project.build.directory}/classes</outputDirectory>
<finalName>${project.artifactId}-${project.version}</finalName>
<testOutputDirectory>${project.build.directory}/test-classes</testOutputDirectory>
<sourceDirectory>${project.basedir}/src/main/java</sourceDirectory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<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>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>clean-project</id>
<phase>clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.6</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</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>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>

View File

@ -0,0 +1,10 @@
package org.telegram.telegrambots.session;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.session.mgt.eis.SessionIdGenerator;
import java.io.Serializable;
public interface ChatIdConverter extends SessionKey, SessionIdGenerator {
void setSessionId(Serializable sessionId);
}

View File

@ -0,0 +1,34 @@
package org.telegram.telegrambots.session;
import org.apache.shiro.session.Session;
import java.io.Serializable;
@SuppressWarnings({"unused", "WeakerAccess"})
public class DefaultChatIdConverter implements ChatIdConverter {
private long sessionId;
public DefaultChatIdConverter() {
super();
}
public DefaultChatIdConverter(long sessionId) {
this();
this.sessionId = sessionId;
}
@Override
public void setSessionId(Serializable sessionId){
this.sessionId = (long) sessionId;
};
@Override
public Serializable getSessionId() {
return sessionId;
}
@Override
public Serializable generateId(Session session) {
return getSessionId();
}
}

View File

@ -0,0 +1,37 @@
package org.telegram.telegrambots.session;
import org.apache.shiro.session.mgt.SessionContext;
import java.io.Serializable;
import java.util.HashMap;
@SuppressWarnings("WeakerAccess")
public class DefaultChatSessionContext extends HashMap<String, Object> implements SessionContext {
private long sessionId;
private String host;
public DefaultChatSessionContext(long sessionId, String host) {
this.sessionId = sessionId;
this.host = host;
}
@Override
public String getHost() {
return host;
}
@Override
public void setHost(String host) {
this.host = host;
}
@Override
public Serializable getSessionId() {
return sessionId;
}
@Override
public void setSessionId(Serializable serializable) {
this.sessionId = (long) serializable;
}
}

View File

@ -0,0 +1,72 @@
package org.telegram.telegrambots.session;
import org.apache.shiro.session.Session;
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;
import java.util.Optional;
@SuppressWarnings({"WeakerAccess", "OptionalUsedAsFieldOrParameterType", "unused"})
public abstract class TelegramLongPollingSessionBot extends TelegramLongPollingBot {
DefaultSessionManager sessionManager;
ChatIdConverter chatIdConverter;
public TelegramLongPollingSessionBot(){
this(new DefaultChatIdConverter());
}
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();
sessionDAO.setSessionIdGenerator(chatIdConverter);
}
public void setSessionManager(DefaultSessionManager sessionManager) {
this.sessionManager = sessionManager;
}
public void setChatIdConverter(ChatIdConverter chatIdConverter) {
this.chatIdConverter = chatIdConverter;
}
@Override
public void onUpdateReceived(Update update) {
Optional<Session> chatSession;
Message message;
if (update.hasMessage()) {
message = update.getMessage();
} else if (update.hasCallbackQuery()) {
message = update.getCallbackQuery().getMessage();
} else {
chatSession = Optional.empty();
onUpdateReceived(update, chatSession);
return;
}
chatIdConverter.setSessionId(message.getChatId());
chatSession = this.getSession(message);
onUpdateReceived(update, chatSession);
}
public Optional<Session> getSession(Message message){
try {
return Optional.of(sessionManager.getSession(chatIdConverter));
} catch (UnknownSessionException e) {
SessionContext botSession = new DefaultChatSessionContext(message.getChatId(), message.getFrom().getUserName());
return Optional.of(sessionManager.start(botSession));
}
}
public abstract void onUpdateReceived(Update update, Optional<Session> botSession);
}

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>3.3</version>
<version>5.0.1</version>
</dependency>
```
2. Using Gradle:
```gradle
compile "org.telegram:telegrambotsextensions:3.3"
compile "org.telegram:telegrambotsextensions:5.0.1"
```

View File

@ -3,9 +3,14 @@
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.telegram</groupId>
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>5.0.1</version>
</parent>
<artifactId>telegrambotsextensions</artifactId>
<version>3.3</version>
<packaging>jar</packaging>
<name>Telegram Bots Extensions</name>
@ -57,16 +62,20 @@
</distributionManagement>
<properties>
<java.version>11</java.version>
<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>
<bots.version>3.3</bots.version>
</properties>
<dependencies>
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>${bots.version}</version>
<version>5.0.1</version>
</dependency>
</dependencies>
@ -79,22 +88,13 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-gpg-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<id>sign-artifacts</id>
<phase>verify</phase>
<goals>
<goal>sign</goal>
</goals>
</execution>
</executions>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
</plugin>
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.3</version>
<version>1.6.8</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
@ -104,7 +104,7 @@
</plugin>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<id>clean-project</id>
@ -117,7 +117,7 @@
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
@ -136,7 +136,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.0</version>
<version>3.1.0</version>
<executions>
<execution>
<goals>
@ -148,14 +148,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>2.10.3</version>
<version>3.1.0</version>
<executions>
<execution>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<additionalparam>-Xdoclint:none</additionalparam>
<doclint>none</doclint>
</configuration>
</execution>
</executions>
@ -163,7 +163,7 @@
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.7.7.201606060606</version>
<version>0.8.6</version>
<executions>
<execution>
<goals>
@ -182,7 +182,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<version>3.0.0-M2</version>
<executions>
<execution>
<id>enforce-versions</id>
@ -200,7 +200,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.4</version>
<version>3.1.1</version>
<executions>
<execution>
<id>copy</id>
@ -214,13 +214,14 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
</project>

View File

@ -1,15 +1,14 @@
package org.telegram.telegrambots.bots.commandbot;
package org.telegram.telegrambots.extensions.bots.commandbot;
import org.telegram.telegrambots.ApiContext;
import org.telegram.telegrambots.api.objects.Message;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.bots.AbsSender;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.meta.bots.AbsSender;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.bots.commandbot.commands.BotCommand;
import org.telegram.telegrambots.bots.commandbot.commands.CommandRegistry;
import org.telegram.telegrambots.bots.commandbot.commands.ICommandRegistry;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.CommandRegistry;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.IBotCommand;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.ICommandRegistry;
import java.util.Collection;
import java.util.Map;
@ -22,41 +21,38 @@ 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());
}
/**
* Creates a TelegramLongPollingCommandBot with custom options and allowing commands with
* usernames
* Use ICommandRegistry's methods on this bot to register commands
* @param options Bot options
* @param botUsername Username of the bot
*
* @param options Bot options
*/
public TelegramLongPollingCommandBot(DefaultBotOptions options, String botUsername) {
this(options, true, botUsername);
public TelegramLongPollingCommandBot(DefaultBotOptions options) {
this(options, true);
}
/**
* Creates a TelegramLongPollingCommandBot
* Use ICommandRegistry's methods on this bot to register commands
* @param options Bot options
*
* @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
@ -64,53 +60,66 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
if (update.hasMessage()) {
Message message = update.getMessage();
if (message.isCommand() && !filter(message)) {
if (commandRegistry.executeCommand(this, message)) {
return;
if (!commandRegistry.executeCommand(this, message)) {
//we have received a not registered command, handle it as invalid
processInvalidCommandUpdate(update);
}
return;
}
}
processNonCommandUpdate(update);
}
/**
* This method is called when user sends a not registered command. By default it will just call processNonCommandUpdate(),
* override it in your implementation if you want your bot to do other things, such as sending an error message
*
* @param update Received update from Telegram
*/
protected void processInvalidCommandUpdate(Update update) {
processNonCommandUpdate(update);
}
/**
* Override this function in your bot implementation to filter messages with commands
*
* <p>
* For example, if you want to prevent commands execution incoming from group chat:
* #
* # return !message.getChat().isGroupChat();
* #
* #
* # return !message.getChat().isGroupChat();
* #
*
* @note Default implementation doesn't filter anything
* @param message Received message
* @return true if the message must be ignored by the command bot and treated as a non command message,
* false otherwise
* @note Default implementation doesn't filter anything
*/
protected boolean filter(Message message) {
return false;
}
@Override
public final boolean register(BotCommand botCommand) {
public final boolean register(IBotCommand botCommand) {
return commandRegistry.register(botCommand);
}
@Override
public final Map<BotCommand, Boolean> registerAll(BotCommand... botCommands) {
public final Map<IBotCommand, Boolean> registerAll(IBotCommand... botCommands) {
return commandRegistry.registerAll(botCommands);
}
@Override
public final boolean deregister(BotCommand botCommand) {
public final boolean deregister(IBotCommand botCommand) {
return commandRegistry.deregister(botCommand);
}
@Override
public final Map<BotCommand, Boolean> deregisterAll(BotCommand... botCommands) {
public final Map<IBotCommand, Boolean> deregisterAll(IBotCommand... botCommands) {
return commandRegistry.deregisterAll(botCommands);
}
@Override
public final Collection<BotCommand> getRegisteredCommands() {
public final Collection<IBotCommand> getRegisteredCommands() {
return commandRegistry.getRegisteredCommands();
}
@ -120,7 +129,7 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
}
@Override
public final BotCommand getRegisteredCommand(String commandIdentifier) {
public final IBotCommand getRegisteredCommand(String commandIdentifier) {
return commandRegistry.getRegisteredCommand(commandIdentifier);
}
@ -128,16 +137,14 @@ 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.
* @warning Commands that have valid syntax but are not registered on this bot,
* won't be forwarded to this method <b>if a default action is present</b>.
*
* @param update the update
* @warning Commands that have valid syntax but are not registered on this bot,
* won't be forwarded to this method <b>if a default action is present</b>.
*/
public abstract void processNonCommandUpdate(Update update);
}

View File

@ -1,17 +1,18 @@
package org.telegram.telegrambots.bots.commandbot.commands;
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.telegram.telegrambots.api.objects.Chat;
import org.telegram.telegrambots.api.objects.User;
import org.telegram.telegrambots.bots.AbsSender;
import org.telegram.telegrambots.meta.api.objects.Chat;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.bots.AbsSender;
/**
* Representation of a command, which can be executed
*
* @author Timo Schulz (Mit0x2)
*/
public abstract class BotCommand {
public abstract class BotCommand implements IBotCommand {
public final static String COMMAND_INIT_CHARACTER = "/";
public static final String COMMAND_PARAMETER_SEPARATOR = " ";
public static final String COMMAND_PARAMETER_SEPARATOR_REGEXP = "\\s+";
private final static int COMMAND_MAX_LENGTH = 32;
private final String commandIdentifier;
@ -66,6 +67,17 @@ public abstract class BotCommand {
"</b>\n" + getDescription();
}
/**
* Process the message and execute the command
*
* @param absSender absSender to send messages over
* @param message the message to process
* @param arguments passed arguments
*/
public void processMessage(AbsSender absSender, Message message, String[] arguments) {
execute(absSender, message.getFrom(), message.getChat(), arguments);
}
/**
* Execute the command
*
@ -75,4 +87,4 @@ public abstract class BotCommand {
* @param arguments passed arguments
*/
public abstract void execute(AbsSender absSender, User user, Chat chat, String[] arguments);
}
}

View File

@ -1,13 +1,16 @@
package org.telegram.telegrambots.bots.commandbot.commands;
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.telegram.telegrambots.api.objects.Message;
import org.telegram.telegrambots.bots.AbsSender;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.bots.AbsSender;
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
@ -16,19 +19,19 @@ import java.util.function.BiConsumer;
*/
public final class CommandRegistry implements ICommandRegistry {
private final Map<String, BotCommand> commandRegistryMap = new HashMap<>();
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
@ -37,7 +40,7 @@ public final class CommandRegistry implements ICommandRegistry {
}
@Override
public final boolean register(BotCommand botCommand) {
public final boolean register(IBotCommand botCommand) {
if (commandRegistryMap.containsKey(botCommand.getCommandIdentifier())) {
return false;
}
@ -46,16 +49,16 @@ public final class CommandRegistry implements ICommandRegistry {
}
@Override
public final Map<BotCommand, Boolean> registerAll(BotCommand... botCommands) {
Map<BotCommand, Boolean> resultMap = new HashMap<>(botCommands.length);
for (BotCommand botCommand : botCommands) {
public final Map<IBotCommand, Boolean> registerAll(IBotCommand... botCommands) {
Map<IBotCommand, Boolean> resultMap = new HashMap<>(botCommands.length);
for (IBotCommand botCommand : botCommands) {
resultMap.put(botCommand, register(botCommand));
}
return resultMap;
}
@Override
public final boolean deregister(BotCommand botCommand) {
public final boolean deregister(IBotCommand botCommand) {
if (commandRegistryMap.containsKey(botCommand.getCommandIdentifier())) {
commandRegistryMap.remove(botCommand.getCommandIdentifier());
return true;
@ -64,28 +67,28 @@ public final class CommandRegistry implements ICommandRegistry {
}
@Override
public final Map<BotCommand, Boolean> deregisterAll(BotCommand... botCommands) {
Map<BotCommand, Boolean> resultMap = new HashMap<>(botCommands.length);
for (BotCommand botCommand : botCommands) {
public final Map<IBotCommand, Boolean> deregisterAll(IBotCommand... botCommands) {
Map<IBotCommand, Boolean> resultMap = new HashMap<>(botCommands.length);
for (IBotCommand botCommand : botCommands) {
resultMap.put(botCommand, deregister(botCommand));
}
return resultMap;
}
@Override
public final Collection<BotCommand> getRegisteredCommands() {
public final Collection<IBotCommand> getRegisteredCommands() {
return commandRegistryMap.values();
}
@Override
public final BotCommand getRegisteredCommand(String commandIdentifier) {
public final IBotCommand getRegisteredCommand(String commandIdentifier) {
return commandRegistryMap.get(commandIdentifier);
}
/**
* 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
@ -97,13 +100,13 @@ public final class CommandRegistry implements ICommandRegistry {
String text = message.getText();
if (text.startsWith(BotCommand.COMMAND_INIT_CHARACTER)) {
String commandMessage = text.substring(1);
String[] commandSplit = commandMessage.split(BotCommand.COMMAND_PARAMETER_SEPARATOR);
String[] commandSplit = commandMessage.split(BotCommand.COMMAND_PARAMETER_SEPARATOR_REGEXP);
String command = removeUsernameFromCommandIfNeeded(commandSplit[0]);
if (commandRegistryMap.containsKey(command)) {
String[] parameters = Arrays.copyOfRange(commandSplit, 1, commandSplit.length);
commandRegistryMap.get(command).execute(absSender, message.getFrom(), message.getChat(), parameters);
commandRegistryMap.get(command).processMessage(absSender, message, parameters);
return true;
} else if (defaultConsumer != null) {
defaultConsumer.accept(absSender, message);
@ -119,11 +122,14 @@ 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

@ -0,0 +1,53 @@
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.telegram.telegrambots.meta.api.objects.Chat;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.bots.AbsSender;
/**
* Bot command with message ID in execute method
*
* @author Vadim Goroshevsky (@vadimgoroshevsky)
*/
public abstract class DefaultBotCommand extends BotCommand {
/**
* Construct a command
*
* @param commandIdentifier the unique identifier of this command (e.g. the command string to
* enter into chat)
* @param description the description of this command
*/
public DefaultBotCommand(String commandIdentifier, String description) {
super(commandIdentifier, description);
}
/**
* Process the message and execute the command
*
* @param absSender absSender to send messages over
* @param message the message to process
* @param arguments passed arguments
*/
@Override
public void processMessage(AbsSender absSender, Message message, String[] arguments) {
execute(absSender, message.getFrom(), message.getChat(), message.getMessageId(), arguments);
}
// We'll override this method here for not repeating it in DefaultBotCommand's children
@Override
public final void execute(AbsSender absSender, User user, Chat chat, String[] arguments) {
}
/**
* Execute the command
*
* @param absSender absSender to send messages over
* @param user the user who sent the command
* @param chat the chat, to be able to send replies
* @param messageId message id for interaction
* @param arguments passed arguments
*/
public abstract void execute(AbsSender absSender, User user, Chat chat, Integer messageId, String[] arguments);
}

View File

@ -0,0 +1,33 @@
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.bots.AbsSender;
/**
* This Interface represents the a Command that can be executed
*
* @author Timo Schulz (Mit0x2)
*/
public interface IBotCommand {
/**
* Get the identifier of this command
*
* @return the identifier
*/
String getCommandIdentifier();
/**
* Get the description of this command
*
* @return the description as String
*/
String getDescription();
/**
* Process the message and execute the command
*
* @param absSender absSender to send messages over
* @param message the message to process
*/
void processMessage(AbsSender absSender, Message message, String[] arguments);
}

View File

@ -1,7 +1,7 @@
package org.telegram.telegrambots.bots.commandbot.commands;
package org.telegram.telegrambots.extensions.bots.commandbot.commands;
import org.telegram.telegrambots.api.objects.Message;
import org.telegram.telegrambots.bots.AbsSender;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.bots.AbsSender;
import java.util.Collection;
import java.util.Map;
@ -29,7 +29,7 @@ public interface ICommandRegistry {
* @param botCommand the command to register
* @return whether the command could be registered, was not already registered
*/
boolean register(BotCommand botCommand);
boolean register(IBotCommand botCommand);
/**
* register multiple commands
@ -37,7 +37,7 @@ public interface ICommandRegistry {
* @param botCommands commands to register
* @return map with results of the command register per command
*/
Map<BotCommand, Boolean> registerAll(BotCommand... botCommands);
Map<IBotCommand, Boolean> registerAll(IBotCommand... botCommands);
/**
* deregister a command
@ -45,7 +45,7 @@ public interface ICommandRegistry {
* @param botCommand the command to deregister
* @return whether the command could be deregistered, was registered
*/
boolean deregister(BotCommand botCommand);
boolean deregister(IBotCommand botCommand);
/**
* deregister multiple commands
@ -53,19 +53,19 @@ public interface ICommandRegistry {
* @param botCommands commands to deregister
* @return map with results of the command deregistered per command
*/
Map<BotCommand, Boolean> deregisterAll(BotCommand... botCommands);
Map<IBotCommand, Boolean> deregisterAll(IBotCommand... botCommands);
/**
* get a collection of all registered commands
*
* @return a collection of registered commands
*/
Collection<BotCommand> getRegisteredCommands();
Collection<IBotCommand> getRegisteredCommands();
/**
* get registered command
*
* @return registered command if exists or null if not
*/
BotCommand getRegisteredCommand(String commandIdentifier);
IBotCommand getRegisteredCommand(String commandIdentifier);
}

View File

@ -0,0 +1,116 @@
package org.telegram.telegrambots.extensions.bots.commandbot.commands.helpCommand;
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
import org.telegram.telegrambots.meta.api.objects.Chat;
import org.telegram.telegrambots.meta.api.objects.User;
import org.telegram.telegrambots.meta.bots.AbsSender;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.IBotCommand;
import org.telegram.telegrambots.extensions.bots.commandbot.commands.ICommandRegistry;
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
import java.util.Collection;
/**
* A special bot command used for printing help messages similiar to the Linux man command.
* The commands printed by this command should implement the {@link IManCommand} interface to provide an extended description.
* @author Lukas Prediger(Chase)
* @version 1.0.0
*
*/
public class HelpCommand extends ManCommand {
private static final String COMMAND_IDENTIFIER = "help";
private static final String COMMAND_DESCRIPTION = "shows all commands. Use /help [command] for more info";
private static final String EXTENDED_DESCRIPTION = "This command displays all commands the bot has to offer.\n /help [command] can display deeper information";
/**
* Returns the command and description of all supplied commands as a formatted String
* @param botCommands the Commands that should be included in the String
* @return a formatted String containing command and description for all supplied commands
*/
public static String getHelpText(IBotCommand...botCommands) {
StringBuilder reply = new StringBuilder();
for (IBotCommand com : botCommands) {
reply.append(com.toString()).append(System.lineSeparator()).append(System.lineSeparator());
}
return reply.toString();
}
/**
* Returns the command and description of all supplied commands as a formatted String
* @param botCommands a collection of commands that should be included in the String
* @return a formatted String containing command and description for all supplied commands
*/
public static String getHelpText(Collection<IBotCommand> botCommands) {
return getHelpText(botCommands.toArray(new IBotCommand[botCommands.size()]));
}
/**
* Returns the command and description of all supplied commands as a formatted String
* @param registry a commandRegistry which commands are formatted into the String
* @return a formatted String containing command and description for all supplied commands
*/
public static String getHelpText(ICommandRegistry registry) {
return getHelpText(registry.getRegisteredCommands());
}
/**
* Reads the extended Description from a BotCommand. If the Command is not of Type {@link IManCommand}, it calls toString();
* @param command a command the extended Descriptions is read from
* @return the extended Description or the toString() if IManCommand is not implemented
*/
public static String getManText(IBotCommand command) {
return IManCommand.class.isInstance(command) ? getManText((IManCommand) command) : command.toString();
}
/**
* Reads the extended Description from a BotCommand;
* @param command a command the extended Descriptions is read from
* @return the extended Description
*/
public static String getManText(IManCommand command) {
return command.toMan();
}
/**
* Create a Help command with the standard Arguments.
*/
public HelpCommand() {
super(COMMAND_IDENTIFIER, COMMAND_DESCRIPTION, EXTENDED_DESCRIPTION);
}
/**
* Creates a Help Command with custom identifier, description and extended Description
* @param commandIdentifier the unique identifier of this command (e.g. the command string to enter into chat)
* @param description the description of this command
* @param extendedDescription The extended Description for the Command, should provide detailed information about arguments and possible options
*/
public HelpCommand(String commandIdentifier, String description, String extendedDescription) {
super(commandIdentifier, description, extendedDescription);
}
@Override
public void execute(AbsSender absSender, User user, Chat chat, String[] arguments) {
if (ICommandRegistry.class.isInstance(absSender)) {
ICommandRegistry registry = (ICommandRegistry) absSender;
if (arguments.length > 0) {
IBotCommand command = registry.getRegisteredCommand(arguments[0]);
String reply = getManText(command);
try {
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(SendMessage.builder().chatId(chat.getId().toString()).text(reply).parseMode("HTML").build());
} catch (TelegramApiException e) {
e.printStackTrace();
}
}
}
}
}

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