Compare commits

...

30 Commits

Author SHA1 Message Date
Andrea Cavalli 73b8f90ee2 Merge commit '203b26587f771f9b9bf0f97f8df04e18612a5fe5' 2020-08-21 12:35:54 +02:00
Ruben Bermudez 203b26587f
Merge pull request #788 from rubenlagus/dev
Dev
2020-08-02 11:38:03 +01:00
Ruben Bermudez e6b9fd892f
Merge pull request #789 from addo37/fix-longest-match
Fix matching longest ability name on continuous text feature
2020-08-01 16:02:46 +01:00
Abbas Abou Daya 402c36e6b2 Fix matching longest ability name on continuous text feature 2020-08-01 05:56:50 -07:00
rubenlagus 749c6d3b6f Fixes #767, #766, #761, #763, #776, #772, #771, #780 2020-08-01 12:49:46 +01:00
Ruben Bermudez 196d0a3924
Merge pull request #785 from oskov/update-comments-for-AbilityBot
Simple comment update
2020-07-31 01:29:31 +01:00
Ruben Bermudez 280b9f2686
Merge pull request #780 from addo37/improve-token-fetch
Add customization options for command processing
2020-07-31 01:12:55 +01:00
Ruben Bermudez 4a4267768c
Merge pull request #779 from addo37/access-privacy
Ease the access modifier on getPrivacy and other auxiliary methods
2020-07-31 01:12:06 +01:00
Ruben Bermudez fdb60e7b53
Merge pull request #778 from addo37/safe-get-user
Make getUser a safer method by returning a non-null user
2020-07-31 01:10:40 +01:00
Ruben Bermudez cec61f95a1
Merge pull request #777 from addo37/prevent-reply-crash
Wrap reply calls with a try-catch clause to avoid dynamic exceptions
2020-07-31 01:09:20 +01:00
oskov 12619422c0 Seems to have forgotten about the comment after refactoring 2020-07-23 23:13:45 +03:00
Abbas Abou Daya a419a88ce1 Add test 2020-07-18 02:04:57 -07:00
Abbas Abou Daya 4991eef1f1 Add wiki and rename no space feature 2020-07-18 01:50:16 -07:00
Abbas Abou Daya e6aae3c282 Allow users to customize command prefix, split regex and NoSpaceText 2020-07-18 01:27:39 -07:00
Abbas Abou Daya 709ed0f212 Ease the access modifier on getPrivacy and other auxiliary methods 2020-07-18 01:07:57 -07:00
Abbas Abou Daya 54096d2e85 Add test 2020-07-18 00:43:28 -07:00
Abbas Abou Daya 5281caad39 Make getUser a safer method by returning an EMPTY_USER on null 2020-07-18 00:36:29 -07:00
Abbas Abou Daya 2c1ba312b3 Wrap reply calls with a try-catch clause to avoid dynamic exceptions 2020-07-17 22:55:25 -07:00
Ruben Bermudez 023772142a
Merge pull request #763 from FireFrogSEO/master
SEND_DATA_TO_PROVIDER fieldsanf methods
2020-07-11 15:52:58 +01:00
Ruben Bermudez 427f1ee8d2
Merge pull request #761 from b1sar/master
Update README.md
2020-07-11 15:50:37 +01:00
Ruben Bermudez f6a4489498
Merge pull request #766 from addo37/ability-replyflow
Fix reply flow registration when using ability definitions
2020-07-11 15:47:51 +01:00
Ruben Bermudez de3246058b
Merge pull request #767 from UnAfraid/patch-2
Update FAQ.md
2020-07-11 15:46:12 +01:00
Rumen Nikiforov 7c993feaeb
Update README.md
Fixed SpringApplicationRun for spring boot starter readme as well
2020-06-15 11:53:37 +03:00
Rumen Nikiforov 129c6d0c2f
Update FAQ.md
Fixed typo in SpringApplicationRun
Fixed inconsistency in sendMessage example
Fixed link to my webhook example
2020-06-15 11:47:34 +03:00
Abbas Abou Daya fd91b3a2ba Fix reply flow registration when using ability definitions 2020-06-14 18:39:49 -07:00
Vladimir 223c6cccc0 added SEND_PHONE_NUMBER_TO_PROVIDER and SEND_EMAIL_TO_PROVIDER fields and methods for them 2020-06-09 12:48:25 +03:00
Vladimir 03fdafd54f added SEND_PHONE_NUMBER_TO_PROVIDER and SEND_EMAIL_TO_PROVIDER fields and methods for them 2020-06-07 15:28:55 +03:00
Vladimir 504db77a7a renamed PRIVIDER_DATA_FIELD to PROVIDER_DATA_FIELD in SendInvoice.java 2020-06-07 14:59:56 +03:00
Vladimir 8f0b31d3f1
Merge pull request #1 from rubenlagus/master
update to 4.9
2020-06-07 14:17:03 +03:00
Cebrail Yılmaz 4b1783e198
Update README.md
Altough the current version is 4.9, the readme was using version 4.1.2 which made me encounter lots of bugs. Fixed it.
2020-06-07 10:59:05 +03:00
28 changed files with 423 additions and 108 deletions

