Working login and chats list

This commit is contained in:
Andrea Cavalli 2020-10-17 01:54:06 +02:00
parent 336a5522f1
commit d152f1a5a1
18 changed files with 1510 additions and 37 deletions

462
install4j.install4j Normal file
View File

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8"?>
<install4j version="8.0.8" transformSequenceNumber="8">
<directoryPresets config="/home/ubuntu/IdeaProjects/TransferBot/target" />
<application name="TransferBot" applicationId="6797-3437-8055-9394" mediaDir="/home/ubuntu/IdeaProjects/TransferBot/target" shortName="transferbot" publisher="Cavallium" publisherWeb="https://cavallium.it" version="1.0.0" macVolumeId="8bc612c51b1b1b24" javaMinVersion="11">
<jreBundles jdkProviderId="AdoptOpenJDK" release="openjdk15/jdk-15+36" />
</application>
<files>
<mountPoints>
<mountPoint id="157" />
</mountPoints>
<entries>
<dirEntry mountPoint="157" file="/home/ubuntu/IdeaProjects/TransferBot/target/lib" entryMode="subdir" subDirectory="lib" />
<fileEntry mountPoint="157" file="/home/ubuntu/IdeaProjects/TransferBot/target/TransferBot-1.0-SNAPSHOT.jar" />
</entries>
</files>
<launchers>
<launcher name="transferbot" id="58">
<executable name="transferbot" executableDir="." executableMode="gui" />
<java mainClass="it.cavallium/it.cavallium.App" mainMode="module">
<modulePath>
<directory location="classes" failOnError="false" />
<archive location="TransferBot-1.0-SNAPSHOT.jar" failOnError="false" />
<directory location="lib" failOnError="false" />
</modulePath>
</java>
</launcher>
</launchers>
<installerGui>
<applications>
<application id="installer" beanClass="com.install4j.runtime.beans.applications.InstallerApplication">
<startup>
<screen id="1" beanClass="com.install4j.runtime.beans.screens.StartupScreen" rollbackBarrierExitCode="0">
<actions>
<action id="22" beanClass="com.install4j.runtime.beans.actions.misc.RequestPrivilegesAction" actionElevationType="none" rollbackBarrierExitCode="0" />
</actions>
</screen>
</startup>
<screens>
<screen id="2" beanClass="com.install4j.runtime.beans.screens.WelcomeScreen" styleId="41" rollbackBarrierExitCode="0">
<actions>
<action id="7" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" multiExec="true">
<serializedBean>
<property name="excludedVariables" type="array" elementType="string" length="1">
<element index="0">sys.installationDir</element>
</property>
</serializedBean>
<condition>context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
</action>
</actions>
<formComponents>
<formComponent id="3" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
<serializedBean>
<property name="labelText" type="string">${form:welcomeMessage}</property>
</serializedBean>
<visibilityScript>!context.isConsole()</visibilityScript>
</formComponent>
<formComponent id="4" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
<serializedBean>
<property name="consoleScript">
<object class="com.install4j.api.beans.ScriptProperty">
<property name="value" type="string">String message = context.getMessage("ConsoleWelcomeLabel", context.getApplicationName());
return console.askOkCancel(message, true);
</property>
</object>
</property>
</serializedBean>
</formComponent>
<formComponent id="5" beanClass="com.install4j.runtime.beans.formcomponents.UpdateAlertComponent" useExternalParametrization="true" externalParametrizationName="Update Alert" externalParametrizationMode="include">
<externalParametrizationPropertyNames>
<propertyName>updateCheck</propertyName>
</externalParametrizationPropertyNames>
</formComponent>
<formComponent id="6" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetTop="20">
<serializedBean>
<property name="labelText" type="string">${i18n:ClickNext}</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
<screen id="8" beanClass="com.install4j.runtime.beans.screens.InstallationDirectoryScreen" rollbackBarrierExitCode="0">
<condition>!context.getBooleanVariable("sys.confirmedUpdateInstallation")</condition>
<actions>
<action id="11" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" multiExec="true">
<serializedBean>
<property name="excludedVariables" type="array" elementType="string" length="1">
<element index="0">sys.installationDir</element>
</property>
</serializedBean>
<condition>context.getVariable("sys.responseFile") == null</condition>
</action>
</actions>
<formComponents>
<formComponent id="9" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="25">
<serializedBean>
<property name="labelText" type="string">${i18n:SelectDirLabel(${compiler:sys.fullName})}</property>
</serializedBean>
</formComponent>
<formComponent id="10" beanClass="com.install4j.runtime.beans.formcomponents.InstallationDirectoryChooserComponent" useExternalParametrization="true" externalParametrizationName="Installation Directory Chooser" externalParametrizationMode="include">
<serializedBean>
<property name="requestFocus" type="boolean" value="true" />
</serializedBean>
<externalParametrizationPropertyNames>
<propertyName>suggestAppDir</propertyName>
<propertyName>validateApplicationId</propertyName>
<propertyName>existingDirWarning</propertyName>
<propertyName>checkWritable</propertyName>
<propertyName>manualEntryAllowed</propertyName>
<propertyName>checkFreeSpace</propertyName>
<propertyName>showRequiredDiskSpace</propertyName>
<propertyName>showFreeDiskSpace</propertyName>
<propertyName>allowSpacesOnUnix</propertyName>
<propertyName>validationScript</propertyName>
<propertyName>standardValidation</propertyName>
</externalParametrizationPropertyNames>
</formComponent>
</formComponents>
</screen>
<screen id="12" beanClass="com.install4j.runtime.beans.screens.ComponentsScreen" rollbackBarrierExitCode="0">
<formComponents>
<formComponent id="13" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent">
<serializedBean>
<property name="labelText" type="string">${i18n:SelectComponentsLabel2}</property>
</serializedBean>
<visibilityScript>!context.isConsole()</visibilityScript>
</formComponent>
<formComponent id="14" beanClass="com.install4j.runtime.beans.formcomponents.ComponentSelectorComponent" useExternalParametrization="true" externalParametrizationName="Installation Components" externalParametrizationMode="include">
<serializedBean>
<property name="fillVertical" type="boolean" value="true" />
</serializedBean>
<externalParametrizationPropertyNames>
<propertyName>selectionChangedScript</propertyName>
</externalParametrizationPropertyNames>
</formComponent>
</formComponents>
</screen>
<screen id="15" beanClass="com.install4j.runtime.beans.screens.InstallationScreen" rollbackBarrier="true" rollbackBarrierExitCode="0">
<actions>
<action id="17" beanClass="com.install4j.runtime.beans.actions.InstallFilesAction" actionElevationType="elevated" rollbackBarrierExitCode="0" failureStrategy="quit" errorMessage="${i18n:FileCorrupted}" />
<action id="18" beanClass="com.install4j.runtime.beans.actions.desktop.CreateProgramGroupAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
<serializedBean>
<property name="uninstallerMenuName" type="string">${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</property>
</serializedBean>
<condition>!context.getBooleanVariable("sys.programGroupDisabled")</condition>
</action>
<action id="19" beanClass="com.install4j.runtime.beans.actions.desktop.RegisterAddRemoveAction" actionElevationType="elevated" rollbackBarrierExitCode="0">
<serializedBean>
<property name="itemName" type="string">${compiler:sys.fullName} ${compiler:sys.version}</property>
</serializedBean>
</action>
</actions>
<formComponents>
<formComponent id="16" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent">
<serializedBean>
<property name="initialStatusMessage" type="string">${i18n:WizardPreparing}</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
<screen id="20" beanClass="com.install4j.runtime.beans.screens.FinishedScreen" styleId="41" rollbackBarrierExitCode="0" finishScreen="true">
<formComponents>
<formComponent id="21" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
<serializedBean>
<property name="labelText" type="string">${form:finishedMessage}</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
</screens>
</application>
<application id="uninstaller" beanClass="com.install4j.runtime.beans.applications.UninstallerApplication">
<serializedBean>
<property name="customMacosExecutableName" type="string">${i18n:UninstallerMenuEntry(${compiler:sys.fullName})}</property>
<property name="useCustomMacosExecutableName" type="boolean" value="true" />
</serializedBean>
<startup>
<screen id="23" beanClass="com.install4j.runtime.beans.screens.StartupScreen" rollbackBarrierExitCode="0">
<actions>
<action id="33" beanClass="com.install4j.runtime.beans.actions.misc.LoadResponseFileAction" rollbackBarrierExitCode="0" />
<action id="34" beanClass="com.install4j.runtime.beans.actions.misc.RequireInstallerPrivilegesAction" actionElevationType="none" rollbackBarrierExitCode="0" />
</actions>
</screen>
</startup>
<screens>
<screen id="24" beanClass="com.install4j.runtime.beans.screens.UninstallWelcomeScreen" styleId="41" rollbackBarrierExitCode="0">
<formComponents>
<formComponent id="25" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
<serializedBean>
<property name="labelText" type="string">${form:welcomeMessage}</property>
</serializedBean>
<visibilityScript>!context.isConsole()</visibilityScript>
</formComponent>
<formComponent id="26" beanClass="com.install4j.runtime.beans.formcomponents.ConsoleHandlerFormComponent">
<serializedBean>
<property name="consoleScript">
<object class="com.install4j.api.beans.ScriptProperty">
<property name="value" type="string">String message = context.getMessage("ConfirmUninstall", context.getApplicationName());
return console.askYesNo(message, true);
</property>
</object>
</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
<screen id="27" beanClass="com.install4j.runtime.beans.screens.UninstallationScreen" rollbackBarrierExitCode="0">
<actions>
<action id="29" beanClass="com.install4j.runtime.beans.actions.UninstallFilesAction" actionElevationType="elevated" rollbackBarrierExitCode="0" />
</actions>
<formComponents>
<formComponent id="28" beanClass="com.install4j.runtime.beans.formcomponents.ProgressComponent">
<serializedBean>
<property name="initialStatusMessage" type="string">${i18n:UninstallerPreparing}</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
<screen id="32" beanClass="com.install4j.runtime.beans.screens.UninstallFailureScreen" rollbackBarrierExitCode="0" finishScreen="true" />
<screen id="30" beanClass="com.install4j.runtime.beans.screens.UninstallSuccessScreen" styleId="41" rollbackBarrierExitCode="0" finishScreen="true">
<formComponents>
<formComponent id="31" beanClass="com.install4j.runtime.beans.formcomponents.MultilineLabelComponent" insetBottom="10">
<serializedBean>
<property name="labelText" type="string">${form:successMessage}</property>
</serializedBean>
</formComponent>
</formComponents>
</screen>
</screens>
</application>
</applications>
<styles defaultStyleId="35">
<style name="Standard" id="35" beanClass="com.install4j.runtime.beans.styles.FormStyle">
<formComponents>
<formComponent name="Header" id="36" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetTop="0" insetBottom="0">
<serializedBean>
<property name="styleId" type="string">48</property>
</serializedBean>
</formComponent>
<group name="Main" id="37" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup">
<beans>
<formComponent id="38" beanClass="com.install4j.runtime.beans.styles.ContentComponent" insetTop="10" insetLeft="20" insetBottom="10" insetRight="20" />
<formComponent name="Watermark" id="39" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" insetTop="0" insetLeft="5" insetBottom="0" useExternalParametrization="true" externalParametrizationName="Custom watermark" externalParametrizationMode="include">
<serializedBean>
<property name="enabledTitleText" type="boolean" value="false" />
<property name="labelText" type="string">install4j</property>
</serializedBean>
<externalParametrizationPropertyNames>
<propertyName>labelText</propertyName>
</externalParametrizationPropertyNames>
</formComponent>
<formComponent name="Footer" id="40" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetTop="0" insetBottom="0">
<serializedBean>
<property name="styleId" type="string">52</property>
</serializedBean>
</formComponent>
</beans>
</group>
</formComponents>
</style>
<style name="Banner" id="41" beanClass="com.install4j.runtime.beans.styles.FormStyle">
<formComponents>
<group id="42" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize banner image" externalParametrizationMode="include">
<serializedBean>
<property name="backgroundColor">
<object class="java.awt.Color">
<int>255</int>
<int>255</int>
<int>255</int>
<int>255</int>
</object>
</property>
<property name="borderSides">
<object class="com.install4j.runtime.beans.formcomponents.BorderSides">
<property name="bottom" type="boolean" value="true" />
</object>
</property>
<property name="imageEdgeBackgroundColor">
<object class="java.awt.Color">
<int>25</int>
<int>143</int>
<int>220</int>
<int>255</int>
</object>
</property>
<property name="imageEdgeBorder" type="boolean" value="true" />
<property name="imageFile">
<object class="com.install4j.api.beans.ExternalFile">
<string>${compiler:sys.install4jHome}/resource/styles/wizard.png</string>
</object>
</property>
<property name="insets">
<object class="java.awt.Insets">
<int>5</int>
<int>10</int>
<int>10</int>
<int>10</int>
</object>
</property>
</serializedBean>
<beans>
<formComponent id="43" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" insetTop="0">
<serializedBean>
<property name="labelFontSizePercent" type="int" value="130" />
<property name="labelFontStyle" type="enum" class="com.install4j.runtime.beans.formcomponents.FontStyle" value="BOLD" />
<property name="labelFontType" type="enum" class="com.install4j.runtime.beans.formcomponents.FontType" value="DERIVED" />
</serializedBean>
</formComponent>
<formComponent id="44" beanClass="com.install4j.runtime.beans.formcomponents.SeparatorComponent" />
<formComponent id="45" beanClass="com.install4j.runtime.beans.styles.ContentComponent" insetTop="10" insetBottom="0" />
</beans>
<externalParametrizationPropertyNames>
<propertyName>imageAnchor</propertyName>
<propertyName>imageEdgeBackgroundColor</propertyName>
<propertyName>imageFile</propertyName>
</externalParametrizationPropertyNames>
</group>
<formComponent id="46" beanClass="com.install4j.runtime.beans.styles.NestedStyleComponent" insetBottom="0">
<serializedBean>
<property name="styleId" type="string">52</property>
</serializedBean>
</formComponent>
</formComponents>
</style>
<group name="Style components" id="47" beanClass="com.install4j.runtime.beans.groups.StyleGroup">
<beans>
<style name="Standard header" id="48" beanClass="com.install4j.runtime.beans.styles.FormStyle">
<serializedBean>
<property name="fillVertical" type="boolean" value="false" />
<property name="standalone" type="boolean" value="false" />
<property name="verticalAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="NORTH" />
</serializedBean>
<formComponents>
<group id="49" beanClass="com.install4j.runtime.beans.groups.VerticalFormComponentGroup" useExternalParametrization="true" externalParametrizationName="Customize title bar" externalParametrizationMode="include">
<serializedBean>
<property name="backgroundColor">
<object class="java.awt.Color">
<int>255</int>
<int>255</int>
<int>255</int>
<int>255</int>
</object>
</property>
<property name="borderSides">
<object class="com.install4j.runtime.beans.formcomponents.BorderSides">
<property name="bottom" type="boolean" value="true" />
</object>
</property>
<property name="imageAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="NORTHEAST" />
<property name="imageEdgeBorderWidth" type="int" value="2" />
<property name="imageFile">
<object class="com.install4j.api.beans.ExternalFile">
<string>icon:${installer:sys.installerApplicationMode}_header.png</string>
</object>
</property>
<property name="imageInsets">
<object class="java.awt.Insets">
<int>0</int>
<int>5</int>
<int>1</int>
<int>1</int>
</object>
</property>
<property name="insets">
<object class="java.awt.Insets">
<int>0</int>
<int>20</int>
<int>0</int>
<int>10</int>
</object>
</property>
</serializedBean>
<beans>
<formComponent name="Title" id="50" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent">
<serializedBean>
<property name="labelFontStyle" type="enum" class="com.install4j.runtime.beans.formcomponents.FontStyle" value="BOLD" />
<property name="labelFontType" type="enum" class="com.install4j.runtime.beans.formcomponents.FontType" value="DERIVED" />
</serializedBean>
</formComponent>
<formComponent name="Subtitle" id="51" beanClass="com.install4j.runtime.beans.styles.ScreenTitleComponent" insetLeft="8">
<serializedBean>
<property name="titleType" type="enum" class="com.install4j.runtime.beans.styles.TitleType" value="SUB_TITLE" />
</serializedBean>
</formComponent>
</beans>
<externalParametrizationPropertyNames>
<propertyName>backgroundColor</propertyName>
<propertyName>foregroundColor</propertyName>
<propertyName>imageAnchor</propertyName>
<propertyName>imageFile</propertyName>
<propertyName>imageOverlap</propertyName>
</externalParametrizationPropertyNames>
</group>
</formComponents>
</style>
<style name="Standard footer" id="52" beanClass="com.install4j.runtime.beans.styles.FormStyle">
<serializedBean>
<property name="fillVertical" type="boolean" value="false" />
<property name="standalone" type="boolean" value="false" />
<property name="verticalAnchor" type="enum" class="com.install4j.api.beans.Anchor" value="SOUTH" />
</serializedBean>
<formComponents>
<group id="53" beanClass="com.install4j.runtime.beans.groups.HorizontalFormComponentGroup">
<serializedBean>
<property name="alignFirstLabel" type="boolean" value="false" />
<property name="insets">
<object class="java.awt.Insets">
<int>3</int>
<int>5</int>
<int>8</int>
<int>5</int>
</object>
</property>
</serializedBean>
<beans>
<formComponent id="54" beanClass="com.install4j.runtime.beans.formcomponents.SpringComponent" />
<formComponent name="Back button" id="55" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
<serializedBean>
<property name="buttonText" type="string">&lt; ${i18n:ButtonBack}</property>
<property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="PREVIOUS" />
</serializedBean>
</formComponent>
<formComponent name="Next button" id="56" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent">
<serializedBean>
<property name="buttonText" type="string">${i18n:ButtonNext} &gt;</property>
<property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="NEXT" />
</serializedBean>
</formComponent>
<formComponent name="Cancel button" id="57" beanClass="com.install4j.runtime.beans.styles.StandardControlButtonComponent" insetLeft="5">
<serializedBean>
<property name="buttonText" type="string">${i18n:ButtonCancel}</property>
<property name="controlButtonType" type="enum" class="com.install4j.api.context.ControlButtonType" value="CANCEL" />
</serializedBean>
</formComponent>
</beans>
</group>
</formComponents>
</style>
</beans>
</group>
</styles>
</installerGui>
<mediaSets>
<windows name="Windows" id="59">
<jreBundle jdkProviderId="AdoptOpenJDK" release="openjdk15/jdk-15+36" usePack200="false" overrideJdkRelease="true" jreBundleSource="generated" shared="true">
<modules>
<defaultModules set="jre" />
<jmodDirectory location="/home/ubuntu/.local/lib/jvm/windows-javafx-jmods-15/" />
</modules>
</jreBundle>
</windows>
<linuxDeb name="Linux Deb Archive" id="61" require64BitVm="true">
<jreBundle jdkProviderId="AdoptOpenJDK" release="openjdk15/jdk-15+36" overrideJdkRelease="true" jreBundleSource="generated" manualJreEntry="true">
<modules>
<defaultModules set="jre" />
<jmodDirectory location="/home/ubuntu/.local/lib/jvm/linux-javafx-jmods-15/" />
</modules>
</jreBundle>
</linuxDeb>
</mediaSets>
<buildIds>
<mediaSet refId="61" />
</buildIds>
</install4j>

86
pom.xml
View File

@ -7,8 +7,6 @@
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
</properties>
<repositories>
<repository>
@ -33,22 +31,65 @@
<artifactId>javafx-fxml</artifactId>
<version>15</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>15</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>15</version>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>15</version>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>it.tdlight</groupId>
<artifactId>tdlib-session-container</artifactId>
<version>4.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>15</version>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>15</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-base</artifactId>
<version>15</version>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>com.googlecode.libphonenumber</groupId>
<artifactId>libphonenumber</artifactId>
<version>8.12.11</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.8.1</version>
<configuration>
<release>15</release>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<!--
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
@ -65,22 +106,39 @@
<mainClass>it.cavallium/it.cavallium.App</mainClass>
</configuration>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.1</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation=
"org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>it.cavallium.Launcher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,41 @@
package it.cavallium;
import java.util.Objects;
import java.util.StringJoiner;
import reactor.util.annotation.Nullable;
public class AddUserBotResult {
private final String errorMessage;
private AddUserBotResult(@Nullable String errorMessage) {
this.errorMessage = errorMessage;
}
public static AddUserBotResult newSuccess() {
return new AddUserBotResult(null);
}
public static AddUserBotResult newFailed(String errorMessage) {
return new AddUserBotResult(Objects.requireNonNull(errorMessage));
}
public boolean success() {
return errorMessage == null;
}
public boolean failed() {
return errorMessage != null;
}
public String getErrorMessage() {
return errorMessage;
}
@Override
public String toString() {
return new StringJoiner(", ", AddUserBotResult.class.getSimpleName() + "[", "]")
.add("errorMessage='" + errorMessage + "'")
.toString();
}
}

View File

@ -1,5 +1,10 @@
package it.cavallium;
import io.vertx.core.Vertx;
import it.tdlight.common.Init;
import it.tdlight.common.utils.CantLoadLibrary;
import it.tdlight.tdlibsession.td.middle.TdClusterManager;
import java.time.Duration;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
@ -13,15 +18,33 @@ import java.io.IOException;
*/
public class App extends Application {
public static final String VERSION = "1.0.0";
private static TdClusterManager clusterManager;
private static TransferService transferService;
private static Scene scene;
@Override
public void start(Stage stage) throws IOException {
scene = new Scene(loadFXML("primary"), 640, 480);
public void start(Stage stage) throws IOException, CantLoadLibrary {
Init.start();
clusterManager = new TdClusterManager(null, null, Vertx.vertx(), null);
transferService = new TransferServiceImpl(clusterManager);
transferService.setApiId(94575);
transferService.setApiHash("a3406de8d171bb422bb6ddf3bbd800e2");
scene = new Scene(loadFXML("primary"), 800, 600);
stage.setScene(scene);
stage.show();
}
@Override
public void stop() throws Exception {
App.getTransferService().quit().block(Duration.ofSeconds(15));
System.exit(0);
}
static void setRoot(String fxml) throws IOException {
scene.setRoot(loadFXML(fxml));
}
@ -35,4 +58,7 @@ public class App extends Application {
launch();
}
public static TransferService getTransferService() {
return transferService;
}
}

View File

@ -0,0 +1,45 @@
package it.cavallium;
import java.util.StringJoiner;
public class BaseChatInfo {
private final long supergroupId;
private final String title;
public BaseChatInfo(long supergroupId, String title) {
this.supergroupId = supergroupId;
this.title = title;
}
public long getSupergroupId() {
return supergroupId;
}
public String getTitle() {
return title;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
BaseChatInfo that = (BaseChatInfo) o;
return supergroupId == that.supergroupId;
}
@Override
public int hashCode() {
return (int) (supergroupId ^ (supergroupId >>> 32));
}
@Override
public String toString() {
return (supergroupId + " " + title).trim();
}
}

View File

@ -0,0 +1,7 @@
package it.cavallium;
public class Launcher {
public static void main(String[] args) {
App.main(args);
}
}

View File

@ -0,0 +1,49 @@
package it.cavallium;
import java.util.Optional;
import java.util.function.Supplier;
import javafx.application.Platform;
import javafx.scene.control.Dialog;
import reactor.core.publisher.Mono;
public class MonoFxUtils {
public static <T> Mono<T> showAndWait(Dialog<T> dialog) {
return Mono.create(sink -> {
Platform.runLater(() -> {
dialog.setOnHidden(event -> {
if (dialog.getResult() != null) {
sink.success(dialog.getResult());
} else {
sink.success();
}
});
dialog.show();
});
});
}
public static <T> Mono<Optional<T>> showAndWaitOpt(Dialog<T> dialog) {
return Mono.create(sink -> {
Platform.runLater(() -> {
dialog.setOnHidden(event -> {
sink.success(Optional.<T>ofNullable(dialog.getResult()));
});
dialog.show();
});
});
}
public static <T> Mono<T> runLater(Supplier<Mono<T>> mono) {
return Mono.create(sink -> {
Platform.runLater(() -> {
mono.get().subscribe(value -> {
sink.success(value);
}, e -> {
sink.error(e);
}, () -> {
sink.success();
});
});
});
}
}

View File

@ -1,12 +1,262 @@
package it.cavallium;
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.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.ComboBox;
import javafx.scene.control.ListView;
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 reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class PrimaryController {
@FXML private Pane main;
@FXML private TextField phoneNumber;
@FXML private ListView<Text> userbotsList;
@FXML private ComboBox<BaseChatInfo> sourceGroupCombo;
@FXML private ComboBox<BaseChatInfo> destGroupCombo;
@FXML
private void switchToSecondary() throws IOException {
App.setRoot("secondary");
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(true, false)) {
srcItems.add(originSupergroups);
}
var destItems = destGroupCombo.getItems();
destItems.clear();
for (BaseChatInfo destSupergroups : App.getTransferService().getAdminSupergroups(false, true)) {
destItems.add(destSupergroups);
}
}
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;
}
});
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) -> {
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) {
}
@FXML
public void onDoTransfer(ActionEvent actionEvent) {
}
}

