diff --git a/Bots.ipr b/Bots.ipr index 48d3590a..94516c63 100644 --- a/Bots.ipr +++ b/Bots.ipr @@ -303,18 +303,12 @@ - - - - - - diff --git a/README.md b/README.md index c37e5307..8a4aece9 100644 --- a/README.md +++ b/README.md @@ -27,16 +27,16 @@ Just import add the library to your project with one of these options: org.telegram telegrambots - 3.3 + 3.4 ``` ```gradle - compile "org.telegram:telegrambots:3.3" + compile "org.telegram:telegrambots:3.4" ``` - 2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/3.3) - 3. Download the jar(including all dependencies) from [here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.3) + 2. Using Jitpack from [here](https://jitpack.io/#rubenlagus/TelegramBots/3.4) + 3. Download the jar(including all dependencies) from [here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.4) In order to use Long Polling mode, just create your own bot extending `org.telegram.telegrambots.bots.TelegramLongPollingBot`. @@ -50,7 +50,7 @@ Once done, you just need to create a `org.telegram.telegrambots.TelegramBotsApi` // Example taken from https://github.com/rubenlagus/TelegramBotsExample public class Main { public static void main(String[] args) { - + ApiContextInitializer.init(); TelegramBotsApi telegramBotsApi = new TelegramBotsApi(); try { telegramBotsApi.registerBot(new ChannelHandlers()); diff --git a/TelegramBots.wiki/Changelog.md b/TelegramBots.wiki/Changelog.md index 2027e50b..1ca38cc0 100644 --- a/TelegramBots.wiki/Changelog.md +++ b/TelegramBots.wiki/Changelog.md @@ -1,5 +1,12 @@ -### 3.2 ### -1. Support for Api Version [3.3](https://core.telegram.org/bots/api-changelog#july-21-2017) +### 3.4 ### +1. Support for Api Version [3.4](https://core.telegram.org/bots/api-changelog#october-11-2017) +2. Use regular expressions to split parameters in `TelegramLongPollingCommandBot` (#309) +3. Option to handle bunch of updates at a time via `onUpdatesReceived` in `TelegramLongPollingBot` (#284) +4. Fix characters encoding (#275) + + +### 3.3 ### +1. Support for Api Version [3.3](https://core.telegram.org/bots/api-changelog#august-23-2017) ### 3.2 ### diff --git a/TelegramBots.wiki/Getting-Started.md b/TelegramBots.wiki/Getting-Started.md index c8f49e42..179577fb 100644 --- a/TelegramBots.wiki/Getting-Started.md +++ b/TelegramBots.wiki/Getting-Started.md @@ -11,13 +11,13 @@ First you need ot get the library and add it to your project. There are few poss org.telegram telegrambots - 3.3 + 3.4 ``` * With **Gradle**: ```groovy - compile group: 'org.telegram', name: 'telegrambots', version: '3.3' + compile group: 'org.telegram', name: 'telegrambots', version: '3.4' ``` 2. Don't like **Maven Central Repository**? It can also be taken from [Jitpack](https://jitpack.io/#rubenlagus/TelegramBots). diff --git a/pom.xml b/pom.xml index 15e915d9..9e5c6e2e 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ org.telegram Bots pom - 3.3 + 3.4 telegrambots @@ -26,6 +26,6 @@ true - 3.3 + 3.4 \ No newline at end of file diff --git a/telegrambots-abilities/README.md b/telegrambots-abilities/README.md index 8c8f6ba6..7d6e6d86 100644 --- a/telegrambots-abilities/README.md +++ b/telegrambots-abilities/README.md @@ -18,19 +18,19 @@ Usage org.telegram telegrambots-abilities - 3.3 + 3.4 ``` **Gradle** ```gradle - compile "org.telegram:telegrambots-abilities:3.3" + compile "org.telegram:telegrambots-abilities:3.4" ``` -**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v3.3) +**JitPack** - [JitPack](https://jitpack.io/#rubenlagus/TelegramBots/v3.4) -**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.3) +**Plain imports** - [Here](https://github.com/rubenlagus/TelegramBots/releases/tag/v3.4) Motivation ---------- diff --git a/telegrambots-abilities/pom.xml b/telegrambots-abilities/pom.xml index 4565f134..33154d65 100644 --- a/telegrambots-abilities/pom.xml +++ b/telegrambots-abilities/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots-abilities - 3.3 + 3.4 jar Telegram Ability Bot @@ -65,7 +65,7 @@ UTF-8 UTF-8 - 3.3 + 3.4 3.5 3.0.4 19.0 diff --git a/telegrambots-extensions/README.md b/telegrambots-extensions/README.md index bf75827a..35e40b10 100644 --- a/telegrambots-extensions/README.md +++ b/telegrambots-extensions/README.md @@ -16,12 +16,12 @@ Just import add the library to your project with one of these options: org.telegram telegrambotsextensions - 3.3 + 3.4 ``` 2. Using Gradle: ```gradle - compile "org.telegram:telegrambotsextensions:3.3" + compile "org.telegram:telegrambotsextensions:3.4" ``` \ No newline at end of file diff --git a/telegrambots-extensions/pom.xml b/telegrambots-extensions/pom.xml index c8ef2b38..0a8913f2 100644 --- a/telegrambots-extensions/pom.xml +++ b/telegrambots-extensions/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambotsextensions - 3.3 + 3.4 jar Telegram Bots Extensions @@ -59,7 +59,7 @@ UTF-8 UTF-8 - 3.3 + 3.4 diff --git a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/BotCommand.java b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/BotCommand.java index 02da6ebd..00f490db 100644 --- a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/BotCommand.java +++ b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/BotCommand.java @@ -11,7 +11,7 @@ import org.telegram.telegrambots.bots.AbsSender; */ public abstract class BotCommand { public final static String COMMAND_INIT_CHARACTER = "/"; - public static final String COMMAND_PARAMETER_SEPARATOR = " "; + public static final String COMMAND_PARAMETER_SEPARATOR_REGEXP = "\\s+"; private final static int COMMAND_MAX_LENGTH = 32; private final String commandIdentifier; @@ -75,4 +75,4 @@ public abstract class BotCommand { * @param arguments passed arguments */ public abstract void execute(AbsSender absSender, User user, Chat chat, String[] arguments); -} \ No newline at end of file +} diff --git a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/CommandRegistry.java b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/CommandRegistry.java index 17da10d3..99858e3f 100644 --- a/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/CommandRegistry.java +++ b/telegrambots-extensions/src/main/java/org/telegram/telegrambots/bots/commandbot/commands/CommandRegistry.java @@ -97,7 +97,7 @@ public final class CommandRegistry implements ICommandRegistry { String text = message.getText(); if (text.startsWith(BotCommand.COMMAND_INIT_CHARACTER)) { String commandMessage = text.substring(1); - String[] commandSplit = commandMessage.split(BotCommand.COMMAND_PARAMETER_SEPARATOR); + String[] commandSplit = commandMessage.split(BotCommand.COMMAND_PARAMETER_SEPARATOR_REGEXP); String command = removeUsernameFromCommandIfNeeded(commandSplit[0]); @@ -126,4 +126,4 @@ public final class CommandRegistry implements ICommandRegistry { } return command; } -} \ No newline at end of file +} diff --git a/telegrambots-meta/pom.xml b/telegrambots-meta/pom.xml index 501eec82..20cd6cb4 100644 --- a/telegrambots-meta/pom.xml +++ b/telegrambots-meta/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots-meta - 3.3 + 3.4 jar Telegram Bots Meta diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/StopMessageLiveLocation.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/StopMessageLiveLocation.java new file mode 100644 index 00000000..9b8ff3ef --- /dev/null +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/StopMessageLiveLocation.java @@ -0,0 +1,157 @@ +package org.telegram.telegrambots.api.methods; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import org.telegram.telegrambots.api.objects.Message; +import org.telegram.telegrambots.api.objects.replykeyboard.ApiResponse; +import org.telegram.telegrambots.api.objects.replykeyboard.InlineKeyboardMarkup; +import org.telegram.telegrambots.exceptions.TelegramApiRequestException; +import org.telegram.telegrambots.exceptions.TelegramApiValidationException; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Objects; + +/** + * @author Ruben Bermudez + * @version 1.0 + * Use this method to stop updating a live location message sent by the bot or via the bot (for inline bots) + * before live_period expires. On success, if the message was sent by the bot, the sent Message is returned, + * otherwise True is returned. + */ +public class StopMessageLiveLocation extends BotApiMethod { + public static final String PATH = "stopMessageLiveLocation"; + + private static final String CHATID_FIELD = "chat_id"; + private static final String MESSAGEID_FIELD = "message_id"; + private static final String INLINE_MESSAGE_ID_FIELD = "inline_message_id"; + private static final String REPLYMARKUP_FIELD = "reply_markup"; + + /** + * Required if inline_message_id is not specified. Unique identifier for the chat to send the + * message to (Or username for channels) + */ + @JsonProperty(CHATID_FIELD) + private String chatId; + /** + * Required if inline_message_id is not specified. Unique identifier of the sent message + */ + @JsonProperty(MESSAGEID_FIELD) + private Integer messageId; + /** + * Required if chat_id and message_id are not specified. Identifier of the inline message + */ + @JsonProperty(INLINE_MESSAGE_ID_FIELD) + private String inlineMessageId; + @JsonProperty(REPLYMARKUP_FIELD) + private InlineKeyboardMarkup replyMarkup; ///< Optional. A JSON-serialized object for an inline keyboard. + + public StopMessageLiveLocation() { + super(); + } + + public String getChatId() { + return chatId; + } + + public StopMessageLiveLocation setChatId(String chatId) { + this.chatId = chatId; + return this; + } + + public StopMessageLiveLocation setChatId(Long chatId) { + Objects.requireNonNull(chatId); + this.chatId = chatId.toString(); + return this; + } + + public Integer getMessageId() { + return messageId; + } + + public StopMessageLiveLocation setMessageId(Integer messageId) { + this.messageId = messageId; + return this; + } + + public String getInlineMessageId() { + return inlineMessageId; + } + + public StopMessageLiveLocation setInlineMessageId(String inlineMessageId) { + this.inlineMessageId = inlineMessageId; + return this; + } + + public InlineKeyboardMarkup getReplyMarkup() { + return replyMarkup; + } + + public StopMessageLiveLocation setReplyMarkup(InlineKeyboardMarkup replyMarkup) { + this.replyMarkup = replyMarkup; + return this; + } + + @Override + public String getMethod() { + return PATH; + } + + @Override + public Serializable deserializeResponse(String answer) throws TelegramApiRequestException { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>(){}); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error stopping message live location", result); + } + } catch (IOException e) { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>() { + }); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error stopping message live location", result); + } + } catch (IOException e2) { + throw new TelegramApiRequestException("Unable to deserialize response", e); + } + } + } + + @Override + public void validate() throws TelegramApiValidationException { + if (inlineMessageId == null) { + if (chatId == null) { + throw new TelegramApiValidationException("ChatId parameter can't be empty if inlineMessageId is not present", this); + } + if (messageId == null) { + throw new TelegramApiValidationException("MessageId parameter can't be empty if inlineMessageId is not present", this); + } + } else { + if (chatId != null) { + throw new TelegramApiValidationException("ChatId parameter must be empty if inlineMessageId is provided", this); + } + if (messageId != null) { + throw new TelegramApiValidationException("MessageId parameter must be empty if inlineMessageId is provided", this); + } + } + if (replyMarkup != null) { + replyMarkup.validate(); + } + } + + @Override + public String toString() { + return "StopMessageLiveLocation{" + + "chatId='" + chatId + '\'' + + ", messageId=" + messageId + + ", inlineMessageId='" + inlineMessageId + '\'' + + ", replyMarkup=" + replyMarkup + + '}'; + } +} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/DeleteStickerSetName.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/DeleteStickerSetName.java new file mode 100644 index 00000000..5dc0e858 --- /dev/null +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/DeleteStickerSetName.java @@ -0,0 +1,93 @@ +package org.telegram.telegrambots.api.methods.groupadministration; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import org.telegram.telegrambots.api.methods.BotApiMethod; +import org.telegram.telegrambots.api.objects.replykeyboard.ApiResponse; +import org.telegram.telegrambots.exceptions.TelegramApiRequestException; +import org.telegram.telegrambots.exceptions.TelegramApiValidationException; + +import java.io.IOException; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Ruben Bermudez + * @version 3.4 + * Use this method to delete a group sticker set from a supergroup. + * The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + * Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. + * Returns True on success. + */ +public class DeleteStickerSetName extends BotApiMethod { + public static final String PATH = "deleteChatStickerSet"; + + private static final String CHATID_FIELD = "chat_id"; + + @JsonProperty(CHATID_FIELD) + private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels) + + public DeleteStickerSetName() { + super(); + } + + public DeleteStickerSetName(String chatId) { + super(); + this.chatId = checkNotNull(chatId); + } + + public DeleteStickerSetName(Long chatId) { + super(); + this.chatId = checkNotNull(chatId).toString(); + } + + public String getChatId() { + return chatId; + } + + public DeleteStickerSetName setChatId(String chatId) { + this.chatId = chatId; + return this; + } + + public DeleteStickerSetName setChatId(Long chatId) { + Objects.requireNonNull(chatId); + this.chatId = chatId.toString(); + return this; + } + + @Override + public String getMethod() { + return PATH; + } + + @Override + public Boolean deserializeResponse(String answer) throws TelegramApiRequestException { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>(){}); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error deleting sticker set name", result); + } + } catch (IOException e) { + throw new TelegramApiRequestException("Unable to deserialize response", e); + } + } + + @Override + public void validate() throws TelegramApiValidationException { + if (chatId == null || chatId.isEmpty()) { + throw new TelegramApiValidationException("ChatId can't be empty", this); + } + } + + @Override + public String toString() { + return "GetChat{" + + "chatId='" + chatId + '\'' + + '}'; + } +} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/SetChatStickerSet.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/SetChatStickerSet.java new file mode 100644 index 00000000..15fa288a --- /dev/null +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/groupadministration/SetChatStickerSet.java @@ -0,0 +1,111 @@ +package org.telegram.telegrambots.api.methods.groupadministration; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import org.telegram.telegrambots.api.methods.BotApiMethod; +import org.telegram.telegrambots.api.objects.replykeyboard.ApiResponse; +import org.telegram.telegrambots.exceptions.TelegramApiRequestException; +import org.telegram.telegrambots.exceptions.TelegramApiValidationException; + +import java.io.IOException; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * @author Ruben Bermudez + * @version 1.0 + * Use this method to set a new group sticker set for a supergroup. + * The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + * Use the field can_set_sticker_set optionally returned in getChat requests to check if the bot can use this method. + * Returns True on success. + */ +public class SetChatStickerSet extends BotApiMethod { + public static final String PATH = "setChatStickerSet"; + + private static final String CHATID_FIELD = "chat_id"; + private static final String STICKERSETNAME_FIELD = "sticker_set_name"; + + @JsonProperty(CHATID_FIELD) + private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels) + @JsonProperty(STICKERSETNAME_FIELD) + private String stickerSetName; ///< Name of the sticker set to be set as the group sticker set + + public SetChatStickerSet() { + super(); + } + + public SetChatStickerSet(String chatId, String stickerSetName) { + super(); + this.chatId = checkNotNull(chatId); + this.stickerSetName = checkNotNull(stickerSetName); + } + + public SetChatStickerSet(Long chatId) { + super(); + this.chatId = checkNotNull(chatId).toString(); + } + + public String getChatId() { + return chatId; + } + + public SetChatStickerSet setChatId(String chatId) { + this.chatId = chatId; + return this; + } + + public SetChatStickerSet setChatId(Long chatId) { + Objects.requireNonNull(chatId); + this.chatId = chatId.toString(); + return this; + } + + public String getStickerSetName() { + return stickerSetName; + } + + public SetChatStickerSet setStickerSetName(String stickerSetName) { + Objects.requireNonNull(stickerSetName); + this.stickerSetName = stickerSetName; + return this; + } + + @Override + public String getMethod() { + return PATH; + } + + @Override + public Boolean deserializeResponse(String answer) throws TelegramApiRequestException { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>(){}); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error setting chat sticker set", result); + } + } catch (IOException e) { + throw new TelegramApiRequestException("Unable to deserialize response", e); + } + } + + @Override + public void validate() throws TelegramApiValidationException { + if (chatId == null || chatId.isEmpty()) { + throw new TelegramApiValidationException("ChatId can't be empty", this); + } + if (stickerSetName == null || stickerSetName.isEmpty()) { + throw new TelegramApiValidationException("StickerSetName can't be empty", this); + } + } + + @Override + public String toString() { + return "SetChatStickerSet{" + + "chatId='" + chatId + '\'' + + ", stickerSetName='" + stickerSetName + '\'' + + '}'; + } +} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/send/SendLocation.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/send/SendLocation.java index 74f58ced..83d98a5e 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/send/SendLocation.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/send/SendLocation.java @@ -2,7 +2,6 @@ package org.telegram.telegrambots.api.methods.send; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.type.TypeReference; - import org.telegram.telegrambots.api.methods.BotApiMethod; import org.telegram.telegrambots.api.objects.Message; import org.telegram.telegrambots.api.objects.replykeyboard.ApiResponse; @@ -13,11 +12,12 @@ import org.telegram.telegrambots.exceptions.TelegramApiValidationException; import java.io.IOException; import java.util.Objects; +import static com.google.common.base.Preconditions.checkNotNull; + /** * @author Ruben Bermudez * @version 1.0 - * @brief Use this method to send point on the map. On success, the sent Message is returned. - * @date 20 of June of 2015 + * Use this method to send point on the map. On success, the sent Message is returned. */ public class SendLocation extends BotApiMethod { public static final String PATH = "sendlocation"; @@ -28,6 +28,7 @@ public class SendLocation extends BotApiMethod { private static final String DISABLENOTIFICATION_FIELD = "disable_notification"; private static final String REPLYTOMESSAGEID_FIELD = "reply_to_message_id"; private static final String REPLYMARKUP_FIELD = "reply_markup"; + private static final String LIVEPERIOD_FIELD = "live_period"; @JsonProperty(CHATID_FIELD) private String chatId; ///< Unique identifier for the chat to send the message to (Or username for channels) @@ -41,11 +42,20 @@ public class SendLocation extends BotApiMethod { private Integer replyToMessageId; ///< Optional. If the message is a reply, ID of the original message @JsonProperty(REPLYMARKUP_FIELD) private ReplyKeyboard replyMarkup; ///< Optional. JSON-serialized object for a custom reply keyboard + @JsonProperty(LIVEPERIOD_FIELD) + private Integer livePeriod; ///< Optional. Period in seconds for which the location will be updated (see Live Locations), should be between 60 and 86400. public SendLocation() { super(); } + public SendLocation(Float latitude, Float longitude) { + super(); + this.latitude = checkNotNull(latitude); + this.longitude = checkNotNull(longitude); + } + + public String getChatId() { return chatId; } @@ -56,7 +66,6 @@ public class SendLocation extends BotApiMethod { } public SendLocation setChatId(Long chatId) { - Objects.requireNonNull(chatId); this.chatId = chatId.toString(); return this; } @@ -66,6 +75,7 @@ public class SendLocation extends BotApiMethod { } public SendLocation setLatitude(Float latitude) { + Objects.requireNonNull(latitude); this.latitude = latitude; return this; } @@ -75,6 +85,7 @@ public class SendLocation extends BotApiMethod { } public SendLocation setLongitude(Float longitude) { + Objects.requireNonNull(longitude); this.longitude = longitude; return this; } @@ -111,6 +122,15 @@ public class SendLocation extends BotApiMethod { return this; } + public Integer getLivePeriod() { + return livePeriod; + } + + public SendLocation setLivePeriod(Integer livePeriod) { + this.livePeriod = livePeriod; + return this; + } + @Override public String getMethod() { return PATH; @@ -145,6 +165,9 @@ public class SendLocation extends BotApiMethod { if (replyMarkup != null) { replyMarkup.validate(); } + if (livePeriod != null && (livePeriod < 60 || livePeriod > 86400)) { + throw new TelegramApiValidationException("Live period parameter must be between 60 and 86400", this); + } } @Override @@ -153,8 +176,10 @@ public class SendLocation extends BotApiMethod { "chatId='" + chatId + '\'' + ", latitude=" + latitude + ", longitude=" + longitude + + ", disableNotification=" + disableNotification + ", replyToMessageId=" + replyToMessageId + ", replyMarkup=" + replyMarkup + + ", livePeriod=" + livePeriod + '}'; } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/updatingmessages/EditMessageLiveLocation.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/updatingmessages/EditMessageLiveLocation.java new file mode 100644 index 00000000..2260b035 --- /dev/null +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/methods/updatingmessages/EditMessageLiveLocation.java @@ -0,0 +1,192 @@ +package org.telegram.telegrambots.api.methods.updatingmessages; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import org.telegram.telegrambots.api.methods.BotApiMethod; +import org.telegram.telegrambots.api.objects.Message; +import org.telegram.telegrambots.api.objects.replykeyboard.ApiResponse; +import org.telegram.telegrambots.api.objects.replykeyboard.InlineKeyboardMarkup; +import org.telegram.telegrambots.exceptions.TelegramApiRequestException; +import org.telegram.telegrambots.exceptions.TelegramApiValidationException; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Objects; + +/** + * @author Ruben Bermudez + * @version 1.0 + * Use this method to edit live location messages sent by the bot or via the bot (for inline bots). + * A location can be edited until its live_period expires or editing is explicitly disabled by a call to + * stopMessageLiveLocation. On success, if the edited message was sent by the bot, the edited Message is returned, + * otherwise True is returned. + */ +public class EditMessageLiveLocation extends BotApiMethod { + public static final String PATH = "editMessageLiveLocation"; + + private static final String CHATID_FIELD = "chat_id"; + private static final String MESSAGEID_FIELD = "message_id"; + private static final String INLINE_MESSAGE_ID_FIELD = "inline_message_id"; + private static final String LATITUDE_FIELD = "latitude"; + private static final String LONGITUDE_FIELD = "longitude"; + private static final String REPLYMARKUP_FIELD = "reply_markup"; + + /** + * Required if inline_message_id is not specified. Unique identifier for the chat to send the + * message to (Or username for channels) + */ + @JsonProperty(CHATID_FIELD) + private String chatId; + /** + * Required if inline_message_id is not specified. Unique identifier of the sent message + */ + @JsonProperty(MESSAGEID_FIELD) + private Integer messageId; + /** + * Required if chat_id and message_id are not specified. Identifier of the inline message + */ + @JsonProperty(INLINE_MESSAGE_ID_FIELD) + private String inlineMessageId; + @JsonProperty(LATITUDE_FIELD) + private Float latitude; ///< Latitude of new location + @JsonProperty(LONGITUDE_FIELD) + private Float longitud; ///< Longitude of new location + @JsonProperty(REPLYMARKUP_FIELD) + private InlineKeyboardMarkup replyMarkup; ///< Optional. A JSON-serialized object for an inline keyboard. + + public EditMessageLiveLocation() { + super(); + } + + public String getChatId() { + return chatId; + } + + public EditMessageLiveLocation setChatId(String chatId) { + this.chatId = chatId; + return this; + } + + public EditMessageLiveLocation setChatId(Long chatId) { + this.chatId = chatId.toString(); + return this; + } + + public Integer getMessageId() { + return messageId; + } + + public EditMessageLiveLocation setMessageId(Integer messageId) { + this.messageId = messageId; + return this; + } + + public String getInlineMessageId() { + return inlineMessageId; + } + + public EditMessageLiveLocation setInlineMessageId(String inlineMessageId) { + this.inlineMessageId = inlineMessageId; + return this; + } + + public InlineKeyboardMarkup getReplyMarkup() { + return replyMarkup; + } + + public EditMessageLiveLocation setReplyMarkup(InlineKeyboardMarkup replyMarkup) { + this.replyMarkup = replyMarkup; + return this; + } + + public Float getLatitude() { + return latitude; + } + + public EditMessageLiveLocation setLatitude(Float latitude) { + Objects.requireNonNull(chatId); + this.latitude = latitude; + return this; + } + + public Float getLongitud() { + return longitud; + } + + public EditMessageLiveLocation setLongitud(Float longitud) { + Objects.requireNonNull(chatId); + this.longitud = longitud; + return this; + } + + @Override + public String getMethod() { + return PATH; + } + + @Override + public Serializable deserializeResponse(String answer) throws TelegramApiRequestException { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>(){}); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error editing message live location", result); + } + } catch (IOException e) { + try { + ApiResponse result = OBJECT_MAPPER.readValue(answer, + new TypeReference>() { + }); + if (result.getOk()) { + return result.getResult(); + } else { + throw new TelegramApiRequestException("Error editing message live location", result); + } + } catch (IOException e2) { + throw new TelegramApiRequestException("Unable to deserialize response", e); + } + } + } + + @Override + public void validate() throws TelegramApiValidationException { + if (inlineMessageId == null) { + if (chatId == null) { + throw new TelegramApiValidationException("ChatId parameter can't be empty if inlineMessageId is not present", this); + } + if (messageId == null) { + throw new TelegramApiValidationException("MessageId parameter can't be empty if inlineMessageId is not present", this); + } + } else { + if (chatId != null) { + throw new TelegramApiValidationException("ChatId parameter must be empty if inlineMessageId is provided", this); + } + if (messageId != null) { + throw new TelegramApiValidationException("MessageId parameter must be empty if inlineMessageId is provided", this); + } + } + if (latitude == null) { + throw new TelegramApiValidationException("Latitude parameter can't be empty", this); + } + if (longitud == null) { + throw new TelegramApiValidationException("Longitud parameter can't be empty", this); + } + if (replyMarkup != null) { + replyMarkup.validate(); + } + } + + @Override + public String toString() { + return "EditMessageLiveLocation{" + + "chatId='" + chatId + '\'' + + ", messageId=" + messageId + + ", inlineMessageId='" + inlineMessageId + '\'' + + ", latitude=" + latitude + + ", longitud=" + longitud + + ", replyMarkup=" + replyMarkup + + '}'; + } +} diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Chat.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Chat.java index 0ac20c70..6d26e953 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Chat.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Chat.java @@ -22,6 +22,8 @@ public class Chat implements BotApiObject { private static final String DESCRIPTION_FIELD = "description"; private static final String INVITELINK_FIELD = "invite_link"; private static final String PINNEDMESSAGE_FIELD = "pinned_message"; + private static final String STICKERSETNAME_FIELD = "sticker_set_name"; + private static final String CANSETSTICKERSET_FIELD = "can_set_sticker_set"; private static final String USERCHATTYPE = "private"; private static final String GROUPCHATTYPE = "group"; @@ -59,6 +61,10 @@ public class Chat implements BotApiObject { private String inviteLink; ///< Optional. Chat invite link, for supergroups and channel chats. Returned only in getChat. @JsonProperty(PINNEDMESSAGE_FIELD) private Message pinnedMessage; ///< Optional. Pinned message, for supergroups. Returned only in getChat. + @JsonProperty(STICKERSETNAME_FIELD) + private String stickerSetName; ///< Optional. For supergroups, name of Group sticker set. Returned only in getChat. + @JsonProperty(CANSETSTICKERSET_FIELD) + private Message canSetStickerSet; ///< Optional. True, if the bot can change group the sticker set. Returned only in getChat. public Chat() { super(); @@ -120,6 +126,14 @@ public class Chat implements BotApiObject { return pinnedMessage; } + public String getStickerSetName() { + return stickerSetName; + } + + public Message getCanSetStickerSet() { + return canSetStickerSet; + } + @Override public String toString() { return "Chat{" + @@ -134,6 +148,8 @@ public class Chat implements BotApiObject { ", description='" + description + '\'' + ", inviteLink='" + inviteLink + '\'' + ", pinnedMessage=" + pinnedMessage + + ", stickerSetName='" + stickerSetName + '\'' + + ", canSetStickerSet=" + canSetStickerSet + '}'; } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Message.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Message.java index d396ce8a..b4d0fb37 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Message.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/Message.java @@ -24,6 +24,7 @@ public class Message implements BotApiObject { private static final String FORWARDDATE_FIELD = "forward_date"; private static final String TEXT_FIELD = "text"; private static final String ENTITIES_FIELD = "entities"; + private static final String CAPTIONENTITIES_FIELD = "caption_entities"; private static final String AUDIO_FIELD = "audio"; private static final String DOCUMENT_FIELD = "document"; private static final String PHOTO_FIELD = "photo"; @@ -77,6 +78,12 @@ public class Message implements BotApiObject { */ @JsonProperty(ENTITIES_FIELD) private List entities; + /** + * Optional. For messages with a caption, special entities like usernames, + * URLs, bot commands, etc. that appear in the caption + */ + @JsonProperty(CAPTIONENTITIES_FIELD) + private List captionEntities; @JsonProperty(AUDIO_FIELD) private Audio audio; ///< Optional. Message is an audio file, information about the file @JsonProperty(DOCUMENT_FIELD) @@ -206,6 +213,13 @@ public class Message implements BotApiObject { return entities; } + public List getCaptionEntities() { + if (captionEntities != null) { + captionEntities.forEach(x -> x.computeText(caption)); + } + return captionEntities; + } + public Audio getAudio() { return audio; } @@ -390,6 +404,14 @@ public class Message implements BotApiObject { return videoNote; } + public String getAuthorSignature() { + return authorSignature; + } + + public String getForwardSignature() { + return forwardSignature; + } + @Override public String toString() { return "Message{" + @@ -402,6 +424,7 @@ public class Message implements BotApiObject { ", forwardDate=" + forwardDate + ", text='" + text + '\'' + ", entities=" + entities + + ", captionEntities=" + captionEntities + ", audio=" + audio + ", document=" + document + ", photo=" + photo + @@ -430,6 +453,8 @@ public class Message implements BotApiObject { ", invoice=" + invoice + ", successfulPayment=" + successfulPayment + ", videoNote=" + videoNote + + ", authorSignature='" + authorSignature + '\'' + + ", forwardSignature='" + forwardSignature + '\'' + '}'; } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/inputmessagecontent/InputLocationMessageContent.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/inputmessagecontent/InputLocationMessageContent.java index cda61d2a..75fc3069 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/inputmessagecontent/InputLocationMessageContent.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/inputmessagecontent/InputLocationMessageContent.java @@ -4,33 +4,46 @@ import com.fasterxml.jackson.annotation.JsonProperty; import org.telegram.telegrambots.exceptions.TelegramApiValidationException; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + /** * @author Ruben Bermudez * @version 1.0 - * @brief Represents the content of a location message to be sent as the result of an inline query. + * Represents the content of a location message to be sent as the result of an inline query. * @note This will only work in Telegram versions released after 9 April, 2016. Older clients will * ignore them. - * @date 10 of April of 2016 */ public class InputLocationMessageContent implements InputMessageContent { private static final String LATITUDE_FIELD = "latitude"; private static final String LONGITUDE_FIELD = "longitude"; + private static final String LIVEPERIOD_FIELD = "live_period"; @JsonProperty(LATITUDE_FIELD) private Float latitude; ///< Latitude of the location in degrees @JsonProperty(LONGITUDE_FIELD) private Float longitude; ///< Longitude of the location in degrees + @JsonProperty(LIVEPERIOD_FIELD) + private Integer livePeriod; ///< Optional. Period in seconds for which the location can be updated, should be between 60 and 86400. public InputLocationMessageContent() { super(); } + public InputLocationMessageContent(Float latitude, Float longitude) { + super(); + this.latitude = checkNotNull(latitude); + this.longitude = checkNotNull(longitude); + } + public Float getLongitude() { return longitude; } public InputLocationMessageContent setLongitude(Float longitude) { + Objects.requireNonNull(longitude); this.longitude = longitude; return this; } @@ -40,10 +53,20 @@ public class InputLocationMessageContent implements InputMessageContent { } public InputLocationMessageContent setLatitude(Float latitude) { + Objects.requireNonNull(latitude); this.latitude = latitude; return this; } + public Integer getLivePeriod() { + return livePeriod; + } + + public InputLocationMessageContent setLivePeriod(Integer livePeriod) { + this.livePeriod = livePeriod; + return this; + } + @Override public void validate() throws TelegramApiValidationException { if (latitude == null) { @@ -52,13 +75,17 @@ public class InputLocationMessageContent implements InputMessageContent { if (longitude == null) { throw new TelegramApiValidationException("Longitude parameter can't be empty", this); } + if (livePeriod != null && (livePeriod < 60 || livePeriod > 86400)) { + throw new TelegramApiValidationException("Live period parameter must be between 60 and 86400", this); + } } @Override public String toString() { return "InputLocationMessageContent{" + - "latitude='" + latitude + '\'' + - ", longitude='" + longitude + '\'' + + "latitude=" + latitude + + ", longitude=" + longitude + + ", livePeriod=" + livePeriod + '}'; } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/result/InlineQueryResultLocation.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/result/InlineQueryResultLocation.java index fa3b973d..a1ff830e 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/result/InlineQueryResultLocation.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/api/objects/inlinequery/result/InlineQueryResultLocation.java @@ -9,12 +9,11 @@ import org.telegram.telegrambots.exceptions.TelegramApiValidationException; /** * @author Ruben Bermudez * @version 1.0 - * @brief Represents a location on a map. By default, the location will be sent by the user. + * Represents a location on a map. By default, the location will be sent by the user. * Alternatively, you can use input_message_content to send a message with the specified content * instead of the location. * @note This will only work in Telegram versions released after 9 April, 2016. Older clients will * ignore them. - * @date 10 of April of 2016 */ public class InlineQueryResultLocation implements InlineQueryResult { @@ -28,6 +27,7 @@ public class InlineQueryResultLocation implements InlineQueryResult { private static final String THUMBURL_FIELD = "thumb_url"; private static final String THUMBWIDTH_FIELD = "thumb_width"; private static final String THUMBHEIGHT_FIELD = "thumb_height"; + private static final String LIVEPERIOD_FIELD = "live_period"; @JsonProperty(TYPE_FIELD) private final String type = "location"; ///< Type of the result, must be "location" @@ -49,6 +49,8 @@ public class InlineQueryResultLocation implements InlineQueryResult { private Integer thumbWidth; ///< Optional. Thumbnail width @JsonProperty(THUMBHEIGHT_FIELD) private Integer thumbHeight; ///< Optional. Thumbnail height + @JsonProperty(LIVEPERIOD_FIELD) + private Integer livePeriod; ///< Optional. Period in seconds for which the location can be updated, should be between 60 and 86400. public InlineQueryResultLocation() { super(); @@ -139,6 +141,15 @@ public class InlineQueryResultLocation implements InlineQueryResult { return this; } + public Integer getLivePeriod() { + return livePeriod; + } + + public InlineQueryResultLocation setLivePeriod(Integer livePeriod) { + this.livePeriod = livePeriod; + return this; + } + @Override public void validate() throws TelegramApiValidationException { if (id == null || id.isEmpty()) { @@ -153,6 +164,9 @@ public class InlineQueryResultLocation implements InlineQueryResult { if (longitude == null) { throw new TelegramApiValidationException("Longitude parameter can't be empty", this); } + if (livePeriod != null && (livePeriod < 60 || livePeriod > 86400)) { + throw new TelegramApiValidationException("Live period parameter must be between 60 and 86400", this); + } if (inputMessageContent != null) { inputMessageContent.validate(); } @@ -166,14 +180,15 @@ public class InlineQueryResultLocation implements InlineQueryResult { return "InlineQueryResultLocation{" + "type='" + type + '\'' + ", id='" + id + '\'' + - ", mimeType='" + latitude + '\'' + - ", documentUrl='" + longitude + '\'' + - ", thumbHeight=" + thumbHeight + - ", thumbWidth=" + thumbWidth + - ", thumbUrl='" + thumbUrl + '\'' + ", title='" + title + '\'' + - ", inputMessageContent='" + inputMessageContent + '\'' + - ", replyMarkup='" + replyMarkup + '\'' + + ", latitude=" + latitude + + ", longitude=" + longitude + + ", replyMarkup=" + replyMarkup + + ", inputMessageContent=" + inputMessageContent + + ", thumbUrl='" + thumbUrl + '\'' + + ", thumbWidth=" + thumbWidth + + ", thumbHeight=" + thumbHeight + + ", livePeriod=" + livePeriod + '}'; } } diff --git a/telegrambots-meta/src/main/java/org/telegram/telegrambots/generics/LongPollingBot.java b/telegrambots-meta/src/main/java/org/telegram/telegrambots/generics/LongPollingBot.java index ce9bda79..32118616 100644 --- a/telegrambots-meta/src/main/java/org/telegram/telegrambots/generics/LongPollingBot.java +++ b/telegrambots-meta/src/main/java/org/telegram/telegrambots/generics/LongPollingBot.java @@ -3,6 +3,8 @@ package org.telegram.telegrambots.generics; import org.telegram.telegrambots.api.objects.Update; import org.telegram.telegrambots.exceptions.TelegramApiRequestException; +import java.util.List; + /** * @author Ruben Bermudez * @version 1.0 @@ -16,6 +18,15 @@ public interface LongPollingBot { */ void onUpdateReceived(Update update); + /** + * This method is called when receiving updates via GetUpdates method. + * If not reimplemented - it just sends updates by one into {@link #onUpdateReceived(Update)} + * @param updates list of Update received + */ + default void onUpdatesReceived(List updates) { + updates.forEach(this::onUpdateReceived); + } + /** * Return bot username of this bot */ diff --git a/telegrambots/pom.xml b/telegrambots/pom.xml index 36e7b5f1..d514c5dd 100644 --- a/telegrambots/pom.xml +++ b/telegrambots/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.telegram telegrambots - 3.3 + 3.4 jar Telegram Bots @@ -66,7 +66,7 @@ 2.8.7 2.8.0 2.5 - 3.3 + 3.4 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 c4c09f29..82546c59 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/bots/DefaultAbsSender.java @@ -133,6 +133,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendDocument.CHATID_FIELD, sendDocument.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendDocument.isNewDocument()) { @@ -177,6 +178,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendPhoto.CHATID_FIELD, sendPhoto.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendPhoto.isNewPhoto()) { @@ -221,6 +223,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendVideo.CHATID_FIELD, sendVideo.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendVideo.isNewVideo()) { @@ -274,6 +277,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendVideoNote.CHATID_FIELD, sendVideoNote.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendVideoNote.isNewVideoNote()) { @@ -322,6 +326,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendSticker.CHATID_FIELD, sendSticker.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendSticker.isNewSticker()) { @@ -367,6 +372,7 @@ public abstract class DefaultAbsSender extends AbsSender { String url = getBaseUrl() + SendAudio.PATH; HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendAudio.CHATID_FIELD, sendAudio.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendAudio.isNewAudio()) { @@ -426,6 +432,7 @@ public abstract class DefaultAbsSender extends AbsSender { String url = getBaseUrl() + SendVoice.PATH; HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SendVoice.CHATID_FIELD, sendVoice.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (sendVoice.isNewVoice()) { @@ -473,6 +480,7 @@ public abstract class DefaultAbsSender extends AbsSender { HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(SetChatPhoto.CHATID_FIELD, setChatPhoto.getChatId(), TEXT_PLAIN_CONTENT_TYPE); if (setChatPhoto.getPhoto() != null) { @@ -498,6 +506,7 @@ public abstract class DefaultAbsSender extends AbsSender { String url = getBaseUrl() + AddStickerToSet.PATH; HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(AddStickerToSet.USERID_FIELD, addStickerToSet.getUserId().toString(), TEXT_PLAIN_CONTENT_TYPE); builder.addTextBody(AddStickerToSet.NAME_FIELD, addStickerToSet.getName(), TEXT_PLAIN_CONTENT_TYPE); @@ -533,6 +542,7 @@ public abstract class DefaultAbsSender extends AbsSender { String url = getBaseUrl() + CreateNewStickerSet.PATH; HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(CreateNewStickerSet.USERID_FIELD, createNewStickerSet.getUserId().toString(), TEXT_PLAIN_CONTENT_TYPE); builder.addTextBody(CreateNewStickerSet.NAME_FIELD, createNewStickerSet.getName(), TEXT_PLAIN_CONTENT_TYPE); @@ -570,6 +580,7 @@ public abstract class DefaultAbsSender extends AbsSender { String url = getBaseUrl() + UploadStickerFile.PATH; HttpPost httppost = configuredHttpPost(url); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setLaxMode(); builder.setCharset(StandardCharsets.UTF_8); builder.addTextBody(UploadStickerFile.USERID_FIELD, uploadStickerFile.getUserId().toString(), TEXT_PLAIN_CONTENT_TYPE); if (uploadStickerFile.getNewPngStickerFile() != null) { diff --git a/telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java b/telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java index ef2eb08f..50da8e71 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingBot.java @@ -22,9 +22,8 @@ import java.nio.charset.StandardCharsets; /** * @author Ruben Bermudez * @version 1.0 - * @brief Base abstract class for a bot that will get updates using + * Base abstract class for a bot that will get updates using * long-polling method - * @date 14 of January of 2016 */ public abstract class TelegramLongPollingBot extends DefaultAbsSender implements LongPollingBot { public TelegramLongPollingBot() { 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 1347e03a..23f5b618 100644 --- a/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java +++ b/telegrambots/src/main/java/org/telegram/telegrambots/updatesreceivers/DefaultBotSession.java @@ -27,7 +27,7 @@ import java.io.InvalidObjectException; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; import java.security.InvalidParameterException; -import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; @@ -52,6 +52,7 @@ public class DefaultBotSession implements BotSession { private String token; private int lastReceivedUpdate = 0; private DefaultBotOptions options; + private UpdatesSupplier updatesSupplier; @Inject public DefaultBotSession() { @@ -67,7 +68,7 @@ public class DefaultBotSession implements BotSession { lastReceivedUpdate = 0; - readerThread = new ReaderThread(); + readerThread = new ReaderThread(updatesSupplier); readerThread.setName(callback.getBotUsername() + " Telegram Connection"); readerThread.start(); @@ -97,6 +98,10 @@ public class DefaultBotSession implements BotSession { } } + public void setUpdatesSupplier(UpdatesSupplier updatesSupplier) { + this.updatesSupplier = updatesSupplier; + } + @Override public void setOptions(BotOptions options) { if (this.options != null) { @@ -127,10 +132,16 @@ public class DefaultBotSession implements BotSession { } private class ReaderThread extends Thread implements UpdatesReader { + + private final UpdatesSupplier updatesSupplier; private CloseableHttpClient httpclient; private ExponentialBackOff exponentialBackOff; private RequestConfig requestConfig; + public ReaderThread(UpdatesSupplier updatesSupplier) { + this.updatesSupplier = Optional.ofNullable(updatesSupplier).orElse(this::getUpdatesFromServer); + } + @Override public synchronized void start() { httpclient = HttpClientBuilder.create() @@ -172,62 +183,23 @@ public class DefaultBotSession implements BotSession { setPriority(Thread.MIN_PRIORITY); while (running) { try { - GetUpdates request = new GetUpdates() - .setLimit(100) - .setTimeout(ApiConstants.GETUPDATES_TIMEOUT) - .setOffset(lastReceivedUpdate + 1); - - if (options.getAllowedUpdates() != null) { - request.setAllowedUpdates(options.getAllowedUpdates()); - } - - String url = options.getBaseUrl() + token + "/" + GetUpdates.PATH; - //http client - HttpPost httpPost = new HttpPost(url); - httpPost.addHeader("charset", StandardCharsets.UTF_8.name()); - httpPost.setConfig(requestConfig); - httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(request), ContentType.APPLICATION_JSON)); - - try (CloseableHttpResponse response = httpclient.execute(httpPost)) { - HttpEntity ht = response.getEntity(); - BufferedHttpEntity buf = new BufferedHttpEntity(ht); - String responseContent = EntityUtils.toString(buf, StandardCharsets.UTF_8); - - if (response.getStatusLine().getStatusCode() >= 500) { - BotLogger.warn(LOGTAG, responseContent); - synchronized (this) { - this.wait(500); - } - } else { - try { - List updates = request.deserializeResponse(responseContent); - exponentialBackOff.reset(); - - if (updates.isEmpty()) { - synchronized (this) { - this.wait(500); - } - } else { - updates.removeIf(x -> x.getUpdateId() < lastReceivedUpdate); - lastReceivedUpdate = updates.parallelStream() - .map( - Update::getUpdateId) - .max(Integer::compareTo) - .orElse(0); - receivedUpdates.addAll(updates); - - synchronized (receivedUpdates) { - receivedUpdates.notifyAll(); - } - } - } catch (JSONException e) { - BotLogger.severe(responseContent, LOGTAG, e); - } + List updates = updatesSupplier.getUpdates(); + if (updates.isEmpty()) { + synchronized (this) { + this.wait(500); + } + } else { + updates.removeIf(x -> x.getUpdateId() < lastReceivedUpdate); + lastReceivedUpdate = updates.parallelStream() + .map( + Update::getUpdateId) + .max(Integer::compareTo) + .orElse(0); + receivedUpdates.addAll(updates); + + synchronized (receivedUpdates) { + receivedUpdates.notifyAll(); } - } catch (SocketTimeoutException e) { - BotLogger.fine(LOGTAG, e); - } catch (InvalidObjectException | TelegramApiRequestException e) { - BotLogger.severe(LOGTAG, e); } } catch (InterruptedException e) { if (!running) { @@ -250,6 +222,64 @@ public class DefaultBotSession implements BotSession { } BotLogger.debug(LOGTAG, "Reader thread has being closed"); } + + private List getUpdatesFromServer() throws InterruptedException, Exception { + GetUpdates request = new GetUpdates() + .setLimit(100) + .setTimeout(ApiConstants.GETUPDATES_TIMEOUT) + .setOffset(lastReceivedUpdate + 1); + + if (options.getAllowedUpdates() != null) { + request.setAllowedUpdates(options.getAllowedUpdates()); + } + + String url = options.getBaseUrl() + token + "/" + GetUpdates.PATH; + //http client + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("charset", StandardCharsets.UTF_8.name()); + httpPost.setConfig(requestConfig); + httpPost.setEntity(new StringEntity(objectMapper.writeValueAsString(request), ContentType.APPLICATION_JSON)); + + try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + HttpEntity ht = response.getEntity(); + BufferedHttpEntity buf = new BufferedHttpEntity(ht); + String responseContent = EntityUtils.toString(buf, StandardCharsets.UTF_8); + + if (response.getStatusLine().getStatusCode() >= 500) { + BotLogger.warn(LOGTAG, responseContent); + synchronized (this) { + this.wait(500); + } + } else { + try { + List updates = request.deserializeResponse(responseContent); + exponentialBackOff.reset(); + return updates; + } catch (JSONException e) { + BotLogger.severe(responseContent, LOGTAG, e); + } + } + } catch (SocketTimeoutException e) { + BotLogger.fine(LOGTAG, e); + } catch (InvalidObjectException | TelegramApiRequestException e) { + BotLogger.severe(LOGTAG, e); + } + return Collections.emptyList(); + } + } + + public interface UpdatesSupplier { + + List getUpdates() throws InterruptedException, Exception; + } + + private List getUpdateList() { + List updates = new ArrayList<>(); + for (Iterator it = receivedUpdates.iterator(); it.hasNext();) { + updates.add(it.next()); + it.remove(); + } + return updates; } private class HandlerThread extends Thread implements UpdatesHandler { @@ -258,17 +288,17 @@ public class DefaultBotSession implements BotSession { setPriority(Thread.MIN_PRIORITY); while (running) { try { - Update update = receivedUpdates.pollLast(); - if (update == null) { + List updates = getUpdateList(); + if (updates.isEmpty()) { synchronized (receivedUpdates) { receivedUpdates.wait(); - update = receivedUpdates.pollLast(); - if (update == null) { + updates = getUpdateList(); + if (updates.isEmpty()) { continue; } } } - callback.onUpdateReceived(update); + callback.onUpdatesReceived(updates); } catch (InterruptedException e) { BotLogger.debug(LOGTAG, e); } catch (Exception e) { diff --git a/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramLongPollingBotTest.java b/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramLongPollingBotTest.java new file mode 100644 index 00000000..23dbfc6d --- /dev/null +++ b/telegrambots/src/test/java/org/telegram/telegrambots/test/TelegramLongPollingBotTest.java @@ -0,0 +1,24 @@ +package org.telegram.telegrambots.test; + +import org.junit.Test; +import org.mockito.Mockito; +import org.telegram.telegrambots.api.objects.Update; +import org.telegram.telegrambots.bots.TelegramLongPollingBot; + +import static java.util.Arrays.asList; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyList; + +public class TelegramLongPollingBotTest { + + @Test + public void testOnUpdateReceived() throws Exception { + TelegramLongPollingBot bot = Mockito.mock(TelegramLongPollingBot.class); + Mockito.doCallRealMethod().when(bot).onUpdatesReceived(any()); + Update update1 = new Update(); + Update update2 = new Update(); + bot.onUpdatesReceived(asList(update1, update2)); + Mockito.verify(bot).onUpdateReceived(update1); + Mockito.verify(bot).onUpdateReceived(update2); + } +} \ No newline at end of file diff --git a/telegrambots/src/test/java/org/telegram/telegrambots/test/TestDefaultBotSession.java b/telegrambots/src/test/java/org/telegram/telegrambots/test/TestDefaultBotSession.java index c37fd5a6..17d4aa65 100644 --- a/telegrambots/src/test/java/org/telegram/telegrambots/test/TestDefaultBotSession.java +++ b/telegrambots/src/test/java/org/telegram/telegrambots/test/TestDefaultBotSession.java @@ -11,12 +11,20 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Matchers; import org.mockito.Mockito; +import org.telegram.telegrambots.api.objects.Update; import org.telegram.telegrambots.bots.DefaultBotOptions; +import org.telegram.telegrambots.generics.LongPollingBot; import org.telegram.telegrambots.test.Fakes.FakeLongPollingBot; import org.telegram.telegrambots.updatesreceivers.DefaultBotSession; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.IntStream; /** * @author Ruben Bermudez @@ -79,7 +87,85 @@ public class TestDefaultBotSession { session.stop(); } + @Test + public void testUpdates() throws Exception { + LongPollingBot bot = Mockito.spy(new FakeLongPollingBot()); + session = getDefaultBotSession(bot); + AtomicInteger flag = new AtomicInteger(); + Update[] updates = createFakeUpdates(9); + session.setUpdatesSupplier(createFakeUpdatesSupplier(flag, updates)); + session.start(); + Thread.sleep(1000); + Mockito.verify(bot, Mockito.never()).onUpdateReceived(Matchers.any()); + flag.compareAndSet(0, 1); + Thread.sleep(1000); + Mockito.verify(bot).onUpdateReceived(updates[0]); + Mockito.verify(bot).onUpdateReceived(updates[1]); + flag.compareAndSet(2, 3); + Thread.sleep(1000); + Mockito.verify(bot).onUpdateReceived(updates[2]); + Mockito.verify(bot).onUpdateReceived(updates[3]); + Mockito.verify(bot).onUpdateReceived(updates[4]); + flag.compareAndSet(4, 5); + Thread.sleep(1000); + Mockito.verify(bot).onUpdateReceived(updates[5]); + Mockito.verify(bot).onUpdateReceived(updates[6]); + Mockito.verify(bot).onUpdateReceived(updates[7]); + Mockito.verify(bot).onUpdateReceived(updates[8]); + session.stop(); + } + + @Test + public void testBatchUpdates() throws Exception { + LongPollingBot bot = Mockito.spy(new FakeLongPollingBot()); + session = getDefaultBotSession(bot); + AtomicInteger flag = new AtomicInteger(); + Update[] updates = createFakeUpdates(9); + session.setUpdatesSupplier(createFakeUpdatesSupplier(flag, updates)); + session.start(); + Thread.sleep(1000); + Mockito.verify(bot, Mockito.never()).onUpdateReceived(Matchers.any()); + flag.compareAndSet(0, 1); + Thread.sleep(1000); + Mockito.verify(bot).onUpdatesReceived(Arrays.asList(updates[0], updates[1])); + flag.compareAndSet(2, 3); + Thread.sleep(1000); + Mockito.verify(bot).onUpdatesReceived(Arrays.asList(updates[2], updates[3], updates[4])); + flag.compareAndSet(4, 5); + Thread.sleep(1000); + Mockito.verify(bot).onUpdatesReceived(Arrays.asList(updates[5], updates[6], updates[7], updates[8])); + session.stop(); + } + + private Update[] createFakeUpdates(int count) { + return IntStream.range(0, count).mapToObj(x -> { + Update mock = Mockito.mock(Update.class); + Mockito.when(mock.getUpdateId()).thenReturn(x); + return mock; + }).toArray(Update[]::new); + } + + private DefaultBotSession.UpdatesSupplier createFakeUpdatesSupplier(AtomicInteger flag, Update[] updates) { + return new DefaultBotSession.UpdatesSupplier() { + @Override + public List getUpdates() throws InterruptedException, Exception { + if (flag.compareAndSet(1, 2)) { + return Arrays.asList(updates[0], updates[1]); + } else if (flag.compareAndSet(3, 4)) { + return Arrays.asList(updates[2], updates[3], updates[4]); + } else if (flag.compareAndSet(5, 6)) { + return Arrays.asList(updates[5], updates[6], updates[7], updates[8]); + } + return Collections.emptyList(); + } + }; + } + private DefaultBotSession getDefaultBotSession() throws IOException { + return getDefaultBotSession(new FakeLongPollingBot()); + } + + private DefaultBotSession getDefaultBotSession(LongPollingBot bot) throws IOException { HttpResponse response = new BasicHttpResponse(new BasicStatusLine( new ProtocolVersion("HTTP", 1, 1), 200, "")); response.setStatusCode(200); @@ -89,7 +175,7 @@ public class TestDefaultBotSession { Mockito.when(mockHttpClient.execute(Mockito.any(HttpPost.class))) .thenReturn(response); DefaultBotSession session = new DefaultBotSession(); - session.setCallback(new FakeLongPollingBot()); + session.setCallback(bot); session.setOptions(new DefaultBotOptions()); return session; }