This commit is contained in:
Andrea Cavalli 2020-10-20 02:14:04 +02:00
parent 6bf3e55358
commit a5b06b8d47
7 changed files with 162 additions and 61 deletions

View File

@ -362,6 +362,7 @@ public class PrimaryController {
}).then())
.doOnTerminate(() -> {
Platform.runLater(() -> {
statusTable.refresh();
statusTxt.setText("");
statusPercentage.setText("0%");
statusBar.setProgress(0d);

View File

@ -139,7 +139,7 @@ public class TransferClient {
if (chatInfo == null || baseInfo == null) {
return Optional.empty();
} else {
var baseChatInfo = new BaseChatInfo(id, chatInfo.title);
var baseChatInfo = new BaseChatInfo(TransferUtils.chatEntityIdToChatId(id, TChatType.SUPERGROUP), chatInfo.title);
if (baseInfo.status.getConstructor() == ChatMemberStatusCreator.CONSTRUCTOR) {
var sgInfo = new SupergroupInfo(baseChatInfo, true, true);
return Optional.of(sgInfo);

View File

@ -15,19 +15,23 @@ import it.tdlight.jni.TdApi.AuthorizationStateLoggingOut;
import it.tdlight.jni.TdApi.AuthorizationStateReady;
import it.tdlight.jni.TdApi.ChatMemberStatusAdministrator;
import it.tdlight.jni.TdApi.ChatMemberStatusCreator;
import it.tdlight.jni.TdApi.ChatMemberStatusLeft;
import it.tdlight.jni.TdApi.GetUserPrivacySettingRules;
import it.tdlight.jni.TdApi.Ok;
import it.tdlight.jni.TdApi.Supergroup;
import it.tdlight.jni.TdApi.SupergroupFullInfo;
import it.tdlight.jni.TdApi.Update;
import it.tdlight.jni.TdApi.User;
import it.tdlight.jni.TdApi.UserFullInfo;
import it.tdlight.jni.TdApi.UserTypeRegular;
import it.tdlight.tdlibsession.td.TdError;
import it.tdlight.tdlibsession.td.easy.AsyncTdEasy;
import it.tdlight.tdlibsession.td.easy.ParameterInfoPasswordHint;
import it.tdlight.tdlibsession.td.easy.TdEasySettings;
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
import it.tdlight.tdlibsession.td.middle.direct.AsyncTdMiddleDirect;
import it.tdlight.utils.MonoUtils;
import it.tdlight.utils.TdLightUtils;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectSets;
import java.io.File;
@ -36,6 +40,8 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
@ -294,6 +300,8 @@ public class TransferServiceImpl implements TransferService {
return Mono.error(new Exception("No userbot can add members to the destination group"));
}
AtomicInteger transferredSuccessfullyUsersStats = new AtomicInteger(0);
return percentageConsumer
.apply(0)
.then(phaseDescriptionConsumer.apply("Transfer from " + sourceGroup.getTitle() + " to " + destGroup.getTitle()))
@ -304,27 +312,26 @@ public class TransferServiceImpl implements TransferService {
.flatMap(client -> client
.send(new TdApi.GetMe())
.timeout(Duration.ofSeconds(5))
.flatMap(MonoUtils::orElseThrow)
.then(client.<SupergroupFullInfo>send(new TdApi.GetSupergroupFullInfo(sourceGroup.getSupergroupIdInt())))
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofSeconds(5))
.then(client.<Supergroup>send(new TdApi.GetSupergroup(sourceGroup.getSupergroupIdInt())))
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofSeconds(5))
.filter(sourceGroupFullInfo -> {
if (sourceGroupFullInfo.succeeded()) {
if (sourceGroupFullInfo.result().status.getConstructor() == ChatMemberStatusAdministrator.CONSTRUCTOR) {
var statusAdmin = (ChatMemberStatusAdministrator) sourceGroupFullInfo.result().status;
if (sourceGroupFullInfo.status.getConstructor() == ChatMemberStatusAdministrator.CONSTRUCTOR) {
var statusAdmin = (ChatMemberStatusAdministrator) sourceGroupFullInfo.status;
if (statusAdmin.canRestrictMembers) {
return true;
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: Can't restrict members of group " + sourceGroup.getTitle());
}
} else if (sourceGroupFullInfo.result().status.getConstructor() == ChatMemberStatusCreator.CONSTRUCTOR) {
} else if (sourceGroupFullInfo.status.getConstructor() == ChatMemberStatusCreator.CONSTRUCTOR) {
return true;
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: Can't administer group " + sourceGroup.getTitle());
}
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: " + sourceGroupFullInfo.cause());
}
return false;
})
.map(_v -> client)
@ -338,26 +345,24 @@ public class TransferServiceImpl implements TransferService {
.thenMany(Flux.fromIterable(destSupergroupClients))
.flatMap(client -> client
.send(new TdApi.GetMe())
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofSeconds(5))
.then(client.<Supergroup>send(new TdApi.GetSupergroup(destGroup.getSupergroupIdInt())))
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofSeconds(5))
.filter(destGroupFullInfo -> {
if (destGroupFullInfo.succeeded()) {
if (destGroupFullInfo.result().status.getConstructor() == ChatMemberStatusAdministrator.CONSTRUCTOR) {
var statusAdmin = (ChatMemberStatusAdministrator) destGroupFullInfo.result().status;
if (destGroupFullInfo.status.getConstructor() == ChatMemberStatusAdministrator.CONSTRUCTOR) {
var statusAdmin = (ChatMemberStatusAdministrator) destGroupFullInfo.status;
if (statusAdmin.canInviteUsers) {
return true;
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: Can't invite members to group " + destGroup.getTitle());
}
} else if (destGroupFullInfo.result().status.getConstructor() == ChatMemberStatusCreator.CONSTRUCTOR) {
} else if (destGroupFullInfo.status.getConstructor() == ChatMemberStatusCreator.CONSTRUCTOR) {
return true;
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: Can't administer group " + destGroup.getTitle());
}
} else {
App.getLogService().append(Level.WARN, "Userbot " + client + " failed: " + destGroupFullInfo.cause());
}
return false;
})
.map(_v -> client)
@ -416,8 +421,12 @@ public class TransferServiceImpl implements TransferService {
return Flux
.fromIterable(unresolvedUsers)
.flatMap(userId -> client.<User>send(new TdApi.GetUser(userId)))
.timeout(Duration.ofMinutes(2))
.flatMap(MonoFxUtils::orElseLogSkipError)
.timeout(Duration.ofMinutes(2))
.onErrorResume(error -> {
App.getLogService().append(Level.WARN, "Error while resolving an user: " + error);
return Mono.empty();
})
.collect(Collectors.toSet())
.map(resolvedUsers -> Tuple3.of(client, clients, resolvedUsers));
})
@ -434,45 +443,109 @@ public class TransferServiceImpl implements TransferService {
var unfilteredUsers = context.element3;
return Flux
.fromIterable(unfilteredUsers)
.filter(user -> {
.<User>flatMap(user -> {
if (user.haveAccess) {
return true;
return Mono.just(user);
}
userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.NO_ACCESS_HASH, ""));
return false;
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.NO_ACCESS_HASH, "")).then(Mono.empty());
})
.filter(user -> {
.flatMap(user -> {
if (user.type.getConstructor() == UserTypeRegular.CONSTRUCTOR) {
return true;
return Mono.just(user);
}
userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.NOT_REGULAR_USER, ""));
return false;
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.NOT_REGULAR_USER, "")).then(Mono.empty());
})
.filter(user -> {
.flatMap(user -> {
if (!user.isScam) {
return true;
return Mono.just(user);
}
userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.SCAM_USER, ""));
return false;
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.SCAM_USER, "")).then(Mono.empty());
})
.filter(user -> {
.flatMap(user -> {
if (user.restrictionReason == null || user.restrictionReason.isEmpty()) {
return true;
return Mono.just(user);
}
userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.RESTRICTED_USER, "Restricted user: " + user.restrictionReason));
return false;
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.RESTRICTED_USER, "Restricted user: " + user.restrictionReason)).then(Mono.empty());
})
.flatMap(user -> {
return userStatusConsumer
.apply(new UserStatus(getName(user), user.id, UserStatusType.JUST_FOUND, ""))
.thenReturn(user);
})
.timeout(Duration.ofMinutes(2))
.collect(Collectors.toSet())
.map(resolvedUsers -> Tuple3.of(client, clients, resolvedUsers));
.map(resolvedUsers -> Tuple2.of(clients, resolvedUsers));
})
// Finished filtering unsuitable users
// Transfer users
.flatMap(context -> {
return phaseDescriptionConsumer.apply("Transferring users")
.then(percentageConsumer.apply(20)).thenReturn(context);
})
.flatMap(context -> {
var clients = context.element1;
var users = context.element2;
var client = clients.stream().skip(ThreadLocalRandom.current().nextInt(clients.size())).findFirst().orElseThrow();
AtomicInteger processedUsersStats = new AtomicInteger(0);
return Flux
.fromIterable(users)
.flatMap(user -> {
return percentageConsumer
.apply(20 + processedUsersStats.getAndIncrement() / users.size() * (100 - 20))
.thenReturn(user);
})
.<User>flatMap(user -> {
return client.<Ok>send(new TdApi.AddChatMember(destGroup.getSupergroupId(), user.id, 0))
.flatMap(result -> {
if (result.failed()) {
if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 403, "USER_PRIVACY_RESTRICTED")) {
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.USER_PRIVACY_RESTRICTED, "")).then(Mono.empty());
} else if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 400, "USER_NOT_MUTUAL_CONTACT")) {
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.USER_NOT_MUTUAL_CONTACT, "")).then(Mono.empty());
}
}
return Mono.just(result);
})
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofMinutes(2))
.onErrorResume((error) -> {
App.getLogService().append(Level.WARN, "Can't add user \"" + getName(user) + "\" to supergroup \"" + destGroup.getSupergroupIdInt() + " " + destGroup.getTitle() + "\"");
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.CANT_ADD, "Can't add to destination supergroup: " + error.getLocalizedMessage())).then(Mono.empty());
})
.flatMap(_v -> {
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.ADDED_AND_WAITING_TO_BE_REMOVED, "")).then(Mono.empty()).thenReturn(user);
});
})
.<User>flatMap(user -> {
return client.<Ok>send(new TdApi.SetChatMemberStatus(sourceGroup.getSupergroupId(), user.id, new ChatMemberStatusLeft()))
.flatMap(result -> {
if (result.failed()) {
if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 3, "Can't remove chat owner")) {
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.CANT_REMOVE_CHAT_OWNER, "")).then(Mono.empty());
}
}
return Mono.just(result);
})
.flatMap(MonoUtils::orElseThrow)
.timeout(Duration.ofMinutes(2))
.onErrorResume((error) -> {
App.getLogService().append(Level.WARN, "Can't remove user \"" + getName(user) + "\" from supergroup \"" + sourceGroup.getSupergroupIdInt() + " " + sourceGroup.getTitle() + "\"");
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.CANT_REMOVE, "Can't remove from source supergroup: " + error.getLocalizedMessage())).then(Mono.empty());
})
.flatMap(_v -> {
transferredSuccessfullyUsersStats.incrementAndGet();
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.DONE, "")).then(Mono.empty()).thenReturn(user);
});
})
.collect(Collectors.toSet())
.map(resolvedUsers -> Tuple2.of(clients, resolvedUsers));
})
// Finished transferring users
.doOnNext(context -> {
App.getLogService().append(Level.INFO, "Transfer done. Transferred " + transferredSuccessfullyUsersStats.get() + "/" + context.element2.size() + " users");
})
.then(percentageConsumer.apply(100))
.then(phaseDescriptionConsumer.apply("Done"))
@ -489,6 +562,10 @@ public class TransferServiceImpl implements TransferService {
return Flux
.fromIterable(clients.values())
.flatMap(client -> client.send(new TdApi.Close()))
.onErrorResume(error -> {
App.getLogService().append(Level.ERROR, "Can't close a tdlib instance: " + error.getLocalizedMessage());
return Mono.empty();
})
.collectList()
.then();
}

