Completed
Push — master ( d5f627...ec853f )
by Roannel Fernández
03:05
created

getBotUsername()   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
eloc 3
rs 10
cc 1
1
package com.github.netkorp.telegram.framework.bots;
2
3
import com.github.netkorp.telegram.framework.commands.interfaces.Command;
4
import com.github.netkorp.telegram.framework.commands.interfaces.MultistageCommand;
5
import com.github.netkorp.telegram.framework.commands.interfaces.SimpleCommand;
6
import com.github.netkorp.telegram.framework.commands.multistage.MultistageCloseCommand;
7
import com.github.netkorp.telegram.framework.commands.multistage.MultistageDoneCommand;
8
import com.github.netkorp.telegram.framework.exceptions.CommandNotActive;
9
import com.github.netkorp.telegram.framework.exceptions.CommandNotFound;
10
import com.github.netkorp.telegram.framework.managers.CommandManager;
11
import com.github.netkorp.telegram.framework.managers.SecurityManager;
12
import org.slf4j.Logger;
13
import org.slf4j.LoggerFactory;
14
import org.springframework.beans.factory.annotation.Autowired;
15
import org.springframework.beans.factory.annotation.Value;
16
import org.springframework.context.annotation.Lazy;
17
import org.springframework.stereotype.Component;
18
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
19
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
20
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
21
import org.telegram.telegrambots.meta.api.objects.Update;
22
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
23
24
import java.lang.invoke.MethodHandles;
25
import java.util.AbstractMap;
26
import java.util.Arrays;
27
import java.util.Map;
28
import java.util.Optional;
29
30
/**
31
 * Provides the component for sharing information with Telegram using
32
 * <a href="https://core.telegram.org/bots/api#getupdates">long-polling</a> method.
33
 * It has the responsibility to execute the proper command when an incoming message is received.
34
 */
