TDLight Java 2

This commit is contained in:
Andrea Cavalli 2021-09-27 19:27:13 +02:00
parent bd8b7af73a
commit 55436cd354
69 changed files with 2266 additions and 1266 deletions

213
README.md
View File

@ -5,76 +5,73 @@ TDLight Java
[![Release tag](https://img.shields.io/github/v/release/tdlight-team/tdlight-java.svg?include_prereleases&style=flat-square)](https://github.com/tdlight-team/tdlight-java/releases)
[![Java profiler](https://local.cavallium.it/mirrors/jprofiler-logo/jprofiler-logo-badge.svg)](https://www.ej-technologies.com/products/jprofiler/overview.html)
A barebone java wrapper for TDLib (and TDLight)
Complete Bot and Userbot Telegram library written in Java based on TDLib
This wrapper gives you direct access to TDLib API in Java.
## Supported platforms
Java versions: from Java 8 to Java 17
## Requirements
Java versions: Java 8, 9, 10, 11, 12, 13, 14, 15, 16
Operating systems: Linux, Windows, MacOS
Operating system: Linux, Windows, MacOS
Supported CPU architectures:
- x86 (Linux, Windows)
- amd64/x86_64 (Linux, Windows, MacOS)
- armv6 (Linux)
- armv7 (Linux)
- aarch64/armv8 (Linux)
CPU architectures:
- i386/x86 (Linux, Windows)
- amd64/x86_64 (Linux, Windows, OSX)
- armhf/armv7 (Linux)
- aarch64/armv8/arm64 (Linux)
- s390x (Linux)
- ppc64el/ppc64le (Linux)
Required libraries for linux: OpenSSL and zlib
## Including TDLight Java in a project
There are two packages of TDLight:
- [TDLight](#For-TDLight-Java-with-optimized-TDLight), with our optimized fork of TDLib (Reccomended for bots)
- [TDLight with official TDLib](#For-TDLight-Java-with-official-TDLib) (Reccomended for GUI clients)
The two packages are compatible, but while TDLight is focused on long term resources usage and speed, TDLib is more focused on stability.
Choose one of the two different TDLight packages and then follow the guide below.
Replace `REPLACE_WITH_LATEST_VERSION` with the latest version of tdlight, you can find it on the **Releases** tab on github.
### For TDLight Java with optimized TDLight
#### Maven
Repository:
## How to use the library
### Installing with Maven
```xml
<repositories>
<repository>
<id>mchv</id>
<name>MCHV Apache Maven Packages</name>
<url>https://mvn.mchv.eu/repository/mchv/</url>
</repository>
</repositories>
<project>
<repositories>
<repository>
<id>mchv</id>
<name>MCHV Apache Maven Packages</name>
<url>https://mvn.mchv.eu/repository/mchv/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-java</artifactId>
<version>VERSION</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-linux-amd64</artifactId>
<version>NATIVES_VERSION</version>
</dependency>
<!-- Include other native versions that you want, for example for windows, osx, ... -->
</dependencies>
</project>
```
Dependency:
```xml
<dependencies>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-java</artifactId>
<version>REPLACE_WITH_LATEST_VERSION</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-natives-linux-amd64</artifactId>
<version>REPLACE_WITH_LATEST_NATIVES_VERSION</version>
</dependency>
<!-- include other native versions that you want, for example for macos, windows, and other architectures here -->
</dependencies>
```
#### Gradle
Replace `VERSION` with the latest release version, you can find it [here](https://github.com/tdlight-team/tdlight-java/releases).
Replace `NATIVES_VERSION` with the latest native version.
Make sure that you are using the correct natives version for the release that you are using.
### Installing with Gradle
```groovy
repositories {
maven { url "https://mvn.mchv.eu/repository/mchv/" }
}
dependencies {
implementation 'it.tdlight:tdlight-java:REPLACE_WITH_LATEST_VERSION'
implementation 'it.tdlight:tdlight-natives-linux-amd64:REPLACE_WITH_LATEST_NATIVES_VERSION'
// include other native versions that you want, for example for macos, windows, and other architectures here
implementation 'it.tdlight:tdlight-java:LATEST_VERSION'
implementation 'it.tdlight:tdlight-natives-linux-amd64:NATIVES_VERSION'
// Include other native versions that you want, for example for windows, osx, ...
}
```
#### Natives inclusion
To use TDLight java for a specific platform, you need to include the related native dependencies:
Replace `VERSION` with the latest release version, you can find it [here](https://github.com/tdlight-team/tdlight-java/releases).
Replace `NATIVES_VERSION` with the latest native version.
Make sure that you are using the correct natives version for the release that you are using.
### Native dependencies
To use TDLight Java you need to include one or more native dependencies:
- `tdlight-natives-linux-amd64`
- `tdlight-natives-linux-aarch64`
- `tdlight-natives-linux-x86`
@ -84,114 +81,16 @@ To use TDLight java for a specific platform, you need to include the related nat
- `tdlight-natives-windows-amd64`
- `tdlight-natives-osx-amd64`
### For TDLight Java with official TDLib
#### Maven
Repository:
```xml
<repositories>
<repository>
<id>mchv</id>
<name>MCHV Apache Maven Packages</name>
<url>https://mvn.mchv.eu/repository/mchv/</url>
</repository>
</repositories>
```
Dependency:
```xml
<dependencies>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-java</artifactId>
<version>REPLACE_WITH_LATEST_VERSION</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-natives-linux-amd64</artifactId>
<version>REPLACE_WITH_LATEST_NATIVES_VERSION</version>
</dependency>
<!-- include other native versions that you want, for example for macos, windows, and other architectures here -->
</dependencies>
```
#### Gradle
```groovy
repositories {
maven { url "https://mvn.mchv.eu/repository/mchv/" }
}
dependencies {
implementation 'it.tdlight:tdlib-java:REPLACE_WITH_LATEST_VERSION'
implementation 'it.tdlight:tdlib-natives-linux-amd64:REPLACE_WITH_LATEST_NATIVES_VERSION'
// include other native versions that you want, for example for macos, windows, and other architectures here
}
```
#### Natives inclusion
To use TDLight java for a specific platform, you need to include the related native dependencies:
- `tdlib-natives-linux-amd64`
- `tdlib-natives-linux-aarch64`
- `tdlib-natives-linux-x86`
- `tdlib-natives-linux-armv6`
- `tdlib-natives-linux-armv7`
- `tdlib-natives-linux-ppc64le`
- `tdlib-natives-windows-amd64`
- `tdlib-natives-osx-amd64`
## Usage
Simple initialization of a native TDLib client
```java
package it.tdlight.example;
import it.tdlight.common.TelegramClient;
import it.tdlight.tdlight.ClientManager;
import it.tdlight.common.Init;
An example on how to use TDLight Java can be found here: [Example.java](https://github.com/tdlight-team/tdlight-java/blob/master/example/src/main/java/it.tdlight.example/Example.java)
import it.tdlight.jni.TdApi;
public class Example {
public static void main(String[] args) {
// Initialize TDLight native libraries
Init.start();
// Create a client
TelegramClient client = ClientManager.create();
// Initialize the client
client.initialize(Example::onUpdate, Example::onUpdateError, Example::onError);
// Here you can use the client.
// Documentation of tdlib methods can be found here:
// https://tdlight-team.github.io/tdlight-docs
// An example on how to use tdlight java can be found here:
// https://github.com/tdlight-team/tdlight-java/blob/master/example/src/main/java/it.tdlight.example/Example.java
// A similar example on how to use tdlib can be found here:
// https://github.com/tdlib/td/blob/master/example/java/org/drinkless/tdlib/example/Example.java
}
private static void onUpdate(TdApi.Object object) {
TdApi.Update update = (TdApi.Update) object;
System.out.println("Received update: " + update);
}
private static void onUpdateError(Throwable exception) {
System.out.println("Received an error from updates:");
exception.printStackTrace();
}
private static void onError(Throwable exception) {
System.out.println("Received an error:");
exception.printStackTrace();
}
}
```
### TDLib methods documentation
[TdApi class javadoc](https://tdlight-team.github.io/tdlight-docs)
### TDLight methods documentation
[TdApi JavaDoc](https://tdlight-team.github.io/tdlight-docs)
### TDLight extended features
TDLight Java with TDLight has some extended features, that you can see on the [TDLight official repository](https://github.com/tdlight-team/tdlight).
This features are present only if you use the optimized TDLight package.
TDLight has some extended features compared to TDLib, that you can see on the [TDLight official repository](https://github.com/tdlight-team/tdlight#tdlight-extra-features).
## About
### License

View File

@ -24,7 +24,8 @@
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-java</artifactId>
<version>[1.7.6.1,)</version>
<!--<version>[1.7.6.1,)</version>-->
<version>1.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>

View File

@ -0,0 +1,47 @@
package it.tdlight.example;
import it.tdlight.common.TelegramClient;
import it.tdlight.tdlight.ClientManager;
import it.tdlight.common.Init;
import it.tdlight.common.utils.CantLoadLibrary;
import it.tdlight.jni.TdApi;
/**
* This is an advanced example that uses directly the native client without using the SimpleClient implementation
*/
public class AdvancedExample {
public static void main(String[] args) throws CantLoadLibrary {
// Initialize TDLight native libraries
Init.start();
// Create a client
TelegramClient client = ClientManager.create();
// Initialize the client
client.initialize(AdvancedExample::onUpdate, AdvancedExample::onUpdateError, AdvancedExample::onError);
// Here you can use the client.
// An example on how to use TDLight java can be found here:
// https://github.com/tdlight-team/tdlight-java/blob/master/example/src/main/java/it.tdlight.example/Example.java
// The documentation of the TDLight methods can be found here:
// https://tdlight-team.github.io/tdlight-docs
}
private static void onUpdate(TdApi.Object object) {
TdApi.Update update = (TdApi.Update) object;
System.out.println("Received update: " + update);
}
private static void onUpdateError(Throwable exception) {
System.out.println("Received an error from updates:");
exception.printStackTrace();
}
private static void onError(Throwable exception) {
System.out.println("Received an error:");
exception.printStackTrace();
}
}

View File

@ -6,12 +6,27 @@
//
package it.tdlight.example;
import it.tdlight.client.APIToken;
import it.tdlight.client.Authenticable;
import it.tdlight.client.AuthenticationData;
import it.tdlight.client.CommandHandler;
import it.tdlight.client.Result;
import it.tdlight.client.SimpleTelegramClient;
import it.tdlight.client.TDLibSettings;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.Init;
import it.tdlight.common.ResultHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.common.utils.CantLoadLibrary;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationState;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.MessageContent;
import it.tdlight.jni.TdApi.MessageSender;
import it.tdlight.jni.TdApi.MessageSenderUser;
import it.tdlight.jni.TdApi.MessageText;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import it.tdlight.jni.TdApi.UpdateNewMessage;
import it.tdlight.tdlight.ClientManager;
import java.io.BufferedReader;
import java.io.IOError;
@ -26,592 +41,118 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Example class for TDLib usage from Java.
* (Based on the official TDLib java example)
* Example class for TDLight Java
*
* The documentation of the TDLight functions can be found here:
* https://tdlight-team.github.io/tdlight-docs
*/
public final class Example {
private static TelegramClient client = null;
private static TdApi.AuthorizationState authorizationState = null;
private static volatile boolean haveAuthorization = false;
private static volatile boolean needQuit = false;
private static volatile boolean canQuit = false;
/**
* Admin user id, used by the stop command example
*/
private static final long ADMIN_ID = 667900586;
private static final ResultHandler defaultHandler = new DefaultHandler();
private static final Lock authorizationLock = new ReentrantLock();
private static final Condition gotAuthorization = authorizationLock.newCondition();
private static final ConcurrentMap<Integer, TdApi.User> users = new ConcurrentHashMap<Integer, TdApi.User>();
private static final ConcurrentMap<Integer, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Integer, TdApi.BasicGroup>();
private static final ConcurrentMap<Integer, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Integer, TdApi.Supergroup>();
private static final ConcurrentMap<Integer, TdApi.SecretChat> secretChats = new ConcurrentHashMap<Integer, TdApi.SecretChat>();
private static final ConcurrentMap<Long, TdApi.Chat> chats = new ConcurrentHashMap<Long, TdApi.Chat>();
private static final NavigableSet<OrderedChat> mainChatList = new TreeSet<OrderedChat>();
private static boolean haveFullMainChatList = false;
private static final ConcurrentMap<Integer, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Integer, TdApi.UserFullInfo>();
private static final ConcurrentMap<Integer, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.BasicGroupFullInfo>();
private static final ConcurrentMap<Integer, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Integer, TdApi.SupergroupFullInfo>();
private static final String newLine = System.getProperty("line.separator");
private static final String commandsLine = "Enter command (gcs - GetChats, gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ";
private static volatile String currentPrompt = null;
private static void print(String str) {
if (currentPrompt != null) {
System.out.println("");
}
System.out.println(str);
if (currentPrompt != null) {
System.out.print(currentPrompt);
}
}
private static void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
synchronized (mainChatList) {
synchronized (chat) {
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isRemoved = mainChatList.remove(new OrderedChat(chat.id, position));
assert isRemoved;
}
}
chat.positions = positions;
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position));
assert isAdded;
}
}
}
}
}
private static void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) {
if (authorizationState != null) {
Example.authorizationState = authorizationState;
}
switch (Example.authorizationState.getConstructor()) {
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
TdApi.TdlibParameters parameters = new TdApi.TdlibParameters();
parameters.databaseDirectory = "tdlib";
parameters.useMessageDatabase = true;
parameters.useSecretChats = true;
parameters.apiId = 94575;
parameters.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
parameters.systemLanguageCode = "en";
parameters.deviceModel = "Desktop";
parameters.applicationVersion = "1.0";
parameters.enableStorageOptimizer = true;
client.send(new TdApi.SetTdlibParameters(parameters), new AuthorizationRequestHandler());
break;
case TdApi.AuthorizationStateWaitEncryptionKey.CONSTRUCTOR:
client.send(new TdApi.CheckDatabaseEncryptionKey(), new AuthorizationRequestHandler());
break;
case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: {
String phoneNumber = promptString("Please enter phone number: ");
client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: {
String link = ((TdApi.AuthorizationStateWaitOtherDeviceConfirmation) Example.authorizationState).link;
System.out.println("Please confirm this login link on another device: " + link);
break;
}
case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: {
String code = promptString("Please enter authentication code: ");
client.send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitRegistration.CONSTRUCTOR: {
String firstName = promptString("Please enter your first name: ");
String lastName = promptString("Please enter your last name: ");
client.send(new TdApi.RegisterUser(firstName, lastName), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: {
String password = promptString("Please enter password: ");
client.send(new TdApi.CheckAuthenticationPassword(password), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateReady.CONSTRUCTOR:
haveAuthorization = true;
authorizationLock.lock();
try {
gotAuthorization.signal();
} finally {
authorizationLock.unlock();
}
break;
case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR:
haveAuthorization = false;
print("Logging out");
break;
case TdApi.AuthorizationStateClosing.CONSTRUCTOR:
haveAuthorization = false;
print("Closing");
break;
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
print("Closed");
if (!needQuit) {
client = ClientManager.create(); // recreate client after previous has closed
client.initialize(new UpdateHandler(), new ErrorHandler(), new ErrorHandler());
} else {
canQuit = true;
}
break;
default:
System.err.println("Unsupported authorization state:" + newLine + Example.authorizationState);
}
}
private static int toInt(String arg) {
int result = 0;
try {
result = Integer.parseInt(arg);
} catch (NumberFormatException ignored) {
}
return result;
}
private static long getChatId(String arg) {
long chatId = 0;
try {
chatId = Long.parseLong(arg);
} catch (NumberFormatException ignored) {
}
return chatId;
}
private static String promptString(String prompt) {
System.out.print(prompt);
currentPrompt = prompt;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String str = "";
try {
str = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
currentPrompt = null;
return str;
}
private static void getCommand() {
String command = promptString(commandsLine);
String[] commands = command.split(" ", 2);
try {
switch (commands[0]) {
case "gcs": {
int limit = 20;
if (commands.length > 1) {
limit = toInt(commands[1]);
}
getMainChatList(limit);
break;
}
case "gc":
client.send(new TdApi.GetChat(getChatId(commands[1])), defaultHandler);
break;
case "me":
client.send(new TdApi.GetMe(), defaultHandler);
break;
case "sm": {
String[] args = commands[1].split(" ", 2);
sendMessage(getChatId(args[0]), args[1]);
break;
}
case "lo":
haveAuthorization = false;
client.send(new TdApi.LogOut(), defaultHandler);
break;
case "q":
needQuit = true;
haveAuthorization = false;
client.send(new TdApi.Close(), defaultHandler);
break;
default:
System.err.println("Unsupported command: " + command);
}
} catch (ArrayIndexOutOfBoundsException e) {
print("Not enough arguments");
}
}
private static void getMainChatList(final int limit) {
synchronized (mainChatList) {
if (!haveFullMainChatList && limit > mainChatList.size()) {
// have enough chats in the chat list or chat list is too small
long offsetOrder = Long.MAX_VALUE;
long offsetChatId = 0;
if (!mainChatList.isEmpty()) {
OrderedChat last = mainChatList.last();
offsetOrder = last.position.order;
offsetChatId = last.chatId;
}
client.send(new TdApi.GetChats(new TdApi.ChatListMain(), offsetOrder, offsetChatId, limit - mainChatList.size()), new ResultHandler() {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.Error.CONSTRUCTOR:
System.err.println("Receive an error for GetChats:" + newLine + object);
break;
case TdApi.Chats.CONSTRUCTOR:
long[] chatIds = ((TdApi.Chats) object).chatIds;
if (chatIds.length == 0) {
synchronized (mainChatList) {
haveFullMainChatList = true;
}
}
// chats had already been received through updates, let's retry request
getMainChatList(limit);
break;
default:
System.err.println("Receive wrong response from TDLib:" + newLine + object);
}
}
});
return;
}
// have enough chats in the chat list to answer request
java.util.Iterator<OrderedChat> iter = mainChatList.iterator();
System.out.println();
System.out.println("First " + limit + " chat(s) out of " + mainChatList.size() + " known chat(s):");
for (int i = 0; i < limit && iter.hasNext(); i++) {
long chatId = iter.next().chatId;
TdApi.Chat chat = chats.get(chatId);
synchronized (chat) {
System.out.println(chatId + ": " + chat.title);
}
}
print("");
}
}
private static void sendMessage(long chatId, String message) {
// initialize reply markup just for testing
TdApi.InlineKeyboardButton[] row = {new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl())};
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row});
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), false, true);
client.send(new TdApi.SendMessage(chatId, 0, 0, null, replyMarkup, content), defaultHandler);
}
public static void main(String[] args) throws InterruptedException, CantLoadLibrary {
// create client
public static void main(String[] args) throws CantLoadLibrary, InterruptedException {
// Initialize TDLight native libraries
Init.start();
client = ClientManager.create();
client.initialize(new UpdateHandler(), new ErrorHandler(), new ErrorHandler());
client.execute(new TdApi.SetLogVerbosityLevel(0));
// disable TDLib log
if (client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false))) instanceof TdApi.Error) {
throw new IOError(new IOException("Write access to the current directory is required"));
}
// Obtain the API token
APIToken apiToken = APIToken.example();
// test Client.execute
defaultHandler.onResult(client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
// Configure the client
TDLibSettings settings = TDLibSettings.create(apiToken);
// main loop
while (!needQuit) {
// await authorization
authorizationLock.lock();
try {
while (!haveAuthorization) {
gotAuthorization.await();
}
} finally {
authorizationLock.unlock();
// Create a client
SimpleTelegramClient client = new SimpleTelegramClient(settings);
// Configure the authentication info
AuthenticationData authenticationData = AuthenticationData.bot("1458657665:AAFotSQj1pHcRrH1C0DmDX6VpwSanJgb2-g");
// Add an example update handler that prints when the bot is started
client.addUpdateHandler(UpdateAuthorizationState.class, update -> printStatus(update.authorizationState));
// Add an example command handler that stops the bot
client.addCommandHandler("stop", new StopCommandHandler(client));
// Add an example update handler that prints every received message
client.addUpdateHandler(UpdateNewMessage.class, update -> {
// Get the message content
MessageContent messageContent = update.message.content;
// Get the message text
String text;
if (messageContent instanceof MessageText) {
// Get the text of the text message
text = ((MessageText) messageContent).text.text;
} else {
// We handle only text messages, the other messages will be printed as their type
text = "(" + messageContent.getClass().getSimpleName() + ")";
}
while (haveAuthorization) {
getCommand();
}
}
while (!canQuit) {
Thread.sleep(1);
// Get the chat title
client.send(new TdApi.GetChat(update.message.chatId), (Result<Chat> chatIdResult) -> {
// Get the chat response
Chat chat = chatIdResult.get();
// Get the chat name
String chatName = chat.title;
// Print the message
System.out.println("Received new message from chat " + chatName + ": " + text);
});
});
// Start the client
client.start(authenticationData);
// Wait for exit
client.waitForExit();
}
/**
* Print the bot status
*/
private static void printStatus(AuthorizationState authorizationState) {
if (authorizationState instanceof TdApi.AuthorizationStateReady) {
System.out.println("Logged in");
} else if (authorizationState instanceof TdApi.AuthorizationStateClosing) {
System.out.println("Closing...");
} else if (authorizationState instanceof TdApi.AuthorizationStateClosed) {
System.out.println("Closed");
} else if (authorizationState instanceof TdApi.AuthorizationStateLoggingOut) {
System.out.println("Logging out...");
}
}
public static class OrderedChat implements Comparable<OrderedChat> {
final long chatId;
final TdApi.ChatPosition position;
OrderedChat(long chatId, TdApi.ChatPosition position) {
this.chatId = chatId;
this.position = position;
}
@Override
public int compareTo(OrderedChat o) {
if (this.position.order != o.position.order) {
return o.position.order < this.position.order ? -1 : 1;
}
if (this.chatId != o.chatId) {
return o.chatId < this.chatId ? -1 : 1;
}
return 0;
}
@Override
public boolean equals(Object obj) {
OrderedChat o = (OrderedChat) obj;
return this.chatId == o.chatId && this.position.order == o.position.order;
/**
* Check if the command sender is admin
*/
private static boolean isAdmin(MessageSender sender) {
if (sender instanceof MessageSenderUser) {
MessageSenderUser senderUser = (MessageSenderUser) sender;
return senderUser.userId == ADMIN_ID;
}
return false;
}
private static class DefaultHandler implements ResultHandler {
@Override
public void onResult(TdApi.Object object) {
print(object.toString());
/**
* Close the bot if the /stop command is sent by the administrator
*/
private static class StopCommandHandler implements CommandHandler {
private final SimpleTelegramClient client;
public StopCommandHandler(SimpleTelegramClient client) {
this.client = client;
}
}
private static class UpdateHandler implements ResultHandler {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.UpdateAuthorizationState.CONSTRUCTOR:
onAuthorizationStateUpdated(((TdApi.UpdateAuthorizationState) object).authorizationState);
break;
case TdApi.UpdateUser.CONSTRUCTOR:
TdApi.UpdateUser updateUser = (TdApi.UpdateUser) object;
users.put(updateUser.user.id, updateUser.user);
break;
case TdApi.UpdateUserStatus.CONSTRUCTOR: {
TdApi.UpdateUserStatus updateUserStatus = (TdApi.UpdateUserStatus) object;
TdApi.User user = users.get(updateUserStatus.userId);
synchronized (user) {
user.status = updateUserStatus.status;
}
break;
}
case TdApi.UpdateBasicGroup.CONSTRUCTOR:
TdApi.UpdateBasicGroup updateBasicGroup = (TdApi.UpdateBasicGroup) object;
basicGroups.put(updateBasicGroup.basicGroup.id, updateBasicGroup.basicGroup);
break;
case TdApi.UpdateSupergroup.CONSTRUCTOR:
TdApi.UpdateSupergroup updateSupergroup = (TdApi.UpdateSupergroup) object;
supergroups.put(updateSupergroup.supergroup.id, updateSupergroup.supergroup);
break;
case TdApi.UpdateSecretChat.CONSTRUCTOR:
TdApi.UpdateSecretChat updateSecretChat = (TdApi.UpdateSecretChat) object;
secretChats.put(updateSecretChat.secretChat.id, updateSecretChat.secretChat);
break;
case TdApi.UpdateNewChat.CONSTRUCTOR: {
TdApi.UpdateNewChat updateNewChat = (TdApi.UpdateNewChat) object;
TdApi.Chat chat = updateNewChat.chat;
synchronized (chat) {
chats.put(chat.id, chat);
TdApi.ChatPosition[] positions = chat.positions;
chat.positions = new TdApi.ChatPosition[0];
setChatPositions(chat, positions);
}
break;
}
case TdApi.UpdateChatTitle.CONSTRUCTOR: {
TdApi.UpdateChatTitle updateChat = (TdApi.UpdateChatTitle) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.title = updateChat.title;
}
break;
}
case TdApi.UpdateChatPhoto.CONSTRUCTOR: {
TdApi.UpdateChatPhoto updateChat = (TdApi.UpdateChatPhoto) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.photo = updateChat.photo;
}
break;
}
case TdApi.UpdateChatLastMessage.CONSTRUCTOR: {
TdApi.UpdateChatLastMessage updateChat = (TdApi.UpdateChatLastMessage) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastMessage = updateChat.lastMessage;
setChatPositions(chat, updateChat.positions);
}
break;
}
case TdApi.UpdateChatPosition.CONSTRUCTOR: {
TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object;
if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) {
break;
}
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
int i;
for (i = 0; i < chat.positions.length; i++) {
if (chat.positions[i].list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
break;
}
}
TdApi.ChatPosition[] new_positions = new TdApi.ChatPosition[chat.positions.length + (updateChat.position.order == 0 ? 0 : 1) - (i < chat.positions.length ? 1 : 0)];
int pos = 0;
if (updateChat.position.order != 0) {
new_positions[pos++] = updateChat.position;
}
for (int j = 0; j < chat.positions.length; j++) {
if (j != i) {
new_positions[pos++] = chat.positions[j];
}
}
assert pos == new_positions.length;
setChatPositions(chat, new_positions);
}
break;
}
case TdApi.UpdateChatReadInbox.CONSTRUCTOR: {
TdApi.UpdateChatReadInbox updateChat = (TdApi.UpdateChatReadInbox) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastReadInboxMessageId = updateChat.lastReadInboxMessageId;
chat.unreadCount = updateChat.unreadCount;
}
break;
}
case TdApi.UpdateChatReadOutbox.CONSTRUCTOR: {
TdApi.UpdateChatReadOutbox updateChat = (TdApi.UpdateChatReadOutbox) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastReadOutboxMessageId = updateChat.lastReadOutboxMessageId;
}
break;
}
case TdApi.UpdateChatUnreadMentionCount.CONSTRUCTOR: {
TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadMentionCount = updateChat.unreadMentionCount;
}
break;
}
case TdApi.UpdateMessageMentionRead.CONSTRUCTOR: {
TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadMentionCount = updateChat.unreadMentionCount;
}
break;
}
case TdApi.UpdateChatReplyMarkup.CONSTRUCTOR: {
TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.replyMarkupMessageId = updateChat.replyMarkupMessageId;
}
break;
}
case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: {
TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.draftMessage = updateChat.draftMessage;
setChatPositions(chat, updateChat.positions);
}
break;
}
case TdApi.UpdateChatPermissions.CONSTRUCTOR: {
TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.permissions = update.permissions;
}
break;
}
case TdApi.UpdateChatNotificationSettings.CONSTRUCTOR: {
TdApi.UpdateChatNotificationSettings update = (TdApi.UpdateChatNotificationSettings) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.notificationSettings = update.notificationSettings;
}
break;
}
case TdApi.UpdateChatDefaultDisableNotification.CONSTRUCTOR: {
TdApi.UpdateChatDefaultDisableNotification update = (TdApi.UpdateChatDefaultDisableNotification) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.defaultDisableNotification = update.defaultDisableNotification;
}
break;
}
case TdApi.UpdateChatIsMarkedAsUnread.CONSTRUCTOR: {
TdApi.UpdateChatIsMarkedAsUnread update = (TdApi.UpdateChatIsMarkedAsUnread) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.isMarkedAsUnread = update.isMarkedAsUnread;
}
break;
}
case TdApi.UpdateChatIsBlocked.CONSTRUCTOR: {
TdApi.UpdateChatIsBlocked update = (TdApi.UpdateChatIsBlocked) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.isBlocked = update.isBlocked;
}
break;
}
case TdApi.UpdateChatHasScheduledMessages.CONSTRUCTOR: {
TdApi.UpdateChatHasScheduledMessages update = (TdApi.UpdateChatHasScheduledMessages) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.hasScheduledMessages = update.hasScheduledMessages;
}
break;
}
case TdApi.UpdateUserFullInfo.CONSTRUCTOR:
TdApi.UpdateUserFullInfo updateUserFullInfo = (TdApi.UpdateUserFullInfo) object;
usersFullInfo.put(updateUserFullInfo.userId, updateUserFullInfo.userFullInfo);
break;
case TdApi.UpdateBasicGroupFullInfo.CONSTRUCTOR:
TdApi.UpdateBasicGroupFullInfo updateBasicGroupFullInfo = (TdApi.UpdateBasicGroupFullInfo) object;
basicGroupsFullInfo.put(updateBasicGroupFullInfo.basicGroupId, updateBasicGroupFullInfo.basicGroupFullInfo);
break;
case TdApi.UpdateSupergroupFullInfo.CONSTRUCTOR:
TdApi.UpdateSupergroupFullInfo updateSupergroupFullInfo = (TdApi.UpdateSupergroupFullInfo) object;
supergroupsFullInfo.put(updateSupergroupFullInfo.supergroupId, updateSupergroupFullInfo.supergroupFullInfo);
break;
default:
// print("Unsupported update:" + newLine + object);
}
}
}
private static class ErrorHandler implements ExceptionHandler {
@Override
public void onException(Throwable e) {
e.printStackTrace();
}
}
private static class AuthorizationRequestHandler implements ResultHandler {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.Error.CONSTRUCTOR:
System.err.println("Receive an error:" + newLine + object);
onAuthorizationStateUpdated(null); // repeat last action
break;
case TdApi.Ok.CONSTRUCTOR:
// result is already received through UpdateAuthorizationState, nothing to do
break;
default:
System.err.println("Receive wrong response from TDLib:" + newLine + object);
public void onCommand(Chat chat, MessageSender commandSender, String arguments) {
// Check if the sender is the admin
if (isAdmin(commandSender)) {
// Stop the client
System.out.println("Received stop command. closing...");
client.sendClose();
}
}
}

