|
|
|
@ -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();
|
|
|
|
|
}
|
|
|
|
|