This commit is contained in:
2024-10-10 19:05:48 +00:00
commit cffdcba6af
1880 changed files with 813614 additions and 0 deletions

5
td/example/java/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/*build/
/bin/
/docs/
/org/drinkless/tdlib/TdApi.java
/td/

View File

@@ -0,0 +1,155 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if (POLICY CMP0065)
# do not export symbols from executables
# affects compiler checks in project(), so must be set before it
cmake_policy(SET CMP0065 NEW)
endif()
project(TdJavaExample VERSION 1.0 LANGUAGES CXX)
option(TD_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
if (POLICY CMP0054)
# do not expand quoted arguments
cmake_policy(SET CMP0054 NEW)
endif()
if (POLICY CMP0060)
# link libraries by full path
cmake_policy(SET CMP0060 NEW)
endif()
if (POLICY CMP0074)
# use environment variables to find libraries
cmake_policy(SET CMP0074 NEW)
endif()
find_package(Td REQUIRED)
if (NOT JNI_FOUND)
find_package(JNI REQUIRED COMPONENTS JVM)
endif()
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
if (NOT Java_FOUND)
find_package(Java REQUIRED)
endif()
message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}")
# Generating TdApi.java
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}")
if (TD_JSON_JAVA)
add_custom_target(td_generate_java_api
COMMAND cmake -E echo ""
COMMENT "Skip generation of Java TDLib API source files"
)
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/JsonExample.java")
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/JsonClient.java")
else()
find_program(PHP_EXECUTABLE php)
if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD"))
set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND")
endif()
set(TD_API_TLO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tlo)
set(TD_API_TL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tl)
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/JavadocTlDocumentationGenerator.php)
set(GENERATE_JAVA_API_CMD ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api TdApi "${TD_API_TLO_PATH}" "${TD_API_JAVA_PATH}" "${TD_API_JAVA_PACKAGE}")
if (PHP_EXECUTABLE)
set(GENERATE_JAVA_API_CMD ${GENERATE_JAVA_API_CMD} && ${PHP_EXECUTABLE} "${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}" "${TD_API_TL_PATH}" "${JAVA_SOURCE_PATH}/TdApi.java")
endif()
add_custom_target(td_generate_java_api
COMMAND ${GENERATE_JAVA_API_CMD}
COMMENT "Generating Java TDLib API source files"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api ${TD_API_TLO_PATH} ${TD_API_TL_PATH} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}
)
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/Example.java")
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/Client.java" "${JAVA_SOURCE_PATH}/TdApi.java")
endif()
if (CMAKE_VERSION VERSION_LESS "3.17")
set(CMAKE_RM_COMMAND remove_directory)
else()
set(CMAKE_RM_COMMAND rm -rf --)
endif()
get_filename_component(JAVA_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
file(MAKE_DIRECTORY ${JAVA_OUTPUT_DIRECTORY})
add_custom_target(build_java
COMMAND ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/${TD_API_JAVA_PACKAGE}"
COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}" ${JAVA_EXAMPLE_FILES} ${JAVA_SOURCE_FILES}
COMMENT "Building Java code"
DEPENDS td_generate_java_api
)
add_custom_target(generate_javadoc
COMMAND ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/../docs"
COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}/../docs" ${JAVA_SOURCE_FILES}
WORKING_DIRECTORY ${TD_API_JAVA_PATH}
COMMENT "Generating Javadoc documentation"
DEPENDS td_generate_java_api
)
# Building shared library
add_library(tdjni SHARED
td_jni.cpp
)
target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
target_link_libraries(tdjni PRIVATE ${JAVA_JVM_LIBRARY})
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}")
if (TD_JSON_JAVA)
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
else()
target_link_libraries(tdjni PRIVATE Td::TdStatic)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC 1)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CLANG 1)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
set(INTEL 1)
elseif (NOT MSVC)
message(FATAL_ERROR "Compiler isn't supported")
endif()
include(CheckCXXCompilerFlag)
if (GCC OR CLANG OR INTEL)
if (WIN32 AND INTEL)
set(STD14_FLAG /Qstd=c++14)
else()
set(STD14_FLAG -std=c++14)
endif()
check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14)
if (NOT HAVE_STD14)
string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}")
check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y)
set(HAVE_STD14 ${HAVE_STD1Y})
endif()
target_compile_options(tdjni PRIVATE "${STD14_FLAG}")
elseif (MSVC)
set(HAVE_STD14 MSVC_VERSION>=1900)
endif()
if (NOT HAVE_STD14)
message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.")
endif()
add_dependencies(tdjni td_generate_java_api build_java generate_javadoc)
install(TARGETS tdjni
LIBRARY DESTINATION bin
RUNTIME DESTINATION bin
)
if (MSVC AND VCPKG_TOOLCHAIN)
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb")
endif()

47
td/example/java/README.md Normal file
View File

@@ -0,0 +1,47 @@
# TDLib Java example
To run this example, you will need installed JDK >= 1.6.
For Javadoc documentation generation PHP is needed.
You can find complete build instructions for your operating system at https://tdlib.github.io/td/build.html?language=Java.
In general, the build process looks as follows.
TDLib should be prebuilt with JNI bindings and installed to local subdirectory `td/` as follows:
```
cd <path to TDLib sources>
mkdir jnibuild
cd jnibuild
cmake -DCMAKE_BUILD_TYPE=Release -DTD_ENABLE_JNI=ON -DCMAKE_INSTALL_PREFIX:PATH=../example/java/td ..
cmake --build . --target install
```
If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you will also need to add `-A Win32`/`-A x64` option to CMake.
In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake
If you want to build JsonClient.java wrapper for JSON interface instead of the native JNI interface, add `-DTD_JSON_JAVA=ON` option to CMake.
After this you can compile the example source code:
```
cd <path to TDLib sources>/example/java
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/example/java/td/lib/cmake/Td -DCMAKE_INSTALL_PREFIX:PATH=.. ..
cmake --build . --target install
```
Compiled TDLib shared library and Java example after that will be placed in `bin/` and Javadoc documentation in `docs/`.
After this you can run the Java example:
```
cd <path to TDLib sources>/example/java/bin
java '-Djava.library.path=.' org/drinkless/tdlib/example/Example
```
If you built JSON interface example using `-DTD_JSON_JAVA=ON` option, then use the command `java '-Djava.library.path=.' org/drinkless/tdlib/example/JsonExample` instead.
If you receive "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/".
If you receive java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared OpenSSL and zlib libraries to `bin/`.
Make sure that you compiled the example for the same architecture as your JVM.

View File

@@ -0,0 +1,259 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Main class for interaction with the TDLib.
*/
public final class Client {
static {
try {
System.loadLibrary("tdjni");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
/**
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
*/
public interface ResultHandler {
/**
* Callback called on result of query to TDLib or incoming update from TDLib.
*
* @param object Result of query or update of type TdApi.Update about new events.
*/
void onResult(TdApi.Object object);
}
/**
* 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 {
/**
* Callback called on exceptions thrown while invoking ResultHandler.
*
* @param e Exception thrown by ResultHandler.
*/
void onException(Throwable e);
}
/**
* Interface for handler of messages that are added to the internal TDLib log.
*/
public interface LogMessageHandler {
/**
* Callback called on messages that are added to the internal TDLib log.
*
* @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024.
* If 0, then TDLib will crash as soon as the callback returns.
* None of the TDLib methods can be called from the callback.
* @param message The message added to the internal TDLib log.
*/
void onLogMessage(int verbosityLevel, String message);
}
/**
* Exception class thrown when TDLib error occurred while performing {@link #execute(TdApi.Function)}.
*/
public static class ExecutionException extends Exception {
/**
* Original TDLib error occurred when performing one of the synchronous functions.
*/
public final TdApi.Error error;
/**
* @param error TDLib error occurred while performing {@link #execute(TdApi.Function)}.
*/
ExecutionException (TdApi.Error error) {
super(error.code + ": " + error.message);
this.error = error;
}
}
/**
* 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.
*/
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
long queryId = currentQueryId.incrementAndGet();
if (resultHandler != null) {
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
}
nativeClientSend(nativeClientId, queryId, query);
}
/**
* 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.
*/
public void send(TdApi.Function query, ResultHandler resultHandler) {
send(query, resultHandler, null);
}
/**
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
*
* @param query Object representing a query to the TDLib.
* @param <T> Automatically deduced return type of the query.
* @return request result.
* @throws ExecutionException if query execution fails.
*/
@SuppressWarnings("unchecked")
public static <T extends TdApi.Object> T execute(TdApi.Function<T> query) throws ExecutionException {
TdApi.Object object = nativeClientExecute(query);
if (object instanceof TdApi.Error) {
throw new ExecutionException((TdApi.Error) object);
}
return (T) object;
}
/**
* Creates new Client.
*
* @param updateHandler Handler for incoming updates.
* @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be ignored.
* @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be ignored.
* @return created Client
*/
public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler);
synchronized (responseReceiver) {
if (!responseReceiver.isRun) {
responseReceiver.isRun = true;
Thread receiverThread = new Thread(responseReceiver, "TDLib thread");
receiverThread.setDaemon(true);
receiverThread.start();
}
}
return client;
}
/**
* Sets the handler for messages that are added to the internal TDLib log.
* None of the TDLib methods can be called from the callback.
*
* @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
* @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
public static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) {
nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler);
}
private static class ResponseReceiver implements Runnable {
public boolean isRun = false;
@Override
public void run() {
while (true) {
int resultN = nativeClientReceive(clientIds, eventIds, events, 100000.0 /*seconds*/);
for (int i = 0; i < resultN; i++) {
processResult(clientIds[i], eventIds[i], events[i]);
events[i] = null;
}
}
}
private void processResult(int clientId, long id, TdApi.Object object) {
boolean isClosed = false;
if (id == 0 && object instanceof TdApi.UpdateAuthorizationState) {
TdApi.AuthorizationState authorizationState = ((TdApi.UpdateAuthorizationState) object).authorizationState;
if (authorizationState instanceof TdApi.AuthorizationStateClosed) {
isClosed = true;
}
}
Handler handler = id == 0 ? updateHandlers.get(clientId) : handlers.remove(id);
if (handler != null) {
try {
handler.resultHandler.onResult(object);
} catch (Throwable cause) {
ExceptionHandler exceptionHandler = handler.exceptionHandler;
if (exceptionHandler == null) {
exceptionHandler = defaultExceptionHandlers.get(clientId);
}
if (exceptionHandler != null) {
try {
exceptionHandler.onException(cause);
} catch (Throwable ignored) {
}
}
}
}
if (isClosed) {
updateHandlers.remove(clientId); // there will be no more updates
defaultExceptionHandlers.remove(clientId); // ignore further exceptions
clientCount.decrementAndGet();
}
}
private static final int MAX_EVENTS = 1000;
private final int[] clientIds = new int[MAX_EVENTS];
private final long[] eventIds = new long[MAX_EVENTS];
private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
}
private final int nativeClientId;
private static final ConcurrentHashMap<Integer, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Integer, ExceptionHandler>();
private static final ConcurrentHashMap<Integer, Handler> updateHandlers = new ConcurrentHashMap<Integer, Handler>();
private static final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
private static final AtomicLong currentQueryId = new AtomicLong();
private static final AtomicLong clientCount = new AtomicLong();
private static final ResponseReceiver responseReceiver = new ResponseReceiver();
private static class Handler {
final ResultHandler resultHandler;
final ExceptionHandler exceptionHandler;
Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
this.resultHandler = resultHandler;
this.exceptionHandler = exceptionHandler;
}
}
private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
clientCount.incrementAndGet();
nativeClientId = createNativeClient();
if (updateHandler != null) {
updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
}
if (defaultExceptionHandler != null) {
defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
}
send(new TdApi.GetOption("version"), null, null);
}
private static native int createNativeClient();
private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
private static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
private static native TdApi.Object nativeClientExecute(TdApi.Function function);
private static native void nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler);
}

