Compare commits
622 Commits
rubenlagus
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
228e583c26 | ||
|
9b8c41dab1 | ||
|
f343d17ae4 | ||
|
aad139de98 | ||
|
bfd9ae3fe6 | ||
|
7029a35cd0 | ||
|
bfb16280b0 | ||
|
a48c4b2b94 | ||
|
8343e10fca | ||
|
79ecff7224 | ||
|
c3925e035d | ||
|
364a92101d | ||
|
0f60dec532 | ||
|
a076d221a0 | ||
|
61eb857248 | ||
|
890f6aef3b | ||
|
507c4dee41 | ||
|
e1b116e64e | ||
|
402a4c46ea | ||
|
cafe8cc3b4 | ||
|
4c36c432f6 | ||
|
d1c70210f1 | ||
|
4a53ed85e4 | ||
|
a1a8925ae4 | ||
|
2cc5aca3a1 | ||
|
7c7ba29283 | ||
|
b3099f58cf | ||
|
bea63d4aaf | ||
|
1ec2a5103b | ||
|
6815f1f225 | ||
|
f87f4137c3 | ||
|
490756c79e | ||
|
95c2a597e1 | ||
|
28c21574d9 | ||
|
a941d30074 | ||
|
75a65810d6 | ||
|
7d2abecd88 | ||
|
888e0deac6 | ||
|
fd3118b9b2 | ||
|
1e040bea4d | ||
|
4db7532b61 | ||
|
76f5cb2f4e | ||
|
fc63843e14 | ||
|
588dcc5cc2 | ||
|
5a400c19c3 | ||
|
5fc55bae8c | ||
|
a1cab968c6 | ||
|
0e6d41ee42 | ||
|
64388f46b3 | ||
|
f85c52aaf0 | ||
|
c0f51040bb | ||
|
8b8646c61c | ||
|
e5d994f16d | ||
|
da0a69309b | ||
|
27c59e60de | ||
|
40006eabd3 | ||
|
c0ea9e1773 | ||
|
7d5fa667c0 | ||
|
82659125d1 | ||
|
f7bcab98e8 | ||
|
3d07f27d41 | ||
|
9a3e5b3943 | ||
|
9c9bfd0950 | ||
|
07b67edde4 | ||
|
ab29b41872 | ||
|
80c918807d | ||
|
a423b23440 | ||
|
cfc1806509 | ||
|
0c574563b9 | ||
|
db1b24cd09 | ||
|
d2c6e6439b | ||
|
0170c93555 | ||
|
69e4a6a149 | ||
|
92c7b1c3ce | ||
|
ce4760d994 | ||
|
96114e1f72 | ||
|
06b8250325 | ||
|
91de0dd3a3 | ||
|
f650e2376a | ||
|
1c5110dab6 | ||
|
b4bea40a86 | ||
|
21e5a22a4c | ||
|
746c8ec784 | ||
|
482dee7a61 | ||
|
3b080a149a | ||
|
4276fb2459 | ||
|
7b18185e48 | ||
|
ea4076b319 | ||
|
132f8b7be8 | ||
|
80ef86b90d | ||
|
622cab39fc | ||
|
0bb4ecbd85 | ||
|
19466416a4 | ||
|
8282529b89 | ||
|
1e59ebaf91 | ||
|
134a15d5bc | ||
|
14d93968de | ||
|
e779402e74 | ||
|
32cd231474 | ||
|
ce6782c4b9 | ||
|
082f4a9786 | ||
|
43aa32fd64 | ||
|
fb54d1f603 | ||
|
fd79082fcf | ||
|
a8a00430e6 | ||
|
b97248058d | ||
|
4759fa808e | ||
|
9dafb2cf32 | ||
|
336c026828 | ||
|
302bf90515 | ||
|
60eed343f6 | ||
|
75b0525986 | ||
|
842040551d | ||
|
d1daddc652 | ||
|
b86e0d26ac | ||
|
3cd13ffc61 | ||
|
9cb206b14b | ||
|
db8aa6ac3e | ||
|
54575990bc | ||
|
359d1db9af | ||
|
a202c47c65 | ||
|
c6dbe717a3 | ||
|
c8ad62b85a | ||
|
6bb17461f0 | ||
|
6da32da4f8 | ||
|
bd6665ccc5 | ||
|
0327638e0d | ||
|
7067e1a65e | ||
|
6a2416e81e | ||
|
d6f1aa70db | ||
|
b1b191a6e7 | ||
|
82bc60d510 | ||
|
1daa69cbea | ||
|
cfa4876e20 | ||
|
06387a9d73 | ||
|
729d4d1b69 | ||
|
dd9eb40651 | ||
|
3a6f04ce84 | ||
|
70fb99e3b1 | ||
|
31c85b7e77 | ||
|
89cf8007ad | ||
|
0a896dce38 | ||
|
d24eecca2b | ||
|
134f32d03e | ||
|
78820140dd | ||
|
b8d6c23064 | ||
|
e3e7502a37 | ||
|
d195d8f2eb | ||
|
f549d6a4e4 | ||
|
b316a677f0 | ||
|
9be30998e6 | ||
|
c4e3f16871 | ||
|
aca3f8b688 | ||
|
be99e95f36 | ||
|
7273952a5b | ||
|
feffffe112 | ||
|
5c6bbbe1db | ||
|
45c1bdc37b | ||
|
6f904af461 | ||
|
e597baf533 | ||
|
0010c00a0b | ||
|
4da9123b07 | ||
|
2295336fc0 | ||
|
3ef4bf0e87 | ||
|
cc33d6641d | ||
|
57f043e239 | ||
|
10f556510e | ||
|
5adc17f6cc | ||
|
92acba219b | ||
|
0ee70dd08e | ||
|
2963971fa3 | ||
|
7dd0711cdb | ||
|
e606e2afa3 | ||
|
2663d01761 | ||
|
3eea18aef6 | ||
|
df26c228f8 | ||
|
9888db476a | ||
|
2212afc864 | ||
|
94d5281929 | ||
|
7d8018c79d | ||
|
a5cad1407b | ||
|
5e9e91216a | ||
|
e5deb95933 | ||
|
0544429c47 | ||
|
17c336b1ec | ||
|
46c5aa6b80 | ||
|
729c22c97d | ||
|
3412e0dd48 | ||
|
29cdebad8a | ||
|
0c202ac134 | ||
|
0580210a3e | ||
|
eef0681ddf | ||
|
eeff4d3ae6 | ||
|
2b2ede3833 | ||
|
e5753bc037 | ||
|
50e5c16078 | ||
|
a7f242fca0 | ||
|
ee3114cc7f | ||
|
3d1ba16902 | ||
|
2cc4fb1fb6 | ||
|
fe2466e25d | ||
|
aff5385685 | ||
|
0ec2e32bc9 | ||
|
247274a568 | ||
|
c8d461926b | ||
|
96d3cf187f | ||
|
ff6ce9f764 | ||
|
c453b54bd4 | ||
|
b02034c6c4 | ||
|
745709ea3c | ||
|
5c1bf4a0f4 | ||
|
d315745f2e | ||
|
09cdf87724 | ||
|
330d9c29fa | ||
|
9abce70359 | ||
|
45671f1bab | ||
|
02fdbec4cc | ||
|
2b721194d1 | ||
|
bb18562222 | ||
|
242af20024 | ||
|
d3e58de851 | ||
|
0e80f2a4c8 | ||
|
e38c3b0ba9 | ||
|
f437fd885c | ||
|
934023632f | ||
|
92e68b51da | ||
|
58de20ae9f | ||
|
8658de9b94 | ||
|
0adf90fe8c | ||
|
58822f5113 | ||
|
00085adfd6 | ||
|
d7781f23e0 | ||
|
528d5b8331 | ||
|
3d924b883b | ||
|
99899f1bd3 | ||
|
4882040822 | ||
|
6ce7eb1323 | ||
|
bbaafbc2a4 | ||
|
1f8b7a0f01 | ||
|
87e3f23e8e | ||
|
93106c8cfa | ||
|
608581ce67 | ||
|
60c1927c34 | ||
|
cea06afcef | ||
|
6653ffe937 | ||
|
ecca28cedc | ||
|
c7728fbc8a | ||
|
272be4a79c | ||
|
e994bbe263 | ||
|
5877f1b5ae | ||
|
7287c38444 | ||
|
4ae315eb09 | ||
|
f724181daf | ||
|
aa4e0d85f1 | ||
|
74dd1c8505 | ||
|
af9a0e308e | ||
|
c2ea41520b | ||
|
93043c4c98 | ||
|
39a5678543 | ||
|
97b427d9fa | ||
|
9ae03e511f | ||
|
37171fa94a | ||
|
e09c13cf0e | ||
|
537a4c1f82 | ||
|
b1a7db4590 | ||
|
fe464e0092 | ||
|
ec4f81b94a | ||
|
eea4c3adc1 | ||
|
aac8afe209 | ||
|
6342d1ff4e | ||
|
451bdce90b | ||
|
adb765d739 | ||
|
e7cb0e6ced | ||
|
b03fe98798 | ||
|
415d31eaec | ||
|
4af620770b | ||
|
f60425820a | ||
|
88dd390566 | ||
|
0bbe66986b | ||
|
04880cb9ed | ||
|
c42955985e | ||
|
bd2aa24e00 | ||
|
b2fcbaa3c2 | ||
|
b7ffc57704 | ||
|
cd2f746a21 | ||
|
934e3e8f1a | ||
|
2f56a05a22 | ||
|
93bd1e0efe | ||
|
95216b90c5 | ||
|
ab00b182e8 | ||
|
fb5626de7a | ||
|
447c9d3f92 | ||
|
3275c10913 | ||
|
bea0b95fc7 | ||
|
4fb51ec99c | ||
|
0a58ef7120 | ||
|
6b18fbb545 | ||
|
3e4a300318 | ||
|
48dea8184c | ||
|
f776950240 | ||
|
48f9bf8105 | ||
|
341c330857 | ||
|
ac83b5d517 | ||
|
b4684b9ec7 | ||
|
5d058d41c9 | ||
|
3898480a87 | ||
|
cdef29f1ba | ||
|
814955c2a6 | ||
|
aa030fa148 | ||
|
8b98ef71ba | ||
|
5ea6fc9f09 | ||
|
f83e2b3ccb | ||
|
02674a2a39 | ||
|
7336766502 | ||
|
323d7607d6 | ||
|
30fa92cd8b | ||
|
3f07bfdff1 | ||
|
247ca5f984 | ||
|
a1034707e3 | ||
|
5b465e79f3 | ||
|
5d8d10b02d | ||
|
fe79de9bb7 | ||
|
1855a27d13 | ||
|
b715f2a154 | ||
|
74e305c2a7 | ||
|
a106e3190c | ||
|
2009ef411d | ||
|
13f6b1f336 | ||
|
6fcd4d85c1 | ||
|
f3626b7bf1 | ||
|
3c5163990b | ||
|
b79911b45f | ||
|
203b26587f | ||
|
e6b9fd892f | ||
|
402c36e6b2 | ||
|
749c6d3b6f | ||
|
196d0a3924 | ||
|
280b9f2686 | ||
|
4a4267768c | ||
|
fdb60e7b53 | ||
|
cec61f95a1 | ||
|
12619422c0 | ||
|
8d6e5c80d3 | ||
|
a419a88ce1 | ||
|
4991eef1f1 | ||
|
e6aae3c282 | ||
|
709ed0f212 | ||
|
54096d2e85 | ||
|
5281caad39 | ||
|
2c1ba312b3 | ||
|
023772142a | ||
|
427f1ee8d2 | ||
|
f6a4489498 | ||
|
de3246058b | ||
|
7c993feaeb | ||
|
129c6d0c2f | ||
|
fd91b3a2ba | ||
|
223c6cccc0 | ||
|
03fdafd54f | ||
|
504db77a7a | ||
|
8f0b31d3f1 | ||
|
4b1783e198 | ||
|
1a2e7c9773 | ||
|
72b239e851 | ||
|
28f60e3024 | ||
|
765c47b77b | ||
|
05db723802 | ||
|
39af4ced4c | ||
|
202fb72c3f | ||
|
9ffc547cdf | ||
|
e4dbf27c55 | ||
|
260d8369eb | ||
|
854b336a24 | ||
|
40c17126ae | ||
|
6ecc2af3b3 | ||
|
f84ec0b020 | ||
|
cd0ec5959f | ||
|
99e9b31f40 | ||
|
cdfd49e9b5 | ||
|
05566742c0 | ||
|
52d69853e3 | ||
|
bd0c96f5e0 | ||
|
21472d67cd | ||
|
a3781ca0a5 | ||
|
da0aacfa40 | ||
|
f351e63307 | ||
|
571d58e13f | ||
|
43082ebeea | ||
|
2083b29ae5 | ||
|
a7aec1e581 | ||
|
14633b4040 | ||
|
10ed2b5bf5 | ||
|
2568822d58 | ||
|
263d690933 | ||
|
7b03a2889d | ||
|
791826fb02 | ||
|
e4bb479972 | ||
|
cc0e0c9771 | ||
|
21822f7803 | ||
|
5120595cb0 | ||
|
caf3659de9 | ||
|
1b18b1ccdb | ||
|
55e27c1167 | ||
|
bd5c5a2f41 | ||
|
00f68950aa | ||
|
ce2e1f9c7b | ||
|
d61ade0487 | ||
|
32be70c9eb | ||
|
581a2f3092 | ||
|
2ca6d985fb | ||
|
915a3dcd41 | ||
|
d8eacfc0b4 | ||
|
e2bc7b4794 | ||
|
6b1cea5e82 | ||
|
b27f273ba7 | ||
|
e4b4c07e84 | ||
|
59958c57c0 | ||
|
a0b53d1530 | ||
|
4f0e5f6af0 | ||
|
547013dcd9 | ||
|
f58def6326 | ||
|
26f83bd4bb | ||
|
ff96318fef | ||
|
27649a99cf | ||
|
030aed2756 | ||
|
0cbc21b142 | ||
|
44c814256d | ||
|
7cfcca3ae3 | ||
|
31e5fe6638 | ||
|
777dc843ef | ||
|
eb3d159dde | ||
|
2d27f9e60f | ||
|
e526b9db14 | ||
|
15fe637998 | ||
|
fc12de81c0 | ||
|
1ebbc68485 | ||
|
5d166d3d90 | ||
|
4d3011b89f | ||
|
d1f060fb04 | ||
|
8e97852ee1 | ||
|
51df90464e | ||
|
2b07b87fdf | ||
|
4c5a25302b | ||
|
5d60c72a46 | ||
|
861b7f24f9 | ||
|
0ff63149f7 | ||
|
837b4d2360 | ||
|
ed7333a21e | ||
|
7b7478f180 | ||
|
b3c6623eaa | ||
|
aa3448544e | ||
|
ef1bd09a6a | ||
|
afe35b4b14 | ||
|
f85409f852 | ||
|
6f0247232f | ||
|
97c8011972 | ||
|
0d0fea0f54 | ||
|
e7e369472b | ||
|
378b1e3eb8 | ||
|
5cee266478 | ||
|
b19ae64899 | ||
|
d7875bba34 | ||
|
e551c64eea | ||
|
87bd7aefd2 | ||
|
fd342ad7a7 | ||
|
830b873776 | ||
|
08bcf2bdac | ||
|
0448bd82bc | ||
|
727f5a7a3e | ||
|
c633f1fcc2 | ||
|
251d19f456 | ||
|
37eea6c2be | ||
|
04248e64a7 | ||
|
7b5acf0987 | ||
|
679ad618b9 | ||
|
91508aeebb | ||
|
01a7e7dc21 | ||
|
e616ebbddb | ||
|
7c11293d07 | ||
|
7fd3d871dc | ||
|
7c64bf5f1b | ||
|
c89ee9b07c | ||
|
25e69f8055 | ||
|
c07bfb4903 | ||
|
14652bdd21 | ||
|
b3914c5e76 | ||
|
06dad7c262 | ||
|
c125b103e6 | ||
|
54383aa626 | ||
|
ba3555fefc | ||
|
59f2f8e9cd | ||
|
6e689fbf88 | ||
|
ee89128adb | ||
|
4a3ebed6ac | ||
|
c578d47e9c | ||
|
d62354915d | ||
|
b76d4da5bf | ||
|
20f45bd604 | ||
|
64b0be3154 | ||
|
506329e51a | ||
|
d78acbdda0 | ||
|
3861b29b25 | ||
|
140574f54e | ||
|
d20cf9d4bd | ||
|
16308c1be7 | ||
|
a4c7d73f0a | ||
|
a80ee81ba4 | ||
|
0c72769976 | ||
|
ed18c2c265 | ||
|
ef0d78eb4c | ||
|
a317001d4b | ||
|
21a308079c | ||
|
b4f63905c5 | ||
|
935b75d685 | ||
|
b099cb6f4e | ||
|
1883320445 | ||
|
261384b9d7 | ||
|
da5e7a776f | ||
|
f96c91cd48 | ||
|
390808c804 | ||
|
18412d9c98 | ||
|
05c92d4037 | ||
|
d37ea25fa6 | ||
|
61d1eb3319 | ||
|
c1597058f4 | ||
|
fed36f60f8 | ||
|
2f05976d0a | ||
|
f998cc2509 | ||
|
e203abfab5 | ||
|
293d903083 | ||
|
702285e631 | ||
|
97e0aaa10f | ||
|
885a2b69ff | ||
|
e746a99188 | ||
|
4ea1651b28 | ||
|
c06aa5344c | ||
|
880c8a2087 | ||
|
8e8156646c | ||
|
6b0a738dac | ||
|
72464edd2b | ||
|
4c86a3994d | ||
|
ad195d8d59 | ||
|
d091480f71 | ||
|
1099ea07a3 | ||
|
9a6682688e | ||
|
f7bde89ec8 | ||
|
5fec33f9ea | ||
|
c71c2a3c29 | ||
|
d420625e7b | ||
|
28740da0df | ||
|
ee78c70164 | ||
|
ff01673f8f | ||
|
159de38b29 | ||
|
a05988f5b4 | ||
|
d515d8e44f | ||
|
4916e9f853 | ||
|
ce05d68e81 | ||
|
f133572a3a | ||
|
aa3e1fcf63 | ||
|
0f611bfd17 | ||
|
1e764dfaf2 | ||
|
cc5d365aed | ||
|
bdc8db3c87 | ||
|
2c24058d5e | ||
|
ea6345b26f | ||
|
7c33c8f454 | ||
|
698a9eec76 | ||
|
60d5634938 | ||
|
87fdde7fe1 | ||
|
4a244ce4da | ||
|
dc62f9ce63 | ||
|
4efaa253b8 | ||
|
b4f710beaf | ||
|
77a3c635b5 | ||
|
d7c5d28d45 | ||
|
8aa77f54eb | ||
|
ce82e627a6 | ||
|
584b9d5f98 | ||
|
6cb8b7905d | ||
|
ffc1f713dc | ||
|
1728a23b00 | ||
|
01d3de4945 | ||
|
a4b7abce78 | ||
|
f415afb568 | ||
|
3e5f12a4c4 | ||
|
7f8df47f0b | ||
|
27e0751b7e | ||
|
b0bc5ea5b7 | ||
|
e52b7c48a7 | ||
|
2bc07613bb | ||
|
996a11064b | ||
|
df1cf5a43b | ||
|
542a93015f | ||
|
01765d07a8 | ||
|
ed1df4402b | ||
|
4ea716cfea | ||
|
7daebe1318 | ||
|
904dc39a97 | ||
|
777da2eccd | ||
|
1c1f340dc9 | ||
|
fe10bc8c03 | ||
|
a75ca5f08d | ||
|
6c24155bdf | ||
|
753d1d2ed8 | ||
|
0357fdb137 | ||
|
ad3dbfdd58 | ||
|
036032a39c | ||
|
dd5d14df6c | ||
|
1356a27e1d | ||
|
1e90c0c0e4 | ||
|
c0b903e689 | ||
|
db9ae4f586 | ||
|
493568649a | ||
|
5f64fb90ed | ||
|
80d4a65c30 | ||
|
e1e829151b | ||
|
b5bfaced7e | ||
|
ff73a43337 | ||
|
0c1cb1e05a | ||
|
6087b37483 | ||
|
346a9dc045 | ||
|
95dd4e3a3f |
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = false
|
||||
max_line_length = 120
|
||||
tab_width = 4
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
6
.gitignore
vendored
@ -35,9 +35,13 @@ hs_err_pid*
|
||||
.idea/
|
||||
copyright/
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
|
||||
#File System specific files
|
||||
.DS_STORE
|
||||
.DS_Store
|
||||
|
||||
telegrambots/.factorypath
|
||||
|
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal 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
@ -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
|
17
.travis.yml
@ -1,11 +1,22 @@
|
||||
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:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- jdk: openjdk-ea
|
||||
- jdk: oraclejdk-ea
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
|
21
README.md
@ -1,7 +1,7 @@
|
||||
# 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://telegrambots.teamcity.com/app/rest/builds/buildType:(id:TelegramBots_TelegramBotsBuild)/statusIcon.svg)](https://telegrambots.teamcity.com/viewType.html?buildTypeId=TelegramBots_TelegramBotsBuild)
|
||||
[![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)
|
||||
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.telegram/telegrambots/badge.svg)](http://mvnrepository.com/artifact/org.telegram/telegrambots)
|
||||
@ -27,32 +27,33 @@ Just import add the library to your project with one of these options:
|
||||
<dependency>
|
||||
<groupId>org.telegram</groupId>
|
||||
<artifactId>telegrambots</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. Using Gradle:
|
||||
|
||||
```gradle
|
||||
compile "org.telegram:telegrambots:3.6"
|
||||
implementation 'org.telegram:telegrambots:6.8.0'
|
||||
```
|
||||
|
||||
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/3.6)
|
||||
3. Download the jar(including all dependencies) from [here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.6)
|
||||
3. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/6.8.0)
|
||||
4. Download the jar(including all dependencies) from [here](https://mvnrepository.com/artifact/org.telegram/telegrambots/6.8.0)
|
||||
|
||||
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) {
|
||||
ApiContextInitializer.init();
|
||||
TelegramBotsApi telegramBotsApi = new TelegramBotsApi();
|
||||
try {
|
||||
TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
|
||||
telegramBotsApi.registerBot(new ChannelHandlers());
|
||||
telegramBotsApi.registerBot(new DirectionsHandlers());
|
||||
telegramBotsApi.registerBot(new RaeHandlers());
|
||||
@ -95,7 +96,7 @@ Feel free to create issues [here](https://github.com/rubenlagus/TelegramBots/iss
|
||||
|
||||
## Powered by Intellij
|
||||
<p align="center">
|
||||
<a href="https://www.jetbrains.com"><img src="jetbrains.png" width="75"></a>
|
||||
<a href="https://www.jetbrains.com/?from=TelegramBots"><img src="jetbrains.png" width="75"></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
22
TelegramBots.svg
Normal file
@ -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 |
@ -1,3 +1,185 @@
|
||||
### <a id="6.8.0"></a>6.8.0 ###
|
||||
1. Update Api version [6.8](https://core.telegram.org/bots/api-changelog#august-18-2023)
|
||||
2. Fix #1254
|
||||
|
||||
### <a id="6.7.0"></a>6.7.0 ###
|
||||
1. Update Api version [6.7](https://core.telegram.org/bots/api-changelog#april-21-2023)
|
||||
|
||||
**[[How to update to version 6.7.0|How-To-Update#6.7.0]]**
|
||||
|
||||
### <a id="6.6.0"></a>6.6.0 ###
|
||||
1. Update Api version [6.6](https://core.telegram.org/bots/api-changelog#march-9-2023)
|
||||
|
||||
### <a id="6.5.0"></a>6.5.0 ###
|
||||
1. Update Api version [6.5](https://core.telegram.org/bots/api-changelog#february-3-2023)
|
||||
|
||||
### <a id="6.4.0"></a>6.4.0 ###
|
||||
1. Update Api version [6.4](https://core.telegram.org/bots/api-changelog#december-30-2022)
|
||||
2. Bug fixing: #1159
|
||||
|
||||
### <a id="6.3.0"></a>6.3.0 ###
|
||||
1. Update Api version [6.3](https://core.telegram.org/bots/api-changelog#November-5-2022)
|
||||
2. Bug fixing: #1116
|
||||
|
||||
### <a id="6.1.0"></a>6.1.0 ###
|
||||
1. Update Api version [6.1](https://core.telegram.org/bots/api-changelog#june-20-2022)
|
||||
2. Add support for Long when setting the chatId: #1065
|
||||
3. Bug fixing: #755, #1036, #1055, #1060, #1064, #1065, #1067, #1069, #1070, #1086, #1088
|
||||
|
||||
**[[How to update to version 6.1.0|How-To-Update#6.1.0]]**
|
||||
|
||||
### <a id="6.0.1"></a>6.0.1 ###
|
||||
1. Update Api version [6.0](https://core.telegram.org/bots/api-changelog#april-16-2022)
|
||||
2. Removed all deprecated methods/classes with this major upgrade
|
||||
|
||||
### <a id="5.7.1"></a>5.7.1 ###
|
||||
1. Update Api version [5.7](https://core.telegram.org/bots/api-changelog#january-31-2022)
|
||||
2. Spring boot 2.5.8
|
||||
3. Bug Fixing: #1023
|
||||
|
||||
### <a id="5.6.0"></a>5.6.0 ###
|
||||
1. Update Api version [5.6](https://core.telegram.org/bots/api-changelog#december-30-2021)
|
||||
|
||||
### <a id="5.5.0"></a>5.5.0 ###
|
||||
1. Update Api version [5.5](https://core.telegram.org/bots/api-changelog#december-7-2021)
|
||||
|
||||
### <a id="5.4.0.1"></a>5.4.0.1 ###
|
||||
1. Bug fixing: #999, #1000
|
||||
|
||||
### <a id="5.4.0"></a>5.4.0 ###
|
||||
1. Update Api version [5.4](https://core.telegram.org/bots/api-changelog#november-5-2021)
|
||||
2. Bug fixing: #968, #958, #942
|
||||
|
||||
### <a id="5.3.0"></a>5.3.0 ###
|
||||
1. Update Api version [5.3](https://core.telegram.org/bots/api-changelog#june-25-2021)
|
||||
2. `KeyboardRow` now support creation from a collection of buttons.
|
||||
3. Bug fixing: #920, #931 and #937
|
||||
|
||||
**[[How to update to version 5.3.0|How-To-Update#5.3.0]]**
|
||||
|
||||
### <a id="5.2.0"></a>5.2.0 ###
|
||||
1. Update Api version [5.2](https://core.telegram.org/bots/api-changelog#april-26-2021)
|
||||
2. Allow custom BackOff implementations
|
||||
3. Spring version 2.4.5 in spring module
|
||||
4. Bug fixing: #869, #888 and #892
|
||||
|
||||
### <a id="5.1.1"></a>5.1.1 ###
|
||||
1. Bug fixing: #874
|
||||
|
||||
### <a id="5.1.0"></a>5.1.0 ###
|
||||
1. Update Api version [5.1](https://core.telegram.org/bots/api-changelog#march-9-2021)
|
||||
2. Bug fixing: #832, #841, #844, #851, #857
|
||||
3. Update Spring boot version 2.4.3
|
||||
4. Update Gradle docs
|
||||
5. Added CommandMessage to extensions
|
||||
6. Abilities: Inject bot instance to reply update consumer support for multiple reply declarations.
|
||||
|
||||
**[[How to update to version 5.1.0|How-To-Update#5.1.0]]**
|
||||
|
||||
### <a id="5.0.1"></a>5.0.1 ###
|
||||
1. Fixing couple of bugs from 5.0.0
|
||||
2. Bug 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
|
||||
|
@ -1,13 +1,6 @@
|
||||
* [Terminated by other long poll or webhook](#terminted_by_other)
|
||||
* ["No implementation for org.telegram.telegrambots.generics.BotSession was bound"](#no_implementation_was_bound)
|
||||
* ["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.
|
||||
|
||||
## <a id="no_implementation_was_bound"></a>No implementation for org.telegram.telegrambots.generics.BotSession was bound ##
|
||||
Please follow the steps as explained [here](https://github.com/rubenlagus/TelegramBots/wiki/How-To-Update#to-version-243) in "How To Update"
|
||||
> At the beginning of your program (before creating your TelegramBotsApi instance, add the following line:
|
||||
```
|
||||
ApiContextInitializer.init();
|
||||
```
|
||||
|
@ -1,6 +1,8 @@
|
||||
* [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 send stickers?](#how_to_send_stickers)
|
||||
* [How to use custom keyboards?](#how_to_use_custom_keyboards)
|
||||
* [How can I run my bot?](#how_to_host)
|
||||
* [How can I compile my project?](#how_to_compile)
|
||||
@ -21,9 +23,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
|
||||
@ -74,9 +74,41 @@ 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:
|
||||
There are several methods 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:
|
||||
|
||||
```java
|
||||
public void sendImageFromUrl(String url, String chatId) {
|
||||
@ -85,10 +117,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();
|
||||
}
|
||||
@ -100,10 +132,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();
|
||||
}
|
||||
@ -114,41 +146,67 @@ 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();
|
||||
}
|
||||
}
|
||||
```
|
||||
## <a id="how_to_send_stickers"></a>How to send stickers? ##
|
||||
|
||||
There are several ways to send a sticker, but now we will use `file_id` and `url`.
|
||||
|
||||
`file_id`: To get the *file_id*, you have to send your sticker to the [**Get Sticker ID**](https://t.me/idstickerbot?do=open_link) bot and then you will receive a string.
|
||||
`url`: All you need to have is an link to the sticker in `.webp` format, like [**This**](https://www.gstatic.com/webp/gallery/5.webp).
|
||||
#### Implementation
|
||||
Just call the method below in your `onUpdateReceived(Update update)` method.
|
||||
```java
|
||||
// Sticker_file_id is received from @idstickerbot bot
|
||||
private void StickerSender(Update update, String Sticker_file_id) {
|
||||
//the ChatId that we received form Update class
|
||||
String ChatId = update.getMessage().getChatId().toString();
|
||||
// Create an InputFile containing Sticker's file_id or URL
|
||||
InputFile StickerFile = new InputFile(Sticker_file_id);
|
||||
// Create a SendSticker object using the ChatId and StickerFile
|
||||
SendSticker TheSticker = new SendSticker(ChatId, StickerFile);
|
||||
|
||||
// Will reply the sticker to the message sent
|
||||
//TheSticker.setReplyToMessageId(update.getMessage().getMessageId());
|
||||
|
||||
try { // Execute the method
|
||||
execute(TheSticker);
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
## <a id="how_to_send_photos_file_id"></a>How to send photo by its file_id? ##
|
||||
|
||||
In this example we will check if user sends to bot a photo, if it is, get Photo's file_id and send this photo by file_id to user.
|
||||
```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? ##
|
||||
@ -195,6 +253,46 @@ Custom keyboards can be appended to messages using the `setReplyMarkup`. In this
|
||||
}
|
||||
```
|
||||
|
||||
[InlineKeyboardMarkup](https://core.telegram.org/bots/api#inlinekeyboardmarkup) use list to capture the buttons instead of KeyboardRow.
|
||||
|
||||
```java
|
||||
public void sendInlineKeyboard(String chatId) {
|
||||
SendMessage message = new SendMessage();
|
||||
message.setChatId(chatId);
|
||||
message.setText("Inline model below.");
|
||||
|
||||
// Create InlineKeyboardMarkup object
|
||||
InlineKeyboardMarkup inlineKeyboardMarkup = new InlineKeyboardMarkup();
|
||||
// Create the keyboard (list of InlineKeyboardButton list)
|
||||
List<List<InlineKeyboardButton>> keyboard = new ArrayList<>();
|
||||
// Create a list for buttons
|
||||
List<InlineKeyboardButton> Buttons = new ArrayList<InlineKeyboardButton>();
|
||||
// Initialize each button, the text must be written
|
||||
InlineKeyboardButton youtube= new InlineKeyboardButton("youtube");
|
||||
// Also must use exactly one of the optional fields,it can edit by set method
|
||||
youtube.setUrl("https://www.youtube.com");
|
||||
// Add button to the list
|
||||
Buttons.add(youtube);
|
||||
// Initialize each button, the text must be written
|
||||
InlineKeyboardButton github= new InlineKeyboardButton("github");
|
||||
// Also must use exactly one of the optional fields,it can edit by set method
|
||||
github.setUrl("https://github.com");
|
||||
// Add button to the list
|
||||
Buttons.add(github);
|
||||
keyboard.add(Buttons);
|
||||
inlineKeyboardMarkup.setKeyboard(keyboard);
|
||||
// Add it to the message
|
||||
message.setReplyMarkup(inlineKeyboardMarkup);
|
||||
|
||||
try {
|
||||
// Send the message
|
||||
execute(message);
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## <a id="how_to_host"></a>How can I run my bot? ##
|
||||
|
||||
You don't need to spend a lot of money into hosting your own telegram bot. Basically, there are two options around how to host:
|
||||
@ -212,16 +310,16 @@ This is just one way, how you can compile it (here with maven). The example belo
|
||||
Please use ```execute()``` instead.
|
||||
Example:
|
||||
```java
|
||||
SendMessage sn = new SendMessage();
|
||||
SendMessage message = new SendMessage();
|
||||
//add chat id and text
|
||||
execute(sn);
|
||||
execute(message);
|
||||
```
|
||||
|
||||
If you extend ```TelegramLongPollingCommandBot```, then use ```AbsSender.execute()``` instead.
|
||||
|
||||
|
||||
## <a id="example_webhook"></a>Is there any example for WebHook? ##
|
||||
Please see the example Bot for https://telegram.me/SnowcrashBot in the [TelegramBotsExample]() repo and also an [example bot for Sping Boot](https://github.com/UnAfraid/SpringTelegramBot) from [UnAfraid](https://github.com/UnAfraid) [here](https://github.com/UnAfraid/SpringTelegramBot/blob/master/src/main/java/com/github/unafraid/spring/bot/TelegramWebhookBot.java)
|
||||
Please see the example Bot for https://telegram.me/SnowcrashBot in the [TelegramBotsExample]() repo and also an [example bot for Sping Boot](https://github.com/UnAfraid/SpringTelegramBot) from [UnAfraid](https://github.com/UnAfraid) [here](https://github.com/UnAfraid/SpringTelegramBot/blob/master/src/main/java/com/github/unafraid/spring/bot/TelegramWebHookBot.java)
|
||||
|
||||
|
||||
|
||||
@ -231,25 +329,20 @@ Your main spring boot class should look like this:
|
||||
|
||||
```java
|
||||
@SpringBootApplication
|
||||
//Add this annotation to enable automatic bots initializing
|
||||
@EnableTelegramBots
|
||||
public class YourApplicationMainClass {
|
||||
|
||||
public static void main(String[] args) {
|
||||
//Add this line to initialize bots context
|
||||
ApiContextInitializer.init();
|
||||
|
||||
SpringApplication.run(MusicUploaderApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(YourApplicationMainClass.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After that your bot will look like:
|
||||
```java
|
||||
//Standart Spring component annotation
|
||||
//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.
|
||||
Also you could just implement LongPollingBot or WebHookBot interfaces. All this bots will be registered in context and connected to Telegram api.
|
||||
|
@ -1,7 +1,7 @@
|
||||
So, you just wanna program your own Telegram bot with TelegramBots? Let's see the fast version.
|
||||
So, you’d like to create your own Telegram bot with TelegramBots? Then Let's get You started quickly.
|
||||
|
||||
## Grab the library
|
||||
First you need ot get the library and add it to your project. There are few possibilities for this:
|
||||
First you need to acquire the library and add it to your project. There are several ways to do this:
|
||||
|
||||
1. If you use [Maven](https://maven.apache.org/), [Gradle](https://gradle.org/), etc; you should be able to import the dependency directly from [Maven Central Repository](http://mvnrepository.com/artifact/org.telegram/telegrambots). For example:
|
||||
|
||||
@ -11,23 +11,23 @@ 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.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
* With **Gradle**:
|
||||
|
||||
```groovy
|
||||
compile group: 'org.telegram', name: 'telegrambots', version: '3.5'
|
||||
```gradle
|
||||
implementation 'org.telegram:telegrambots:6.8.0'
|
||||
```
|
||||
|
||||
2. Don't like **Maven Central Repository**? It can also be taken from [Jitpack](https://jitpack.io/#rubenlagus/TelegramBots).
|
||||
3. Import the library *.jar* direclty to your project. You can find it [here](https://github.com/rubenlagus/TelegramBots/releases), don't forget to take last version, it usually is a good idea. Depending on the IDE you are using, the process to add a library is different, here is a video that may help with [Intellij](https://www.youtube.com/watch?v=NZaH4tjwMYg) or [Eclipse](https://www.youtube.com/watch?v=VWnfHkBgO1I)
|
||||
2. Don't like the **Maven Central Repository**? It can also be grabbed from [Jitpack](https://jitpack.io/#rubenlagus/TelegramBots).
|
||||
3. Import the library *.jar* direclty to your project. You can find it [here](https://github.com/rubenlagus/TelegramBots/releases), don't forget to fetch the latest version, it usually is a good idea. Depending on the IDE you are using, the process to add a library is different, here is a video that may help with [Intellij](https://www.youtube.com/watch?v=NZaH4tjwMYg) or [Eclipse](https://www.youtube.com/watch?v=VWnfHkBgO1I)
|
||||
|
||||
|
||||
## Build our first bot
|
||||
Now that we have the library, we can start coding. There are few steps to follow, in this tutorial (for the sake of simplicity), we are going to build a [Long Polling Bot](http://en.wikipedia.org/wiki/Push_technology#Long_polling):
|
||||
## Building your first bot
|
||||
Now that you have the library, you can start coding. There are few steps to follow, in this tutorial (for the sake of simplicity), we are going to build a [Long Polling Bot](http://en.wikipedia.org/wiki/Push_technology#Long_polling):
|
||||
|
||||
1. **Create your actual bot:**
|
||||
1. **Creating your actual bot:**
|
||||
The class must extends `TelegramLongPollingBot` and implement necessary methods:
|
||||
|
||||
```java
|
||||
@ -84,9 +84,10 @@ Now that we have the library, we can start coding. There are few steps to follow
|
||||
public void onUpdateReceived(Update update) {
|
||||
// We check if the update has a message and the message has text
|
||||
if (update.hasMessage() && update.getMessage().hasText()) {
|
||||
SendMessage message = new SendMessage() // Create a SendMessage object with mandatory fields
|
||||
.setChatId(update.getMessage().getChatId())
|
||||
.setText(update.getMessage().getText());
|
||||
SendMessage message = new SendMessage(); // Create a SendMessage object with mandatory fields
|
||||
message.setChatId(update.getMessage().getChatId().toString());
|
||||
message.setText(update.getMessage().getText());
|
||||
|
||||
try {
|
||||
execute(message); // Call method to send the message
|
||||
} catch (TelegramApiException e) {
|
||||
@ -98,15 +99,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 do 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 +114,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 +136,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();
|
||||
|
88
TelegramBots.wiki/Handling-Bot-Tokens.md
Normal file
@ -0,0 +1,88 @@
|
||||
* [Bot Token Rules](#bot-token-rules)
|
||||
* [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-rules"></a> Bot Token Rules ##
|
||||
* 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 start a new terminal or run the command above
|
||||
|
||||
### 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]
|
||||
```
|
@ -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/)
|
||||
|
@ -1,3 +1,149 @@
|
||||
### <a id="6.8.0"></a>To version 6.8.0 ###
|
||||
1. Api methods with thumbnails have changed the field, use getThumbnail()/setThumbnail() instead of getThumb()/setThumb()
|
||||
2. In `AddStickerToSet`/`CreateNewStickerSet`/`UploadStickerFile`/etc, use field `sticker` instead of the deprecated fields.
|
||||
3. `ChatMember` has more details permissions, use those instead of the legacy general ones.
|
||||
4. All classes with mandatory fields will lose the default no-arg constructor in the future.
|
||||
5. In `AnswerInlineQuery`, start using the `button` field instead of deprecated parameters.
|
||||
|
||||
### <a id="6.1.0"></a>To version 6.1.0 ###
|
||||
1. As per API guidelines, FileSize can now have 64 bits size, hence they are now using Long datatype instead of Integer.
|
||||
2. Methods accept chatId as Long or String.
|
||||
|
||||
### <a id="5.3.0"></a>To version 5.3.0 ###
|
||||
1. As per API guidelines, ChatMember method has been divided in different classed.
|
||||
Where used in your code, replace old import with new one
|
||||
(GetChatAdministrators.java, GetChatMember.java, ChatMemberUpdated.java):
|
||||
|
||||
`import org.telegram.telegrambots.meta.api.objects.ChatMember;`
|
||||
|
||||
to
|
||||
|
||||
`import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember;`
|
||||
2. ChatMember is an interface now, you'll need to cast it to the corresponding implementation when using it.
|
||||
3. `GetChatMembersCount` renamed to `GetChatMemberCount`, old version will still work until next major version.
|
||||
4. `KickChatMember` renamed to `BanChatMember`, old version will still work until next major version.
|
||||
|
||||
|
||||
### <a id="5.1.0"></a>To version 5.1.0 ###
|
||||
1. All users IDs fields are now Long type as per API guidelines.
|
||||
|
||||
### <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` or `Bot` instance, add the following line:
|
||||
|
@ -31,7 +31,7 @@ public class MyBot extends AbilityBot {
|
||||
|
||||
Now you are able to set up your proxy
|
||||
|
||||
#### without authentication
|
||||
#### Without authentication
|
||||
|
||||
```java
|
||||
public class Main {
|
||||
@ -44,20 +44,16 @@ public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
|
||||
ApiContextInitializer.init();
|
||||
|
||||
// Create the TelegramBotsApi object to register your bots
|
||||
TelegramBotsApi botsApi = new TelegramBotsApi();
|
||||
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSessioin.class);
|
||||
|
||||
// Set up Http proxy
|
||||
DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class);
|
||||
DefaultBotOptions botOptions = new DefaultBotOptions();
|
||||
|
||||
HttpHost httpHost = new HttpHost(PROXY_HOST, PROXY_PORT);
|
||||
|
||||
RequestConfig requestConfig = RequestConfig.custom().setProxy(httpHost).setAuthenticationEnabled(false).build();
|
||||
botOptions.setRequestConfig(requestConfig);
|
||||
botOptions.setHttpProxy(httpHost);
|
||||
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);
|
||||
@ -89,25 +85,24 @@ public class Main {
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
|
||||
ApiContextInitializer.init();
|
||||
|
||||
// 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();
|
||||
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
|
||||
|
||||
// Set up Http proxy
|
||||
DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class);
|
||||
DefaultBotOptions botOptions = new DefaultBotOptions();
|
||||
|
||||
CredentialsProvider credsProvider = new BasicCredentialsProvider();
|
||||
credsProvider.setCredentials(
|
||||
new AuthScope(PROXY_HOST, PROXY_PORT),
|
||||
new UsernamePasswordCredentials(PROXY_USER, PROXY_PASSWORD));
|
||||
|
||||
HttpHost httpHost = new HttpHost(PROXY_HOST, PROXY_PORT);
|
||||
|
||||
RequestConfig requestConfig = RequestConfig.custom().setProxy(httpHost).setAuthenticationEnabled(true).build();
|
||||
botOptions.setRequestConfig(requestConfig);
|
||||
botOptions.setCredentialsProvider(credsProvider);
|
||||
botOptions.setHttpProxy(httpHost);
|
||||
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);
|
||||
@ -119,4 +114,6 @@ public class Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
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)
|
||||
|
@ -3,13 +3,19 @@
|
||||
* [[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]]
|
61
TelegramBots.wiki/abilities/Ability-Extensions.md
Normal file
@ -0,0 +1,61 @@
|
||||
# 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 {
|
||||
private AbilityBot extensionUser;
|
||||
|
||||
public MrGoodGuy(AbilityBot extensionUser) { this.extensionUser = extensionUser; }
|
||||
|
||||
public Ability nice() {
|
||||
return Ability.builder()
|
||||
.name("nice")
|
||||
.privacy(PUBLIC)
|
||||
.locality(ALL)
|
||||
.action(ctx -> extensionUser.silent().send("You're awesome!", ctx.chatId())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class MrBadGuy implements AbilityExtension {
|
||||
private AbilityBot extensionUser;
|
||||
|
||||
public MrBadGuy(AbilityBot extensionUser) { this.extensionUser = extensionUser; }
|
||||
public Ability notnice() {
|
||||
return Ability.builder()
|
||||
.name("notnice")
|
||||
.privacy(PUBLIC)
|
||||
.locality(ALL)
|
||||
.action(ctx -> extensionUser.silent().send("You're horrible!", ctx.chatId())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class YourAwesomeBot implements AbilityBot {
|
||||
|
||||
// Constructor for your bot
|
||||
|
||||
public AbilityExtension goodGuy() {
|
||||
return new MrGoodGuy(this);
|
||||
}
|
||||
|
||||
public AbilityExtension badGuy() {
|
||||
return new MrBadGuy(this);
|
||||
}
|
||||
|
||||
// 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(this), new MrBadGuy(this));
|
||||
}
|
||||
|
||||
// Override creatorId
|
||||
}
|
||||
```
|
45
TelegramBots.wiki/abilities/Ability-Toggle.md
Normal 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.
|
5
TelegramBots.wiki/abilities/Additional-Examples.md
Normal 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
|
@ -29,6 +29,50 @@ As an example, if you want to restrict the updates to photos only, then you may
|
||||
```java
|
||||
@Override
|
||||
public boolean checkGlobalFlags(Update update) {
|
||||
return Flag.PHOTO;
|
||||
return Flag.PHOTO.test(update);
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
## 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
|
||||
}
|
||||
```
|
||||
|
@ -54,21 +54,20 @@ public Ability saysHelloWorld() {
|
||||
The test for this ability would be:
|
||||
|
||||
```java
|
||||
@Test
|
||||
@Test
|
||||
public void canSayHelloWorld() {
|
||||
Update upd = new Update();
|
||||
// Create a new EndUser - EndUser is a class similar to Telegram User, but contains
|
||||
// some utility methods like fullName() and shortName() for ease of use
|
||||
EndUser endUser = EndUser.endUser(USER_ID, "Abbas", "Abou Daya", "addo37");
|
||||
// Create a new User - User is a class similar to Telegram User
|
||||
User user = new User(USER_ID, "Abbas", false, "Abou Daya", "addo37", null);
|
||||
// This is the context that you're used to, it is the necessary conumer item for the ability
|
||||
MessageContext context = MessageContext.newContext(upd, endUser, CHAT_ID);
|
||||
MessageContext context = MessageContext.newContext(upd, user, CHAT_ID);
|
||||
|
||||
// We consume a context in the lamda declaration, so we pass the context to the action logic
|
||||
bot.saysHelloWorld().action().accept(context);
|
||||
|
||||
// We verify that the sender was called only ONCE and sent Hello World to CHAT_ID
|
||||
// The sender here is a mock!
|
||||
Mockito.verify(sender, times(1)).send("Hello World!", CHAT_ID);
|
||||
// We verify that the silent sender was called only ONCE and sent Hello World to CHAT_ID
|
||||
// The silent sender here is a mock!
|
||||
Mockito.verify(silent, times(1)).send("Hello World!", CHAT_ID);
|
||||
}
|
||||
```
|
||||
|
||||
@ -85,10 +84,10 @@ import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.telegram.abilitybots.api.db.DBContext;
|
||||
import org.telegram.abilitybots.api.db.MapDBContext;
|
||||
import org.telegram.abilitybots.api.objects.EndUser;
|
||||
import org.telegram.abilitybots.api.objects.MessageContext;
|
||||
import org.telegram.abilitybots.api.sender.MessageSender;
|
||||
import org.telegram.telegrambots.api.objects.Update;
|
||||
import org.telegram.abilitybots.api.sender.SilentSender;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
import org.telegram.telegrambots.meta.api.objects.User;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ -98,36 +97,38 @@ public class ExampleBotTest {
|
||||
|
||||
// Your bot handle here
|
||||
private ExampleBot bot;
|
||||
// Your sender here
|
||||
private MessageSender sender;
|
||||
// Your sender here. Also you can create MessageSender
|
||||
private SilentSender silent;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
// Create your bot
|
||||
bot = new ExampleBot();
|
||||
// Call onRegister() to initialize abilities etc.
|
||||
bot.onRegister();
|
||||
// Create a new sender as a mock
|
||||
sender = mock(MessageSender.class);
|
||||
// Set your bot sender to the mocked sender
|
||||
silent = mock(SilentSender.class);
|
||||
// Set your bot silent sender to the mocked sender
|
||||
// THIS is the line that prevents your bot from communicating with Telegram servers when it's running its own abilities
|
||||
// All method calls will go through the mocked interface -> which would do nothing except logging the fact that you've called this function with the specific arguments
|
||||
bot.sender = sender;
|
||||
// Create setter in your bot
|
||||
bot.setSilentSender(silent);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSayHelloWorld() {
|
||||
Update upd = new Update();
|
||||
// Create a new EndUser - EndUser is a class similar to Telegram User, but contains
|
||||
// some utility methods like fullName() and shortName() for ease of use
|
||||
EndUser endUser = EndUser.endUser(USER_ID, "Abbas", "Abou Daya", "addo37");
|
||||
// Create a new User - User is a class similar to Telegram User
|
||||
User user = new User(USER_ID, "Abbas", false, "Abou Daya", "addo37", null);
|
||||
// This is the context that you're used to, it is the necessary conumer item for the ability
|
||||
MessageContext context = MessageContext.newContext(upd, endUser, CHAT_ID);
|
||||
MessageContext context = MessageContext.newContext(upd, user, CHAT_ID);
|
||||
|
||||
// We consume a context in the lamda declaration, so we pass the context to the action logic
|
||||
bot.saysHelloWorld().action().accept(context);
|
||||
|
||||
// We verify that the sender was called only ONCE and sent Hello World to CHAT_ID
|
||||
// The sender here is a mock!
|
||||
Mockito.verify(sender, times(1)).send("Hello World!", CHAT_ID);
|
||||
// We verify that the silent sender was called only ONCE and sent Hello World to CHAT_ID
|
||||
// The silent sender here is a mock!
|
||||
Mockito.verify(silent, times(1)).send("Hello World!", CHAT_ID);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -157,6 +158,7 @@ public class ExampleBotTest {
|
||||
// Offline instance will get deleted at JVM shutdown
|
||||
db = MapDBContext.offlineInstance("test");
|
||||
bot = new ExampleBot(db);
|
||||
bot.onRegister();
|
||||
|
||||
...
|
||||
}
|
||||
@ -178,11 +180,14 @@ public class ExampleBotTest {
|
||||
private DBContext db;
|
||||
private MessageSender sender;
|
||||
|
||||
@Before
|
||||
@Before
|
||||
public void setUp() {
|
||||
bot = new ExampleBot(db);
|
||||
bot.onRegister();
|
||||
sender = mock(MessageSender.class);
|
||||
bot.silent = new SilentSender(sender);
|
||||
SilentSender silent = new SilentSender(sender);
|
||||
// Create setter in your bot
|
||||
bot.setSilentSender(silent);
|
||||
...
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ We'll be introducing an ability that maintains a special counter for every user.
|
||||
// db.getMap takes in a string, this must be unique and the same everytime you want to call the exact same map
|
||||
// TODO: Using integer as a key in this db map is not recommended, it won't be serialized/deserialized properly if you ever decide to recover/backup db
|
||||
Map<String, Integer> countMap = db.getMap("COUNTERS");
|
||||
int userId = ctx.user().id();
|
||||
int userId = ctx.user().getId();
|
||||
|
||||
// Get and increment counter, put it back in the map
|
||||
Integer counter = countMap.compute(String.valueOf(userId), (id, count) -> count == null ? 1 : ++count);
|
||||
@ -41,7 +41,7 @@ We'll be introducing an ability that maintains a special counter for every user.
|
||||
*/
|
||||
|
||||
// Send formatted will enable markdown
|
||||
String message = String.format("%s, your count is now *%d*!", ctx.user().shortName(), counter);
|
||||
String message = String.format("%s, your count is now *%d*!", ctx.user().getUserName(), counter);
|
||||
silent.send(message, ctx.chatId());
|
||||
})
|
||||
.build();
|
||||
|
@ -9,12 +9,12 @@ As with any Java project, you will need to set your dependencies.
|
||||
<dependency>
|
||||
<groupId>org.telegram</groupId>
|
||||
<artifactId>telegrambots-abilities</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
* **Gradle**
|
||||
```groovy
|
||||
compile group: 'org.telegram', name: 'telegrambots-abilties', version: '3.6'
|
||||
```gradle
|
||||
implementation 'org.telegram:telegrambots-abilities:6.8.0'
|
||||
```
|
||||
* [JitPack](https://jitpack.io/#rubenlagus/TelegramBots)
|
||||
|
||||
@ -81,13 +81,10 @@ Running the bot is just like running the regular Telegram bots. Create a Java cl
|
||||
```java
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
// Initializes dependencies necessary for the base bot - Guice
|
||||
ApiContextInitializer.init();
|
||||
|
||||
// Create the TelegramBotsApi object to register your bots
|
||||
TelegramBotsApi botsApi = new TelegramBotsApi();
|
||||
|
||||
try {
|
||||
// Create the TelegramBotsApi object to register your bots
|
||||
TelegramBotsApi botsApi = new TelegramBotsApi(DefaultBotSession.class);
|
||||
|
||||
// Register your newly created AbilityBot
|
||||
botsApi.registerBot(new HelloBot());
|
||||
} catch (TelegramApiException e) {
|
||||
@ -112,7 +109,9 @@ Since you've implemented an AbilityBot, you get **factory abilities** as well. T
|
||||
* /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
|
||||
* /stats - displays how many times were your abilities called
|
||||
* **This command returns empty String by default.** To use this command, add ```.setStatsEnabled(true)``` to your abilities. You'll then be able to view how many times each of them was called.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
131
TelegramBots.wiki/abilities/State-Machines.md
Normal 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>)
|
||||
```
|
@ -32,7 +32,7 @@ public Ability playWithMe() {
|
||||
.privacy(PUBLIC)
|
||||
.locality(ALL)
|
||||
.input(0)
|
||||
.action(ctx -> sender.forceReply(playMessage, ctx.chatId()))
|
||||
.action(ctx -> silent.forceReply(playMessage, ctx.chatId()))
|
||||
// The signature of a reply is -> (Consumer<Update> action, Predicate<Update>... conditions)
|
||||
// So, we first declare the action that takes an update (NOT A MESSAGECONTEXT) like the action above
|
||||
// The reason of that is that a reply can be so versatile depending on the message, context becomes an inefficient wrapping
|
||||
@ -40,7 +40,7 @@ public Ability playWithMe() {
|
||||
// Prints to console
|
||||
System.out.println("I'm in a reply!");
|
||||
// Sends message
|
||||
sender.send("It's been nice playing with you!", upd.getMessage().getChatId());
|
||||
silent.send("It's been nice playing with you!", upd.getMessage().getChatId());
|
||||
},
|
||||
// Now we start declaring conditions, MESSAGE is a member of the enum Flag class
|
||||
// That class contains out-of-the-box predicates for your replies!
|
||||
|
3
TelegramBots.wiki/abilities/img/replyflow_diagram.svg
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
TelegramBots.wiki/understanding-the-library/Bot_curl.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
TelegramBots.wiki/understanding-the-library/Bot_intellij.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
TelegramBots.wiki/understanding-the-library/Telegram-Diagram.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -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.
|
BIN
TelegramBots.wiki/understanding-the-library/poll_example.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
TelegramBots.wiki/understanding-the-library/poll_params.png
Normal file
After Width: | Height: | Size: 41 KiB |
310
mvnw
vendored
Executable 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
@ -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%
|
218
pom.xml
@ -4,10 +4,10 @@
|
||||
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>
|
||||
<artifactId>Bots</artifactId>
|
||||
<groupId>org.warp</groupId>
|
||||
<artifactId>bots</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>3.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
|
||||
<modules>
|
||||
<module>telegrambots</module>
|
||||
@ -15,18 +15,222 @@
|
||||
<module>telegrambots-extensions</module>
|
||||
<module>telegrambots-abilities</module>
|
||||
<module>telegrambots-spring-boot-starter</module>
|
||||
<module>telegrambots-chat-session-bot</module>
|
||||
</modules>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>MIT License</name>
|
||||
<url>http://www.opensource.org/licenses/mit-license.php</url>
|
||||
<url>https://www.opensource.org/licenses/mit-license.php</url>
|
||||
<distribution>repo</distribution>
|
||||
</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>
|
||||
<repository>
|
||||
<id>mchv-release-distribution</id>
|
||||
<name>MCHV Release Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>mchv-snapshot-distribution</id>
|
||||
<name>MCHV Snapshot Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
|
||||
</snapshotRepository>
|
||||
</distributionManagement>
|
||||
|
||||
<properties>
|
||||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
<bots.version>3.6.1</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.9.1</junit.version>
|
||||
<mockito.version>4.8.1</mockito.version>
|
||||
<mockitojupiter.version>4.8.1</mockitojupiter.version>
|
||||
<jacksonanotation.version>2.14.2</jacksonanotation.version>
|
||||
<jackson.version>2.14.2</jackson.version>
|
||||
<slf4j.version>2.0.3</slf4j.version>
|
||||
<jakarta.annotation.version>2.1.1</jakarta.annotation.version>
|
||||
<lombok.version>1.18.28</lombok.version>
|
||||
<guava.version>32.0.0-jre</guava.version>
|
||||
<commons.version>3.12.0</commons.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>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-jakarta-xmlbind-annotations</artifactId>
|
||||
<version>${jacksonanotation.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.jakarta.rs</groupId>
|
||||
<artifactId>jackson-jakarta-rs-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.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>${json.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons.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.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.12.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>aggregate</id>
|
||||
<phase>site</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<doclint>none</doclint>
|
||||
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-versions</id>
|
||||
<goals>
|
||||
<goal>enforce</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<rules>
|
||||
<DependencyConvergence/>
|
||||
</rules>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
@ -18,19 +18,19 @@ Usage
|
||||
<dependency>
|
||||
<groupId>org.telegram</groupId>
|
||||
<artifactId>telegrambots-abilities</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle**
|
||||
|
||||
```gradle
|
||||
compile "org.telegram:telegrambots-abilities:3.6"
|
||||
implementation 'org.telegram:telegrambots-abilities:6.8.0'
|
||||
```
|
||||
|
||||
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v3.6)
|
||||
**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v5.0.1)
|
||||
|
||||
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.6)
|
||||
**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v5.0.1)
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
@ -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.warp</groupId>
|
||||
<artifactId>bots</artifactId>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>telegrambots-abilities</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Telegram Ability Bot</name>
|
||||
@ -52,58 +57,56 @@
|
||||
</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>
|
||||
<id>mchv-release-distribution</id>
|
||||
<name>MCHV Release Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>mchv-snapshot-distribution</id>
|
||||
<name>MCHV Snapshot Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
|
||||
</snapshotRepository>
|
||||
</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.6.1</bots.version>
|
||||
<commonslang.version>3.5</commonslang.version>
|
||||
<mapdb.version>3.0.4</mapdb.version>
|
||||
<guava.version>19.0</guava.version>
|
||||
|
||||
<commonslang.version>3.12.0</commonslang.version>
|
||||
<mapdb.version>3.0.8</mapdb.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.telegram</groupId>
|
||||
<groupId>org.warp</groupId>
|
||||
<artifactId>telegrambots</artifactId>
|
||||
<version>${bots.version}</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commonslang.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<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>
|
||||
|
||||
@ -114,10 +117,15 @@
|
||||
<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-M6</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
@ -128,20 +136,9 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.6.3</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.0.0</version>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>clean-project</id>
|
||||
@ -154,7 +151,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
@ -173,7 +170,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@ -185,14 +182,14 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.10.3</version>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
<doclint>none</doclint>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@ -200,7 +197,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.7.201606060606</version>
|
||||
<version>0.8.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@ -219,7 +216,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<version>3.0.0-M3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-versions</id>
|
||||
@ -237,7 +234,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
@ -251,13 +248,21 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
13
telegrambots-abilities/src/main/java/module-info.java
Normal file
@ -0,0 +1,13 @@
|
||||
module telegrambots.abilities {
|
||||
requires telegrambots;
|
||||
requires telegrambots.meta;
|
||||
requires com.google.common;
|
||||
requires org.slf4j;
|
||||
requires com.fasterxml.jackson.core;
|
||||
requires com.fasterxml.jackson.databind;
|
||||
requires mapdb;
|
||||
requires org.apache.commons.lang3;
|
||||
requires annotations;
|
||||
requires org.apache.commons.io;
|
||||
requires static com.fasterxml.jackson.annotation;
|
||||
}
|
@ -1,875 +1,62 @@
|
||||
package org.telegram.abilitybots.api.bot;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableList.Builder;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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.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.groupadministration.GetChatAdministrators;
|
||||
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.telegrambots.api.objects.User;
|
||||
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.*;
|
||||
import java.util.Map.Entry;
|
||||
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 com.google.common.collect.MultimapBuilder.hashKeys;
|
||||
import static java.lang.String.format;
|
||||
import static java.time.ZonedDateTime.now;
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.Objects.nonNull;
|
||||
import static java.util.Optional.ofNullable;
|
||||
import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
||||
import static java.util.regex.Pattern.compile;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static jersey.repackaged.com.google.common.base.Throwables.propagate;
|
||||
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||
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.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.AbilityMessageCodes.*;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.*;
|
||||
|
||||
/**
|
||||
* The <b>father</b> of all ability bots. Bots that need to utilize abilities need to extend this bot.
|
||||
* <p>
|
||||
* It's important to note that this bot strictly extends {@link TelegramLongPollingBot}.
|
||||
* <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>/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 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";
|
||||
protected static final String REPORT = "report";
|
||||
|
||||
// DB and sender
|
||||
protected final DBContext db;
|
||||
protected MessageSender sender;
|
||||
protected SilentSender silent;
|
||||
|
||||
// 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;
|
||||
|
||||
public abstract int creatorId();
|
||||
|
||||
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions botOptions) {
|
||||
super(botOptions);
|
||||
|
||||
this.botToken = botToken;
|
||||
this.botUsername = botUsername;
|
||||
this.db = db;
|
||||
this.sender = new DefaultSender(this);
|
||||
silent = new SilentSender(sender);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map of ID -> User
|
||||
*/
|
||||
protected Map<Integer, User> 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user with the specified username.
|
||||
*
|
||||
* @param username the username of the required user
|
||||
* @return the user
|
||||
*/
|
||||
protected User getUser(String username) {
|
||||
Integer id = userIds().get(username.toLowerCase());
|
||||
if (id == null) {
|
||||
throw new IllegalStateException(format("Could not find ID corresponding to username [%s]", username));
|
||||
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 User getUser(int id) {
|
||||
User user = users().get(id);
|
||||
if (user == null) {
|
||||
throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id));
|
||||
protected AbilityBot(String botToken, String botUsername, AbilityToggle toggle, DefaultBotOptions options) {
|
||||
this(botToken, botUsername, onlineInstance(botUsername), toggle, options);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
protected int getUserIdSendError(String username, MessageContext ctx) {
|
||||
try {
|
||||
return getUser(username).getId();
|
||||
} catch (IllegalStateException ex) {
|
||||
silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId());
|
||||
throw 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(REPORT)
|
||||
.locality(ALL)
|
||||
.privacy(CREATOR)
|
||||
.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(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode()));
|
||||
|
||||
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 = getPrivacy(ctx.update(), ctx.user().getId());
|
||||
|
||||
ListMultimap<Privacy, String> abilitiesPerPrivacy = abilities.entrySet().stream()
|
||||
.map(entry -> {
|
||||
String name = entry.getValue().name();
|
||||
String info = entry.getValue().info();
|
||||
|
||||
if (!isEmpty(info))
|
||||
return Pair.of(entry.getValue().privacy(), format("/%s - %s", name, info));
|
||||
return Pair.of(entry.getValue().privacy(), format("/%s", name));
|
||||
})
|
||||
.sorted(comparing(Pair::b))
|
||||
.collect(() -> hashKeys().arrayListValues().build(),
|
||||
(map, pair) -> map.put(pair.a(), pair.b()),
|
||||
Multimap::putAll);
|
||||
|
||||
String commands = abilitiesPerPrivacy.asMap().entrySet().stream()
|
||||
.filter(entry -> privacy.compareTo(entry.getKey()) >= 0)
|
||||
.sorted(comparing(Entry::getKey))
|
||||
.map(entry ->
|
||||
entry.getValue().stream()
|
||||
.reduce(entry.getKey().toString(), (a, b) -> format("%s\n%s", a, b))
|
||||
)
|
||||
.collect(joining("\n"));
|
||||
|
||||
if (commands.isEmpty())
|
||||
commands = getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode());
|
||||
|
||||
silent.send(commands, ctx.chatId());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 -> silent.forceReply(
|
||||
getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId()))
|
||||
.reply(update -> {
|
||||
String replyToMsg = update.getMessage().getReplyToMessage().getText();
|
||||
String recoverMessage = getLocalizedMessage(ABILITY_RECOVER_MESSAGE, AbilityUtils.getUser(update).getLanguageCode());
|
||||
if (!replyToMsg.equals(recoverMessage))
|
||||
return;
|
||||
|
||||
String fileId = update.getMessage().getDocument().getFileId();
|
||||
try (FileReader reader = new FileReader(downloadFileWithId(fileId))) {
|
||||
String backupData = IOUtils.toString(reader);
|
||||
if (db.recover(backupData)) {
|
||||
send(ABILITY_RECOVER_SUCCESS, update);
|
||||
} else {
|
||||
send(ABILITY_RECOVER_FAIL, update);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BotLogger.error("Could not recover DB from backup", TAG, 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 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);
|
||||
String bannedUser;
|
||||
|
||||
// Protection from abuse
|
||||
if (userId == creatorId()) {
|
||||
userId = ctx.user().getId();
|
||||
bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user());
|
||||
} else {
|
||||
bannedUser = addTag(username);
|
||||
}
|
||||
|
||||
Set<Integer> blacklist = blacklist();
|
||||
if (blacklist.contains(userId))
|
||||
sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser));
|
||||
else {
|
||||
blacklist.add(userId);
|
||||
sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser));
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Usage: <code>/unban @username</code>
|
||||
*
|
||||
* @return the ability to unban a user
|
||||
*/
|
||||
public Ability unbanUser() {
|
||||
return builder()
|
||||
.name(UNBAN)
|
||||
.locality(ALL)
|
||||
.privacy(ADMIN)
|
||||
.input(1)
|
||||
.action(ctx -> {
|
||||
String username = stripTag(ctx.firstArg());
|
||||
Integer userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Integer> blacklist = blacklist();
|
||||
|
||||
if (!blacklist.remove(userId))
|
||||
silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
|
||||
else {
|
||||
silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId());
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ability to promote a user to a bot admin
|
||||
*/
|
||||
public Ability promoteAdmin() {
|
||||
return builder()
|
||||
.name(PROMOTE)
|
||||
.locality(ALL)
|
||||
.privacy(ADMIN)
|
||||
.input(1)
|
||||
.action(ctx -> {
|
||||
String username = stripTag(ctx.firstArg());
|
||||
Integer userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Integer> admins = admins();
|
||||
if (admins.contains(userId))
|
||||
sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username));
|
||||
else {
|
||||
admins.add(userId);
|
||||
sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username));
|
||||
}
|
||||
}).post(commitTo(db))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the ability to demote an admin to a user
|
||||
*/
|
||||
public Ability demoteAdmin() {
|
||||
return builder()
|
||||
.name(DEMOTE)
|
||||
.locality(ALL)
|
||||
.privacy(ADMIN)
|
||||
.input(1)
|
||||
.action(ctx -> {
|
||||
String username = stripTag(ctx.firstArg());
|
||||
Integer userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Integer> admins = admins();
|
||||
if (admins.remove(userId)) {
|
||||
sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username));
|
||||
} else {
|
||||
sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username));
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regular users and admins who try to claim the bot will get <b>banned</b>.
|
||||
*
|
||||
* @return the ability to claim yourself as the master and creator of the bot
|
||||
*/
|
||||
public Ability claimCreator() {
|
||||
return builder()
|
||||
.name(CLAIM)
|
||||
.locality(ALL)
|
||||
.privacy(CREATOR)
|
||||
.input(0)
|
||||
.action(ctx -> {
|
||||
Set<Integer> admins = admins();
|
||||
int id = creatorId();
|
||||
|
||||
if (admins.contains(id))
|
||||
send(ABILITY_CLAIM_FAIL, ctx);
|
||||
else {
|
||||
admins.add(id);
|
||||
send(ABILITY_CLAIM_SUCCESS, ctx);
|
||||
}
|
||||
})
|
||||
.post(commitTo(db))
|
||||
.build();
|
||||
}
|
||||
|
||||
private Optional<Message> send(String message, MessageContext ctx, String... args) {
|
||||
return silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
|
||||
}
|
||||
|
||||
private Optional<Message> sendMd(String message, MessageContext ctx, String... args) {
|
||||
return silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId());
|
||||
}
|
||||
|
||||
private Optional<Message> send(String message, Update upd) {
|
||||
Long chatId = upd.getMessage().getChatId();
|
||||
return silent.send(getLocalizedMessage(message, AbilityUtils.getUser(upd).getLanguageCode()), chatId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(ImmutableMap::<String, Ability>builder,
|
||||
(b, a) -> b.put(a.name(), a),
|
||||
(b1, b2) -> b1.putAll(b2.build()))
|
||||
.build();
|
||||
|
||||
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(
|
||||
ImmutableList::<Reply>builder,
|
||||
Builder::add,
|
||||
(b1, b2) -> b1.addAll(b2.build()))
|
||||
.build();
|
||||
} 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();
|
||||
User user = 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)
|
||||
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;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Privacy getPrivacy(Update update, int id) {
|
||||
return isCreator(id) ?
|
||||
CREATOR : isAdmin(id) ?
|
||||
ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ?
|
||||
GROUP_ADMIN : PUBLIC;
|
||||
}
|
||||
|
||||
private boolean isGroupAdmin(Update update, int id) {
|
||||
GetChatAdministrators admins = new GetChatAdministrators().setChatId(getChatId(update));
|
||||
|
||||
return silent.execute(admins)
|
||||
.orElse(new ArrayList<>()).stream()
|
||||
.anyMatch(member -> member.getUser().getId() == id);
|
||||
}
|
||||
|
||||
private boolean isCreator(int id) {
|
||||
return id == creatorId();
|
||||
}
|
||||
|
||||
private boolean isAdmin(Integer id) {
|
||||
return admins().contains(id);
|
||||
}
|
||||
|
||||
boolean validateAbility(Trio<Update, Ability, String[]> trio) {
|
||||
return trio.b() != null;
|
||||
}
|
||||
|
||||
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[]{});
|
||||
|
||||
String[] tokens = msg.getText().split(" ");
|
||||
|
||||
if (tokens[0].startsWith("/")) {
|
||||
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
|
||||
Ability ability = abilities.get(abilityToken);
|
||||
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
|
||||
return Trio.of(update, ability, tokens);
|
||||
} 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) {
|
||||
User endUser = AbilityUtils.getUser(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;
|
||||
});
|
||||
|
||||
db.commit();
|
||||
return update;
|
||||
}
|
||||
|
||||
private void updateUserId(User oldUser, User newUser) {
|
||||
if (oldUser != null && oldUser.getUserName() != null) {
|
||||
// Remove old username -> ID
|
||||
userIds().remove(oldUser.getUserName());
|
||||
protected AbilityBot(String botToken, String botUsername, DBContext db, DefaultBotOptions options) {
|
||||
this(botToken, botUsername, db, new DefaultToggle(), options);
|
||||
}
|
||||
|
||||
if (newUser.getUserName() != null) {
|
||||
// Add new mapping with the new username
|
||||
userIds().put(newUser.getUserName().toLowerCase(), newUser.getId());
|
||||
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();
|
||||
protected AbilityBot(String botToken, String botUsername, DBContext db) {
|
||||
this(botToken, botUsername, db, new DefaultToggle());
|
||||
}
|
||||
|
||||
// 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) {
|
||||
this(botToken, botUsername, onlineInstance(botUsername));
|
||||
}
|
||||
|
||||
private File downloadFileWithId(String fileId) throws TelegramApiException {
|
||||
return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId)));
|
||||
}
|
||||
@Override
|
||||
public void onUpdateReceived(Update update) {
|
||||
super.onUpdateReceived(update);
|
||||
}
|
||||
|
||||
|
||||
private String escape(String username) {
|
||||
return username.replace("_", "\\_");
|
||||
}
|
||||
@Override
|
||||
public void clearWebhook() throws TelegramApiRequestException {
|
||||
WebhookUtils.clearWebhook(this);
|
||||
}
|
||||
}
|
@ -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, this, setWebhook);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBotPath() {
|
||||
return botPath;
|
||||
}
|
||||
}
|
@ -0,0 +1,714 @@
|
||||
package org.telegram.abilitybots.api.bot;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.Ability;
|
||||
import org.telegram.abilitybots.api.objects.Locality;
|
||||
import org.telegram.abilitybots.api.objects.MessageContext;
|
||||
import org.telegram.abilitybots.api.objects.Privacy;
|
||||
import org.telegram.abilitybots.api.objects.Reply;
|
||||
import org.telegram.abilitybots.api.objects.ReplyCollection;
|
||||
import org.telegram.abilitybots.api.objects.Stats;
|
||||
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 org.telegram.telegrambots.meta.api.objects.chatmember.ChatMemberAdministrator;
|
||||
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMemberOwner;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
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.ALL;
|
||||
import static org.telegram.abilitybots.api.objects.Locality.GROUP;
|
||||
import static org.telegram.abilitybots.api.objects.Locality.USER;
|
||||
import static org.telegram.abilitybots.api.objects.MessageContext.newContext;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.CREATOR;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.GROUP_ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
|
||||
import static org.telegram.abilitybots.api.objects.Stats.createStats;
|
||||
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_INPUT_FAIL;
|
||||
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_LOCALITY_FAIL;
|
||||
import static org.telegram.abilitybots.api.util.AbilityMessageCodes.CHECK_PRIVACY_FAIL;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.EMPTY_USER;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.getChatId;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.getLocalizedMessage;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.getUser;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.isGroupUpdate;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.isSuperGroupUpdate;
|
||||
import static org.telegram.abilitybots.api.util.AbilityUtils.isUserMessage;
|
||||
|
||||
/**
|
||||
* 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({"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 username
|
||||
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 long creatorId();
|
||||
|
||||
protected BaseAbilityBot(String botToken, String botUsername, DBContext db, AbilityToggle toggle, DefaultBotOptions botOptions) {
|
||||
super(botOptions, 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<Long, User> users() {
|
||||
return db.getMap(USERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the map of <Username,ID>
|
||||
*/
|
||||
public Map<String, Long> userIds() {
|
||||
return db.getMap(USER_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a blacklist containing all the IDs of the banned users
|
||||
*/
|
||||
public Set<Long> blacklist() {
|
||||
return db.getSet(BLACKLIST);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an admin set of all the IDs of bot administrators
|
||||
*/
|
||||
public Set<Long> 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 getBotUsername() {
|
||||
return botUsername;
|
||||
}
|
||||
|
||||
public Privacy getPrivacy(Update update, long id) {
|
||||
return isCreator(id) ?
|
||||
CREATOR : isAdmin(id) ?
|
||||
ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ?
|
||||
GROUP_ADMIN : PUBLIC;
|
||||
}
|
||||
|
||||
public boolean isGroupAdmin(Update update, long id) {
|
||||
return isGroupAdmin(getChatId(update), id);
|
||||
}
|
||||
|
||||
public boolean isGroupAdmin(long chatId, long id) {
|
||||
GetChatAdministrators admins = GetChatAdministrators.builder().chatId(chatId).build();
|
||||
return silent.execute(admins)
|
||||
.orElse(new ArrayList<>())
|
||||
.stream()
|
||||
.map(member -> {
|
||||
final String status = member.getStatus();
|
||||
if (status.equals(ChatMemberOwner.STATUS)
|
||||
|| status.equals(ChatMemberAdministrator.STATUS)) {
|
||||
return member.getUser().getId();
|
||||
}
|
||||
return 0L;
|
||||
})
|
||||
.anyMatch(member -> member == id);
|
||||
}
|
||||
|
||||
public boolean isCreator(long id) {
|
||||
return id == creatorId();
|
||||
}
|
||||
|
||||
public boolean isAdmin(long 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);
|
||||
|
||||
// Extract all replies from extension instances methods, returning ReplyCollection
|
||||
Stream<Reply> extensionCollectionReplies = extensions.stream()
|
||||
.flatMap(extension -> stream(extension.getClass().getMethods())
|
||||
.filter(checkReturnType(ReplyCollection.class))
|
||||
.map(returnReplyCollection(extension))
|
||||
.flatMap(ReplyCollection::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.of(abilityReplies, extensionReplies, extensionCollectionReplies)
|
||||
.flatMap(replyStream -> replyStream)
|
||||
.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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes the method and retrieves its return {@link ReplyCollection}.
|
||||
*
|
||||
* @param obj a bot or extension that this method is invoked with
|
||||
* @return a {@link Function} which returns the {@link ReplyCollection} returned by the given method
|
||||
*/
|
||||
private static Function<? super Method, ReplyCollection> returnReplyCollection(Object obj) {
|
||||
return method -> {
|
||||
try {
|
||||
return (ReplyCollection) method.invoke(obj);
|
||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||
log.error("Could not add Reply Collection", 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;
|
||||
}
|
||||
|
||||
long 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;
|
||||
long 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(this, 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) {
|
||||
String msg = format("Reply [%s] failed to check for conditions. " +
|
||||
"Make sure you're safeguarding against all possible updates.", name);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.error(msg, ex);
|
||||
} else {
|
||||
log.error(msg);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
@ -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())
|
||||
.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((bot, 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());
|
||||
long 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<Long> 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());
|
||||
Long userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Long> 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());
|
||||
Long userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Long> 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());
|
||||
Long userId = getUserIdSendError(username, ctx);
|
||||
|
||||
Set<Long> 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<Long> admins = bot.admins();
|
||||
long 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) {
|
||||
Long 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(long 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 long 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()));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
*/
|
||||
|
@ -7,21 +7,20 @@ 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}.
|
||||
@ -29,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;
|
||||
@ -71,7 +70,6 @@ public class MapDBContext implements DBContext {
|
||||
.fileDB(name)
|
||||
.fileMmapEnableIfSupported()
|
||||
.closeOnJvmShutdown()
|
||||
.cleanerHackEnable()
|
||||
.transactionEnable()
|
||||
.fileDeleteAfterClose()
|
||||
.make();
|
||||
@ -123,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;
|
||||
@ -169,7 +167,7 @@ public class MapDBContext implements DBContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
public void close() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
@ -184,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();
|
||||
@ -223,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...";
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,14 @@ 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.abilitybots.api.bot.BaseAbilityBot;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@ -16,7 +19,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 +37,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 +66,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 +99,10 @@ public final class Ability {
|
||||
return argNum;
|
||||
}
|
||||
|
||||
public boolean statsEnabled() {
|
||||
return statsEnabled;
|
||||
}
|
||||
|
||||
public Consumer<MessageContext> action() {
|
||||
return action;
|
||||
}
|
||||
@ -146,17 +154,19 @@ public final class Ability {
|
||||
private Privacy privacy;
|
||||
private Locality locality;
|
||||
private int argNum;
|
||||
private Consumer<MessageContext> consumer;
|
||||
private Consumer<MessageContext> postConsumer;
|
||||
private boolean statsEnabled;
|
||||
private Consumer<MessageContext> action;
|
||||
private Consumer<MessageContext> postAction;
|
||||
private List<Reply> replies;
|
||||
private Predicate<Update>[] flags;
|
||||
|
||||
private AbilityBuilder() {
|
||||
statsEnabled = false;
|
||||
replies = newArrayList();
|
||||
}
|
||||
|
||||
public AbilityBuilder action(Consumer<MessageContext> consumer) {
|
||||
this.consumer = consumer;
|
||||
this.action = consumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -185,24 +195,52 @@ public final class Ability {
|
||||
return this;
|
||||
}
|
||||
|
||||
public AbilityBuilder enableStats() {
|
||||
statsEnabled = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public AbilityBuilder setStatsEnabled(boolean statsEnabled) {
|
||||
this.statsEnabled = statsEnabled;
|
||||
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;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public final AbilityBuilder reply(Consumer<Update> action, Predicate<Update>... conditions) {
|
||||
public final AbilityBuilder reply(BiConsumer<BaseAbilityBot, Update> action, Predicate<Update>... conditions) {
|
||||
replies.add(Reply.of(action, conditions));
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
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.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.Objects.nonNull;
|
||||
@ -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(Predicate[])} 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(BiConsumer, Predicate[])}.
|
||||
*
|
||||
* @author Abbas Abou Daya
|
||||
*/
|
||||
@ -25,14 +25,22 @@ 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),
|
||||
MY_CHAT_MEMBER(Update::hasMyChatMember),
|
||||
CHAT_MEMBER(Update::hasChatMember),
|
||||
CHAT_JOIN_REQUEST(Update::hasChatJoinRequest),
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
|
@ -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.api.objects.User;
|
||||
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;
|
||||
|
||||
@ -19,16 +20,18 @@ public class MessageContext {
|
||||
private final Long chatId;
|
||||
private final String[] arguments;
|
||||
private final Update update;
|
||||
private final BaseAbilityBot bot;
|
||||
|
||||
private MessageContext(Update update, User user, Long chatId, String[] arguments) {
|
||||
private MessageContext(Update update, User user, Long chatId, BaseAbilityBot bot, String[] arguments) {
|
||||
this.user = user;
|
||||
this.chatId = chatId;
|
||||
this.update = update;
|
||||
this.bot = bot;
|
||||
this.arguments = arguments;
|
||||
}
|
||||
|
||||
public static MessageContext newContext(Update update, User user, Long chatId, String... arguments) {
|
||||
return new MessageContext(update, user, chatId, arguments);
|
||||
public static MessageContext newContext(Update update, User user, Long chatId, BaseAbilityBot bot, String... arguments) {
|
||||
return new MessageContext(update, user, chatId, bot, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,6 +48,13 @@ public class MessageContext {
|
||||
return chatId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the bot in which this message is executed
|
||||
*/
|
||||
public BaseAbilityBot bot() {
|
||||
return bot;
|
||||
}
|
||||
|
||||
/**
|
||||
* If there's no message in the update, then this will an empty array.
|
||||
*
|
||||
|
@ -1,35 +1,54 @@
|
||||
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.abilitybots.api.bot.BaseAbilityBot;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
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(BaseAbilityBot, Update)}.
|
||||
*
|
||||
* @author Abbas Abou Daya
|
||||
*/
|
||||
public final class Reply {
|
||||
public class Reply {
|
||||
public final List<Predicate<Update>> conditions;
|
||||
public final Consumer<Update> action;
|
||||
public final BiConsumer<BaseAbilityBot, 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, BiConsumer<BaseAbilityBot, Update> action) {
|
||||
this.conditions = ImmutableList.<Predicate<Update>>builder()
|
||||
.addAll(conditions)
|
||||
.build();
|
||||
this.action = action;
|
||||
statsEnabled = false;
|
||||
}
|
||||
|
||||
Reply(List<Predicate<Update>> conditions, BiConsumer<BaseAbilityBot, Update> action, String name) {
|
||||
this(conditions, action);
|
||||
if (Objects.nonNull(name)) {
|
||||
enableStats(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static Reply of(BiConsumer<BaseAbilityBot, 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);
|
||||
public static Reply of(BiConsumer<BaseAbilityBot, Update> action, Predicate<Update>... conditions) {
|
||||
return Reply.of(action, newArrayList(conditions));
|
||||
}
|
||||
|
||||
public boolean isOkFor(Update update) {
|
||||
@ -38,8 +57,34 @@ public final class Reply {
|
||||
return conditions.stream().reduce(true, stateAnd, Boolean::logicalAnd);
|
||||
}
|
||||
|
||||
public void actOn(Update update) {
|
||||
action.accept(update);
|
||||
public void actOn(BaseAbilityBot bot, Update update) {
|
||||
action.accept(bot, update);
|
||||
}
|
||||
|
||||
public List<Predicate<Update>> conditions() {
|
||||
return conditions;
|
||||
}
|
||||
|
||||
public BiConsumer<BaseAbilityBot, 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
|
||||
|
@ -0,0 +1,32 @@
|
||||
package org.telegram.abilitybots.api.objects;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.google.common.collect.Lists.newArrayList;
|
||||
|
||||
/**
|
||||
* A wrapping object containing Replies. Return this in your bot class to get replies registered.
|
||||
*
|
||||
* @see Reply
|
||||
*/
|
||||
public class ReplyCollection {
|
||||
|
||||
public final Collection<Reply> replies;
|
||||
|
||||
public ReplyCollection(Collection<Reply> replies) {
|
||||
this.replies = replies;
|
||||
}
|
||||
|
||||
public Collection<Reply> getReplies() {
|
||||
return replies;
|
||||
}
|
||||
|
||||
public Stream<Reply> stream(){
|
||||
return replies.stream();
|
||||
}
|
||||
|
||||
public static ReplyCollection of(Reply... replies){
|
||||
return new ReplyCollection(newArrayList(replies));
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package org.telegram.abilitybots.api.objects;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.telegram.abilitybots.api.bot.BaseAbilityBot;
|
||||
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.BiConsumer;
|
||||
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, BiConsumer<BaseAbilityBot, 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 BiConsumer<BaseAbilityBot, 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(BiConsumer<BaseAbilityBot, 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());
|
||||
BiConsumer<BaseAbilityBot, Update> statefulAction = nextReply.action().andThen((unused, 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 = (bot, upd) -> {};
|
||||
|
||||
BiConsumer<BaseAbilityBot, Update> statefulAction;
|
||||
if (nextReplies.size() > 0) {
|
||||
statefulAction = action.andThen((bot, upd) -> {
|
||||
Long chatId = AbilityUtils.getChatId(upd);
|
||||
db.<Long, Integer>getMap(STATES).put(chatId, id);
|
||||
});
|
||||
} else {
|
||||
statefulAction = action.andThen((bot, 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
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 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();
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
package org.telegram.abilitybots.api.sender;
|
||||
|
||||
import org.telegram.telegrambots.api.methods.BotApiMethod;
|
||||
import org.telegram.telegrambots.api.methods.groupadministration.SetChatPhoto;
|
||||
import org.telegram.telegrambots.api.methods.send.*;
|
||||
import org.telegram.telegrambots.api.methods.stickers.AddStickerToSet;
|
||||
import org.telegram.telegrambots.api.methods.stickers.CreateNewStickerSet;
|
||||
import org.telegram.telegrambots.api.methods.stickers.UploadStickerFile;
|
||||
import org.telegram.telegrambots.api.objects.File;
|
||||
import org.telegram.telegrambots.api.objects.Message;
|
||||
import org.telegram.telegrambots.api.objects.User;
|
||||
import org.telegram.telegrambots.api.objects.WebhookInfo;
|
||||
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;
|
||||
|
||||
@ -43,22 +43,22 @@ public class DefaultSender implements MessageSender {
|
||||
|
||||
@Override
|
||||
public Boolean addStickerToSet(AddStickerToSet addStickerToSet) throws TelegramApiException {
|
||||
return bot.addStickerToSet(addStickerToSet);
|
||||
return bot.execute(addStickerToSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean createNewStickerSet(CreateNewStickerSet createNewStickerSet) throws TelegramApiException {
|
||||
return bot.createNewStickerSet(createNewStickerSet);
|
||||
return bot.execute(createNewStickerSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File uploadStickerFile(UploadStickerFile uploadStickerFile) throws TelegramApiException {
|
||||
return bot.uploadStickerFile(uploadStickerFile);
|
||||
return bot.execute(uploadStickerFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean setChatPhoto(SetChatPhoto setChatPhoto) throws TelegramApiException {
|
||||
return bot.setChatPhoto(setChatPhoto);
|
||||
return bot.execute(setChatPhoto);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -104,36 +104,36 @@ public class DefaultSender implements MessageSender {
|
||||
|
||||
@Override
|
||||
public Message sendDocument(SendDocument sendDocument) throws TelegramApiException {
|
||||
return bot.sendDocument(sendDocument);
|
||||
return bot.execute(sendDocument);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendPhoto(SendPhoto sendPhoto) throws TelegramApiException {
|
||||
return bot.sendPhoto(sendPhoto);
|
||||
return bot.execute(sendPhoto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendVideo(SendVideo sendVideo) throws TelegramApiException {
|
||||
return bot.sendVideo(sendVideo);
|
||||
return bot.execute(sendVideo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendSticker(SendSticker sendSticker) throws TelegramApiException {
|
||||
return bot.sendSticker(sendSticker);
|
||||
return bot.execute(sendSticker);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendAudio(SendAudio sendAudio) throws TelegramApiException {
|
||||
return bot.sendAudio(sendAudio);
|
||||
return bot.execute(sendAudio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendVoice(SendVoice sendVoice) throws TelegramApiException {
|
||||
return bot.sendVoice(sendVoice);
|
||||
return bot.execute(sendVoice);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message sendVideoNote(SendVideoNote sendVideoNote) throws TelegramApiException {
|
||||
public Message sendVideoNote(SendVideoNote sendVideoNote) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
package org.telegram.abilitybots.api.sender;
|
||||
|
||||
import org.telegram.telegrambots.api.methods.BotApiMethod;
|
||||
import org.telegram.telegrambots.api.methods.groupadministration.SetChatPhoto;
|
||||
import org.telegram.telegrambots.api.methods.send.*;
|
||||
import org.telegram.telegrambots.api.methods.stickers.AddStickerToSet;
|
||||
import org.telegram.telegrambots.api.methods.stickers.CreateNewStickerSet;
|
||||
import org.telegram.telegrambots.api.methods.stickers.UploadStickerFile;
|
||||
import org.telegram.telegrambots.api.objects.File;
|
||||
import org.telegram.telegrambots.api.objects.Message;
|
||||
import org.telegram.telegrambots.api.objects.User;
|
||||
import org.telegram.telegrambots.api.objects.WebhookInfo;
|
||||
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;
|
||||
|
||||
@ -30,9 +30,9 @@ public interface MessageSender {
|
||||
|
||||
Boolean addStickerToSet(AddStickerToSet addStickerToSet) throws TelegramApiException;
|
||||
|
||||
public Boolean createNewStickerSet(CreateNewStickerSet createNewStickerSet) throws TelegramApiException;
|
||||
Boolean createNewStickerSet(CreateNewStickerSet createNewStickerSet) throws TelegramApiException;
|
||||
|
||||
public File uploadStickerFile(UploadStickerFile uploadStickerFile) throws TelegramApiException;
|
||||
File uploadStickerFile(UploadStickerFile uploadStickerFile) throws TelegramApiException;
|
||||
|
||||
Boolean setChatPhoto(SetChatPhoto setChatPhoto) throws TelegramApiException;
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
package org.telegram.abilitybots.api.sender;
|
||||
|
||||
import org.telegram.telegrambots.api.methods.BotApiMethod;
|
||||
import org.telegram.telegrambots.api.methods.send.SendMessage;
|
||||
import org.telegram.telegrambots.api.objects.Message;
|
||||
import org.telegram.telegrambots.api.objects.replykeyboard.ForceReplyKeyboard;
|
||||
import org.telegram.telegrambots.exceptions.TelegramApiException;
|
||||
import org.telegram.telegrambots.logging.BotLogger;
|
||||
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;
|
||||
@ -17,7 +19,7 @@ import java.util.Optional;
|
||||
* @author Abbas Abou Daya
|
||||
*/
|
||||
public class SilentSender {
|
||||
private static final String TAG = SilentSender.class.getSimpleName();
|
||||
private static final Logger log = LoggerFactory.getLogger(SilentSender.class);
|
||||
|
||||
private final MessageSender sender;
|
||||
|
||||
@ -37,7 +39,10 @@ public class SilentSender {
|
||||
SendMessage msg = new SendMessage();
|
||||
msg.setText(message);
|
||||
msg.setChatId(id);
|
||||
msg.setReplyMarkup(new ForceReplyKeyboard());
|
||||
ForceReplyKeyboard kb = new ForceReplyKeyboard();
|
||||
kb.setForceReply(true);
|
||||
kb.setSelective(true);
|
||||
msg.setReplyMarkup(kb);
|
||||
|
||||
return execute(msg);
|
||||
}
|
||||
@ -46,17 +51,17 @@ public class SilentSender {
|
||||
try {
|
||||
return Optional.ofNullable(sender.execute(method));
|
||||
} catch (TelegramApiException e) {
|
||||
BotLogger.error("Could not execute bot API method", TAG, e);
|
||||
log.error("Could not execute bot API method", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public <T extends Serializable, Method extends BotApiMethod<T>> Optional<T> executeAsync(Method method) {
|
||||
public <T extends Serializable, Method extends BotApiMethod<T>, Callback extends SentCallback<T>> void
|
||||
executeAsync(Method method, Callback callable) {
|
||||
try {
|
||||
return Optional.ofNullable(sender.execute(method));
|
||||
sender.executeAsync(method, callable);
|
||||
} catch (TelegramApiException e) {
|
||||
BotLogger.error("Could not execute bot API method", TAG, e);
|
||||
return Optional.empty();
|
||||
log.error("Could not execute bot API method", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
package org.telegram.abilitybots.api.toggle;
|
||||
|
||||
import org.telegram.abilitybots.api.objects.Ability;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* This custom toggle can be used to customize default abilities supplied by the library. Users can call {@link CustomToggle#toggle} to
|
||||
* rename the default abilities 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param properties the abilities toggle definition
|
||||
* @return the toggle instance
|
||||
*/
|
||||
public CustomToggle config(Properties properties) {
|
||||
for (String key : properties.stringPropertyNames()) {
|
||||
String value = properties.getProperty(key);
|
||||
key = key.toLowerCase();
|
||||
|
||||
// compare with legal configuration names
|
||||
for (Property p: Property.values()) {
|
||||
if (key.equals(p.key())) {
|
||||
String ability = key.split("\\.")[1];
|
||||
if (key.contains("enabled") && value.equalsIgnoreCase("false")) {
|
||||
this.turnOff(ability);
|
||||
}else if (key.contains("toggle")) {
|
||||
this.toggle(ability, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of all the properties recognized by {@link CustomToggle}.
|
||||
* Can be used to programmatically get, set or remove default values.
|
||||
*/
|
||||
public enum Property{
|
||||
CLAIM_ENABLED("ability.claim.enabled"),
|
||||
CLAIM_TOGGLE("ability.claim.toggle"),
|
||||
|
||||
BAN_ENABLED("ability.ban.enabled"),
|
||||
BAN_TOGGLE("ability.ban.toggle"),
|
||||
|
||||
PROMOTE_ENABLED("ability.promote.enabled"),
|
||||
PROMOTE_TOGGLE("ability.promote.toggle"),
|
||||
|
||||
DEMOTE_ENABLED("ability.demote.enabled"),
|
||||
DEMOTE_TOGGLE("ability.demote.toggle"),
|
||||
|
||||
UNBAN_ENABLED("ability.unban.enabled"),
|
||||
UNBAN_TOGGLE("ability.unban.toggle"),
|
||||
|
||||
BACKUP_ENABLED("ability.backup.enabled"),
|
||||
BACKUP_TOGGLE("ability.backup.toggle"),
|
||||
|
||||
RECOVER_ENABLED("ability.recover.enabled"),
|
||||
RECOVER_TOGGLE("ability.recover.toggle"),
|
||||
|
||||
COMMANDS_ENABLED("ability.commands.enabled"),
|
||||
COMMANDS_TOGGLE("ability.commands.toggle"),
|
||||
|
||||
REPORT_ENABLED("ability.report.enabled"),
|
||||
REPORT_TOGGLE("ability.report.toggle"),
|
||||
|
||||
STATS_ENABLED("ability.stats.enabled"),
|
||||
STATS_TOGGLE("ability.stats.toggle")
|
||||
;
|
||||
|
||||
private final String key;
|
||||
|
||||
Property (final String key){
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String key() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -3,8 +3,8 @@ 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;
|
||||
@ -13,10 +13,12 @@ import java.util.ResourceBundle;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import org.telegram.telegrambots.meta.api.objects.payments.PreCheckoutQuery;
|
||||
|
||||
import static java.util.ResourceBundle.Control.FORMAT_PROPERTIES;
|
||||
import static java.util.ResourceBundle.Control.getNoFallbackControl;
|
||||
import static java.util.ResourceBundle.getBundle;
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
import static org.apache.commons.lang3.StringUtils.isEmpty;
|
||||
import static org.telegram.abilitybots.api.objects.Flag.*;
|
||||
|
||||
@ -24,6 +26,8 @@ import static org.telegram.abilitybots.api.objects.Flag.*;
|
||||
* Helper and utility methods
|
||||
*/
|
||||
public final class AbilityUtils {
|
||||
public static User EMPTY_USER = new User(0L, "", false);
|
||||
|
||||
private AbilityUtils() {
|
||||
|
||||
}
|
||||
@ -34,7 +38,7 @@ public final class AbilityUtils {
|
||||
*/
|
||||
public static String stripTag(String username) {
|
||||
String lowerCase = username.toLowerCase();
|
||||
return lowerCase.startsWith("@") ? lowerCase.substring(1, lowerCase.length()) : lowerCase;
|
||||
return lowerCase.startsWith("@") ? lowerCase.substring(1) : lowerCase;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,6 +59,10 @@ public final class AbilityUtils {
|
||||
* @throws IllegalStateException if the user could not be found
|
||||
*/
|
||||
public static User getUser(Update update) {
|
||||
return defaultIfNull(getUserElseThrow(update), EMPTY_USER);
|
||||
}
|
||||
|
||||
private static User getUserElseThrow(Update update) {
|
||||
if (MESSAGE.test(update)) {
|
||||
return update.getMessage().getFrom();
|
||||
} else if (CALLBACK_QUERY.test(update)) {
|
||||
@ -69,6 +77,20 @@ 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 (MY_CHAT_MEMBER.test(update)) {
|
||||
return update.getMyChatMember().getFrom();
|
||||
} else if (CHAT_MEMBER.test(update)) {
|
||||
return update.getChatMember().getFrom();
|
||||
} else if (CHAT_JOIN_REQUEST.test(update)) {
|
||||
return update.getChatJoinRequest().getUser();
|
||||
} else if (POLL.test(update)) {
|
||||
return EMPTY_USER;
|
||||
} else {
|
||||
throw new IllegalStateException("Could not retrieve originating user from update");
|
||||
}
|
||||
@ -131,7 +153,7 @@ public final class AbilityUtils {
|
||||
} else if (CALLBACK_QUERY.test(update)) {
|
||||
return update.getCallbackQuery().getMessage().getChatId();
|
||||
} else if (INLINE_QUERY.test(update)) {
|
||||
return (long) update.getInlineQuery().getFrom().getId();
|
||||
return update.getInlineQuery().getFrom().getId();
|
||||
} else if (CHANNEL_POST.test(update)) {
|
||||
return update.getChannelPost().getChatId();
|
||||
} else if (EDITED_CHANNEL_POST.test(update)) {
|
||||
@ -139,7 +161,21 @@ public final class AbilityUtils {
|
||||
} else if (EDITED_MESSAGE.test(update)) {
|
||||
return update.getEditedMessage().getChatId();
|
||||
} else if (CHOSEN_INLINE_QUERY.test(update)) {
|
||||
return (long) update.getChosenInlineQuery().getFrom().getId();
|
||||
return update.getChosenInlineQuery().getFrom().getId();
|
||||
} else if (SHIPPING_QUERY.test(update)) {
|
||||
return update.getShippingQuery().getFrom().getId();
|
||||
} else if (PRECHECKOUT_QUERY.test(update)) {
|
||||
return update.getPreCheckoutQuery().getFrom().getId();
|
||||
} else if (POLL_ANSWER.test(update)) {
|
||||
return update.getPollAnswer().getUser().getId();
|
||||
} else if (POLL.test(update)) {
|
||||
return EMPTY_USER.getId();
|
||||
} else if (MY_CHAT_MEMBER.test(update)) {
|
||||
return update.getMyChatMember().getChat().getId();
|
||||
} else if (CHAT_MEMBER.test(update)) {
|
||||
return update.getChatMember().getChat().getId();
|
||||
} else if (CHAT_JOIN_REQUEST.test(update)) {
|
||||
return update.getChatJoinRequest().getChat().getId();
|
||||
} else {
|
||||
throw new IllegalStateException("Could not retrieve originating chat ID from update");
|
||||
}
|
||||
@ -160,10 +196,8 @@ public final class AbilityUtils {
|
||||
return update.getEditedChannelPost().isUserMessage();
|
||||
} else if (EDITED_MESSAGE.test(update)) {
|
||||
return update.getEditedMessage().isUserMessage();
|
||||
} else if (CHOSEN_INLINE_QUERY.test(update) || INLINE_QUERY.test(update)) {
|
||||
return true;
|
||||
} else {
|
||||
throw new IllegalStateException("Could not retrieve update context origin (user/group)");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,16 +217,16 @@ public final class AbilityUtils {
|
||||
return update -> update.getMessage().getReplyToMessage().getText().equals(msg);
|
||||
}
|
||||
|
||||
public static String getLocalizedMessage(String messageCode, Locale locale, Object...arguments) {
|
||||
public static String getLocalizedMessage(String messageCode, Locale locale, Object... arguments) {
|
||||
ResourceBundle bundle;
|
||||
if (locale == null) {
|
||||
bundle = getBundle("messages", Locale.ROOT);
|
||||
} else {
|
||||
try {
|
||||
bundle = getBundle(
|
||||
"messages",
|
||||
locale,
|
||||
getNoFallbackControl(FORMAT_PROPERTIES));
|
||||
"messages",
|
||||
locale,
|
||||
getNoFallbackControl(FORMAT_PROPERTIES));
|
||||
} catch (MissingResourceException e) {
|
||||
bundle = getBundle("messages", Locale.ROOT);
|
||||
}
|
||||
@ -201,7 +235,7 @@ public final class AbilityUtils {
|
||||
return MessageFormat.format(message, arguments);
|
||||
}
|
||||
|
||||
public static String getLocalizedMessage(String messageCode, String languageCode, Object...arguments){
|
||||
public static String getLocalizedMessage(String messageCode, String languageCode, Object... arguments) {
|
||||
Locale locale = Strings.isNullOrEmpty(languageCode) ? null : Locale.forLanguageTag(languageCode);
|
||||
return getLocalizedMessage(messageCode, locale, arguments);
|
||||
}
|
||||
@ -231,8 +265,8 @@ public final class AbilityUtils {
|
||||
* The full name is identified as the concatenation of the first and last name, separated by a space.
|
||||
* This method can return an empty name if both first and last name are empty.
|
||||
*
|
||||
* @param user User to use
|
||||
* @return the full name of the user
|
||||
* @param user
|
||||
*/
|
||||
public static String fullName(User user) {
|
||||
StringJoiner name = new StringJoiner(" ");
|
||||
@ -244,4 +278,33 @@ public final class AbilityUtils {
|
||||
|
||||
return name.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static String escape(String username) {
|
||||
return username.replace("_", "\\_");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed string is a valid bot command according to the requirements of Telegram Bot API:
|
||||
* "A command must always start with the '/' symbol and may not be longer than 32 characters.
|
||||
* Commands can use latin letters, numbers and underscores."
|
||||
* (https://core.telegram.org/bots#commands)
|
||||
*
|
||||
* @param command String representation of a command to be checked for validity
|
||||
* @return whether the command is valid
|
||||
*/
|
||||
public static boolean isValidCommand(String command){
|
||||
if (command == null || command.length() > 32) return false;
|
||||
return command.matches("/[A-Za-z_0-9]+");
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the passed String is a valid command name. Command name is text of a command without leading '/'
|
||||
*
|
||||
* @param commandName the command name to be checked for validity
|
||||
* @return whether the command name is valid
|
||||
*/
|
||||
public static boolean isValidCommandName(String commandName){
|
||||
if (commandName == null || commandName.length() > 31) return false;
|
||||
return commandName.matches("[A-Za-z_0-9]+");
|
||||
}
|
||||
}
|
||||
|
@ -1,80 +1,81 @@
|
||||
package org.telegram.abilitybots.api.bot;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.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.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.AbilityBotTest.mockContext;
|
||||
import static org.telegram.abilitybots.api.bot.TestUtils.mockContext;
|
||||
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
|
||||
|
||||
public class AbilityBotI18nTest {
|
||||
private static final User NO_LANGUAGE_USER = new User(1, "first", false, "last", "username", null);
|
||||
private static final User ITALIAN_USER = new User(2, "first", false, "last", "username", "it-IT");
|
||||
import java.io.IOException;
|
||||
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;
|
||||
|
||||
class AbilityBotI18nTest {
|
||||
private static final User NO_LANGUAGE_USER = new User(1L, "first", false, "last", "username", null, false, false, false, false, false, false, false, false, false, false, false);
|
||||
private static final User ITALIAN_USER = new User(2L, "first", false, "last", "username", "it-IT", false, false, false, false, false, false, false, false, false, false, false);
|
||||
|
||||
private DBContext db;
|
||||
private NoPublicCommandsBot bot;
|
||||
private DefaultAbilities defaultAbs;
|
||||
|
||||
private MessageSender sender;
|
||||
private SilentSender silent;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@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
|
||||
public void missingPublicCommandsLocalizedInEnglishByDefault() {
|
||||
void missingPublicCommandsLocalizedInEnglishByDefault() {
|
||||
MessageContext context = mockContext(NO_LANGUAGE_USER);
|
||||
|
||||
bot.reportCommands().action().accept(context);
|
||||
defaultAbs.reportCommands().action().accept(context);
|
||||
|
||||
verify(silent, times(1))
|
||||
.send("No available commands found.", NO_LANGUAGE_USER.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPublicCommandsLocalizedInItalian() {
|
||||
void missingPublicCommandsLocalizedInItalian() {
|
||||
MessageContext context = mockContext(ITALIAN_USER);
|
||||
|
||||
bot.reportCommands().action().accept(context);
|
||||
defaultAbs.reportCommands().action().accept(context);
|
||||
|
||||
verify(silent, times(1))
|
||||
.send("Non sono presenti comandi disponibile.", ITALIAN_USER.getId());
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException {
|
||||
db.clear();
|
||||
db.close();
|
||||
}
|
||||
|
||||
public static class NoPublicCommandsBot extends AbilityBot {
|
||||
|
||||
protected NoPublicCommandsBot(String botToken, String botUsername, DBContext db) {
|
||||
NoPublicCommandsBot(String botToken, String botUsername, DBContext db) {
|
||||
super(botToken, botUsername, db);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int creatorId() {
|
||||
public long creatorId() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,5 @@
|
||||
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.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.Pair;
|
||||
import org.telegram.abilitybots.api.util.Trio;
|
||||
import org.telegram.telegrambots.api.methods.groupadministration.GetChatAdministrators;
|
||||
import org.telegram.telegrambots.api.objects.*;
|
||||
import org.telegram.telegrambots.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 static com.google.common.collect.Lists.newArrayList;
|
||||
import static com.google.common.collect.Sets.newHashSet;
|
||||
import static java.lang.String.format;
|
||||
@ -32,18 +8,70 @@ 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.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyLong;
|
||||
import static org.mockito.Mockito.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||
import static org.telegram.abilitybots.api.bot.DefaultBot.FIRST_REPLY_KEY_MESSAGE;
|
||||
import static org.telegram.abilitybots.api.bot.DefaultBot.SECOND_REPLY_KEY_MESSAGE;
|
||||
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.USER;
|
||||
import static org.telegram.abilitybots.api.bot.TestUtils.mockContext;
|
||||
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.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.*;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.GROUP_ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.io.Files;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
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.Ability;
|
||||
import org.telegram.abilitybots.api.objects.Flag;
|
||||
import org.telegram.abilitybots.api.objects.Locality;
|
||||
import org.telegram.abilitybots.api.objects.MessageContext;
|
||||
import org.telegram.abilitybots.api.objects.Privacy;
|
||||
import org.telegram.abilitybots.api.sender.MessageSender;
|
||||
import org.telegram.abilitybots.api.sender.SilentSender;
|
||||
import org.telegram.abilitybots.api.util.AbilityUtils;
|
||||
import org.telegram.abilitybots.api.util.Pair;
|
||||
import org.telegram.abilitybots.api.util.Trio;
|
||||
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators;
|
||||
import org.telegram.telegrambots.meta.api.objects.Document;
|
||||
import org.telegram.telegrambots.meta.api.objects.File;
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
import org.telegram.telegrambots.meta.api.objects.User;
|
||||
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMember;
|
||||
import org.telegram.telegrambots.meta.api.objects.chatmember.ChatMemberAdministrator;
|
||||
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
|
||||
|
||||
public class AbilityBotTest {
|
||||
// Messages
|
||||
@ -54,18 +82,19 @@ public class AbilityBotTest {
|
||||
private static final long GROUP_ID = 10L;
|
||||
private static final String TEST = "test";
|
||||
private static final String[] TEXT = {TEST};
|
||||
public static final User USER = new User(1, "first", false, "last", "username", null);
|
||||
public static final User CREATOR = new User(1337, "creatorFirst", false, "creatorLast", "creatorUsername", null);
|
||||
|
||||
private DefaultBot bot;
|
||||
private DefaultAbilities defaultAbs;
|
||||
private DBContext db;
|
||||
private MessageSender sender;
|
||||
private SilentSender silent;
|
||||
|
||||
@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);
|
||||
silent = mock(SilentSender.class);
|
||||
@ -74,9 +103,15 @@ public class AbilityBotTest {
|
||||
bot.silent = silent;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
db.clear();
|
||||
db.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsPrivacyViolation() {
|
||||
Update update = mockFullUpdate(USER, "/admin");
|
||||
void sendsPrivacyViolation() {
|
||||
Update update = mockFullUpdate(bot, USER, "/admin");
|
||||
|
||||
bot.onUpdateReceived(update);
|
||||
|
||||
@ -84,8 +119,8 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsLocalityViolation() {
|
||||
Update update = mockFullUpdate(USER, "/group");
|
||||
void sendsLocalityViolation() {
|
||||
Update update = mockFullUpdate(bot, USER, "/group");
|
||||
|
||||
bot.onUpdateReceived(update);
|
||||
|
||||
@ -94,8 +129,8 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sendsInputArgsViolation() {
|
||||
Update update = mockFullUpdate(USER, "/count 1 2 3");
|
||||
void sendsInputArgsViolation() {
|
||||
Update update = mockFullUpdate(bot, USER, "/count 1 2 3");
|
||||
|
||||
bot.onUpdateReceived(update);
|
||||
|
||||
@ -103,8 +138,8 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canProcessRepliesIfSatisfyRequirements() {
|
||||
Update update = mockFullUpdate(USER, "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));
|
||||
@ -112,31 +147,100 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@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(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);
|
||||
|
||||
defaultAbs.recoverDB().replies().get(0).actOn(bot, update);
|
||||
|
||||
verify(silent, 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());
|
||||
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);
|
||||
|
||||
@ -144,56 +248,56 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDemote() {
|
||||
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);
|
||||
Set<Long> actual = bot.admins();
|
||||
Set<Long> expected = emptySet();
|
||||
assertEquals(expected, actual, "Could not sudont super-admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canPromote() {
|
||||
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(USER.getId());
|
||||
assertEquals("Could not sudo user", expected, actual);
|
||||
Set<Long> actual = bot.admins();
|
||||
Set<Long> expected = newHashSet(USER.getId());
|
||||
assertEquals(expected, actual, "Could not sudo user");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBanUser() {
|
||||
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(USER.getId());
|
||||
assertEquals("The ban was not emplaced", expected, actual);
|
||||
Set<Long> actual = bot.blacklist();
|
||||
Set<Long> expected = newHashSet(USER.getId());
|
||||
assertEquals(expected, actual, "The ban was not emplaced");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canUnbanUser() {
|
||||
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);
|
||||
Set<Long> actual = bot.blacklist();
|
||||
Set<Long> expected = newHashSet();
|
||||
assertEquals(expected, actual, "The ban was not lifted");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@ -202,15 +306,15 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotBanCreator() {
|
||||
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(USER.getId());
|
||||
assertEquals("Impostor was not added to the blacklist", expected, actual);
|
||||
Set<Long> actual = bot.blacklist();
|
||||
Set<Long> expected = newHashSet(USER.getId());
|
||||
assertEquals(expected, actual, "Impostor was not added to the blacklist");
|
||||
}
|
||||
|
||||
private void addUsers(User... users) {
|
||||
@ -221,18 +325,18 @@ public class AbilityBotTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void creatorCanClaimBot() {
|
||||
void creatorCanClaimBot() {
|
||||
MessageContext context = mockContext(CREATOR, GROUP_ID);
|
||||
|
||||
bot.claimCreator().action().accept(context);
|
||||
defaultAbs.claimCreator().action().accept(context);
|
||||
|
||||
Set<Integer> actual = bot.admins();
|
||||
Set<Integer> expected = newHashSet(CREATOR.getId());
|
||||
assertEquals("Creator was not properly added to the super admins set", expected, actual);
|
||||
Set<Long> actual = bot.admins();
|
||||
Set<Long> expected = newHashSet(CREATOR.getId());
|
||||
assertEquals(expected, actual, "Creator was not properly added to the super admins set");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bannedCreatorPassesBlacklistCheck() {
|
||||
void bannedCreatorPassesBlacklistCheck() {
|
||||
bot.blacklist().add(CREATOR.getId());
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
@ -241,11 +345,11 @@ 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);
|
||||
|
||||
@ -253,14 +357,14 @@ public class AbilityBotTest {
|
||||
|
||||
bot.addUser(update);
|
||||
|
||||
Map<String, Integer> expectedUserIds = ImmutableMap.of(USER.getUserName(), USER.getId());
|
||||
Map<Integer, User> expectedUsers = ImmutableMap.of(USER.getId(), USER);
|
||||
assertEquals("User was not added", expectedUserIds, bot.userIds());
|
||||
assertEquals("User was not added", expectedUsers, bot.users());
|
||||
Map<String, Long> expectedUserIds = ImmutableMap.of(USER.getUserName(), USER.getId());
|
||||
Map<Long, 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() {
|
||||
void canEditUser() {
|
||||
addUsers(USER);
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
@ -268,32 +372,32 @@ public class AbilityBotTest {
|
||||
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, null);
|
||||
long sameId = USER.getId();
|
||||
User changedUser = new User(sameId, newFirstName, false, newLastName, newUsername, "en", false, false, false, false, false, false, false, false, false, false, false);
|
||||
|
||||
mockAlternateUser(update, message, changedUser);
|
||||
|
||||
bot.addUser(update);
|
||||
|
||||
Map<String, Integer> expectedUserIds = ImmutableMap.of(changedUser.getUserName(), changedUser.getId());
|
||||
Map<Integer, User> expectedUsers = ImmutableMap.of(changedUser.getId(), changedUser);
|
||||
assertEquals("User was not properly edited", bot.userIds(), expectedUserIds);
|
||||
assertEquals("User was not properly edited", expectedUsers, expectedUsers);
|
||||
Map<String, Long> expectedUserIds = ImmutableMap.of(changedUser.getUserName(), changedUser.getId());
|
||||
Map<Long, User> expectedUsers = ImmutableMap.of(changedUser.getId(), changedUser);
|
||||
assertEquals(bot.userIds(), expectedUserIds, "User was not properly edited");
|
||||
assertEquals(bot.users(), 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);
|
||||
|
||||
assertFalse("Bot can't validate ability properly", bot.validateAbility(invalidPair));
|
||||
assertTrue("Bot can't validate ability properly", 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(USER, "/something");
|
||||
void canCheckInput() {
|
||||
Update update = mockFullUpdate(bot, USER, "/something");
|
||||
Ability abilityWithOneInput = getDefaultBuilder()
|
||||
.build();
|
||||
Ability abilityWithZeroInput = getDefaultBuilder()
|
||||
@ -303,22 +407,22 @@ public class AbilityBotTest {
|
||||
Trio<Update, Ability, String[]> trioOneArg = Trio.of(update, abilityWithOneInput, TEXT);
|
||||
Trio<Update, Ability, String[]> trioZeroArg = Trio.of(update, abilityWithZeroInput, TEXT);
|
||||
|
||||
assertTrue("Unexpected result when applying token filter", bot.checkInput(trioOneArg));
|
||||
assertTrue(bot.checkInput(trioOneArg), "Unexpected result when applying token filter");
|
||||
|
||||
trioOneArg = Trio.of(update, abilityWithOneInput, addAll(TEXT, TEXT));
|
||||
assertFalse("Unexpected result when applying token filter", bot.checkInput(trioOneArg));
|
||||
assertFalse(bot.checkInput(trioOneArg), "Unexpected result when applying token filter");
|
||||
|
||||
assertTrue("Unexpected result when applying token filter", bot.checkInput(trioZeroArg));
|
||||
assertTrue(bot.checkInput(trioZeroArg), "Unexpected result when applying token filter");
|
||||
|
||||
trioZeroArg = Trio.of(update, abilityWithZeroInput, EMPTY_ARRAY);
|
||||
assertTrue("Unexpected result when applying token filter", 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();
|
||||
@ -331,17 +435,17 @@ public class AbilityBotTest {
|
||||
|
||||
mockUser(update, message, user);
|
||||
|
||||
assertTrue("Unexpected result when checking for privacy", bot.checkPrivacy(publicTrio));
|
||||
assertFalse("Unexpected result when checking for privacy", bot.checkPrivacy(groupAdminTrio));
|
||||
assertFalse("Unexpected result when checking for privacy", bot.checkPrivacy(adminTrio));
|
||||
assertFalse("Unexpected result when checking for privacy", 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 canValidateGroupAdminPrivacy() {
|
||||
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);
|
||||
@ -350,19 +454,19 @@ public class AbilityBotTest {
|
||||
when(message.isGroupMessage()).thenReturn(true);
|
||||
|
||||
ChatMember member = mock(ChatMember.class);
|
||||
when(member.getUser()).thenReturn(user);
|
||||
when(member.getStatus()).thenReturn(ChatMemberAdministrator.STATUS);
|
||||
when(member.getUser()).thenReturn(user);
|
||||
|
||||
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(Optional.of(newArrayList(member)));
|
||||
|
||||
assertTrue("Unexpected result when checking for privacy", bot.checkPrivacy(groupAdminTrio));
|
||||
assertTrue(bot.checkPrivacy(groupAdminTrio), "Unexpected result when checking for privacy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRestrictNormalUsersFromGroupAdminAbilities() {
|
||||
void canRestrictNormalUsersFromGroupAdminAbilities() {
|
||||
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);
|
||||
@ -372,14 +476,14 @@ public class AbilityBotTest {
|
||||
|
||||
when(silent.execute(any(GetChatAdministrators.class))).thenReturn(empty());
|
||||
|
||||
assertFalse("Unexpected result when checking for privacy", bot.checkPrivacy(groupAdminTrio));
|
||||
assertFalse(bot.checkPrivacy(groupAdminTrio), "Unexpected result when checking for privacy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canBlockAdminsFromCreatorAbilities() {
|
||||
void canBlockAdminsFromCreatorAbilities() {
|
||||
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 creatorAbility = getDefaultBuilder().privacy(Privacy.CREATOR).build();
|
||||
|
||||
Trio<Update, Ability, String[]> creatorTrio = Trio.of(update, creatorAbility, TEXT);
|
||||
@ -387,11 +491,11 @@ public class AbilityBotTest {
|
||||
bot.admins().add(USER.getId());
|
||||
mockUser(update, message, user);
|
||||
|
||||
assertFalse("Unexpected result when checking for privacy", 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);
|
||||
@ -406,13 +510,13 @@ public class AbilityBotTest {
|
||||
mockUser(update, message, user);
|
||||
when(message.isUserMessage()).thenReturn(true);
|
||||
|
||||
assertTrue("Unexpected result when checking for locality", bot.checkLocality(publicTrio));
|
||||
assertTrue("Unexpected result when checking for locality", bot.checkLocality(userTrio));
|
||||
assertFalse("Unexpected result when checking for locality", 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);
|
||||
Ability ability = getDefaultBuilder().build();
|
||||
@ -422,19 +526,20 @@ public class AbilityBotTest {
|
||||
mockUser(update, message, USER);
|
||||
|
||||
Pair<MessageContext, Ability> actualPair = bot.getContext(trio);
|
||||
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, USER, GROUP_ID, TEXT), ability);
|
||||
Pair<MessageContext, Ability> expectedPair = Pair.of(newContext(update, USER, GROUP_ID, bot, TEXT), ability);
|
||||
|
||||
assertEquals("Unexpected result when fetching for context", expectedPair, actualPair);
|
||||
assertEquals(expectedPair, actualPair, "Unexpected result when fetching for context");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultGlobalFlagIsTrue() {
|
||||
void defaultGlobalFlagIsTrue() {
|
||||
Update update = mock(Update.class);
|
||||
assertTrue("Unexpected result when checking for the default global flags", 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;
|
||||
@ -443,11 +548,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);
|
||||
|
||||
@ -462,11 +567,11 @@ 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 canFetchAbilityCaseInsensitive() {
|
||||
void canFetchAbilityCaseInsensitive() {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
|
||||
@ -481,11 +586,11 @@ 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 canFetchDefaultAbility() {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
|
||||
@ -498,11 +603,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);
|
||||
|
||||
@ -517,56 +622,37 @@ public class AbilityBotTest {
|
||||
Trio<Update, Ability, String[]> docTrio = Trio.of(update, documentAbility, TEXT);
|
||||
Trio<Update, Ability, String[]> textTrio = Trio.of(update, textAbility, TEXT);
|
||||
|
||||
assertFalse("Unexpected result when checking for message flags", bot.checkMessageFlags(docTrio));
|
||||
assertTrue("Unexpected result when checking for message flags", 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);
|
||||
|
||||
bot.reportCommands().action().accept(context);
|
||||
defaultAbs.reportCommands().action().accept(context);
|
||||
|
||||
verify(silent, times(1)).send("default - dis iz default command", GROUP_ID);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageContext mockContext(User user) {
|
||||
return mockContext(user, user.getId());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static MessageContext mockContext(User user, long groupId, String... args) {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
|
||||
when(update.hasMessage()).thenReturn(true);
|
||||
when(update.getMessage()).thenReturn(message);
|
||||
|
||||
when(message.getFrom()).thenReturn(user);
|
||||
when(message.hasText()).thenReturn(true);
|
||||
|
||||
return newContext(update, user, groupId, args);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canPrintCommandsBasedOnPrivacy() {
|
||||
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 creatorCtx = newContext(update, CREATOR, GROUP_ID);
|
||||
MessageContext creatorCtx = newContext(update, CREATOR, GROUP_ID, bot);
|
||||
|
||||
bot.commands().action().accept(creatorCtx);
|
||||
defaultAbs.commands().action().accept(creatorCtx);
|
||||
|
||||
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test\nADMIN\n/admin\n/ban\n/demote\n/promote\n/unban\nCREATOR\n/backup\n/claim\n/recover\n/report";
|
||||
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test\nADMIN\n/admin\n/ban\n/demote\n/promote\n/stats\n/unban\nCREATOR\n/backup\n/claim\n/recover\n/report";
|
||||
verify(silent, times(1)).send(expected, GROUP_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void printsOnlyPublicCommandsForNormalUser() {
|
||||
void printsOnlyPublicCommandsForNormalUser() {
|
||||
Update update = mock(Update.class);
|
||||
Message message = mock(Message.class);
|
||||
|
||||
@ -574,39 +660,74 @@ public class AbilityBotTest {
|
||||
when(update.getMessage()).thenReturn(message);
|
||||
when(message.hasText()).thenReturn(true);
|
||||
|
||||
MessageContext userCtx = newContext(update, USER, GROUP_ID);
|
||||
MessageContext userCtx = newContext(update, USER, GROUP_ID, bot);
|
||||
|
||||
bot.commands().action().accept(userCtx);
|
||||
defaultAbs.commands().action().accept(userCtx);
|
||||
|
||||
String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test";
|
||||
verify(silent, times(1)).send(expected, GROUP_ID);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException {
|
||||
db.clear();
|
||||
db.close();
|
||||
@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);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Update mockFullUpdate(User user, String args) {
|
||||
bot.users().put(USER.getId(), USER);
|
||||
bot.users().put(CREATOR.getId(), CREATOR);
|
||||
bot.userIds().put(CREATOR.getUserName(), CREATOR.getId());
|
||||
bot.userIds().put(USER.getUserName(), USER.getId());
|
||||
@Test
|
||||
void canProcessRepliesRegisteredInCollection() {
|
||||
Update firstUpdate = mock(Update.class);
|
||||
Message firstMessage = mock(Message.class);
|
||||
when(firstMessage.getText()).thenReturn(FIRST_REPLY_KEY_MESSAGE);
|
||||
when(firstMessage.getChatId()).thenReturn(1L);
|
||||
|
||||
bot.admins().add(CREATOR.getId());
|
||||
Update secondUpdate = mock(Update.class);
|
||||
Message secondMessage = mock(Message.class);
|
||||
when(secondMessage.getText()).thenReturn(SECOND_REPLY_KEY_MESSAGE);
|
||||
when(secondMessage.getChatId()).thenReturn(1L);
|
||||
|
||||
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;
|
||||
mockUser(firstUpdate, firstMessage, USER);
|
||||
mockUser(secondUpdate, secondMessage, USER);
|
||||
|
||||
|
||||
bot.onUpdateReceived(firstUpdate);
|
||||
bot.onUpdateReceived(secondUpdate);
|
||||
|
||||
verify(silent, times(2)).send(anyString(), anyLong());
|
||||
verify(silent, times(1)).send("first reply answer", 1);
|
||||
verify(silent, times(1)).send("second reply answer", 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) {
|
||||
@ -627,6 +748,7 @@ public class AbilityBotTest {
|
||||
Message botMessage = mock(Message.class);
|
||||
Document document = mock(Document.class);
|
||||
|
||||
when(document.getFileId()).thenReturn("FAKEFILEID");
|
||||
when(message.getFrom()).thenReturn(CREATOR);
|
||||
when(update.getMessage()).thenReturn(message);
|
||||
when(message.getDocument()).thenReturn(document);
|
||||
|
@ -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(1L, "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 long 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,10 @@ package org.telegram.abilitybots.api.bot;
|
||||
import org.telegram.abilitybots.api.db.DBContext;
|
||||
import org.telegram.abilitybots.api.objects.Ability;
|
||||
import org.telegram.abilitybots.api.objects.Ability.AbilityBuilder;
|
||||
import org.telegram.abilitybots.api.objects.Flag;
|
||||
import org.telegram.abilitybots.api.objects.Reply;
|
||||
import org.telegram.abilitybots.api.objects.ReplyCollection;
|
||||
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;
|
||||
@ -12,11 +16,17 @@ import static org.telegram.abilitybots.api.objects.Privacy.ADMIN;
|
||||
import static org.telegram.abilitybots.api.objects.Privacy.PUBLIC;
|
||||
|
||||
public class DefaultBot extends AbilityBot {
|
||||
public static final String FIRST_REPLY_KEY_MESSAGE = "first reply key string";
|
||||
public static final String SECOND_REPLY_KEY_MESSAGE = "second reply key string";
|
||||
|
||||
public DefaultBot(String token, String username, DBContext db) {
|
||||
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")
|
||||
@ -28,16 +38,16 @@ public class DefaultBot extends AbilityBot {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int creatorId() {
|
||||
return 1337;
|
||||
public long creatorId() {
|
||||
return 1337L;
|
||||
}
|
||||
|
||||
public Ability defaultAbility() {
|
||||
return getDefaultBuilder()
|
||||
.name(DEFAULT)
|
||||
.info("dis iz default command")
|
||||
.reply(upd -> silent.send("reply", upd.getMessage().getChatId()), MESSAGE, update -> update.getMessage().getText().equals("must reply"))
|
||||
.reply(upd -> silent.send("reply", upd.getCallbackQuery().getMessage().getChatId()), CALLBACK_QUERY)
|
||||
.reply(Reply.of((bot, upd) -> silent.send("reply", upd.getMessage().getChatId()), MESSAGE, update -> update.getMessage().getText().equals("must reply")).enableStats("mustreply"))
|
||||
.reply((bot, upd) -> silent.send("reply", upd.getCallbackQuery().getMessage().getChatId()), CALLBACK_QUERY)
|
||||
.build();
|
||||
}
|
||||
|
||||
@ -62,10 +72,31 @@ public class DefaultBot extends AbilityBot {
|
||||
.privacy(PUBLIC)
|
||||
.locality(USER)
|
||||
.input(4)
|
||||
.enableStats()
|
||||
.build();
|
||||
}
|
||||
|
||||
public Reply channelPostReply() {
|
||||
return Reply.of(
|
||||
(bot, upd) -> silent.send("test channel post", upd.getChannelPost().getChatId()),
|
||||
Flag.CHANNEL_POST
|
||||
);
|
||||
}
|
||||
|
||||
public ReplyCollection createReplyCollection() {
|
||||
return ReplyCollection.of(
|
||||
Reply.of(
|
||||
(bot, upd) -> silent.send("first reply answer", upd.getMessage().getChatId()),
|
||||
update -> update.getMessage().getText().equalsIgnoreCase(FIRST_REPLY_KEY_MESSAGE)
|
||||
),
|
||||
Reply.of(
|
||||
(bot, upd) -> silent.send("second reply answer", upd.getMessage().getChatId()),
|
||||
update -> update.getMessage().getText().equalsIgnoreCase(SECOND_REPLY_KEY_MESSAGE)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public Ability testAbility() {
|
||||
return getDefaultBuilder().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,117 @@
|
||||
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 {
|
||||
/**
|
||||
* Constructor for ExtensionUsingBot
|
||||
*/
|
||||
ExtensionUsingBot() {
|
||||
// https://github.com/rubenlagus/TelegramBots/issues/834
|
||||
super("", "", offlineInstance("testing"));
|
||||
addExtension(new AbilityBotExtension("addedInConstructor", this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long creatorId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for returning AbiltyExtension
|
||||
* @return AbilityBotExtension instance
|
||||
*/
|
||||
public AbilityBotExtension methodReturningExtensionSubClass() {
|
||||
// https://github.com/rubenlagus/TelegramBots/issues/834
|
||||
return new AbilityBotExtension("returningSubClass", this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method for returning AbilityExtension
|
||||
* @return AbiltyBotExtension instance
|
||||
*/
|
||||
public AbilityExtension methodReturningExtensionSuperClass() {
|
||||
// https://github.com/rubenlagus/TelegramBots/issues/834
|
||||
return new AbilityBotExtension("returningSuperClass", this);
|
||||
}
|
||||
|
||||
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;
|
||||
private AbilityBot extensionUser;
|
||||
|
||||
/**
|
||||
* https://github.com/rubenlagus/TelegramBots/issues/721
|
||||
* Constructor for AbilityBotExtension
|
||||
* @param name Name of the ability extension
|
||||
* @param extensionUser The AbilityBot that uses this AbilityExtension
|
||||
*/
|
||||
AbilityBotExtension(String name, AbilityBot extensionUser) {
|
||||
this.name = name;
|
||||
// https://github.com/rubenlagus/TelegramBots/issues/834
|
||||
this.extensionUser = extensionUser;
|
||||
}
|
||||
|
||||
public Ability abc() {
|
||||
return Ability.builder()
|
||||
.name(name + "0abc")
|
||||
.info("Test ability")
|
||||
.locality(ALL)
|
||||
.privacy(PUBLIC)
|
||||
.action(ctx -> {
|
||||
// https://github.com/rubenlagus/TelegramBots/issues/834
|
||||
extensionUser.silent().send("This is a test message.", ctx.chatId());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 long creatorId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ReplyFlow directionFlow() {
|
||||
Reply saidLeft = Reply.of((bot, upd) -> silent.send("Sir, I have gone left.", getChatId(upd)),
|
||||
hasMessageWith("go left or else"));
|
||||
|
||||
ReplyFlow leftflow = ReplyFlow.builder(db, 2)
|
||||
.action((bot, upd) -> silent.send("I don't know how to go left.", getChatId(upd)))
|
||||
.onlyIf(hasMessageWith("left"))
|
||||
.next(saidLeft).build();
|
||||
|
||||
Reply saidRight = Reply.of((bot, upd) -> silent.send("Sir, I have gone right.", getChatId(upd)),
|
||||
hasMessageWith("right"));
|
||||
|
||||
return ReplyFlow.builder(db, 1)
|
||||
.action((bot, 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(
|
||||
(bot, 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((bot, upd) -> {
|
||||
silent.send("Second reply", upd.getMessage().getChatId());
|
||||
})
|
||||
.onlyIf(hasMessageWith("two"))
|
||||
.build();
|
||||
|
||||
Reply replyWithNickname = ReplyFlow.builder(db, 1)
|
||||
.enableStats("FIRST")
|
||||
.action((bot, 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package org.telegram.abilitybots.api.bot;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.telegram.abilitybots.api.objects.MessageContext.newContext;
|
||||
|
||||
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;
|
||||
|
||||
public final class TestUtils {
|
||||
public static final User USER = new User(1L, "first", false, "last", "username", null, false, false, false, false, false, false, false, false, false, false, false);
|
||||
public static final User CREATOR = new User(1337L, "creatorFirst", false, "creatorLast", "creatorUsername", null, false, false, false, false, false, false, false, false, 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());
|
||||
}
|
||||
}
|
@ -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.telegrambots.api.objects.User;
|
||||
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;
|
||||
@ -12,67 +13,93 @@ import java.util.Set;
|
||||
import static com.google.common.collect.Maps.newHashMap;
|
||||
import static com.google.common.collect.Sets.newHashSet;
|
||||
import static java.lang.String.format;
|
||||
import static org.junit.Assert.*;
|
||||
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.USER;
|
||||
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() {
|
||||
Map<Integer, User> users = db.getMap(USERS);
|
||||
Map<String, Integer> userIds = db.getMap(USER_ID);
|
||||
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<Long, User> users = db.getMap(USERS);
|
||||
Map<String, Long> userIds = db.getMap(USER_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, User> originalUsers = newHashMap(users);
|
||||
Map<Long, User> originalUsers = newHashMap(users);
|
||||
String beforeBackupInfo = db.info(USERS);
|
||||
|
||||
Object jsonBackup = db.backup();
|
||||
db.clear();
|
||||
boolean recovered = db.recover(jsonBackup);
|
||||
|
||||
Map<Integer, User> recoveredUsers = db.getMap(USERS);
|
||||
Map<Long, 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() {
|
||||
void canFallbackDBIfRecoveryFails() {
|
||||
Set<User> users = db.getSet(USERS);
|
||||
users.add(CREATOR);
|
||||
users.add(USER);
|
||||
|
||||
Set<User> originalSet = newHashSet(users);
|
||||
Object jsonBackup = db.backup();
|
||||
String corruptBackup = "!@#$" + String.valueOf(jsonBackup);
|
||||
String corruptBackup = "!@#$" + jsonBackup;
|
||||
boolean recovered = db.recover(corruptBackup);
|
||||
|
||||
Set<User> recoveredSet = db.getSet(USERS);
|
||||
|
||||
assertFalse("Recovery was successful from a CORRUPT backup", 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() {
|
||||
void canGetSummary() {
|
||||
String anotherTest = TEST + 1;
|
||||
db.getSet(TEST).add(TEST);
|
||||
db.getSet(anotherTest).add(anotherTest);
|
||||
@ -81,27 +108,27 @@ 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() {
|
||||
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);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void cantGetInfoFromNonexistentDBStructureName() {
|
||||
db.info(TEST);
|
||||
assertEquals(expectedInfo, actualInfo, "Actual DB structure info does not match that of the expected");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canGetAndSetVariables() {
|
||||
void cantGetInfoFromNonexistentDBStructureName() {
|
||||
Assertions.assertThrows(IllegalStateException.class, () -> db.info(TEST));
|
||||
}
|
||||
|
||||
@Test
|
||||
void canGetAndSetVariables() {
|
||||
String varName = "somevar";
|
||||
Var<User> var = db.getVar(varName);
|
||||
var.set(CREATOR);
|
||||
@ -117,9 +144,16 @@ public class MapDBContextTest {
|
||||
assertEquals(changedVar.get(), USER);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws IOException {
|
||||
db.clear();
|
||||
db.close();
|
||||
@Test
|
||||
void testToString() throws Exception {
|
||||
String varName = "somevar";
|
||||
Var<User> var = db.getVar(varName);
|
||||
var.set(CREATOR);
|
||||
db.commit();
|
||||
var = db.getVar(varName);
|
||||
var.set(USER);
|
||||
db.commit();
|
||||
Var<User> changedVar = db.getVar(varName);
|
||||
Assertions.assertEquals("MapDBVar{var=User(id=1, firstName=first, isBot=false, lastName=last, userName=username, languageCode=null, canJoinGroups=false, canReadAllGroupMessages=false, supportInlineQueries=false, isPremium=false, addedToAttachmentMenu=false)}", ((MapDBVar) (changedVar)).toString());
|
||||
}
|
||||
}
|
||||
|
@ -1,58 +1,73 @@
|
||||
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.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
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");
|
||||
}
|
||||
|
||||
@Test
|
||||
void abilityBuilderSetStatsEnabledTrueTest() {
|
||||
Ability statsEnabledAbility = getDefaultBuilder().setStatsEnabled(true).build();
|
||||
assertTrue(statsEnabledAbility.statsEnabled());
|
||||
}
|
||||
|
||||
@Test
|
||||
void abilityBuilderSetStatsEnabledFalseTest() {
|
||||
Ability statsDisabledAbility = getDefaultBuilder().setStatsEnabled(false).build();
|
||||
assertFalse(statsDisabledAbility.statsEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,43 +1,78 @@
|
||||
package org.telegram.abilitybots.api.sender;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.telegram.telegrambots.exceptions.TelegramApiException;
|
||||
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 junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertFalse;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class SilentSenderTest {
|
||||
class SilentSenderTest {
|
||||
private SilentSender silent;
|
||||
private MessageSender sender;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
sender = mock(MessageSender.class);
|
||||
silent = new SilentSender(sender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsEmptyOnError() throws TelegramApiException {
|
||||
void returnsEmptyOnError() throws TelegramApiException {
|
||||
when(sender.execute(any())).thenThrow(TelegramApiException.class);
|
||||
|
||||
Optional execute = silent.execute(null);
|
||||
|
||||
assertFalse("Execution of a bot API method with execption results in a nonempty optional", execute.isPresent());
|
||||
assertFalse(execute.isPresent(), "Execution of a bot API method with execption results in a nonempty optional");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnOptionalOnSuccess() throws TelegramApiException {
|
||||
void returnOptionalOnSuccess() throws TelegramApiException {
|
||||
String data = "data";
|
||||
when(sender.execute(any())).thenReturn(data);
|
||||
|
||||
Optional execute = silent.execute(null);
|
||||
|
||||
assertEquals("Silent execution resulted in a different object", data, execute.get());
|
||||
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 static 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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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()));
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
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 java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.EMPTY;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance;
|
||||
|
||||
class CustomToggleTest {
|
||||
private DBContext db;
|
||||
private AbilityToggle toggle;
|
||||
private DefaultBot customBot;
|
||||
private String filename;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
db = offlineInstance("db");
|
||||
filename = "src/test/resources/toggle.properties";
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTurnOffAbilitiesThroughProperties() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(Files.newInputStream(Paths.get(filename)));
|
||||
toggle = new CustomToggle().config(properties);
|
||||
} catch (IOException e) {
|
||||
System.out.println("No such file");
|
||||
}
|
||||
|
||||
customBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
|
||||
customBot.onRegister();
|
||||
|
||||
assertFalse(customBot.abilities().containsKey(DefaultAbilities.CLAIM));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canProcessAbilitiesThroughProperties() {
|
||||
Properties properties = new Properties();
|
||||
try {
|
||||
properties.load(Files.newInputStream(Paths.get(filename)));
|
||||
toggle = new CustomToggle().config(properties);
|
||||
} catch (IOException e) {
|
||||
System.out.println("No such file");
|
||||
}
|
||||
|
||||
customBot = new DefaultBot(EMPTY, EMPTY, db, toggle);
|
||||
customBot.onRegister();
|
||||
|
||||
String targetName = "restrict";
|
||||
assertTrue(customBot.abilities().containsKey(targetName));
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
19
telegrambots-abilities/src/test/resources/log4j2-test.xml
Normal 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>
|
@ -0,0 +1,2 @@
|
||||
ability.claim.enabled=false
|
||||
ability.ban.toggle=restrict
|
61
telegrambots-chat-session-bot/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
<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>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle**
|
||||
|
||||
```gradle
|
||||
implementation 'org.telegram:telegrambots-chat-session-bot:6.8.0'
|
||||
```
|
||||
|
||||
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`
|
261
telegrambots-chat-session-bot/pom.xml
Normal file
@ -0,0 +1,261 @@
|
||||
<?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.warp</groupId>
|
||||
<artifactId>bots</artifactId>
|
||||
<version>6.8.0</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>
|
||||
<repository>
|
||||
<id>mchv-release-distribution</id>
|
||||
<name>MCHV Release Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>mchv-snapshot-distribution</id>
|
||||
<name>MCHV Snapshot Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
|
||||
</snapshotRepository>
|
||||
</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.10.0</shiro.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.warp</groupId>
|
||||
<artifactId>telegrambots</artifactId>
|
||||
<version>6.8.0</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>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</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-M6</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>3.2.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.3.0</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.2.1</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.4.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.8</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-M3</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.3.0</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.10.1</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
@ -0,0 +1,5 @@
|
||||
module telegrambots.chat.session.bot {
|
||||
requires shiro.core;
|
||||
requires telegrambots;
|
||||
requires telegrambots.meta;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
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.bots.TelegramLongPollingBot;
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@SuppressWarnings({"WeakerAccess", "OptionalUsedAsFieldOrParameterType", "unused"})
|
||||
public abstract class TelegramLongPollingSessionBot extends TelegramLongPollingBot {
|
||||
DefaultSessionManager sessionManager;
|
||||
|
||||
ChatIdConverter chatIdConverter;
|
||||
/**
|
||||
* If this is used getBotToken has to be overridden in order to return the bot token!
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramLongPollingSessionBot(){
|
||||
this(new DefaultChatIdConverter());
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is used getBotToken has to be overridden in order to return the bot token!
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter){
|
||||
this(chatIdConverter, new DefaultBotOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is used getBotToken has to be overridden in order to return the bot token!
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter, DefaultBotOptions defaultBotOptions){
|
||||
this(chatIdConverter, defaultBotOptions, null);
|
||||
}
|
||||
|
||||
public TelegramLongPollingSessionBot(String botToken){
|
||||
this(new DefaultChatIdConverter(), botToken);
|
||||
}
|
||||
|
||||
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter, String botToken){
|
||||
this(chatIdConverter, new DefaultBotOptions(), botToken);
|
||||
}
|
||||
|
||||
public TelegramLongPollingSessionBot(ChatIdConverter chatIdConverter, DefaultBotOptions defaultBotOptions, String botToken){
|
||||
super(defaultBotOptions, botToken);
|
||||
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;
|
||||
}
|
||||
if (message.getChat() != null) {
|
||||
chatIdConverter.setSessionId(message.getChatId());
|
||||
chatSession = this.getSession(message);
|
||||
} else {
|
||||
chatSession = Optional.empty();
|
||||
}
|
||||
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);
|
||||
}
|
@ -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.6.1</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
2. Using Gradle:
|
||||
|
||||
```gradle
|
||||
compile "org.telegram:telegrambotsextensions:3.6"
|
||||
implementation 'org.telegram:telegrambotsextensions:6.8.0'
|
||||
```
|
@ -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.warp</groupId>
|
||||
<artifactId>bots</artifactId>
|
||||
<version>6.8.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>telegrambotsextensions</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Telegram Bots Extensions</name>
|
||||
@ -46,27 +51,33 @@
|
||||
</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>
|
||||
<id>mchv-release-distribution</id>
|
||||
<name>MCHV Release Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv</url>
|
||||
</repository>
|
||||
<snapshotRepository>
|
||||
<id>mchv-snapshot-distribution</id>
|
||||
<name>MCHV Snapshot Apache Maven Packages Distribution</name>
|
||||
<url>https://mvn.mchv.eu/repository/mchv-snapshot</url>
|
||||
</snapshotRepository>
|
||||
</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.6.1</bots.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.telegram</groupId>
|
||||
<groupId>org.warp</groupId>
|
||||
<artifactId>telegrambots</artifactId>
|
||||
<version>${bots.version}</version>
|
||||
<version>6.8.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@ -77,10 +88,15 @@
|
||||
<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-M6</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-gpg-plugin</artifactId>
|
||||
<version>1.5</version>
|
||||
<version>1.6</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>sign-artifacts</id>
|
||||
@ -91,20 +107,9 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.sonatype.plugins</groupId>
|
||||
<artifactId>nexus-staging-maven-plugin</artifactId>
|
||||
<version>1.6.3</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.0.0</version>
|
||||
<version>3.2.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>clean-project</id>
|
||||
@ -117,7 +122,7 @@
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<version>3.3.0</version>
|
||||
<configuration>
|
||||
<descriptorRefs>
|
||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
||||
@ -136,7 +141,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@ -148,14 +153,14 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.10.3</version>
|
||||
<version>3.4.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
<doclint>none</doclint>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@ -163,7 +168,7 @@
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<version>0.7.7.201606060606</version>
|
||||
<version>0.8.8</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
@ -182,7 +187,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-enforcer-plugin</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<version>3.0.0-M3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>enforce-versions</id>
|
||||
@ -200,7 +205,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
<version>3.3.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy</id>
|
||||
@ -214,13 +219,21 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
</project>
|
||||
</project>
|
||||
|
4
telegrambots-extensions/src/main/java/module-info.java
Normal file
@ -0,0 +1,4 @@
|
||||
module telegrambotsextensions {
|
||||
requires telegrambots.meta;
|
||||
requires telegrambots;
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.telegram.telegrambots.extensions.bots.commandbot;
|
||||
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
|
||||
/**
|
||||
* This interface represents common functions for command bots
|
||||
*
|
||||
* @author Andrey Korsakov (loolzaaa)
|
||||
*/
|
||||
public interface CommandBot {
|
||||
/**
|
||||
* Process all updates, that are not commands.
|
||||
*
|
||||
* @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>.
|
||||
*/
|
||||
void processNonCommandUpdate(Update 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
|
||||
*/
|
||||
default 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();
|
||||
* #
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
default boolean filter(Message message) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package org.telegram.telegrambots.extensions.bots.commandbot;
|
||||
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
|
||||
/**
|
||||
* @author Varun Singh
|
||||
* This class adds argument functionality to a given message that a user typed, that also counts as a command
|
||||
*/
|
||||
public class CommandMessage {
|
||||
/**
|
||||
* The message associated with the command
|
||||
*/
|
||||
private Message commandMessage;
|
||||
|
||||
/**
|
||||
* Constructor for CommandMessage
|
||||
* @param msg The message that represents the command that the user typed
|
||||
* Preconditions:
|
||||
* msg.isCommand() is true
|
||||
* msg.hasText() is true
|
||||
* Postcondition:
|
||||
* The developer has the ability to determine which command the message is executing
|
||||
*/
|
||||
public CommandMessage(Message msg) {
|
||||
commandMessage = msg;
|
||||
}
|
||||
|
||||
public Message getCommandMessage() {
|
||||
return commandMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the String representation of the message for ease
|
||||
*/
|
||||
public String getMessageText() {
|
||||
return commandMessage.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates the given text after the command into an ordered array of arguments
|
||||
* @return each argument as a String item in an array
|
||||
*/
|
||||
public String[] getArgs() {
|
||||
return getArgsStr().split(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the text of the command WITHOUT the slash
|
||||
* For example, if /language English
|
||||
*/
|
||||
public String getCommandText() {
|
||||
return getCommandText(false).substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The text of the message from and including the '/' to the space,
|
||||
* which assumes the end of the command name and start of the command arguments
|
||||
*/
|
||||
public String getCommandText(boolean includeHandle) {
|
||||
if (includeHandle || commandMessage.isUserMessage()) {
|
||||
return getMessageText().substring(0, getMessageText().indexOf(" "));
|
||||
} else
|
||||
return getMessageText().substring(0, getMessageText().indexOf("@"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Singles out the text of the message after the command
|
||||
* @return all the arguments grouped in one string
|
||||
*/
|
||||
public String getArgsStr() {
|
||||
return getMessageText().substring(getCommandText(true).length() + 1); // Skip space
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +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.IBotCommand;
|
||||
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;
|
||||
@ -21,18 +19,18 @@ import java.util.function.BiConsumer;
|
||||
*
|
||||
* @author Timo Schulz (Mit0x2)
|
||||
*/
|
||||
public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingBot implements ICommandRegistry {
|
||||
public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingBot implements CommandBot, 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
|
||||
* @deprecated Use {{@link #TelegramLongPollingBot(String)}
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(String botUsername) {
|
||||
this(ApiContext.getInstance(DefaultBotOptions.class), botUsername);
|
||||
@Deprecated
|
||||
public TelegramLongPollingCommandBot() {
|
||||
this(new DefaultBotOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -41,10 +39,12 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @param options Bot options
|
||||
* @param botUsername Username of the bot
|
||||
*
|
||||
* @deprecated Use {{@link #TelegramLongPollingBot(DefaultBotOptions, String)}
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options, String botUsername) {
|
||||
this(options, true, botUsername);
|
||||
@Deprecated
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options) {
|
||||
this(options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,12 +54,46 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
|
||||
* @param options Bot options
|
||||
* @param allowCommandsWithUsername true to allow commands with parameters (default),
|
||||
* false otherwise
|
||||
* @param botUsername bot username of this bot
|
||||
*
|
||||
* @deprecated Use {{@link #TelegramLongPollingBot(DefaultBotOptions, boolean, String)}
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername, String botUsername) {
|
||||
@Deprecated
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername) {
|
||||
super(options);
|
||||
this.botUsername = botUsername;
|
||||
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, botUsername);
|
||||
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, this::getBotUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramLongPollingCommandBot using default options
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(String botToken) {
|
||||
this(new DefaultBotOptions(), botToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options, String botToken) {
|
||||
this(options, true, botToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramLongPollingCommandBot
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @param options Bot options
|
||||
* @param allowCommandsWithUsername true to allow commands with parameters (default),
|
||||
* false otherwise
|
||||
*/
|
||||
public TelegramLongPollingCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername, String botToken) {
|
||||
super(options, botToken);
|
||||
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, this::getBotUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -77,34 +111,6 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
|
||||
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();
|
||||
* #
|
||||
*
|
||||
* @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(IBotCommand botCommand) {
|
||||
return commandRegistry.register(botCommand);
|
||||
@ -144,16 +150,5 @@ public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingB
|
||||
* @return Bot username
|
||||
*/
|
||||
@Override
|
||||
public final String getBotUsername() {
|
||||
return botUsername;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process all updates, that are not commands.
|
||||
*
|
||||
* @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);
|
||||
public abstract String getBotUsername();
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
package org.telegram.telegrambots.extensions.bots.commandbot;
|
||||
|
||||
|
||||
import org.telegram.telegrambots.bots.DefaultBotOptions;
|
||||
import org.telegram.telegrambots.bots.TelegramWebhookBot;
|
||||
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 org.telegram.telegrambots.meta.api.methods.BotApiMethod;
|
||||
import org.telegram.telegrambots.meta.api.objects.Message;
|
||||
import org.telegram.telegrambots.meta.api.objects.Update;
|
||||
import org.telegram.telegrambots.meta.bots.AbsSender;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This class adds command functionality to the TelegramWebhookBot
|
||||
*
|
||||
* @author Andrey Korsakov (loolzaaa)
|
||||
*/
|
||||
public abstract class TelegramWebhookCommandBot extends TelegramWebhookBot implements CommandBot, ICommandRegistry {
|
||||
private final CommandRegistry commandRegistry;
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot using default options
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramWebhookCommandBot() {
|
||||
this(new DefaultBotOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot with custom options and allowing commands with
|
||||
* usernames
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*
|
||||
* @param options Bot options
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramWebhookCommandBot(DefaultBotOptions options) {
|
||||
this(options, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @deprecated Overwriting the getBotToken() method is deprecated. Use the constructor instead
|
||||
*
|
||||
* @param options Bot options
|
||||
* @param allowCommandsWithUsername true to allow commands with parameters (default),
|
||||
* false otherwise
|
||||
*/
|
||||
@Deprecated
|
||||
public TelegramWebhookCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername) {
|
||||
super(options);
|
||||
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, this::getBotUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot using default options
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @param botToken the telegram api token
|
||||
*/
|
||||
public TelegramWebhookCommandBot(String botToken) {
|
||||
this(new DefaultBotOptions(), botToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot with custom options and allowing commands with
|
||||
* usernames
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @param options Bot options
|
||||
* @param botToken the telegram api token
|
||||
*/
|
||||
public TelegramWebhookCommandBot(DefaultBotOptions options, String botToken) {
|
||||
this(options, true, botToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a TelegramWebhookCommandBot
|
||||
* Use ICommandRegistry's methods on this bot to register commands
|
||||
*
|
||||
* @param options Bot options
|
||||
* @param allowCommandsWithUsername true to allow commands with parameters (default),
|
||||
* false otherwise
|
||||
* @param botToken the telegram api token
|
||||
*/
|
||||
public TelegramWebhookCommandBot(DefaultBotOptions options, boolean allowCommandsWithUsername, String botToken) {
|
||||
super(options, botToken);
|
||||
this.commandRegistry = new CommandRegistry(allowCommandsWithUsername, this::getBotUsername);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BotApiMethod<?> onWebhookUpdateReceived(Update update) {
|
||||
if (update.hasMessage()) {
|
||||
Message message = update.getMessage();
|
||||
if (message.isCommand() && !filter(message)) {
|
||||
if (!commandRegistry.executeCommand(this, message)) {
|
||||
//we have received a not registered command, handle it as invalid
|
||||
processInvalidCommandUpdate(update);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
processNonCommandUpdate(update);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean register(IBotCommand botCommand) {
|
||||
return commandRegistry.register(botCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<IBotCommand, Boolean> registerAll(IBotCommand... botCommands) {
|
||||
return commandRegistry.registerAll(botCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean deregister(IBotCommand botCommand) {
|
||||
return commandRegistry.deregister(botCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Map<IBotCommand, Boolean> deregisterAll(IBotCommand... botCommands) {
|
||||
return commandRegistry.deregisterAll(botCommands);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Collection<IBotCommand> getRegisteredCommands() {
|
||||
return commandRegistry.getRegisteredCommands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerDefaultAction(BiConsumer<AbsSender, Message> defaultConsumer) {
|
||||
commandRegistry.registerDefaultAction(defaultConsumer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final IBotCommand getRegisteredCommand(String commandIdentifier) {
|
||||
return commandRegistry.getRegisteredCommand(commandIdentifier);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
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.Message;
|
||||
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
|
@ -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
|
||||
@ -18,17 +21,17 @@ public final class CommandRegistry implements ICommandRegistry {
|
||||
|
||||
private final Map<String, IBotCommand> commandRegistryMap = new HashMap<>();
|
||||
private final boolean allowCommandsWithUsername;
|
||||
private final String botUsername;
|
||||
private final Supplier<String> botUsernameSupplier;
|
||||
private BiConsumer<AbsSender, Message> defaultConsumer;
|
||||
|
||||
/**
|
||||
* Creates a Command registry
|
||||
* @param allowCommandsWithUsername True to allow commands with username, false otherwise
|
||||
* @param botUsername Bot username
|
||||
* @param botUsernameSupplier Bot username supplier
|
||||
*/
|
||||
public CommandRegistry(boolean allowCommandsWithUsername, String botUsername) {
|
||||
public CommandRegistry(boolean allowCommandsWithUsername, Supplier<String> botUsernameSupplier) {
|
||||
this.allowCommandsWithUsername = allowCommandsWithUsername;
|
||||
this.botUsername = botUsername;
|
||||
this.botUsernameSupplier = botUsernameSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,7 +88,7 @@ public final class CommandRegistry implements ICommandRegistry {
|
||||
/**
|
||||
* Executes a command action if the command is registered.
|
||||
*
|
||||
* @note If the command is not registered and there is a default consumer,
|
||||
* @apiNote If the command is not registered and there is a default consumer,
|
||||
* that action will be performed
|
||||
*
|
||||
* @param absSender absSender
|
||||
@ -119,10 +122,13 @@ public final class CommandRegistry implements ICommandRegistry {
|
||||
* the command
|
||||
* @param command Command to simplify
|
||||
* @return Simplified command
|
||||
* @throws java.lang.NullPointerException if {@code allowCommandsWithUsername} is {@code true}
|
||||
* and {@code botUsernameSupplier} returns {@code null}
|
||||
*/
|
||||
private String removeUsernameFromCommandIfNeeded(String command) {
|
||||
if (allowCommandsWithUsername) {
|
||||
return command.replace("@" + botUsername, "").trim();
|
||||
String botUsername = Objects.requireNonNull(botUsernameSupplier.get(), "Bot username must not be null");
|
||||
return command.replaceAll("(?i)@" + Pattern.quote(botUsername), "").trim();
|
||||
}
|
||||
return command;
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
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.Message;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Bot command with message ID in execute method
|
@ -1,11 +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 java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
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
|
@ -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;
|
@ -1,116 +1,116 @@
|
||||
package org.telegram.telegrambots.bots.commandbot.commands.helpCommand;
|
||||
|
||||
import org.telegram.telegrambots.api.methods.send.SendMessage;
|
||||
import org.telegram.telegrambots.api.objects.Chat;
|
||||
import org.telegram.telegrambots.api.objects.User;
|
||||
import org.telegram.telegrambots.bots.AbsSender;
|
||||
import org.telegram.telegrambots.bots.commandbot.commands.IBotCommand;
|
||||
import org.telegram.telegrambots.bots.commandbot.commands.ICommandRegistry;
|
||||
import org.telegram.telegrambots.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(new SendMessage(chat.getId(), reply).setParseMode("HTML"));
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
String reply = getHelpText(registry);
|
||||
try {
|
||||
absSender.execute(new SendMessage(chat.getId(), reply).setParseMode("HTML"));
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
package org.telegram.telegrambots.extensions.bots.commandbot.commands.helpCommand;
|
||||
|
||||
import org.telegram.telegrambots.extensions.bots.commandbot.commands.IBotCommand;
|
||||
import org.telegram.telegrambots.extensions.bots.commandbot.commands.ICommandRegistry;
|
||||
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.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[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()).text(reply).parseMode("HTML").build());
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
String reply = getHelpText(registry);
|
||||
try {
|
||||
absSender.execute(SendMessage.builder().chatId(chat.getId()).text(reply).parseMode("HTML").build());
|
||||
} catch (TelegramApiException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|