View File

@ -27,16 +27,16 @@ Just import add the library to your project with one of these options:
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
```
```gradle
compile "org.telegram:telegrambots:4.9"
compile "org.telegram:telegrambots:4.9.1"
```
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/4.9)
3. Download the jar(including all dependencies) from [here](https://mvnrepository.com/artifact/org.telegram/telegrambots/4.9)
2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/4.9.1)
3. Download the jar(including all dependencies) from [here](https://mvnrepository.com/artifact/org.telegram/telegrambots/4.9.1)
In order to use Long Polling mode, just create your own bot extending `org.telegram.telegrambots.bots.TelegramLongPollingBot`.

View File

@ -1,3 +1,6 @@
### <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

View File

@ -243,16 +243,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)
@ -268,7 +268,7 @@ public class YourApplicationMainClass {
//Add this line to initialize bots context
ApiContextInitializer.init();
SpringApplication.run(MusicUploaderApplication.class, args);
SpringApplication.run(YourApplicationMainClass.class, args);
}
}
```

View File

@ -11,13 +11,13 @@ First you need ot get the library and add it to your project. There are few poss
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
```
* With **Gradle**:
```groovy
compile group: 'org.telegram', name: 'telegrambots', version: '4.9'
compile group: 'org.telegram', name: 'telegrambots', version: '4.9.1'
```
2. Don't like **Maven Central Repository**? It can also be taken from [Jitpack](https://jitpack.io/#rubenlagus/TelegramBots).

View File

@ -33,5 +33,35 @@ As an example, if you want to restrict the updates to photos only, then you may
}
```
## 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.

View File