View File

@ -0,0 +1,68 @@
package it.tdlight.client;
import java.util.Objects;
import java.util.StringJoiner;
public final class APIToken {
private int apiID;
private String apiHash;
/**
* Obtain your API token here: <a href="https://my.telegram.org/auth?to=apps">https://my.telegram.org/auth?to=apps</a>
*/
public APIToken(int apiID, String apiHash) {
this.apiID = apiID;
this.apiHash = apiHash;
}
/**
* Example token
*/
public static APIToken example() {
int apiID = 94575;
String apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
return new APIToken(apiID, apiHash);
}
public int getApiID() {
return apiID;
}
public void setApiID(int apiID) {
this.apiID = apiID;
}
public String getApiHash() {
return apiHash;
}
public void setApiHash(String apiHash) {
this.apiHash = apiHash;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
APIToken apiData = (APIToken) o;
return apiID == apiData.apiID && Objects.equals(apiHash, apiData.apiHash);
}
@Override
public int hashCode() {
return Objects.hash(apiID, apiHash);
}
@Override
public String toString() {
return new StringJoiner(", ", APIToken.class.getSimpleName() + "[", "]")
.add("apiID=" + apiID)
.add("apiHash='" + apiHash + "'")
.toString();
}
}

View File

@ -0,0 +1,6 @@
package it.tdlight.client;
public interface Authenticable {
AuthenticationData getAuthenticationData();
}

