Issues (369)

telegram/framework/bots/PollingTelegramBot.java (82 issues)

1
package com.github.netkorp.telegram.framework.bots;
0 ignored issues
show
It is a best practice to supply a copyright/licence header in your code. Your organisation probably has a template for that.
Loading history...
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.exceptions.UserNotAuthorized;
11
import com.github.netkorp.telegram.framework.managers.CommandManager;
12
import com.github.netkorp.telegram.framework.managers.SecurityManager;
13
import org.slf4j.Logger;
14
import org.slf4j.LoggerFactory;
15
import org.springframework.beans.factory.annotation.Autowired;
16
import org.springframework.beans.factory.annotation.Value;
17
import org.springframework.context.annotation.Lazy;
18
import org.springframework.stereotype.Component;
19
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
20
import org.telegram.telegrambots.meta.api.methods.BotApiMethod;
21
import org.telegram.telegrambots.meta.api.methods.send.SendMessage;
22
import org.telegram.telegrambots.meta.api.objects.Update;
23
import org.telegram.telegrambots.meta.exceptions.TelegramApiException;
24
25
import java.lang.invoke.MethodHandles;
26
import java.util.AbstractMap;
27
import java.util.Arrays;
28
import java.util.Map;
29
import java.util.Optional;
30
31
/**
32
 * Provides the component for sharing information with Telegram using
33
 * <a href="https://core.telegram.org/bots/api#getupdates">long-polling</a> method.
34
 * It has the responsibility to execute the proper command when an incoming message is received.
35
 */
36
@Component
37
public class PollingTelegramBot extends TelegramLongPollingBot {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
38
39
    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
0 ignored issues
show
Make this line start at column 3.
Loading history...
40
41
    /**
42
     * The bot's username.
43
     */
44
    private String botUsername;
45
46
    /**
47
     * The bot's token.
48
     */
49
    private String botToken;
50
51
    /**
52
     * The component to know which user is authorized.
53
     */
54
    private final SecurityManager securityManager;
55
56
    /**
57
     * The component for managing all of the available commands in the bot.
58
     */
59
    private final CommandManager commandManager;
60
61
    /**
62
     * Constructs a new {@link PollingTelegramBot} instance with both username and token of the bot,
63
     * the {@link SecurityManager} component instance and the {@link CommandManager} instance.
64
     *
65
     * @param botUsername     the username of the bot.
66
     * @param botToken        the token of the bot.
67
     * @param securityManager the {@link SecurityManager} component instance.
68
     * @param commandManager  the {@link CommandManager} instance.
69
     */
70
    @Autowired
71
    public PollingTelegramBot(@Value("${telegram.bots.username}") String botUsername,
72
                              @Value("${telegram.bots.token}") String botToken,
73
                              SecurityManager securityManager,
74
                              @Lazy CommandManager commandManager) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
75
        this.botUsername = botUsername;
0 ignored issues
show
Make this line start at column 5.
Loading history...
76
        this.botToken = botToken;
77
        this.securityManager = securityManager;
78
        this.commandManager = commandManager;
79
    }
80
81
    /**
82
     * This method is called when receiving updates via GetUpdates method.
83
     *
84
     * @param update Update received.
85
     */
86
    @Override
87
    public void onUpdateReceived(Update update) {
0 ignored issues
show
Comprehensibility introduced by
The Cyclomatic Complexity of this method "onUpdateReceived" is 11 which is greater than 10 authorized.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
Refactor this method to reduce its Cognitive Complexity from 33 to the 15 allowed.
Loading history...
88
        // We check if the update has a message and the message has text
89
        if (update.hasMessage()) {
0 ignored issues
show
Make this line start at column 5.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
90
            Long chatId = update.getMessage().getChatId();
0 ignored issues
show
Make this line start at column 7.
Loading history...
91
92
            if (commandManager.hasActiveCommand(chatId)) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
93
                if (!update.getMessage().isCommand()
0 ignored issues
show
Make this line start at column 9.
Loading history...
94
                        || !reservedCommands(getCleanedCommand(update), update)) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
95
                    try {
0 ignored issues
show
Make this line start at column 11.
Loading history...
Comprehensibility introduced by
Refactor this code to not nest more than 3 if/for/while/switch/try statements.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
96
                        commandManager.getActiveCommand(chatId).execute(update);
0 ignored issues
show
Make this line start at column 13.
Loading history...
97
                    } catch (CommandNotActive commandNotActive) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "catch" keyword to a new dedicated line.
Loading history...
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
98
                        // Do nothing. This point is impossible to reach.
99
                    }
100
                }