35
@Component
36
public class PollingTelegramBot extends TelegramLongPollingBot {
37
38
    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
39
40
    /**
41
     * The bot's username.
42
     */
43
    private String botUsername;
44
45
    /**
46
     * The bot's token.
47
     */
48
    private String botToken;
49
50
    /**
51
     * The component to know which user is authorized.
52
     */
53
    private final SecurityManager securityManager;
54
55
    /**
56
     * The component for managing all of the available commands in the bot.
57
     */
58
    private final CommandManager commandManager;
59
60
    /**
61
     * Constructs a new {@link PollingTelegramBot} instance with both username and token of the bot,
62
     * the {@link SecurityManager} component instance and the {@link CommandManager} instance.
63
     *
64
     * @param botUsername     the username of the bot.
65
     * @param botToken        the token of the bot.
66
     * @param securityManager the {@link SecurityManager} component instance.
67
     * @param commandManager  the {@link CommandManager} instance.
68
     */
69
    @Autowired
70
    public PollingTelegramBot(@Value("${telegram.bots.username}") String botUsername,
71
                              @Value("${telegram.bots.token}") String botToken,
72
                              SecurityManager securityManager,
73
                              @Lazy CommandManager commandManager) {
74
        this.botUsername = botUsername;
75
        this.botToken = botToken;
76
        this.securityManager = securityManager;
77
        this.commandManager = commandManager;
78
    }
79
80
    /**
81
     * This method is called when receiving updates via GetUpdates method.
82
     *
83
     * @param update Update received.
84
     */
85
    @Override
86
    public void onUpdateReceived(Update update) {
87
        // We check if the update has a message and the message has text
88
        if (update.hasMessage()) {
89
            Long idChat = update.getMessage().getChatId();
90
91
            try {
92
                // Checking if this is a command
93
                if (update.getMessage().hasText()) {
94
                    Map.Entry<String, String[]> command = getCommand(update);
95
96
                    // Checking if it's a non-secure command
97
                    try {
98
                        Command commandInstance = commandManager.getNonSecureCommand(command.getKey());
99
                        if (commandInstance instanceof SimpleCommand) {
100
                            executeSimpleCommand((SimpleCommand) commandInstance, command.getValue(), update);
101
                        }
102
                        return;
103
                    } catch (CommandNotFound commandNotFound) {
104
                        // Do nothing
105
                    }
106
                }
107
108
                // Checking if the chat is authorized
109
                if (securityManager.isAuthorized(idChat)) {
110
                    processMessage(update);
111
                }
112
            } catch (CommandNotFound commandNotFound) {
113
                sendMessage(commandNotFound.getMessage(), idChat);
114
                commandManager.getHelpCommand().ifPresent(command -> command.execute(update));
115
            }
116
        }
117
    }
118
119
    /**
120
     * Returns the command invoked by the user from the message sent by him, cleaning the text and deleting the bot's username.
121
     *
122
     * @param update the received update.
123
     * @return the command and the parameters.
124
     */
125
    private Map.Entry<String, String[]> getCommand(Update update) {
126
        String cleanedCommand = update.getMessage().getText().toLowerCase()
127
                .replace(String.format("@%s", getBotUsername().toLowerCase()), "");
128
129
        String[] dividedText = cleanedCommand.split(" ");
130
131
        return new AbstractMap.SimpleEntry<>(dividedText[0],
132
                Arrays.copyOfRange(dividedText, 1, dividedText.length));
133
    }
134
135
    /**
136
     * Returns the bot's username.
137
     *
138
     * @return the bot's username.
139
     */
140
    @Override
141
    public String getBotUsername() {
142
        return botUsername;
143
    }
144
145
    /**
146
     * Returns the bot's token.
147
     *
148
     * @return the bot's token.
149
     */
150
    @Override
151
    public String getBotToken() {
152
        return botToken;
153
    }
154
155
    /**
156
     * Processes a given message.
157
     *
158
     * @param update the message sent by the user.
159
     */
160
    private void processMessage(Update update) throws CommandNotFound {
161
        final Long idChat = update.getMessage().getChatId();
162
163
        // Perhaps is a command
164
        if (update.getMessage().hasText()) {
165
            Map.Entry<String, String[]> commandText = getCommand(update);
166
167
            if (reservedCommands(commandText.getKey(), update)) {
168
                return;
169
            }
170
171
            // Trying to get a command
172
            try {
173
                Command command = commandManager.getCommand(commandText.getKey());
174
175
                // If there is no active command defined, we understand this is an attempt to define/execute one
176
                if (!commandManager.hasActiveCommand(idChat)) {
177
178
                    if (command instanceof MultistageCommand) {
179
                        if (((MultistageCommand) command).init(update)) {
180
                            commandManager.setActiveCommand(idChat, ((MultistageCommand) command));
181
                        }
182
                    } else if (command instanceof SimpleCommand) {
183
                        executeSimpleCommand((SimpleCommand) command, commandText.getValue(), update);
184
                    }
185
186
                    return;
187
                }
188
            } catch (CommandNotFound commandNotFound) {
189
                // Perhaps we're talking about data here. If there is an active command, we leave it to it.
190
                if (!commandManager.hasActiveCommand(idChat)) {
191
                    throw commandNotFound;
192
                }
193
            }
194
        }
195
196
        // If there is an active command, we handle the messages as data
197
        try {
198
            commandManager.getActiveCommand(idChat).execute(update);
199
        } catch (CommandNotActive commandNotActive) {
200
            sendMessage(commandNotActive.getMessage(), idChat);
201
        }
202
    }
203
204
    /**
205
     * Executes a {@link SimpleCommand} attending if there are parameters or not.
206
     *
207
     * @param command the command to be executed.
208
     * @param args    the parameters passed to the command execution.
209
     * @param update  the message sent by the user.
210
     */
211
    private void executeSimpleCommand(SimpleCommand command, String[] args, Update update) {
212
        if (args.length == 0) {
213
            command.execute(update);
214
        } else {
215
            command.execute(update, args);
216
        }
217
    }
218
219
    /**
220
     * Checks if the entered text matches with some reserved command.
221
     * If this is the case it will execute the corresponding command.
222
     *
223
     * @param commandText the text entered by the user.
224
     * @param update      the message sent by the user.
225
     * @return {@code true} if some reserved command was executed; {@code false} otherwise.
226
     */
227
    private boolean reservedCommands(String commandText, Update update) {
228
        Optional<MultistageCloseCommand> closeCommand = commandManager.getCloseCommand()
229
                .filter(command -> CommandManager.getCommandFullName(command).equals(commandText));
230
231
        if (closeCommand.isPresent()) {
232
            closeCommand.get().execute(update);
233
            return true;
234
        }
235
236
        Optional<MultistageDoneCommand> doneCommand = commandManager.getDoneCommand()
237
                .filter(command -> CommandManager.getCommandFullName(command).equals(commandText));
238
239
        if (doneCommand.isPresent()) {
240
            doneCommand.get().execute(update);
241
            return true;
242
        }
243
244
        return false;
245
    }
246
247
    /**
248
     * Sends a text message to Telegram.
249
     * This is a shortcut for {@link #sendMessage(String, Long, boolean)} with HTML format disabled.
250
     *
251
     * @param content the message content.
252
     * @param idChat  the chat identification to which the message should be sent.
253
     */
254
    public void sendMessage(String content, Long idChat) {
255
        sendMessage(content, idChat, false);
256
    }
257
258
    /**
259
     * Sends a text message to Telegram. This is a shortcut for {@link #execute(BotApiMethod)}.
260
     *
261
     * @param content the message content.
262
     * @param idChat  the chat identification to which the message should be sent.
263
     * @param html    {@code true} if HTML format is enabled or {@code false} otherwise.
264
     */
265
    public void sendMessage(String content, Long idChat, boolean html) {
266
        SendMessage message = new SendMessage() // Create a SendMessage object with mandatory fields
267
                .setChatId(idChat).setText(content).enableHtml(html);
268
269
        try {
270
            this.execute(message); // Call method to send the message
271
        } catch (TelegramApiException e) {
272
            LOG.error(e.getMessage(), e);
273
        }
274
    }
275
}
276