View File

@@ -0,0 +1,79 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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;
/**
* Main class for interaction with the TDLib using JSON interface.
*/
public final class JsonClient {
static {
try {
System.loadLibrary("tdjsonjava");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
/**
* Returns an opaque identifier of a new TDLib instance.
* The TDLib instance will not send updates until the first request is sent to it.
* @return Opaque identifier of a new TDLib instance.
*/
public static native int createClientId();
/**
* Sends request to the TDLib client. May be called from any thread.
* @param clientId TDLib client identifier.
* @param request JSON-serialized request.
*/
public static native void send(int clientId, String request);
/**
* Receives incoming updates and request responses. Must not be called simultaneously from two different threads.
* @param timeout The maximum number of seconds allowed for this function to wait for new data.
* @return JSON-serialized incoming update or request response. May be null if the timeout expired before new data received.
*/
public static native String receive(double timeout);
/**
* Synchronously executes a TDLib request.
* A request can be executed synchronously, only if it is documented with "Can be called synchronously".
* @param request JSON-serialized request.
* @return JSON-serialized request response. May be null if the request is invalid.
*/
public static native String execute(String request);
/**
* Interface for handler of messages that are added to the internal TDLib log.
*/
public interface LogMessageHandler {
/**
* Callback called on messages that are added to the internal TDLib log.
*
* @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024.
* If 0, then TDLib will crash as soon as the callback returns.
* None of the TDLib methods can be called from the callback.
* @param message The message added to the internal TDLib log.
*/
void onLogMessage(int verbosityLevel, String message);
}
/**
* Sets the handler for messages that are added to the internal TDLib log.
* None of the TDLib methods can be called from the callback.
*
* @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
* @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
public static native void setLogMessageHandler(int maxVerbosityLevel, JsonClient.LogMessageHandler logMessageHandler);
/**
* The class can't be instantiated.
*/
private JsonClient() {
}
}

View File

@@ -0,0 +1,780 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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.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.atomic.AtomicLong;
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<Long, TdApi.User> users = new ConcurrentHashMap<Long, TdApi.User>();
private static final ConcurrentMap<Long, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Long, TdApi.BasicGroup>();
private static final ConcurrentMap<Long, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Long, TdApi.Supergroup>();
private static final ConcurrentMap<Integer, TdApi.SecretChat> secretChats = new ConcurrentHashMap<Integer, TdApi.SecretChat>();
private static final ConcurrentMap<Long, TdApi.Chat> chats = new ConcurrentHashMap<Long, TdApi.Chat>();
private static final NavigableSet<OrderedChat> mainChatList = new TreeSet<OrderedChat>();
private static boolean haveFullMainChatList = false;
private static final ConcurrentMap<Long, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Long, TdApi.UserFullInfo>();
private static final ConcurrentMap<Long, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Long, TdApi.BasicGroupFullInfo>();
private static final ConcurrentMap<Long, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Long, TdApi.SupergroupFullInfo>();
private static final String newLine = System.getProperty("line.separator");
private static final String commandsLine = "Enter command (gcs - GetChats, gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ";
private static volatile String currentPrompt = null;
private static void print(String str) {
if (currentPrompt != null) {
System.out.println("");
}
System.out.println(str);
if (currentPrompt != null) {
System.out.print(currentPrompt);
}
}
private static void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
synchronized (mainChatList) {
synchronized (chat) {
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isRemoved = mainChatList.remove(new OrderedChat(chat.id, position));
assert isRemoved;
}
}
chat.positions = positions;
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position));
assert isAdded;
}
}
}
}
}
private static void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) {
if (authorizationState != null) {
Example.authorizationState = authorizationState;
}
switch (Example.authorizationState.getConstructor()) {
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters();
request.databaseDirectory = "tdlib";
request.useMessageDatabase = true;
request.useSecretChats = true;
request.apiId = 94575;
request.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
request.systemLanguageCode = "en";
request.deviceModel = "Desktop";
request.applicationVersion = "1.0";
client.send(request, 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.AuthorizationStateWaitEmailAddress.CONSTRUCTOR: {
String emailAddress = promptString("Please enter email address: ");
client.send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR: {
String code = promptString("Please enter email authentication code: ");
client.send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler());
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, false), 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<OrderedChat> iter = mainChatList.iterator();
System.out.println();
System.out.println("First " + limit + " chat(s) out of " + mainChatList.size() + " known chat(s):");
for (int i = 0; i < limit && 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), null, true);
client.send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), defaultHandler);
}
public static void main(String[] args) throws InterruptedException {
// set log message handler to handle only fatal errors (0) and plain log messages (-1)
Client.setLogMessageHandler(0, new LogMessageHandler());
// disable TDLib log and redirect fatal errors and plain log messages to a file
try {
Client.execute(new TdApi.SetLogVerbosityLevel(0));
Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false)));
} catch (Client.ExecutionException error) {
throw new IOError(new IOException("Write access to the current directory is required"));
}
// create client
client = Client.create(new UpdateHandler(), null, null);
// 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<OrderedChat> {
final long chatId;
final TdApi.ChatPosition position;
OrderedChat(long chatId, TdApi.ChatPosition position) {
this.chatId = chatId;
this.position = position;
}
@Override
public int compareTo(OrderedChat o) {
if (this.position.order != o.position.order) {
return o.position.order < this.position.order ? -1 : 1;
}
if (this.chatId != o.chatId) {
return o.chatId < this.chatId ? -1 : 1;
}
return 0;
}
@Override
public boolean equals(Object obj) {
OrderedChat o = (OrderedChat) obj;
return this.chatId == o.chatId && this.position.order == o.position.order;
}
}
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.UpdateChatPermissions.CONSTRUCTOR: {
TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.permissions = update.permissions;
}
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.UpdateChatActionBar.CONSTRUCTOR: {
TdApi.UpdateChatActionBar updateChat = (TdApi.UpdateChatActionBar) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.actionBar = updateChat.actionBar;
}
break;
}
case TdApi.UpdateChatAvailableReactions.CONSTRUCTOR: {
TdApi.UpdateChatAvailableReactions updateChat = (TdApi.UpdateChatAvailableReactions) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.availableReactions = updateChat.availableReactions;
}
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.UpdateChatMessageSender.CONSTRUCTOR: {
TdApi.UpdateChatMessageSender updateChat = (TdApi.UpdateChatMessageSender) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.messageSenderId = updateChat.messageSenderId;
}
break;
}
case TdApi.UpdateChatMessageAutoDeleteTime.CONSTRUCTOR: {
TdApi.UpdateChatMessageAutoDeleteTime updateChat = (TdApi.UpdateChatMessageAutoDeleteTime) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.messageAutoDeleteTime = updateChat.messageAutoDeleteTime;
}
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.UpdateChatPendingJoinRequests.CONSTRUCTOR: {
TdApi.UpdateChatPendingJoinRequests update = (TdApi.UpdateChatPendingJoinRequests) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.pendingJoinRequests = update.pendingJoinRequests;
}
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.UpdateChatBackground.CONSTRUCTOR: {
TdApi.UpdateChatBackground updateChat = (TdApi.UpdateChatBackground) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.background = updateChat.background;
}
break;
}
case TdApi.UpdateChatTheme.CONSTRUCTOR: {
TdApi.UpdateChatTheme updateChat = (TdApi.UpdateChatTheme) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.themeName = updateChat.themeName;
}
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.UpdateChatUnreadReactionCount.CONSTRUCTOR: {
TdApi.UpdateChatUnreadReactionCount updateChat = (TdApi.UpdateChatUnreadReactionCount) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadReactionCount = updateChat.unreadReactionCount;
}
break;
}
case TdApi.UpdateChatVideoChat.CONSTRUCTOR: {
TdApi.UpdateChatVideoChat updateChat = (TdApi.UpdateChatVideoChat) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.videoChat = updateChat.videoChat;
}
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.UpdateChatHasProtectedContent.CONSTRUCTOR: {
TdApi.UpdateChatHasProtectedContent updateChat = (TdApi.UpdateChatHasProtectedContent) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.hasProtectedContent = updateChat.hasProtectedContent;
}
break;
}
case TdApi.UpdateChatIsTranslatable.CONSTRUCTOR: {
TdApi.UpdateChatIsTranslatable update = (TdApi.UpdateChatIsTranslatable) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.isTranslatable = update.isTranslatable;
}
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.UpdateChatBlockList.CONSTRUCTOR: {
TdApi.UpdateChatBlockList update = (TdApi.UpdateChatBlockList) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.blockList = update.blockList;
}
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.UpdateMessageMentionRead.CONSTRUCTOR: {
TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadMentionCount = updateChat.unreadMentionCount;
}
break;
}
case TdApi.UpdateMessageUnreadReactions.CONSTRUCTOR: {
TdApi.UpdateMessageUnreadReactions updateChat = (TdApi.UpdateMessageUnreadReactions) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadReactionCount = updateChat.unreadReactionCount;
}
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);
}
}
}
private static class LogMessageHandler implements Client.LogMessageHandler {
@Override
public void onLogMessage(int verbosityLevel, String message) {
if (verbosityLevel == 0) {
onFatalError(message);
return;
}
System.err.println(message);
}
}
private static void onFatalError(String errorMessage) {
final class ThrowError implements Runnable {
private final String errorMessage;
private final AtomicLong errorThrowTime;
private ThrowError(String errorMessage, AtomicLong errorThrowTime) {
this.errorMessage = errorMessage;
this.errorThrowTime = errorThrowTime;
}
@Override
public void run() {
if (isDatabaseBrokenError(errorMessage) || isDiskFullError(errorMessage) || isDiskError(errorMessage)) {
processExternalError();
return;
}
errorThrowTime.set(System.currentTimeMillis());
throw new ClientError("TDLib fatal error: " + errorMessage);
}
private void processExternalError() {
errorThrowTime.set(System.currentTimeMillis());
throw new ExternalClientError("Fatal error: " + errorMessage);
}
final class ClientError extends Error {
private ClientError(String message) {
super(message);
}
}
final class ExternalClientError extends Error {
public ExternalClientError(String message) {
super(message);
}
}
private boolean isDatabaseBrokenError(String message) {
return message.contains("Wrong key or database is corrupted") ||
message.contains("SQL logic error or missing database") ||
message.contains("database disk image is malformed") ||
message.contains("file is encrypted or is not a database") ||
message.contains("unsupported file format") ||
message.contains("Database was corrupted and deleted during execution and can't be recreated");
}
private boolean isDiskFullError(String message) {
return message.contains("PosixError : No space left on device") ||
message.contains("database or disk is full");
}
private boolean isDiskError(String message) {
return message.contains("I/O error") || message.contains("Structure needs cleaning");
}
}
final AtomicLong errorThrowTime = new AtomicLong(Long.MAX_VALUE);
new Thread(new ThrowError(errorMessage, errorThrowTime), "TDLib fatal error thread").start();
// wait at least 10 seconds after the error is thrown
while (errorThrowTime.get() >= System.currentTimeMillis() - 10000) {
try {
Thread.sleep(1000 /* milliseconds */);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
}
}
}

