This commit is contained in:
Andrea Cavalli 2021-10-24 01:03:16 +02:00
parent d79cf305cb
commit 4f3c8cb6b7
12 changed files with 224 additions and 182 deletions

View File

@ -26,7 +26,7 @@
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlight-java</artifactId>
<version>2.7.8.38</version>
<version>2.7.8.39</version>
</dependency>
<!-- TDLight natives -->

View File

@ -43,7 +43,7 @@ public final class Example {
client = new SimpleTelegramClient(settings);
// Configure the authentication info
var authenticationData = AuthenticationData.consoleLogin().askAuthData();
var authenticationData = AuthenticationData.consoleLogin();
// Add an example update handler that prints when the bot is started
client.addUpdateHandler(TdApi.UpdateAuthorizationState.class, Example::onUpdateAuthorizationState);

View File

@ -1,6 +1,8 @@
package it.tdlight.client;
import java.util.function.Consumer;
public interface Authenticable {
AuthenticationData getAuthenticationData();
void getAuthenticationData(Consumer<AuthenticationData> result);
}

View File

@ -3,7 +3,6 @@ package it.tdlight.client;
import it.tdlight.common.ExceptionHandler;
import it.tdlight.common.TelegramClient;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateWaitPhoneNumber;
import it.tdlight.jni.TdApi.PhoneNumberAuthenticationSettings;
import it.tdlight.jni.TdApi.SetAuthenticationPhoneNumber;
import it.tdlight.jni.TdApi.UpdateAuthorizationState;
@ -25,39 +24,36 @@ final class AuthorizationStateWaitAuthenticationDataHandler implements GenericUp
@Override
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR) {
AuthenticationData authenticationData = authenticable.getAuthenticationData();
authenticable.getAuthenticationData(this::onAuthData);
}
}
// Ask login parameters
if (authenticationData instanceof ConsoleInteractiveAuthenticationData) {
((ConsoleInteractiveAuthenticationData) authenticationData).askData();
}
public void onAuthData(AuthenticationData authenticationData) {
if (authenticationData.isBot()) {
String botToken = authenticationData.getBotToken();
TdApi.CheckAuthenticationBotToken response = new TdApi.CheckAuthenticationBotToken(botToken);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
} else if (authenticationData.isQrCode()) {
TdApi.RequestQrCodeAuthentication response = new TdApi.RequestQrCodeAuthentication();
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
} else {
PhoneNumberAuthenticationSettings phoneSettings = new PhoneNumberAuthenticationSettings(false, false, false);
if (authenticationData.isBot()) {
String botToken = authenticationData.getBotToken();
TdApi.CheckAuthenticationBotToken response = new TdApi.CheckAuthenticationBotToken(botToken);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
} else if (authenticationData.isQrCode()) {
TdApi.RequestQrCodeAuthentication response = new TdApi.RequestQrCodeAuthentication();
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
} else {
PhoneNumberAuthenticationSettings phoneSettings = new PhoneNumberAuthenticationSettings(false, false, false);
String phoneNumber = String.valueOf(authenticationData.getUserPhoneNumber());
SetAuthenticationPhoneNumber response = new SetAuthenticationPhoneNumber(phoneNumber, phoneSettings);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
}
String phoneNumber = String.valueOf(authenticationData.getUserPhoneNumber());
SetAuthenticationPhoneNumber response = new SetAuthenticationPhoneNumber(phoneNumber, phoneSettings);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
}
}
}

View File

@ -30,13 +30,14 @@ final class AuthorizationStateWaitCodeHandler implements GenericUpdateHandler<Up
authorizationState.codeInfo.timeout,
authorizationState.codeInfo.type
);
String code = clientInteraction.onParameterRequest(InputParameter.ASK_CODE, parameterInfo);
CheckAuthenticationCode response = new CheckAuthenticationCode(code);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
clientInteraction.onParameterRequest(InputParameter.ASK_CODE, parameterInfo, code -> {
CheckAuthenticationCode response = new CheckAuthenticationCode(code);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
});
}
}
}

View File

@ -17,7 +17,9 @@ final class AuthorizationStateWaitOtherDeviceConfirmationHandler implements
if (update.authorizationState.getConstructor() == AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR) {
AuthorizationStateWaitOtherDeviceConfirmation authorizationState = (AuthorizationStateWaitOtherDeviceConfirmation) update.authorizationState;
ParameterInfo parameterInfo = new ParameterInfoNotifyLink(authorizationState.link);
clientInteraction.onParameterRequest(InputParameter.NOTIFY_LINK, parameterInfo);
clientInteraction.onParameterRequest(InputParameter.NOTIFY_LINK, parameterInfo, ignored -> {
});
}
}
}

View File