@ -9,12 +9,12 @@ As with any Java project, you will need to set your dependencies.
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-abilities</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
```
* **Gradle**
```groovy
implementation group: 'org.telegram', name: 'telegrambots-abilities', version: '4.9'
implementation group: 'org.telegram', name: 'telegrambots-abilities', version: '4.9.1'
```
* [JitPack](https://jitpack.io/#rubenlagus/TelegramBots)

View File

@ -7,7 +7,7 @@
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<packaging>pom</packaging>
<version>4.9</version>
<version>4.9.1</version>
<modules>
<module>telegrambots</module>

View File

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

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambots-abilities</artifactId>
@ -84,7 +84,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>

View File

@ -3,9 +3,9 @@ package org.telegram.abilitybots.api.bot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
import org.telegram.abilitybots.api.toggle.DefaultToggle;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.meta.api.objects.Update;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.exceptions.TelegramApiRequestException;
import org.telegram.telegrambots.meta.generics.LongPollingBot;
import org.telegram.telegrambots.util.WebhookUtils;
@ -13,7 +13,7 @@ import org.telegram.telegrambots.util.WebhookUtils;
import static org.telegram.abilitybots.api.db.MapDBContext.onlineInstance;
/**
* The default AbilityBot class implements {@link LongPollingBot}. It delegates all updates to a {@link TelegramLongPollingBot} instance.
* The default AbilityBot class implements {@link LongPollingBot}. It delegates all updates to a {@link DefaultAbsSender} instance.
*
* @author Abbas Abou Daya
*/

View File

@ -3,7 +3,6 @@ package org.telegram.abilitybots.api.bot;
import com.google.common.collect.*;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.telegram.abilitybots.api.db.DBContext;
@ -18,7 +17,6 @@ import org.telegram.abilitybots.api.util.Pair;
import org.telegram.abilitybots.api.util.Trio;
import org.telegram.telegrambots.bots.DefaultAbsSender;
import org.telegram.telegrambots.bots.DefaultBotOptions;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdministrators;
import org.telegram.telegrambots.meta.api.objects.Message;
import org.telegram.telegrambots.meta.api.objects.Update;
@ -27,6 +25,7 @@ import org.telegram.telegrambots.meta.api.objects.User;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
@ -37,6 +36,8 @@ 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;
@ -51,7 +52,7 @@ import static org.telegram.abilitybots.api.util.AbilityUtils.*;
/**
* The <b>father</b> of all ability bots. Bots that need to utilize abilities need to extend this bot.
* <p>
* It's important to note that this bot strictly extends {@link TelegramLongPollingBot}.
* It's important to note that this bot strictly extends {@link DefaultAbsSender}.
* <p>
* All bots extending the {@link BaseAbilityBot} get implicit abilities:
* <ul>
@ -130,35 +131,35 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
/**
* @return the map of <ID,User>
*/
protected Map<Integer, User> users() {
public Map<Integer, User> users() {
return db.getMap(USERS);
}
/**
* @return the map of <Username,ID>
*/
protected Map<String, Integer> userIds() {
public Map<String, Integer> userIds() {
return db.getMap(USER_ID);
}
/**
* @return a blacklist containing all the IDs of the banned users
*/
protected Set<Integer> blacklist() {
public Set<Integer> blacklist() {
return db.getSet(BLACKLIST);
}
/**
* @return an admin set of all the IDs of bot administrators
*/
protected Set<Integer> admins() {
public Set<Integer> admins() {
return db.getSet(ADMINS);
}
/**
* @return a mapping of ability and reply names to their corresponding statistics
*/
protected Map<String, Stats> stats() {
public Map<String, Stats> stats() {
return stats;
}
@ -221,6 +222,32 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return botUsername;
}
public Privacy getPrivacy(Update update, int id) {
return isCreator(id) ?
CREATOR : isAdmin(id) ?
ADMIN : (isGroupUpdate(update) || isSuperGroupUpdate(update)) && isGroupAdmin(update, id) ?
GROUP_ADMIN : PUBLIC;
}
public boolean isGroupAdmin(Update update, int id) {
return isGroupAdmin(getChatId(update), id);
}
public boolean isGroupAdmin(long chatId, int id) {
GetChatAdministrators admins = new GetChatAdministrators().setChatId(chatId);
return silent.execute(admins)
.orElse(new ArrayList<>()).stream()
.anyMatch(member -> member.getUser().getId() == id);
}
public boolean isCreator(int id) {
return id == creatorId();
}
public boolean isAdmin(Integer id) {
return admins().contains(id);
}
/**
* Test the update against the provided global flags. The default implementation is a passthrough to all updates.
* <p>
@ -233,6 +260,18 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return true;
}
protected String getCommandPrefix() {
return "/";
}
protected String getCommandRegexSplit() {
return " ";
}
protected boolean allowContinuousText() {
return false;
}
/**
* Registers the declared abilities using method reflection. Also, replies are accumulated using the built abilities and standalone methods that return a Reply.
* <p>
@ -277,7 +316,8 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
// Replies can be standalone or attached to abilities, fetch those too
Stream<Reply> abilityReplies = abilities.values().stream()
.flatMap(ability -> ability.replies().stream());
.flatMap(ability -> ability.replies().stream())
.flatMap(Reply::stream);
// Now create the replies registry (list)
replies = Stream.concat(abilityReplies, extensionReplies).collect(
@ -401,8 +441,12 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
}
boolean checkBlacklist(Update update) {
Integer id = AbilityUtils.getUser(update).getId();
User user = getUser(update);
if (isNull(user)) {
return true;
}
int id = user.getId();
return id == creatorId() || !blacklist().contains(id);
}
@ -458,30 +502,6 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
return isOk;
}
@NotNull
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;
}
@ -493,17 +513,28 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
if (!update.hasMessage() || !msg.hasText())
return Trio.of(update, abilities.get(DEFAULT), new String[]{});
String[] tokens = msg.getText().split(" ");
if (tokens[0].startsWith("/")) {
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
Ability ability = abilities.get(abilityToken);
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
return Trio.of(update, ability, tokens);
Ability ability;
String[] tokens;
if (allowContinuousText()) {
String abName = abilities.keySet().stream()
.filter(name -> msg.getText().startsWith(format("%s%s", getCommandPrefix(), name)))
.max(comparingInt(String::length))
.orElse(DEFAULT);
tokens = msg.getText()
.replaceFirst(getCommandPrefix() + abName, "")
.split(getCommandRegexSplit());
ability = abilities.get(abName);
} else {
Ability ability = abilities.get(DEFAULT);
return Trio.of(update, ability, tokens);
tokens = msg.getText().split(getCommandRegexSplit());
if (tokens[0].startsWith(getCommandPrefix())) {
String abilityToken = stripBotUsername(tokens[0].substring(1)).toLowerCase();
ability = abilities.get(abilityToken);
tokens = Arrays.copyOfRange(tokens, 1, tokens.length);
} else {
ability = abilities.get(DEFAULT);
}
}
return Trio.of(update, ability, tokens);
}
private String stripBotUsername(String token) {
@ -556,15 +587,25 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability
boolean filterReply(Update update) {
return replies.stream()
.filter(reply -> reply.isOkFor(update))
.map(reply -> {
.filter(reply -> runSilently(() -> reply.isOkFor(update), reply.name()))
.map(reply -> runSilently(() -> {
reply.actOn(update);
updateReplyStats(reply);
return false;
})
}, reply.name()))
.reduce(true, Boolean::logicalAnd);
}
boolean runSilently(Callable<Boolean> callable, String name) {
try {
return callable.call();
} catch(Exception ex) {
log.error(format("Reply [%s] failed to check for conditions. " +
"Make sure you're safeguarding against all possible updates.", name));
}
return false;
}
boolean checkMessageFlags(Trio<Update, Ability, String[]> trio) {
Ability ability = trio.b();
Update update = trio.a();

View File

@ -2,10 +2,8 @@ package org.telegram.abilitybots.api.objects;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.telegram.telegrambots.meta.api.objects.Update;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
@ -14,7 +12,6 @@ import java.util.function.Predicate;
import java.util.stream.Stream;
import static com.google.common.collect.Lists.newArrayList;
import static java.util.Arrays.asList;
/**
* A reply consists of update conditionals and an action to be applied on the update.
@ -37,6 +34,13 @@ public class Reply {
statsEnabled = false;
}
Reply(List<Predicate<Update>> conditions, Consumer<Update> action, String name) {
this(conditions, action);
if (Objects.nonNull(name)) {
enableStats(name);
}
}
public static Reply of(Consumer<Update> action, List<Predicate<Update>> conditions) {
return new Reply(conditions, action);
}

View File

@ -20,8 +20,8 @@ public class ReplyFlow extends Reply {
private final Set<Reply> nextReplies;
private ReplyFlow(List<Predicate<Update>> conditions, Consumer<Update> action, Set<Reply> nextReplies) {
super(conditions, action);
private ReplyFlow(List<Predicate<Update>> conditions, Consumer<Update> action, Set<Reply> nextReplies, String name) {
super(conditions, action, name);
this.nextReplies = nextReplies;
}
@ -50,6 +50,7 @@ public class ReplyFlow extends Reply {
private List<Predicate<Update>> conds;
private Consumer<Update> action;
private Set<Reply> nextReplies;
private String name;
private ReplyFlowBuilder(DBContext db, int id) {
conds = new ArrayList<>();
@ -67,6 +68,11 @@ public class ReplyFlow extends Reply {
return this;
}
public ReplyFlowBuilder enableStats(String name) {
this.name = name;
return this;
}
public ReplyFlowBuilder onlyIf(Predicate<Update> pred) {
conds.add(pred);
return this;
@ -79,7 +85,7 @@ public class ReplyFlow extends Reply {
db.<Long, Integer>getMap(STATES).remove(chatId);
});
Reply statefulReply = Reply.of(statefulAction, statefulConditions);
Reply statefulReply = new Reply(statefulConditions, statefulAction, nextReply.name());
nextReplies.add(statefulReply);
return this;
}
@ -87,7 +93,7 @@ public class ReplyFlow extends Reply {
public ReplyFlowBuilder next(ReplyFlow nextReplyFlow) {
List<Predicate<Update>> statefulConditions = toStateful(nextReplyFlow.conditions());
ReplyFlow statefulReplyFlow = new ReplyFlow(statefulConditions, nextReplyFlow.action(), nextReplyFlow.nextReplies());
ReplyFlow statefulReplyFlow = new ReplyFlow(statefulConditions, nextReplyFlow.action(), nextReplyFlow.nextReplies(), nextReplyFlow.name());
nextReplies.add(statefulReplyFlow);
return this;
}
@ -95,12 +101,21 @@ public class ReplyFlow extends Reply {
public ReplyFlow build() {
if (action == null)
action = upd -> {};
Consumer<Update> statefulAction = action.andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).put(chatId, id);
});
return new ReplyFlow(conds, statefulAction, nextReplies);
Consumer<Update> statefulAction;
if (nextReplies.size() > 0) {
statefulAction = action.andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).put(chatId, id);
});
} else {
statefulAction = action.andThen(upd -> {
Long chatId = AbilityUtils.getChatId(upd);
db.<Long, Integer>getMap(STATES).remove(chatId);
});
}
return new ReplyFlow(conds, statefulAction, nextReplies, name);
}
@NotNull

View File

@ -17,6 +17,7 @@ import java.util.function.Predicate;
import static java.util.ResourceBundle.Control.FORMAT_PROPERTIES;
import static java.util.ResourceBundle.Control.getNoFallbackControl;
import static java.util.ResourceBundle.getBundle;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isEmpty;
import static org.telegram.abilitybots.api.objects.Flag.*;
@ -57,6 +58,10 @@ public final class AbilityUtils {
* @throws IllegalStateException if the user could not be found
*/
public static User getUser(Update update) {
return defaultIfNull(getUserElseThrow(update), EMPTY_USER);
}
private static User getUserElseThrow(Update update) {
if (MESSAGE.test(update)) {
return update.getMessage().getFrom();
} else if (CALLBACK_QUERY.test(update)) {
@ -199,16 +204,16 @@ public final class AbilityUtils {
return update -> update.getMessage().getReplyToMessage().getText().equals(msg);
}
public static String getLocalizedMessage(String messageCode, Locale locale, Object...arguments) {
public static String getLocalizedMessage(String messageCode, Locale locale, Object... arguments) {
ResourceBundle bundle;
if (locale == null) {
bundle = getBundle("messages", Locale.ROOT);
} else {
try {
bundle = getBundle(
"messages",
locale,
getNoFallbackControl(FORMAT_PROPERTIES));
"messages",
locale,
getNoFallbackControl(FORMAT_PROPERTIES));
} catch (MissingResourceException e) {
bundle = getBundle("messages", Locale.ROOT);
}
@ -217,7 +222,7 @@ public final class AbilityUtils {
return MessageFormat.format(message, arguments);
}
public static String getLocalizedMessage(String messageCode, String languageCode, Object...arguments){
public static String getLocalizedMessage(String messageCode, String languageCode, Object... arguments) {
Locale locale = Strings.isNullOrEmpty(languageCode) ? null : Locale.forLanguageTag(languageCode);
return getLocalizedMessage(messageCode, locale, arguments);
}
@ -247,8 +252,8 @@ public final class AbilityUtils {
* The full name is identified as the concatenation of the first and last name, separated by a space.
* This method can return an empty name if both first and last name are empty.
*
* @return the full name of the user
* @param user
* @return the full name of the user
*/
public static String fullName(User user) {
StringJoiner name = new StringJoiner(" ");
@ -262,6 +267,6 @@ public final class AbilityUtils {
}
public static String escape(String username) {
return username.replace("_", "\\_");
return username.replace("_", "\\_");
}
}

View File

@ -644,6 +644,21 @@ public class AbilityBotTest {
verify(silent, times(1)).send(expected, GROUP_ID);
}
@Test
void canProcessChannelPosts() {
Update update = mock(Update.class);
Message message = mock(Message.class);
when(message.getChatId()).thenReturn(1L);
when(update.getChannelPost()).thenReturn(message);
when(update.hasChannelPost()).thenReturn(true);
bot.onUpdateReceived(update);
String expected = "test channel post";
verify(silent, times(1)).send(expected, 1);
}
private void handlesAllUpdates(Consumer<Update> utilMethod) {
Arrays.stream(Update.class.getMethods())
// filter to all these methods of hasXXX (hasPoll, hasMessage, etc...)

View File

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

View File

@ -3,6 +3,7 @@ package org.telegram.abilitybots.api.bot;
import org.telegram.abilitybots.api.db.DBContext;
import org.telegram.abilitybots.api.objects.Ability;
import org.telegram.abilitybots.api.objects.Ability.AbilityBuilder;
import org.telegram.abilitybots.api.objects.Flag;
import org.telegram.abilitybots.api.objects.Reply;
import org.telegram.abilitybots.api.toggle.AbilityToggle;
@ -72,6 +73,12 @@ public class DefaultBot extends AbilityBot {
.build();
}
public Reply channelPostReply() {
return Reply.of(
upd -> silent.send("test channel post", upd.getChannelPost().getChatId()), Flag.CHANNEL_POST
);
}
public Ability testAbility() {
return getDefaultBuilder().build();
}

View File

@ -5,17 +5,18 @@ 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.Flag;
import org.telegram.abilitybots.api.objects.Reply;
import org.telegram.abilitybots.api.objects.ReplyFlow;
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.*;
@ -122,6 +123,33 @@ public class ReplyFlowTest {
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) {
@ -153,6 +181,43 @@ public class ReplyFlowTest {
.build();
}
public Reply errantReply() {
return Reply.of(
upd -> {
throw new RuntimeException("Throwing an exception inside the update consumer");
},
upd -> {
throw new RuntimeException("Throwing an exception inside the reply conditions (flags)");
});
}
public Ability replyFlowsWithAbility() {
Reply replyWithVk = ReplyFlow.builder(db, 2)
.enableStats("SECOND")
.action(upd -> {
silent.send("Second reply", upd.getMessage().getChatId());
})
.onlyIf(hasMessageWith("two"))
.build();
Reply replyWithNickname = ReplyFlow.builder(db, 1)
.enableStats("FIRST")
.action(upd -> {
silent.send("First reply", upd.getMessage().getChatId());
})
.onlyIf(hasMessageWith("one"))
.next(replyWithVk)
.build();
return Ability.builder()
.name("trigger")
.privacy(Privacy.PUBLIC)
.locality(Locality.ALL)
.action(ctx -> silent.send("I'm in an ability", ctx.chatId()))
.reply(replyWithNickname)
.build();
}
@NotNull
private Predicate<Update> hasMessageWith(String msg) {
return upd -> Flag.MESSAGE.test(upd) && upd.getMessage().getText().equalsIgnoreCase(msg);

View File

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

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambots-chat-session-bot</artifactId>
@ -84,7 +84,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->

View File

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

View File

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

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambotsextensions</artifactId>
@ -75,7 +75,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
</dependencies>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambots-meta</artifactId>

View File

@ -39,11 +39,13 @@ public class SendInvoice extends BotApiMethod<Message> {
private static final String NEED_PHONE_NUMBER_FIELD = "need_phone_number";
private static final String NEED_EMAIL_FIELD = "need_email";
private static final String NEED_SHIPPING_ADDRESS_FIELD = "need_shipping_address";
private static final String SEND_PHONE_NUMBER_TO_PROVIDER_FIELD = "send_phone_number_to_provider";
private static final String SEND_EMAIL_TO_PROVIDER_FIELD = "send_email_to_provider";
private static final String IS_FLEXIBLE_FIELD = "is_flexible";
private static final String DISABLE_NOTIFICATION_FIELD = "disable_notification";
private static final String REPLY_TO_MESSAGE_ID_FIELD = "reply_to_message_id";
private static final String REPLY_MARKUP_FIELD = "reply_markup";
private static final String PRIVIDER_DATA_FIELD = "provider_data";
private static final String PROVIDER_DATA_FIELD = "provider_data";
@JsonProperty(CHATID_FIELD)
private Integer chatId; ///< Unique identifier for the target private chat
@ -87,6 +89,11 @@ public class SendInvoice extends BotApiMethod<Message> {
private Boolean disableNotification; ///< Optional. Sends the message silently. Users will receive a notification with no sound.
@JsonProperty(REPLY_TO_MESSAGE_ID_FIELD)
private Integer replyToMessageId; ///< Optional. If the message is a reply, ID of the original message
@JsonProperty(SEND_PHONE_NUMBER_TO_PROVIDER_FIELD)
private Boolean sendPhoneNumberToProvider; ///< Optional. Pass True, if user's phone number should be sent to provider
@JsonProperty(SEND_EMAIL_TO_PROVIDER_FIELD)
private Boolean sendEmailToProvider; ///< Optional. Pass True, if user's email address should be sent to provider
/**
* Optional. A JSON-serialized object for an inline keyboard.
*
@ -99,11 +106,10 @@ public class SendInvoice extends BotApiMethod<Message> {
*
* @note A detailed description of required fields should be provided by the payment provider.
*/
@JsonProperty(PRIVIDER_DATA_FIELD)
@JsonProperty(PROVIDER_DATA_FIELD)
private String providerData;
/**
/**
* Build an empty SendInvoice object
*/
public SendInvoice() {
@ -277,6 +283,20 @@ public class SendInvoice extends BotApiMethod<Message> {
return this;
}
public Boolean getSendPhoneNumberToProvider() { return sendPhoneNumberToProvider; }
public SendInvoice setSendPhoneNumberToProvider(Boolean sendPhoneNumberToProvider) {
this.sendPhoneNumberToProvider = sendPhoneNumberToProvider;
return this;
}
public Boolean getSendEmailToProvider() { return sendEmailToProvider; }
public SendInvoice setSendEmailToProvider(Boolean sendEmailToProvider) {
this.sendEmailToProvider = sendEmailToProvider;
return this;
}
public Boolean getFlexible() {
return isFlexible;
}
@ -396,6 +416,8 @@ public class SendInvoice extends BotApiMethod<Message> {
", needPhoneNumber=" + needPhoneNumber +
", needEmail=" + needEmail +
", needShippingAddress=" + needShippingAddress +
", sendPhoneNumberToProvider=" + sendPhoneNumberToProvider +
", sendEmailToProvider=" + sendEmailToProvider +
", isFlexible=" + isFlexible +
", disableNotification=" + disableNotification +
", replyToMessageId=" + replyToMessageId +

View File

@ -18,14 +18,14 @@ Usage
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-spring-boot-starter</artifactId>
<version>4.1.2</version>
<version>4.9.1</version>
</dependency>
```
**Gradle**
```gradle
compile "org.telegram:telegrambots-spring-boot-starter:4.1.2"
compile "org.telegram:telegrambots-spring-boot-starter:4.9.1"
```
Motivation
@ -45,7 +45,7 @@ public class YourApplicationMainClass {
//Add this line to initialize bots context
ApiContextInitializer.init();
SpringApplication.run(MusicUploaderApplication.class, args);
SpringApplication.run(YourApplicationMainClass.class, args);
}
}
```

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambots-spring-boot-starter</artifactId>
@ -79,7 +79,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>org.telegram</groupId>
<artifactId>Bots</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</parent>
<artifactId>telegrambots</artifactId>
@ -95,7 +95,7 @@
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-meta</artifactId>
<version>4.9</version>
<version>4.9.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>