View File

@@ -0,0 +1,47 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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.JsonClient;
/**
* Example class for TDLib usage from Java using JSON interface.
*/
public final class JsonExample {
public static void main(String[] args) throws InterruptedException {
// set log message handler to handle only fatal errors (0) and plain log messages (-1)
JsonClient.setLogMessageHandler(0, new LogMessageHandler());
// disable TDLib log and redirect fatal errors and plain log messages to a file
JsonClient.execute("{\"@type\":\"setLogVerbosityLevel\",\"new_verbosity_level\":0}");
JsonClient.execute("{\"@type\":\"setLogStream\",\"log_stream\":{\"@type\":\"logStreamFile\",\"path\":\"tdlib.log\",\"max_file_size\":128000000}}");
// create client identifier
int clientId = JsonClient.createClientId();
// send first request to activate the client
JsonClient.send(clientId, "{\"@type\":\"getOption\",\"name\":\"version\"}");
// main loop
while (true) {
String result = JsonClient.receive(100.0);
if (result != null) {
System.out.println(result);
}
}
}
private static class LogMessageHandler implements JsonClient.LogMessageHandler {
@Override
public void onLogMessage(int verbosityLevel, String message) {
System.err.print(message);
if (verbosityLevel == 0) {
System.err.println("Receive fatal error; the process will crash now");
}
}
}
}

