diff --git a/README.md b/README.md index 77d7a7ef..46836ab8 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,16 @@ Just import add the library to your project with one of these options: org.telegram telegrambots - 4.9 + 4.9.1 ``` ```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`. diff --git a/TelegramBots.wiki/Changelog.md b/TelegramBots.wiki/Changelog.md index e8efbbc5..15818304 100644 --- a/TelegramBots.wiki/Changelog.md +++ b/TelegramBots.wiki/Changelog.md @@ -1,3 +1,6 @@ +### 4.9.1 ### +1. Bug fixing: #767, #766, #761, #763, #776, #772, #771, #780 + ### 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 diff --git a/TelegramBots.wiki/FAQ.md b/TelegramBots.wiki/FAQ.md index 6de36ec0..5a94cbf4 100644 --- a/TelegramBots.wiki/FAQ.md +++ b/TelegramBots.wiki/FAQ.md @@ -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. ## 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); } } ``` diff --git a/TelegramBots.wiki/Getting-Started.md b/TelegramBots.wiki/Getting-Started.md index 24e4fa58..a22d6719 100644 --- a/TelegramBots.wiki/Getting-Started.md +++ b/TelegramBots.wiki/Getting-Started.md @@ -11,13 +11,13 @@ First you need ot get the library and add it to your project. There are few poss org.telegram telegrambots - 4.9 + 4.9.1 ``` * 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). diff --git a/TelegramBots.wiki/abilities/Advanced.md b/TelegramBots.wiki/abilities/Advanced.md index 45f90300..ec2f4617 100644 --- a/TelegramBots.wiki/abilities/Advanced.md +++ b/TelegramBots.wiki/abilities/Advanced.md @@ -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()` 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. \ No newline at end of file diff --git a/TelegramBots.wiki/abilities/Simple-Example.md b/TelegramBots.wiki/abilities/Simple-Example.md index bd234493..a6e2492f 100644 --- a/TelegramBots.wiki/abilities/Simple-Example.md +++ b/TelegramBots.wiki/abilities/Simple-Example.md @@ -9,12 +9,12 @@ As with any Java project, you will need to set your dependencies. org.telegram telegrambots-abilities - 4.9 + 4.9.1 ``` * **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) diff --git a/pom.xml b/pom.xml index e54ebacc..91026843 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots pom - 4.9 + 4.9.1 telegrambots diff --git a/telegrambots-abilities/README.md b/telegrambots-abilities/README.md index 496ba550..62f3579a 100644 --- a/telegrambots-abilities/README.md +++ b/telegrambots-abilities/README.md @@ -18,19 +18,19 @@ Usage org.telegram telegrambots-abilities - 4.9 + 4.9.1 ``` **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 ---------- diff --git a/telegrambots-abilities/pom.xml b/telegrambots-abilities/pom.xml index ec692783..de940fed 100644 --- a/telegrambots-abilities/pom.xml +++ b/telegrambots-abilities/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambots-abilities @@ -84,7 +84,7 @@ org.telegram telegrambots - 4.9 + 4.9.1 org.apache.commons diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java index 12894316..cdad9f55 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/AbilityBot.java @@ -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 */ diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java index c3bc3e31..317f222f 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/bot/BaseAbilityBot.java @@ -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 father of all ability bots. Bots that need to utilize abilities need to extend this bot. *

- * It's important to note that this bot strictly extends {@link TelegramLongPollingBot}. + * It's important to note that this bot strictly extends {@link DefaultAbsSender}. *