View File

@ -0,0 +1,75 @@
package it.tdlight.client;
import java.util.Objects;
import java.util.StringJoiner;
@SuppressWarnings("unused")
public final class AuthenticationData {
private final Long userPhoneNumber;
private final String botToken;
private AuthenticationData(Long userPhoneNumber, String botToken) {
if ((userPhoneNumber == null) == (botToken == null)) {
throw new IllegalArgumentException("Please use either a bot token or a phone number");
}
if (botToken != null) {
if (botToken.length() < 5 || botToken.length() > 200) {
throw new IllegalArgumentException("Bot token is invalid: " + botToken);
}
}
this.userPhoneNumber = userPhoneNumber;
this.botToken = botToken;
}
public static AuthenticationData user(long userPhoneNumber) {
return new AuthenticationData(userPhoneNumber, null);
}
public static AuthenticationData bot(String botToken) {
return new AuthenticationData(null, botToken);
}
public boolean isBot() {
return botToken != null;
}
public Long getUserPhoneNumber() {
if (userPhoneNumber == null) {
throw new UnsupportedOperationException("This is not a user");
}
return userPhoneNumber;
}
public String getBotToken() {
if (botToken == null) {
throw new UnsupportedOperationException("This is not a bot");
}
return botToken;
}
@Override
public String toString() {
if (userPhoneNumber != null) {
return "+" + userPhoneNumber;
} else {
return "\"" + botToken + "\"";
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AuthenticationData that = (AuthenticationData) o;
return Objects.equals(userPhoneNumber, that.userPhoneNumber) && Objects.equals(botToken, that.botToken);
}
@Override
public int hashCode() {
return Objects.hash(userPhoneNumber, botToken);
}
}

View File

@ -0,0 +1,31 @@
package it.tdlight.client;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateReady;
import it.tdlight.jni.TdApi.GetMe;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import it.tdlight.jni.TdApi.User;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateReadyGetMe implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateReadyGetMe.class);
private final TelegramClient client;
private final AtomicReference<User> me;
public AuthorizationStateReadyGetMe(TelegramClient client, AtomicReference<User> me) {
this.client = client;
this.me = me;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) {
client.send(new GetMe(), me -> this.me.set((User) me), error -> logger.warn("Failed to execute TdApi.GetMe()"));
}
}
}

View File

@ -0,0 +1,54 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.common.utils.ScannerUtils;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateWaitEncryptionKey;
import it.tdlight.jni.TdApi.CheckDatabaseEncryptionKey;
import it.tdlight.jni.TdApi.PhoneNumberAuthenticationSettings;
import it.tdlight.jni.TdApi.SetAuthenticationPhoneNumber;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitAuthenticationDataHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitAuthenticationDataHandler.class);
private final TelegramClient client;
private final Authenticable authenticable;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitAuthenticationDataHandler(TelegramClient client,
Authenticable authenticable,
ExceptionHandler exceptionHandler) {
this.client = client;
this.authenticable = authenticable;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR) {
AuthenticationData authenticationData = authenticable.getAuthenticationData();
if (authenticationData.isBot()) {
String botToken = authenticationData.getBotToken();
TdApi.CheckAuthenticationBotToken response = new TdApi.CheckAuthenticationBotToken(botToken);
client.send(response, ok -> {}, ex -> {
logger.error("Failed to set TDLight phone number or bot token!", ex);
exceptionHandler.onException(ex);
});
} else {
PhoneNumberAuthenticationSettings phoneSettings = new PhoneNumberAuthenticationSettings(false, false, false);
String phoneNumber = String.valueOf(authenticationData.getUserPhoneNumber());
SetAuthenticationPhoneNumber response = new SetAuthenticationPhoneNumber(phoneNumber, phoneSettings);
client.send(response, ok -> {}, ex -> {
logger.error("Failed to set TDLight phone number!", ex);
exceptionHandler.onException(ex);
});
}
}
}
}

View File

@ -0,0 +1,48 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateWaitCode;
import it.tdlight.jni.TdApi.AuthorizationStateWaitOtherDeviceConfirmation;
import it.tdlight.jni.TdApi.CheckAuthenticationCode;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitCodeHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitCodeHandler.class);
private final TelegramClient client;
private final ClientInteraction clientInteraction;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitCodeHandler(TelegramClient client,
ClientInteraction clientInteraction,
ExceptionHandler exceptionHandler) {
this.client = client;
this.clientInteraction = clientInteraction;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitCode.CONSTRUCTOR) {
AuthorizationStateWaitCode authorizationState =
(AuthorizationStateWaitCode) update.authorizationState;
ParameterInfo parameterInfo = new ParameterInfoCode(authorizationState.codeInfo.phoneNumber,
authorizationState.codeInfo.nextType,
authorizationState.codeInfo.timeout,
authorizationState.codeInfo.type
);
String code = clientInteraction.onParameterRequest(InputParameter.ASK_CODE, parameterInfo);
Function response = new CheckAuthenticationCode(code);
client.send(response, ok -> {}, ex -> {
logger.error("Failed to check authentication code", ex);
exceptionHandler.onException(ex);
});
}
}
}

View File

@ -0,0 +1,35 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi.AuthorizationStateWaitEncryptionKey;
import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters;
import it.tdlight.jni.TdApi.CheckDatabaseEncryptionKey;
import it.tdlight.jni.TdApi.SetTdlibParameters;
import it.tdlight.jni.TdApi.TdlibParameters;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitEncryptionKeyHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitEncryptionKeyHandler.class);
private final TelegramClient client;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitEncryptionKeyHandler(TelegramClient client, ExceptionHandler exceptionHandler) {
this.client = client;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitEncryptionKey.CONSTRUCTOR) {
client.send(new CheckDatabaseEncryptionKey(), ok -> {}, ex -> {
logger.error("Failed to manage TDLight database encryption key!", ex);
exceptionHandler.onException(ex);
});
}
}
}

View File

@ -0,0 +1,23 @@
package it.tdlight.client;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import java.util.concurrent.CountDownLatch;
final class AuthorizationStateWaitForExit implements GenericUpdateHandler<TdApi.UpdateAuthorizationState> {
private final CountDownLatch closed;
public AuthorizationStateWaitForExit(CountDownLatch closed) {
this.closed = closed;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) {
closed.countDown();
}
}
}

View File

@ -0,0 +1,32 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi.AuthorizationStateWaitOtherDeviceConfirmation;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
final class AuthorizationStateWaitOtherDeviceConfirmationHandler
implements GenericUpdateHandler<UpdateAuthorizationState> {
private final TelegramClient client;
private final ClientInteraction clientInteraction;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitOtherDeviceConfirmationHandler(TelegramClient client,
ClientInteraction clientInteraction,
ExceptionHandler exceptionHandler) {
this.client = client;
this.clientInteraction = clientInteraction;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR) {
AuthorizationStateWaitOtherDeviceConfirmation authorizationState =
(AuthorizationStateWaitOtherDeviceConfirmation) update.authorizationState;
ParameterInfo parameterInfo = new ParameterInfoNotifyLink(authorizationState.link);
clientInteraction.onParameterRequest(InputParameter.NOTIFY_LINK, parameterInfo);
}
}
}

View File

@ -0,0 +1,47 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi.AuthorizationStateWaitCode;
import it.tdlight.jni.TdApi.AuthorizationStateWaitPassword;
import it.tdlight.jni.TdApi.CheckAuthenticationCode;
import it.tdlight.jni.TdApi.CheckAuthenticationPassword;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitPasswordHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitPasswordHandler.class);
private final TelegramClient client;
private final ClientInteraction clientInteraction;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitPasswordHandler(TelegramClient client,
ClientInteraction clientInteraction,
ExceptionHandler exceptionHandler) {
this.client = client;
this.clientInteraction = clientInteraction;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitPassword.CONSTRUCTOR) {
AuthorizationStateWaitPassword authorizationState =
(AuthorizationStateWaitPassword) update.authorizationState;
ParameterInfo parameterInfo = new ParameterInfoPasswordHint(authorizationState.passwordHint,
authorizationState.hasRecoveryEmailAddress,
authorizationState.recoveryEmailAddressPattern
);
String password = clientInteraction.onParameterRequest(InputParameter.ASK_PASSWORD, parameterInfo);
Function response = new CheckAuthenticationPassword(password);
client.send(response, ok -> {}, ex -> {
logger.error("Failed to check authentication password", ex);
exceptionHandler.onException(ex);
});
}
}
}

View File

@ -0,0 +1,59 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateWaitRegistration;
import it.tdlight.jni.TdApi.RegisterUser;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitRegistrationHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitRegistrationHandler.class);
private final TelegramClient client;
private final ClientInteraction clientInteraction;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitRegistrationHandler(TelegramClient client,
ClientInteraction clientInteraction,
ExceptionHandler exceptionHandler) {
this.client = client;
this.clientInteraction = clientInteraction;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitRegistration.CONSTRUCTOR) {
TdApi.AuthorizationStateWaitRegistration authorizationState =
(TdApi.AuthorizationStateWaitRegistration) update.authorizationState;
clientInteraction.onParameterRequest(InputParameter.TERMS_OF_SERVICE, new ParameterInfoTermsOfService(authorizationState.termsOfService));
String firstName = clientInteraction.onParameterRequest(InputParameter.ASK_FIRST_NAME, new EmptyParameterInfo());
String lastName = clientInteraction.onParameterRequest(InputParameter.ASK_LAST_NAME, new EmptyParameterInfo());
if (firstName == null || firstName.isEmpty()) {
exceptionHandler.onException(new IllegalArgumentException("First name must not be null or empty"));
return;
}
if (firstName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("First name must be under 64 characters"));
return;
}
if (lastName == null) {
exceptionHandler.onException(new IllegalArgumentException("Last name must not be null"));
return;
}
if (lastName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("Last name must be under 64 characters"));
return;
}
RegisterUser response = new RegisterUser(firstName, lastName);
client.send(response, ok -> {}, ex -> {
logger.error("Failed to register user", ex);
exceptionHandler.onException(ex);
});
}
}
}

View File

