diff --git a/README.md b/README.md index 51f6272..b268e02 100644 --- a/README.md +++ b/README.md @@ -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 - - - mchv - MCHV Apache Maven Packages - https://mvn.mchv.eu/repository/mchv/ - - + + + + mchv + MCHV Apache Maven Packages + https://mvn.mchv.eu/repository/mchv/ + + + + + + it.tdlight + tdlight-java + VERSION + + + it.tdlight + tdlight-natives-linux-amd64 + NATIVES_VERSION + + + + ``` -Dependency: -```xml - - - it.tdlight - tdlight-java - REPLACE_WITH_LATEST_VERSION - - - it.tdlight - tdlight-natives-linux-amd64 - REPLACE_WITH_LATEST_NATIVES_VERSION - - - -``` -#### 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 - - - mchv - MCHV Apache Maven Packages - https://mvn.mchv.eu/repository/mchv/ - - -``` -Dependency: -```xml - - - it.tdlight - tdlib-java - REPLACE_WITH_LATEST_VERSION - - - it.tdlight - tdlib-natives-linux-amd64 - REPLACE_WITH_LATEST_NATIVES_VERSION - - - -``` -#### 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 diff --git a/example/pom.xml b/example/pom.xml index 2799578..a7c3b17 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -24,7 +24,8 @@ it.tdlight tdlight-java - [1.7.6.1,) + + 1.0.0-SNAPSHOT it.tdlight diff --git a/example/src/main/java/it.tdlight.example/AdvancedExample.java b/example/src/main/java/it.tdlight.example/AdvancedExample.java new file mode 100644 index 0000000..0efb164 --- /dev/null +++ b/example/src/main/java/it.tdlight.example/AdvancedExample.java @@ -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(); + } +} \ No newline at end of file diff --git a/example/src/main/java/it.tdlight.example/Example.java b/example/src/main/java/it.tdlight.example/Example.java index 5fae159..b563960 100644 --- a/example/src/main/java/it.tdlight.example/Example.java +++ b/example/src/main/java/it.tdlight.example/Example.java @@ -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 users = new ConcurrentHashMap(); - private static final ConcurrentMap basicGroups = new ConcurrentHashMap(); - private static final ConcurrentMap supergroups = new ConcurrentHashMap(); - private static final ConcurrentMap secretChats = new ConcurrentHashMap(); - - private static final ConcurrentMap chats = new ConcurrentHashMap(); - private static final NavigableSet mainChatList = new TreeSet(); - private static boolean haveFullMainChatList = false; - - private static final ConcurrentMap usersFullInfo = new ConcurrentHashMap(); - private static final ConcurrentMap basicGroupsFullInfo = new ConcurrentHashMap(); - private static final ConcurrentMap supergroupsFullInfo = new ConcurrentHashMap(); - - private static final String newLine = System.getProperty("line.separator"); - private static final String commandsLine = "Enter command (gcs - GetChats, gc - GetChat, me - GetMe, sm - 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 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 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 { - 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(); } } } diff --git a/src/main/java/it/tdlight/client/APIToken.java b/src/main/java/it/tdlight/client/APIToken.java new file mode 100644 index 0000000..6045401 --- /dev/null +++ b/src/main/java/it/tdlight/client/APIToken.java @@ -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: https://my.telegram.org/auth?to=apps + */ + 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(); + } +} diff --git a/src/main/java/it/tdlight/client/Authenticable.java b/src/main/java/it/tdlight/client/Authenticable.java new file mode 100644 index 0000000..8e792d9 --- /dev/null +++ b/src/main/java/it/tdlight/client/Authenticable.java @@ -0,0 +1,6 @@ +package it.tdlight.client; + +public interface Authenticable { + + AuthenticationData getAuthenticationData(); +} diff --git a/src/main/java/it/tdlight/client/AuthenticationData.java b/src/main/java/it/tdlight/client/AuthenticationData.java new file mode 100644 index 0000000..dde3037 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthenticationData.java @@ -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); + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateReadyGetMe.java b/src/main/java/it/tdlight/client/AuthorizationStateReadyGetMe.java new file mode 100644 index 0000000..f933654 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateReadyGetMe.java @@ -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 { + + private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateReadyGetMe.class); + + private final TelegramClient client; + private final AtomicReference me; + + public AuthorizationStateReadyGetMe(TelegramClient client, AtomicReference 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()")); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitAuthenticationDataHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitAuthenticationDataHandler.java new file mode 100644 index 0000000..4399274 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitAuthenticationDataHandler.java @@ -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 { + + 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); + }); + } + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitCodeHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitCodeHandler.java new file mode 100644 index 0000000..755af52 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitCodeHandler.java @@ -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 { + + 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); + }); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitEncryptionKeyHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitEncryptionKeyHandler.java new file mode 100644 index 0000000..86b0efb --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitEncryptionKeyHandler.java @@ -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 { + + 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); + }); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitForExit.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitForExit.java new file mode 100644 index 0000000..22a540d --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitForExit.java @@ -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 { + + 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(); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitOtherDeviceConfirmationHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitOtherDeviceConfirmationHandler.java new file mode 100644 index 0000000..72c1c0b --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitOtherDeviceConfirmationHandler.java @@ -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 { + + 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); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitPasswordHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitPasswordHandler.java new file mode 100644 index 0000000..8001af7 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitPasswordHandler.java @@ -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 { + + 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); + }); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitRegistrationHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitRegistrationHandler.java new file mode 100644 index 0000000..c4e7636 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitRegistrationHandler.java @@ -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 { + + 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); + }); + } + } +} diff --git a/src/main/java/it/tdlight/client/AuthorizationStateWaitTdlibParametersHandler.java b/src/main/java/it/tdlight/client/AuthorizationStateWaitTdlibParametersHandler.java new file mode 100644 index 0000000..4174aa1 --- /dev/null +++ b/src/main/java/it/tdlight/client/AuthorizationStateWaitTdlibParametersHandler.java @@ -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 { + + 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); + }); + } + } +} diff --git a/src/main/java/it/tdlight/client/ClientInteraction.java b/src/main/java/it/tdlight/client/ClientInteraction.java new file mode 100644 index 0000000..8244569 --- /dev/null +++ b/src/main/java/it/tdlight/client/ClientInteraction.java @@ -0,0 +1,6 @@ +package it.tdlight.client; + +public interface ClientInteraction { + + String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo); +} diff --git a/src/main/java/it/tdlight/client/CommandHandler.java b/src/main/java/it/tdlight/client/CommandHandler.java new file mode 100644 index 0000000..bea6140 --- /dev/null +++ b/src/main/java/it/tdlight/client/CommandHandler.java @@ -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); +} diff --git a/src/main/java/it/tdlight/client/CommandsHandler.java b/src/main/java/it/tdlight/client/CommandsHandler.java new file mode 100644 index 0000000..f54960b --- /dev/null +++ b/src/main/java/it/tdlight/client/CommandsHandler.java @@ -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 { + + private static final Logger logger = LoggerFactory.getLogger(CommandsHandler.class); + + private final TelegramClient client; + private final Map> commandHandlers; + private final AtomicReference me; + + public CommandsHandler(TelegramClient client, + Map> commandHandlers, + AtomicReference 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 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 getMyUsername() { + User user = this.me.get(); + if (user == null || user.username == null || user.username.isEmpty()) { + return Optional.empty(); + } else { + return Optional.of(user.username); + } + } +} diff --git a/src/main/java/it/tdlight/client/EmptyParameterInfo.java b/src/main/java/it/tdlight/client/EmptyParameterInfo.java new file mode 100644 index 0000000..effc222 --- /dev/null +++ b/src/main/java/it/tdlight/client/EmptyParameterInfo.java @@ -0,0 +1,3 @@ +package it.tdlight.client; + +public final class EmptyParameterInfo implements ParameterInfo {} diff --git a/src/main/java/it/tdlight/client/GenericResultHandler.java b/src/main/java/it/tdlight/client/GenericResultHandler.java new file mode 100644 index 0000000..123aefc --- /dev/null +++ b/src/main/java/it/tdlight/client/GenericResultHandler.java @@ -0,0 +1,23 @@ +package it.tdlight.client; + +import it.tdlight.jni.TdApi; + +/** + * Interface for incoming responses from TDLib. + */ +@FunctionalInterface +public interface GenericResultHandler { + + /** + * Callback called when TDLib responds. + */ + void onResult(Result result); + + default void onResult(TdApi.Object result) { + onResult(Result.of(result)); + } + + default void onErrorResult(Throwable exception) { + onResult(Result.ofError(exception)); + } +} \ No newline at end of file diff --git a/src/main/java/it/tdlight/client/GenericUpdateHandler.java b/src/main/java/it/tdlight/client/GenericUpdateHandler.java new file mode 100644 index 0000000..79506bf --- /dev/null +++ b/src/main/java/it/tdlight/client/GenericUpdateHandler.java @@ -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 { + + /** + * Callback called on incoming update from TDLib. + * + * @param update Update of type TdApi.Update about new events. + */ + void onUpdate(T update); +} \ No newline at end of file diff --git a/src/main/java/it/tdlight/client/InputParameter.java b/src/main/java/it/tdlight/client/InputParameter.java new file mode 100644 index 0000000..a5690d7 --- /dev/null +++ b/src/main/java/it/tdlight/client/InputParameter.java @@ -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 +} diff --git a/src/main/java/it/tdlight/client/ParameterInfo.java b/src/main/java/it/tdlight/client/ParameterInfo.java new file mode 100644 index 0000000..a077547 --- /dev/null +++ b/src/main/java/it/tdlight/client/ParameterInfo.java @@ -0,0 +1,3 @@ +package it.tdlight.client; + +public interface ParameterInfo {} diff --git a/src/main/java/it/tdlight/client/ParameterInfoCode.java b/src/main/java/it/tdlight/client/ParameterInfoCode.java new file mode 100644 index 0000000..60b8970 --- /dev/null +++ b/src/main/java/it/tdlight/client/ParameterInfoCode.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/client/ParameterInfoNotifyLink.java b/src/main/java/it/tdlight/client/ParameterInfoNotifyLink.java new file mode 100644 index 0000000..a0f4554 --- /dev/null +++ b/src/main/java/it/tdlight/client/ParameterInfoNotifyLink.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/client/ParameterInfoPasswordHint.java b/src/main/java/it/tdlight/client/ParameterInfoPasswordHint.java new file mode 100644 index 0000000..213fa8e --- /dev/null +++ b/src/main/java/it/tdlight/client/ParameterInfoPasswordHint.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/client/ParameterInfoTermsOfService.java b/src/main/java/it/tdlight/client/ParameterInfoTermsOfService.java new file mode 100644 index 0000000..bcea013 --- /dev/null +++ b/src/main/java/it/tdlight/client/ParameterInfoTermsOfService.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/client/Result.java b/src/main/java/it/tdlight/client/Result.java new file mode 100644 index 0000000..325f977 --- /dev/null +++ b/src/main/java/it/tdlight/client/Result.java @@ -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 { + + 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 Result 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 Result 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 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(); + } +} diff --git a/src/main/java/it/tdlight/client/ScannerClientInteraction.java b/src/main/java/it/tdlight/client/ScannerClientInteraction.java new file mode 100644 index 0000000..a4e9aec --- /dev/null +++ b/src/main/java/it/tdlight/client/ScannerClientInteraction.java @@ -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; + } + } +} diff --git a/src/main/java/it/tdlight/client/SimpleTelegramClient.java b/src/main/java/it/tdlight/client/SimpleTelegramClient.java new file mode 100644 index 0000000..8eb98f7 --- /dev/null +++ b/src/main/java/it/tdlight/client/SimpleTelegramClient.java @@ -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> commandHandlers = new ConcurrentHashMap<>(); + private final Set updateHandlers = new ConcurrentHashMap() + .keySet(new Object()); + private final Set updateExceptionHandlers = new ConcurrentHashMap() + .keySet(new Object()); + private final Set defaultExceptionHandlers = new ConcurrentHashMap() + .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 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 void addCommandHandler(String commandName, CommandHandler handler) { + Set handlers = this.commandHandlers + .computeIfAbsent(commandName, k -> new ConcurrentHashMap().keySet(new Object())); + handlers.add(handler); + } + + @SuppressWarnings("unchecked") + public void addUpdateHandler(Class updateType, GenericUpdateHandler handler) { + int updateConstructor = ConstructorDetector.getConstructor(updateType); + this.updateHandlers.add(update -> { + if (update.getConstructor() == updateConstructor) { + handler.onUpdate((T) update); + } + }); + } + + public void addUpdatesHandler(GenericUpdateHandler 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 void send(TdApi.Function function, GenericResultHandler resultHandler) { + client.send(function, resultHandler::onResult, resultHandler::onErrorResult); + } + + /** + * Execute a synchronous function. + * Please note that only some functions can be executed using this method. + * If you want to execute a function please use {@link #send(Function, GenericResultHandler)}! + */ + public Result 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); + } + } +} diff --git a/src/main/java/it/tdlight/client/TDLibSettings.java b/src/main/java/it/tdlight/client/TDLibSettings.java new file mode 100644 index 0000000..f3f1736 --- /dev/null +++ b/src/main/java/it/tdlight/client/TDLibSettings.java @@ -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(); + } +} diff --git a/src/main/java/it/tdlight/client/TelegramError.java b/src/main/java/it/tdlight/client/TelegramError.java new file mode 100644 index 0000000..af32313 --- /dev/null +++ b/src/main/java/it/tdlight/client/TelegramError.java @@ -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); + } +} diff --git a/src/main/java/it/tdlight/common/ConstructorDetector.java b/src/main/java/it/tdlight/common/ConstructorDetector.java index bc47378..ca0904e 100644 --- a/src/main/java/it/tdlight/common/ConstructorDetector.java +++ b/src/main/java/it/tdlight/common/ConstructorDetector.java @@ -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 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 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 constructorHashMap; + private static ConcurrentHashMap 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 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); + } } } diff --git a/src/main/java/it/tdlight/common/EventsHandler.java b/src/main/java/it/tdlight/common/EventsHandler.java index 0c87f06..a9c8236 100644 --- a/src/main/java/it/tdlight/common/EventsHandler.java +++ b/src/main/java/it/tdlight/common/EventsHandler.java @@ -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); } diff --git a/src/main/java/it/tdlight/common/ExceptionHandler.java b/src/main/java/it/tdlight/common/ExceptionHandler.java index 10eaaed..bab189a 100644 --- a/src/main/java/it/tdlight/common/ExceptionHandler.java +++ b/src/main/java/it/tdlight/common/ExceptionHandler.java @@ -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 { diff --git a/src/main/java/it/tdlight/common/Init.java b/src/main/java/it/tdlight/common/Init.java index 59d8d3f..a13ea0a 100644 --- a/src/main/java/it/tdlight/common/Init.java +++ b/src/main/java/it/tdlight/common/Init.java @@ -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; + } + } } diff --git a/src/main/java/it/tdlight/common/Log.java b/src/main/java/it/tdlight/common/Log.java index 8415dcd..cd07fa4 100644 --- a/src/main/java/it/tdlight/common/Log.java +++ b/src/main/java/it/tdlight/common/Log.java @@ -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 fatalErrorCallback) { NativeLog.setFatalErrorCallback(fatalErrorCallback); diff --git a/src/main/java/it/tdlight/common/ReactiveItem.java b/src/main/java/it/tdlight/common/ReactiveItem.java index 53a6a60..09dd3bd 100644 --- a/src/main/java/it/tdlight/common/ReactiveItem.java +++ b/src/main/java/it/tdlight/common/ReactiveItem.java @@ -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; diff --git a/src/main/java/it/tdlight/common/ReactiveTelegramClient.java b/src/main/java/it/tdlight/common/ReactiveTelegramClient.java index 18e34c4..8ff1e65 100644 --- a/src/main/java/it/tdlight/common/ReactiveTelegramClient.java +++ b/src/main/java/it/tdlight/common/ReactiveTelegramClient.java @@ -14,9 +14,9 @@ public interface ReactiveTelegramClient extends Publisher { /** * 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 send(TdApi.Function query); diff --git a/src/main/java/it/tdlight/common/Response.java b/src/main/java/it/tdlight/common/Response.java index 14bddfb..33c7436 100644 --- a/src/main/java/it/tdlight/common/Response.java +++ b/src/main/java/it/tdlight/common/Response.java @@ -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(); + } } diff --git a/src/main/java/it/tdlight/common/TelegramClient.java b/src/main/java/it/tdlight/common/TelegramClient.java index 355e999..a727f70 100644 --- a/src/main/java/it/tdlight/common/TelegramClient.java +++ b/src/main/java/it/tdlight/common/TelegramClient.java @@ -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) { diff --git a/src/main/java/it/tdlight/common/UpdatesHandler.java b/src/main/java/it/tdlight/common/UpdatesHandler.java index 8ca463d..7d47ad5 100644 --- a/src/main/java/it/tdlight/common/UpdatesHandler.java +++ b/src/main/java/it/tdlight/common/UpdatesHandler.java @@ -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); } \ No newline at end of file diff --git a/src/main/java/it/tdlight/common/internal/CommonClientManager.java b/src/main/java/it/tdlight/common/internal/CommonClientManager.java index 4b933ea..0220bbf 100644 --- a/src/main/java/it/tdlight/common/internal/CommonClientManager.java +++ b/src/main/java/it/tdlight/common/internal/CommonClientManager.java @@ -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); } diff --git a/src/main/java/it/tdlight/common/internal/Handler.java b/src/main/java/it/tdlight/common/internal/Handler.java index 0b55667..bad52fd 100644 --- a/src/main/java/it/tdlight/common/internal/Handler.java +++ b/src/main/java/it/tdlight/common/internal/Handler.java @@ -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; diff --git a/src/main/java/it/tdlight/common/internal/InternalClient.java b/src/main/java/it/tdlight/common/internal/InternalClient.java index 77a91ac..61abbb5 100644 --- a/src/main/java/it/tdlight/common/internal/InternalClient.java +++ b/src/main/java/it/tdlight/common/internal/InternalClient.java @@ -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 handlers = new ConcurrentHashMap(); diff --git a/src/main/java/it/tdlight/common/internal/InternalClientManager.java b/src/main/java/it/tdlight/common/internal/InternalClientManager.java index 199e302..661c24a 100644 --- a/src/main/java/it/tdlight/common/internal/InternalClientManager.java +++ b/src/main/java/it/tdlight/common/internal/InternalClientManager.java @@ -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 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> droppedEvents = getEffectivelyDroppedEvents(clientEventIds, clientEvents); + java.util.List droppedEvents = getEffectivelyDroppedEvents(clientEventIds, clientEvents); if (!droppedEvents.isEmpty()) { logger.error("Unknown client id \"{}\"! {} events have been dropped!", clientId, droppedEvents.size()); - for (Entry 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> getEffectivelyDroppedEvents(long[] clientEventIds, TdApi.Object[] clientEvents) { - java.util.List> droppedEvents = new ArrayList<>(clientEvents.length); + private List getEffectivelyDroppedEvents(long[] clientEventIds, TdApi.Object[] clientEvents) { + java.util.List 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; + } + } } diff --git a/src/main/java/it/tdlight/common/internal/InternalReactiveClient.java b/src/main/java/it/tdlight/common/internal/InternalReactiveClient.java index a1f6bcf..e411127 100644 --- a/src/main/java/it/tdlight/common/internal/InternalReactiveClient.java +++ b/src/main/java/it/tdlight/common/internal/InternalReactiveClient.java @@ -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 handlers = new ConcurrentHashMap(); private final ConcurrentLinkedQueue 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() { @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() { @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(); } diff --git a/src/main/java/it/tdlight/common/internal/MultiHandler.java b/src/main/java/it/tdlight/common/internal/MultiHandler.java index a550135..56aa123 100644 --- a/src/main/java/it/tdlight/common/internal/MultiHandler.java +++ b/src/main/java/it/tdlight/common/internal/MultiHandler.java @@ -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; diff --git a/src/main/java/it/tdlight/common/internal/NativeClientAccess.java b/src/main/java/it/tdlight/common/internal/NativeClientAccess.java index 99b6549..60d1908 100644 --- a/src/main/java/it/tdlight/common/internal/NativeClientAccess.java +++ b/src/main/java/it/tdlight/common/internal/NativeClientAccess.java @@ -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(); diff --git a/src/main/java/it/tdlight/common/internal/ResponseReceiver.java b/src/main/java/it/tdlight/common/internal/ResponseReceiver.java index 1d82511..4fb753a 100644 --- a/src/main/java/it/tdlight/common/internal/ResponseReceiver.java +++ b/src/main/java/it/tdlight/common/internal/ResponseReceiver.java @@ -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", diff --git a/src/main/java/it/tdlight/common/utils/Arch.java b/src/main/java/it/tdlight/common/utils/Arch.java index 61f48b7..28587e3 100644 --- a/src/main/java/it/tdlight/common/utils/Arch.java +++ b/src/main/java/it/tdlight/common/utils/Arch.java @@ -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 } diff --git a/src/main/java/it/tdlight/common/utils/CantLoadLibrary.java b/src/main/java/it/tdlight/common/utils/CantLoadLibrary.java index 735cf30..f594c19 100644 --- a/src/main/java/it/tdlight/common/utils/CantLoadLibrary.java +++ b/src/main/java/it/tdlight/common/utils/CantLoadLibrary.java @@ -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"); } } diff --git a/src/main/java/it/tdlight/common/utils/CloseCallback.java b/src/main/java/it/tdlight/common/utils/CloseCallback.java deleted file mode 100644 index 83f66d5..0000000 --- a/src/main/java/it/tdlight/common/utils/CloseCallback.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2018. Ernesto Castellotti - * 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 . - */ - -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(); -} diff --git a/src/main/java/it/tdlight/common/utils/ErrorCallback.java b/src/main/java/it/tdlight/common/utils/ErrorCallback.java deleted file mode 100644 index bf4c2a0..0000000 --- a/src/main/java/it/tdlight/common/utils/ErrorCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2018. Ernesto Castellotti - * 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 . - */ - -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); -} diff --git a/src/main/java/it/tdlight/common/utils/IntSwapper.java b/src/main/java/it/tdlight/common/utils/IntSwapper.java index f024d92..f4fa09b 100644 --- a/src/main/java/it/tdlight/common/utils/IntSwapper.java +++ b/src/main/java/it/tdlight/common/utils/IntSwapper.java @@ -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; diff --git a/src/main/java/it/tdlight/common/utils/LoadLibrary.java b/src/main/java/it/tdlight/common/utils/LoadLibrary.java index b555df9..6b9ddd8 100644 --- a/src/main/java/it/tdlight/common/utils/LoadLibrary.java +++ b/src/main/java/it/tdlight/common/utils/LoadLibrary.java @@ -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 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; } diff --git a/src/main/java/it/tdlight/common/utils/NativeErrorCallback.java b/src/main/java/it/tdlight/common/utils/NativeErrorCallback.java deleted file mode 100644 index dbbf91f..0000000 --- a/src/main/java/it/tdlight/common/utils/NativeErrorCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2018. Ernesto Castellotti - * 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 . - */ - -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); -} diff --git a/src/main/java/it/tdlight/common/utils/Os.java b/src/main/java/it/tdlight/common/utils/Os.java index 780523e..618f7eb 100644 --- a/src/main/java/it/tdlight/common/utils/Os.java +++ b/src/main/java/it/tdlight/common/utils/Os.java @@ -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 } diff --git a/src/main/java/it/tdlight/common/utils/ReceiveCallback.java b/src/main/java/it/tdlight/common/utils/ReceiveCallback.java deleted file mode 100644 index a6fc1d8..0000000 --- a/src/main/java/it/tdlight/common/utils/ReceiveCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2018. Ernesto Castellotti - * 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 . - */ - -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); -} diff --git a/src/main/java/it/tdlight/common/utils/ScannerUtils.java b/src/main/java/it/tdlight/common/utils/ScannerUtils.java index 3e7cea0..a7b25ce 100644 --- a/src/main/java/it/tdlight/common/utils/ScannerUtils.java +++ b/src/main/java/it/tdlight/common/utils/ScannerUtils.java @@ -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(); } } diff --git a/src/main/java/it/tdlight/common/utils/SpinWaitSupport.java b/src/main/java/it/tdlight/common/utils/SpinWaitSupport.java index 3294b6d..db8daee 100644 --- a/src/main/java/it/tdlight/common/utils/SpinWaitSupport.java +++ b/src/main/java/it/tdlight/common/utils/SpinWaitSupport.java @@ -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 - } - } } } diff --git a/src/main/java/it/tdlight/tdnative/NativeClient.java b/src/main/java/it/tdlight/tdnative/NativeClient.java index b4e04da..684509d 100644 --- a/src/main/java/it/tdlight/tdnative/NativeClient.java +++ b/src/main/java/it/tdlight/tdnative/NativeClient.java @@ -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); } diff --git a/src/main/java/it/tdlight/tdnative/NativeLog.java b/src/main/java/it/tdlight/tdnative/NativeLog.java index bb645e7..94c5ac6 100644 --- a/src/main/java/it/tdlight/tdnative/NativeLog.java +++ b/src/main/java/it/tdlight/tdnative/NativeLog.java @@ -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 defaultFatalErrorCallbackPtr = System.err::println; - private static Consumer fatalErrorCallback = defaultFatalErrorCallbackPtr; + private static final Consumer defaultFatalErrorCallbackPtr = System.err::println; + private static Consumer 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 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 fatalErrorCallback) { + if (fatalErrorCallback == null) { + NativeLog.fatalErrorCallback = defaultFatalErrorCallbackPtr; + } else { + NativeLog.fatalErrorCallback = fatalErrorCallback; + } + } } diff --git a/src/main/java/it/tdlight/tdnative/ObjectsUtils.java b/src/main/java/it/tdlight/tdnative/ObjectsUtils.java deleted file mode 100644 index 06484b8..0000000 --- a/src/main/java/it/tdlight/tdnative/ObjectsUtils.java +++ /dev/null @@ -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 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 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: - *
-	 * public Foo(Bar bar) {
-	 *     this.bar = Objects.requireNonNull(bar);
-	 * }
-	 * 
- * - * @param obj the object reference to check for nullity - * @param the type of the reference - * @return {@code obj} if not {@code null} - * @throws NullPointerException if {@code obj} is {@code null} - */ - public static 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: - *
-	 * 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");
-	 * }
-	 * 
- * - * @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 the type of the reference - * @return {@code obj} if not {@code null} - * @throws NullPointerException if {@code obj} is {@code null} - */ - public static T requireNonNull(T obj, String message) { - if (obj == null) - throw new NullPointerException(message); - return obj; - } -} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index bac5164..23acfa3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -9,4 +9,4 @@ module tdlight.java { exports it.tdlight.common; exports it.tdlight.common.utils; exports it.tdlight.common.internal; -} \ No newline at end of file +} diff --git a/src/main/java11/it/tdlight/common/utils/SpinWaitSupport.java b/src/main/java11/it/tdlight/common/utils/SpinWaitSupport.java new file mode 100644 index 0000000..d161234 --- /dev/null +++ b/src/main/java11/it/tdlight/common/utils/SpinWaitSupport.java @@ -0,0 +1,11 @@ +package it.tdlight.common.utils; + +public class SpinWaitSupport { + + private SpinWaitSupport() { + } + + public static void onSpinWait() { + java.lang.Thread.onSpinWait(); + } +} diff --git a/tdlib/pom.xml b/tdlib/pom.xml index a45d35e..e1d6b3f 100644 --- a/tdlib/pom.xml +++ b/tdlib/pom.xml @@ -8,8 +8,8 @@ UTF-8 1.0.0-SNAPSHOT - 3.3.127 - 3.3.129 + 3.3.147 + 3.3.149 @@ -65,6 +65,10 @@ reactive-streams 1.0.3 + + net.harawata + appdirs + @@ -73,8 +77,13 @@ fastutil 8.5.6 + + net.harawata + appdirs + 1.2.1 + - + releaseDir diff --git a/tdlight/pom.xml b/tdlight/pom.xml index 270f463..2559a02 100644 --- a/tdlight/pom.xml +++ b/tdlight/pom.xml @@ -8,8 +8,8 @@ UTF-8 1.0.0-SNAPSHOT - 3.3.127 - 3.3.129 + 3.3.147 + 3.3.149 @@ -65,6 +65,10 @@ reactive-streams 1.0.3 + + net.harawata + appdirs + @@ -73,6 +77,11 @@ fastutil 8.5.6 + + net.harawata + appdirs + 1.2.1 + @@ -90,7 +99,7 @@ - ../src/main/java + ${project.basedir}/../src/main/java maven-clean-plugin @@ -116,9 +125,14 @@ maven-javadoc-plugin 3.2.0 - 8 + 17 -html5 - + public + false + true + -Xdoclint:none + -Xdoclint:none + attach-javadocs @@ -128,81 +142,92 @@ - - - org.apache.maven.plugins - maven-enforcer-plugin - 3.0.0-M3 - - - enforce-jdk9 - - enforce - - - - - [1.9,) - JDK 9+ is required for compilation - - - - - - - - + + org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - UTF-8 - 11 - 11 - false - - it/tdlight/tdlib/ClientManager.java - - - - - - default-compile - none - - - 9 - - - - - java-9-module-compile - - compile - - - 9 - - - - - java-8-compile - - compile - - - - 8 - 8 - - - it/tdlight/tdlib/ClientManager.java - module-info.java - - - - - + maven-enforcer-plugin + 3.0.0-M3 + + + enforce-jdk9 + + enforce + + + + + [1.9,) + JDK 9+ is required for compilation + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + UTF-8 + + it/tdlight/tdlib/ClientManager.java + + + + + + default-compile + none + + + 9 + + + + + java-11-module-compile + + compile + + + 11 + + ${project.basedir}/../src/main/java11 + + true + + + + + java-9-module-compile + + compile + + + 9 + + + + + java-8-compile + + compile + + + + 8 + + + module-info.java + + + + + + + maven-jar-plugin 3.2.0 + + + + true + + + maven-install-plugin