101
102
                return;
103
            }
104
105
            // Checking if this is a command
106
            if (update.getMessage().isCommand()) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
107
                try {
0 ignored issues
show
Make this line start at column 9.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
108
                    Map.Entry<String, String[]> commandAndArgs = getCleanedCommandAndArgs(update);
0 ignored issues
show
Make this line start at column 11.
Loading history...
109
                    Command command = commandManager.getCommand(commandAndArgs.getKey());
110
111
                    if (!securityManager.isAuthorized(chatId, command)) {
0 ignored issues
show
Comprehensibility introduced by
Refactor this code to not nest more than 3 if/for/while/switch/try statements.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
112
                        throw new UserNotAuthorized();
0 ignored issues
show
Make this line start at column 13.
Loading history...
As defined in your coding style, remove the checked exception: UserNotAuthorized. Consider throwing an unchecked exception instead.

Opionions on checked exceptions are divided.

On the positive side they do make code safer and provide self-documentation on possible error conditions that the caller has to be aware of.

On the negative side, they do force the caller to deal with these errors, either by catching or specifying them in the method definition. This may make the code more verbose than necessary.

The Java documentation recommends using them wisely.

Loading history...
113
                    }
114
115
                    if (command instanceof MultistageCommand && ((MultistageCommand) command).init(update)) {
0 ignored issues
show
Comprehensibility introduced by
Refactor this code to not nest more than 3 if/for/while/switch/try statements.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
116
                        commandManager.setActiveCommand(chatId, ((MultistageCommand) command));
0 ignored issues
show
Make this line start at column 13.
Loading history...
117
                    } else if (command instanceof SimpleCommand) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "else" keyword to a new dedicated line.
Loading history...
118
                        if (commandAndArgs.getValue().length == 0) {
0 ignored issues
show
Make this line start at column 13.
Loading history...
Move this left curly brace to the beginning of next line of code.
Loading history...
119
                            ((SimpleCommand) command).execute(update);
0 ignored issues
show
Make this line start at column 15.
Loading history...
120
                        } else {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "else" keyword to a new dedicated line.
Loading history...
121
                            ((SimpleCommand) command).execute(update, commandAndArgs.getValue());
0 ignored issues
show
Make this line start at column 15.
Loading history...
122
                        }
123
                    }
124
                } catch (CommandNotFound commandNotFound) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "catch" keyword to a new dedicated line.
Loading history...
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
125
                    sendMessage(commandNotFound.getMessage(), chatId);
0 ignored issues
show
Make this line start at column 11.
Loading history...
126
                    commandManager.getHelpCommand()
127
                            .filter(command -> securityManager.isAuthorized(chatId, command))
0 ignored issues
show
Specify a type for: 'command'
Loading history...
128
                            .ifPresent(command -> command.execute(update));
0 ignored issues
show
Specify a type for: 'command'
Loading history...
129
                } catch (UserNotAuthorized userNotAuthorized) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "catch" keyword to a new dedicated line.
Loading history...
Ignoring an Exception may lead to hard to find bugs. Consider logging or rethrowing the original exception. If you want to throw a different exception, you can set the original exception as its cause to preserve the stacktrace.

When instantiating a new Exception, you can set another Exception as its cause.

See the Oracle documentation on Throwables.

Usage example

throw new Exception("Exception Message", originalException);

Complete Example:

class ReThrowException {
  public static void throwsException() {
        try {
            throw new Exception("I am the original exception");
        } catch (final Exception e) {
            throw new RuntimeException("I am the new exception", e);
        }
    }
    public static void main(String[] args) {
        try {
            throwsException();
        }
        catch (final RuntimeException e) {
            System.out.println(e.getMessage());
            System.out.println("and my cause is: " + e.getCause().getMessage());
            e.printStackTrace();
        }
    }
}
Loading history...
130
                    sendMessage(userNotAuthorized.getMessage(), chatId);
0 ignored issues
show
Make this line start at column 11.
Loading history...
131
                }
132
            } else {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "else" keyword to a new dedicated line.
Loading history...
133
                sendMessage("That is not a command", chatId);
0 ignored issues
show
Make this line start at column 9.
Loading history...
134
            }
135
        }
136
    }
137
138
    /**
139
     * Returns the command invoked by the user and the parameters, cleaning the text and deleting the bot's username.
140
     *
141
     * @param update the received update.
142
     * @return the command and the parameters.
143
     */
144
    private Map.Entry<String, String[]> getCleanedCommandAndArgs(Update update) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
145
        String cleanedCommand = getCleanedCommand(update);
