diff --git a/src/main/java/org/telegram/telegrambots/TelegramBotsApi.java b/src/main/java/org/telegram/telegrambots/TelegramBotsApi.java index 17f37ff2..824fc051 100644 --- a/src/main/java/org/telegram/telegrambots/TelegramBotsApi.java +++ b/src/main/java/org/telegram/telegrambots/TelegramBotsApi.java @@ -126,7 +126,7 @@ public class TelegramBotsApi { /** * Register a bot. The Bot Session is started immediately, and may be disconnected by calling close. - * @param bot + * @param bot the bot to register */ public BotSession registerBot(TelegramLongPollingBot bot) throws TelegramApiException { setWebhook(bot.getBotToken()); diff --git a/src/main/java/org/telegram/telegrambots/api/commands/BotCommand.java b/src/main/java/org/telegram/telegrambots/api/commands/BotCommand.java new file mode 100644 index 00000000..b22bfde6 --- /dev/null +++ b/src/main/java/org/telegram/telegrambots/api/commands/BotCommand.java @@ -0,0 +1,70 @@ +package org.telegram.telegrambots.api.commands; + +import org.telegram.telegrambots.api.objects.Chat; +import org.telegram.telegrambots.bots.AbsSender; + +/** + * Representation of a command, which can be executed + * + * @author tschulz + */ +public abstract class BotCommand { + + public final static String COMMAND_INIT_CHARACTER = "/"; + public final static String COMMAND_PARAMETER_SEPARATOR = " "; + private final static int COMMAND_MAX_LENGTH = 32; + + private final String commandIdentifier; + private final String description; + + /** + * construct a command + * + * @param commandIdentifier the unique identifier of this command (e.g. the command string to enter into chat) + * @param description the description of this command + */ + public BotCommand(String commandIdentifier, String description) { + + if (commandIdentifier == null || commandIdentifier.isEmpty()) { + throw new IllegalArgumentException("commandIdentifier for command cannot be null or empty"); + } + + if (commandIdentifier.startsWith(COMMAND_INIT_CHARACTER)) { + commandIdentifier = commandIdentifier.substring(1); + } + + if (commandIdentifier.length() + 1 > COMMAND_MAX_LENGTH) { + throw new IllegalArgumentException("commandIdentifier cannot be longer than " + COMMAND_MAX_LENGTH + " (including " + COMMAND_INIT_CHARACTER + ")"); + } + + this.commandIdentifier = commandIdentifier.toLowerCase(); + this.description = description; + } + + /** + * get the identifier of this command + * + * @return the identifier + */ + public final String getCommandIdentifier() { + return commandIdentifier; + } + + /** + * get the description of this command + * + * @return the description as String + */ + public final String getDescription() { + return description; + } + + /** + * execute the command + * + * @param absSender absSender to send messages over + * @param chat the chat, to be able to send replies + * @param arguments passed arguments + */ + public abstract void execute(AbsSender absSender, Chat chat, String[] arguments); +} \ No newline at end of file diff --git a/src/main/java/org/telegram/telegrambots/api/commands/CommandRegistry.java b/src/main/java/org/telegram/telegrambots/api/commands/CommandRegistry.java new file mode 100644 index 00000000..8e176508 --- /dev/null +++ b/src/main/java/org/telegram/telegrambots/api/commands/CommandRegistry.java @@ -0,0 +1,87 @@ +package org.telegram.telegrambots.api.commands; + +import org.telegram.telegrambots.api.objects.Message; +import org.telegram.telegrambots.bots.AbsSender; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author tschulz + */ +public final class CommandRegistry implements ICommandRegistry { + + private final Map commandRegistryMap = new HashMap<>(); + + public CommandRegistry(String botToken) { + register(new HelpBotCommand(this, botToken)); + } + + @Override + public final boolean register(BotCommand botCommand) { + if (commandRegistryMap.containsKey(botCommand.getCommandIdentifier())) { + return false; + } + commandRegistryMap.put(botCommand.getCommandIdentifier(), botCommand); + return true; + } + + @Override + public final Map registerAll(BotCommand... botCommands) { + Map resultMap = new HashMap<>(botCommands.length); + for (BotCommand botCommand : botCommands) { + resultMap.put(botCommand, register(botCommand)); + } + return resultMap; + } + + @Override + public final boolean deregister(BotCommand botCommand) { + if (commandRegistryMap.containsKey(botCommand.getCommandIdentifier())) { + commandRegistryMap.remove(botCommand.getCommandIdentifier()); + return true; + } + return false; + } + + @Override + public final Map deregisterAll(BotCommand... botCommands) { + Map resultMap = new HashMap<>(botCommands.length); + for (BotCommand botCommand : botCommands) { + resultMap.put(botCommand, deregister(botCommand)); + } + return resultMap; + } + + @Override + public final Collection getRegisteredCommands() { + return commandRegistryMap.values(); + } + + /** + * executes a command if present and replies the success + * + * @param message input message + * @return true if success or false otherwise + */ + public final boolean executeCommand(AbsSender absSender, Message message) { + if (message.hasText()) { + String text = message.getText(); + if (!text.isEmpty() && text.startsWith(BotCommand.COMMAND_INIT_CHARACTER)) { + String commandMessage = text.substring(1); + String[] commandSplit = commandMessage.split(BotCommand.COMMAND_PARAMETER_SEPARATOR); + + String command = commandSplit[0]; + + if (commandRegistryMap.containsKey(command)) { + String[] parameters = Arrays.copyOfRange(commandSplit, 1, commandSplit.length); + commandRegistryMap.get(command).execute(absSender, message.getChat(), parameters); + return true; + } + } + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/telegram/telegrambots/api/commands/HelpBotCommand.java b/src/main/java/org/telegram/telegrambots/api/commands/HelpBotCommand.java new file mode 100644 index 00000000..2c02af26 --- /dev/null +++ b/src/main/java/org/telegram/telegrambots/api/commands/HelpBotCommand.java @@ -0,0 +1,40 @@ +package org.telegram.telegrambots.api.commands; + +import org.telegram.telegrambots.TelegramApiException; +import org.telegram.telegrambots.api.methods.send.SendMessage; +import org.telegram.telegrambots.api.objects.Chat; +import org.telegram.telegrambots.bots.AbsSender; +import org.telegram.telegrambots.logging.BotLogger; + +/** + * standard help command, which gets registered by default, to supply a list of all available commands + * + * @author tschulz + */ +public class HelpBotCommand extends BotCommand { + + private static final String LOGTAG = "HELPCOMMAND"; + private final ICommandRegistry commandRegistry; + + public HelpBotCommand(ICommandRegistry commandRegistry, String botToken) { + super("help", "Gives an overview over all Commands registered for this bot"); + this.commandRegistry = commandRegistry; + } + + @Override + public void execute(AbsSender absSender, Chat chat, String[] arguments) { + + for (BotCommand registeredBotCommand : commandRegistry.getRegisteredCommands()) { + SendMessage sendMessage = new SendMessage(); + sendMessage.setChatId(chat.getId().toString()); + sendMessage.enableHtml(true); + sendMessage.setText("" + COMMAND_INIT_CHARACTER + registeredBotCommand.getCommandIdentifier() + "\n" + registeredBotCommand.getDescription()); + + try { + absSender.sendMessage(sendMessage); + } catch (TelegramApiException e) { + BotLogger.error("Failed to send HelpMessage", LOGTAG, e); + } + } + } +} diff --git a/src/main/java/org/telegram/telegrambots/api/commands/ICommandRegistry.java b/src/main/java/org/telegram/telegrambots/api/commands/ICommandRegistry.java new file mode 100644 index 00000000..06db1d88 --- /dev/null +++ b/src/main/java/org/telegram/telegrambots/api/commands/ICommandRegistry.java @@ -0,0 +1,50 @@ +package org.telegram.telegrambots.api.commands; + +import java.util.Collection; +import java.util.Map; + +/** + * + */ +public interface ICommandRegistry { + + /** + * register a command + * + * @param botCommand the command to register + * @return whether the command could be registered, was not already registered + */ + boolean register(BotCommand botCommand); + + /** + * register multiple commands + * + * @param botCommands commands to register + * @return map with results of the command register per command + */ + Map registerAll(BotCommand... botCommands); + + /** + * deregister a command + * + * @param botCommand the command to deregister + * @return whether the command could be deregistered, was registered + */ + boolean deregister(BotCommand botCommand); + + /** + * deregister multiple commands + * + * @param botCommands commands to deregister + * @return map with results of the command deregistered per command + */ + Map deregisterAll(BotCommand... botCommands); + + /** + * get a collection of all registered commands + * + * @return a collection of registered commands + */ + Collection getRegisteredCommands(); + +} \ No newline at end of file diff --git a/src/main/java/org/telegram/telegrambots/api/objects/Message.java b/src/main/java/org/telegram/telegrambots/api/objects/Message.java index b5029148..f32a8c15 100644 --- a/src/main/java/org/telegram/telegrambots/api/objects/Message.java +++ b/src/main/java/org/telegram/telegrambots/api/objects/Message.java @@ -381,6 +381,17 @@ public class Message implements IBotApiObject { return text != null && !text.isEmpty(); } + public boolean isCommand() { + if (entities != null) { + for (MessageEntity entity : entities) { + if (entity != null && "bot_command".equals(entity.getType())) { + return text != null && !text.isEmpty(); + } + } + } + return false; + } + public boolean hasDocument() { return this.document != null; } diff --git a/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingCommandBot.java b/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingCommandBot.java new file mode 100644 index 00000000..17c0b0a8 --- /dev/null +++ b/src/main/java/org/telegram/telegrambots/bots/TelegramLongPollingCommandBot.java @@ -0,0 +1,88 @@ +package org.telegram.telegrambots.bots; + + +import org.telegram.telegrambots.TelegramApiException; +import org.telegram.telegrambots.api.commands.BotCommand; +import org.telegram.telegrambots.api.commands.CommandRegistry; +import org.telegram.telegrambots.api.commands.ICommandRegistry; +import org.telegram.telegrambots.api.methods.send.SendMessage; +import org.telegram.telegrambots.api.objects.Message; +import org.telegram.telegrambots.api.objects.Update; +import org.telegram.telegrambots.logging.BotLogger; + +import java.util.Collection; +import java.util.Map; + +/** + * This class adds command functionality to the TelegramLongPollingBot + * + * @author tschulz + */ +public abstract class TelegramLongPollingCommandBot extends TelegramLongPollingBot implements ICommandRegistry { + + public static final String LOGTAG = "TelegramLongPollingCommandBot"; + private final CommandRegistry commandRegistry; + + /** + * construct creates CommandRegistry for this bot. + * Use ICommandRegistry's methods on this bot to register commands + */ + public TelegramLongPollingCommandBot() { + this.commandRegistry = new CommandRegistry(getBotToken()); + } + + @Override + public final void onUpdateReceived(Update update) { + if (update.hasMessage()) { + Message message = update.getMessage(); + if (message.isCommand()) { + if (!commandRegistry.executeCommand(this, message)) { + SendMessage sendMessage = new SendMessage(); + sendMessage.setChatId(message.getChatId().toString()); + sendMessage.setText("The command you provided is not registered for this bot"); + try { + sendMessage(sendMessage); + } catch (TelegramApiException e) { + BotLogger.error("Cannot send message", LOGTAG, e); + } + } + return; + } + } + processNonCommandUpdate(update); + } + + @Override + public final boolean register(BotCommand botCommand) { + return commandRegistry.register(botCommand); + } + + @Override + public final Map registerAll(BotCommand... botCommands) { + return commandRegistry.registerAll(botCommands); + } + + @Override + public final boolean deregister(BotCommand botCommand) { + return commandRegistry.deregister(botCommand); + } + + @Override + public final Map deregisterAll(BotCommand... botCommands) { + return commandRegistry.deregisterAll(botCommands); + } + + @Override + public final Collection getRegisteredCommands() { + return commandRegistry.getRegisteredCommands(); + } + + /** + * Process all updates, that are not commands. + * Attention: commands, that have valid syntax but are not registered on this bot, + * won't be forwarded to this method! + * + * @param update the update + */ + public abstract void processNonCommandUpdate(Update update); +}