Compare commits
8 Commits
master
...
parallel-a
Author | SHA1 | Date |
---|---|---|
Andrea Cavalli | ebc30f2718 | |
Andrea Cavalli | c286176031 | |
Andrea Cavalli | f1642713d6 | |
Andrea Cavalli | a258991e83 | |
Andrea Cavalli | 35ef9912f4 | |
Andrea Cavalli | 8c38d60c72 | |
Andrea Cavalli | 3ba6ef1d2d | |
Andrea Cavalli | 213e5a88f1 |
|
@ -198,4 +198,5 @@ $RECYCLE.BIN/
|
|||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/java,maven,windows,linux,macos,intellij+all
|
||||
# End of https://www.toptal.com/developers/gitignore/api/java,maven,windows,linux,macos,intellij+all
|
||||
/debug/
|
||||
|
|
14
pom.xml
14
pom.xml
|
@ -2,7 +2,7 @@
|
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>it.cavallium</groupId>
|
||||
<artifactId>TransferBot</artifactId>
|
||||
<artifactId>ParallelAdder</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<properties>
|
||||
|
@ -52,7 +52,17 @@
|
|||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlib-session-container</artifactId>
|
||||
<version>4.0.16</version>
|
||||
<version>[4.2.7,)</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-linux-amd64</artifactId>
|
||||
<version>[1.0.0,)</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>it.tdlight</groupId>
|
||||
<artifactId>tdlight-natives-windows-amd64</artifactId>
|
||||
<version>[1.0.0,)</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
|
|
|
@ -30,7 +30,13 @@ public class App extends Application {
|
|||
|
||||
@Override
|
||||
public void start(Stage stage) throws IOException, CantLoadLibrary {
|
||||
if (!SystemUtils.lockInstance(".app-lock")) {
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Initialize TDLib library natives
|
||||
Init.start();
|
||||
|
||||
clusterManager = new TdClusterManager(null, null, Vertx.vertx(), null);
|
||||
|
||||
settingsService = new SettingsServiceImpl();
|
||||
|
|
|
@ -2,15 +2,18 @@ package it.cavallium;
|
|||
|
||||
import static it.cavallium.StaticSettings.REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP;
|
||||
import static it.cavallium.StaticSettings.requiresAdminPrivilegesOnSourceSupergroup;
|
||||
import static it.cavallium.TransferUtils.getLongPhoneNumber;
|
||||
|
||||
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 com.sun.scenario.Settings;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
|
@ -110,6 +113,16 @@ public class PrimaryController {
|
|||
log.scrollTo(logString);
|
||||
return Mono.empty();
|
||||
})).subscribe();
|
||||
|
||||
this.onAfterInitialize();
|
||||
}
|
||||
|
||||
private void onAfterInitialize() {
|
||||
for (Long number : App.getTransferService().getInitialPhoneNumbers()) {
|
||||
this.addUserbot("+" + number, phoneNumber -> {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void disableClicks() {
|
||||
|
@ -132,8 +145,13 @@ public class PrimaryController {
|
|||
|
||||
@FXML
|
||||
public void addUserbot(ActionEvent actionEvent) {
|
||||
var eventSource = (Node) actionEvent.getSource();
|
||||
var phoneNumberText = phoneNumber.getText();
|
||||
this.addUserbot(phoneNumberText, phoneNumber -> {
|
||||
App.getSettingsService().addPhoneNumber(getLongPhoneNumber(phoneNumber));
|
||||
});
|
||||
}
|
||||
|
||||
private void addUserbot(String phoneNumberText, Consumer<PhoneNumber> doneSuccessfully) {
|
||||
try {
|
||||
var phoneNumber = getUserbotPhoneNumber(phoneNumberText);
|
||||
var phoneNumberPrettyText = getUserbotPrettyPrintPhoneNumber(phoneNumber);
|
||||
|
@ -198,40 +216,47 @@ public class PrimaryController {
|
|||
}
|
||||
}).flatMap((_v) -> MonoFxUtils.runLater(() -> {
|
||||
var alert = new Alert(AlertType.INFORMATION,
|
||||
"Added userbot " + PhoneNumberUtil
|
||||
.getInstance()
|
||||
.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL),
|
||||
ButtonType.CLOSE
|
||||
"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)
|
||||
);
|
||||
.getItems()
|
||||
.add(0,
|
||||
new Text(phoneNumberPrettyText)
|
||||
);
|
||||
this.phoneNumber.setText("");
|
||||
doneSuccessfully.accept(phoneNumber);
|
||||
});
|
||||
})
|
||||
.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();
|
||||
});
|
||||
});
|
||||
.doOnTerminate(this::enableClicks)
|
||||
.subscribe(_v -> {}, error -> {
|
||||
Platform.runLater(() -> {
|
||||
var skipAndDeleteUserbotBtn = new ButtonType("Skip and delete userbot");
|
||||
var skipBtn = new ButtonType("Skip");
|
||||
var alert = new Alert(AlertType.ERROR,
|
||||
error.getLocalizedMessage(),
|
||||
skipBtn,
|
||||
skipAndDeleteUserbotBtn
|
||||
);
|
||||
alert.setHeaderText("Error while adding the userbot " + PhoneNumberUtil
|
||||
.getInstance()
|
||||
.format(phoneNumber, PhoneNumberFormat.INTERNATIONAL));
|
||||
MonoFxUtils.showAndWait(alert).subscribe(result -> {
|
||||
if (skipAndDeleteUserbotBtn.getText().equals(result.getText())) {
|
||||
App.getSettingsService().removePhoneNumber(getLongPhoneNumber(phoneNumber));
|
||||
}
|
||||
}, System.err::println, this::enableClicks);
|
||||
});
|
||||
});
|
||||
} catch (NumberFormatException | NumberParseException ex) {
|
||||
var alert = new Alert(AlertType.ERROR, "Can't parse phone number format", ButtonType.CLOSE);
|
||||
alert.showAndWait();
|
||||
enableClicks();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -359,7 +384,7 @@ public class PrimaryController {
|
|||
})
|
||||
)
|
||||
.onErrorResume(error -> MonoFxUtils.runLater(() -> {
|
||||
var alert = new Alert(AlertType.ERROR, "Error during transfer", ButtonType.CLOSE);
|
||||
var alert = new Alert(AlertType.ERROR, "Error during users addition", ButtonType.CLOSE);
|
||||
alert.setContentText(error.getLocalizedMessage());
|
||||
return MonoFxUtils.showAndWait(alert);
|
||||
}).then())
|
||||
|
|
|
@ -1,9 +1,30 @@
|
|||
package it.cavallium;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Set;
|
||||
|
||||
public interface SettingsService {
|
||||
void setDelayBetweenAdds(Duration duration);
|
||||
|
||||
Duration getDelayBetweenAdds();
|
||||
|
||||
void addPhoneNumber(Long phoneNumber);
|
||||
|
||||
void removePhoneNumber(Long phoneNumber);
|
||||
|
||||
void clearPhoneNumbers();
|
||||
|
||||
Set<Long> getPhoneNumbers();
|
||||
|
||||
String getProxyIp();
|
||||
|
||||
void setProxyIp(String value);
|
||||
|
||||
Integer getProxyPort();
|
||||
|
||||
void setProxyPort(int value);
|
||||
|
||||
String getProxySecret();
|
||||
|
||||
void setProxySecret(String value);
|
||||
}
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
package it.cavallium;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import io.vertx.core.impl.ConcurrentHashSet;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.Duration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.slf4j.event.Level;
|
||||
|
||||
public class SettingsServiceImpl implements SettingsService {
|
||||
|
||||
private static final Gson gson = new Gson();
|
||||
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
private final AtomicReference<Duration> delayBetweenAdds = new AtomicReference<>(Duration.ZERO);
|
||||
private final AtomicReference<String> proxyIp = new AtomicReference<>("proxy.digitalresistance.dog");
|
||||
private final AtomicReference<Integer> proxyPort = new AtomicReference<>(443);
|
||||
private final AtomicReference<String> proxySecret = new AtomicReference<>("d41d8cd98f00b204e9800998ecf8427e");
|
||||
private final ConcurrentHashSet<Long> phoneNumbers = new ConcurrentHashSet<>();
|
||||
|
||||
public SettingsServiceImpl() {
|
||||
synchronized (gson) {
|
||||
|
@ -27,6 +36,21 @@ public class SettingsServiceImpl implements SettingsService {
|
|||
Optional.ofNullable(settings.getAsJsonPrimitive("delayBetweenAdds")).ifPresent(seconds -> {
|
||||
this.delayBetweenAdds.set(Duration.ofSeconds(seconds.getAsInt()));
|
||||
});
|
||||
Optional.ofNullable(settings.getAsJsonArray("phoneNumbers")).ifPresent(phoneNumbers -> {
|
||||
this.phoneNumbers.clear();
|
||||
for (JsonElement phoneNumber : phoneNumbers) {
|
||||
this.phoneNumbers.add(Long.parseLong(phoneNumber.getAsString()));
|
||||
}
|
||||
});
|
||||
Optional.ofNullable(settings.getAsJsonPrimitive("proxyIp")).ifPresent(value -> {
|
||||
this.proxyIp.set(value.getAsString());
|
||||
});
|
||||
Optional.ofNullable(settings.getAsJsonPrimitive("proxyPort")).ifPresent(value -> {
|
||||
this.proxyPort.set(value.getAsInt());
|
||||
});
|
||||
Optional.ofNullable(settings.getAsJsonPrimitive("proxySecret")).ifPresent(value -> {
|
||||
this.proxySecret.set(value.getAsString());
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -39,8 +63,16 @@ public class SettingsServiceImpl implements SettingsService {
|
|||
synchronized (gson) {
|
||||
var settings = new JsonObject();
|
||||
settings.addProperty("delayBetweenAdds", delayBetweenAdds.get().toSeconds());
|
||||
var phoneNumbers = new JsonArray();
|
||||
for (Long phoneNumber : this.phoneNumbers) {
|
||||
phoneNumbers.add("" + phoneNumber);
|
||||
}
|
||||
settings.add("phoneNumbers", phoneNumbers);
|
||||
settings.addProperty("proxyIp", proxyIp.get());
|
||||
settings.addProperty("proxyPort", proxyPort.get());
|
||||
settings.addProperty("proxySecret", proxySecret.get());
|
||||
try {
|
||||
Files.writeString(Paths.get("settings.json"), gson.toJson(settings), StandardCharsets.UTF_8, StandardOpenOption.CREATE);
|
||||
Files.writeString(Paths.get("settings.json"), gson.toJson(settings), StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
|
||||
} catch (IOException e) {
|
||||
App.getLogService().append(Level.ERROR, e.getLocalizedMessage());
|
||||
}
|
||||
|
@ -57,4 +89,60 @@ public class SettingsServiceImpl implements SettingsService {
|
|||
public Duration getDelayBetweenAdds() {
|
||||
return delayBetweenAdds.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPhoneNumber(Long phoneNumber) {
|
||||
phoneNumbers.add(phoneNumber);
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removePhoneNumber(Long phoneNumber) {
|
||||
phoneNumbers.remove(phoneNumber);
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearPhoneNumbers() {
|
||||
phoneNumbers.clear();
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getPhoneNumbers() {
|
||||
return new HashSet<>(phoneNumbers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProxyIp() {
|
||||
return proxyIp.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProxyIp(String value) {
|
||||
this.proxyIp.set(value);
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getProxyPort() {
|
||||
return proxyPort.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProxyPort(int value) {
|
||||
this.proxyPort.set(value);
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProxySecret() {
|
||||
return proxySecret.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProxySecret(String value) {
|
||||
this.proxySecret.set(value);
|
||||
saveSettings();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,8 +2,8 @@ package it.cavallium;
|
|||
|
||||
public class StaticSettings {
|
||||
|
||||
public static final String BRANDING_NAME = "TransferBot";
|
||||
public static final boolean REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP = true;
|
||||
public static final String BRANDING_NAME = "P4RALLEL ADDER";
|
||||
public static final boolean REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP = false;
|
||||
|
||||
public static boolean requiresAdminPrivilegesOnSourceSupergroup() {
|
||||
return REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP;
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package it.cavallium;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SystemUtils {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SystemUtils.class);
|
||||
|
||||
public static boolean lockInstance(final String lockFile) {
|
||||
try {
|
||||
final File file = new File(lockFile);
|
||||
final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
|
||||
final FileLock fileLock = randomAccessFile.getChannel().tryLock();
|
||||
if (fileLock != null) {
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
try {
|
||||
fileLock.release();
|
||||
randomAccessFile.close();
|
||||
if (!file.delete()) {
|
||||
log.warn("Unable to delete lock file: " + lockFile);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
System.err.println();
|
||||
log.error("Unable to remove lock file: " + lockFile, e);
|
||||
}
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Unable to create and/or lock file: " + lockFile, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package it.cavallium;
|
||||
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.AddProxy;
|
||||
import it.tdlight.jni.TdApi.AuthorizationState;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateReady;
|
||||
import it.tdlight.jni.TdApi.Chat;
|
||||
|
@ -9,6 +10,7 @@ import it.tdlight.jni.TdApi.ChatMemberStatusCreator;
|
|||
import it.tdlight.jni.TdApi.ChatMemberStatusMember;
|
||||
import it.tdlight.jni.TdApi.GetSupergroupFullInfo;
|
||||
import it.tdlight.jni.TdApi.Object;
|
||||
import it.tdlight.jni.TdApi.ProxyTypeMtproto;
|
||||
import it.tdlight.jni.TdApi.Supergroup;
|
||||
import it.tdlight.jni.TdApi.SupergroupFullInfo;
|
||||
import it.tdlight.jni.TdApi.Update;
|
||||
|
@ -56,6 +58,18 @@ public class TransferClient {
|
|||
.publishOn(scheduler)
|
||||
.flatMap(this::onUpdate)
|
||||
.subscribe();
|
||||
|
||||
this.setupProxy();
|
||||
}
|
||||
|
||||
private void setupProxy() {
|
||||
var ip = App.getSettingsService().getProxyIp();
|
||||
var port = App.getSettingsService().getProxyPort();
|
||||
var secret = App.getSettingsService().getProxySecret();
|
||||
|
||||
this.client.send(new AddProxy(ip, port, true, new ProxyTypeMtproto(secret))).doOnError(e -> {
|
||||
e.printStackTrace();
|
||||
}).subscribe();
|
||||
}
|
||||
|
||||
public User getClientUser() {
|
||||
|
|
|
@ -35,4 +35,6 @@ public interface TransferService {
|
|||
Function<UserStatus, Mono<Void>> userStatusConsumer,
|
||||
Function<Integer, Mono<Void>> percentageConsumer,
|
||||
Function<String, Mono<Void>> phaseDescriptionConsumer);
|
||||
|
||||
Iterable<? extends Long> getInitialPhoneNumbers();
|
||||
}
|
||||
|
|
|
@ -8,9 +8,7 @@ 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 com.hazelcast.cp.internal.util.Tuple2;
|
||||
import com.hazelcast.cp.internal.util.Tuple3;
|
||||
import io.vertx.core.impl.ConcurrentHashSet;
|
||||
import com.hazelcast.cp.internal.datastructures.atomicref.AtomicRef;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
|
||||
import it.tdlight.jni.TdApi.AuthorizationStateClosing;
|
||||
|
@ -20,15 +18,13 @@ import it.tdlight.jni.TdApi.ChatMemberStatusAdministrator;
|
|||
import it.tdlight.jni.TdApi.ChatMemberStatusCreator;
|
||||
import it.tdlight.jni.TdApi.ChatMemberStatusLeft;
|
||||
import it.tdlight.jni.TdApi.ChatMemberStatusMember;
|
||||
import it.tdlight.jni.TdApi.GetMe;
|
||||
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.FatalErrorType;
|
||||
import it.tdlight.tdlibsession.td.TdError;
|
||||
import it.tdlight.tdlibsession.td.easy.AsyncTdEasy;
|
||||
import it.tdlight.tdlibsession.td.easy.ParameterInfoPasswordHint;
|
||||
|
@ -47,6 +43,7 @@ import java.util.Set;
|
|||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -57,6 +54,7 @@ import reactor.core.publisher.Flux;
|
|||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public class TransferServiceImpl implements TransferService {
|
||||
|
||||
|
@ -67,6 +65,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
private final Scheduler updatesScheduler;
|
||||
private int apiId;
|
||||
private String apiHash;
|
||||
private boolean waitingInitialPhoneNumbers = true;
|
||||
|
||||
public TransferServiceImpl(TdClusterManager clusterManager) {
|
||||
this.clusterManager = clusterManager;
|
||||
|
@ -89,19 +88,23 @@ public class TransferServiceImpl implements TransferService {
|
|||
BiFunction<AsyncTdEasy, String, Mono<String>> otpSupplier,
|
||||
Supplier<Mono<Void>> onUserbotClosed) {
|
||||
|
||||
long phoneNumberLong = getLongPhoneNumber(phoneNumber);
|
||||
long phoneNumberLong = TransferUtils.getLongPhoneNumber(phoneNumber);
|
||||
|
||||
if (clients.containsKey(phoneNumberLong)) {
|
||||
return Mono.just(AddUserBotResult.newFailed("Userbot already added!"));
|
||||
}
|
||||
|
||||
String alias = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberFormat.INTERNATIONAL);
|
||||
final AtomicReference<FatalErrorType> fatalError = new AtomicReference<>(null);
|
||||
|
||||
return Mono
|
||||
.fromCallable(() -> {
|
||||
return AsyncTdMiddleDirect.getAndDeployInstance(clusterManager, alias, "" + phoneNumberLong);
|
||||
synchronized (clusterManager) {
|
||||
return AsyncTdMiddleDirect.getAndDeployInstance(clusterManager, alias, "" + phoneNumberLong)
|
||||
.block(Duration.ofSeconds(60));
|
||||
}
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.flatMap(v -> v)
|
||||
.map(middle -> new AsyncTdEasy(middle, alias)).flatMap(client -> {
|
||||
return client
|
||||
.execute(new TdApi.SetLogVerbosityLevel(0))
|
||||
|
@ -121,6 +124,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
.setSystemLanguageCode("en")
|
||||
.setDeviceModel(System.getProperty("os.name"))
|
||||
.setSystemVersion(System.getProperty("os.version"))
|
||||
.setFatalErrorHandler((FatalErrorType error) -> Mono.fromRunnable(() -> fatalError.set(error)))
|
||||
.setParameterRequestHandler((parameter, parameterInfo) -> {
|
||||
switch (parameter) {
|
||||
case ASK_FIRST_NAME:
|
||||
|
@ -189,7 +193,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
if (state.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) {
|
||||
sink.complete();
|
||||
} else {
|
||||
sink.error(new Exception(state.getClass().getSimpleName()));
|
||||
sink.error(new Exception("Userbot closed with state: " + state.getClass().getSimpleName()));
|
||||
}
|
||||
})
|
||||
.then();
|
||||
|
@ -220,23 +224,24 @@ public class TransferServiceImpl implements TransferService {
|
|||
newClients.onNext(new ItemUpdate<>(false, newClient));
|
||||
}));
|
||||
})
|
||||
.map(_v -> AddUserBotResult.newSuccess());
|
||||
.map(_v -> AddUserBotResult.newSuccess())
|
||||
.onErrorMap(error -> {
|
||||
var fatalErrorValue = fatalError.get();
|
||||
if (fatalErrorValue != null) {
|
||||
return new TdError(500, fatalErrorValue.toString());
|
||||
} else {
|
||||
return error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Mono<Void> onClientUpdate(Update update) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
private static long getLongPhoneNumber(PhoneNumber phoneNumber) {
|
||||
return Long.parseLong(PhoneNumberUtil
|
||||
.getInstance()
|
||||
.format(phoneNumber, PhoneNumberFormat.E164)
|
||||
.replace("+", ""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> closeUserbot(PhoneNumber phoneNumber) {
|
||||
var client = clients.remove(getLongPhoneNumber(phoneNumber));
|
||||
var client = clients.remove(TransferUtils.getLongPhoneNumber(phoneNumber));
|
||||
newClients.onNext(new ItemUpdate<>(true, client));
|
||||
if (client == null) {
|
||||
return Mono.error(new Exception("Userbot " + phoneNumber + " was not found!"));
|
||||
|
@ -309,7 +314,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
|
||||
return percentageConsumer
|
||||
.apply(0)
|
||||
.then(phaseDescriptionConsumer.apply("Transfer from " + sourceGroup.getTitle() + " to " + destGroup.getTitle()))
|
||||
.then(phaseDescriptionConsumer.apply("Add users from " + sourceGroup.getTitle() + " to " + destGroup.getTitle()))
|
||||
|
||||
// Check and get the set of userbots that can transfer users from group X to group Y
|
||||
.then(phaseDescriptionConsumer.apply("Checking available userbots for " + (REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP ? "removing" : "managing") + " users in the source group"))
|
||||
|
@ -389,11 +394,11 @@ public class TransferServiceImpl implements TransferService {
|
|||
return Mono.empty();
|
||||
}))
|
||||
.collect(Collectors.toSet())
|
||||
.map(transferDestClients -> Tuple2.of(transferSourceClients, transferDestClients));
|
||||
.map(transferDestClients -> Tuples.of(transferSourceClients, transferDestClients));
|
||||
})
|
||||
.map(clientsTuple -> {
|
||||
var sourceClients = clientsTuple.element1;
|
||||
var destClients = clientsTuple.element2;
|
||||
var sourceClients = clientsTuple.getT1();
|
||||
var destClients = clientsTuple.getT2();
|
||||
App.getLogService().append(Level.INFO, "Found source userbots: " + sourceClients.stream().map(TransferClient::toString).collect(Collectors.joining(", ")));
|
||||
App.getLogService().append(Level.INFO, "Found destination userbots: " + destClients.stream().map(TransferClient::toString).collect(Collectors.joining(", ")));
|
||||
var chosenClients = new HashSet<TransferClient>(sourceClients);
|
||||
|
@ -432,7 +437,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
return new Exception("Error while obtaining source group members: " + error.getLocalizedMessage(), error);
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.map(members -> Tuple2.of(clients, members));
|
||||
.map(members -> Tuples.of(clients, members));
|
||||
})
|
||||
// Finished getting the list of members of the source group
|
||||
|
||||
|
@ -442,8 +447,8 @@ public class TransferServiceImpl implements TransferService {
|
|||
.then(percentageConsumer.apply(10)).thenReturn(context);
|
||||
})
|
||||
.flatMap(context -> {
|
||||
var clients = context.element1;
|
||||
var unresolvedUsers = context.element2;
|
||||
var clients = context.getT1();
|
||||
var unresolvedUsers = context.getT2();
|
||||
return Flux
|
||||
.fromIterable(clients)
|
||||
.flatMap(client -> Flux
|
||||
|
@ -458,7 +463,7 @@ public class TransferServiceImpl implements TransferService {
|
|||
.collect(Collectors.toSet())
|
||||
)
|
||||
.last()
|
||||
.map(resolvedUsers -> Tuple2.of(clients, resolvedUsers));
|
||||
.map(resolvedUsers -> Tuples.of(clients, resolvedUsers));
|
||||
})
|
||||
// Finished resolving users
|
||||
|
||||
|
@ -468,8 +473,8 @@ public class TransferServiceImpl implements TransferService {
|
|||
.then(percentageConsumer.apply(15)).thenReturn(context);
|
||||
})
|
||||
.flatMap(context -> {
|
||||
var clients = context.element1;
|
||||
var unfilteredUsers = context.element2;
|
||||
var clients = context.getT1();
|
||||
var unfilteredUsers = context.getT2();
|
||||
return Flux
|
||||
.fromIterable(unfilteredUsers)
|
||||
.<User>flatMap(user -> {
|
||||
|
@ -508,23 +513,26 @@ public class TransferServiceImpl implements TransferService {
|
|||
.thenReturn(user);
|
||||
})
|
||||
.collect(Collectors.toSet())
|
||||
.map(resolvedUsers -> Tuple3.of(clients, resolvedUsers, unfilteredUsers.size()));
|
||||
.map(resolvedUsers -> Tuples.of(clients, resolvedUsers, unfilteredUsers.size()));
|
||||
})
|
||||
// Finished filtering unsuitable users
|
||||
|
||||
// Transfer users
|
||||
.flatMap(context -> {
|
||||
return phaseDescriptionConsumer.apply("Transferring users")
|
||||
return phaseDescriptionConsumer.apply("Adding users")
|
||||
.then(percentageConsumer.apply(20)).thenReturn(context);
|
||||
})
|
||||
.flatMap(context -> {
|
||||
var clients = context.element1;
|
||||
var users = context.element2;
|
||||
var totalUsersCount = context.element3;
|
||||
var clients = context.getT1();
|
||||
var users = context.getT2();
|
||||
var totalUsersCount = context.getT3();
|
||||
var client = clients.stream().skip(ThreadLocalRandom.current().nextInt(clients.size())).findFirst().orElseThrow();
|
||||
AtomicInteger processedUsersStats = new AtomicInteger(0);
|
||||
return Flux
|
||||
.fromIterable(users)
|
||||
// Apply delay between user
|
||||
.delayElements(App.getSettingsService().getDelayBetweenAdds())
|
||||
|
||||
.flatMap(user -> {
|
||||
return percentageConsumer
|
||||
.apply(20 + processedUsersStats.getAndIncrement() / users.size() * (100 - 20))
|
||||
|
@ -538,18 +546,36 @@ public class TransferServiceImpl implements TransferService {
|
|||
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());
|
||||
} else if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 400, "PEER_FLOOD")) {
|
||||
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.ADD_FLOOD, "")).then(Mono.empty());
|
||||
} else if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 429, "Too Many Requests")) {
|
||||
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.ADD_TOO_MANY_REQUESTS, "")).then(Mono.empty());
|
||||
} else if (TdLightUtils.errorEquals(new TdError(result.cause().code, result.cause().message), 403, "USER_CHANNELS_TOO_MUCH")) {
|
||||
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.USER_CHANNELS_TOO_MUCH, "")).then(Mono.empty());
|
||||
} else {
|
||||
return Mono.just(result);
|
||||
}
|
||||
} else {
|
||||
return Mono.just(result);
|
||||
}
|
||||
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() + "\"");
|
||||
App.getLogService().append(Level.WARN, "Can't add user \"" + getName(user) + "\" to supergroup \"" + destGroup.getSupergroupIdInt() + " " + destGroup.getTitle() + "\" (" + error.getLocalizedMessage() + ")");
|
||||
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);
|
||||
UserStatusType userStatusType;
|
||||
if (REMOVE_MEMBERS_FROM_SOURCE_SUPERGROUP) {
|
||||
userStatusType = UserStatusType.ADDED_AND_WAITING_TO_BE_REMOVED;
|
||||
} else {
|
||||
userStatusType = UserStatusType.DONE;
|
||||
}
|
||||
return userStatusConsumer
|
||||
.apply(new UserStatus(getName(user), user.id, userStatusType, ""))
|
||||
.then(Mono.empty())
|
||||
.thenReturn(user);
|
||||
});
|
||||
})
|
||||
.<User>flatMap(user -> {
|
||||
|
@ -576,18 +602,18 @@ public class TransferServiceImpl implements TransferService {
|
|||
});
|
||||
} else {
|
||||
// Don't remove the user from the source supergroup
|
||||
return Mono.just(user);
|
||||
transferredSuccessfullyUsersStats.incrementAndGet();
|
||||
return userStatusConsumer.apply(new UserStatus(getName(user), user.id, UserStatusType.DONE, "")).then(Mono.empty()).thenReturn(user);
|
||||
}
|
||||
})
|
||||
.delayElements(App.getSettingsService().getDelayBetweenAdds())
|
||||
.collect(Collectors.toSet())
|
||||
.map(resolvedUsers -> Tuple3.of(clients, resolvedUsers, totalUsersCount));
|
||||
.map(resolvedUsers -> Tuples.of(clients, resolvedUsers, totalUsersCount));
|
||||
})
|
||||
// Finished transferring users
|
||||
|
||||
|
||||
.doOnNext(context -> {
|
||||
App.getLogService().append(Level.INFO, "Transfer done. Transferred " + transferredSuccessfullyUsersStats.get() + "/" + context.element3 + " users");
|
||||
App.getLogService().append(Level.INFO, "Users added. Added " + transferredSuccessfullyUsersStats.get() + "/" + context.getT3() + " users");
|
||||
})
|
||||
|
||||
.then(percentageConsumer.apply(100))
|
||||
|
@ -596,6 +622,16 @@ public class TransferServiceImpl implements TransferService {
|
|||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<? extends Long> getInitialPhoneNumbers() {
|
||||
if (waitingInitialPhoneNumbers) {
|
||||
waitingInitialPhoneNumbers = false;
|
||||
return new HashSet<>(App.getSettingsService().getPhoneNumbers());
|
||||
} else {
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getName(User user) {
|
||||
return String.join(" ", List.of("" + user.id, user.firstName, user.lastName));
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package it.cavallium;
|
||||
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
|
||||
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
|
||||
import it.tdlight.jni.TdApi;
|
||||
import it.tdlight.jni.TdApi.Chat;
|
||||
import it.tdlight.jni.TdApi.ChatListMain;
|
||||
|
@ -52,6 +55,13 @@ public class TransferUtils {
|
|||
}
|
||||
}
|
||||
|
||||
public static long getLongPhoneNumber(PhoneNumber phoneNumber) {
|
||||
return Long.parseLong(PhoneNumberUtil
|
||||
.getInstance()
|
||||
.format(phoneNumber, PhoneNumberFormat.E164)
|
||||
.replace("+", ""));
|
||||
}
|
||||
|
||||
|
||||
private static class ChatIdAndOrderOffsets {
|
||||
|
||||
|
|
|
@ -53,6 +53,12 @@ public class UserStatus {
|
|||
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 ADD_FLOOD:
|
||||
return "✔️ Can't add to destination group: an userbot has been blocked by telegram for flooding";
|
||||
case USER_CHANNELS_TOO_MUCH:
|
||||
return "✔️ Can't add to destination group: subscribed to too many channels";
|
||||
case ADD_TOO_MANY_REQUESTS:
|
||||
return "✔️ Can't add to destination group: an userbot has been blocked by telegram for making too many requests";
|
||||
case CANT_ADD:
|
||||
return "❌ Can't add to destination group";
|
||||
case UNKNOWN:
|
||||
|
|
|
@ -1,5 +1,43 @@
|
|||
package it.cavallium;
|
||||
|
||||
public enum UserStatusType {
|
||||
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
|
||||
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,
|
||||
USER_CHANNELS_TOO_MUCH,
|
||||
ADD_FLOOD,
|
||||
ADD_TOO_MANY_REQUESTS,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
/*
|
||||
Possible errors
|
||||
Code Type Description
|
||||
400 BOTS_TOO_MUCH There are too many bots in this chat/channel
|
||||
400 BOT_GROUPS_BLOCKED This bot can't be added to groups
|
||||
400 CHANNEL_INVALID The provided channel is invalid
|
||||
400 CHANNEL_PRIVATE You haven't joined this channel/supergroup
|
||||
400 CHAT_ADMIN_REQUIRED You must be an admin in this chat to do this
|
||||
400 CHAT_INVALID Invalid chat
|
||||
403 CHAT_WRITE_FORBIDDEN You can't write in this chat
|
||||
400 INPUT_USER_DEACTIVATED The specified user was deleted
|
||||
400 MSG_ID_INVALID Invalid message ID provided
|
||||
400 USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example)
|
||||
400 USER_BANNED_IN_CHANNEL You're banned from sending messages in supergroups/channels
|
||||
400 USER_BLOCKED User blocked
|
||||
400 USER_BOT Bots can only be admins in channels.
|
||||
403 USER_CHANNELS_TOO_MUCH One of the users you tried to add is already in too many channels/supergroups
|
||||
400 USER_ID_INVALID The provided user ID is invalid
|
||||
400 USER_KICKED This user was kicked from this supergroup/channel
|
||||
400 USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact
|
||||
403 USER_PRIVACY_RESTRICTED The user's privacy settings do not allow you to do this
|
||||
*/
|
|
@ -73,7 +73,7 @@
|
|||
</BorderPane>
|
||||
<BorderPane>
|
||||
<top>
|
||||
<Label alignment="CENTER" prefWidth="-1.0" style=" " text="Transfer" textAlignment="CENTER" wrapText="false" BorderPane.alignment="CENTER">
|
||||
<Label alignment="CENTER" prefWidth="-1.0" style=" " text="Current operation management" textAlignment="CENTER" wrapText="false" BorderPane.alignment="CENTER">
|
||||
<font>
|
||||
<Font size="18.0" fx:id="x11" />
|
||||
</font>
|
||||
|
@ -97,7 +97,7 @@
|
|||
<children>
|
||||
<ChoiceBox fx:id="sourceGroupCombo" minWidth="200.0" prefHeight="28.0" />
|
||||
<ChoiceBox fx:id="destGroupCombo" minWidth="200.0" prefHeight="28.0" />
|
||||
<Button fx:id="transferBtn" mnemonicParsing="false" onAction="#onDoTransfer" text="Transfer" />
|
||||
<Button fx:id="transferBtn" mnemonicParsing="false" onAction="#onDoTransfer" text="Add users" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- log4j2.xml - Example log4j configuration file
|
||||
Place this file in the same directory as your server.jar, edit
|
||||
to taste, and add -Dlog4j.configurationFile=log4j2.xml to your
|
||||
server startup flags.
|
||||
More log4j example configs can be found at
|
||||
<http://logging.apache.org/log4j/2.x/manual/appenders.html>.
|
||||
-->
|
||||
|
||||
<Configuration status="WARN" packages="net.minecraft,com.mojang">
|
||||
<Appenders>
|
||||
|
||||
<!-- DEFAULT APPENDERS -->
|
||||
|
||||
<!-- console logging - logs to stdout -->
|
||||
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} %highlight{${LOG_LEVEL_PATTERN:-%5p}}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} %style{%processId}{magenta} [%15.15t] %style{%-20.20c{1}}{cyan} : %m%n%ex"/>
|
||||
</Console>
|
||||
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
|
||||
<!-- DEFAULT LOGGERS -->
|
||||
|
||||
<filters>
|
||||
<MarkerFilter marker="NETWORK_PACKETS" onMatch="DENY"
|
||||
onMismatch="NEUTRAL" />
|
||||
</filters>
|
||||
<AppenderRef ref="ConsoleAppender"/>
|
||||
|
||||
<!-- EXAMPLE LOGGERS -->
|
||||
|
||||
<!-- <AppenderRef ref="LegacyServerLog"/> -->
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit b1ab4777479c10d48d2fec8392c52a6b66d66233
|
Loading…
Reference in New Issue