@ -0,0 +1,53 @@
package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters;
import it.tdlight.jni.TdApi.SetTdlibParameters;
import it.tdlight.jni.TdApi.TdlibParameters;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class AuthorizationStateWaitTdlibParametersHandler implements GenericUpdateHandler<UpdateAuthorizationState> {
private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateWaitEncryptionKeyHandler.class);
private final TelegramClient client;
private final TDLibSettings settings;
private final ExceptionHandler exceptionHandler;
public AuthorizationStateWaitTdlibParametersHandler(TelegramClient client,
TDLibSettings settings,
ExceptionHandler exceptionHandler) {
this.client = client;
this.settings = settings;
this.exceptionHandler = exceptionHandler;
}
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitTdlibParameters.CONSTRUCTOR) {
TdlibParameters params = new TdlibParameters();
params.useTestDc = settings.isUsingTestDatacenter();
params.databaseDirectory = settings.getDatabaseDirectoryPath().toString();
params.filesDirectory = settings.getDownloadedFilesDirectoryPath().toString();
params.useFileDatabase = settings.isFileDatabaseEnabled();
params.useChatInfoDatabase = settings.isChatInfoDatabaseEnabled();
params.useMessageDatabase = settings.isMessageDatabaseEnabled();
params.useSecretChats = false;
params.apiId = settings.getApiToken().getApiID();
params.apiHash = settings.getApiToken().getApiHash();
params.systemLanguageCode = settings.getSystemLanguageCode();
params.deviceModel = settings.getDeviceModel();
params.systemVersion = settings.getSystemVersion();
params.applicationVersion = settings.getApplicationVersion();
params.enableStorageOptimizer = settings.isStorageOptimizerEnabled();
params.ignoreFileNames = settings.isIgnoreFileNames();
client.send(new SetTdlibParameters(params), ok -> {}, ex -> {
logger.error("Failed to set TDLight parameters!", ex);
exceptionHandler.onException(ex);
});
}
}
}

View File

@ -0,0 +1,6 @@
package it.tdlight.client;
public interface ClientInteraction {
String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo);
}

View File

@ -0,0 +1,10 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.User;
public interface CommandHandler {
void onCommand(Chat chat, TdApi.MessageSender commandSender, String arguments);
}

View File

@ -0,0 +1,93 @@
package it.tdlight.client;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.Message;
import it.tdlight.jni.TdApi.MessageText;
import it.tdlight.jni.TdApi.UpdateNewMessage;
import it.tdlight.jni.TdApi.User;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
final class CommandsHandler implements GenericUpdateHandler<UpdateNewMessage> {
private static final Logger logger = LoggerFactory.getLogger(CommandsHandler.class);
private final TelegramClient client;
private final Map<String, Set<CommandHandler>> commandHandlers;
private final AtomicReference<User> me;
public CommandsHandler(TelegramClient client,
Map<String, Set<CommandHandler>> commandHandlers,
AtomicReference<User> me) {
this.client = client;
this.commandHandlers = commandHandlers;
this.me = me;
}
@Override
public void onUpdate(UpdateNewMessage update) {
if (update.getConstructor() == UpdateNewMessage.CONSTRUCTOR) {
Message message = update.message;
if (message.forwardInfo == null && !message.isChannelPost
&& (message.authorSignature == null || message.authorSignature.isEmpty())
&& message.content.getConstructor() == MessageText.CONSTRUCTOR) {
MessageText messageText = (MessageText) message.content;
String text = messageText.text.text;
if (text.startsWith("/")) {
String[] parts = text.split(" ", 2);
if (parts.length == 1) {
parts = new String[] {parts[0], ""};
}
if (parts.length == 2) {
String currentUnsplittedCommandName = parts[0].substring(1);
String arguments = parts[1].trim();
String[] commandParts = currentUnsplittedCommandName.split("@", 2);
String currentCommandName = null;
boolean correctTarget = false;
if (commandParts.length == 2) {
String myUsername = this.getMyUsername().orElse(null);
if (myUsername != null) {
currentCommandName = commandParts[0].trim();
String currentTargetUsername = commandParts[1];
if (myUsername.equalsIgnoreCase(currentTargetUsername)) {
correctTarget = true;
}
}
} else if (commandParts.length == 1) {
currentCommandName = commandParts[0].trim();
correctTarget = true;
}
if (correctTarget) {
String commandName = currentCommandName;
Set<CommandHandler> handlers = commandHandlers.getOrDefault(currentCommandName, Collections.emptySet());
for (CommandHandler handler : handlers) {
client.send(new TdApi.GetChat(message.chatId),
response -> handler.onCommand((Chat) response, message.sender, arguments),
error -> logger.warn("Error when handling the command {}", commandName, error)
);
}
}
}
}
}
}
}
private Optional<String> getMyUsername() {
User user = this.me.get();
if (user == null || user.username == null || user.username.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(user.username);
}
}
}

View File

@ -0,0 +1,3 @@
package it.tdlight.client;
public final class EmptyParameterInfo implements ParameterInfo {}

View File

@ -0,0 +1,23 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi;
/**
* Interface for incoming responses from TDLib.
*/
@FunctionalInterface
public interface GenericResultHandler<T extends TdApi.Object> {
/**
* Callback called when TDLib responds.
*/
void onResult(Result<T> result);
default void onResult(TdApi.Object result) {
onResult(Result.of(result));
}
default void onErrorResult(Throwable exception) {
onResult(Result.ofError(exception));
}
}

View File

@ -0,0 +1,18 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.Update;
/**
* Interface for incoming updates from TDLib.
*/
@FunctionalInterface
public interface GenericUpdateHandler<T extends Update> {
/**
* Callback called on incoming update from TDLib.
*
* @param update Update of type TdApi.Update about new events.
*/
void onUpdate(T update);
}

View File

@ -0,0 +1,10 @@
package it.tdlight.client;
public enum InputParameter {
ASK_FIRST_NAME,
ASK_LAST_NAME,
ASK_CODE,
ASK_PASSWORD,
NOTIFY_LINK,
TERMS_OF_SERVICE
}

View File

@ -0,0 +1,3 @@
package it.tdlight.client;
public interface ParameterInfo {}

View File

@ -0,0 +1,67 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi.AuthenticationCodeType;
import java.util.Objects;
import java.util.StringJoiner;
public final class ParameterInfoCode implements ParameterInfo {
private final String phoneNumber;
private final AuthenticationCodeType nextType;
private final int timeout;
private final AuthenticationCodeType type;
public ParameterInfoCode(String phoneNumber,
AuthenticationCodeType nextType,
int timeout,
AuthenticationCodeType type) {
this.phoneNumber = phoneNumber;
this.nextType = nextType;
this.timeout = timeout;
this.type = type;
}
public String getPhoneNumber() {
return phoneNumber;
}
public AuthenticationCodeType getNextType() {
return nextType;
}
public int getTimeout() {
return timeout;
}
public AuthenticationCodeType getType() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterInfoCode that = (ParameterInfoCode) o;
return timeout == that.timeout && Objects.equals(phoneNumber, that.phoneNumber) && Objects.equals(nextType,
that.nextType
) && Objects.equals(type, that.type);
}
@Override
public int hashCode() {
return Objects.hash(phoneNumber, nextType, timeout, type);
}
@Override
public String toString() {
return new StringJoiner(", ", ParameterInfoCode.class.getSimpleName() + "[", "]")
.add("phoneNumber='" + phoneNumber + "'")
.add("nextType=" + nextType)
.add("timeout=" + timeout)
.add("type=" + type)
.toString();
}
}

View File

@ -0,0 +1,41 @@
package it.tdlight.client;
import java.util.Objects;
import java.util.StringJoiner;
public final class ParameterInfoNotifyLink implements ParameterInfo {
private final String link;
public ParameterInfoNotifyLink(String link) {
this.link = link;
}
public String getLink() {
return link;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterInfoNotifyLink that = (ParameterInfoNotifyLink) o;
return Objects.equals(link, that.link);
}
@Override
public int hashCode() {
return Objects.hash(link);
}
@Override
public String toString() {
return new StringJoiner(", ", ParameterInfoNotifyLink.class.getSimpleName() + "[", "]")
.add("link='" + link + "'")
.toString();
}
}

View File

@ -0,0 +1,58 @@
package it.tdlight.client;
import java.util.Objects;
import java.util.StringJoiner;
public final class ParameterInfoPasswordHint implements ParameterInfo {
private final String hint;
private final boolean hasRecoveryEmailAddress;
private final String recoveryEmailAddressPattern;
public ParameterInfoPasswordHint(String hint, boolean hasRecoveryEmailAddress, String recoveryEmailAddressPattern) {
this.hint = hint;
this.hasRecoveryEmailAddress = hasRecoveryEmailAddress;
this.recoveryEmailAddressPattern = recoveryEmailAddressPattern;
}
public String getHint() {
return hint;
}
public String getRecoveryEmailAddressPattern() {
return recoveryEmailAddressPattern;
}
public boolean hasRecoveryEmailAddress() {
return hasRecoveryEmailAddress;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterInfoPasswordHint that = (ParameterInfoPasswordHint) o;
return hasRecoveryEmailAddress == that.hasRecoveryEmailAddress && Objects.equals(hint, that.hint) && Objects.equals(
recoveryEmailAddressPattern,
that.recoveryEmailAddressPattern
);
}
@Override
public int hashCode() {
return Objects.hash(hint, hasRecoveryEmailAddress, recoveryEmailAddressPattern);
}
@Override
public String toString() {
return new StringJoiner(", ", ParameterInfoPasswordHint.class.getSimpleName() + "[", "]")
.add("hint='" + hint + "'")
.add("hasRecoveryEmailAddress=" + hasRecoveryEmailAddress)
.add("recoveryEmailAddressPattern='" + recoveryEmailAddressPattern + "'")
.toString();
}
}

View File

@ -0,0 +1,41 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi.TermsOfService;
import java.util.Objects;
import java.util.StringJoiner;
public final class ParameterInfoTermsOfService implements ParameterInfo {
private final TermsOfService termsOfService;
public ParameterInfoTermsOfService(TermsOfService termsOfService) {
this.termsOfService = termsOfService;
}
public TermsOfService getTermsOfService() {
return termsOfService;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ParameterInfoTermsOfService that = (ParameterInfoTermsOfService) o;
return Objects.equals(termsOfService, that.termsOfService);
}
@Override
public int hashCode() {
return Objects.hash(termsOfService);
}
@Override
public String toString() {
return new StringJoiner(", ", ParameterInfoTermsOfService.class.getSimpleName() + "[", "]")
.add("termsOfService=" + termsOfService)
.toString();
}
}

View File

@ -0,0 +1,91 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Error;
import java.util.Objects;
import java.util.Optional;
import java.util.StringJoiner;
public final class Result<T extends TdApi.Object> {
private final T object;
private final Error error;
private final TelegramError telegramError;
private Result(T object, TdApi.Error error, TelegramError telegramError) {
this.object = object;
this.error = error;
this.telegramError = telegramError;
}
public static <T extends TdApi.Object> Result<T> of(TdApi.Object response) {
if (response instanceof TdApi.Error) {
return new Result<>(null, (TdApi.Error) response, null);
} else {
//noinspection unchecked
return new Result<>((T) response, null, null);
}
}
public static <T extends TdApi.Object> Result<T> ofError(Throwable response) {
return new Result<>(null, null, new TelegramError(response));
}
public T get() {
if (error != null) {
throw new TelegramError(error);
} else if (telegramError != null) {
throw telegramError;
}
return Objects.requireNonNull(object);
}
public boolean isError() {
return error != null || telegramError != null;
}
public TdApi.Error getError() {
if (telegramError != null) {
return telegramError.getError();
}
return Objects.requireNonNull(error);
}
public Optional<TdApi.Error> error() {
if (error != null) {
return Optional.of(error);
} else if (telegramError != null) {
return Optional.of(telegramError.getError());
} else {
return Optional.empty();
}
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Result<?> result = (Result<?>) o;
return Objects.equals(object, result.object) && Objects.equals(error, result.error) && Objects.equals(telegramError,
result.telegramError
);
}
@Override
public int hashCode() {
return Objects.hash(object, error, telegramError);
}
@Override
public String toString() {
return new StringJoiner(", ", Result.class.getSimpleName() + "[", "]")
.add("object=" + object)
.add("error=" + error)
.add("telegramError=" + telegramError)
.toString();
}
}

View File

@ -0,0 +1,81 @@
package it.tdlight.client;
import it.tdlight.common.utils.ScannerUtils;
import it.tdlight.jni.TdApi.TermsOfService;
final class ScannerClientInteraction implements ClientInteraction {
private final Authenticable authenticable;
public ScannerClientInteraction(Authenticable authenticable) {
this.authenticable = authenticable;
}
@Override
public String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) {
AuthenticationData authenticationData = authenticable.getAuthenticationData();
String who;
if (authenticationData.isBot()) {
who = authenticationData.getBotToken().split(":", 2)[0];
} else {
who = "+" + authenticationData.getUserPhoneNumber();
}
String question;
boolean trim = false;
switch (parameter) {
case ASK_FIRST_NAME: question = "Enter first name"; trim = true; break;
case ASK_LAST_NAME: question = "Enter last name"; trim = true; break;
case ASK_CODE:
question = "Enter authentication code";
ParameterInfoCode codeInfo = ((ParameterInfoCode) parameterInfo);
question += "\n\tPhone number: " + codeInfo.getPhoneNumber();
question += "\n\tTimeout: " + codeInfo.getTimeout() + " seconds";
question += "\n\tCode type: " + codeInfo.getType().getClass().getSimpleName().replace("AuthenticationCodeType", "");
if (codeInfo.getNextType() != null) {
question += "\n\tNext code type: " + codeInfo.getNextType().getClass().getSimpleName().replace("AuthenticationCodeType", "");
}
trim = true;
break;
case ASK_PASSWORD:
question = "Enter your password";
String passwordMessage = "Password authorization:";
String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint();
if (hint != null && !hint.isEmpty()) {
passwordMessage += "\n\tHint: " + hint;
}
boolean hasRecoveryEmailAddress = ((ParameterInfoPasswordHint) parameterInfo).hasRecoveryEmailAddress();
passwordMessage += "\n\tHas recovery email: " + hasRecoveryEmailAddress;
String recoveryEmailAddressPattern = ((ParameterInfoPasswordHint) parameterInfo).getRecoveryEmailAddressPattern();
if (recoveryEmailAddressPattern != null && !recoveryEmailAddressPattern.isEmpty()) {
passwordMessage += "\n\tRecovery email address pattern: " + recoveryEmailAddressPattern;
}
System.out.println(passwordMessage);
break;
case NOTIFY_LINK:
System.out.println("Please confirm this login link on another device: "
+ ((ParameterInfoNotifyLink) parameterInfo).getLink());
return "";
case TERMS_OF_SERVICE:
TermsOfService tos = ((ParameterInfoTermsOfService) parameterInfo).getTermsOfService();
question = "Terms of service:\n\t" + tos.text.text;
if (tos.minUserAge > 0) {
question += "\n\tMinimum user age: " + tos.minUserAge;
}
if (tos.showPopup) {
question += "\nPlease press enter.";
trim = true;
} else {
System.out.println(question);
return "";
}
break;
default: question = parameter.toString(); break;
}
String result = ScannerUtils.askParameter(who, question);
if (trim) {
return result.trim();
} else {
return result;
}
}
}