View File

@ -0,0 +1,7 @@
package it.cavallium;
public enum TChatType {
SUPERGROUP,
USER,
BASICGROUP
}

View File

@ -0,0 +1,112 @@
package it.cavallium;
import it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.Chat;
import it.tdlight.jni.TdApi.ChatMemberStatusAdministrator;
import it.tdlight.jni.TdApi.GetSupergroupFullInfo;
import it.tdlight.jni.TdApi.Object;
import it.tdlight.jni.TdApi.Supergroup;
import it.tdlight.jni.TdApi.SupergroupFullInfo;
import it.tdlight.jni.TdApi.Update;
import it.tdlight.jni.TdApi.UpdateNewChat;
import it.tdlight.jni.TdApi.UpdateSupergroup;
import it.tdlight.jni.TdApi.UpdateSupergroupFullInfo;
import it.tdlight.tdlibsession.td.TdResult;
import it.tdlight.tdlibsession.td.easy.AsyncTdEasy;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class TransferClient {
private final AsyncTdEasy client;
private final ConcurrentHashMap<Integer, Supergroup> supergroupInfos = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, SupergroupFullInfo> supergroupFullInfos = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Long, Chat> chats = new ConcurrentHashMap<>();
public TransferClient(AsyncTdEasy client) {
this.client = client;
this.client.getIncomingUpdates()
.subscribeOn(Schedulers.boundedElastic())
.flatMap(this::onUpdate)
.subscribe();
}
private Mono<Void> onUpdate(Update update) {
switch (update.getConstructor()) {
case UpdateSupergroup.CONSTRUCTOR:
return this.onUpdateSupergroup(((UpdateSupergroup) update).supergroup);
case UpdateSupergroupFullInfo.CONSTRUCTOR:
return this.onUpdateSupergroupFullInfo(
((UpdateSupergroupFullInfo) update).supergroupId,
((UpdateSupergroupFullInfo) update).supergroupFullInfo);
case UpdateNewChat.CONSTRUCTOR:
return this.onChat(((UpdateNewChat) update).chat);
default:
return Mono.empty();
}
}
private Mono<Void> onChat(Chat chat) {
if (chats.put(chat.id, chat) == null) {
// ok
}
return Mono.empty();
}
private Mono<Void> onUpdateSupergroup(Supergroup supergroup) {
if (supergroupInfos.put(supergroup.id, supergroup) == null) {
return this
.<SupergroupFullInfo>send(new GetSupergroupFullInfo(supergroup.id))
.filter(TdResult::succeeded)
.map(TdResult::result)
.flatMap(supergroupFullInfo -> onUpdateSupergroupFullInfo(supergroup.id, supergroupFullInfo));
}
return Mono.empty();
}
private Mono<Void> onUpdateSupergroupFullInfo(int id, SupergroupFullInfo supergroupFullInfo) {
if (supergroupFullInfos.put(id, supergroupFullInfo) == null) {
// ok
}
return Mono.empty();
}
/**
* Sends request to TDLib.
* @return The response or {@link TdApi.Error}.
*/
public <T extends Object> Mono<TdResult<T>> send(TdApi.Function request) {
return client.send(request);
}
public Set<BaseChatInfo> getAdminSupergroups(boolean canRestrictMembers, boolean canInviteUsers) {
return supergroupFullInfos.entrySet().stream().flatMap(entry -> {
int id = entry.getKey();
var fullInfo = entry.getValue();
var chatInfo = chats.get(TransferUtils.chatEntityIdToChatId(id, TChatType.SUPERGROUP));
var baseInfo = supergroupInfos.get(id);
if (chatInfo == null || baseInfo == null) {
return Stream.<BaseChatInfo>empty();
} else {
if (baseInfo.status.getConstructor() != ChatMemberStatusAdministrator.CONSTRUCTOR) {
return Stream.empty();
}
var adminStatus = (ChatMemberStatusAdministrator) baseInfo.status;
if (!adminStatus.canRestrictMembers && canRestrictMembers) {
return Stream.empty();
}
if (!adminStatus.canInviteUsers && canInviteUsers) {
return Stream.empty();
}
var baseChatInfo = new BaseChatInfo(id, chatInfo.title);
return Stream.<BaseChatInfo>of(baseChatInfo);
}
}).collect(Collectors.toSet());
}
}

