// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // 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 org.drinkless.tdlib.example; import org.drinkless.tdlib.Client; import org.drinkless.tdlib.TdApi; import java.io.IOError; import java.io.IOException; import java.io.BufferedReader; 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. */ public final class Example { private static Client 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 Client.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 = Client.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()) { // send LoadChats request if there are some unknown chats and have not enough known chats client.send(new TdApi.LoadChats(new TdApi.ChatListMain(), limit - mainChatList.size()), new Client.ResultHandler() { @Override public void onResult(TdApi.Object object) { switch (object.getConstructor()) { case TdApi.Error.CONSTRUCTOR: if (((TdApi.Error) object).code == 404) { synchronized (mainChatList) { haveFullMainChatList = true; } } else { System.err.println("Receive an error for LoadChats:" + newLine + object); } break; case TdApi.Ok.CONSTRUCTOR: // 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; } 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 < mainChatList.size(); 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 { // disable TDLib log Client.execute(new TdApi.SetLogVerbosityLevel(0)); 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")); } // create client client = Client.create(new UpdateHandler(), null, null); // 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 Client.ResultHandler { @Override public void onResult(TdApi.Object object) { print(object.toString()); } } private static class UpdateHandler implements Client.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 Client.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); } } } }