0 ignored issues
show
Make this line start at column 5.
Loading history...
146
147
        String[] dividedText = cleanedCommand.split(" ");
148
149
        return new AbstractMap.SimpleEntry<>(dividedText[0],
150
                Arrays.copyOfRange(dividedText, 1, dividedText.length));
151
    }
152
153
    /**
154
     * Returns the command invoked by the user, cleaning the text and deleting the bot's username.
155
     *
156
     * @param update the received update.
157
     * @return the command.
158
     */
159
    private String getCleanedCommand(Update update) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
160
        return update.getMessage().getText().toLowerCase()
0 ignored issues
show
Define the locale to be used in this String operation.
Loading history...
Make this line start at column 5.
Loading history...
161
                .replace(String.format("@%s", getBotUsername().toLowerCase()), "");
0 ignored issues
show
Define the locale to be used in this String operation.
Loading history...
162
    }
163
164
    /**
165
     * Returns the bot's username.
166
     *
167
     * @return the bot's username.
168
     */
169
    @Override
170
    public String getBotUsername() {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
171
        return botUsername;
0 ignored issues
show
Make this line start at column 5.
Loading history...
172
    }
173
174
    /**
175
     * Returns the bot's token.
176
     *
177
     * @return the bot's token.
178
     */
179
    @Override
180
    public String getBotToken() {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
181
        return botToken;
0 ignored issues
show
Make this line start at column 5.
Loading history...
182
    }
183
184
    /**
185
     * Checks if the entered text matches with some reserved command.
186
     * If this is the case it will execute the corresponding command.
187
     *
188
     * @param commandText the text entered by the user.
189
     * @param update      the message sent by the user.
190
     * @return {@code true} if some reserved command was executed; {@code false} otherwise.
191
     */
192
    private boolean reservedCommands(String commandText, Update update) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
193
        Optional<MultistageCloseCommand> closeCommand = commandManager.getCloseCommand()
0 ignored issues
show
Make this line start at column 5.
Loading history...
194
                .filter(command -> CommandManager.getCommandFullNames(command).get(0).equals(commandText));
0 ignored issues
show
Specify a type for: 'command'
Loading history...
195
196
        if (closeCommand.isPresent()) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
197
            closeCommand.get().execute(update);
0 ignored issues
show
Make this line start at column 7.
Loading history...
198
            return true;
199
        }
200
201
        Optional<MultistageDoneCommand> doneCommand = commandManager.getDoneCommand()
202
                .filter(command -> CommandManager.getCommandFullNames(command).get(0).equals(commandText));
0 ignored issues
show
Specify a type for: 'command'
Loading history...
203
204
        if (doneCommand.isPresent()) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
205
            doneCommand.get().execute(update);
0 ignored issues
show
Make this line start at column 7.
Loading history...
206
            return true;
207
        }
208
209
        return false;
210
    }
211
212
    /**
213
     * Sends a text message to Telegram.
214
     * This is a shortcut for {@link #sendMessage(String, Long, boolean)} with HTML format disabled.
215
     *
216
     * @param content the message content.
217
     * @param idChat  the chat identification to which the message should be sent.
218
     */
219
    public void sendMessage(String content, Long idChat) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
220
        sendMessage(content, idChat, false);
0 ignored issues
show
Make this line start at column 5.
Loading history...
221
    }
222
223
    /**
224
     * Sends a text message to Telegram. This is a shortcut for {@link #execute(BotApiMethod)}.
225
     *
226
     * @param content the message content.
227
     * @param idChat  the chat identification to which the message should be sent.
228
     * @param html    {@code true} if HTML format is enabled or {@code false} otherwise.
229
     */
230
    public void sendMessage(String content, Long idChat, boolean html) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
231
        SendMessage message = new SendMessage() // Create a SendMessage object with mandatory fields
0 ignored issues
show
Make this line start at column 5.
Loading history...
As per coding-style, please move this trailing comment to its own line above this one.
Loading history...
232
                .setChatId(idChat).setText(content).enableHtml(html);
233
234
        try {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
235
            this.execute(message); // Call method to send the message
0 ignored issues
show
Make this line start at column 7.
Loading history...
As per coding-style, please move this trailing comment to its own line above this one.
Loading history...
236
        } catch (TelegramApiException e) {
0 ignored issues
show
Move this left curly brace to the beginning of next line of code.
Loading history...
Move this "catch" keyword to a new dedicated line.
Loading history...
237
            LOG.error(e.getMessage(), e);
0 ignored issues
show
Make this line start at column 7.
Loading history...
238
        }
239
    }
240
}
241