* All bots extending the {@link BaseAbilityBot} get implicit abilities: *

    @@ -130,35 +131,35 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability /** * @return the map of */ - protected Map users() { + public Map users() { return db.getMap(USERS); } /** * @return the map of */ - protected Map userIds() { + public Map userIds() { return db.getMap(USER_ID); } /** * @return a blacklist containing all the IDs of the banned users */ - protected Set blacklist() { + public Set blacklist() { return db.getSet(BLACKLIST); } /** * @return an admin set of all the IDs of bot administrators */ - protected Set admins() { + public Set admins() { return db.getSet(ADMINS); } /** * @return a mapping of ability and reply names to their corresponding statistics */ - protected Map stats() { + public Map 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. *

    @@ -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. *

    @@ -277,7 +316,8 @@ public abstract class BaseAbilityBot extends DefaultAbsSender implements Ability // Replies can be standalone or attached to abilities, fetch those too Stream 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 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 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 trio) { Ability ability = trio.b(); Update update = trio.a(); diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Reply.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Reply.java index e7479489..38677306 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Reply.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Reply.java @@ -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> conditions, Consumer action, String name) { + this(conditions, action); + if (Objects.nonNull(name)) { + enableStats(name); + } + } + public static Reply of(Consumer action, List> conditions) { return new Reply(conditions, action); } diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/ReplyFlow.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/ReplyFlow.java index 799a1a9b..5b0d8328 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/ReplyFlow.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/ReplyFlow.java @@ -20,8 +20,8 @@ public class ReplyFlow extends Reply { private final Set nextReplies; - private ReplyFlow(List> conditions, Consumer action, Set nextReplies) { - super(conditions, action); + private ReplyFlow(List> conditions, Consumer action, Set nextReplies, String name) { + super(conditions, action, name); this.nextReplies = nextReplies; } @@ -50,6 +50,7 @@ public class ReplyFlow extends Reply { private List> conds; private Consumer action; private Set 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 pred) { conds.add(pred); return this; @@ -79,7 +85,7 @@ public class ReplyFlow extends Reply { db.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> 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 statefulAction = action.andThen(upd -> { - Long chatId = AbilityUtils.getChatId(upd); - db.getMap(STATES).put(chatId, id); - }); - return new ReplyFlow(conds, statefulAction, nextReplies); + Consumer statefulAction; + if (nextReplies.size() > 0) { + statefulAction = action.andThen(upd -> { + Long chatId = AbilityUtils.getChatId(upd); + db.getMap(STATES).put(chatId, id); + }); + } else { + statefulAction = action.andThen(upd -> { + Long chatId = AbilityUtils.getChatId(upd); + db.getMap(STATES).remove(chatId); + }); + } + + return new ReplyFlow(conds, statefulAction, nextReplies, name); } @NotNull diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityUtils.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityUtils.java index 6382f866..6b8ca9ce 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityUtils.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityUtils.java @@ -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("_", "\\_"); } } \ No newline at end of file diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotTest.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotTest.java index 476b3b1e..f6823690 100644 --- a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotTest.java +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotTest.java @@ -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 utilMethod) { Arrays.stream(Update.class.getMethods()) // filter to all these methods of hasXXX (hasPoll, hasMessage, etc...) diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ContinuousTextTest.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ContinuousTextTest.java new file mode 100644 index 00000000..6e204625 --- /dev/null +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ContinuousTextTest.java @@ -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(); + } + } +} \ No newline at end of file diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/DefaultBot.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/DefaultBot.java index 462453da..7838d07f 100644 --- a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/DefaultBot.java +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/DefaultBot.java @@ -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(); } diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ReplyFlowTest.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ReplyFlowTest.java index bb5cd9e3..b117ab7a 100644 --- a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ReplyFlowTest.java +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/ReplyFlowTest.java @@ -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.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.getMap(STATES).containsKey(chatId), "User is still in a state"); + } + + @Test + void replyFlowsPertainNames() { + Set 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 hasMessageWith(String msg) { return upd -> Flag.MESSAGE.test(upd) && upd.getMessage().getText().equalsIgnoreCase(msg); diff --git a/telegrambots-chat-session-bot/README.md b/telegrambots-chat-session-bot/README.md index 4b80eb7d..1802bdd4 100644 --- a/telegrambots-chat-session-bot/README.md +++ b/telegrambots-chat-session-bot/README.md @@ -15,7 +15,7 @@ Usage org.telegram telegrambots-chat-session-bot - 4.9 + 4.9.1 ``` diff --git a/telegrambots-chat-session-bot/pom.xml b/telegrambots-chat-session-bot/pom.xml index 715e0224..a1291c1f 100644 --- a/telegrambots-chat-session-bot/pom.xml +++ b/telegrambots-chat-session-bot/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambots-chat-session-bot @@ -84,7 +84,7 @@ org.telegram telegrambots - 4.9 + 4.9.1 diff --git a/telegrambots-chat-session-bot/src/main/java/org/telegram/telegrambots/session/TelegramLongPollingSessionBot.java b/telegrambots-chat-session-bot/src/main/java/org/telegram/telegrambots/session/TelegramLongPollingSessionBot.java index 8182578e..1cceff89 100644 --- a/telegrambots-chat-session-bot/src/main/java/org/telegram/telegrambots/session/TelegramLongPollingSessionBot.java +++ b/telegrambots-chat-session-bot/src/main/java/org/telegram/telegrambots/session/TelegramLongPollingSessionBot.java @@ -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(); diff --git a/telegrambots-extensions/README.md b/telegrambots-extensions/README.md index 247bb691..998f63c4 100644 --- a/telegrambots-extensions/README.md +++ b/telegrambots-extensions/README.md @@ -16,12 +16,12 @@ Just import add the library to your project with one of these options: org.telegram telegrambotsextensions - 4.9 + 4.9.1 ``` 2. Using Gradle: ```gradle - compile "org.telegram:telegrambotsextensions:4.9" + compile "org.telegram:telegrambotsextensions:4.9.1" ``` \ No newline at end of file diff --git a/telegrambots-extensions/pom.xml b/telegrambots-extensions/pom.xml index 7fde69c6..e0fdc053 100644 --- a/telegrambots-extensions/pom.xml +++ b/telegrambots-extensions/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambotsextensions @@ -75,7 +75,7 @@ org.telegram telegrambots - 4.9 + 4.9.1 diff --git a/telegrambots-meta/pom.xml b/telegrambots-meta/pom.xml index 6a060936..c31ab809 100644 --- a/telegrambots-meta/pom.xml +++ b/telegrambots-meta/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambots-meta diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/methods/send/SendInvoice.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/methods/send/SendInvoice.java index 22dd87b1..5fa8debf 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/methods/send/SendInvoice.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/methods/send/SendInvoice.java @@ -39,11 +39,13 @@ public class SendInvoice extends BotApiMethod { 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 { 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 { * * @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 { 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 { ", needPhoneNumber=" + needPhoneNumber + ", needEmail=" + needEmail + ", needShippingAddress=" + needShippingAddress + + ", sendPhoneNumberToProvider=" + sendPhoneNumberToProvider + + ", sendEmailToProvider=" + sendEmailToProvider + ", isFlexible=" + isFlexible + ", disableNotification=" + disableNotification + ", replyToMessageId=" + replyToMessageId + diff --git a/telegrambots-spring-boot-starter/README.md b/telegrambots-spring-boot-starter/README.md index 86cafe15..dc6b0e03 100644 --- a/telegrambots-spring-boot-starter/README.md +++ b/telegrambots-spring-boot-starter/README.md @@ -18,14 +18,14 @@ Usage org.telegram telegrambots-spring-boot-starter - 4.1.2 + 4.9.1 ``` **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); } } ``` diff --git a/telegrambots-spring-boot-starter/pom.xml b/telegrambots-spring-boot-starter/pom.xml index b9d59a0a..7155c80e 100644 --- a/telegrambots-spring-boot-starter/pom.xml +++ b/telegrambots-spring-boot-starter/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambots-spring-boot-starter @@ -79,7 +79,7 @@ org.telegram telegrambots - 4.9 + 4.9.1 org.springframework.boot diff --git a/telegrambots/pom.xml b/telegrambots/pom.xml index 03123e30..66e70503 100644 --- a/telegrambots/pom.xml +++ b/telegrambots/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots - 4.9 + 4.9.1 telegrambots @@ -95,7 +95,7 @@ org.telegram telegrambots-meta - 4.9 + 4.9.1 com.fasterxml.jackson.core