Spring Boot Starter Telegram is a library designed to simplify the setup of Telegram bots using
Spring Boot
and org.telegram:telegrambots
as the core dependency. It provides several key
features to facilitate the bot development process
-
Controller Update Handling: Allows receiving updates from the bot via
TelegramController
similar toRestController
in Spring. This enables seamless integration of Telegram bot functionality with the existing Spring ecosystem. -
Customizable Scenarios: Users can define custom scenarios for their bots, which can be configured via Java configuration. These scenarios allow the bot to process user interactions in a more organized and structured manner.
-
Flexible Update Filters: The library provides the capability to set up custom filters for individual bot updates. These filters are executed before and after the user-defined code, allowing for pre-processing and post-processing of updates.
-
Centralized Error Handling: Leveraging annotations in conjunction with
TelegramAdvice
andTelegramExceptionHandler
, the library offers a centralized approach for handling errors, similar toControllerAdvice
andExceptionHandler
in Spring.
- Requirements
- Installation
- Usage
- Additional Info
- Configuration
- Dependencies
- Contributing
- License
- Authors
Java 17
or a later version and Spring Boot 3
as the Spring Boot Starter Telegram library is
based on Spring Boot 3. Spring Boot 3 requires Java 17 or higher.
To use the Spring Boot Starter Telegram library in your project, follow the instructions below based on your preferred build tool.
The Latest version you can check in Releases tab or in Maven Central badge
Add the repository for the library in your to your pom.xml
file to fetch the artifact from:
<repositories>
<repository>
<id>central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2</url>
</repository>
</repositories>
Add the following dependency to your pom.xml
file:
<dependency>
<groupId>io.github.drednote</groupId>
<artifactId>spring-boot-starter-telegram</artifactId>
<version>yourVersion</version> <!-- Replace with the actual version -->
</dependency>
Add the following repository to your build.gradle
file to fetch the artifact from:
repositories {
mavenCentral()
}
Add the following dependency to your build.gradle
file:
dependencies {
// Replace with the actual version
implementation 'io.github.drednote:spring-boot-starter-telegram:yourVersion'
}
Add to application.yml
your bot token and specify the name of bot
drednote:
telegram:
name: <Your bot name>
token: <Your bot token>
Or if you preferred properties instead of yml
drednote.telegram.name=<Your bot name>
drednote.telegram.token=<Your bot token>
If you want, you can disable the autoconfiguring telegram bot by setting
drednote.telegram.enabled
to false. This will prevent the automatic creation of any beans.
Create your main controller
@TelegramController
public class MainController {
@TelegramCommand("/start")
public String onStart(User user) {
return "Hello " + user.getFirstName();
}
@TelegramMessage
public String onMessage(UpdateRequest request) {
return "You sent message with types %s".formatted(request.getMessageTypes());
}
@TelegramMessage("My name is {name:.*}")
public String onPattern(@TelegramPatternVariable("name") String name) {
return "Hello " + name;
}
@TelegramRequest
public TelegramResponse onAll(UpdateRequest request) {
return new GenericTelegramResponse("Unsupported command");
}
}
Create your main class
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
That's all! Enjoy your bot
This library's implementation closely resembles the familiar structure of Java HTTP servlets
.
-
Upon application startup, a session is established to connect with the Telegram API, initiating the reception of updates. These updates are obtained either through a long polling strategy or by setting up a webhook that prompts Telegram to directly transmit updates to the application.
-
Upon receiving an Update, a UpdateRequest object is generated. This object serves as the central entity throughout the subsequent update processing pipeline. Initially, it contains only the information extracted from the Update object. As the processing continued, the UpdateRequest accumulates additional data, enriching its content.
-
At the very beginning of the update processing chain, the UpdateRequest is stored in the context of the current thread. This is done to create a Telegram Scope.
-
After that, the main update processing starts with calls to Filters. These filters help in determining which updates should be processed further. Or you can put logic in the filter that will be executed for each update, for example, log something
-
Once the updates are filtered, the available
UpdateHandler
are called in a specific order based on their priority. There are different mechanisms available for handling updates, and you can find more information about them in the Update Handling section -
After the successful processing of updates, the Filters are called again as part of the post-processing stage. This gives an opportunity for any additional filtering or actions to be applied.
-
Once all the processing and filtering are done, the response is processed using a specialized mechanism called Response Processing. This mechanism takes the defined response and performs necessary actions, such as sending it back to the user or performing any other desired logic.
-
Throughout the entire processing chain, there is a dedicated mechanism for handling errors called Exception Handling. This ensures that any errors or exceptions that occur during the processing are properly handled and don't disrupt the flow of the program.
-
Additionally, some filters and handlers defined by this library require access to a database. For this purpose, they can make use of the Data Source functionality. This allows them to interact with the database and retrieve or store data as needed.
This library offers users two primary approaches for handling updates: controllers and scenarios. Both controllers and scenarios offer their own benefits, so you can choose the one that suits your bot's requirements and your preferred coding style. But I recommend using both
If you need to make your own handler, all you have to do is create a spring bean that will implement the
UpdateHandler
interface and set its execution priority using a spring annotations@Order
if needed. Also, after successful processing of the message, it is necessary put in the objectUpdateRequest
response with typeTelegramResponse
, so that update processing can be considered successful. If this is not done, further update handlers will be called
In order to begin receiving updates from Telegram, you will first need to create a controller and specify the criteria for which updates you want to receive.
To assist with the analysis, let's utilize the controller provided in the Quick Start section:
@TelegramController
public class MainController {
@TelegramCommand("/start")
public String onStart(User user) {
return "Hello " + user.getFirstName();
}
@TelegramMessage
public String onMessage(UpdateRequest request) {
return "You sent message with types %s".formatted(request.getMessageTypes());
}
@TelegramMessage("My name is {name:.*}")
public String onPattern(@TelegramPatternVariable("name") String name) {
return "Hello " + name;
}
@TelegramRequest
public TelegramResponse onAll(UpdateRequest request) {
return new GenericTelegramResponse("Unsupported command");
}
}
- In order for an application to register a class as a controller, it must be marked
annotation
@TelegramController
- To receive updates, there is an annotation
@TelegramRequest
. Also, to make it easier to work with updates, created several derived annotations. The main ones are@TelegramCommand
and@TelegramMessage
. As the names imply, with the help of the first one you can only receive commands (Message#isCommand), and from the second any messages (Update#getMessage) - The
@TelegramRequest
annotation's pattern() parameter takes a string that serves as a matching condition for the text in the incoming message, whether it's just a message text or a photo caption. For matcher usesAntPathMatcher
, so you can specify any condition in the string valid forAntPathMatcher
. For example:Hello*
, will match any string that starts withHello
. If you do not specify pattern() it will be replaced with the**
pattern and matched with any text. - If the update matches with several patterns that you specified in different methods
marked with
@TelegramRequest
, than sorted will be applied:TelegramRequest
without requestType has the lowest priorityTelegramRequest
without messageType has the lowest priority among the requestType equal toRequestType.MESSAGE
TelegramRequest
with exclusiveMessageType set to true and with more elements specified in messageType takes precedence over others- If
TelegramRequest
has the same priority by the above conditions, than theAntPathMatcher
order appliedThis ensures that the most specific controllers are given precedence in the update handling process
- Methods marked with
@TelegramRequest
annotation can accept a specific set of inputs parameters as defined in the Argument resolving section - Methods marked with
@TelegramRequest
annotation can return any object, as a result. The response processing mechanism is detailed in the Response Processing section - Also, if you need to get some data from the user's message by a specific pattern, then you can use
the TelegramPatternVariable
annotation
Essentially
TelegramPatternVariable
works the same asPathVariable
. - Any uncaught errors that occur during the execution of user code, are caught
by
ExceptionHandler
. More here.
To create scenarios, you will need to implement the ScenarioConfigurerAdapter
interface by
creating a Spring bean. This interface is the main tool for creating scenarios and allows you to
define and customize the behavior of your scenarios.
Here example of a configuring scenario, for additional info you can see javadocs.
@Configuration
@RequiredArgsConstructor
public class ScenarioConfig extends ScenarioConfigurerAdapter<Enum<?>> {
private final ScenarioRepository scenarioRepository;
@Override
public void onConfigure(@NonNull ScenarioTransitionConfigurer<Enum<?>> configurer) {
configurer.withCreateInlineMessage()
.source(State.INITIAL).target(ASSISTANT_CHOICE)
.telegramRequest(command(ASSISTANT_SETTINGS))
.action(settingsActionsFactory::returnSettingsMenu)
.and().withExternal()
.source(State.INITIAL).target(State.TEST)
.telegramRequest(command("/test"))
.action(context -> "Test")
.and().withRollback()
.source(ASSISTANT_CHOICE).target(GET_SETTINGS)
.telegramRequest(callbackQuery(SettingsKeyboardButton.GET_CURRENT))
.action(settingsActionsFactory.getSettings())
.rollbackTelegramRequest(callbackQuery(ROLLBACK))
.rollbackAction(settingsActionsFactory.rollbackToSettingsMenu())
.and();
}
@Override
public void onConfigure(ScenarioConfigConfigurer<Enum<?>> configurer) {
configurer
.withPersister(new JpaScenarioRepositoryAdapter<>(scenarioRepository));
}
@Override
public void onConfigure(ScenarioStateConfigurer<Enum<?>> configurer) {
configurer.withInitialState(State.INITIAL);
}
}
Filters serve as both pre-processing and post-processing mechanisms for the primary stage of update processing. They allow you to define specific criteria for filtering and manipulating updates before and after they are processed.
-
Filters are needed for several main purposes:
- To filter updates
- To execute the code for each update
-
To control update filtering, filters can set some properties in UpdateRequest, such as
response
. If any filter set propertyresponse
then the update is considered successful and an attempt will be made to send a response -
Filters are called twice: before (pre-filters) the main Update Handling and after (post-filters). It is important to note that, even if an error occurred during the main processing of the update, post-filters will still be executed
-
There are two main interfaces for creating a filter:
PreUpdateFilter
- spring beans that implement this interface will be called before the main Update HandlingPostUpdateFilter
- spring beans that implement this interface will be called after the main Update HandlingConclusivePostUpdateFilter
- spring beans that implement this interface will be called * after* the response is sent to telegram. see
-
Also, for convenience, one interface are created. First one -
PriorityPreUpdateFilter
is implemented fromPreUpdateFilter
and take precedence overPreUpdateFilter
and is executed earlier whatever returns getPreOrder(). -
To add a filter, you need to create a spring bean that will implement the
PreUpdateFilter
orPostUpdateFilter
interface. -
Additionally, it is possible to create a filter with Telegram Scope. With this approach, a unique filter instance is created for each update, and it remains active until the processing of the update is completed. This allows for better separation and management of filters for individual updates. Example:
@Component
@TelegramScope
public class LoggingFilter implements PriorityPreUpdateFilter, PostUpdateFilter {
private LocalDateTime startTime;
@Override
public void preFilter(@NonNull UpdateRequest request) {
this.startTime = LocalDateTime.now();
log.info("Receive request with id {}", request.getId());
}
@Override
public void postFilter(@NonNull UpdateRequest request) {
log.info("Request with id {} processed for {} ms", request.getId(),
ChronoUnit.MILLIS.between(startTime, LocalDateTime.now()));
}
@Override
public int getPreOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
After the update processing is complete, it is expected that a response will be sent to the user. To handle this, there is a component called Response Processing, which follows certain rules.
- The response represents by interface
TelegramResponse
- Response can only be sent if Update has a
chatId
. So if in update there is nochatId
than you should returnvoid
- Any response will automatically be wrapped in the
TelegramResponse
interface and execute sending method. Rules of wrapping:-
void
ornull
will not trigger sending the response -
String
will be wrapped inGenericTelegramResponse
and execution method will send simple text response (SendMessage
) -
For
byte[]
same rule like forString
except thatString
instance will be created frombyte[]
(new String(byte[])
) -
BotApiMethod
andSendMediaBotMethod
will be executed as is.BotApiMethod
is an abstract class that represents sending object.For
BotApiMethod
orSendMediaBotMethod
the 'chatId' property will be automatically set (only if it is null). If you manually set 'chatId', nothing happens -
A
TelegramResponse
object will be handled without wrapping. -
List of
TelegramResponse
will be wrapped inCompositeTelegramResponse
and execute with specified priority.Priority specified by
@Order
annotation -
For
java object
theGenericTelegramResponse
will try to serialize it withJackson
. In simple words will doobjectMapper.writeValueAsString(response)
-
For more information on wrapping rules, see the
ResponseSetter
andGenericTelegramResponse
classes
-
The exception is three types of response -
Collection<?>
,Stream<?>
,Flux<?>
. For handling these types of response are created three additional implementations ofTelegramResponse
-CompositeTelegramResponse
,FluxTelegramResponse
andStreamTelegramResponse
- You can create any implementation of
TelegramResponse
for sending response - Any custom code can be written in
TelegramResponse
, but I strongly recommend using this interface only for sending a response to Telegram - If you pass {@link BotApiMethod} or {@link SendMediaBotMethod} in the constructor of this class, the 'chatId' property will be automatically set (only if it is null). If you manually set 'chatId', nothing happens
The purpose of the ExceptionHandler mechanism is to provide centralized error handling. Any errors
that occur during the processing will be caught and sent to the ExceptionHandler
for further
handling. Here are some important rules to keep in mind:
- To initiate error handling, the class must be annotated with
@TelegramAdvice
. Additionally, you need to specify at least one method and annotate it with@TelegramExceptionHandler
. - If the user is not marked any method with the
@TelegramExceptionHandler
annotation, the default error handling approach will be applied. - In situations where multiple handlers are present for a particular error, a sorting mechanism is
implemented to determine the priority of method calls. The higher in the hierarchy the error
(throwable at the very top) that the handler expects, the lower the priority of the method call
will be compared to others.
This ensures that the most specific error handlers are given precedence in the error handling process
- Methods marked with
@TelegramExceptionHandler
annotation can accept a specific set of inputs parameters as defined in the Argument resolving section - Methods marked with
@TelegramExceptionHandler
annotation can return any object, as a result. The response processing mechanism is detailed in the Response Processing section
To provide maximum flexibility when calling custom code through the reflection mechanism, the input parameters for any method are calculated dynamically according to the following rules:
If you need to add support for your custom argument, you need to create spring bean and implement
io.github.drednote.telegram.core.resolver.HandlerMethodArgumentResolver
interface
You can specify arguments based on a java type:
- Update and any top-level nested objects within it.
Message
,Poll
,InlineQuery
, etc. - UpdateRequest
TelegramBot
or any subclasses if you need to consume current telegram bot instanceString
arguments will fill with the text property of UpdateRequest if it exists, or it will benull
In almost all cases this will be the text of the
Message
Long
arguments will fill with the chatId property of UpdateRequestThrowable
arguments will fill with the error property of UpdateRequest if it exists. If no error than it will benull
This type should be only used in methods marked with
@TelegramExceptionHandler
. In other methods, it more likely will benull
You can annotate certain arguments using @TelegramPatternVariable
. Here are the rules to follow:
- This annotation is only valid if there's text in the update (e.g., message text, photo caption).
Otherwise, it will be
null
- This annotation supports only two Java types:
String
andMap
- When using
String
, the value of the template variable from the pattern is exposed - In the case of
Map
, all template variables are exposed
The functionality of @TelegramScope
is similar to the Spring annotation @Scope("request")
, with
the difference being that the context is created at the start of update processing instead of at the
request level. By marking a spring bean with this annotation, a new instance of the bean will be
created for each update processing.
It's important to note that each update handling is associated with a specific thread. In cases where you create sub-threads within the main thread, you will need to manually bind the
UpdateRequest
to the new thread. This can be achieved using theUpdateRequestContext
class.
-
For some filters and scenarios to work correctly, you need to save information to the database. You can save data to the application memory, but then during the restart, all information will be lost. Therefore, it is better if you configure the datasource using spring.
-
This library is fully based on Spring JPA in working with the database. Therefore, to support different databases (postgres, mongo, etc.), using the implementations of
DataSourceAdapter
interface -
If you want to add support for a database that currently is not supported, you should to create entity and create repository extending
PermissionRepository
,ScenarioRepository
orScenarioIdRepository
Currently supported
JpaRepository
Note: To enable auto scan for jpa entities, you should manually pick main interfaces for entities and use
@EntityScan
annotation. To create spring data repository, you need to just implement one of the repository interfaces
@EntityScan(basePackageClasses = {Permission.class, PersistScenario.class})
@Configuration
public class JpaConfig {
}
public interface PermissionRepository extends JpaPermissionRepository {}
Update
is the main object that comes from the Telegram API. It contains all information about the event that happened in the bot, whether it's a new message from the user, or changes in some settings chat in which the bot is located.
Additional docs - Telegram API docs
UpdateRequest
is a primary object that stores all information about update. Any change
that occurs during the processing of an update is written to it. Thus, if you get it in the user
code, you can find out all the information about the current update. For example, in this way:
@TelegramController
public class Example {
@TelegramRequest
public void onAll(UpdateRequest request) {
System.out.printf("request is %s", request);
}
}
Read more about Telegram Controllers.
- The
UpdateHandler
interface is the entry point for handling updates. More in Update Handling section.
UpdateFilters
allow you to execute some code before or after the mainUpdateHandlers
call. The main interfaces arePreUpdateFilter
andPostUpdateFilter
. More about filters here
TelegramResponse
represents the action that need to be executed to sent response to the user who initiated the update processing. For this here special mechanism Response Processing
TelegramScope
is a specialization of@Scope
for a component whose lifecycle is bound to the current telegram update handling. More here
- All packages are children of
io.github.drednote.telegram
marked with two annotations@NonNullApi
and@NonNullFields
. This means that by default all fields and APIs accept and return non-null objects. If it is needed to return a nullable object, then the field or method is annotated with@Nullable
- You can use
HasRole
annotation to check controller access
All settings tables contain 4 columns:
Name
- the name of the variable as it is called in the codeDescription
- a brief description of what this setting doesDefault Value
- the default value of the variableRequired
- whether the variable is required
If the
Required
field istrue
and the value of theDefault Value
column is not equal to-
, it means that you don't need to manually set the value for the variable. However, if you manually set it tonull
or any value that can be considered empty, the application will not start
Name | Description | Default Value | Required |
---|---|---|---|
enabled | Enable the bot. | true | false |
name | The name of a bot. Example: TheBestBot. | false | |
token* | The token of a bot. | must be set by user | true |
defaultLocale | The default locale for sending responses. | - | false |
session | Session properties. | Session properties | |
updateHandler | Properties of update handlers. | Update handlers properties | |
filters | Filters properties. | Filters properties | |
menu | Menu properties. | Menu properties |
Name | Description | Default Value | Required |
---|---|---|---|
maxUserParallelRequests | Max number of threads used for consumption messages from a telegram for concrete user. 0 - no restrictions | 1 | true |
consumeMaxThreads | Max number of threads used for consumption messages from a telegram | 10 | true |
maxMessagesInQueue | Limits the number of updates to be store in memory queue for update processing. 0 - no limit. Defaults to (consumeMaxThreads * 1.5). | 15 | true |
updateStrategy | The strategy to receive updates from Telegram API. Long polling or webhooks. | LONG_POLLING | true |
backOffStrategy | Backoff strategy for failed requests to Telegram API. Impl of BackOff interface must be with public empty constructor | ExponentialBackOff | true |
proxyType | The proxy type for executing requests to Telegram API. | NO_PROXY | true |
proxyUrl | The proxy url in format host:port or if auth needed host:port:username:password . |
- | false |
cacheLiveDuration | Cache lifetime used in DefaultTelegramBotSession |
1 | true |
cacheLiveDurationUnit | The TimeUnit which will be applied to cacheLiveDuration |
hours | true |
longPolling | LongPolling properties. | LongPolling properties | false |
Name | Description | Default Value | Required |
---|---|---|---|
updateLimit | Limits the number of updates to be retrieved. Values between 1-100 are accepted | 100 | true |
updateTimeout | Timeout in seconds for long polling. Should be positive, short polling (0) for testing purposes only | 50 | true |
allowedUpdates | A JSON-serialized list of update types to receive. See RequestType for available update types. | - | false |
Additional docs Telegram API docs
Name | Description | Default Value | Required |
---|---|---|---|
controllerEnabled | Enabled controller update handling. | true | true |
scenarioEnabled | Enabled scenario update handling. | true | true |
setDefaultErrorAnswer | If an exception occurs and no handler processes it, set InternalErrorTelegramResponse as response. | true | true |
scenarioLockMs | The time that scenario executor will wait if a concurrent interaction was performed. 0 - no limit. | 0 | false |
serializeJavaObjectWithJackson | Whether to serialize Java POJO objects with Jackson to JSON in GenericTelegramResponse. | true | true |
enabledWarningForScenario | Throws an error with a warning about using scenario safe only when getMaxThreadsPerUser is set to 1, if value set to different value | true | true |
Name | Description | Default Value | Required |
---|---|---|---|
permission | Permission filter properties. | Permission properties | |
userRateLimit | How often each user can perform requests to bot. 0 = no rules. | 0 | true |
userRateLimitUnit | The ChronoUnit which will be applied to userRateLimit. | SECONDS | true |
userRateLimitCacheExpire | How long cache with rate limit bucket will not expire. This parameter needed just for delete staled buckets to free up memory. | 1 | true |
userRateLimitCacheExpireUnit | The ChronoUnit which will be applied to userRateLimitCacheExpire. | HOURS | true |
setDefaultAnswer | If response is null at the end of update handling and post filtering, set NotHandledTelegramResponse as response. | true | true |
Name | Description | Default Value | Required |
---|---|---|---|
access | Define who has access to the bot. | ALL | true |
defaultRole | If a user has no role, this role will be set by default. | NONE | true |
roles | The list of roles with privileges. | - | false |
assignRole | The map of [userId:Role]. (Deprecated: Not safe for production.) | - | false |
public class Role {
/**
* Boolean indicating if the role has basic interaction permission and can send requests to bot
*/
private boolean canRead;
}
Name | Description | Default Value | Required |
---|---|---|---|
values | Map of [name:CommandCls]. | - | false |
sendPolicy | Send policy. | ON_STARTUP | false |
Name | Description | Default Value | Required |
---|---|---|---|
text | Text for the button. | - | true |
command | Command for the button. | - | true |
scopes | Scopes of users for which the commands are relevant. | [DEFAULT] | true |
languageCode | A two-letter ISO 639-1 language code. | - | false |
userIds | Unique identifier of the target users to who apply commands. | - | false |
chatIds | Unique identifier for the target chats or usernames of the target supergroup. | - | false |
These dependencies will automatically be included in your project
org.telegram:telegrambots-longpolling
org.telegram:telegrambots-client
org.springframework.boot:spring-boot-starter-web
com.esotericsoftware:kryo
com.github.vladimir-bukhtoyarov:bucket4j-core
com.github.ben-manes.caffeine:caffeine
org.apache.httpcomponents.client5:httpclient5
You can manually add them if you want to configure datasource. For what you should configure datasource read in Data Source block
org.springframework.boot:spring-boot-starter-data-jpa
or
org.springframework.boot:spring-boot-starter-data-mongo
I welcome contributions from the community to improve and extend the Spring Boot Starter Telegram library. If you would like to contribute, follow these steps:
- Fork the repository on GitHub by clicking the "Fork" button in the top right corner of the repository page.
- Clone your forked repository to your local development environment:
git clone https://github.com/your-username/spring-boot-starter-telegram.git
cd spring-boot-starter-telegram
- Create a new branch for your changes:
# Replace feature/new-feature with a descriptive name for your branch.
git checkout -b feature/new-feature
-
Make the necessary changes and additions to the codebase.
-
Test your changes to ensure they work as expected and do not introduce any regressions.
-
Commit your changes:
git add .
git commit -m "Add your commit message here"
Be sure to write a clear and concise commit message explaining the changes you made.
- Push your branch to your forked repository:
git push origin feature/new-feature
-
Open a pull request (PR) on the original repository by navigating to your forked repository on GitHub and clicking the "Compare & pull request" button next to your newly pushed branch.
-
Fill out the pull request template with relevant information about your changes, and click the "Create pull request" button.
-
I will review your pull request and provide feedback or request further changes if necessary.
-
Once your pull request is approved, it will be merged into the main repository, and your contributions will be part of the Spring Boot Starter Telegram library.
Thank you for considering contributing to the project! I appreciate your efforts to make this library better and more useful for the community. If you have any questions or need assistance with the contribution process, feel free to ask in the pull request discussion.
Happy coding!
This project is licensed under the MIT License. You are free to use, modify, and distribute this library according to the terms of the license.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Thanks to OlegNyr for an idea of controllers
The Spring Boot Starter Telegram library is maintained and developed by Ivan Galushko (Drednote).
Happy bot development!