View File

@ -0,0 +1,29 @@
package it.cavallium;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import it.tdlight.tdlibsession.td.easy.AsyncTdEasy;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import reactor.core.publisher.Mono;
public interface TransferService {
void setApiId(int apiId);
void setApiHash(String apiHash);
Mono<AddUserBotResult> addUserbot(PhoneNumber phoneNumber,
Function<AsyncTdEasy, Mono<Integer>> codeSupplier,
BiFunction<AsyncTdEasy, String, Mono<String>> otpSupplier,
Supplier<Mono<Void>> onUserbotClosed);
Mono<Void> quit();
Mono<Void> closeUserbot(PhoneNumber phoneNumber);
Set<PhoneNumber> getPhoneNumbers();
Set<BaseChatInfo> getAdminSupergroups(boolean canRestrictMembers, boolean canInviteUsers);
}

View File

@ -0,0 +1,226 @@
package it.cavallium;
import static it.cavallium.PrimaryController.getUserbotPhoneNumber;
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 it.tdlight.jni.TdApi;
import it.tdlight.jni.TdApi.AuthorizationStateClosed;
import it.tdlight.jni.TdApi.AuthorizationStateClosing;
import it.tdlight.jni.TdApi.AuthorizationStateLoggingOut;
import it.tdlight.jni.TdApi.AuthorizationStateReady;
import it.tdlight.jni.TdApi.Update;
import it.tdlight.jni.TdApi.UpdateSupergroup;
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 java.io.File;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.warp.commonutils.error.InitializationException;
import reactor.core.publisher.EmitterProcessor;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
public class TransferServiceImpl implements TransferService {
private final TdClusterManager clusterManager;
private final ConcurrentHashMap<Long, TransferClient> clients = new ConcurrentHashMap<>();
private int apiId;
private String apiHash;
public TransferServiceImpl(TdClusterManager clusterManager) {
this.clusterManager = clusterManager;
}
@Override
public void setApiId(int apiId) {
this.apiId = apiId;
}
@Override
public void setApiHash(String apiHash) {
this.apiHash = apiHash;
}
@Override
public Mono<AddUserBotResult> addUserbot(PhoneNumber phoneNumber,
Function<AsyncTdEasy, Mono<Integer>> codeSupplier,
BiFunction<AsyncTdEasy, String, Mono<String>> otpSupplier,
Supplier<Mono<Void>> onUserbotClosed) {
long phoneNumberLong = getLongPhoneNumber(phoneNumber);
if (clients.containsKey(phoneNumberLong)) {
return Mono.just(AddUserBotResult.newFailed("Userbot already added!"));
}
String alias = PhoneNumberUtil.getInstance().format(phoneNumber, PhoneNumberFormat.INTERNATIONAL);
return Mono
.fromCallable(() -> {
return AsyncTdMiddleDirect.getAndDeployInstance(clusterManager, alias, "" + phoneNumberLong);
})
.subscribeOn(Schedulers.boundedElastic())
.flatMap(v -> v)
.map(middle -> new AsyncTdEasy(middle, alias))
.flatMap(client -> {
return client
.create(TdEasySettings
.newBuilder()
.setUseMessageDatabase(false)
.setUseFileDatabase(false)
.setUseChatInfoDatabase(false)
.setApiId(apiId)
.setApiHash(apiHash)
.setEnableStorageOptimizer(false)
.setApplicationVersion(App.VERSION)
.setDatabaseDirectory("sessions" + File.separator + "userbot_" + phoneNumberLong)
.setIgnoreFileNames(true)
.setPhoneNumber(phoneNumberLong)
.setSystemLanguageCode("en")
.setDeviceModel(System.getProperty("os.name"))
.setSystemVersion(System.getProperty("os.version"))
.setParameterRequestHandler((parameter, parameterInfo) -> {
switch (parameter) {
case ASK_FIRST_NAME:
return Mono.just("FirstName");
case ASK_LAST_NAME:
return Mono.just("LastName");
case ASK_CODE:
return codeSupplier
.apply(client)
.map(i -> "" + i)
.switchIfEmpty(client
.send(new TdApi.Close())
.materialize()
.flatMap(signal -> onUserbotClosed.get().thenReturn(signal))
.dematerialize()
.then(Mono.empty()));
case ASK_PASSWORD:
return otpSupplier
.apply(client, ((ParameterInfoPasswordHint) parameterInfo).getHint())
.switchIfEmpty(client
.send(new TdApi.Close())
.materialize()
.flatMap(signal -> onUserbotClosed.get().thenReturn(signal))
.dematerialize()
.then(Mono.empty()));
case NOTIFY_LINK:
default:
return Mono.empty();
}
})
.build())
.then(Mono.defer(() -> {
var clientStateFlux = client.getState().skip(1).publish().autoConnect(2);
clientStateFlux
.filter(state -> state.getConstructor() == AuthorizationStateClosing.CONSTRUCTOR
|| state.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR
|| state.getConstructor() == AuthorizationStateLoggingOut.CONSTRUCTOR)
.take(1)
.singleOrEmpty()
.then()
.materialize()
.flatMap(signal -> onUserbotClosed.get().thenReturn(signal))
.dematerialize()
.subscribe(state -> System.out.println("State: " + state));
client.getIncomingUpdates()
.flatMap(this::onClientUpdate)
.subscribe(u -> System.out.println(u), e-> System.err.println(e));
return clientStateFlux
.filter(state -> state.getConstructor() == AuthorizationStateClosing.CONSTRUCTOR
|| state.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR
|| state.getConstructor() == AuthorizationStateLoggingOut.CONSTRUCTOR
|| state.getConstructor() == AuthorizationStateReady.CONSTRUCTOR)
.take(1)
.singleOrEmpty()
.doOnNext(state -> System.out.println("aState: " + state))
.handle((state, sink) -> {
if (state.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) {
sink.complete();
} else {
sink.error(new Exception(state.getClass().getSimpleName()));
}
})
.then();
}))
.doOnSuccess((_v) -> clients.put(phoneNumberLong, new TransferClient(client)));
})
.map(_v -> AddUserBotResult.newSuccess());
}
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));
if (client == null) {
return Mono.error(new Exception("Userbot " + phoneNumber + " was not found!"));
} else {
return MonoUtils.thenOrError(client.send(new TdApi.Close()));
}
}
@Override
public Set<PhoneNumber> getPhoneNumbers() {
var phonenumbers = new HashSet<PhoneNumber>();
clients.forEach((phoneNumberLong, client) -> {
try {
phonenumbers.add(getUserbotPhoneNumber("+" + phoneNumberLong));
} catch (NumberParseException e) {
// Can't happen
e.printStackTrace();
}
});
return phonenumbers;
}
@Override
public Set<BaseChatInfo> getAdminSupergroups(boolean canRestrictMembers, boolean canInviteUsers) {
var adminSupergroups = clients
.values()
.stream()
.flatMap((TransferClient transferClient) -> transferClient
.getAdminSupergroups(canRestrictMembers, canInviteUsers)
.stream())
.collect(Collectors.toSet());
return adminSupergroups;
}
@Override
public Mono<Void> quit() {
return Flux
.fromIterable(clients.values())
.flatMap(client -> client.send(new TdApi.Close()))
.log()
.collectList()
.then();
}
}