View File

@ -0,0 +1,245 @@
package it.tdlight.client;
import it.tdlight.common.ConstructorDetector;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.Init;
import it.tdlight.common.Log;
import it.tdlight.common.ResultHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.common.internal.CommonClientManager;
import it.tdlight.common.utils.CantLoadLibrary;
import it.tdlight.common.utils.LibraryVersion;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Message;
import it.tdlight.jni.TdApi.MessageText;
import it.tdlight.jni.TdApi.UpdateNewMessage;
import it.tdlight.jni.TdApi.User;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("unused")
public final class SimpleTelegramClient implements Authenticable {
private static final Logger logger = LoggerFactory.getLogger(SimpleTelegramClient.class);
static {
try {
Init.start();
} catch (CantLoadLibrary e) {
throw new RuntimeException("Can't load native libraries", e);
}
try {
//noinspection deprecation
Log.setVerbosityLevel(1);
} catch (Throwable ex) {
logger.warn("Can't set verbosity level", ex);
}
}
private final TelegramClient client;
private ClientInteraction clientInteraction = new ScannerClientInteraction(this);
private final TDLibSettings settings;
private AuthenticationData authenticationData;
private final Map<String, Set<CommandHandler>> commandHandlers = new ConcurrentHashMap<>();
private final Set<ResultHandler> updateHandlers = new ConcurrentHashMap<ResultHandler, Object>()
.keySet(new Object());
private final Set<ExceptionHandler> updateExceptionHandlers = new ConcurrentHashMap<ExceptionHandler, Object>()
.keySet(new Object());
private final Set<ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<ExceptionHandler, Object>()
.keySet(new Object());
private final CountDownLatch closed = new CountDownLatch(1);
public SimpleTelegramClient(TDLibSettings settings) {
this.client = CommonClientManager.create(LibraryVersion.IMPLEMENTATION_NAME);
this.settings = settings;
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitTdlibParametersHandler(client, settings, this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitEncryptionKeyHandler(client, this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitAuthenticationDataHandler(client, this,
this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitRegistrationHandler(client, new SimpleTelegramClientInteraction(),
this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitPasswordHandler(client, new SimpleTelegramClientInteraction(),
this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitOtherDeviceConfirmationHandler(client, new SimpleTelegramClientInteraction(),
this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitCodeHandler(client, new SimpleTelegramClientInteraction(),
this::handleDefaultException));
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class, new AuthorizationStateWaitForExit(this.closed));
AtomicReference<User> me = new AtomicReference<>();
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class, new AuthorizationStateReadyGetMe(client, me));
this.addUpdateHandler(TdApi.UpdateNewMessage.class, new CommandsHandler(client, this.commandHandlers, me));
}
private void handleUpdate(TdApi.Object update) {
boolean handled = false;
for (ResultHandler updateHandler : updateHandlers) {
updateHandler.onResult(update);
handled = true;
}
if (!handled) {
logger.warn("An update was not handled, please use addUpdateHandler(handler) before starting the client!");
}
}
private void handleUpdateException(Throwable ex) {
boolean handled = false;
for (ExceptionHandler updateExceptionHandler : updateExceptionHandlers) {
updateExceptionHandler.onException(ex);
handled = true;
}
if (!handled) {
logger.warn("Error received from Telegram!", ex);
}
}
private void handleDefaultException(Throwable ex) {
boolean handled = false;
for (ExceptionHandler exceptionHandler : defaultExceptionHandlers) {
exceptionHandler.onException(ex);
handled = true;
}
if (!handled) {
logger.warn("Unhandled exception!", ex);
}
}
@Override
public AuthenticationData getAuthenticationData() {
return authenticationData;
}
public void setClientInteraction(ClientInteraction clientInteraction) {
this.clientInteraction = clientInteraction;
}
public <T extends TdApi.Update> void addCommandHandler(String commandName, CommandHandler handler) {
Set<CommandHandler> handlers = this.commandHandlers
.computeIfAbsent(commandName, k -> new ConcurrentHashMap<CommandHandler, Object>().keySet(new Object()));
handlers.add(handler);
}
@SuppressWarnings("unchecked")
public <T extends TdApi.Update> void addUpdateHandler(Class<T> updateType, GenericUpdateHandler<T> handler) {
int updateConstructor = ConstructorDetector.getConstructor(updateType);
this.updateHandlers.add(update -> {
if (update.getConstructor() == updateConstructor) {
handler.onUpdate((T) update);
}
});
}
public void addUpdatesHandler(GenericUpdateHandler<TdApi.Update> handler) {
this.updateHandlers.add(update -> {
if (update instanceof TdApi.Update) {
handler.onUpdate((TdApi.Update) update);
} else {
logger.warn("Unknown update type: {}", update);
}
});
}
/**
* Optional handler to handle errors received from TDLib
*/
public void addUpdateExceptionHandler(ExceptionHandler updateExceptionHandler) {
this.updateExceptionHandlers.add(updateExceptionHandler);
}
/**
* Optional handler to handle uncaught errors (when using send without an appropriate error handler)
*/
public void addDefaultExceptionHandler(ExceptionHandler defaultExceptionHandlers) {
this.defaultExceptionHandlers.add(defaultExceptionHandlers);
}
/**
* Start the client
*/
public void start(AuthenticationData authenticationData) {
this.authenticationData = authenticationData;
createDirectories();
client.initialize(this::handleUpdate, this::handleUpdateException, this::handleDefaultException);
}
private void createDirectories() {
try {
if (Files.notExists(settings.getDatabaseDirectoryPath())) {
Files.createDirectories(settings.getDatabaseDirectoryPath());
}
if (Files.notExists(settings.getDownloadedFilesDirectoryPath())) {
Files.createDirectories(settings.getDownloadedFilesDirectoryPath());
}
} catch (IOException ex) {
throw new UncheckedIOException("Can't create TDLight directories", ex);
}
}
/**
* Send a function and get the result
*/
public <T extends TdApi.Object> void send(TdApi.Function function, GenericResultHandler<T> resultHandler) {
client.send(function, resultHandler::onResult, resultHandler::onErrorResult);
}
/**
* Execute a synchronous function.
* <strong>Please note that only some functions can be executed using this method.</strong>
* If you want to execute a function please use {@link #send(Function, GenericResultHandler)}!
*/
public <T extends TdApi.Object> Result<T> execute(TdApi.Function function) {
return Result.of(client.execute(function));
}
/**
* Send the close signal but don't wait
*/
public void sendClose() {
client.send(new TdApi.Close(), ok -> {});
}
/**
* Send the close signal and wait for exit
*/
public void closeAndWait() throws InterruptedException {
client.send(new TdApi.Close(), ok -> {});
this.waitForExit();
}
/**
* Wait until TDLight is closed
*/
public void waitForExit() throws InterruptedException {
closed.await();
}
private final class SimpleTelegramClientInteraction implements ClientInteraction {
@Override
public String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) {
return clientInteraction.onParameterRequest(parameter, parameterInfo);
}
}
}

View File

@ -0,0 +1,240 @@
package it.tdlight.client;
import it.tdlight.common.utils.LibraryVersion;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import java.util.Objects;
import java.util.StringJoiner;
@SuppressWarnings("unused")
public final class TDLibSettings {
private static final Path USER_HOME_PATH = Paths.get(System.getProperty("user.home"));
private static final String DISPLAY_LANGUAGE = Locale.getDefault().getDisplayLanguage();
private static final String OS_NAME = System.getProperty("os.name", "unknown");
private static final String OS_VERSION = System.getProperty("os.version", "unknown");
private static final Path TDLIGHT_SESSION_PATH = USER_HOME_PATH.resolve("tdlight-session");
private static final Path TDLIGHT_SESSION_DATA_PATH = TDLIGHT_SESSION_PATH.resolve("data");
private static final Path TDLIGHT_SESSION_DOWNLOADS_PATH = TDLIGHT_SESSION_PATH.resolve("downloads");
private boolean useTestDatacenter;
private Path databaseDirectoryPath;
private Path downloadedFilesDirectoryPath;
private boolean fileDatabaseEnabled;
private boolean chatInfoDatabaseEnabled;
private boolean messageDatabaseEnabled;
private APIToken apiToken;
private String systemLanguageCode;
private String deviceModel;
private String systemVersion;
private String applicationVersion;
private boolean enableStorageOptimizer;
private boolean ignoreFileNames;
private TDLibSettings(boolean useTestDatacenter,
Path databaseDirectoryPath,
Path downloadedFilesDirectoryPath,
boolean fileDatabaseEnabled,
boolean chatInfoDatabaseEnabled,
boolean messageDatabaseEnabled,
APIToken apiToken,
String systemLanguageCode,
String deviceModel,
String systemVersion,
String applicationVersion,
boolean enableStorageOptimizer,
boolean ignoreFileNames) {
this.useTestDatacenter = useTestDatacenter;
this.databaseDirectoryPath = databaseDirectoryPath;
this.downloadedFilesDirectoryPath = downloadedFilesDirectoryPath;
this.fileDatabaseEnabled = fileDatabaseEnabled;
this.chatInfoDatabaseEnabled = chatInfoDatabaseEnabled;
this.messageDatabaseEnabled = messageDatabaseEnabled;
this.apiToken = apiToken;
this.systemLanguageCode = systemLanguageCode;
this.deviceModel = deviceModel;
this.systemVersion = systemVersion;
this.applicationVersion = applicationVersion;
this.enableStorageOptimizer = enableStorageOptimizer;
this.ignoreFileNames = ignoreFileNames;
}
public static TDLibSettings create(APIToken apiToken) {
return new TDLibSettings(false,
TDLIGHT_SESSION_DATA_PATH,
TDLIGHT_SESSION_DOWNLOADS_PATH,
true,
true,
true,
apiToken,
DISPLAY_LANGUAGE,
OS_NAME,
OS_VERSION,
LibraryVersion.VERSION,
true,
false
);
}
public boolean isUsingTestDatacenter() {
return useTestDatacenter;
}
public void setUseTestDatacenter(boolean useTestDatacenter) {
this.useTestDatacenter = useTestDatacenter;
}
public Path getDatabaseDirectoryPath() {
return databaseDirectoryPath;
}
public void setDatabaseDirectoryPath(Path databaseDirectoryPath) {
this.databaseDirectoryPath = databaseDirectoryPath;
}
public Path getDownloadedFilesDirectoryPath() {
return downloadedFilesDirectoryPath;
}
public void setDownloadedFilesDirectoryPath(Path downloadedFilesDirectoryPath) {
this.downloadedFilesDirectoryPath = downloadedFilesDirectoryPath;
}
public boolean isFileDatabaseEnabled() {
return fileDatabaseEnabled;
}
public void setFileDatabaseEnabled(boolean fileDatabaseEnabled) {
this.fileDatabaseEnabled = fileDatabaseEnabled;
}
public boolean isChatInfoDatabaseEnabled() {
return chatInfoDatabaseEnabled;
}
public void setChatInfoDatabaseEnabled(boolean chatInfoDatabaseEnabled) {
this.chatInfoDatabaseEnabled = chatInfoDatabaseEnabled;
}
public boolean isMessageDatabaseEnabled() {
return messageDatabaseEnabled;
}
public void setMessageDatabaseEnabled(boolean messageDatabaseEnabled) {
this.messageDatabaseEnabled = messageDatabaseEnabled;
}
public APIToken getApiToken() {
return apiToken;
}
public void setApiToken(APIToken apiToken) {
this.apiToken = apiToken;
}
public String getSystemLanguageCode() {
return systemLanguageCode;
}
public void setSystemLanguageCode(String systemLanguageCode) {
this.systemLanguageCode = systemLanguageCode;
}
public String getDeviceModel() {
return deviceModel;
}
public void setDeviceModel(String deviceModel) {
this.deviceModel = deviceModel;
}
public String getSystemVersion() {
return systemVersion;
}
public void setSystemVersion(String systemVersion) {
this.systemVersion = systemVersion;
}
public String getApplicationVersion() {
return applicationVersion;
}
public void setApplicationVersion(String applicationVersion) {
this.applicationVersion = applicationVersion;
}
public boolean isStorageOptimizerEnabled() {
return enableStorageOptimizer;
}
public void setEnableStorageOptimizer(boolean enableStorageOptimizer) {
this.enableStorageOptimizer = enableStorageOptimizer;
}
public boolean isIgnoreFileNames() {
return ignoreFileNames;
}
public void setIgnoreFileNames(boolean ignoreFileNames) {
this.ignoreFileNames = ignoreFileNames;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TDLibSettings that = (TDLibSettings) o;
return useTestDatacenter == that.useTestDatacenter && fileDatabaseEnabled == that.fileDatabaseEnabled
&& chatInfoDatabaseEnabled == that.chatInfoDatabaseEnabled
&& messageDatabaseEnabled == that.messageDatabaseEnabled
&& enableStorageOptimizer == that.enableStorageOptimizer && ignoreFileNames == that.ignoreFileNames
&& Objects.equals(databaseDirectoryPath, that.databaseDirectoryPath) && Objects.equals(
downloadedFilesDirectoryPath,
that.downloadedFilesDirectoryPath
) && Objects.equals(apiToken, that.apiToken) && Objects.equals(systemLanguageCode, that.systemLanguageCode)
&& Objects.equals(deviceModel, that.deviceModel) && Objects.equals(systemVersion, that.systemVersion)
&& Objects.equals(applicationVersion, that.applicationVersion);
}
@Override
public int hashCode() {
return Objects.hash(useTestDatacenter,
databaseDirectoryPath,
downloadedFilesDirectoryPath,
fileDatabaseEnabled,
chatInfoDatabaseEnabled,
messageDatabaseEnabled, apiToken,
systemLanguageCode,
deviceModel,
systemVersion,
applicationVersion,
enableStorageOptimizer,
ignoreFileNames
);
}
@Override
public String toString() {
return new StringJoiner(", ", TDLibSettings.class.getSimpleName() + "[", "]")
.add("useTestDatacenter=" + useTestDatacenter)
.add("databaseDirectoryPath=" + databaseDirectoryPath)
.add("downloadedFilesDirectoryPath=" + downloadedFilesDirectoryPath)
.add("fileDatabaseEnabled=" + fileDatabaseEnabled)
.add("chatInfoDatabaseEnabled=" + chatInfoDatabaseEnabled)
.add("messageDatabaseEnabled=" + messageDatabaseEnabled)
.add("apiData=" + apiToken)
.add("systemLanguageCode='" + systemLanguageCode + "'")
.add("deviceModel='" + deviceModel + "'")
.add("systemVersion='" + systemVersion + "'")
.add("applicationVersion='" + applicationVersion + "'")
.add("enableStorageOptimizer=" + enableStorageOptimizer)
.add("ignoreFileNames=" + ignoreFileNames)
.toString();
}
}

View File

@ -0,0 +1,63 @@
package it.tdlight.client;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Error;
import java.util.Objects;
import java.util.StringJoiner;
public final class TelegramError extends RuntimeException {
private final int code;
private final String message;
public TelegramError(Error error) {
super(error.code + ": " + error.message);
this.code = error.code;
this.message = error.message;
}
public TelegramError(Error error, Throwable cause) {
super(error.code + ": " + error.message, cause);
this.code = error.code;
this.message = error.message;
}
public TelegramError(Throwable cause) {
super(cause);
this.code = 500;
if (cause.getMessage() != null) {
this.message = cause.getMessage();
} else {
this.message = "Error";
}
}
public int getErrorCode() {
return code;
}
public String getErrorMessage() {
return message;
}
public Error getError() {
return new TdApi.Error(code, message);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
TelegramError that = (TelegramError) o;
return code == that.code && Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(code, message);
}
}

View File

@ -19,73 +19,81 @@ package it.tdlight.common;
import it.tdlight.jni.TdApi;
import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
* Identify the class by using the Constructor.
*/
public class ConstructorDetector {
@SuppressWarnings("rawtypes")
public final class ConstructorDetector {
static {
// Call this to load static methods and prevent errors during startup!
try {
Init.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
private static ConcurrentHashMap<Integer, Class> constructorHashMap;
/**
* Initialize the ConstructorDetector, it is called from the Init class.
*/
public static void init() {
if (constructorHashMap != null) {
return;
}
Class[] classes = TdApi.class.getDeclaredClasses();
setConstructorHashMap(classes);
}
/**
* Identify the class.
* @param CONSTRUCTOR CONSTRUCTOR of the Tdlib API.
* @return The class related to CONSTRUCTOR.
*/
public static Class getClass(int CONSTRUCTOR) {
return constructorHashMap.getOrDefault(CONSTRUCTOR, null);
}
private static void setConstructorHashMap(Class[] tdApiClasses) {
constructorHashMap = new ConcurrentHashMap<>();
for (Class apiClass : tdApiClasses) {
Field CONSTRUCTORField;
int CONSTRUCTOR;
try {
CONSTRUCTORField = apiClass.getDeclaredField("CONSTRUCTOR");
} catch (NoSuchFieldException e) {
continue;
}
try {
CONSTRUCTOR = CONSTRUCTORField.getInt(null);
} catch (IllegalAccessException e) {
continue;
}
constructorHashMap.put(CONSTRUCTOR, apiClass);
}
}
public static ConcurrentHashMap<Integer, Class> getTDConstructorsUnsafe() {
return constructorHashMap;
static {
// Call this to load static methods and prevent errors during startup!
try {
Init.start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
public static void registerExternalClass(int constructor, Class<?> clazz) {
constructorHashMap.put(constructor, clazz);
private static ConcurrentHashMap<Integer, Class> constructorHashMap;
private static ConcurrentHashMap<Class, Integer> constructorHashMapInverse;
/**
* Initialize the ConstructorDetector, it is called from the Init class.
*/
public static void init() {
if (constructorHashMap != null) {
return;
}
Class[] classes = TdApi.class.getDeclaredClasses();
setConstructorHashMap(classes);
}
/**
* Identify the class.
*
* @param CONSTRUCTOR CONSTRUCTOR of the Tdlib API.
* @return The class related to CONSTRUCTOR.
*/
public static Class getClass(int CONSTRUCTOR) {
return constructorHashMap.getOrDefault(CONSTRUCTOR, null);
}
/**
* Identify the class.
*
* @param clazz class of the TDLib API.
* @return The CONSTRUCTOR.
*/
public static int getConstructor(Class<? extends TdApi.Object> clazz) {
return Objects.requireNonNull(constructorHashMapInverse.get(clazz));
}
private static void setConstructorHashMap(Class[] tdApiClasses) {
constructorHashMap = new ConcurrentHashMap<>();
constructorHashMapInverse = new ConcurrentHashMap<>();
for (Class apiClass : tdApiClasses) {
Field CONSTRUCTORField;
int CONSTRUCTOR;
try {
CONSTRUCTORField = apiClass.getDeclaredField("CONSTRUCTOR");
} catch (NoSuchFieldException e) {
continue;
}
try {
CONSTRUCTOR = CONSTRUCTORField.getInt(null);
} catch (IllegalAccessException e) {
continue;
}
constructorHashMap.put(CONSTRUCTOR, apiClass);
constructorHashMapInverse.put(apiClass, CONSTRUCTOR);
}
}
}

View File

@ -3,5 +3,6 @@ package it.tdlight.common;
import it.tdlight.jni.TdApi.Object;
public interface EventsHandler {
void handleClientEvents(int clientId, boolean isClosed, long[] clientEventIds, Object[] clientEvents);
}

View File

@ -1,8 +1,7 @@
package it.tdlight.common;
/**
* Interface for handler of exceptions thrown while invoking ResultHandler.
* By default, all such exceptions are ignored.
* Interface for handler of exceptions thrown while invoking ResultHandler. By default, all such exceptions are ignored.
* All exceptions thrown from ExceptionHandler are ignored.
*/
public interface ExceptionHandler {

View File

@ -24,26 +24,26 @@ import it.tdlight.common.utils.Os;
/**
* Init class to successfully initialize Tdlib
*/
public class Init {
public final class Init {
private static boolean started = false;
private static boolean started = false;
/**
* Initialize Tdlib
*
* @throws CantLoadLibrary An exception that is thrown when the LoadLibrary class fails to load the library.
*/
public synchronized static void start() throws CantLoadLibrary {
if (!started) {
Os os = LoadLibrary.getOs();
/**
* Initialize Tdlib
*
* @throws CantLoadLibrary An exception that is thrown when the LoadLibrary class fails to load the library.
*/
public synchronized static void start() throws CantLoadLibrary {
if (!started) {
Os os = LoadLibrary.getOs();
if (os == Os.WINDOWS) {
// Since 3.0.0, libraries for windows are statically compiled into tdjni.dll
}
if (os == Os.WINDOWS) {
// Since 3.0.0, libraries for windows are statically compiled into tdjni.dll
}
LoadLibrary.load("tdjni");
ConstructorDetector.init();
started = true;
}
}
LoadLibrary.load("tdjni");
ConstructorDetector.init();
started = true;
}
}
}

View File

@ -5,10 +5,9 @@ import it.tdlight.jni.TdApi;
import java.util.function.Consumer;
/**
* Class used for managing internal TDLib logging.
* Use TdApi.*Log* methods instead.
* Class used for managing internal TDLib logging. Use TdApi.*Log* methods instead.
*/
public class Log {
public final class Log {
static {
try {
@ -21,13 +20,13 @@ public class Log {
/**
* Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err.
* Use this method to write the log to a file instead.
* Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err. Use this method to
* write the log to a file instead.
*
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
* @param filePath Path to a file for writing TDLib internal log. Use an empty path to
* switch back to logging to the System.err.
* @param filePath Path to a file for writing TDLib internal log. Use an empty path to switch back to logging to the
* System.err.
* @return whether opening the log file succeeded.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
*/
@Deprecated
public static synchronized boolean setFilePath(String filePath) {
@ -37,9 +36,9 @@ public class Log {
/**
* Changes the maximum size of TDLib log file.
*
* @param maxFileSize The maximum size of the file to where the internal TDLib log is written before the file will be
* auto-rotated. Must be positive. Defaults to 10 MB.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
* @param maxFileSize The maximum size of the file to where the internal TDLib log is written
* before the file will be auto-rotated. Must be positive. Defaults to 10 MB.
*/
@Deprecated
public static synchronized void setMaxFileSize(long maxFileSize) {
@ -49,16 +48,13 @@ public class Log {
/**
* Changes TDLib log verbosity.
*
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogVerbosityLevel}, to be removed in the future.
* @param verbosityLevel New value of log verbosity level. Must be non-negative.
* Value 0 corresponds to fatal errors,
* value 1 corresponds to java.util.logging.Level.SEVERE,
* value 2 corresponds to java.util.logging.Level.WARNING,
* value 3 corresponds to java.util.logging.Level.INFO,
* value 4 corresponds to java.util.logging.Level.FINE,
* value 5 corresponds to java.util.logging.Level.FINER,
* value greater than 5 can be used to enable even more logging.
* @param verbosityLevel New value of log verbosity level. Must be non-negative. Value 0 corresponds to fatal errors,
* value 1 corresponds to java.util.logging.Level.SEVERE, value 2 corresponds to
* java.util.logging.Level.WARNING, value 3 corresponds to java.util.logging.Level.INFO, value 4
* corresponds to java.util.logging.Level.FINE, value 5 corresponds to
* java.util.logging.Level.FINER, value greater than 5 can be used to enable even more logging.
* Default value of the log verbosity level is 5.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogVerbosityLevel}, to be removed in the future.
*/
@Deprecated
public static synchronized void setVerbosityLevel(int verbosityLevel) {
@ -66,8 +62,11 @@ public class Log {
}
/**
* Sets the callback that will be called when a fatal error happens. None of the TDLib methods can be called from the callback. The TDLib will crash as soon as callback returns. By default the callback set to print in stderr.
* @param fatalErrorCallback Callback that will be called when a fatal error happens. Pass null to restore default callback.
* Sets the callback that will be called when a fatal error happens. None of the TDLib methods can be called from the
* callback. The TDLib will crash as soon as callback returns. By default the callback set to print in stderr.
*
* @param fatalErrorCallback Callback that will be called when a fatal error happens. Pass null to restore default
* callback.
*/
public static synchronized void setFatalErrorCallback(Consumer<String> fatalErrorCallback) {
NativeLog.setFatalErrorCallback(fatalErrorCallback);

View File

@ -3,7 +3,7 @@ package it.tdlight.common;
import it.tdlight.jni.TdApi;
import java.util.Objects;
public class ReactiveItem {
public final class ReactiveItem {
private final TdApi.Object item;
private final Throwable ex;

View File

@ -14,9 +14,9 @@ public interface ReactiveTelegramClient extends Publisher<ReactiveItem> {
/**
* Sends a request to the TDLib.
*
* @param query Object representing a query to the TDLib.
* @throws NullPointerException if query is null.
* @param query Object representing a query to the TDLib.
* @return a publisher that will emit exactly one item, or an error
* @throws NullPointerException if query is null.
*/
Publisher<TdApi.Object> send(TdApi.Function query);

View File

@ -17,21 +17,24 @@
package it.tdlight.common;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi;
import java.util.Objects;
import java.util.StringJoiner;
/**
* A response to a request, or an incoming update from TDLib.
*/
public class Response {
private long id;
private Object object;
@SuppressWarnings("unused")
public final class Response {
private final long id;
private final TdApi.Object object;
/**
* Creates a response with eventId and object, do not create answers explicitly! you must receive the reply through a client.
* @param id TDLib request identifier, which corresponds to the response or 0 for incoming updates from TDLib.
* @param object TDLib API object representing a response to a TDLib request or an incoming update.
*/
public Response(long id, Object object) {
public Response(long id, TdApi.Object object) {
this.id = id;
this.object = object;
}
@ -48,7 +51,29 @@ public class Response {
* Get TDLib API object.
* @return TDLib API object representing a response to a TDLib request or an incoming update.
*/
public Object getObject() {
public TdApi.Object getObject() {
return this.object;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Response response = (Response) o;
return id == response.id && Objects.equals(object, response.object);
}
@Override
public int hashCode() {
return Objects.hash(id, object);
}
@Override
public String toString() {
return new StringJoiner(", ", Response.class.getSimpleName() + "[", "]").add("object=" + object).toString();
}
}

View File

@ -6,8 +6,9 @@ public interface TelegramClient {
/**
* Initialize the client synchronously.
* @param updatesHandler Handler in which the updates are received
* @param updateExceptionHandler Handler in which the errors from updates are received
*
* @param updatesHandler Handler in which the updates are received
* @param updateExceptionHandler Handler in which the errors from updates are received
* @param defaultExceptionHandler Handler that receives exceptions triggered in a handler
*/
void initialize(UpdatesHandler updatesHandler,
@ -16,8 +17,9 @@ public interface TelegramClient {
/**
* Initialize the client synchronously.
* @param updateHandler Handler in which the updates are received
* @param updateExceptionHandler Handler in which the errors from updates are received
*
* @param updateHandler Handler in which the updates are received
* @param updateExceptionHandler Handler in which the errors from updates are received
* @param defaultExceptionHandler Handler that receives exceptions triggered in a handler
*/
default void initialize(ResultHandler updateHandler,
@ -33,12 +35,10 @@ public interface TelegramClient {
* Sends a request to the TDLib.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, nothing
* will be called.
* @param exceptionHandler Exception handler with onException method which will be called on
* exception thrown from resultHandler. If it is null, then
* defaultExceptionHandler will be called.
* @param resultHandler Result handler with onResult method which will be called with result of the query or with
* TdApi.Error as parameter. If it is null, nothing will be called.
* @param exceptionHandler Exception handler with onException method which will be called on exception thrown from
* resultHandler. If it is null, then defaultExceptionHandler will be called.
* @throws NullPointerException if query is null.
*/
void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler);
@ -47,9 +47,8 @@ public interface TelegramClient {
* Sends a request to the TDLib with an empty ExceptionHandler.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, then
* defaultExceptionHandler will be called.
* @param resultHandler Result handler with onResult method which will be called with result of the query or with
* TdApi.Error as parameter. If it is null, then defaultExceptionHandler will be called.
* @throws NullPointerException if query is null.
*/
default void send(TdApi.Function query, ResultHandler resultHandler) {

View File

@ -11,7 +11,8 @@ public interface UpdatesHandler {
/**
* Callback called on incoming update from TDLib.
*
* @param object Updates of type {@link it.tdlight.jni.TdApi.Update} about new events, or {@link it.tdlight.jni.TdApi.Error}.
* @param object Updates of type {@link it.tdlight.jni.TdApi.Update} about new events, or {@link
* it.tdlight.jni.TdApi.Error}.
*/
void onUpdates(List<Object> object);
}

View File

@ -6,16 +6,16 @@ import it.tdlight.common.TelegramClient;
public abstract class CommonClientManager {
private static InternalClientManager getClientManager(String implementationName) {
// ClientManager is singleton:
// ClientManager is singleton
return InternalClientManager.get(implementationName);
}
protected synchronized static TelegramClient create(String implementationName) {
public synchronized static TelegramClient create(String implementationName) {
InternalClient client = new InternalClient(getClientManager(implementationName));
return create(client);
}
protected synchronized static ReactiveTelegramClient createReactive(String implementationName) {
public synchronized static ReactiveTelegramClient createReactive(String implementationName) {
InternalReactiveClient reactiveClient = new InternalReactiveClient(getClientManager(implementationName));
return createReactive(reactiveClient);
}

View File

@ -3,7 +3,7 @@ package it.tdlight.common.internal;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.ResultHandler;
public class Handler {
public final class Handler {
private final ResultHandler resultHandler;
private final ExceptionHandler exceptionHandler;

View File

@ -17,7 +17,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InternalClient implements ClientEventsHandler, TelegramClient {
public final class InternalClient implements ClientEventsHandler, TelegramClient {
private static final Logger logger = LoggerFactory.getLogger(TelegramClient.class);
private final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();

View File

@ -15,7 +15,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InternalClientManager implements AutoCloseable {
public final class InternalClientManager implements AutoCloseable {
private static final Logger logger = LoggerFactory.getLogger(InternalClientManager.class);
private static final AtomicReference<InternalClientManager> INSTANCE = new AtomicReference<>(null);
@ -45,14 +45,14 @@ public class InternalClientManager implements AutoCloseable {
if (handler != null) {
handler.handleEvents(isClosed, clientEventIds, clientEvents);
} else {
java.util.List<Entry<Long, TdApi.Object>> droppedEvents = getEffectivelyDroppedEvents(clientEventIds, clientEvents);
java.util.List<DroppedEvent> droppedEvents = getEffectivelyDroppedEvents(clientEventIds, clientEvents);
if (!droppedEvents.isEmpty()) {
logger.error("Unknown client id \"{}\"! {} events have been dropped!", clientId, droppedEvents.size());
for (Entry<Long, Object> droppedEvent : droppedEvents) {
for (DroppedEvent droppedEvent : droppedEvents) {
logger.error("The following event, with id \"{}\", has been dropped: {}",
droppedEvent.getKey(),
droppedEvent.getValue());
droppedEvent.id,
droppedEvent.event);
}
}
}
@ -67,8 +67,8 @@ public class InternalClientManager implements AutoCloseable {
/**
* Get only events that have been dropped, ignoring synthetic errors related to the closure of a client
*/
private List<Entry<Long, TdApi.Object>> getEffectivelyDroppedEvents(long[] clientEventIds, TdApi.Object[] clientEvents) {
java.util.List<Entry<Long, TdApi.Object>> droppedEvents = new ArrayList<>(clientEvents.length);
private List<DroppedEvent> getEffectivelyDroppedEvents(long[] clientEventIds, TdApi.Object[] clientEvents) {
java.util.List<DroppedEvent> droppedEvents = new ArrayList<>(clientEvents.length);
for (int i = 0; i < clientEvents.length; i++) {
long id = clientEventIds[i];
TdApi.Object event = clientEvents[i];
@ -80,7 +80,7 @@ public class InternalClientManager implements AutoCloseable {
}
}
if (mustPrintError) {
droppedEvents.add(Map.entry(id, event));
droppedEvents.add(new DroppedEvent(id, event));
}
}
return droppedEvents;
@ -106,4 +106,14 @@ public class InternalClientManager implements AutoCloseable {
public void close() throws InterruptedException {
responseReceiver.close();
}
private static final class DroppedEvent {
private final long id;
private final TdApi.Object event;
private DroppedEvent(long id, Object event) {
this.id = id;
this.event = event;
}
}
}

View File

@ -7,7 +7,6 @@ import it.tdlight.common.ReactiveTelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Error;
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.Object;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -20,9 +19,8 @@ import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class InternalReactiveClient implements ClientEventsHandler, ReactiveTelegramClient {
public final class InternalReactiveClient implements ClientEventsHandler, ReactiveTelegramClient {
private static boolean ENABLE_BACKPRESSURE_QUEUE = false;
private static final Logger logger = LoggerFactory.getLogger(InternalReactiveClient.class);
private final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
private final ConcurrentLinkedQueue<ReactiveItem> backpressureQueue = new ConcurrentLinkedQueue<>();
@ -66,7 +64,7 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
}
@Override
public void handleEvents(boolean isClosed, long[] eventIds, Object[] events) {
public void handleEvents(boolean isClosed, long[] eventIds, TdApi.Object[] events) {
for (int i = 0; i < eventIds.length; i++) {
handleEvent(eventIds[i], events[i]);
}
@ -88,7 +86,7 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
/**
* Handles only a response (not an update!)
*/
private void handleResponse(long eventId, Object event, Handler handler) {
private void handleResponse(long eventId, TdApi.Object event, Handler handler) {
if (handler != null) {
try {
if (eventId == 0) {
@ -108,7 +106,7 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
/**
* Handles a response or an update
*/
private void handleEvent(long eventId, Object event) {
private void handleEvent(long eventId, TdApi.Object event) {
Handler handler = eventId == 0 ? updateHandler : handlers.remove(eventId);
handleResponse(eventId, event, handler);
}
@ -152,14 +150,14 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
@Override
public void cancel() {
if (!isClosed.get()) {
send(new TdApi.Close()).subscribe(new Subscriber<>() {
send(new TdApi.Close()).subscribe(new Subscriber<TdApi.Object>() {
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(1);
}
@Override
public void onNext(Object item) {
public void onNext(TdApi.Object o) {
}
@ -195,14 +193,14 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
CountDownLatch registeredClient = new CountDownLatch(1);
// Send a dummy request because @levlam is too lazy to fix race conditions in a better way
this.send(new TdApi.GetAuthorizationState()).subscribe(new Subscriber<>() {
this.send(new TdApi.GetAuthorizationState()).subscribe(new Subscriber<TdApi.Object>() {
@Override
public void onSubscribe(Subscription subscription) {
subscription.request(1);
}
@Override
public void onNext(Object item) {
public void onNext(TdApi.Object item) {
registeredClient.countDown();
}
@ -278,7 +276,7 @@ public class InternalReactiveClient implements ClientEventsHandler, ReactiveTele
}
@Override
public Object execute(Function query) {
public TdApi.Object execute(Function query) {
if (isClosedAndMaybeThrow(query)) {
return new TdApi.Ok();
}

View File

@ -3,7 +3,7 @@ package it.tdlight.common.internal;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.UpdatesHandler;
public class MultiHandler {
public final class MultiHandler {
private final UpdatesHandler updatesHandler;
private final ExceptionHandler exceptionHandler;

View File

@ -4,7 +4,7 @@ import it.tdlight.tdnative.NativeClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Function;
class NativeClientAccess extends NativeClient {
final class NativeClientAccess extends NativeClient {
public static int create() {
return NativeClientAccess.createNativeClient();

View File

@ -14,7 +14,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
public class ResponseReceiver extends Thread implements AutoCloseable {
public final class ResponseReceiver extends Thread implements AutoCloseable {
private static final boolean USE_OPTIMIZED_DISPATCHER = Boolean.parseBoolean(System.getProperty(
"tdlight.dispatcher.use_optimized_dispatcher",

View File

@ -18,14 +18,8 @@
package it.tdlight.common.utils;
/**
* Enumeration with all architectures recognized by this library.
* Architectures recognized by this library.
*/
public enum Arch {
UNKNOWN,
AMD64,
I386,
ARMHF,
AARCH64,
PPC64LE,
S390X
UNKNOWN, AMD64, I386, ARMHF, AARCH64, PPC64LE, S390X
}

View File

@ -20,11 +20,11 @@ package it.tdlight.common.utils;
/**
* An exception that is thrown when the LoadLibrary class fails to load the library.
*/
public class CantLoadLibrary extends Exception {
public final class CantLoadLibrary extends Exception {
/**
* Creates a new CantLoadLibrary exception.
*/
CantLoadLibrary() {
super("FATAL: Init failled when load tdlib library, execution can't continue");
super("FATAL: Init failed when loading TDLib native libraries, execution can't continue");
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright (c) 2018. Ernesto Castellotti <erny.castell@gmail.com>
* This file is part of JTdlib.
*
* JTdlib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* JTdlib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JTdlib. If not, see <http://www.gnu.org/licenses/>.
*/
package it.tdlight.common.utils;
/**
* Interface of callback for receive notification of closing Tdlib.
*/
public interface CloseCallback {
/**
* This method is called when tdlib is closing
*/
void onClosed();
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (c) 2018. Ernesto Castellotti <erny.castell@gmail.com>
* This file is part of JTdlib.
*
* JTdlib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* JTdlib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JTdlib. If not, see <http://www.gnu.org/licenses/>.
*/
package it.tdlight.common.utils;
import it.tdlight.common.Response;
/**
* Interface of callback for receive incoming error response.
*/
public interface ErrorCallback {
/**
* This method is called when the library receives error responses
* @param error The incoming error response.
*/
void onError(Response error);
}

View File

@ -2,7 +2,7 @@ package it.tdlight.common.utils;
import it.unimi.dsi.fastutil.Swapper;
public class IntSwapper implements Swapper {
public final class IntSwapper implements Swapper {
private final int[] array;
int tmp;

View File

@ -23,18 +23,19 @@ import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.reflect.InvocationTargetException;
import it.tdlight.tdnative.ObjectsUtils;
/**
* The class to load the libraries needed to run Tdlib
*/
public class LoadLibrary {
public final class LoadLibrary {
private static final ConcurrentHashMap<String, Boolean> libraryLoaded = new ConcurrentHashMap<>();
private static final Path librariesPath = Paths.get(".cache");
private static final String libsVersion = LibraryVersion.IMPLEMENTATION_NAME
+ "-" + LibraryVersion.VERSION + "-" + LibraryVersion.NATIVES_VERSION;
private static final String libsVersion =
LibraryVersion.IMPLEMENTATION_NAME + "-" + LibraryVersion.VERSION + "-" + LibraryVersion.NATIVES_VERSION;
static {
if (Files.notExists(librariesPath)) {
@ -72,11 +73,13 @@ public class LoadLibrary {
Os os = getOs();
if (arch == Arch.UNKNOWN) {
throw (CantLoadLibrary) new CantLoadLibrary().initCause(new IllegalStateException("Arch: \"" + System.getProperty("os.arch") + "\" is unknown"));
throw (CantLoadLibrary) new CantLoadLibrary().initCause(new IllegalStateException(
"Arch: \"" + System.getProperty("os.arch") + "\" is unknown"));
}
if (os == Os.UNKNOWN) {
throw (CantLoadLibrary) new CantLoadLibrary().initCause(new IllegalStateException("Os: \"" + System.getProperty("os.name") + "\" is unknown"));
throw (CantLoadLibrary) new CantLoadLibrary().initCause(new IllegalStateException(
"Os: \"" + System.getProperty("os.name") + "\" is unknown"));
}
try {
@ -178,7 +181,9 @@ public class LoadLibrary {
}
InputStream libInputStream;
try {
libInputStream = ObjectsUtils.requireNonNull((InputStream) classForResource.getDeclaredMethod("getLibraryAsStream").invoke(InputStream.class));
libInputStream = Objects.requireNonNull((InputStream) classForResource
.getDeclaredMethod("getLibraryAsStream")
.invoke(InputStream.class));
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException | NullPointerException e) {
throw new IOException("Native libraries for platform " + os + "-" + arch + " not found!", e);
}
@ -222,10 +227,14 @@ public class LoadLibrary {
case "ppc64":
case "ppc64le":
case "ppc64el":
if (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) // Java always returns ppc64 for all 64-bit powerpc but
if (ByteOrder
.nativeOrder()
.equals(ByteOrder.LITTLE_ENDIAN)) // Java always returns ppc64 for all 64-bit powerpc but
{
return Arch.PPC64LE; // powerpc64le (our target) is very different, it uses this condition to accurately identify the architecture
else
} else {
return Arch.UNKNOWN;
}
default:
return Arch.UNKNOWN;
}
@ -233,14 +242,18 @@ public class LoadLibrary {
public static Os getOs() {
String os = System.getProperty("os.name").toLowerCase().trim();
if (os.contains("linux"))
if (os.contains("linux")) {
return Os.LINUX;
if (os.contains("windows"))
}
if (os.contains("windows")) {
return Os.WINDOWS;
if (os.contains("mac"))
}
if (os.contains("mac")) {
return Os.OSX;
if (os.contains("darwin"))
}
if (os.contains("darwin")) {
return Os.OSX;
}
return Os.UNKNOWN;
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (c) 2018. Ernesto Castellotti <erny.castell@gmail.com>
* This file is part of JTdlib.
*
* JTdlib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* JTdlib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JTdlib. If not, see <http://www.gnu.org/licenses/>.
*/
package it.tdlight.common.utils;
import it.tdlight.common.Response;
/**
* Interface of callback for receive incoming error response.
*/
public interface NativeErrorCallback {
/**
* This method is called when the library receives error responses
* @param error The incoming error response.
*/
void onNativeError(Response error);
}

View File

@ -21,8 +21,5 @@ package it.tdlight.common.utils;
* Enumeration with all operating systems recognized by this library.
*/
public enum Os {
LINUX,
WINDOWS,
OSX,
UNKNOWN
LINUX, WINDOWS, OSX, UNKNOWN
}

View File

@ -1,31 +0,0 @@
/*
* Copyright (c) 2018. Ernesto Castellotti <erny.castell@gmail.com>
* This file is part of JTdlib.
*
* JTdlib is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* JTdlib is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JTdlib. If not, see <http://www.gnu.org/licenses/>.
*/
package it.tdlight.common.utils;
import it.tdlight.common.Response;
/**
* Interface of callback for receive incoming update or request response.
*/
public interface ReceiveCallback {
/**
* This method is called when the library receives update or request response.
* @param response The incoming update or request response.
*/
void onResult(Response response);
}

View File

@ -2,20 +2,16 @@ package it.tdlight.common.utils;
import java.util.Scanner;
public class ScannerUtils {
public final class ScannerUtils {
private static final Scanner scanner;
private static final Object lock = new Object();
static {
synchronized (lock) {
scanner = new Scanner(System.in);
}
scanner = new Scanner(System.in);
}
public static String askParameter(String displayName, String question) {
synchronized (lock) {
System.out.print("[" + displayName + "] " + question + ": ");
return scanner.nextLine();
}
System.out.print("[" + displayName + "] " + question + ": ");
return scanner.nextLine();
}
}

View File

@ -5,31 +5,10 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class SpinWaitSupport {
private static final MethodHandle onSpinWaitHandle;
static {
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle handle;
try {
handle = lookup.findStatic(java.lang.Thread.class, "onSpinWait", MethodType.methodType(void.class));
} catch (Exception e) {
handle = null;
}
onSpinWaitHandle = handle;
}
private SpinWaitSupport() {
}
public static void onSpinWait() {
if (onSpinWaitHandle != null) {
try {
onSpinWaitHandle.invokeExact();
} catch (Throwable throwable) {
// This can't happen
}
}
}
}

View File

@ -4,11 +4,12 @@ import it.tdlight.jni.TdApi;
public class NativeClient {
protected static native int createNativeClient();
protected static native int createNativeClient();
protected static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
protected static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
protected static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
protected static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events,
double timeout);
protected static native TdApi.Object nativeClientExecute(TdApi.Function function);
protected static native TdApi.Object nativeClientExecute(TdApi.Function function);
}

View File

@ -1,71 +1,75 @@
package it.tdlight.tdnative;
import it.tdlight.jni.TdApi;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Class used for managing internal TDLib logging.
* Use TdApi.*Log* methods instead.
* Class used for managing internal TDLib logging. Use TdApi.*Log* methods instead.
*/
public class NativeLog {
private static final Consumer<String> defaultFatalErrorCallbackPtr = System.err::println;
private static Consumer<String> fatalErrorCallback = defaultFatalErrorCallbackPtr;
private static final Consumer<String> defaultFatalErrorCallbackPtr = System.err::println;
private static Consumer<String> fatalErrorCallback = defaultFatalErrorCallbackPtr;
/**
* Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err.
* Use this method to write the log to a file instead.
*
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
* @param filePath Path to a file for writing TDLib internal log. Use an empty path to
* switch back to logging to the System.err.
* @return whether opening the log file succeeded.
*/
@Deprecated
public static native boolean setFilePath(String filePath);
/**
* Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err. Use this method to
* write the log to a file instead.
*
* @param filePath Path to a file for writing TDLib internal log. Use an empty path to switch back to logging to the
* System.err.
* @return whether opening the log file succeeded.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
*/
@Deprecated
public static native boolean setFilePath(String filePath);
/**
* Changes the maximum size of TDLib log file.
*
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
* @param maxFileSize The maximum size of the file to where the internal TDLib log is written
* before the file will be auto-rotated. Must be positive. Defaults to 10 MB.
*/
@Deprecated
public static native void setMaxFileSize(long maxFileSize);
/**
* Changes the maximum size of TDLib log file.
*
* @param maxFileSize The maximum size of the file to where the internal TDLib log is written before the file will be
* auto-rotated. Must be positive. Defaults to 10 MB.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future.
*/
@Deprecated
public static native void setMaxFileSize(long maxFileSize);
/**
* Changes TDLib log verbosity.
*
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogVerbosityLevel}, to be removed in the future.
* @param verbosityLevel New value of log verbosity level. Must be non-negative.
* Value 0 corresponds to fatal errors,
* value 1 corresponds to java.util.logging.Level.SEVERE,
* value 2 corresponds to java.util.logging.Level.WARNING,
* value 3 corresponds to java.util.logging.Level.INFO,
* value 4 corresponds to java.util.logging.Level.FINE,
* value 5 corresponds to java.util.logging.Level.FINER,
* value greater than 5 can be used to enable even more logging.
* Default value of the log verbosity level is 5.
*/
@Deprecated
public static native void setVerbosityLevel(int verbosityLevel);
/**
* Changes TDLib log verbosity.
*
* @param verbosityLevel New value of log verbosity level. Must be non-negative. Value 0 corresponds to fatal errors,
* value 1 corresponds to java.util.logging.Level.SEVERE, value 2 corresponds to
* java.util.logging.Level.WARNING, value 3 corresponds to java.util.logging.Level.INFO, value 4
* corresponds to java.util.logging.Level.FINE, value 5 corresponds to
* java.util.logging.Level.FINER, value greater than 5 can be used to enable even more logging.
* Default value of the log verbosity level is 5.
* @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogVerbosityLevel}, to be removed in the future.
*/
@Deprecated
public static native void setVerbosityLevel(int verbosityLevel);
/**
* This function is called from the JNI when a fatal error happens to provide a better error message.
* The function does not return.
*
* @param errorMessage Error message.
*/
private static synchronized void onFatalError(String errorMessage) {
new Thread(() -> NativeLog.fatalErrorCallback.accept(errorMessage)).start();
}
/**
* This function is called from the JNI when a fatal error happens to provide a better error message. The function
* does not return.
*
* @param errorMessage Error message.
*/
private static synchronized void onFatalError(String errorMessage) {
new Thread(() -> NativeLog.fatalErrorCallback.accept(errorMessage)).start();
}
/**
* Sets the callback that will be called when a fatal error happens. None of the TDLib methods can be called from the callback. The TDLib will crash as soon as callback returns. By default the callback set to print in stderr.
* @param fatalErrorCallback Callback that will be called when a fatal error happens. Pass null to restore default callback.
*/
public static synchronized void setFatalErrorCallback(Consumer<String> fatalErrorCallback) {
NativeLog.fatalErrorCallback = ObjectsUtils.requireNonNullElse(fatalErrorCallback, defaultFatalErrorCallbackPtr);
}
/**
* Sets the callback that will be called when a fatal error happens. None of the TDLib methods can be called from the
* callback. The TDLib will crash as soon as callback returns. By default the callback set to print in stderr.
*
* @param fatalErrorCallback Callback that will be called when a fatal error happens. Pass null to restore default
* callback.
*/
public static synchronized void setFatalErrorCallback(Consumer<String> fatalErrorCallback) {
if (fatalErrorCallback == null) {
NativeLog.fatalErrorCallback = defaultFatalErrorCallbackPtr;
} else {
NativeLog.fatalErrorCallback = fatalErrorCallback;
}
}
}

View File

@ -1,67 +0,0 @@
package it.tdlight.tdnative;
public class ObjectsUtils {
/**
* Returns the first argument if it is non-{@code null} and
* otherwise returns the non-{@code null} second argument.
*
* @param obj an object
* @param defaultObj a non-{@code null} object to return if the first argument
* is {@code null}
* @param <T> the type of the reference
* @return the first argument if it is non-{@code null} and
* otherwise the second argument if it is non-{@code null}
* @throws NullPointerException if both {@code obj} is null and
* {@code defaultObj} is {@code null}
* @since 9
*/
public static <T> T requireNonNullElse(T obj, T defaultObj) {
return (obj != null) ? obj : requireNonNull(defaultObj, "defaultObj");
}
/**
* Checks that the specified object reference is not {@code null}. This
* method is designed primarily for doing parameter validation in methods
* and constructors, as demonstrated below:
* <blockquote><pre>
* public Foo(Bar bar) {
* this.bar = Objects.requireNonNull(bar);
* }
* </pre></blockquote>
*
* @param obj the object reference to check for nullity
* @param <T> the type of the reference
* @return {@code obj} if not {@code null}
* @throws NullPointerException if {@code obj} is {@code null}
*/
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
/**
* Checks that the specified object reference is not {@code null} and
* throws a customized {@link NullPointerException} if it is. This method
* is designed primarily for doing parameter validation in methods and
* constructors with multiple parameters, as demonstrated below:
* <blockquote><pre>
* public Foo(Bar bar, Baz baz) {
* this.bar = Objects.requireNonNull(bar, "bar must not be null");
* this.baz = Objects.requireNonNull(baz, "baz must not be null");
* }
* </pre></blockquote>
*
* @param obj the object reference to check for nullity
* @param message detail message to be used in the event that a {@code
* NullPointerException} is thrown
* @param <T> the type of the reference
* @return {@code obj} if not {@code null}
* @throws NullPointerException if {@code obj} is {@code null}
*/
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
}

View File

@ -9,4 +9,4 @@ module tdlight.java {
exports it.tdlight.common;
exports it.tdlight.common.utils;
exports it.tdlight.common.internal;
}
}

View File

@ -0,0 +1,11 @@
package it.tdlight.common.utils;
public class SpinWaitSupport {
private SpinWaitSupport() {
}
public static void onSpinWait() {
java.lang.Thread.onSpinWait();
}
}

View File

@ -8,8 +8,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>1.0.0-SNAPSHOT</revision>
<api-version>3.3.127</api-version>
<natives-version>3.3.129</natives-version>
<api-version>3.3.147</api-version>
<natives-version>3.3.149</natives-version>
</properties>
<repositories>
<repository>
@ -65,6 +65,10 @@
<artifactId>reactive-streams</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>net.harawata</groupId>
<artifactId>appdirs</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
@ -73,8 +77,13 @@
<artifactId>fastutil</artifactId>
<version>8.5.6</version>
</dependency>
<dependency>
<groupId>net.harawata</groupId>
<artifactId>appdirs</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
</dependencyManagement>
</dependencyManagement>
<profiles>
<profile>
<id>releaseDir</id>

View File

@ -8,8 +8,8 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<revision>1.0.0-SNAPSHOT</revision>
<api-version>3.3.127</api-version>
<natives-version>3.3.129</natives-version>
<api-version>3.3.147</api-version>
<natives-version>3.3.149</natives-version>
</properties>
<repositories>
<repository>
@ -65,6 +65,10 @@
<artifactId>reactive-streams</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>net.harawata</groupId>
<artifactId>appdirs</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
@ -73,6 +77,11 @@
<artifactId>fastutil</artifactId>
<version>8.5.6</version>
</dependency>
<dependency>
<groupId>net.harawata</groupId>
<artifactId>appdirs</artifactId>
<version>1.2.1</version>
</dependency>
</dependencies>
</dependencyManagement>
<profiles>
@ -90,7 +99,7 @@
</profile>
</profiles>
<build>
<sourceDirectory>../src/main/java</sourceDirectory>
<sourceDirectory>${project.basedir}/../src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
@ -116,9 +125,14 @@
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<source>8</source>
<source>17</source>
<additionalOptions>-html5</additionalOptions>
</configuration>
<level>public</level>
<verbose>false</verbose>
<quiet>true</quiet>
<additionalOptions>-Xdoclint:none</additionalOptions>
<additionalJOption>-Xdoclint:none</additionalJOption>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
@ -128,81 +142,92 @@
</execution>
</executions>
</plugin>
<!-- ensure the project is compiling with JDK 9+ -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-jdk9</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[1.9,)</version>
<message>JDK 9+ is required for compilation</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- compile sources -->
<plugin>
<!-- ensure the project is compiling with JDK 9+ -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<encoding>UTF-8</encoding>
<source>11</source>
<target>11</target>
<useIncrementalCompilation>false</useIncrementalCompilation>
<excludes>
<exclude>it/tdlight/tdlib/ClientManager.java</exclude>
</excludes>
</configuration>
<executions>
<!-- disable default phase due to fixed id and position in lifecycle -->
<execution>
<id>default-compile</id>
<phase>none</phase>
<!-- specify source/target for IDE integration -->
<configuration>
<release>9</release>
</configuration>
</execution>
<!-- compile sources with Java 9 to generate and validate module-info.java -->
<execution>
<id>java-9-module-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>9</release>
</configuration>
</execution>
<!-- recompile sources as Java 8 to overwrite Java 9 class files, except module-info.java -->
<execution>
<id>java-8-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<!-- specify JDK 9+ release flag to ensure no classes/methods later than Java 8 are used accidentally -->
<source>8</source>
<target>8</target>
<!-- exclude module-info.java from the compilation, as it is unsupported by Java 8 -->
<excludes>
<exclude>it/tdlight/tdlib/ClientManager.java</exclude>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-jdk9</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[1.9,)</version>
<message>JDK 9+ is required for compilation</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- compile sources -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<encoding>UTF-8</encoding>
<excludes>
<exclude>it/tdlight/tdlib/ClientManager.java</exclude>
</excludes>
</configuration>
<executions>
<!-- disable default phase due to fixed id and position in lifecycle -->
<execution>
<id>default-compile</id>
<phase>none</phase>
<!-- specify source/target for IDE integration -->
<configuration>
<release>9</release>
</configuration>
</execution>
<!-- compile sources with Java 111 -->
<execution>
<id>java-11-module-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>11</release>
<compileSourceRoots>
<compileSourceRoot>${project.basedir}/../src/main/java11</compileSourceRoot>
</compileSourceRoots>
<multiReleaseOutput>true</multiReleaseOutput>
</configuration>
</execution>
<!-- compile sources with Java 9 to generate and validate module-info.java -->
<execution>
<id>java-9-module-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<release>9</release>
</configuration>
</execution>
<!-- recompile sources as Java 8 to overwrite Java 9 class files, except module-info.java -->
<execution>
<id>java-8-compile</id>
<goals>
<goal>compile</goal>
</goals>
<configuration>
<!-- specify JDK 9+ release flag to ensure no classes/methods later than Java 8 are used accidentally -->
<release>8</release>
<!-- exclude module-info.java from the compilation, as it is unsupported by Java 8 -->
<excludes>
<exclude>module-info.java</exclude>
</excludes>
</configuration>
</execution>
</executions>
</plugin>
<!--
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>flatten-maven-plugin</artifactId>
@ -227,10 +252,17 @@
</goals>
</execution>
</executions>
</plugin>
</plugin>-->
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifestEntries>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>