Done
This commit is contained in:
parent
6bf3e55358
commit
a5b06b8d47
|
@ -362,6 +362,7 @@ public class PrimaryController {
|
|||
}).then())
|
||||
.doOnTerminate(() -> {
|
||||
Platform.runLater(() -> {
|
||||
statusTable.refresh();
|
||||
statusTxt.setText("");
|
||||
statusPercentage.setText("0%");
|
||||
statusBar.setProgress(0d);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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=" " 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
|
Loading…
Reference in New Issue
Block a user