241
td/example/java/td_jni.cpp Normal file
View File

@@ -0,0 +1,241 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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)
//
#ifdef TD_JSON_JAVA
#include <td/telegram/td_json_client.h>
#else
#include <td/telegram/Client.h>
#include <td/telegram/td_api.h>
#endif
#include <td/tl/tl_jni_object.h>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
namespace td_jni {
#ifdef TD_JSON_JAVA
static jint JsonClient_createClientId(JNIEnv *env, jclass clazz) {
return static_cast<jint>(td_create_client_id());
}
static void JsonClient_send(JNIEnv *env, jclass clazz, jint client_id, jstring request) {
td_send(static_cast<int>(client_id), td::jni::from_jstring(env, request).c_str());
}
static jstring JsonClient_receive(JNIEnv *env, jclass clazz, jdouble timeout) {
auto result = td_receive(timeout);
if (result == nullptr) {
return nullptr;
}
return td::jni::to_jstring(env, result);
}
static jstring JsonClient_execute(JNIEnv *env, jclass clazz, jstring request) {
auto result = td_execute(td::jni::from_jstring(env, request).c_str());
if (result == nullptr) {
return nullptr;
}
return td::jni::to_jstring(env, result);
}
#else
static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, jobject function) {
td::jni::reset_parse_error();
auto result = td::td_api::Function::fetch(env, function);
if (td::jni::have_parse_error()) {
std::abort();
}
return result;
}
static td::ClientManager *get_manager() {
return td::ClientManager::get_manager_singleton();
}
static jint Client_createNativeClient(JNIEnv *env, jclass clazz) {
return static_cast<jint>(get_manager()->create_client_id());
}
static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) {
get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id),
fetch_function(env, function));
}
static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids,
jobjectArray events, jdouble timeout) {
jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size
if (events_size == 0) {
return 0;
}
jsize result_size = 0;
auto *manager = get_manager();
auto response = manager->receive(timeout);
while (response.object) {
auto client_id = static_cast<jint>(response.client_id);
env->SetIntArrayRegion(client_ids, result_size, 1, &client_id);
auto request_id = static_cast<jlong>(response.request_id);
env->SetLongArrayRegion(ids, result_size, 1, &request_id);
jobject object;
response.object->store(env, object);
env->SetObjectArrayElement(events, result_size, object);
env->DeleteLocalRef(object);
result_size++;
if (result_size == events_size) {
break;
}
response = manager->receive(0);
}
return result_size;
}
static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
jobject result;
td::ClientManager::execute(fetch_function(env, function))->store(env, result);
return result;
}
static jstring Object_toString(JNIEnv *env, jobject object) {
return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object)));
}
static jstring Function_toString(JNIEnv *env, jobject object) {
return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object)));
}
#endif
static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
static JavaVM *java_vm;
static jobject log_message_handler;
static void on_log_message(int verbosity_level, const char *log_message) {
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
if (env == nullptr) {
return;
}
jobject handler = env->NewLocalRef(log_message_handler);
if (!handler) {
return;
}
jclass handler_class = env->GetObjectClass(handler);
if (handler_class) {
jmethodID on_log_message_method = env->GetMethodID(handler_class, "onLogMessage", "(ILjava/lang/String;)V");
if (on_log_message_method) {
jstring log_message_str = td::jni::to_jstring(env.get(), log_message);
if (log_message_str) {
env->CallVoidMethod(handler, on_log_message_method, static_cast<jint>(verbosity_level), log_message_str);
env->DeleteLocalRef((jobject)log_message_str);
}
}
env->DeleteLocalRef((jobject)handler_class);
}
env->DeleteLocalRef(handler);
}
static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level,
jobject new_log_message_handler) {
if (log_message_handler) {
#ifdef TD_JSON_JAVA
td_set_log_message_callback(0, nullptr);
#else
td::ClientManager::set_log_message_callback(0, nullptr);
#endif
jobject old_log_message_handler = log_message_handler;
log_message_handler = jobject();
env->DeleteGlobalRef(old_log_message_handler);
}
if (new_log_message_handler) {
log_message_handler = env->NewGlobalRef(new_log_message_handler);
if (!log_message_handler) {
// out of memory
return;
}
#ifdef TD_JSON_JAVA
td_set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
#else
td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
#endif
}
}
static jint register_native(JavaVM *vm) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JAVA_VERSION) != JNI_OK) {
return -1;
}
java_vm = vm;
auto register_method = [env](jclass clazz, std::string name, std::string signature, auto function_ptr) {
td::jni::register_native_method(env, clazz, std::move(name), std::move(signature),
reinterpret_cast<void *>(function_ptr));
};
#ifdef TD_JSON_JAVA
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/JsonClient");
register_method(client_class, "createClientId", "()I", JsonClient_createClientId);
register_method(client_class, "send", "(ILjava/lang/String;)V", JsonClient_send);
register_method(client_class, "receive", "(D)Ljava/lang/String;", JsonClient_receive);
register_method(client_class, "execute", "(Ljava/lang/String;)Ljava/lang/String;", JsonClient_execute);
register_method(client_class, "setLogMessageHandler", "(IL" PACKAGE_NAME "/JsonClient$LogMessageHandler;)V",
Client_nativeClientSetLogMessageHandler);
#else
auto td_api_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi");
jfieldID commit_hash_field_id =
td::jni::get_static_field_id(env, td_api_class, "GIT_COMMIT_HASH", "Ljava/lang/String;");
std::string td_api_version = td::jni::fetch_static_string(env, td_api_class, commit_hash_field_id);
std::string tdjni_version = td::td_api::get_git_commit_hash();
if (tdjni_version != td_api_version) {
td::jni::set_fatal_error(
env, "Mismatched TdApi.java (" + td_api_version + ") and tdjni shared library (" + tdjni_version + ") versions");
return JAVA_VERSION;
}
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client");
auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object");
auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function");
#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
register_method(client_class, "createNativeClient", "()I", Client_createNativeClient);
register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend);
register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute);
register_method(client_class, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V",
Client_nativeClientSetLogMessageHandler);
register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString);
register_method(function_class, "toString", "()Ljava/lang/String;", Function_toString);
#undef TD_FUNCTION
#undef TD_OBJECT
td::jni::init_vars(env, PACKAGE_NAME);
td::td_api::get_package_name_ref() = PACKAGE_NAME;
#endif
return JAVA_VERSION;
}
} // namespace td_jni
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
static jint jni_version = td_jni::register_native(vm); // call_once
return jni_version;
}