diff --git a/README.md b/README.md index 45716860..dc9c13fd 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,12 @@ This library use [Telegram bot API](https://core.telegram.org/bots), you can fin ## Questions or Suggestions Feel free to create issues [here](https://github.com/rubenlagus/TelegramBots/issues) as you need or join the [chat](https://telegram.me/JavaBotsApi) +## Powered by Intellij +

+ +

+ + ## License MIT License diff --git a/TelegramBots.wiki/Using-Http-Proxy.md b/TelegramBots.wiki/Using-Http-Proxy.md index 502f145e..0b7d4c0a 100644 --- a/TelegramBots.wiki/Using-Http-Proxy.md +++ b/TelegramBots.wiki/Using-Http-Proxy.md @@ -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 { @@ -51,13 +51,12 @@ public class Main { TelegramBotsApi botsApi = new TelegramBotsApi(); // Set up Http proxy - DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class); + DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class); - 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 +88,26 @@ public class Main { public static void main(String[] args) { try { + // Create the Authenticator that will return auth's parameters for proxy authentication + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(PROXY_USER, PROXY_PASSWORD.toCharArray()); + } + }); + ApiContextInitializer.init(); // Create the TelegramBotsApi object to register your bots TelegramBotsApi botsApi = new TelegramBotsApi(); // Set up Http proxy - DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class); + DefaultBotOptions botOptions = ApiContext.getInstance(DefaultBotOptions.class); - 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 +119,6 @@ public class Main { } } } -``` \ No newline at end of file +``` + +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) diff --git a/TelegramBots.wiki/abilities/Simple-Example.md b/TelegramBots.wiki/abilities/Simple-Example.md index 3e2419e3..9b99679b 100644 --- a/TelegramBots.wiki/abilities/Simple-Example.md +++ b/TelegramBots.wiki/abilities/Simple-Example.md @@ -102,8 +102,9 @@ If you're in doubt that you're missing some code, the full code example can be i Go ahead and "/hello" to your bot. It should respond back with "Hello World!". Since you've implemented an AbilityBot, you get **factory abilities** as well. Try: -* /commands - Prints all commands supported by the bot - * This will essentially print "hello - says hello world!". Yes! This is the information we supplied to the ability. The bot prints the commands in the format accepted by BotFather. So, whenever you change, add or remove commands, you can simply /commands and forward that message to BotFather. +* /report - Prints all user-defined commands supported by the bot + * This will essentially print "hello - says hello world!". Yes! This is the information we supplied to the ability. The bot prints the commands in the format accepted by BotFather. So, whenever you change, add or remove commands, you can simply /report and forward that message to BotFather. +* /commands - Prints all commands exposed by the bot (factory and user-defined, with and without info) * /claim - Claims this bot * /backup - returns a backup of the bot database * /recover - recovers the database diff --git a/jetbrains.png b/jetbrains.png new file mode 100644 index 00000000..15024609 Binary files /dev/null and b/jetbrains.png differ diff --git a/pom.xml b/pom.xml index 4b5ea1dc..9bfeb45f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots pom - 3.6.1 + 3.6.2 telegrambots diff --git a/telegrambots-abilities/pom.xml b/telegrambots-abilities/pom.xml index 716b8bd6..9ccbcd17 100644 --- a/telegrambots-abilities/pom.xml +++ b/telegrambots-abilities/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots-abilities - 3.6.1 + 3.6.2 jar Telegram Ability Bot 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 686b186b..7057b3fa 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 @@ -1,6 +1,12 @@ 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; @@ -14,6 +20,7 @@ import org.telegram.telegrambots.meta.api.methods.groupadministration.GetChatAdm import org.telegram.telegrambots.meta.api.methods.send.SendDocument; import org.telegram.telegrambots.meta.api.objects.Message; import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.api.objects.User; import org.telegram.telegrambots.bots.DefaultBotOptions; import org.telegram.telegrambots.bots.TelegramLongPollingBot; import org.telegram.telegrambots.meta.exceptions.TelegramApiException; @@ -26,29 +33,31 @@ 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.function.Function.identity; import static java.util.regex.Pattern.CASE_INSENSITIVE; import static java.util.regex.Pattern.compile; -import static java.util.stream.Collectors.toList; -import static java.util.stream.Collectors.toMap; +import static 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.EndUser.fromUser; import static org.telegram.abilitybots.api.objects.Flag.*; import static org.telegram.abilitybots.api.objects.Locality.*; import static org.telegram.abilitybots.api.objects.MessageContext.newContext; import static org.telegram.abilitybots.api.objects.Privacy.*; +import static org.telegram.abilitybots.api.util.AbilityMessageCodes.*; import static org.telegram.abilitybots.api.util.AbilityUtils.*; /** @@ -63,10 +72,11 @@ import static org.telegram.abilitybots.api.util.AbilityUtils.*; *
  • Sets the user as the {@link Privacy#CREATOR} of the bot
  • *
  • Only the user with the ID returned by {@link AbilityBot#creatorId()} can genuinely claim the bot
  • * - *
  • /commands - reports all user-defined commands (abilities)
  • + *
  • /report - reports all user-defined commands (abilities)
  • * + *
  • /commands - returns a list of all possible bot commands based on the privacy of the requesting user
  • *
  • /backup - returns a backup of the bot database
  • *
  • /recover - recovers the database
  • *
  • /promote @username - promotes user to bot admin
  • @@ -101,10 +111,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot { protected static final String BACKUP = "backup"; protected static final String RECOVER = "recover"; protected static final String COMMANDS = "commands"; - - // Messages - protected static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached."; - protected static final String RECOVER_SUCCESS = "I have successfully recovered."; + protected static final String REPORT = "report"; // DB and sender protected final DBContext db; @@ -148,9 +155,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot { } /** - * @return the map of ID -> EndUser + * @return the map of ID -> User */ - protected Map users() { + protected Map users() { return db.getMap(USERS); } @@ -175,6 +182,20 @@ public abstract class AbilityBot extends TelegramLongPollingBot { return db.getSet(ADMINS); } + /** + * @return the immutable map of String -> Ability + */ + public Map abilities() { + return abilities; + } + + /** + * @return the immutable list carrying the embedded replies + */ + public List replies() { + return replies; + } + /** * This method contains the stream of actions that are applied on any update. *

    @@ -235,7 +256,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot { * @param username the username of the required user * @return the user */ - protected EndUser getUser(String username) { + 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)); @@ -250,26 +271,27 @@ public abstract class AbilityBot extends TelegramLongPollingBot { * @param id the id of the required user * @return the user */ - protected EndUser getUser(int id) { - EndUser endUser = users().get(id); - if (endUser == null) { + protected User getUser(int id) { + User user = users().get(id); + if (user == null) { throw new IllegalStateException(format("Could not find user corresponding to id [%d]", id)); } - return endUser; + 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, long chatId) { + protected int getUserIdSendError(String username, MessageContext ctx) { try { - return getUser(username).id(); + return getUser(username).getId(); } catch (IllegalStateException ex) { - silent.send(format("Sorry, I could not find the user [%s].", username), chatId); + silent.send(getLocalizedMessage(USER_NOT_FOUND, ctx.user().getLanguageCode(), username), ctx.chatId()); throw propagate(ex); } } @@ -292,9 +314,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot { */ public Ability reportCommands() { return builder() - .name(COMMANDS) + .name(REPORT) .locality(ALL) - .privacy(PUBLIC) + .privacy(CREATOR) .input(0) .action(ctx -> { String commands = abilities.entrySet().stream() @@ -306,7 +328,64 @@ public abstract class AbilityBot extends TelegramLongPollingBot { }) .sorted() .reduce((a, b) -> format("%s%n%s", a, b)) - .orElse("No public commands found."); + .orElse(getLocalizedMessage(ABILITY_COMMANDS_NOT_FOUND, ctx.user().getLanguageCode())); + + silent.send(commands, ctx.chatId()); + }) + .build(); + } + + /** + * Default format: + *

    + * PUBLIC + *

    + * [command1] - [description1] + *

    + * [command2] - [description2] + *

    + * GROUP_ADMIN + *

    + * [command1] - [description1] + *

    + * ... + * + * @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 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()); }) @@ -361,23 +440,27 @@ public abstract class AbilityBot extends TelegramLongPollingBot { .locality(USER) .privacy(CREATOR) .input(0) - .action(ctx -> silent.forceReply(RECOVERY_MESSAGE, ctx.chatId())) + .action(ctx -> silent.forceReply( + getLocalizedMessage(ABILITY_RECOVER_MESSAGE, ctx.user().getLanguageCode()), ctx.chatId())) .reply(update -> { - Long chatId = update.getMessage().getChatId(); - String fileId = update.getMessage().getDocument().getFileId(); + 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)) { - silent.send(RECOVER_SUCCESS, chatId); + send(ABILITY_RECOVER_SUCCESS, update); } else { - silent.send("Oops, something went wrong during recovery.", chatId); + send(ABILITY_RECOVER_FAIL, update); } } catch (Exception e) { BotLogger.error("Could not recover DB from backup", TAG, e); - silent.send("I have failed to recover.", chatId); + send(ABILITY_RECOVER_ERROR, update); } - }, MESSAGE, DOCUMENT, REPLY, isReplyTo(RECOVERY_MESSAGE)) + }, MESSAGE, DOCUMENT, REPLY) .build(); } @@ -398,23 +481,23 @@ public abstract class AbilityBot extends TelegramLongPollingBot { .input(1) .action(ctx -> { String username = stripTag(ctx.firstArg()); - int userId = getUserIdSendError(username, ctx.chatId()); + int userId = getUserIdSendError(username, ctx); String bannedUser; // Protection from abuse if (userId == creatorId()) { - userId = ctx.user().id(); - bannedUser = isNullOrEmpty(ctx.user().username()) ? addTag(ctx.user().username()) : ctx.user().shortName(); + userId = ctx.user().getId(); + bannedUser = isNullOrEmpty(ctx.user().getUserName()) ? addTag(ctx.user().getUserName()) : shortName(ctx.user()); } else { bannedUser = addTag(username); } Set blacklist = blacklist(); if (blacklist.contains(userId)) - silent.sendMd(format("%s is already *banned*.", escape(bannedUser)), ctx.chatId()); + sendMd(ABILITY_BAN_FAIL, ctx, escape(bannedUser)); else { blacklist.add(userId); - silent.sendMd(format("%s is now *banned*.", escape(bannedUser)), ctx.chatId()); + sendMd(ABILITY_BAN_SUCCESS, ctx, escape(bannedUser)); } }) .post(commitTo(db)) @@ -434,14 +517,14 @@ public abstract class AbilityBot extends TelegramLongPollingBot { .input(1) .action(ctx -> { String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx.chatId()); + Integer userId = getUserIdSendError(username, ctx); Set blacklist = blacklist(); if (!blacklist.remove(userId)) - silent.sendMd(format("@%s is *not* on the *blacklist*.", escape(username)), ctx.chatId()); + silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_FAIL, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); else { - silent.sendMd(format("@%s, your ban has been *lifted*.", escape(username)), ctx.chatId()); + silent.sendMd(getLocalizedMessage(ABILITY_UNBAN_SUCCESS, ctx.user().getLanguageCode(), escape(username)), ctx.chatId()); } }) .post(commitTo(db)) @@ -459,14 +542,14 @@ public abstract class AbilityBot extends TelegramLongPollingBot { .input(1) .action(ctx -> { String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx.chatId()); + Integer userId = getUserIdSendError(username, ctx); Set admins = admins(); if (admins.contains(userId)) - silent.sendMd(format("@%s is already an *admin*.", escape(username)), ctx.chatId()); + sendMd(ABILITY_PROMOTE_FAIL, ctx, escape(username)); else { admins.add(userId); - silent.sendMd(format("@%s has been *promoted*.", escape(username)), ctx.chatId()); + sendMd(ABILITY_PROMOTE_SUCCESS, ctx, escape(username)); } }).post(commitTo(db)) .build(); @@ -483,13 +566,13 @@ public abstract class AbilityBot extends TelegramLongPollingBot { .input(1) .action(ctx -> { String username = stripTag(ctx.firstArg()); - Integer userId = getUserIdSendError(username, ctx.chatId()); + Integer userId = getUserIdSendError(username, ctx); Set admins = admins(); if (admins.remove(userId)) { - silent.sendMd(format("@%s has been *demoted*.", escape(username)), ctx.chatId()); + sendMd(ABILITY_DEMOTE_SUCCESS, ctx, escape(username)); } else { - silent.sendMd(format("@%s is *not* an *admin*.", escape(username)), ctx.chatId()); + sendMd(ABILITY_DEMOTE_FAIL, ctx, escape(username)); } }) .post(commitTo(db)) @@ -505,29 +588,36 @@ public abstract class AbilityBot extends TelegramLongPollingBot { return builder() .name(CLAIM) .locality(ALL) - .privacy(PUBLIC) + .privacy(CREATOR) .input(0) .action(ctx -> { - if (ctx.user().id() == creatorId()) { - Set admins = admins(); - int id = creatorId(); - long chatId = ctx.chatId(); + Set admins = admins(); + int id = creatorId(); - if (admins.contains(id)) - silent.send("You're already my master.", chatId); - else { - admins.add(id); - silent.send("You're now my master.", chatId); - } - } else { - // This is not a joke - abilities.get(BAN).action().accept(newContext(ctx.update(), ctx.user(), ctx.chatId(), ctx.user().username())); + if (admins.contains(id)) + send(ABILITY_CLAIM_FAIL, ctx); + else { + admins.add(id); + send(ABILITY_CLAIM_SUCCESS, ctx); } }) .post(commitTo(db)) .build(); } + private Optional send(String message, MessageContext ctx, String... args) { + return silent.send(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); + } + + private Optional sendMd(String message, MessageContext ctx, String... args) { + return silent.sendMd(getLocalizedMessage(message, ctx.user().getLanguageCode(), args), ctx.chatId()); + } + + private Optional 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. *

    @@ -538,7 +628,10 @@ public abstract class AbilityBot extends TelegramLongPollingBot { abilities = stream(this.getClass().getMethods()) .filter(method -> method.getReturnType().equals(Ability.class)) .map(this::returnAbility) - .collect(toMap(ability -> ability.name().toLowerCase(), identity())); + .collect(ImmutableMap::builder, + (b, a) -> b.put(a.name(), a), + (b1, b2) -> b1.putAll(b2.build())) + .build(); Stream methodReplies = stream(this.getClass().getMethods()) .filter(method -> method.getReturnType().equals(Reply.class)) @@ -547,7 +640,11 @@ public abstract class AbilityBot extends TelegramLongPollingBot { Stream abilityReplies = abilities.values().stream() .flatMap(ability -> ability.replies().stream()); - replies = Stream.concat(methodReplies, abilityReplies).collect(toList()); + replies = Stream.concat(methodReplies, abilityReplies).collect( + ImmutableList::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); @@ -597,7 +694,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot { Pair getContext(Trio trio) { Update update = trio.a(); - EndUser user = fromUser(AbilityUtils.getUser(update)); + User user = AbilityUtils.getUser(update); return Pair.of(newContext(update, user, getChatId(update), trio.c()), trio.b()); } @@ -615,7 +712,12 @@ public abstract class AbilityBot extends TelegramLongPollingBot { boolean isOk = abilityTokens == 0 || (tokens.length > 0 && tokens.length == abilityTokens); if (!isOk) - silent.send(format("Sorry, this feature requires %d additional %s.", abilityTokens, abilityTokens == 1 ? "input" : "inputs"), getChatId(trio.a())); + silent.send( + getLocalizedMessage( + CHECK_INPUT_FAIL, + AbilityUtils.getUser(trio.a()).getLanguageCode(), + abilityTokens, abilityTokens == 1 ? "input" : "inputs"), + getChatId(trio.a())); return isOk; } @@ -627,30 +729,46 @@ public abstract class AbilityBot extends TelegramLongPollingBot { boolean isOk = abilityLocality == ALL || locality == abilityLocality; if (!isOk) - silent.send(format("Sorry, %s-only feature.", abilityLocality.toString().toLowerCase()), getChatId(trio.a())); + silent.send( + getLocalizedMessage( + CHECK_LOCALITY_FAIL, + AbilityUtils.getUser(trio.a()).getLanguageCode(), + abilityLocality.toString().toLowerCase()), + getChatId(trio.a())); return isOk; } boolean checkPrivacy(Trio trio) { Update update = trio.a(); - EndUser user = fromUser(AbilityUtils.getUser(update)); + User user = AbilityUtils.getUser(update); Privacy privacy; - int id = user.id(); + int id = user.getId(); - privacy = isCreator(id) ? CREATOR : isAdmin(id) ? ADMIN : isGroupAdmin(update, id)? GROUP_ADMIN : PUBLIC; + privacy = getPrivacy(update, id); boolean isOk = privacy.compareTo(trio.b().privacy()) >= 0; if (!isOk) - silent.send("Sorry, you don't have the required access level to do that.", getChatId(trio.a())); - + 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 isGroupUpdate(update) && silent.execute(admins) + return silent.execute(admins) .orElse(new ArrayList<>()).stream() .anyMatch(member -> member.getUser().getId() == id); } @@ -694,9 +812,9 @@ public abstract class AbilityBot extends TelegramLongPollingBot { } Update addUser(Update update) { - EndUser endUser = fromUser(AbilityUtils.getUser(update)); + User endUser = AbilityUtils.getUser(update); - users().compute(endUser.id(), (id, user) -> { + users().compute(endUser.getId(), (id, user) -> { if (user == null) { updateUserId(user, endUser); return endUser; @@ -714,15 +832,15 @@ public abstract class AbilityBot extends TelegramLongPollingBot { return update; } - private void updateUserId(EndUser oldUser, EndUser newUser) { - if (oldUser != null && oldUser.username() != null) { + private void updateUserId(User oldUser, User newUser) { + if (oldUser != null && oldUser.getUserName() != null) { // Remove old username -> ID - userIds().remove(oldUser.username()); + userIds().remove(oldUser.getUserName()); } - if (newUser.username() != null) { + if (newUser.getUserName() != null) { // Add new mapping with the new username - userIds().put(newUser.username().toLowerCase(), newUser.id()); + userIds().put(newUser.getUserName().toLowerCase(), newUser.getId()); } } @@ -750,6 +868,7 @@ public abstract class AbilityBot extends TelegramLongPollingBot { return sender.downloadFile(sender.execute(new GetFile().setFileId(fileId))); } + private String escape(String username) { return username.replace("_", "\\_"); } diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java index c31a856f..f29e94dc 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/DBContext.java @@ -39,6 +39,13 @@ public interface DBContext extends Closeable { */ Set getSet(String name); + /** + * @param name the unique name of the {@link Var} + * @param the type that the variable holds + * @return the variable with the specified name + */ + Var getVar(String name); + /** * @return a high-level summary of the database structures (Sets, Lists, Maps, ...) present. */ diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java index 37707aed..a8f3db16 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBContext.java @@ -3,6 +3,7 @@ package org.telegram.abilitybots.api.db; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import org.mapdb.Atomic; import org.mapdb.DB; import org.mapdb.DBMaker; import org.mapdb.Serializer; @@ -93,6 +94,11 @@ public class MapDBContext implements DBContext { return (Set) db.hashSet(name, JAVA).createOrOpen(); } + @Override + public Var getVar(String name) { + return new MapDBVar<>((Atomic.Var) db.atomicVar(name).createOrOpen()); + } + @Override public String summary() { return stream(db.getAllNames().spliterator(), false) diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBVar.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBVar.java new file mode 100644 index 00000000..a325fd36 --- /dev/null +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/MapDBVar.java @@ -0,0 +1,49 @@ +package org.telegram.abilitybots.api.db; + +import com.google.common.base.MoreObjects; +import org.mapdb.Atomic; + +import java.util.Objects; + +/** + * The MapDB variant for {@link DBContext#getVar(String)}. + * + * @param the type of the inner variable + */ +public final class MapDBVar implements Var { + private Atomic.Var var; + + public MapDBVar(Atomic.Var var) { + this.var = var; + } + + @Override + public T get() { + return var.get(); + } + + @Override + public void set(T var) { + this.var.set(var); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MapDBVar mapDBVar = (MapDBVar) o; + return Objects.equals(var, mapDBVar.var); + } + + @Override + public int hashCode() { + return Objects.hash(var); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("var", var) + .toString(); + } +} diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/Var.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/Var.java new file mode 100644 index 00000000..b6ec4593 --- /dev/null +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/db/Var.java @@ -0,0 +1,19 @@ +package org.telegram.abilitybots.api.db; + +/** + * The interface governing a variable for abstract getters and setters. + * @param the type of the variable + * + * @author Abbas Abou Daya + */ +public interface Var { + /** + * @return the variable contained + */ + T get(); + + /** + * @param var the new variable value + */ + void set(T var); +} diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/EndUser.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/EndUser.java deleted file mode 100644 index f686e38f..00000000 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/EndUser.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.telegram.abilitybots.api.objects; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.common.base.MoreObjects; -import org.telegram.telegrambots.meta.api.objects.User; - -import java.io.Serializable; -import java.util.Objects; -import java.util.StringJoiner; - -import static org.apache.commons.lang3.StringUtils.isEmpty; - -/** - * This class serves the purpose of separating the basic Telegram {@link User} and the augmented {@link EndUser}. - *

    - * It adds proper hashCode, equals, toString as well as useful utility methods such as {@link EndUser#shortName} and {@link EndUser#fullName}. - * - * @author Abbas Abou Daya - */ -public final class EndUser implements Serializable { - @JsonProperty("id") - private final Integer id; - @JsonProperty("firstName") - private final String firstName; - @JsonProperty("lastName") - private final String lastName; - @JsonProperty("username") - private final String username; - - private EndUser(Integer id, String firstName, String lastName, String username) { - this.id = id; - this.firstName = firstName; - this.lastName = lastName; - this.username = username; - } - - @JsonCreator - public static EndUser endUser(@JsonProperty("id") Integer id, - @JsonProperty("firstName") String firstName, - @JsonProperty("lastName") String lastName, - @JsonProperty("username") String username) { - return new EndUser(id, firstName, lastName, username); - } - - /** - * Constructs an {@link EndUser} from a {@link User}. - * - * @param user the Telegram user - * @return an augmented end-user - */ - public static EndUser fromUser(User user) { - return new EndUser(user.getId(), user.getFirstName(), user.getLastName(), user.getUserName()); - } - - public int id() { - return id; - } - - public String firstName() { - return firstName; - } - - public String lastName() { - return lastName; - } - - public String username() { - return username; - } - - /** - * The full name is identified as the concatenation of the first and last name, separated by a space. - * This method can return an empty name if both first and last name are empty. - * - * @return the full name of the user - */ - public String fullName() { - StringJoiner name = new StringJoiner(" "); - - if (!isEmpty(firstName)) - name.add(firstName); - if (!isEmpty(lastName)) - name.add(lastName); - - return name.toString(); - } - - /** - * The short name is one of the following: - *

      - *
    1. First name
    2. - *
    3. Last name
    4. - *
    5. Username
    6. - *
    - * The method will try to return the first valid name in the specified order. - * - * @return the short name of the user - */ - public String shortName() { - if (!isEmpty(firstName)) - return firstName; - - if (!isEmpty(lastName)) - return lastName; - - return username; - } - - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - - EndUser endUser = (EndUser) o; - return Objects.equals(id, endUser.id) && - Objects.equals(firstName, endUser.firstName) && - Objects.equals(lastName, endUser.lastName) && - Objects.equals(username, endUser.username); - } - - @Override - public int hashCode() { - return Objects.hash(id, firstName, lastName, username); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("id", id) - .add("firstName", firstName) - .add("lastName", lastName) - .add("username", username) - .toString(); - } -} diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Flag.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Flag.java index ac8e9613..f59cc25c 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Flag.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/Flag.java @@ -11,7 +11,7 @@ import static java.util.Objects.nonNull; /** * Flags are an conditions that are applied on an {@link Update}. *

    - * They can be used on {@link AbilityBuilder#flag(Flag...)} and on the post conditions in {@link AbilityBuilder#reply(Consumer, Predicate[])}. + * They can be used on {@link AbilityBuilder#flag(Predicate[])} and on the post conditions in {@link AbilityBuilder#reply(Consumer, Predicate[])}. * * @author Abbas Abou Daya */ diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/MessageContext.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/MessageContext.java index c68a2868..c9fc2398 100644 --- a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/MessageContext.java +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/objects/MessageContext.java @@ -3,6 +3,7 @@ package org.telegram.abilitybots.api.objects; import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import org.telegram.telegrambots.meta.api.objects.Update; +import org.telegram.telegrambots.meta.api.objects.User; import java.util.Arrays; @@ -14,26 +15,26 @@ import java.util.Arrays; * @author Abbas Abou Daya */ public class MessageContext { - private final EndUser user; + private final User user; private final Long chatId; private final String[] arguments; private final Update update; - private MessageContext(Update update, EndUser user, Long chatId, String[] arguments) { + private MessageContext(Update update, User user, Long chatId, String[] arguments) { this.user = user; this.chatId = chatId; this.update = update; this.arguments = arguments; } - public static MessageContext newContext(Update update, EndUser user, Long chatId, String... arguments) { + public static MessageContext newContext(Update update, User user, Long chatId, String... arguments) { return new MessageContext(update, user, chatId, arguments); } /** * @return the originating Telegram user of this update */ - public EndUser user() { + public User user() { return user; } 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 0d344ce2..a74e4e2f 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 @@ -14,7 +14,7 @@ import static java.util.Arrays.asList; /** * A reply consists of update conditionals and an action to be applied on the update. *

    - * If an update satisfies the {@link Reply#conditions}set by the reply, then it's safe to {@link Reply#actOn(Update)}. + * If an update satisfies the {@link Reply#conditions} set by the reply, then it's safe to {@link Reply#actOn(Update)}. * * @author Abbas Abou Daya */ diff --git a/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityMessageCodes.java b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityMessageCodes.java new file mode 100644 index 00000000..4f21f937 --- /dev/null +++ b/telegrambots-abilities/src/main/java/org/telegram/abilitybots/api/util/AbilityMessageCodes.java @@ -0,0 +1,31 @@ +package org.telegram.abilitybots.api.util; + +public final class AbilityMessageCodes { + public static String USER_NOT_FOUND = "userNotFound"; + public static String CHECK_INPUT_FAIL = "checkInput.fail"; + public static String CHECK_LOCALITY_FAIL = "checkLocality.fail"; + public static String CHECK_PRIVACY_FAIL = "checkPrivacy.fail"; + + public static String ABILITY_COMMANDS_NOT_FOUND = "ability.commands.notFound"; + + public static String ABILITY_RECOVER_SUCCESS = "ability.recover.success"; + public static String ABILITY_RECOVER_FAIL = "ability.recover.fail"; + public static String ABILITY_RECOVER_MESSAGE = "ability.recover.message"; + public static String ABILITY_RECOVER_ERROR = "ability.recover.error"; + + public static String ABILITY_BAN_SUCCESS = "ability.ban.success"; + public static String ABILITY_BAN_FAIL = "ability.ban.fail"; + + public static String ABILITY_UNBAN_SUCCESS = "ability.unban.success"; + public static String ABILITY_UNBAN_FAIL = "ability.unban.fail"; + + public static String ABILITY_PROMOTE_SUCCESS = "ability.promote.success"; + public static String ABILITY_PROMOTE_FAIL = "ability.promote.fail"; + + public static String ABILITY_DEMOTE_SUCCESS = "ability.demote.success"; + public static String ABILITY_DEMOTE_FAIL = "ability.demote.fail"; + + public static String ABILITY_CLAIM_SUCCESS = "ability.claim.success"; + public static String ABILITY_CLAIM_FAIL = "ability.claim.fail"; + +} 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 a6d01324..749fd4a7 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 @@ -1,13 +1,23 @@ 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.meta.api.objects.Update; import org.telegram.telegrambots.meta.api.objects.User; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.StringJoiner; import java.util.function.Consumer; import java.util.function.Predicate; +import static java.util.ResourceBundle.Control.FORMAT_PROPERTIES; +import static java.util.ResourceBundle.Control.getNoFallbackControl; +import static java.util.ResourceBundle.getBundle; +import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.telegram.abilitybots.api.objects.Flag.*; /** @@ -86,6 +96,28 @@ public final class AbilityUtils { } } + /** + * A "best-effort" boolean stating whether the update is a super-group message or not. + * + * @param update a Telegram {@link Update} + * @return whether the update is linked to a group + */ + public static boolean isSuperGroupUpdate(Update update) { + if (MESSAGE.test(update)) { + return update.getMessage().isSuperGroupMessage(); + } else if (CALLBACK_QUERY.test(update)) { + return update.getCallbackQuery().getMessage().isSuperGroupMessage(); + } else if (CHANNEL_POST.test(update)) { + return update.getChannelPost().isSuperGroupMessage(); + } else if (EDITED_CHANNEL_POST.test(update)) { + return update.getEditedChannelPost().isSuperGroupMessage(); + } else if (EDITED_MESSAGE.test(update)) { + return update.getEditedMessage().isSuperGroupMessage(); + } else { + return false; + } + } + /** * Fetches the direct chat ID of the specified update. * @@ -150,4 +182,66 @@ public final class AbilityUtils { public static Predicate isReplyTo(String msg) { return update -> update.getMessage().getReplyToMessage().getText().equals(msg); } -} + + public static String getLocalizedMessage(String messageCode, Locale locale, Object...arguments) { + ResourceBundle bundle; + if (locale == null) { + bundle = getBundle("messages", Locale.ROOT); + } else { + try { + bundle = getBundle( + "messages", + locale, + getNoFallbackControl(FORMAT_PROPERTIES)); + } catch (MissingResourceException e) { + bundle = getBundle("messages", Locale.ROOT); + } + } + String message = bundle.getString(messageCode); + return MessageFormat.format(message, arguments); + } + + public static String getLocalizedMessage(String messageCode, String languageCode, Object...arguments){ + Locale locale = Strings.isNullOrEmpty(languageCode) ? null : Locale.forLanguageTag(languageCode); + return getLocalizedMessage(messageCode, locale, arguments); + } + + /** + * The short name is one of the following: + *

      + *
    1. First name
    2. + *
    3. Last name
    4. + *
    5. Username
    6. + *
    + * The method will try to return the first valid name in the specified order. + * + * @return the short name of the user + */ + public static String shortName(User user) { + if (!isEmpty(user.getFirstName())) + return user.getFirstName(); + + if (!isEmpty(user.getLastName())) + return user.getLastName(); + + return user.getUserName(); + } + + /** + * The full name is identified as the concatenation of the first and last name, separated by a space. + * This method can return an empty name if both first and last name are empty. + * + * @return the full name of the user + * @param user + */ + public static String fullName(User user) { + StringJoiner name = new StringJoiner(" "); + + if (!isEmpty(user.getFirstName())) + name.add(user.getFirstName()); + if (!isEmpty(user.getLastName())) + name.add(user.getLastName()); + + return name.toString(); + } +} \ No newline at end of file diff --git a/telegrambots-abilities/src/main/resources/messages.properties b/telegrambots-abilities/src/main/resources/messages.properties new file mode 100644 index 00000000..57e2485b --- /dev/null +++ b/telegrambots-abilities/src/main/resources/messages.properties @@ -0,0 +1,27 @@ +ability.commands.notFound=No available commands found. + +ability.recover.success=I have successfully recovered. +ability.recover.fail=Oops, something went wrong during recovery. +ability.recover.message=I am ready to receive the backup file. Please reply to this message with the backup file attached. +ability.recover.error=I have failed to recover. + +ability.ban.success={0} is now *banned*. +ability.ban.fail={0} is already *banned*. + +ability.unban.success=@{0}, your ban has been *lifted*. +ability.unban.fail=@{0} is *not* on the *blacklist*. + +ability.promote.success=@{0} has been *promoted*. +ability.promote.fail=@{0} is already an *admin*. + +ability.demote.success=@{0} has been *demoted*. +ability.demote.fail=@{0} is *not* an *admin*. + +ability.claim.success=You''re now my master. +ability.claim.fail=You''re already my master. + +checkInput.fail=Sorry, this feature requires {0,number,integer} additional {1}. +checkLocality.fail=Sorry, {0}-only feature. +checkPrivacy.fail=Sorry, you don''t have the required access level to do that. + +userNotFound=Sorry, I could not find the user [{0}]. \ No newline at end of file diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotI18nTest.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotI18nTest.java new file mode 100644 index 00000000..fd1b75f9 --- /dev/null +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/bot/AbilityBotI18nTest.java @@ -0,0 +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.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"); + + private DBContext db; + private NoPublicCommandsBot bot; + + private MessageSender sender; + private SilentSender silent; + + @Before + public void setUp() { + db = offlineInstance("db"); + bot = new NoPublicCommandsBot(EMPTY, EMPTY, db); + + sender = mock(MessageSender.class); + silent = mock(SilentSender.class); + + bot.sender = sender; + bot.silent = silent; + + } + + @Test + public void missingPublicCommandsLocalizedInEnglishByDefault() { + MessageContext context = mockContext(NO_LANGUAGE_USER); + + bot.reportCommands().action().accept(context); + + verify(silent, times(1)) + .send("No available commands found.", NO_LANGUAGE_USER.getId()); + } + + @Test + public void missingPublicCommandsLocalizedInItalian() { + MessageContext context = mockContext(ITALIAN_USER); + + bot.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) { + super(botToken, botUsername, db); + } + + @Override + public int creatorId() { + return 1; + } + } +} 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 e4c0598b..7ad96030 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 @@ -36,11 +36,8 @@ import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.*; import static org.mockito.internal.verification.VerificationModeFactory.times; -import static org.telegram.abilitybots.api.bot.AbilityBot.RECOVERY_MESSAGE; -import static org.telegram.abilitybots.api.bot.AbilityBot.RECOVER_SUCCESS; import static org.telegram.abilitybots.api.bot.DefaultBot.getDefaultBuilder; import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance; -import static org.telegram.abilitybots.api.objects.EndUser.endUser; import static org.telegram.abilitybots.api.objects.Flag.DOCUMENT; import static org.telegram.abilitybots.api.objects.Flag.MESSAGE; import static org.telegram.abilitybots.api.objects.Locality.ALL; @@ -49,12 +46,16 @@ import static org.telegram.abilitybots.api.objects.MessageContext.newContext; import static org.telegram.abilitybots.api.objects.Privacy.*; public class AbilityBotTest { + // Messages + private static final String RECOVERY_MESSAGE = "I am ready to receive the backup file. Please reply to this message with the backup file attached."; + private static final String RECOVER_SUCCESS = "I have successfully recovered."; + private static final String[] EMPTY_ARRAY = {}; private static final long GROUP_ID = 10L; private static final String TEST = "test"; private static final String[] TEXT = {TEST}; - public static final EndUser MUSER = endUser(1, "first", "last", "username"); - public static final EndUser CREATOR = endUser(1337, "creatorFirst", "creatorLast", "creatorUsername"); + 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 DBContext db; @@ -75,39 +76,39 @@ public class AbilityBotTest { @Test public void sendsPrivacyViolation() { - Update update = mockFullUpdate(MUSER, "/admin"); + Update update = mockFullUpdate(USER, "/admin"); bot.onUpdateReceived(update); - verify(silent, times(1)).send("Sorry, you don't have the required access level to do that.", MUSER.id()); + verify(silent, times(1)).send("Sorry, you don't have the required access level to do that.", USER.getId()); } @Test public void sendsLocalityViolation() { - Update update = mockFullUpdate(MUSER, "/group"); + Update update = mockFullUpdate(USER, "/group"); bot.onUpdateReceived(update); - verify(silent, times(1)).send(format("Sorry, %s-only feature.", "group"), MUSER.id()); + verify(silent, times(1)).send(format("Sorry, %s-only feature.", "group"), USER.getId()); } @Test public void sendsInputArgsViolation() { - Update update = mockFullUpdate(MUSER, "/count 1 2 3"); + Update update = mockFullUpdate(USER, "/count 1 2 3"); bot.onUpdateReceived(update); - verify(silent, times(1)).send(format("Sorry, this feature requires %d additional inputs.", 4), MUSER.id()); + verify(silent, times(1)).send(format("Sorry, this feature requires %d additional inputs.", 4), USER.getId()); } @Test public void canProcessRepliesIfSatisfyRequirements() { - Update update = mockFullUpdate(MUSER, "must reply"); + Update update = mockFullUpdate(USER, "must reply"); // False means the update was not pushed down the stream since it has been consumed by the reply assertFalse(bot.filterReply(update)); - verify(silent, times(1)).send("reply", MUSER.id()); + verify(silent, times(1)).send("reply", USER.getId()); } @Test @@ -144,8 +145,8 @@ public class AbilityBotTest { @Test public void canDemote() { - addUsers(MUSER); - bot.admins().add(MUSER.id()); + addUsers(USER); + bot.admins().add(USER.getId()); MessageContext context = defaultContext(); @@ -158,33 +159,33 @@ public class AbilityBotTest { @Test public void canPromote() { - addUsers(MUSER); + addUsers(USER); MessageContext context = defaultContext(); bot.promoteAdmin().action().accept(context); Set actual = bot.admins(); - Set expected = newHashSet(MUSER.id()); + Set expected = newHashSet(USER.getId()); assertEquals("Could not sudo user", expected, actual); } @Test public void canBanUser() { - addUsers(MUSER); + addUsers(USER); MessageContext context = defaultContext(); bot.banUser().action().accept(context); Set actual = bot.blacklist(); - Set expected = newHashSet(MUSER.id()); + Set expected = newHashSet(USER.getId()); assertEquals("The ban was not emplaced", expected, actual); } @Test public void canUnbanUser() { - addUsers(MUSER); - bot.blacklist().add(MUSER.id()); + addUsers(USER); + bot.blacklist().add(USER.getId()); MessageContext context = defaultContext(); @@ -197,65 +198,42 @@ public class AbilityBotTest { @NotNull private MessageContext defaultContext() { - MessageContext context = mock(MessageContext.class); - when(context.user()).thenReturn(CREATOR); - when(context.firstArg()).thenReturn(MUSER.username()); - return context; + return mockContext(CREATOR, GROUP_ID, USER.getUserName()); } @Test public void cannotBanCreator() { - addUsers(MUSER, CREATOR); - MessageContext context = mock(MessageContext.class); - when(context.user()).thenReturn(MUSER); - when(context.firstArg()).thenReturn(CREATOR.username()); + addUsers(USER, CREATOR); + MessageContext context = mockContext(USER, GROUP_ID, CREATOR.getUserName()); bot.banUser().action().accept(context); Set actual = bot.blacklist(); - Set expected = newHashSet(MUSER.id()); + Set expected = newHashSet(USER.getId()); assertEquals("Impostor was not added to the blacklist", expected, actual); } - private void addUsers(EndUser... users) { + private void addUsers(User... users) { Arrays.stream(users).forEach(user -> { - bot.users().put(user.id(), user); - bot.userIds().put(user.username().toLowerCase(), user.id()); + bot.users().put(user.getId(), user); + bot.userIds().put(user.getUserName().toLowerCase(), user.getId()); }); } @Test public void creatorCanClaimBot() { - MessageContext context = mock(MessageContext.class); - when(context.user()).thenReturn(CREATOR); + MessageContext context = mockContext(CREATOR, GROUP_ID); bot.claimCreator().action().accept(context); Set actual = bot.admins(); - Set expected = newHashSet(CREATOR.id()); + Set expected = newHashSet(CREATOR.getId()); assertEquals("Creator was not properly added to the super admins set", expected, actual); } - @Test - public void userGetsBannedIfClaimsBot() { - addUsers(MUSER); - MessageContext context = mock(MessageContext.class); - when(context.user()).thenReturn(MUSER); - - bot.claimCreator().action().accept(context); - - Set actual = bot.blacklist(); - Set expected = newHashSet(MUSER.id()); - assertEquals("Could not find user on the blacklist", expected, actual); - - actual = bot.admins(); - expected = emptySet(); - assertEquals("Admins set is not empty", expected, actual); - } - @Test public void bannedCreatorPassesBlacklistCheck() { - bot.blacklist().add(CREATOR.id()); + bot.blacklist().add(CREATOR.getId()); Update update = mock(Update.class); Message message = mock(Message.class); User user = mock(User.class); @@ -270,37 +248,35 @@ public class AbilityBotTest { public void canAddUser() { Update update = mock(Update.class); Message message = mock(Message.class); - User user = mock(User.class); - mockAlternateUser(update, message, user, MUSER); + mockAlternateUser(update, message, USER); bot.addUser(update); - Map expectedUserIds = ImmutableMap.of(MUSER.username(), MUSER.id()); - Map expectedUsers = ImmutableMap.of(MUSER.id(), MUSER); + Map expectedUserIds = ImmutableMap.of(USER.getUserName(), USER.getId()); + Map expectedUsers = ImmutableMap.of(USER.getId(), USER); assertEquals("User was not added", expectedUserIds, bot.userIds()); assertEquals("User was not added", expectedUsers, bot.users()); } @Test public void canEditUser() { - addUsers(MUSER); + addUsers(USER); Update update = mock(Update.class); Message message = mock(Message.class); - User user = mock(User.class); - String newUsername = MUSER.username() + "-test"; - String newFirstName = MUSER.firstName() + "-test"; - String newLastName = MUSER.lastName() + "-test"; - int sameId = MUSER.id(); - EndUser changedUser = endUser(sameId, newFirstName, newLastName, newUsername); + String newUsername = USER.getUserName() + "-test"; + String newFirstName = USER.getFirstName() + "-test"; + String newLastName = USER.getLastName() + "-test"; + int sameId = USER.getId(); + User changedUser = new User(sameId, newFirstName, false, newLastName, newUsername, null); - mockAlternateUser(update, message, user, changedUser); + mockAlternateUser(update, message, changedUser); bot.addUser(update); - Map expectedUserIds = ImmutableMap.of(changedUser.username(), changedUser.id()); - Map expectedUsers = ImmutableMap.of(changedUser.id(), changedUser); + Map expectedUserIds = ImmutableMap.of(changedUser.getUserName(), changedUser.getId()); + Map expectedUsers = ImmutableMap.of(changedUser.getId(), changedUser); assertEquals("User was not properly edited", bot.userIds(), expectedUserIds); assertEquals("User was not properly edited", expectedUsers, expectedUsers); } @@ -311,13 +287,13 @@ public class AbilityBotTest { Ability validAbility = getDefaultBuilder().build(); Trio validPair = Trio.of(null, validAbility, null); - assertEquals("Bot can't validate ability properly", false, bot.validateAbility(invalidPair)); - assertEquals("Bot can't validate ability properly", true, bot.validateAbility(validPair)); + assertFalse("Bot can't validate ability properly", bot.validateAbility(invalidPair)); + assertTrue("Bot can't validate ability properly", bot.validateAbility(validPair)); } @Test public void canCheckInput() { - Update update = mockFullUpdate(MUSER, "/something"); + Update update = mockFullUpdate(USER, "/something"); Ability abilityWithOneInput = getDefaultBuilder() .build(); Ability abilityWithZeroInput = getDefaultBuilder() @@ -327,15 +303,15 @@ public class AbilityBotTest { Trio trioOneArg = Trio.of(update, abilityWithOneInput, TEXT); Trio trioZeroArg = Trio.of(update, abilityWithZeroInput, TEXT); - assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioOneArg)); + assertTrue("Unexpected result when applying token filter", bot.checkInput(trioOneArg)); trioOneArg = Trio.of(update, abilityWithOneInput, addAll(TEXT, TEXT)); - assertEquals("Unexpected result when applying token filter", false, bot.checkInput(trioOneArg)); + assertFalse("Unexpected result when applying token filter", bot.checkInput(trioOneArg)); - assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioZeroArg)); + assertTrue("Unexpected result when applying token filter", bot.checkInput(trioZeroArg)); trioZeroArg = Trio.of(update, abilityWithZeroInput, EMPTY_ARRAY); - assertEquals("Unexpected result when applying token filter", true, bot.checkInput(trioZeroArg)); + assertTrue("Unexpected result when applying token filter", bot.checkInput(trioZeroArg)); } @Test @@ -355,14 +331,14 @@ public class AbilityBotTest { mockUser(update, message, user); - assertEquals("Unexpected result when checking for privacy", true, bot.checkPrivacy(publicTrio)); - assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(groupAdminTrio)); - assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(adminTrio)); - assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(creatorTrio)); + 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)); } @Test - public void canValidateGroupAdminPrivacy() throws TelegramApiException { + public void canValidateGroupAdminPrivacy() { Update update = mock(Update.class); Message message = mock(Message.class); User user = mock(User.class); @@ -379,11 +355,11 @@ public class AbilityBotTest { when(silent.execute(any(GetChatAdministrators.class))).thenReturn(Optional.of(newArrayList(member))); - assertEquals("Unexpected result when checking for privacy", true, bot.checkPrivacy(groupAdminTrio)); + assertTrue("Unexpected result when checking for privacy", bot.checkPrivacy(groupAdminTrio)); } @Test - public void canRestrictNormalUsersFromGroupAdminAbilities() throws TelegramApiException { + public void canRestrictNormalUsersFromGroupAdminAbilities() { Update update = mock(Update.class); Message message = mock(Message.class); User user = mock(User.class); @@ -396,7 +372,7 @@ public class AbilityBotTest { when(silent.execute(any(GetChatAdministrators.class))).thenReturn(empty()); - assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(groupAdminTrio)); + assertFalse("Unexpected result when checking for privacy", bot.checkPrivacy(groupAdminTrio)); } @Test @@ -408,10 +384,10 @@ public class AbilityBotTest { Trio creatorTrio = Trio.of(update, creatorAbility, TEXT); - bot.admins().add(MUSER.id()); + bot.admins().add(USER.getId()); mockUser(update, message, user); - assertEquals("Unexpected result when checking for privacy", false, bot.checkPrivacy(creatorTrio)); + assertFalse("Unexpected result when checking for privacy", bot.checkPrivacy(creatorTrio)); } @Test @@ -430,24 +406,23 @@ public class AbilityBotTest { mockUser(update, message, user); when(message.isUserMessage()).thenReturn(true); - assertEquals("Unexpected result when checking for locality", true, bot.checkLocality(publicTrio)); - assertEquals("Unexpected result when checking for locality", true, bot.checkLocality(userTrio)); - assertEquals("Unexpected result when checking for locality", false, bot.checkLocality(groupTrio)); + assertTrue("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)); } @Test public void canRetrieveContext() { Update update = mock(Update.class); Message message = mock(Message.class); - User user = mock(User.class); Ability ability = getDefaultBuilder().build(); Trio trio = Trio.of(update, ability, TEXT); when(message.getChatId()).thenReturn(GROUP_ID); - mockUser(update, message, user); + mockUser(update, message, USER); Pair actualPair = bot.getContext(trio); - Pair expectedPair = Pair.of(newContext(update, MUSER, GROUP_ID, TEXT), ability); + Pair expectedPair = Pair.of(newContext(update, USER, GROUP_ID, TEXT), ability); assertEquals("Unexpected result when fetching for context", expectedPair, actualPair); } @@ -455,7 +430,7 @@ public class AbilityBotTest { @Test public void defaultGlobalFlagIsTrue() { Update update = mock(Update.class); - assertEquals("Unexpected result when checking for the default global flags", true, bot.checkGlobalFlags(update)); + assertTrue("Unexpected result when checking for the default global flags", bot.checkGlobalFlags(update)); } @Test(expected = ArithmeticException.class) @@ -542,24 +517,69 @@ public class AbilityBotTest { Trio docTrio = Trio.of(update, documentAbility, TEXT); Trio textTrio = Trio.of(update, textAbility, TEXT); - assertEquals("Unexpected result when checking for message flags", false, bot.checkMessageFlags(docTrio)); - assertEquals("Unexpected result when checking for message flags", true, bot.checkMessageFlags(textTrio)); + assertFalse("Unexpected result when checking for message flags", bot.checkMessageFlags(docTrio)); + assertTrue("Unexpected result when checking for message flags", bot.checkMessageFlags(textTrio)); } @Test public void canReportCommands() { + MessageContext context = mockContext(USER, GROUP_ID); + + bot.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() { Update update = mock(Update.class); Message message = mock(Message.class); when(update.hasMessage()).thenReturn(true); when(update.getMessage()).thenReturn(message); when(message.hasText()).thenReturn(true); - MessageContext context = mock(MessageContext.class); - when(context.chatId()).thenReturn(GROUP_ID); + MessageContext creatorCtx = newContext(update, CREATOR, GROUP_ID); - bot.reportCommands().action().accept(context); + bot.commands().action().accept(creatorCtx); - verify(silent, times(1)).send("default - dis iz default command", GROUP_ID); + String expected = "PUBLIC\n/commands\n/count\n/default - dis iz default command\n/group\n/test\nADMIN\n/admin\n/ban\n/demote\n/promote\n/unban\nCREATOR\n/backup\n/claim\n/recover\n/report"; + verify(silent, times(1)).send(expected, GROUP_ID); + } + + @Test + public void printsOnlyPublicCommandsForNormalUser() { + 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 userCtx = newContext(update, USER, GROUP_ID); + + bot.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 @@ -568,26 +588,14 @@ public class AbilityBotTest { db.close(); } - private User mockUser(EndUser fromUser) { - User user = mock(User.class); - when(user.getId()).thenReturn(fromUser.id()); - when(user.getUserName()).thenReturn(fromUser.username()); - when(user.getFirstName()).thenReturn(fromUser.firstName()); - when(user.getLastName()).thenReturn(fromUser.lastName()); - - return user; - } - @NotNull - private Update mockFullUpdate(EndUser fromUser, String args) { - bot.users().put(MUSER.id(), MUSER); - bot.users().put(CREATOR.id(), CREATOR); - bot.userIds().put(CREATOR.username(), CREATOR.id()); - bot.userIds().put(MUSER.username(), MUSER.id()); + private Update mockFullUpdate(User user, String args) { + bot.users().put(USER.getId(), USER); + bot.users().put(CREATOR.getId(), CREATOR); + bot.userIds().put(CREATOR.getUserName(), CREATOR.getId()); + bot.userIds().put(USER.getUserName(), USER.getId()); - bot.admins().add(CREATOR.id()); - - User user = mockUser(fromUser); + bot.admins().add(CREATOR.getId()); Update update = mock(Update.class); when(update.hasMessage()).thenReturn(true); @@ -596,7 +604,7 @@ public class AbilityBotTest { when(message.getText()).thenReturn(args); when(message.hasText()).thenReturn(true); when(message.isUserMessage()).thenReturn(true); - when(message.getChatId()).thenReturn((long) fromUser.id()); + when(message.getChatId()).thenReturn((long) user.getId()); when(update.getMessage()).thenReturn(message); return update; } @@ -605,17 +613,9 @@ public class AbilityBotTest { when(update.hasMessage()).thenReturn(true); when(update.getMessage()).thenReturn(message); when(message.getFrom()).thenReturn(user); - when(user.getFirstName()).thenReturn(MUSER.firstName()); - when(user.getLastName()).thenReturn(MUSER.lastName()); - when(user.getId()).thenReturn(MUSER.id()); - when(user.getUserName()).thenReturn(MUSER.username()); } - private void mockAlternateUser(Update update, Message message, User user, EndUser changedUser) { - when(user.getId()).thenReturn(changedUser.id()); - when(user.getFirstName()).thenReturn(changedUser.firstName()); - when(user.getLastName()).thenReturn(changedUser.lastName()); - when(user.getUserName()).thenReturn(changedUser.username()); + private void mockAlternateUser(Update update, Message message, User user) { when(message.getFrom()).thenReturn(user); when(update.hasMessage()).thenReturn(true); when(update.getMessage()).thenReturn(message); @@ -627,10 +627,12 @@ public class AbilityBotTest { Message botMessage = mock(Message.class); Document document = mock(Document.class); + when(message.getFrom()).thenReturn(CREATOR); when(update.getMessage()).thenReturn(message); when(message.getDocument()).thenReturn(document); when(botMessage.getText()).thenReturn(RECOVERY_MESSAGE); when(message.isReply()).thenReturn(true); + when(update.hasMessage()).thenReturn(true); when(message.hasDocument()).thenReturn(true); when(message.getReplyToMessage()).thenReturn(botMessage); when(message.getChatId()).thenReturn(GROUP_ID); diff --git a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/db/MapDBContextTest.java b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/db/MapDBContextTest.java index 7e53584b..0f85ce36 100644 --- a/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/db/MapDBContextTest.java +++ b/telegrambots-abilities/src/test/java/org/telegram/abilitybots/api/db/MapDBContextTest.java @@ -3,7 +3,7 @@ package org.telegram.abilitybots.api.db; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.telegram.abilitybots.api.objects.EndUser; +import org.telegram.telegrambots.api.objects.User; import java.io.IOException; import java.util.Map; @@ -12,12 +12,11 @@ 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.assertEquals; -import static org.junit.Assert.assertTrue; +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.MUSER; +import static org.telegram.abilitybots.api.bot.AbilityBotTest.USER; import static org.telegram.abilitybots.api.db.MapDBContext.offlineInstance; public class MapDBContextTest { @@ -32,22 +31,22 @@ public class MapDBContextTest { @Test public void canRecoverDB() { - Map users = db.getMap(USERS); + Map users = db.getMap(USERS); Map userIds = db.getMap(USER_ID); - users.put(CREATOR.id(), CREATOR); - users.put(MUSER.id(), MUSER); - userIds.put(CREATOR.username(), CREATOR.id()); - userIds.put(MUSER.username(), MUSER.id()); + users.put(CREATOR.getId(), CREATOR); + users.put(USER.getId(), USER); + userIds.put(CREATOR.getUserName(), CREATOR.getId()); + userIds.put(USER.getUserName(), USER.getId()); db.getSet("AYRE").add(123123); - Map originalUsers = newHashMap(users); + Map originalUsers = newHashMap(users); String beforeBackupInfo = db.info(USERS); Object jsonBackup = db.backup(); db.clear(); boolean recovered = db.recover(jsonBackup); - Map recoveredUsers = db.getMap(USERS); + Map recoveredUsers = db.getMap(USERS); String afterRecoveryInfo = db.info(USERS); assertTrue("Could not recover database successfully", recovered); @@ -56,24 +55,24 @@ public class MapDBContextTest { } @Test - public void canFallbackDBIfRecoveryFails() throws IOException { - Set users = db.getSet(USERS); + public void canFallbackDBIfRecoveryFails() { + Set users = db.getSet(USERS); users.add(CREATOR); - users.add(MUSER); + users.add(USER); - Set originalSet = newHashSet(users); + Set originalSet = newHashSet(users); Object jsonBackup = db.backup(); String corruptBackup = "!@#$" + String.valueOf(jsonBackup); boolean recovered = db.recover(corruptBackup); - Set recoveredSet = db.getSet(USERS); + Set recoveredSet = db.getSet(USERS); - assertEquals("Recovery was successful from a CORRUPT backup", false, recovered); + assertFalse("Recovery was successful from a CORRUPT backup", recovered); assertEquals("Set before and after corrupt recovery are not equal", originalSet, recoveredSet); } @Test - public void canGetSummary() throws IOException { + public void canGetSummary() { String anotherTest = TEST + 1; db.getSet(TEST).add(TEST); db.getSet(anotherTest).add(anotherTest); @@ -86,7 +85,7 @@ public class MapDBContextTest { } @Test - public void canGetInfo() throws IOException { + public void canGetInfo() { db.getSet(TEST).add(TEST); String actualInfo = db.info(TEST); @@ -97,10 +96,27 @@ public class MapDBContextTest { } @Test(expected = IllegalStateException.class) - public void cantGetInfoFromNonexistentDBStructureName() throws IOException { + public void cantGetInfoFromNonexistentDBStructureName() { db.info(TEST); } + @Test + public void canGetAndSetVariables() { + String varName = "somevar"; + Var var = db.getVar(varName); + var.set(CREATOR); + db.commit(); + + var = db.getVar(varName); + assertEquals(var.get(), CREATOR); + + var.set(USER); + db.commit(); + + Var changedVar = db.getVar(varName); + assertEquals(changedVar.get(), USER); + } + @After public void tearDown() throws IOException { db.clear(); diff --git a/telegrambots-abilities/src/test/resources/messages_it_IT.properties b/telegrambots-abilities/src/test/resources/messages_it_IT.properties new file mode 100644 index 00000000..52f1377d --- /dev/null +++ b/telegrambots-abilities/src/test/resources/messages_it_IT.properties @@ -0,0 +1 @@ +ability.commands.notFound=Non sono presenti comandi disponibile. \ No newline at end of file diff --git a/telegrambots-extensions/pom.xml b/telegrambots-extensions/pom.xml index 35d22cd6..b28a6f6d 100644 --- a/telegrambots-extensions/pom.xml +++ b/telegrambots-extensions/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambotsextensions - 3.6.1 + 3.6.2 jar Telegram Bots Extensions diff --git a/telegrambots-meta/pom.xml b/telegrambots-meta/pom.xml index a37020bc..2e813f79 100644 --- a/telegrambots-meta/pom.xml +++ b/telegrambots-meta/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots-meta - 3.6.1 + 3.6.2 jar Telegram Bots Meta diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/User.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/User.java index 536f931d..2776805a 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/User.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/User.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.telegram.telegrambots.meta.api.interfaces.BotApiObject; +import java.util.Objects; + /** * @author Ruben Bermudez * @version 3.0 @@ -35,6 +37,15 @@ public class User implements BotApiObject { super(); } + public User(Integer id, String firstName, Boolean isBot, String lastName, String userName, String languageCode) { + this.id = id; + this.firstName = firstName; + this.isBot = isBot; + this.lastName = lastName; + this.userName = userName; + this.languageCode = languageCode; + } + public Integer getId() { return id; } @@ -59,6 +70,24 @@ public class User implements BotApiObject { return isBot; } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return Objects.equals(id, user.id) && + Objects.equals(firstName, user.firstName) && + Objects.equals(isBot, user.isBot) && + Objects.equals(lastName, user.lastName) && + Objects.equals(userName, user.userName) && + Objects.equals(languageCode, user.languageCode); + } + + @Override + public int hashCode() { + return Objects.hash(id, firstName, isBot, lastName, userName, languageCode); + } + @Override public String toString() { return "User{" + diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/WebhookInfo.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/WebhookInfo.java index 6b013a70..ba573b9b 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/WebhookInfo.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/api/objects/WebhookInfo.java @@ -16,7 +16,7 @@ public class WebhookInfo implements BotApiObject { private static final String URL_FIELD = "url"; private static final String HASCUSTOMCERTIFICATE_FIELD = "has_custom_certificate"; - private static final String PENDINGUPDATESCOUNT_FIELD = "pending_updates_count"; + private static final String PENDINGUPDATECOUNT_FIELD = "pending_update_count"; private static final String MAXCONNECTIONS_FIELD = "max_connections"; private static final String ALLOWEDUPDATES_FIELD = "allowed_updates"; private static final String LASTERRORDATE_FIELD = "last_error_date"; @@ -26,7 +26,7 @@ public class WebhookInfo implements BotApiObject { private String url; ///< Webhook URL, may be empty if webhook is not set up @JsonProperty(HASCUSTOMCERTIFICATE_FIELD) private Boolean hasCustomCertificate; ///< True, if a custom certificate was provided for webhook certificate checks - @JsonProperty(PENDINGUPDATESCOUNT_FIELD) + @JsonProperty(PENDINGUPDATECOUNT_FIELD) private Integer pendingUpdatesCount; ///< Number updates awaiting delivery @JsonProperty(LASTERRORDATE_FIELD) private Integer lastErrorDate; ///< Optional. Unix time for the most recent error that happened when trying to deliver an update via webhook diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/bots/AbsSender.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/bots/AbsSender.java index 96ffe561..6a391c8a 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/bots/AbsSender.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/meta/bots/AbsSender.java @@ -54,7 +54,10 @@ public abstract class AbsSender { } // Send Requests - + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendMessage + */ @Deprecated public final Message sendMessage(SendMessage sendMessage) throws TelegramApiException { if (sendMessage == null) { @@ -64,6 +67,10 @@ public abstract class AbsSender { return sendApiMethod(sendMessage); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see AnswerInlineQuery + */ @Deprecated public final Boolean answerInlineQuery(AnswerInlineQuery answerInlineQuery) throws TelegramApiException { if (answerInlineQuery == null) { @@ -73,6 +80,10 @@ public abstract class AbsSender { return sendApiMethod(answerInlineQuery); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendChatAction + */ @Deprecated public final Boolean sendChatAction(SendChatAction sendChatAction) throws TelegramApiException { if (sendChatAction == null) { @@ -82,6 +93,10 @@ public abstract class AbsSender { return sendApiMethod(sendChatAction); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see ForwardMessage + */ @Deprecated public final Message forwardMessage(ForwardMessage forwardMessage) throws TelegramApiException { if (forwardMessage == null) { @@ -91,6 +106,10 @@ public abstract class AbsSender { return sendApiMethod(forwardMessage); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendLocation + */ @Deprecated public final Message sendLocation(SendLocation sendLocation) throws TelegramApiException { if (sendLocation == null) { @@ -100,6 +119,10 @@ public abstract class AbsSender { return sendApiMethod(sendLocation); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendVenue + */ @Deprecated public final Message sendVenue(SendVenue sendVenue) throws TelegramApiException { if (sendVenue == null) { @@ -109,6 +132,10 @@ public abstract class AbsSender { return sendApiMethod(sendVenue); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendContact + */ @Deprecated public final Message sendContact(SendContact sendContact) throws TelegramApiException { if (sendContact == null) { @@ -118,6 +145,10 @@ public abstract class AbsSender { return sendApiMethod(sendContact); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see KickChatMember + */ @Deprecated public final Boolean kickMember(KickChatMember kickChatMember) throws TelegramApiException { if (kickChatMember == null) { @@ -126,6 +157,10 @@ public abstract class AbsSender { return sendApiMethod(kickChatMember); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see UnbanChatMember + */ @Deprecated public final Boolean unbanMember(UnbanChatMember unbanChatMember) throws TelegramApiException { if (unbanChatMember == null) { @@ -134,6 +169,10 @@ public abstract class AbsSender { return sendApiMethod(unbanChatMember); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see LeaveChat + */ @Deprecated public final Boolean leaveChat(LeaveChat leaveChat) throws TelegramApiException { if (leaveChat == null) { @@ -142,6 +181,10 @@ public abstract class AbsSender { return sendApiMethod(leaveChat); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetChat + */ @Deprecated public final Chat getChat(GetChat getChat) throws TelegramApiException { if (getChat == null) { @@ -150,6 +193,10 @@ public abstract class AbsSender { return sendApiMethod(getChat); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see ExportChatInviteLink + */ @Deprecated public final String exportChatInviteLink(ExportChatInviteLink exportChatInviteLink) throws TelegramApiException { if (exportChatInviteLink == null) { @@ -158,6 +205,10 @@ public abstract class AbsSender { return sendApiMethod(exportChatInviteLink); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetChatAdministrators + */ @Deprecated public final List getChatAdministrators(GetChatAdministrators getChatAdministrators) throws TelegramApiException { if (getChatAdministrators == null) { @@ -166,6 +217,10 @@ public abstract class AbsSender { return sendApiMethod(getChatAdministrators); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetChatMember + */ @Deprecated public final ChatMember getChatMember(GetChatMember getChatMember) throws TelegramApiException { if (getChatMember == null) { @@ -174,6 +229,10 @@ public abstract class AbsSender { return sendApiMethod(getChatMember); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetChatMemberCount + */ @Deprecated public final Integer getChatMemberCount(GetChatMemberCount getChatMemberCount) throws TelegramApiException { if (getChatMemberCount == null) { @@ -182,6 +241,10 @@ public abstract class AbsSender { return sendApiMethod(getChatMemberCount); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see EditMessageText + */ @Deprecated public final Serializable editMessageText(EditMessageText editMessageText) throws TelegramApiException { if (editMessageText == null) { @@ -190,6 +253,10 @@ public abstract class AbsSender { return sendApiMethod(editMessageText); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see EditMessageCaption + */ @Deprecated public final Serializable editMessageCaption(EditMessageCaption editMessageCaption) throws TelegramApiException { if (editMessageCaption == null) { @@ -198,6 +265,10 @@ public abstract class AbsSender { return sendApiMethod(editMessageCaption); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see EditMessageReplyMarkup + */ @Deprecated public final Serializable editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup) throws TelegramApiException { if (editMessageReplyMarkup == null) { @@ -206,6 +277,10 @@ public abstract class AbsSender { return sendApiMethod(editMessageReplyMarkup); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see AnswerCallbackQuery + */ @Deprecated public final Boolean answerCallbackQuery(AnswerCallbackQuery answerCallbackQuery) throws TelegramApiException { if (answerCallbackQuery == null) { @@ -214,6 +289,10 @@ public abstract class AbsSender { return sendApiMethod(answerCallbackQuery); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetUserProfilePhotos + */ @Deprecated public final UserProfilePhotos getUserProfilePhotos(GetUserProfilePhotos getUserProfilePhotos) throws TelegramApiException { if (getUserProfilePhotos == null) { @@ -223,6 +302,10 @@ public abstract class AbsSender { return sendApiMethod(getUserProfilePhotos); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetFile + */ @Deprecated public final File getFile(GetFile getFile) throws TelegramApiException { if(getFile == null){ @@ -243,6 +326,10 @@ public abstract class AbsSender { return sendApiMethod(getWebhookInfo); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SetGameScore + */ @Deprecated public final Serializable setGameScore(SetGameScore setGameScore) throws TelegramApiException { if(setGameScore == null){ @@ -251,6 +338,10 @@ public abstract class AbsSender { return sendApiMethod(setGameScore); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see GetGameHighScores + */ @Deprecated public final Serializable getGameHighScores(GetGameHighScores getGameHighScores) throws TelegramApiException { if(getGameHighScores == null){ @@ -259,6 +350,10 @@ public abstract class AbsSender { return sendApiMethod(getGameHighScores); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendGame + */ @Deprecated public final Message sendGame(SendGame sendGame) throws TelegramApiException { if(sendGame == null){ @@ -267,6 +362,10 @@ public abstract class AbsSender { return sendApiMethod(sendGame); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see DeleteWebhook + */ @Deprecated public final Boolean deleteWebhook(DeleteWebhook deleteWebhook) throws TelegramApiException { if(deleteWebhook == null){ @@ -275,6 +374,10 @@ public abstract class AbsSender { return sendApiMethod(deleteWebhook); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SendInvoice + */ @Deprecated public final Message sendInvoice(SendInvoice sendInvoice) throws TelegramApiException { if(sendInvoice == null){ @@ -283,6 +386,10 @@ public abstract class AbsSender { return sendApiMethod(sendInvoice); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see AnswerShippingQuery + */ @Deprecated public final Boolean answerShippingQuery(AnswerShippingQuery answerShippingQuery) throws TelegramApiException { if(answerShippingQuery == null){ @@ -291,6 +398,10 @@ public abstract class AbsSender { return sendApiMethod(answerShippingQuery); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see AnswerPreCheckoutQuery + */ @Deprecated public final Boolean answerPreCheckoutQuery(AnswerPreCheckoutQuery answerPreCheckoutQuery) throws TelegramApiException { if(answerPreCheckoutQuery == null){ @@ -299,6 +410,10 @@ public abstract class AbsSender { return sendApiMethod(answerPreCheckoutQuery); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see DeleteMessage + */ @Deprecated public final Boolean deleteMessage(DeleteMessage deleteMessage) throws TelegramApiException { if(deleteMessage == null){ @@ -307,6 +422,10 @@ public abstract class AbsSender { return sendApiMethod(deleteMessage); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see DeleteChatPhoto + */ @Deprecated public final Boolean deleteChatPhoto(DeleteChatPhoto deleteChatPhoto) throws TelegramApiException { if(deleteChatPhoto == null){ @@ -315,6 +434,10 @@ public abstract class AbsSender { return sendApiMethod(deleteChatPhoto); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see PinChatMessage + */ @Deprecated public final Boolean pinChatMessage(PinChatMessage pinChatMessage) throws TelegramApiException { if(pinChatMessage == null){ @@ -323,6 +446,10 @@ public abstract class AbsSender { return sendApiMethod(pinChatMessage); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see UnpinChatMessage + */ @Deprecated public final Boolean unpinChatMessage(UnpinChatMessage unpinChatMessage) throws TelegramApiException { if(unpinChatMessage == null){ @@ -331,6 +458,10 @@ public abstract class AbsSender { return sendApiMethod(unpinChatMessage); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see PromoteChatMember + */ @Deprecated public final Boolean promoteChatMember(PromoteChatMember promoteChatMember) throws TelegramApiException { if(promoteChatMember == null){ @@ -339,6 +470,10 @@ public abstract class AbsSender { return sendApiMethod(promoteChatMember); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see RestrictChatMember + */ @Deprecated public final Boolean restrictChatMember(RestrictChatMember restrictChatMember) throws TelegramApiException { if(restrictChatMember == null){ @@ -347,6 +482,10 @@ public abstract class AbsSender { return sendApiMethod(restrictChatMember); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SetChatDescription + */ @Deprecated public final Boolean setChatDescription(SetChatDescription setChatDescription) throws TelegramApiException { if(setChatDescription == null){ @@ -355,6 +494,10 @@ public abstract class AbsSender { return sendApiMethod(setChatDescription); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) execute} Method instead + * @see SetChatTitle + */ @Deprecated public final Boolean setChatTitle(SetChatTitle setChatTitle) throws TelegramApiException { if(setChatTitle == null){ @@ -365,6 +508,10 @@ public abstract class AbsSender { // Send Requests Async + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendMessage + */ @Deprecated public final void sendMessageAsync(SendMessage sendMessage, SentCallback sentCallback) throws TelegramApiException { if (sendMessage == null) { @@ -378,6 +525,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendMessage, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see AnswerInlineQuery + */ @Deprecated public final void answerInlineQueryAsync(AnswerInlineQuery answerInlineQuery, SentCallback sentCallback) throws TelegramApiException { if (answerInlineQuery == null) { @@ -391,6 +542,10 @@ public abstract class AbsSender { sendApiMethodAsync(answerInlineQuery, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendChatAction + */ @Deprecated public final void sendChatActionAsync(SendChatAction sendChatAction, SentCallback sentCallback) throws TelegramApiException { if (sendChatAction == null) { @@ -404,6 +559,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendChatAction, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see ForwardMessage + */ @Deprecated public final void forwardMessageAsync(ForwardMessage forwardMessage, SentCallback sentCallback) throws TelegramApiException { if (forwardMessage == null) { @@ -417,6 +576,10 @@ public abstract class AbsSender { sendApiMethodAsync(forwardMessage, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendLocation + */ @Deprecated public final void sendLocationAsync(SendLocation sendLocation, SentCallback sentCallback) throws TelegramApiException { if (sendLocation == null) { @@ -430,6 +593,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendLocation, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendVenue + */ @Deprecated public final void sendVenueAsync(SendVenue sendVenue, SentCallback sentCallback) throws TelegramApiException { if (sendVenue == null) { @@ -443,6 +610,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendVenue, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendContact + */ @Deprecated public final void sendContactAsync(SendContact sendContact, SentCallback sentCallback) throws TelegramApiException { if (sendContact == null) { @@ -455,6 +626,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendContact, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see KickChatMember + */ @Deprecated public final void kickMemberAsync(KickChatMember kickChatMember, SentCallback sentCallback) throws TelegramApiException { if (kickChatMember == null) { @@ -467,6 +642,10 @@ public abstract class AbsSender { sendApiMethodAsync(kickChatMember, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see UnbanChatMember + */ @Deprecated public final void unbanMemberAsync(UnbanChatMember unbanChatMember, SentCallback sentCallback) throws TelegramApiException { if (unbanChatMember == null) { @@ -479,6 +658,10 @@ public abstract class AbsSender { sendApiMethodAsync(unbanChatMember, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see LeaveChat + */ @Deprecated public final void leaveChatAsync(LeaveChat leaveChat, SentCallback sentCallback) throws TelegramApiException { if (leaveChat == null) { @@ -490,6 +673,10 @@ public abstract class AbsSender { sendApiMethodAsync(leaveChat, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetChat + */ @Deprecated public final void getChatAsync(GetChat getChat, SentCallback sentCallback) throws TelegramApiException { if (getChat == null) { @@ -501,6 +688,10 @@ public abstract class AbsSender { sendApiMethodAsync(getChat, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see ExportChatInviteLink + */ @Deprecated public final void exportChatInviteLinkAsync(ExportChatInviteLink exportChatInviteLink, SentCallback sentCallback) throws TelegramApiException { if (exportChatInviteLink == null) { @@ -512,6 +703,10 @@ public abstract class AbsSender { sendApiMethodAsync(exportChatInviteLink, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetChatAdministrators + */ @Deprecated public final void getChatAdministratorsAsync(GetChatAdministrators getChatAdministrators, SentCallback> sentCallback) throws TelegramApiException { if (getChatAdministrators == null) { @@ -523,6 +718,10 @@ public abstract class AbsSender { sendApiMethodAsync(getChatAdministrators, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetChatMember + */ @Deprecated public final void getChatMemberAsync(GetChatMember getChatMember, SentCallback sentCallback) throws TelegramApiException { if (getChatMember == null) { @@ -534,6 +733,10 @@ public abstract class AbsSender { sendApiMethodAsync(getChatMember, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetChatMemberCount + */ @Deprecated public final void getChatMemberCountAsync(GetChatMemberCount getChatMemberCount, SentCallback sentCallback) throws TelegramApiException { if (getChatMemberCount == null) { @@ -546,6 +749,10 @@ public abstract class AbsSender { sendApiMethodAsync(getChatMemberCount, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see EditMessageText + */ @Deprecated public final void editMessageTextAsync(EditMessageText editMessageText, SentCallback sentCallback) throws TelegramApiException { if (editMessageText == null) { @@ -558,6 +765,10 @@ public abstract class AbsSender { sendApiMethodAsync(editMessageText, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see EditMessageCaption + */ @Deprecated public final void editMessageCaptionAsync(EditMessageCaption editMessageCaption, SentCallback sentCallback) throws TelegramApiException { if (editMessageCaption == null) { @@ -570,6 +781,10 @@ public abstract class AbsSender { sendApiMethodAsync(editMessageCaption, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see EditMessageReplyMarkup + */ @Deprecated public final void editMessageReplyMarkup(EditMessageReplyMarkup editMessageReplyMarkup, SentCallback sentCallback) throws TelegramApiException { if (editMessageReplyMarkup == null) { @@ -582,6 +797,10 @@ public abstract class AbsSender { sendApiMethodAsync(editMessageReplyMarkup, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see AnswerCallbackQuery + */ @Deprecated public final void answerCallbackQueryAsync(AnswerCallbackQuery answerCallbackQuery, SentCallback sentCallback) throws TelegramApiException { if (answerCallbackQuery == null) { @@ -594,6 +813,10 @@ public abstract class AbsSender { sendApiMethodAsync(answerCallbackQuery, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetUserProfilePhotos + */ @Deprecated public final void getUserProfilePhotosAsync(GetUserProfilePhotos getUserProfilePhotos, SentCallback sentCallback) throws TelegramApiException { if (getUserProfilePhotos == null) { @@ -606,6 +829,10 @@ public abstract class AbsSender { sendApiMethodAsync(getUserProfilePhotos, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetFile + */ @Deprecated public final void getFileAsync(GetFile getFile, SentCallback sentCallback) throws TelegramApiException { if (getFile == null) { @@ -632,6 +859,10 @@ public abstract class AbsSender { sendApiMethodAsync(new GetWebhookInfo(), sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SetGameScore + */ @Deprecated public final void setGameScoreAsync(SetGameScore setGameScore, SentCallback sentCallback) throws TelegramApiException { if (setGameScore == null) { @@ -643,6 +874,10 @@ public abstract class AbsSender { sendApiMethodAsync(setGameScore, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see GetGameHighScores + */ @Deprecated public final void getGameHighScoresAsync(GetGameHighScores getGameHighScores, SentCallback> sentCallback) throws TelegramApiException { if (getGameHighScores == null) { @@ -654,6 +889,10 @@ public abstract class AbsSender { sendApiMethodAsync(getGameHighScores, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendGame + */ @Deprecated public final void sendGameAsync(SendGame sendGame, SentCallback sentCallback) throws TelegramApiException { if (sendGame == null) { @@ -665,6 +904,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendGame, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see DeleteWebhook + */ @Deprecated public final void deleteWebhook(DeleteWebhook deleteWebhook, SentCallback sentCallback) throws TelegramApiException { if (deleteWebhook == null) { @@ -676,6 +919,10 @@ public abstract class AbsSender { sendApiMethodAsync(deleteWebhook, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SendInvoice + */ @Deprecated public final void sendInvoice(SendInvoice sendInvoice, SentCallback sentCallback) throws TelegramApiException { if (sendInvoice == null) { @@ -687,6 +934,10 @@ public abstract class AbsSender { sendApiMethodAsync(sendInvoice, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see AnswerShippingQuery + */ @Deprecated public final void answerShippingQuery(AnswerShippingQuery answerShippingQuery, SentCallback sentCallback) throws TelegramApiException { if (answerShippingQuery == null) { @@ -698,6 +949,10 @@ public abstract class AbsSender { sendApiMethodAsync(answerShippingQuery, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see AnswerPreCheckoutQuery + */ @Deprecated public final void answerPreCheckoutQuery(AnswerPreCheckoutQuery answerPreCheckoutQuery, SentCallback sentCallback) throws TelegramApiException { if (answerPreCheckoutQuery == null) { @@ -709,6 +964,10 @@ public abstract class AbsSender { sendApiMethodAsync(answerPreCheckoutQuery, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see DeleteMessage + */ @Deprecated public final void deleteMessage(DeleteMessage deleteMessage, SentCallback sentCallback) throws TelegramApiException { if (deleteMessage == null) { @@ -720,6 +979,10 @@ public abstract class AbsSender { sendApiMethodAsync(deleteMessage, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see DeleteChatPhoto + */ @Deprecated public final void deleteChatPhoto(DeleteChatPhoto deleteChatPhoto, SentCallback sentCallback) throws TelegramApiException { if (deleteChatPhoto == null) { @@ -731,6 +994,10 @@ public abstract class AbsSender { sendApiMethodAsync(deleteChatPhoto, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see PinChatMessage + */ @Deprecated public final void pinChatMessage(PinChatMessage pinChatMessage, SentCallback sentCallback) throws TelegramApiException { if (pinChatMessage == null) { @@ -742,6 +1009,10 @@ public abstract class AbsSender { sendApiMethodAsync(pinChatMessage, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see UnpinChatMessage + */ @Deprecated public final void unpinChatMessage(UnpinChatMessage unpinChatMessage, SentCallback sentCallback) throws TelegramApiException { if (unpinChatMessage == null) { @@ -753,6 +1024,10 @@ public abstract class AbsSender { sendApiMethodAsync(unpinChatMessage, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see PromoteChatMember + */ @Deprecated public final void promoteChatMember(PromoteChatMember promoteChatMember, SentCallback sentCallback) throws TelegramApiException { if (promoteChatMember == null) { @@ -764,6 +1039,10 @@ public abstract class AbsSender { sendApiMethodAsync(promoteChatMember, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see RestrictChatMember + */ @Deprecated public final void restrictChatMember(RestrictChatMember restrictChatMember, SentCallback sentCallback) throws TelegramApiException { if (restrictChatMember == null) { @@ -775,6 +1054,10 @@ public abstract class AbsSender { sendApiMethodAsync(restrictChatMember, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SetChatDescription + */ @Deprecated public final void setChatDescription(SetChatDescription setChatDescription, SentCallback sentCallback) throws TelegramApiException { if (setChatDescription == null) { @@ -786,6 +1069,10 @@ public abstract class AbsSender { sendApiMethodAsync(setChatDescription, sentCallback); } + /** + * Deprecated. Use {@link #execute(BotApiMethod) executeAsync} Method instead + * @see SetChatTitle + */ @Deprecated public final void setChatTitle(SetChatTitle setChatTitle, SentCallback sentCallback) throws TelegramApiException { if (setChatTitle == null) { diff --git a/telegrambots-spring-boot-starter/README.md b/telegrambots-spring-boot-starter/README.md index 8bb3f2a8..34b2cff5 100644 --- a/telegrambots-spring-boot-starter/README.md +++ b/telegrambots-spring-boot-starter/README.md @@ -25,7 +25,7 @@ Usage **Gradle** ```gradle - compile "org.telegram:telegrambots-spring-boot-starter:3.6" + compile "org.telegram:telegrambots-spring-boot-starter:3.6.1" ``` Motivation @@ -39,8 +39,6 @@ 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) { diff --git a/telegrambots-spring-boot-starter/pom.xml b/telegrambots-spring-boot-starter/pom.xml index b40ff9c9..e3dee763 100644 --- a/telegrambots-spring-boot-starter/pom.xml +++ b/telegrambots-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots-spring-boot-starter - 3.6.1 + 3.6.2 jar Telegram Bots Spring Boot Starter @@ -60,7 +60,7 @@ UTF-8 UTF-8 3.6.1 - 1.5.10.RELEASE + 2.0.2.RELEASE @@ -75,11 +75,41 @@ spring-boot ${spring-boot.version} + org.springframework.boot spring-boot-autoconfigure ${spring-boot.version} + + + org.springframework.boot + spring-boot-test + ${spring-boot.version} + test + + + + org.assertj + assertj-core + test + 3.9.1 + + + + org.mockito + mockito-all + 2.0.2-beta + test + + + + junit + junit + 4.11 + test + + diff --git a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/EnableTelegramBots.java b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/EnableTelegramBots.java deleted file mode 100644 index 68c4acf9..00000000 --- a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/EnableTelegramBots.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.telegram.telegrambots.starter; - -import org.springframework.context.annotation.Import; - -/** - * Imports configuration #TelegramBotStarterConfiguration in spring context. - */ -@Import(TelegramBotStarterConfiguration.class) -public @interface EnableTelegramBots { -} diff --git a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java new file mode 100644 index 00000000..5348ae7c --- /dev/null +++ b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotInitializer.java @@ -0,0 +1,45 @@ +package org.telegram.telegrambots.starter; + +import java.util.List; +import java.util.Objects; + +import org.springframework.beans.factory.InitializingBean; +import org.telegram.telegrambots.TelegramBotsApi; +import org.telegram.telegrambots.exceptions.TelegramApiException; +import org.telegram.telegrambots.generics.LongPollingBot; +import org.telegram.telegrambots.generics.WebhookBot; + +/** + * Receives all beand which are #LongPollingBot and #WebhookBot and register them in #TelegramBotsApi. + */ +public class TelegramBotInitializer implements InitializingBean { + + private final TelegramBotsApi telegramBotsApi; + private final List longPollingBots; + private final List webHookBots; + + public TelegramBotInitializer(TelegramBotsApi telegramBotsApi, + List longPollingBots, + List webHookBots) { + Objects.requireNonNull(telegramBotsApi); + Objects.requireNonNull(longPollingBots); + Objects.requireNonNull(webHookBots); + this.telegramBotsApi = telegramBotsApi; + this.longPollingBots = longPollingBots; + this.webHookBots = webHookBots; + } + + @Override + public void afterPropertiesSet() throws Exception { + try { + for (LongPollingBot bot : longPollingBots) { + telegramBotsApi.registerBot(bot); + } + for (WebhookBot bot : webHookBots) { + telegramBotsApi.registerBot(bot); + } + } catch (TelegramApiException e) { + throw new RuntimeException(e); + } + } +} diff --git a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.java b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.java index 26e48804..5fe34191 100644 --- a/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.java +++ b/telegrambots-spring-boot-starter/src/main/java/org/telegram/telegrambots/starter/TelegramBotStarterConfiguration.java @@ -1,55 +1,37 @@ package org.telegram.telegrambots.starter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.telegram.telegrambots.meta.TelegramBotsApi; -import org.telegram.telegrambots.meta.exceptions.TelegramApiException; import org.telegram.telegrambots.meta.generics.LongPollingBot; import org.telegram.telegrambots.meta.generics.WebhookBot; -import java.util.List; - /** - * Receives all beand which are #LongPollingBot and #WebhookBot and register them in #TelegramBotsApi. * #TelegramBotsApi added to spring context as well */ @Configuration -public class TelegramBotStarterConfiguration implements CommandLineRunner { - - - private final List longPollingBots; - private final List webHookBots; - - @Autowired - private TelegramBotsApi telegramBotsApi; - - public TelegramBotStarterConfiguration(List longPollingBots, - List webHookBots) { - this.longPollingBots = longPollingBots; - this.webHookBots = webHookBots; - } - - @Override - public void run(String... args) { - try { - for (LongPollingBot bot : longPollingBots) { - telegramBotsApi.registerBot(bot); - } - for (WebhookBot bot : webHookBots) { - telegramBotsApi.registerBot(bot); - } - } catch (TelegramApiException e) { - e.printStackTrace(); - } - } - +@ConditionalOnProperty(prefix="telegrambots",name = "enabled", havingValue = "true", matchIfMissing = true) +public class TelegramBotStarterConfiguration { @Bean @ConditionalOnMissingBean(TelegramBotsApi.class) public TelegramBotsApi telegramBotsApi() { return new TelegramBotsApi(); } + + @Bean + @ConditionalOnMissingBean + public TelegramBotInitializer telegramBotInitializer(TelegramBotsApi telegramBotsApi, + Optional> longPollingBots, + Optional> webHookBots) { + return new TelegramBotInitializer(telegramBotsApi, + longPollingBots.orElseGet(Collections::emptyList), + webHookBots.orElseGet(Collections::emptyList)); + } } diff --git a/telegrambots-spring-boot-starter/src/main/resources/META-INF/spring.factories b/telegrambots-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..71b3a84d --- /dev/null +++ b/telegrambots-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.telegram.telegrambots.starter.TelegramBotStarterConfiguration \ No newline at end of file diff --git a/telegrambots-spring-boot-starter/src/test/java/org/telegram/telegrambots/starter/TestTelegramBotStarterConfiguration.java b/telegrambots-spring-boot-starter/src/test/java/org/telegram/telegrambots/starter/TestTelegramBotStarterConfiguration.java new file mode 100644 index 00000000..d8723664 --- /dev/null +++ b/telegrambots-spring-boot-starter/src/test/java/org/telegram/telegrambots/starter/TestTelegramBotStarterConfiguration.java @@ -0,0 +1,99 @@ +package org.telegram.telegrambots.starter; + + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import static org.mockito.Mockito.*; +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.telegram.telegrambots.TelegramBotsApi; +import org.telegram.telegrambots.generics.LongPollingBot; +import org.telegram.telegrambots.generics.WebhookBot; + +public class TestTelegramBotStarterConfiguration { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(MockTelegramBotsApi.class, TelegramBotStarterConfiguration.class)); + + @Test + public void createMockTelegramBotsApiWithDefaultSettings() { + this.contextRunner.run((context) -> { + assertThat(context).hasSingleBean(TelegramBotsApi.class); + assertThat(context).hasSingleBean(TelegramBotInitializer.class); + assertThat(context).doesNotHaveBean(LongPollingBot.class); + assertThat(context).doesNotHaveBean(WebhookBot.class); + verifyNoMoreInteractions(context.getBean(TelegramBotsApi.class)); + }); + } + + @Test + public void createOnlyLongPollingBot() { + this.contextRunner.withUserConfiguration(LongPollingBotConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(LongPollingBot.class); + assertThat(context).doesNotHaveBean(WebhookBot.class); + + TelegramBotsApi telegramBotsApi = context.getBean(TelegramBotsApi.class); + + verify(telegramBotsApi, times(1)).registerBot( context.getBean(LongPollingBot.class) ); + verifyNoMoreInteractions(telegramBotsApi); + }); + } + + @Test + public void createOnlyWebhookBot() { + this.contextRunner.withUserConfiguration(WebhookBotConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(WebhookBot.class); + assertThat(context).doesNotHaveBean(LongPollingBot.class); + + TelegramBotsApi telegramBotsApi = context.getBean(TelegramBotsApi.class); + + verify(telegramBotsApi, times(1)).registerBot( context.getBean(WebhookBot.class) ); + verifyNoMoreInteractions(telegramBotsApi); + }); + } + + @Test + public void createLongPoolingBotAndWebhookBot() { + this.contextRunner.withUserConfiguration(LongPollingBotConfig.class, WebhookBotConfig.class) + .run((context) -> { + assertThat(context).hasSingleBean(LongPollingBot.class); + assertThat(context).hasSingleBean(WebhookBot.class); + + TelegramBotsApi telegramBotsApi = context.getBean(TelegramBotsApi.class); + + verify(telegramBotsApi, times(1)).registerBot( context.getBean(LongPollingBot.class) ); + verify(telegramBotsApi, times(1)).registerBot( context.getBean(WebhookBot.class) ); + //verifyNoMoreInteractions(telegramBotsApi); + }); + } + + @Configuration + static class MockTelegramBotsApi{ + + @Bean + public TelegramBotsApi telegramBotsApi() { + return mock(TelegramBotsApi.class); + } + } + + @Configuration + static class LongPollingBotConfig{ + @Bean + public LongPollingBot longPollingBot() { + return mock(LongPollingBot.class); + } + } + + @Configuration + static class WebhookBotConfig{ + @Bean + public WebhookBot webhookBot() { + return mock(WebhookBot.class); + } + } +} diff --git a/telegrambots/pom.xml b/telegrambots/pom.xml index 95d1f3e3..aceeb9d2 100644 --- a/telegrambots/pom.xml +++ b/telegrambots/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots - 3.6.1 + 3.6.2 jar Telegram Bots diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java index e832e8dd..3b588acc 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java @@ -31,6 +31,7 @@ import org.telegram.telegrambots.meta.updateshandlers.SentCallback; import java.io.IOException; import java.io.Serializable; +import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -53,7 +54,7 @@ public abstract class DefaultAbsSender extends AbsSender { protected final ExecutorService exe; private final ObjectMapper objectMapper = new ObjectMapper(); private final DefaultBotOptions options; - private volatile CloseableHttpClient httpclient; + private volatile CloseableHttpClient httpClient; private volatile RequestConfig requestConfig; protected DefaultAbsSender(DefaultBotOptions options) { @@ -61,10 +62,10 @@ public abstract class DefaultAbsSender extends AbsSender { this.exe = Executors.newFixedThreadPool(options.getMaxThreads()); this.options = options; - httpclient = TelegramHttpClientBuilder.build(options); + httpClient = TelegramHttpClientBuilder.build(options); + configureHttpContext(); requestConfig = options.getRequestConfig(); - if (requestConfig == null) { requestConfig = RequestConfig.copy(RequestConfig.custom().build()) .setSocketTimeout(SOCKET_TIMEOUT) @@ -73,6 +74,22 @@ public abstract class DefaultAbsSender extends AbsSender { } } + private void configureHttpContext() { + + if (options.getProxyType() != DefaultBotOptions.ProxyType.NO_PROXY) { + InetSocketAddress socksaddr = new InetSocketAddress(options.getProxyHost(), options.getProxyPort()); + options.getHttpContext().setAttribute("socketAddress", socksaddr); + } + + if (options.getProxyType() == DefaultBotOptions.ProxyType.SOCKS4) { + options.getHttpContext().setAttribute("socksVersion", 4); + } + if (options.getProxyType() == DefaultBotOptions.ProxyType.SOCKS5) { + options.getHttpContext().setAttribute("socksVersion", 5); + } + + } + /** * Returns the token of the bot to be able to perform Telegram Api Requests * @return Token of the bot @@ -731,7 +748,7 @@ public abstract class DefaultAbsSender extends AbsSender { } private String sendHttpPostRequest(HttpPost httppost) throws IOException { - try (CloseableHttpResponse response = httpclient.execute(httppost)) { + try (CloseableHttpResponse response = httpClient.execute(httppost, options.getHttpContext())) { HttpEntity ht = response.getEntity(); BufferedHttpEntity buf = new BufferedHttpEntity(ht); return EntityUtils.toString(buf, StandardCharsets.UTF_8); diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultBotOptions.java b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultBotOptions.java index 39b14af8..7e2e59af 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultBotOptions.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultBotOptions.java @@ -1,8 +1,8 @@ package org.telegram.telegrambots.bots; -import org.apache.http.HttpHost; -import org.apache.http.client.CredentialsProvider; import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; import org.telegram.telegrambots.meta.ApiConstants; import org.telegram.telegrambots.meta.generics.BotOptions; import org.telegram.telegrambots.updatesreceivers.ExponentialBackOff; @@ -18,17 +18,27 @@ import java.util.List; public class DefaultBotOptions implements BotOptions { private int maxThreads; ///< Max number of threads used for async methods executions (default 1) private RequestConfig requestConfig; + private volatile HttpContext httpContext; private ExponentialBackOff exponentialBackOff; private Integer maxWebhookConnections; private String baseUrl; private List allowedUpdates; + private ProxyType proxyType; + private String proxyHost; + private int proxyPort; - private CredentialsProvider credentialsProvider; - private HttpHost httpProxy; + public enum ProxyType { + NO_PROXY, + HTTP, + SOCKS4, + SOCKS5 + } public DefaultBotOptions() { maxThreads = 1; baseUrl = ApiConstants.BASE_URL; + httpContext = HttpClientContext.create(); + proxyType = ProxyType.NO_PROXY; } @Override @@ -56,6 +66,14 @@ public class DefaultBotOptions implements BotOptions { return maxWebhookConnections; } + public HttpContext getHttpContext() { + return httpContext; + } + + public void setHttpContext(HttpContext httpContext) { + this.httpContext = httpContext; + } + public void setMaxWebhookConnections(Integer maxWebhookConnections) { this.maxWebhookConnections = maxWebhookConnections; } @@ -88,19 +106,27 @@ public class DefaultBotOptions implements BotOptions { this.exponentialBackOff = exponentialBackOff; } - public CredentialsProvider getCredentialsProvider() { - return credentialsProvider; + public ProxyType getProxyType() { + return proxyType; } - public void setCredentialsProvider(CredentialsProvider credentialsProvider) { - this.credentialsProvider = credentialsProvider; + public void setProxyType(ProxyType proxyType) { + this.proxyType = proxyType; } - public HttpHost getHttpProxy() { - return httpProxy; + public String getProxyHost() { + return proxyHost; } - public void setHttpProxy(HttpHost httpProxy) { - this.httpProxy = httpProxy; + public void setProxyHost(String proxyHost) { + this.proxyHost = proxyHost; + } + + public int getProxyPort() { + return proxyPort; + } + + public void setProxyPort(int proxyPort) { + this.proxyPort = proxyPort; } } diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/TelegramHttpClientBuilder.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/TelegramHttpClientBuilder.java index fb911c04..38a3a17a 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/TelegramHttpClientBuilder.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/TelegramHttpClientBuilder.java @@ -1,10 +1,19 @@ package org.telegram.telegrambots.facilities; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; import org.telegram.telegrambots.bots.DefaultBotOptions; +import org.telegram.telegrambots.facilities.proxysocketfactorys.HttpConnectionSocketFactory; +import org.telegram.telegrambots.facilities.proxysocketfactorys.HttpSSLConnectionSocketFactory; +import org.telegram.telegrambots.facilities.proxysocketfactorys.SocksSSLConnectionSocketFactory; +import org.telegram.telegrambots.facilities.proxysocketfactorys.SocksConnectionSocketFactory; import java.util.concurrent.TimeUnit; @@ -16,22 +25,30 @@ public class TelegramHttpClientBuilder { public static CloseableHttpClient build(DefaultBotOptions options) { HttpClientBuilder httpClientBuilder = HttpClientBuilder.create() .setSSLHostnameVerifier(new NoopHostnameVerifier()) + .setConnectionManager(createConnectionManager(options)) .setConnectionTimeToLive(70, TimeUnit.SECONDS) .setMaxConnTotal(100); - - if (options.getHttpProxy() != null) { - - httpClientBuilder.setProxy(options.getHttpProxy()); - - if (options.getCredentialsProvider() != null) { - httpClientBuilder - .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()) - .setDefaultCredentialsProvider(options.getCredentialsProvider()); - } - - } - return httpClientBuilder.build(); } + private static HttpClientConnectionManager createConnectionManager(DefaultBotOptions options) { + Registry registry; + switch (options.getProxyType()) { + case NO_PROXY: + return null; + case HTTP: + registry = RegistryBuilder. create() + .register("http", new HttpConnectionSocketFactory()) + .register("https", new HttpSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build(); + return new PoolingHttpClientConnectionManager(registry); + case SOCKS4: + case SOCKS5: + registry = RegistryBuilder. create() + .register("http", new SocksConnectionSocketFactory()) + .register("https", new SocksSSLConnectionSocketFactory(SSLContexts.createSystemDefault())).build(); + return new PoolingHttpClientConnectionManager(registry); + } + return null; + } + } diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpConnectionSocketFactory.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpConnectionSocketFactory.java new file mode 100644 index 00000000..d06f17ce --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpConnectionSocketFactory.java @@ -0,0 +1,33 @@ +package org.telegram.telegrambots.facilities.proxysocketfactorys; + +import org.apache.http.HttpHost; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.protocol.HttpContext; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; + +public class HttpConnectionSocketFactory extends PlainConnectionSocketFactory { + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socketAddress = (InetSocketAddress) context.getAttribute("socketAddress"); + Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress); + return new Socket(proxy); + } + + @Override + public Socket connectSocket( + int connectTimeout, + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + HttpContext context) throws IOException { + String hostName = host.getHostName(); + int port = remoteAddress.getPort(); + InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(hostName, port); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpSSLConnectionSocketFactory.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpSSLConnectionSocketFactory.java new file mode 100644 index 00000000..2c97e934 --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/HttpSSLConnectionSocketFactory.java @@ -0,0 +1,40 @@ +package org.telegram.telegrambots.facilities.proxysocketfactorys; + +import org.apache.http.HttpHost; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.protocol.HttpContext; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; + +public class HttpSSLConnectionSocketFactory extends SSLConnectionSocketFactory { + + public HttpSSLConnectionSocketFactory(final SSLContext sslContext) { + super(sslContext, new NoopHostnameVerifier()); + } + + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socketAddress = (InetSocketAddress) context.getAttribute("socketAddress"); + Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress); + return new Socket(proxy); + } + + @Override + public Socket connectSocket( + int connectTimeout, + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + HttpContext context) throws IOException { + String hostName = host.getHostName(); + int port = remoteAddress.getPort(); + InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(hostName, port); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksConnectionSocketFactory.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksConnectionSocketFactory.java new file mode 100644 index 00000000..d2b29a5e --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksConnectionSocketFactory.java @@ -0,0 +1,37 @@ +package org.telegram.telegrambots.facilities.proxysocketfactorys; + + +import org.apache.http.HttpHost; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.protocol.HttpContext; +import sun.net.SocksProxy; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; + +public class SocksConnectionSocketFactory extends PlainConnectionSocketFactory { + + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socketAddress"); + int socksVersion = (Integer) context.getAttribute("socksVersion"); + Proxy proxy = SocksProxy.create(socksaddr, socksVersion); + return new Socket(proxy); + } + + @Override + public Socket connectSocket( + int connectTimeout, + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + HttpContext context) throws IOException { + String hostName = host.getHostName(); + int port = remoteAddress.getPort(); + InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(hostName, port); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksSSLConnectionSocketFactory.java b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksSSLConnectionSocketFactory.java new file mode 100644 index 00000000..cbd46b6a --- /dev/null +++ b/telegrambots/src/main/java/org/telegram/telegrambots/facilities/proxysocketfactorys/SocksSSLConnectionSocketFactory.java @@ -0,0 +1,43 @@ +package org.telegram.telegrambots.facilities.proxysocketfactorys; + +import org.apache.http.HttpHost; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.protocol.HttpContext; +import sun.net.SocksProxy; + +import javax.net.ssl.SSLContext; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; + + +public class SocksSSLConnectionSocketFactory extends SSLConnectionSocketFactory { + + public SocksSSLConnectionSocketFactory(final SSLContext sslContext) { + super(sslContext, new NoopHostnameVerifier()); + } + + @Override + public Socket createSocket(final HttpContext context) throws IOException { + InetSocketAddress socksaddr = (InetSocketAddress) context.getAttribute("socketAddress"); + int socksVersion = (Integer) context.getAttribute("socksVersion"); + Proxy proxy = SocksProxy.create(socksaddr, socksVersion); + return new Socket(proxy); + } + + @Override + public Socket connectSocket( + int connectTimeout, + Socket socket, + HttpHost host, + InetSocketAddress remoteAddress, + InetSocketAddress localAddress, + HttpContext context) throws IOException { + String hostName = host.getHostName(); + int port = remoteAddress.getPort(); + InetSocketAddress unresolvedRemote = InetSocketAddress.createUnresolved(hostName, port); + return super.connectSocket(connectTimeout, socket, host, unresolvedRemote, localAddress, context); + } +} diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java index 44c92fa6..ed9064a0 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java @@ -241,7 +241,7 @@ public class DefaultBotSession implements BotSession { httpPost.setConfig(requestConfig); httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(request), ContentType.APPLICATION_JSON)); - try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + try (CloseableHttpResponse response = httpclient.execute(httpPost, options.getHttpContext())) { HttpEntity ht = response.getEntity(); BufferedHttpEntity buf = new BufferedHttpEntity(ht); String responseContent = EntityUtils.toString(buf, StandardCharsets.UTF_8);