tdlight-java/tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClient.java

316 lines
11 KiB
Java
Raw Normal View History

2021-09-27 19:27:13 +02:00
package it.tdlight.client;
2023-04-27 02:14:39 +02:00
import it.tdlight.ClientFactory;
import it.tdlight.ExceptionHandler;
import it.tdlight.Init;
import it.tdlight.ResultHandler;
import it.tdlight.TelegramClient;
import it.tdlight.jni.TdApi.Update;
2023-05-10 10:12:43 +02:00
import it.tdlight.util.UnsupportedNativeLibraryException;
2021-09-27 19:27:13 +02:00
import it.tdlight.jni.TdApi;
2022-07-12 17:53:52 +02:00
import it.tdlight.jni.TdApi.ChatListArchive;
import it.tdlight.jni.TdApi.ChatListMain;
2021-09-27 19:27:13 +02:00
import it.tdlight.jni.TdApi.Function;
import it.tdlight.jni.TdApi.User;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.util.Map;
2023-03-12 15:07:55 +01:00
import java.util.Objects;
2021-09-27 19:27:13 +02:00
import java.util.Set;
2023-04-27 02:14:39 +02:00
import java.util.concurrent.CompletableFuture;
2021-09-27 19:27:13 +02:00
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
2021-10-24 01:03:16 +02:00
import java.util.concurrent.RejectedExecutionException;
2021-09-27 19:27:13 +02:00
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("unused")
2023-04-27 02:14:39 +02:00
public final class SimpleTelegramClient implements Authenticable, MutableTelegramClient {
2021-09-27 19:27:13 +02:00
public static final Logger LOG = LoggerFactory.getLogger(SimpleTelegramClient.class);
2021-09-27 19:27:13 +02:00
static {
try {
2023-05-10 10:12:43 +02:00
Init.init();
} catch (UnsupportedNativeLibraryException e) {
2021-09-27 19:27:13 +02:00
throw new RuntimeException("Can't load native libraries", e);
}
}
private final TelegramClient client;
2023-04-27 02:14:39 +02:00
private ClientInteraction clientInteraction;
2021-09-27 19:27:13 +02:00
private final TDLibSettings settings;
2023-04-27 02:14:39 +02:00
private AuthenticationSupplier<?> authenticationData;
2021-09-27 19:27:13 +02:00
private final Map<String, Set<CommandHandler>> commandHandlers = new ConcurrentHashMap<>();
2021-10-22 12:54:28 +02:00
private final Set<ResultHandler<TdApi.Update>> updateHandlers = new ConcurrentHashMap<ResultHandler<TdApi.Update>, Object>().keySet(
new Object());
private final Set<ExceptionHandler> updateExceptionHandlers = new ConcurrentHashMap<ExceptionHandler, Object>().keySet(
new Object());
private final Set<ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<ExceptionHandler, Object>().keySet(
new Object());
2021-09-27 19:27:13 +02:00
2022-07-12 17:53:52 +02:00
private final AuthorizationStateReadyGetMe meGetter;
private final AuthorizationStateReadyLoadChats mainChatsLoader;
private final AuthorizationStateReadyLoadChats archivedChatsLoader;
2021-09-27 19:27:13 +02:00
private final CountDownLatch closed = new CountDownLatch(1);
2023-04-27 02:14:39 +02:00
SimpleTelegramClient(ClientFactory clientFactory,
TDLibSettings settings,
Map<String, Set<CommandHandler>> commandHandlers,
Set<ResultHandler<Update>> updateHandlers,
Set<ExceptionHandler> updateExceptionHandlers,
Set<ExceptionHandler> defaultExceptionHandlers,
ClientInteraction clientInteraction) {
this.client = clientFactory.createClient();
this.settings = Objects.requireNonNull(settings, "TDLight client settings are null");
this.commandHandlers.putAll(commandHandlers);
this.updateHandlers.addAll(updateHandlers);
this.updateExceptionHandlers.addAll(updateExceptionHandlers);
this.defaultExceptionHandlers.addAll(defaultExceptionHandlers);
if (clientInteraction != null) {
this.clientInteraction = clientInteraction;
} else {
this.clientInteraction = new ScannerClientInteraction(SequentialRequestsExecutor.getInstance(), this);
}
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2021-10-22 12:54:28 +02:00
new AuthorizationStateWaitTdlibParametersHandler(client, settings, this::handleDefaultException)
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2021-10-22 12:54:28 +02:00
new AuthorizationStateWaitAuthenticationDataHandler(client, this, this::handleDefaultException)
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2021-10-22 12:54:28 +02:00
new AuthorizationStateWaitRegistrationHandler(client,
2023-04-27 02:14:39 +02:00
new SimpleTelegramClientInteraction(),
2021-10-22 12:54:28 +02:00
this::handleDefaultException
)
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2021-10-22 12:54:28 +02:00
new AuthorizationStateWaitPasswordHandler(client,
2023-04-27 02:14:39 +02:00
new SimpleTelegramClientInteraction(),
2021-10-22 12:54:28 +02:00
this::handleDefaultException
)
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2023-04-27 02:14:39 +02:00
new AuthorizationStateWaitOtherDeviceConfirmationHandler(new SimpleTelegramClientInteraction(),
this::handleDefaultException
)
2021-10-22 12:54:28 +02:00
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
2021-10-22 12:54:28 +02:00
new AuthorizationStateWaitCodeHandler(client,
2023-04-27 02:14:39 +02:00
new SimpleTelegramClientInteraction(),
2021-10-22 12:54:28 +02:00
this::handleDefaultException
)
);
2021-09-27 19:27:13 +02:00
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class, new AuthorizationStateWaitForExit(this.closed));
2022-07-12 17:53:52 +02:00
this.mainChatsLoader = new AuthorizationStateReadyLoadChats(client, new ChatListMain());
this.archivedChatsLoader = new AuthorizationStateReadyLoadChats(client, new ChatListArchive());
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
this.meGetter = new AuthorizationStateReadyGetMe(client, mainChatsLoader, archivedChatsLoader));
this.addUpdateHandler(TdApi.UpdateNewMessage.class, new CommandsHandler(client, this.commandHandlers, this::getMe));
2021-09-27 19:27:13 +02:00
}
private void handleUpdate(TdApi.Object update) {
boolean handled = false;
for (ResultHandler<TdApi.Update> updateHandler : updateHandlers) {
2021-09-27 19:27:13 +02:00
updateHandler.onResult(update);
handled = true;
}
if (!handled) {
LOG.warn("An update was not handled, please use addUpdateHandler(handler) before starting the client!");
2021-09-27 19:27:13 +02:00
}
}
private void handleUpdateException(Throwable ex) {
boolean handled = false;
for (ExceptionHandler updateExceptionHandler : updateExceptionHandlers) {
updateExceptionHandler.onException(ex);
handled = true;
}
if (!handled) {
LOG.warn("Error received from Telegram!", ex);
2021-09-27 19:27:13 +02:00
}
}
private void handleDefaultException(Throwable ex) {
boolean handled = false;
for (ExceptionHandler exceptionHandler : defaultExceptionHandlers) {
exceptionHandler.onException(ex);
handled = true;
}
if (!handled) {
LOG.warn("Unhandled exception!", ex);
2021-09-27 19:27:13 +02:00
}
}
private void handleResultHandlingException(Throwable ex) {
LOG.error("Failed to handle the request result", ex);
}
2021-09-27 19:27:13 +02:00
@Override
2023-04-27 02:14:39 +02:00
public AuthenticationSupplier<?> getAuthenticationSupplier() {
return authenticationData;
2021-09-27 19:27:13 +02:00
}
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public void setClientInteraction(ClientInteraction clientInteraction) {
this.clientInteraction = clientInteraction;
}
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public <T extends TdApi.Update> void addCommandHandler(String commandName, CommandHandler handler) {
2021-10-22 12:54:28 +02:00
Set<CommandHandler> handlers = this.commandHandlers.computeIfAbsent(commandName,
k -> new ConcurrentHashMap<CommandHandler, Object>().keySet(new Object())
);
2021-09-27 19:27:13 +02:00
handlers.add(handler);
}
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public <T extends TdApi.Update> void addUpdateHandler(Class<T> updateType, GenericUpdateHandler<T> handler) {
2023-04-27 02:14:39 +02:00
this.updateHandlers.add(new SimpleResultHandler<>(updateType, handler));
2021-09-27 19:27:13 +02:00
}
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public void addUpdatesHandler(GenericUpdateHandler<TdApi.Update> handler) {
2023-04-27 02:14:39 +02:00
this.updateHandlers.add(new SimpleUpdateHandler(handler, LOG));
2021-09-27 19:27:13 +02:00
}
/**
* Optional handler to handle errors received from TDLib
*/
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public void addUpdateExceptionHandler(ExceptionHandler updateExceptionHandler) {
this.updateExceptionHandlers.add(updateExceptionHandler);
}
/**
* Optional handler to handle uncaught errors (when using send without an appropriate error handler)
*/
2023-04-27 02:14:39 +02:00
@Override
2021-09-27 19:27:13 +02:00
public void addDefaultExceptionHandler(ExceptionHandler defaultExceptionHandlers) {
this.defaultExceptionHandlers.add(defaultExceptionHandlers);
}
/**
* Start the client
*/
2023-04-27 02:14:39 +02:00
public void start(AuthenticationSupplier<?> authenticationData) {
2021-09-27 19:27:13 +02:00
this.authenticationData = authenticationData;
createDirectories();
client.initialize(this::handleUpdate, this::handleUpdateException, this::handleDefaultException);
}
private void createDirectories() {
try {
if (Files.notExists(settings.getDatabaseDirectoryPath())) {
Files.createDirectories(settings.getDatabaseDirectoryPath());
}
if (Files.notExists(settings.getDownloadedFilesDirectoryPath())) {
Files.createDirectories(settings.getDownloadedFilesDirectoryPath());
}
} catch (IOException ex) {
throw new UncheckedIOException("Can't create TDLight directories", ex);
}
}
/**
2023-03-12 15:07:55 +01:00
* Sends a request to TDLib and get the result.
*
* @param function The request to TDLib.
* @param resultHandler Result handler. If it is null, nothing will be called.
* @throws NullPointerException if function is null.
2021-09-27 19:27:13 +02:00
*/
public <R extends TdApi.Object> void send(TdApi.Function<R> function, GenericResultHandler<R> resultHandler) {
2023-03-12 15:10:38 +01:00
client.send(function, result -> resultHandler.onResult(Result.of(result)), this::handleResultHandlingException);
2023-03-12 15:07:55 +01:00
}
/**
* Sends a request to TDLib and get the result.
*
* @param function The request to TDLib.
* @param resultHandler Result handler. If it is null, nothing will be called.
* @param resultHandlerExceptionHandler Handle exceptions thrown inside the result handler.
* If it is null, the default exception handler will be called.
* @throws NullPointerException if function is null.
*/
public <R extends TdApi.Object> void send(TdApi.Function<R> function, GenericResultHandler<R> resultHandler,
ExceptionHandler resultHandlerExceptionHandler) {
2023-03-12 15:10:38 +01:00
if (resultHandlerExceptionHandler == null) {
resultHandlerExceptionHandler = this::handleResultHandlingException;
}
client.send(function, result -> resultHandler.onResult(Result.of(result)), resultHandlerExceptionHandler);
2021-09-27 19:27:13 +02:00
}
/**
* Execute a synchronous function.
* <strong>Please note that only some functions can be executed using this method.</strong>
* If you want to execute a function please use {@link #send(Function, GenericResultHandler)}!
*/
public <R extends TdApi.Object> Result<R> execute(TdApi.Function<R> function) {
2021-09-27 19:27:13 +02:00
return Result.of(client.execute(function));
}
/**
* Send the close signal but don't wait
*/
public void sendClose() {
2021-09-27 20:22:57 +02:00
client.send(new TdApi.Close(), ok -> {
2021-10-22 13:33:03 +02:00
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
2021-09-27 20:22:57 +02:00
}
});
2021-09-27 19:27:13 +02:00
}
/**
* Send the close signal and wait for exit
*/
public void closeAndWait() throws InterruptedException {
2021-09-27 20:22:57 +02:00
client.send(new TdApi.Close(), ok -> {
2021-10-22 13:33:03 +02:00
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
2021-09-27 20:22:57 +02:00
}
});
2021-09-27 19:27:13 +02:00
this.waitForExit();
}
/**
* Wait until TDLight is closed
*/
public void waitForExit() throws InterruptedException {
closed.await();
}
private final class SimpleTelegramClientInteraction implements ClientInteraction {
2023-04-27 02:14:39 +02:00
public SimpleTelegramClientInteraction() {
2021-10-24 01:03:16 +02:00
}
2021-09-27 19:27:13 +02:00
@Override
2023-04-27 02:14:39 +02:00
public CompletableFuture<String> onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) {
2021-10-24 01:03:16 +02:00
try {
2023-04-27 02:14:39 +02:00
return clientInteraction.onParameterRequest(parameter, parameterInfo);
2021-10-24 01:03:16 +02:00
} catch (RejectedExecutionException | NullPointerException ex) {
LOG.error("Failed to execute onParameterRequest. Returning an empty string", ex);
2023-04-27 02:14:39 +02:00
return CompletableFuture.completedFuture("");
2021-10-24 01:03:16 +02:00
}
2021-09-27 19:27:13 +02:00
}
}
2022-07-12 17:53:52 +02:00
public User getMe() {
return meGetter.getMe();
}
public boolean isMainChatsListLoaded() {
return mainChatsLoader.isLoaded();
}
public boolean isArchivedChatsListLoaded() {
return archivedChatsLoader.isLoaded();
}
2021-09-27 19:27:13 +02:00
}