View File

@ -0,0 +1,28 @@
package it.cavallium;
public class TransferUtils {
public static int chatIdToChatEntityId(long id) {
if (id <= -1000000000000L) {
return (int) (Math.abs(id) - 1000000000000L);
}
if (id < 0) {
return (int) Math.abs(id);
} else {
return (int) Math.abs(id);
}
}
public static long chatEntityIdToChatId(int chatEntityId, TChatType chatType) {
switch (chatType) {
case BASICGROUP:
return -Math.abs(chatEntityId);
case SUPERGROUP:
return -1000000000000L - (long) Math.abs(chatEntityId);
case USER:
return Math.abs(chatEntityId);
default:
throw new UnsupportedOperationException("Unsupported chat id type: " + chatEntityId);
}
}
}

View File

@ -1,7 +0,0 @@
module it.cavallium {
requires javafx.controls;
requires javafx.fxml;
opens it.cavallium to javafx.fxml;
exports it.cavallium;
}

View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: it.cavallium.Launcher

View File

@ -1,16 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.Button?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ComboBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ProgressBar?>
<?import javafx.scene.control.SeparatorMenuItem?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.control.TableColumn?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToolBar?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.paint.Color?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.text.Text?>
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="it.cavallium.PrimaryController">
<children>
<Label text="Primary View" />
<Button fx:id="primaryButton" text="Switch to Secondary View" onAction="#switchToSecondary"/>
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
</padding>
<VBox fx:id="main" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="it.cavallium.PrimaryController">
<children>
<MenuBar VBox.vgrow="NEVER">
<menus>
<Menu mnemonicParsing="false" text="File">
<items>
<MenuItem mnemonicParsing="false" onAction="#openSettings" text="Preferences…" />
<SeparatorMenuItem mnemonicParsing="false" />
<MenuItem mnemonicParsing="false" onAction="#quit" text="Quit" />
</items>
</Menu>
</menus>
</MenuBar>
<SplitPane dividerPositions="0.8237442922374429" orientation="VERTICAL">
<items>
<SplitPane dividerPositions="0.5" focusTraversable="true" prefHeight="-1.0" prefWidth="-1.0">
<items>
<BorderPane>
<top>
<Label alignment="CENTER" prefWidth="-1.0" style="&#10;" text="Userbots" textAlignment="CENTER" wrapText="false" BorderPane.alignment="CENTER">
<font>
<Font size="18.0" fx:id="x1" />
</font>
<textFill>
<Color blue="0.624" green="0.624" red="0.624" fx:id="x2" />
</textFill>
</Label>
</top>
<center>
<ListView fx:id="userbotsList" BorderPane.alignment="CENTER" />
</center>
<bottom>
<ToolBar prefHeight="40.0" BorderPane.alignment="CENTER">
<items>
<TextField fx:id="phoneNumber" onAction="#addUserbot" prefHeight="25.0" promptText="Phone number">
<font>
<Font size="9.0" />
</font>
</TextField>
<Button mnemonicParsing="false" onAction="#addUserbot" prefHeight="25.0" text="Add" />
<Button fx:id="removeUserbotBtn" mnemonicParsing="false" onAction="#removeUserbot" prefHeight="25.0" text="Remove" />
</items>
</ToolBar>
</bottom>
</BorderPane>
<BorderPane>
<top>
<Label alignment="CENTER" prefWidth="-1.0" style="&#10;" text="Transfer" textAlignment="CENTER" wrapText="false" BorderPane.alignment="CENTER">
<font>
<Font size="18.0" fx:id="x11" />
</font>
<textFill>
<Color blue="0.624" green="0.624" red="0.624" fx:id="x21" />
</textFill>
</Label>
</top>
<center>
<GridPane BorderPane.alignment="CENTER">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" />
</columnConstraints>
<rowConstraints>
<RowConstraints />
<RowConstraints vgrow="SOMETIMES" />
<RowConstraints vgrow="SOMETIMES" />
</rowConstraints>
<children>
<VBox maxHeight="-Infinity" minHeight="-Infinity" spacing="10.0" GridPane.columnSpan="2">
<children>
<ComboBox fx:id="sourceGroupCombo" editable="true" onAction="#onSourceGroupAction" onHiding="#onSourceGroupHiding" onInputMethodTextChanged="#onSourceGroupTextChanged" onShowing="#onSourceGroupShowing" prefHeight="28.0" promptText="Source group" visibleRowCount="40" />
<ComboBox fx:id="destGroupCombo" editable="true" onAction="#onDestGroupAction" onHiding="#onDestGroupHiding" onInputMethodTextChanged="#onDestGroupTextChanged" onShowing="#onDestGroupShowing" prefHeight="28.0" promptText="Destination group" visibleRowCount="40" VBox.vgrow="ALWAYS" />
<Button fx:id="transferBtn" mnemonicParsing="false" onAction="#onDoTransfer" text="Transfer" />
</children>
<padding>
<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">
<columns>
<TableColumn minWidth="100.0" prefWidth="200.0" text="User" />
<TableColumn minWidth="100.0" prefWidth="100.0" text="Status" />
</columns>
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
</columnResizePolicy>
</TableView>
</children>
</GridPane>
</center>
</BorderPane>
</items>
</SplitPane>
<BorderPane>
<top>
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Log">
<BorderPane.margin>
<Insets left="5.0" right="5.0" top="5.0" />
</BorderPane.margin>
</Text>
</top>
<center>
<ListView prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
</items>
</SplitPane>
<HBox id="HBox" alignment="CENTER_LEFT" spacing="5.0" VBox.vgrow="NEVER">
<children>
<Label fx:id="statusTxt" maxHeight="1.7976931348623157E308" maxWidth="-1.0" text="Done." HBox.hgrow="ALWAYS">
<font>
<Font size="11.0" fx:id="x3" />
</font>
<textFill>
<Color blue="0.625" green="0.625" red="0.625" fx:id="x4" />
</textFill>
</Label>
<ProgressBar fx:id="statusBar" disable="true" maxWidth="1.7976931348623157E308" prefHeight="20.0" progress="0.0" HBox.hgrow="ALWAYS" />
<Label fx:id="statusPercentage" font="$x3" maxWidth="-1.0" text="0%" textFill="$x4" HBox.hgrow="NEVER" />
</children>
<padding>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</padding>
</HBox>
</children>
</VBox>

View File

@ -8,7 +8,7 @@
<VBox alignment="CENTER" spacing="20.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1" fx:controller="it.cavallium.SecondaryController">
<children>
<Label text="Secondary View" />
<Button fx:id="secondaryButton" text="Switch to Primary View" onAction="#switchToPrimary" />
<Button fx:id="secondaryButton" text="Close" onAction="#switchToPrimary" />
</children>
<padding>
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />