From 1d360d7c55549c56a43e0fcda1dfaa7e4286d596 Mon Sep 17 00:00:00 2001 From: Andrea Cavalli Date: Fri, 23 Oct 2020 04:44:15 +0200 Subject: [PATCH] Add a full example --- example/pom.xml | 63 ++ .../main/java/it.tdlight.example/Example.java | 612 ++++++++++++++++++ 2 files changed, 675 insertions(+) create mode 100644 example/pom.xml create mode 100644 example/src/main/java/it.tdlight.example/Example.java diff --git a/example/pom.xml b/example/pom.xml new file mode 100644 index 0000000..8589a7e --- /dev/null +++ b/example/pom.xml @@ -0,0 +1,63 @@ + + 4.0.0 + it.tdlight + example-java + 1.0.0-SNAPSHOT + TDLight Java Example + jar + + UTF-8 + + + + mchv-release + MCHV Release Apache Maven Packages + https://mvn.mchv.eu/repository/mchv + + + mchv-snapshot + MCHV Snapshot Apache Maven Packages + https://mvn.mchv.eu/repository/mchv-snapshot + + + + + it.tdlight + tdlight-java + RELEASE + + + + + + maven-clean-plugin + 3.1.0 + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.1 + + UTF-8 + 11 + 11 + + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 3.0.0-M1 + + + maven-deploy-plugin + 2.8.2 + + + + diff --git a/example/src/main/java/it.tdlight.example/Example.java b/example/src/main/java/it.tdlight.example/Example.java new file mode 100644 index 0000000..2712ba4 --- /dev/null +++ b/example/src/main/java/it.tdlight.example/Example.java @@ -0,0 +1,612 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +package it.tdlight.example; + +import it.tdlight.common.ResultHandler; +import it.tdlight.common.TelegramClient; +import it.tdlight.jni.TdApi; +import it.tdlight.tdlight.ClientManager; +import java.io.BufferedReader; +import java.io.IOError; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.NavigableSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Condition; +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) + */ +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; + + 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; + + static { + try { + System.loadLibrary("tdjni"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } + } + + 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(new UpdateHandler(), null, null); // recreate client after previous has closed + } 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; 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 { + + // create client + client = ClientManager.create(new UpdateHandler(), null, null); + + 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")); + } + + // test Client.execute + defaultHandler.onResult(client.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test"))); + + // main loop + while (!needQuit) { + // await authorization + authorizationLock.lock(); + try { + while (!haveAuthorization) { + gotAuthorization.await(); + } + } finally { + authorizationLock.unlock(); + } + + while (haveAuthorization) { + getCommand(); + } + } + while (!canQuit) { + Thread.sleep(1); + } + } + + private 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; + } + } + + private static class DefaultHandler implements ResultHandler { + @Override + public void onResult(TdApi.Object object) { + print(object.toString()); + } + } + + 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 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); + } + } + } +} \ No newline at end of file