TDLib in Spring Boot Maven project
Introduction
Most Telegram users know about Telegram Bots – small applications to perform various tasks. It is popular to implement a Telegram Bot API and write an own one for any kind of service or automation. But almost no one talks about the tool which opens maximum possibilities within the Telegram ecosystem – Telegram API.
Telegram API is an open interface to develop custom Telegram clients, TDLib – its official implementation.
In this article I will show a way of creating a simple Java application that integrates with TDLib. We won't be creating a complete client. Instead, I'll guide you on integrating TDLib with Spring Boot Maven project and utilizing its fundamental classes.
Inside of TDLib
TDLib is a cross-platform, fully functional Telegram client. It supports all features and takes care of all implementation details. To use TDLib, you need to build it for your operating system. Here is the official instruction for every OS.
The built TDLib directory will have location 'td/tdlib'. It will include .class files, native libraries, and documentation. Native libraries are files compiled for a specific architecture and are used by Java compiled classes within JNI. The number of them depends on the OS.
When Java class has a declared method with keyword 'native', it means that its implementation is inside of native libraries. For example, Client.class in TDLib will have method:
private static native void nativeClientSend(int var0, long var1, TdApi.Function var3);
'Client.class' is responsible for communication with the Telegram server, which is predominantly asynchronous; 'TdApi.class' contains components to interact with 'Client.class', while 'Example.class' illustrates simple usage of TDLib.
Initializing project
Firstly, ensure that Java 17 and Maven are correctly installed. Then, create your Telegram application. Save 'api_id' and 'api_hash' as they will be used later. Next, initialize a project using Spring Initializr. Choose Java 17, Maven, select Lombok and Spring Web dependencies. Open the root of the created project and copy 'td/tdlib' to it.
We’ve already built TDLib. To use it in our application, we will create Maven dependency from the library and add it to 'pom.xml'. Firstly, let's create a JAR file from TDLib. Open the terminal in the project's root directory and execute the following command:
$ jar cf tdlib.jar -C tdlib/bin .
JAR file with the name 'tdlib.jar' should appear in the current folder. Then execute:
$ mvn install:install-file -Dfile="tdlib.jar" -DgroupId=com.telegram -DartifactId=tdlib -Dversion=1.0 -Dpackaging=jar
This command will create a Maven artifact of TDLib jar file and add it to the local Maven repository. While it's typically advisable to upload dependencies to a remote Maven repository and download artifacts from there, this option is ideal for demonstration purposes.
Finally, we can add the TDLib dependency to 'pom.xml' in our application:
</dependencies>
...
<dependency>
<groupId>com.telegram</groupId>
<artifactId>tdlib</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
Using library
Firstly, let’s create configuration properties with telegram client parameters:
领英推荐
@Data
@Configuration
@ConfigurationProperties(prefix = "app.telegram.client")
@AllArgsConstructor
@NoArgsConstructor
public class TDLibParameters {
private Boolean useMessageDatabase;
private Boolean useSecretChats;
private Integer apiId;
private String apiHash;
private String systemLanguageCode;
private String deviceModel;
private String applicationVersion;
private Boolean enableStorageOptimizer;
}
Then, populate them in 'application.properties' file. Here we use 'api_id', 'api_hash' from created Telegram Application:
app.telegram.client.useMessageDatabase=false
app.telegram.client.useSecretChats=true
app.telegram.client.apiId=<api_id>
app.telegram.client.apiHash<api_hash>
app.telegram.client.systemLanguageCode=en
app.telegram.client.deviceModel=Desktop
app.telegram.client.applicationVersion=1.0
app.telegram.client.enableStorageOptimizer=false
We use Java code, which depends on native libraries. So, we need to load these libraries during startup to enable the functionality of the code. The loading method is as follows:
private void loadLibraries() {
try {
String os = System.getProperty("os.name");
if (os != null && os.toLowerCase().startsWith("windows")) {
System.loadLibrary("libcrypto-1_1-x64");
System.loadLibrary("libssl-1_1-x64");
System.loadLibrary("zlib1");
}
System.loadLibrary("tdjni");
} catch (UnsatisfiedLinkError e) {
throw new LibraryNotFoundException(e);
}
}
If there are any other native libraries built with TDLib for your OS – add them accordingly. Define simple exception:
public class LibraryNotFoundException extends RuntimeException {
public LibraryNotFoundException(final Error e) {
super(e);
}
}
As we know, TDLib – is an asynchronous library. To communicate with the server, we need to implement 'Client.ResultHandler' for receiving updates and 'Client' for sending requests. Configuration of 'Client' will look like this:
@Configuration
@RequiredArgsConstructor
public class TelegramConfiguration {
@Bean
public Client client(final ResultHandlerImpl resultHandler) {
loadLibraries();
Client.execute(new TdApi.SetLogVerbosityLevel(0));
Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 134217728L, false)));
Client client = Client.create(resultHandler, (Client.ExceptionHandler) null, (Client.ExceptionHandler) null);
resultHandler.setClient(client);
return client;
}
private void loadLibraries() {
// implementation
}
}
We have specified a file for logging and set log verbosity level to 0 – only the most important messages will be written down. Additionally, this section executes a previously developed method for loading libraries.
Next, an initial implementation of the 'Client.ResultHandler' could appear as follows:
@Component
@RequiredArgsConstructor
public class ResultHandlerImpl implements Client.ResultHandler {
private final TDLibParameters tdLibParameters;
@Setter
private Client client;
@Override
public void onResult(final TdApi.Object update) {
if (update.getClass().equals(TdApi.UpdateAuthorizationState.class)) {
var authStateUpdate = (TdApi.UpdateAuthorizationState) update;
if (authStateUpdate.authorizationState.getClass()
.equals(TdApi.AuthorizationStateWaitTdlibParameters.class)){
client.send(createRequest(), null);
}
}
}
private TdApi.SetTdlibParameters createRequest() {
TdApi.SetTdlibParameters setTdlibParameters = new TdApi.SetTdlibParameters();
setTdlibParameters.apiId = tdLibParameters.getApiId();
setTdlibParameters.apiHash = tdLibParameters.getApiHash();
setTdlibParameters.useMessageDatabase = tdLibParameters.getUseMessageDatabase();
setTdlibParameters.useSecretChats = tdLibParameters.getUseSecretChats();
setTdlibParameters.systemLanguageCode = tdLibParameters.getSystemLanguageCode();
setTdlibParameters.deviceModel = tdLibParameters.getDeviceModel();
setTdlibParameters.applicationVersion = tdLibParameters.getApplicationVersion();
return setTdlibParameters;
}
}
Here we send TDLib parameters, when server needs them. In our case 'ResultHandlerImpl' should know 'Client' and vice versa. I decided to retain this circular dependency and inject 'Client' into 'ResultHandlerImpl' through setter.
Running the application
These are the commands for running our application:
$ mvn clean install
$ java -Djava.library.path="windows64/dlls" -jar "target/<name of jar file>"
'mvn clean install' apart from other actions, compiles and packages the application into a .jar file. The standard location for the .jar file is the 'target' folder.
'-Djava.library.path' – is a VM option, specifying location of native libraries.
Further steps
The project is set up. It has functionality that allows us to send requests and handle updates. Client listens to authorization state updates and sends TDLib parameters when needed.
The next logical step would be implementing authorization in the application. After that it will be possible to receive all account-related updates. For this purpose, check classes 'TdApi.UpdateAuthorizationState', 'TdApi.SetAuthenticationPhoneNumber', and 'TdApi.CheckAuthenticationCode'.
That’s all I planned to cover in this article. I hope it was beneficial for those who are new to Telegram API. Happy coding)
Salesforce software engineer at MagicFuse
6 个月Маю питання по цьому, можна в особист? ?
Junior Java Developer
1 年Допомага? в пошуку?
Part of Smart
1 年Це сильно! Дал? - б?льше ??