View File

@ -17,23 +17,46 @@ public class UserStatus {
this.statusType = statusType;
this.errorDescription = errorDescription;
this.user.set(name);
this.status.set(errorDescription.isBlank() ? translateStatusType(statusType) : errorDescription);
this.status.set(errorDescription.isBlank() ? escapeeStatusType(translateStatusType(statusType)) : errorDescription);
}
private String escapeeStatusType(String statusText) {
return statusText
.replace("", "[ OK ]")
.replace("", "[ ** ]")
.replace("", "[WARN]")
.replace("", "[ERR!]")
.replace("", "[ ?? ]");
}
private String translateStatusType(UserStatusType statusType) {
switch (statusType) {
case SCAM_USER:
return "Scam user, skipped";
case NO_ACCESS_HASH:
return "No access hash, skipped";
case RESTRICTED_USER:
return "User restricted, skipped";
case JUST_FOUND:
return "";
case UNKNOWN:
return "Unknown";
case DONE:
return "✔️ Done".substring(2);
case NOT_REGULAR_USER:
return "Not a regular user, skipped";
return "✔️ Not a regular user, skipped";
case SCAM_USER:
return "✔️ Scam user, skipped";
case CANT_REMOVE_CHAT_OWNER:
return "✔️ Chat owner, skipped";
case JUST_FOUND:
return "⏳ Found";
case ADDED_AND_WAITING_TO_BE_REMOVED:
return "⏳ Added to destination group, still not removed from source group";
case NO_ACCESS_HASH:
return "⚠️ No access hash, skipped";
case RESTRICTED_USER:
return "⚠️ User restricted, skipped";
case CANT_REMOVE:
return "⚠️ Can't remove from source group";
case USER_PRIVACY_RESTRICTED:
return "❌ Can't add to destination group: this user privacy settings don't allow invitations to groups";
case USER_NOT_MUTUAL_CONTACT:
return "❌ Can't add to destination group: this user only allows mutual contacts to invite it in the destination group";
case CANT_ADD:
return "❌ Can't add to destination group";
case UNKNOWN:
return "❓ Unknown";
default:
return statusType.toString();
}

View File

@ -1,5 +1,5 @@
package it.cavallium;
public enum UserStatusType {
NO_ACCESS_HASH, NOT_REGULAR_USER, SCAM_USER, RESTRICTED_USER, JUST_FOUND, UNKNOWN
NO_ACCESS_HASH, NOT_REGULAR_USER, SCAM_USER, RESTRICTED_USER, JUST_FOUND, CANT_ADD, ADDED_AND_WAITING_TO_BE_REMOVED, DONE, CANT_REMOVE, USER_PRIVACY_RESTRICTED, USER_NOT_MUTUAL_CONTACT, CANT_REMOVE_CHAT_OWNER, UNKNOWN
}

View File

@ -41,9 +41,9 @@
</MenuBar>
<SplitPane dividerPositions="0.8237442922374429" orientation="VERTICAL" prefHeight="9999.0">
<items>
<SplitPane dividerPositions="0.5" focusTraversable="true" prefHeight="-1.0" prefWidth="-1.0">
<SplitPane dividerPositions="0.35" focusTraversable="true" prefHeight="-1.0" prefWidth="-1.0">
<items>
<BorderPane>
<BorderPane maxWidth="250.0" prefWidth="250.0">
<top>
<Label alignment="CENTER" prefWidth="-1.0" style="&#10;" text="Userbots" textAlignment="CENTER" wrapText="false" BorderPane.alignment="CENTER">
<font>
@ -103,14 +103,14 @@
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</VBox>
<TableView fx:id="statusTable" GridPane.columnSpan="2" GridPane.rowIndex="1" style="-fx-font-size: 10">
<TableView fx:id="statusTable" style="-fx-font-size: 10; -fx-font-family: monospace" GridPane.columnSpan="2" GridPane.rowIndex="1">
<columns>
<TableColumn minWidth="100.0" prefWidth="200.0" text="User">
<TableColumn maxWidth="300.0" minWidth="100.0" prefWidth="-1.0" text="User">
<cellValueFactory>
<PropertyValueFactory property="user" />
</cellValueFactory>
</TableColumn>
<TableColumn minWidth="100.0" prefWidth="100.0" text="Status">
<TableColumn maxWidth="1.7976931348623157E308" minWidth="100.0" prefWidth="300.0" text="Status">
<cellValueFactory>
<PropertyValueFactory property="status" />
</cellValueFactory>

@ -1 +1 @@
Subproject commit 6fd5b099c3f90e1dbca28d37c9003541e47b4d51
Subproject commit cb1d0dfe3ab6c7c821497eeea8a966debc6379e8