TransferBot/src/main/java/it/cavallium/PrimaryController.java

381 lines
12 KiB
Java

package it.cavallium;
import static it.cavallium.StaticSettings.REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP;
import static it.cavallium.StaticSettings.requiresAdminPrivilegesOnSourceSupergroup;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.function.Function;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Dialog;
import javafx.scene.control.Label;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.TextInputDialog;
import javafx.scene.input.InputMethodEvent;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import org.jetbrains.annotations.NotNull;
import org.slf4j.event.Level;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class PrimaryController {
public Label statusTxt;
public ProgressBar statusBar;
public Label statusPercentage;
public TableView<UserStatus> statusTable;
@FXML private Pane main;
@FXML private TextField phoneNumber;
@FXML private ListView<Text> userbotsList;
@FXML private ChoiceBox<BaseChatInfo> sourceGroupCombo;
@FXML private ChoiceBox<BaseChatInfo> destGroupCombo;
@FXML private ListView<LogEntry> log;
@FXML
protected void initialize() {
for (PhoneNumber number : App.getTransferService().getPhoneNumbers()) {
userbotsList
.getItems()
.add(0,
new Text(getUserbotPrettyPrintPhoneNumber(number))
);
}
var srcItems = sourceGroupCombo.getItems();
srcItems.clear();
for (BaseChatInfo originSupergroups : App.getTransferService().getAdminSupergroups(REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP, false)) {
srcItems.add(originSupergroups);
}
var destItems = destGroupCombo.getItems();
destItems.clear();
for (BaseChatInfo destSupergroups : App.getTransferService().getAdminSupergroups(false, true)) {
destItems.add(destSupergroups);
}
App
.getTransferService()
.subscribeAdminSupergroups()
.subscribeOn(Schedulers.single())
.subscribe((supergroupInfo) -> {
Platform.runLater(() -> {
var srcItems2 = sourceGroupCombo.getItems();
var destItems2 = destGroupCombo.getItems();
if (userbotsList.getItems().isEmpty()) {
srcItems2.clear();
destItems2.clear();
} else {
boolean added = false;
if (supergroupInfo.getItem().canRestrictMembers() || !REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP) {
if (!srcItems2.contains(supergroupInfo.getItem().getBaseChatInfo())) {
added |= srcItems2.add(supergroupInfo.getItem().getBaseChatInfo());
}
}
if (supergroupInfo.getItem().canInviteUsers()) {
if (!destItems2.contains(supergroupInfo.getItem().getBaseChatInfo())) {
added |= destItems2.add(supergroupInfo.getItem().getBaseChatInfo());
}
}
if (added) {
App.getLogService().append(Level.INFO, "Found group \"" + supergroupInfo.getItem().getBaseChatInfo().getTitle() + "\"");
}
}
});
});
App.getLogService().listenUpdates().subscribeOn(Schedulers.boundedElastic()).flatMap(logString -> MonoFxUtils.runLater(() -> {
if (log.getItems().size() >= App.getLogService().getMaxSize()) {
log.getItems().remove(0);
}
log.getItems().add(logString);
log.scrollTo(logString);
return Mono.empty();
})).subscribe();
}
private void disableClicks() {
main.setDisable(true);
}
private void enableClicks() {
main.setDisable(false);
}
@FXML
public void openSettings(ActionEvent actionEvent) throws IOException {
App.setRoot("settings");
}
@FXML
public void quit(ActionEvent actionEvent) {
Platform.exit();
}
@FXML
public void addUserbot(ActionEvent actionEvent) {
var eventSource = (Node) actionEvent.getSource();
var phoneNumberText = phoneNumber.getText();
try {
var phoneNumber = getUserbotPhoneNumber(phoneNumberText);
var phoneNumberPrettyText = getUserbotPrettyPrintPhoneNumber(phoneNumber);
disableClicks();
App.getTransferService().addUserbot(phoneNumber, (client) -> MonoFxUtils.runLater(() -> {
var authCodeAlert = new TextInputDialog();
authCodeAlert.setHeaderText("Insert authentication code that you received");
return PrimaryController.<Integer>loopAlert(authCodeAlert, (authCodeText) -> {
if (authCodeText.matches("^[0-9]{3,8}$")) {
try {
int authCode = Integer.parseUnsignedInt(authCodeText);
return Mono.just(authCode);
} catch (NumberFormatException ex) {
var alert = new Alert(AlertType.ERROR, "Can't parse authentication code number", ButtonType.CLOSE);
return MonoFxUtils.showAndWait(alert).then(Mono.empty());
}
} else {
var alert = new Alert(AlertType.ERROR, "Wrong authentication code number format", ButtonType.CLOSE);
return MonoFxUtils.showAndWait(alert).then(Mono.empty());
}
});
}), (client, hint) -> MonoFxUtils.runLater(() -> {
String hintText = "";
if (hint != null && !hint.isBlank()) {
hintText = " (hint: " + hint + ")";
}
var otpAlert = new TextInputDialog();
otpAlert.setHeaderText("Insert user password" + hintText + ":");
return loopAlert(otpAlert, (otpText) -> {
if (otpText.length() > 0) {
return Mono.just(otpText);
} else {
var alert = new Alert(AlertType.ERROR, "Wrong user password format", ButtonType.CLOSE);
alert.showAndWait();
return Mono.empty();
}
});
}), () -> {
// closed, remove from list
return MonoFxUtils.runLater(() -> {
userbotsList.getItems().removeIf(textBox -> {
try {
return getUserbotPhoneNumber(textBox.getText()).equals(phoneNumber);
} catch (NumberParseException e) {
// Can't happen
e.printStackTrace();
return false;
}
});
if (userbotsList.getItems().isEmpty()) {
this.sourceGroupCombo.getItems().clear();
this.destGroupCombo.getItems().clear();
}
return Mono.empty();
});
}).handle((result, sink) -> {
if (result.failed()) {
sink.error(new Exception(result.getErrorMessage()));
} else {
sink.next(result);
}
}).flatMap((_v) -> MonoFxUtils.runLater(() -> {
var alert = new Alert(AlertType.INFORMATION,
"Added userbot " + PhoneNumberUtil
.getInstance()
.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL),
ButtonType.CLOSE
);
return MonoFxUtils.showAndWait(alert);
})).doOnSuccess((_v) -> {
Platform.runLater(() -> {
userbotsList
.getItems()
.add(0,
new Text(phoneNumberPrettyText)
);
this.phoneNumber.setText("");
});
})
.doOnTerminate(this::enableClicks)
.subscribe(_v -> {}, error -> {
Platform.runLater(() -> {
var alert = new Alert(AlertType.ERROR,
"Error while adding the userbot " + PhoneNumberUtil
.getInstance()
.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL),
ButtonType.CLOSE
);
alert.setContentText(error.getLocalizedMessage());
MonoFxUtils.showAndWait(alert).subscribe();
enableClicks();
});
});
} catch (NumberFormatException | NumberParseException ex) {
var alert = new Alert(AlertType.ERROR, "Can't parse phone number format", ButtonType.CLOSE);
alert.showAndWait();
}
}
private String getUserbotPrettyPrintPhoneNumber(PhoneNumber phoneNumber) {
return PhoneNumberUtil
.getInstance()
.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL);
}
public static PhoneNumber getUserbotPhoneNumber(String phoneNumberText) throws NumberParseException {
return PhoneNumberUtil
.getInstance()
.parse(phoneNumberText, PhoneNumberUtil.getCountryMobileToken(1));
}
private static <T> Mono<T> loopAlert(TextInputDialog alert, Function<String, Mono<T>> resultParser) {
return MonoFxUtils.showAndWaitOpt(alert)
.flatMap(resultOpt -> {
if (resultOpt.isEmpty()) {
return Mono.just(Optional.<T>empty());
} else {
return resultParser.apply(resultOpt.get()).filter(Objects::nonNull).map(Optional::of);
}
})
.repeatWhen(s -> s.takeWhile(n -> n == 0))
.last(Optional.empty())
.flatMap(opt -> opt.map(Mono::just).orElseGet(Mono::empty));
}
@FXML
public void removeUserbot(ActionEvent actionEvent) {
var selectedItem = userbotsList.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
disableClicks();
try {
var phoneNumber = getUserbotPhoneNumber(selectedItem.getText());
App.getTransferService()
.closeUserbot(phoneNumber)
.subscribeOn(Schedulers.boundedElastic())
.then(MonoFxUtils.runLater(() -> {
// closed, remove from list
userbotsList.getItems().remove(selectedItem);
return Mono.empty();
}))
.doOnTerminate(this::enableClicks)
.subscribe();
} catch (NumberParseException e) {
e.printStackTrace();
}
}
}
@FXML
public void onSourceGroupAction(ActionEvent actionEvent) {
}
@FXML
public void onSourceGroupHiding(Event event) {
}
@FXML
public void onSourceGroupTextChanged(InputMethodEvent inputMethodEvent) {
}
@FXML
public void onSourceGroupShowing(Event event) {
}
@FXML
public void onDestGroupAction(ActionEvent actionEvent) {
}
@FXML
public void onDestGroupHiding(Event event) {
}
@FXML
public void onDestGroupTextChanged(InputMethodEvent inputMethodEvent) {
}
@FXML
public void onDestGroupShowing(Event event) {
}
@SuppressWarnings("ConstantConditions")
@FXML
public void onDoTransfer(ActionEvent actionEvent) {
if (sourceGroupCombo.getSelectionModel().getSelectedItem() == null
|| !(sourceGroupCombo.getSelectionModel().getSelectedItem() instanceof BaseChatInfo)) {
MonoFxUtils
.showAndWait(new Alert(AlertType.ERROR, "You must select a source group", ButtonType.CLOSE))
.subscribe();
return;
}
if (destGroupCombo.getSelectionModel().getSelectedItem() == null
|| !(destGroupCombo.getSelectionModel().getSelectedItem() instanceof BaseChatInfo)) {
MonoFxUtils
.showAndWait(new Alert(AlertType.ERROR, "You must select a destination group", ButtonType.CLOSE))
.subscribe();
return;
}
disableClicks();
statusTable.getItems().clear();
App
.getTransferService()
.transferMembers(sourceGroupCombo.getSelectionModel().getSelectedItem(),
destGroupCombo.getSelectionModel().getSelectedItem(),
userStatus -> MonoFxUtils.runLater(() -> {
if (statusTable.getItems().contains(userStatus)) {
statusTable.getItems().remove(userStatus);
}
statusTable.getItems().add(userStatus);
return Mono.empty();
}),
percentage -> MonoFxUtils.runLater(() -> {
statusPercentage.setText(percentage + "%");
statusBar.setProgress(((double) percentage / 100d));
return Mono.empty();
}),
phaseDescription -> MonoFxUtils.runLater(() -> {
App.getLogService().append(Level.INFO, phaseDescription);
statusTxt.setText(phaseDescription);
return Mono.empty();
})
)
.onErrorResume(error -> MonoFxUtils.runLater(() -> {
var alert = new Alert(AlertType.ERROR, "Error during transfer", ButtonType.CLOSE);
alert.setContentText(error.getLocalizedMessage());
return MonoFxUtils.showAndWait(alert);
}).then())
.doOnTerminate(() -> {
Platform.runLater(() -> {
statusTable.refresh();
statusTxt.setText("");
statusPercentage.setText("0%");
statusBar.setProgress(0d);
enableClicks();
});
})
.subscribe(_v -> {
}, _v -> {
}, () -> {});
}
}