@ -29,13 +29,14 @@ final class AuthorizationStateWaitPasswordHandler implements GenericUpdateHandle
authorizationState.hasRecoveryEmailAddress,
authorizationState.recoveryEmailAddressPattern
);
String password = clientInteraction.onParameterRequest(InputParameter.ASK_PASSWORD, parameterInfo);
CheckAuthenticationPassword response = new CheckAuthenticationPassword(password);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
clientInteraction.onParameterRequest(InputParameter.ASK_PASSWORD, parameterInfo, password -> {
CheckAuthenticationPassword response = new CheckAuthenticationPassword(password);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
});
}
}
}

View File

@ -25,33 +25,35 @@ final class AuthorizationStateWaitRegistrationHandler implements GenericUpdateHa
public void onUpdate(UpdateAuthorizationState update) {
if (update.authorizationState.getConstructor() == AuthorizationStateWaitRegistration.CONSTRUCTOR) {
TdApi.AuthorizationStateWaitRegistration authorizationState = (TdApi.AuthorizationStateWaitRegistration) update.authorizationState;
clientInteraction.onParameterRequest(InputParameter.TERMS_OF_SERVICE,
new ParameterInfoTermsOfService(authorizationState.termsOfService)
);
String firstName = clientInteraction.onParameterRequest(InputParameter.ASK_FIRST_NAME, new EmptyParameterInfo());
String lastName = clientInteraction.onParameterRequest(InputParameter.ASK_LAST_NAME, new EmptyParameterInfo());
if (firstName == null || firstName.isEmpty()) {
exceptionHandler.onException(new IllegalArgumentException("First name must not be null or empty"));
return;
}
if (firstName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("First name must be under 64 characters"));
return;
}
if (lastName == null) {
exceptionHandler.onException(new IllegalArgumentException("Last name must not be null"));
return;
}
if (lastName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("Last name must be under 64 characters"));
return;
}
RegisterUser response = new RegisterUser(firstName, lastName);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
ParameterInfoTermsOfService tos = new ParameterInfoTermsOfService(authorizationState.termsOfService);
clientInteraction.onParameterRequest(InputParameter.TERMS_OF_SERVICE, tos, ignored -> {
clientInteraction.onParameterRequest(InputParameter.ASK_FIRST_NAME, new EmptyParameterInfo(), firstName -> {
clientInteraction.onParameterRequest(InputParameter.ASK_LAST_NAME, new EmptyParameterInfo(), lastName -> {
if (firstName == null || firstName.isEmpty()) {
exceptionHandler.onException(new IllegalArgumentException("First name must not be null or empty"));
return;
}
if (firstName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("First name must be under 64 characters"));
return;
}
if (lastName == null) {
exceptionHandler.onException(new IllegalArgumentException("Last name must not be null"));
return;
}
if (lastName.length() > 64) {
exceptionHandler.onException(new IllegalArgumentException("Last name must be under 64 characters"));
return;
}
RegisterUser response = new RegisterUser(firstName, lastName);
client.send(response, ok -> {
if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) {
throw new TelegramError((TdApi.Error) ok);
}
}, exceptionHandler);
});
});
});
}
}
}

View File

@ -1,6 +1,8 @@
package it.tdlight.client;
import java.util.function.Consumer;
public interface ClientInteraction {
String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo);
void onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo, Consumer<String> result);
}

View File

@ -55,11 +55,6 @@ public final class ConsoleInteractiveAuthenticationData implements Authenticatio
return botToken;
}
public ConsoleInteractiveAuthenticationData askAuthData() {
initializeIfNeeded();
return this;
}
private void initializeIfNeeded() {
if (initialized) {
return;

View File

@ -2,106 +2,115 @@ package it.tdlight.client;
import it.tdlight.common.utils.ScannerUtils;
import it.tdlight.jni.TdApi.TermsOfService;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
final class ScannerClientInteraction implements ClientInteraction {
private final ExecutorService blockingExecutor;
private final Authenticable authenticable;
public ScannerClientInteraction(Authenticable authenticable) {
public ScannerClientInteraction(ExecutorService blockingExecutor, Authenticable authenticable) {
this.blockingExecutor = blockingExecutor;
this.authenticable = authenticable;
}
@Override
public String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) {
AuthenticationData authenticationData = authenticable.getAuthenticationData();
String who;
boolean useRealWho;
if (authenticationData instanceof ConsoleInteractiveAuthenticationData) {
useRealWho = ((ConsoleInteractiveAuthenticationData) authenticationData).isInitialized();
} else {
useRealWho = true;
}
if (!useRealWho) {
who = "login";
} else if (authenticationData.isQrCode()) {
who = "QR login";
} else if (authenticationData.isBot()) {
who = authenticationData.getBotToken().split(":", 2)[0];
} else {
who = "+" + authenticationData.getUserPhoneNumber();
}
String question;
boolean trim = false;
switch (parameter) {
case ASK_FIRST_NAME:
question = "Enter first name";
trim = true;
break;
case ASK_LAST_NAME:
question = "Enter last name";
trim = true;
break;
case ASK_CODE:
question = "Enter authentication code";
ParameterInfoCode codeInfo = ((ParameterInfoCode) parameterInfo);
question += "\n\tPhone number: " + codeInfo.getPhoneNumber();
question += "\n\tTimeout: " + codeInfo.getTimeout() + " seconds";
question +=
"\n\tCode type: " + codeInfo.getType().getClass().getSimpleName().replace("AuthenticationCodeType", "");
if (codeInfo.getNextType() != null) {
question += "\n\tNext code type: " + codeInfo
.getNextType()
.getClass()
.getSimpleName()
.replace("AuthenticationCodeType", "");
}
trim = true;
break;
case ASK_PASSWORD:
question = "Enter your password";
String passwordMessage = "Password authorization:";
String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint();
if (hint != null && !hint.isEmpty()) {
passwordMessage += "\n\tHint: " + hint;
}
boolean hasRecoveryEmailAddress = ((ParameterInfoPasswordHint) parameterInfo).hasRecoveryEmailAddress();
passwordMessage += "\n\tHas recovery email: " + hasRecoveryEmailAddress;
String recoveryEmailAddressPattern = ((ParameterInfoPasswordHint) parameterInfo).getRecoveryEmailAddressPattern();
if (recoveryEmailAddressPattern != null && !recoveryEmailAddressPattern.isEmpty()) {
passwordMessage += "\n\tRecovery email address pattern: " + recoveryEmailAddressPattern;
}
System.out.println(passwordMessage);
break;
case NOTIFY_LINK:
String link = ((ParameterInfoNotifyLink) parameterInfo).getLink();
System.out.println("Please confirm this login link on another device: " + link);
System.out.println();
System.out.println(QrCodeTerminal.getQr(link));
System.out.println();
return "";
case TERMS_OF_SERVICE:
TermsOfService tos = ((ParameterInfoTermsOfService) parameterInfo).getTermsOfService();
question = "Terms of service:\n\t" + tos.text.text;
if (tos.minUserAge > 0) {
question += "\n\tMinimum user age: " + tos.minUserAge;
}
if (tos.showPopup) {
question += "\nPlease press enter.";
trim = true;
public void onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo, Consumer<String> resultCons) {
authenticable.getAuthenticationData(authenticationData -> {
blockingExecutor.execute(() -> {
String who;
boolean useRealWho;
if (authenticationData instanceof ConsoleInteractiveAuthenticationData) {
useRealWho = ((ConsoleInteractiveAuthenticationData) authenticationData).isInitialized();
} else {
System.out.println(question);
return "";
useRealWho = true;
}
break;
default:
question = parameter.toString();
break;
}
String result = ScannerUtils.askParameter(who, question);
if (trim) {
return result.trim();
} else {
return result;
}
if (!useRealWho) {
who = "login";
} else if (authenticationData.isQrCode()) {
who = "QR login";
} else if (authenticationData.isBot()) {
who = authenticationData.getBotToken().split(":", 2)[0];
} else {
who = "+" + authenticationData.getUserPhoneNumber();
}
String question;
boolean trim = false;
switch (parameter) {
case ASK_FIRST_NAME:
question = "Enter first name";
trim = true;
break;
case ASK_LAST_NAME:
question = "Enter last name";
trim = true;
break;
case ASK_CODE:
question = "Enter authentication code";
ParameterInfoCode codeInfo = ((ParameterInfoCode) parameterInfo);
question += "\n\tPhone number: " + codeInfo.getPhoneNumber();
question += "\n\tTimeout: " + codeInfo.getTimeout() + " seconds";
question += "\n\tCode type: " + codeInfo.getType().getClass().getSimpleName()
.replace("AuthenticationCodeType", "");
if (codeInfo.getNextType() != null) {
question += "\n\tNext code type: " + codeInfo
.getNextType()
.getClass()
.getSimpleName()
.replace("AuthenticationCodeType", "");
}
trim = true;
break;
case ASK_PASSWORD:
question = "Enter your password";
String passwordMessage = "Password authorization:";
String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint();
if (hint != null && !hint.isEmpty()) {
passwordMessage += "\n\tHint: " + hint;
}
boolean hasRecoveryEmailAddress = ((ParameterInfoPasswordHint) parameterInfo)
.hasRecoveryEmailAddress();
passwordMessage += "\n\tHas recovery email: " + hasRecoveryEmailAddress;
String recoveryEmailAddressPattern = ((ParameterInfoPasswordHint) parameterInfo)
.getRecoveryEmailAddressPattern();
if (recoveryEmailAddressPattern != null && !recoveryEmailAddressPattern.isEmpty()) {
passwordMessage += "\n\tRecovery email address pattern: " + recoveryEmailAddressPattern;
}
System.out.println(passwordMessage);
break;
case NOTIFY_LINK:
String link = ((ParameterInfoNotifyLink) parameterInfo).getLink();
System.out.println("Please confirm this login link on another device: " + link);
System.out.println();
System.out.println(QrCodeTerminal.getQr(link));
System.out.println();
resultCons.accept("");
case TERMS_OF_SERVICE:
TermsOfService tos = ((ParameterInfoTermsOfService) parameterInfo).getTermsOfService();
question = "Terms of service:\n\t" + tos.text.text;
if (tos.minUserAge > 0) {
question += "\n\tMinimum user age: " + tos.minUserAge;
}
if (tos.showPopup) {
question += "\nPlease press enter.";
trim = true;
} else {
System.out.println(question);
resultCons.accept("");
}
break;
default:
question = parameter.toString();
break;
}
String result = ScannerUtils.askParameter(who, question);
if (trim) {
resultCons.accept(result.trim());
} else {
resultCons.accept(result);
}
});
});
}
}

View File

@ -19,7 +19,13 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -27,6 +33,7 @@ import org.slf4j.LoggerFactory;
public final class SimpleTelegramClient implements Authenticable {
public static final Logger LOG = LoggerFactory.getLogger(SimpleTelegramClient.class);
public static ExecutorService blockingExecutor = Executors.newSingleThreadExecutor();
static {
try {
@ -37,7 +44,7 @@ public final class SimpleTelegramClient implements Authenticable {
}
private final TelegramClient client;
private ClientInteraction clientInteraction = new ScannerClientInteraction(this);
private ClientInteraction clientInteraction = new ScannerClientInteraction(blockingExecutor, this);
private final TDLibSettings settings;
private AuthenticationData authenticationData;
@ -65,22 +72,22 @@ public final class SimpleTelegramClient implements Authenticable {
);
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitRegistrationHandler(client,
new SimpleTelegramClientInteraction(),
new SimpleTelegramClientInteraction(blockingExecutor),
this::handleDefaultException
)
);
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitPasswordHandler(client,
new SimpleTelegramClientInteraction(),
new SimpleTelegramClientInteraction(blockingExecutor),
this::handleDefaultException
)
);
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitOtherDeviceConfirmationHandler(new SimpleTelegramClientInteraction())
new AuthorizationStateWaitOtherDeviceConfirmationHandler(new SimpleTelegramClientInteraction(blockingExecutor))
);
this.addUpdateHandler(TdApi.UpdateAuthorizationState.class,
new AuthorizationStateWaitCodeHandler(client,
new SimpleTelegramClientInteraction(),
new SimpleTelegramClientInteraction(blockingExecutor),
this::handleDefaultException
)
);
@ -128,8 +135,22 @@ public final class SimpleTelegramClient implements Authenticable {
}
@Override
public AuthenticationData getAuthenticationData() {
return authenticationData;
public void getAuthenticationData(Consumer<AuthenticationData> result) {
if (authenticationData instanceof ConsoleInteractiveAuthenticationData) {
ConsoleInteractiveAuthenticationData consoleInteractiveAuthenticationData
= (ConsoleInteractiveAuthenticationData) authenticationData;
try {
blockingExecutor.execute(() -> {
consoleInteractiveAuthenticationData.askData();
result.accept(consoleInteractiveAuthenticationData);
});
} catch (RejectedExecutionException | NullPointerException ex) {
LOG.error("Failed to execute askData. Returning an empty string", ex);
result.accept(consoleInteractiveAuthenticationData);
}
} else {
result.accept(authenticationData);
}
}
public void setClientInteraction(ClientInteraction clientInteraction) {
@ -259,9 +280,20 @@ public final class SimpleTelegramClient implements Authenticable {
private final class SimpleTelegramClientInteraction implements ClientInteraction {
private final ExecutorService blockingExecutor;
public SimpleTelegramClientInteraction(ExecutorService blockingExecutor) {
this.blockingExecutor = blockingExecutor;
}
@Override
public String onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) {
return clientInteraction.onParameterRequest(parameter, parameterInfo);
public void onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo, Consumer<String> result) {
try {
blockingExecutor.execute(() -> clientInteraction.onParameterRequest(parameter, parameterInfo, result));
} catch (RejectedExecutionException | NullPointerException ex) {
LOG.error("Failed to execute onParameterRequest. Returning an empty string", ex);
result.accept("");
}
}
}
}