├── .editorconfig ├── .github └── workflows │ └── maven-publish.yml ├── .gitignore ├── .gitmodules ├── .media └── tdlight-logo.png ├── JTDLIB_LICENSE ├── LICENSE ├── README.md ├── bom └── pom.xml ├── example ├── pom.xml └── src │ └── main │ ├── java │ └── it │ │ └── tdlight │ │ └── example │ │ ├── AdvancedExample.java │ │ └── Example.java │ └── resources │ └── log4j2.xml ├── jitpack.yml ├── pom.xml └── tdlight-java ├── .gitignore ├── pom.xml └── src ├── main ├── java-templates │ └── it │ │ └── tdlight │ │ └── util │ │ └── LibraryVersion.java ├── java │ ├── it │ │ └── tdlight │ │ │ ├── ArrayUtil.java │ │ │ ├── AutoCleaningTelegramClient.java │ │ │ ├── ClientEventsHandler.java │ │ │ ├── ClientFactory.java │ │ │ ├── ClientFactoryImpl.java │ │ │ ├── ClientRegistrationEventHandler.java │ │ │ ├── ConstructorDetector.java │ │ │ ├── EventsHandler.java │ │ │ ├── ExceptionHandler.java │ │ │ ├── Handler.java │ │ │ ├── Init.java │ │ │ ├── InternalClient.java │ │ │ ├── InternalClientsState.java │ │ │ ├── InternalReactiveClient.java │ │ │ ├── Log.java │ │ │ ├── MultiHandler.java │ │ │ ├── NativeClientAccess.java │ │ │ ├── NativeResponseReceiver.java │ │ │ ├── ReactiveTelegramClient.java │ │ │ ├── Response.java │ │ │ ├── ResponseReceiver.java │ │ │ ├── ResultHandler.java │ │ │ ├── Signal.java │ │ │ ├── SignalListener.java │ │ │ ├── SignalType.java │ │ │ ├── Slf4JLogMessageHandler.java │ │ │ ├── TelegramClient.java │ │ │ ├── UpdatesHandler.java │ │ │ ├── client │ │ │ ├── APIToken.java │ │ │ ├── Authenticable.java │ │ │ ├── AuthenticationData.java │ │ │ ├── AuthenticationDataImpl.java │ │ │ ├── AuthenticationDataQrCode.java │ │ │ ├── AuthenticationSupplier.java │ │ │ ├── AuthorizationStateReadyGetMe.java │ │ │ ├── AuthorizationStateReadyLoadChats.java │ │ │ ├── AuthorizationStateWaitAuthenticationDataHandler.java │ │ │ ├── AuthorizationStateWaitCodeHandler.java │ │ │ ├── AuthorizationStateWaitEmailAddressHandler.java │ │ │ ├── AuthorizationStateWaitEmailCodeHandler.java │ │ │ ├── AuthorizationStateWaitForExit.java │ │ │ ├── AuthorizationStateWaitOtherDeviceConfirmationHandler.java │ │ │ ├── AuthorizationStateWaitPasswordHandler.java │ │ │ ├── AuthorizationStateWaitReady.java │ │ │ ├── AuthorizationStateWaitRegistrationHandler.java │ │ │ ├── AuthorizationStateWaitTdlibParametersHandler.java │ │ │ ├── ClientInteraction.java │ │ │ ├── CommandHandler.java │ │ │ ├── CommandsHandler.java │ │ │ ├── ConsoleInteractiveAuthenticationData.java │ │ │ ├── EmailAddressResetState.java │ │ │ ├── EmptyParameterInfo.java │ │ │ ├── GenericResultHandler.java │ │ │ ├── GenericUpdateHandler.java │ │ │ ├── InputParameter.java │ │ │ ├── MutableTelegramClient.java │ │ │ ├── ParameterInfo.java │ │ │ ├── ParameterInfoCode.java │ │ │ ├── ParameterInfoEmailAddress.java │ │ │ ├── ParameterInfoEmailCode.java │ │ │ ├── ParameterInfoNotifyLink.java │ │ │ ├── ParameterInfoPasswordHint.java │ │ │ ├── ParameterInfoTermsOfService.java │ │ │ ├── QrCodeTerminal.java │ │ │ ├── Result.java │ │ │ ├── ScannerClientInteraction.java │ │ │ ├── SequentialRequestsExecutor.java │ │ │ ├── SimpleAuthenticationSupplier.java │ │ │ ├── SimpleResultHandler.java │ │ │ ├── SimpleTelegramClient.java │ │ │ ├── SimpleTelegramClientBuilder.java │ │ │ ├── SimpleTelegramClientFactory.java │ │ │ ├── SimpleUpdateHandler.java │ │ │ ├── TDLibSettings.java │ │ │ ├── TelegramError.java │ │ │ ├── TemporaryMessageHandler.java │ │ │ └── TemporaryMessageURL.java │ │ │ ├── tdnative │ │ │ └── NativeClient.java │ │ │ └── util │ │ │ ├── CleanSupport.java │ │ │ ├── FutureSupport.java │ │ │ ├── IntSwapper.java │ │ │ ├── MapUtils.java │ │ │ ├── Native.java │ │ │ ├── NativeLibraryLoader.java │ │ │ ├── NativeLibraryUtil.java │ │ │ ├── ScannerUtils.java │ │ │ ├── SimpleIntQueue.java │ │ │ ├── SpinWaitSupport.java │ │ │ ├── TDLightBlockHoundIntegration.java │ │ │ └── UnsupportedNativeLibraryException.java │ └── module-info.java ├── java11 │ └── it │ │ └── tdlight │ │ └── util │ │ ├── CleanSupport.java │ │ ├── FutureSupport.java │ │ └── SpinWaitSupport.java └── resources │ └── META-INF │ └── services │ └── reactor.blockhound.integration.BlockHoundIntegration └── test └── java └── it └── tdlight ├── Event.java ├── HandleEventsTest.java └── Result.java /.github/workflows/maven-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java#apache-maven-with-a-settings-path 3 | 4 | name: Maven Package 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - develop 11 | tags: 12 | - 'v*' 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | include: 19 | - { os: ubuntu-20.04, arch: "linux/amd64" } 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - name: Branch name and revision 23 | id: branch_name 24 | run: | 25 | set -xeo pipefail 26 | 27 | export SOURCE_NAME="${GITHUB_REF#refs/*/}" 28 | export SOURCE_BRANCH="${GITHUB_REF#refs/heads/}" 29 | export SOURCE_TAG="${GITHUB_REF#refs/tags/}" 30 | export SOURCE_TAG_VERSION="${GITHUB_REF#refs/tags/v}" 31 | export REVISION="$SOURCE_TAG_VERSION" 32 | 33 | echo "SOURCE_TAG_VERSION=$SOURCE_TAG_VERSION" >> $GITHUB_ENV 34 | echo "REVISION=$REVISION" >> $GITHUB_ENV 35 | 36 | cat $GITHUB_ENV > github.env 37 | - uses: actions/checkout@v3 38 | with: 39 | submodules: "recursive" 40 | - name: Set up JDK 17 (Snapshot) 41 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 42 | uses: actions/setup-java@v3 43 | with: 44 | java-version: 17 45 | distribution: temurin 46 | cache: 'maven' 47 | server-id: mchv-snapshot-distribution 48 | server-username: MAVEN_USERNAME 49 | server-password: MAVEN_PASSWORD 50 | - name: Build and deploy to Maven (Snapshot) 51 | if: ${{ !startsWith(github.ref, 'refs/tags/v') }} 52 | shell: bash 53 | run: | 54 | set -xeo pipefail 55 | 56 | mvn -B -f bom/pom.xml -P "java8,java17" clean deploy 57 | echo "Done." 58 | env: 59 | MAVEN_USERNAME: ${{ secrets.MCHV_USERNAME }} 60 | MAVEN_PASSWORD: ${{ secrets.MCHV_TOKEN }} 61 | - name: Set up JDK 17 (Release) 62 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 63 | uses: actions/setup-java@v3 64 | with: 65 | java-version: 17 66 | distribution: temurin 67 | cache: 'maven' 68 | server-id: mchv-release-distribution 69 | server-username: MAVEN_USERNAME 70 | server-password: MAVEN_PASSWORD 71 | - name: Build and deploy to Maven (Release) 72 | if: ${{ startsWith(github.ref, 'refs/tags/v') }} 73 | shell: bash 74 | run: | 75 | set -xeo pipefail 76 | echo "REVISION: $REVISION" 77 | 78 | mvn -B -f bom/pom.xml -Drevision="${REVISION}" -P "java8,java17" clean deploy 79 | echo "Done." 80 | env: 81 | MAVEN_USERNAME: ${{ secrets.MCHV_USERNAME }} 82 | MAVEN_PASSWORD: ${{ secrets.MCHV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ----------- 2 | # Java ignore 3 | 4 | # Compiled class file 5 | *.class 6 | 7 | # Log file 8 | *.log 9 | 10 | # BlueJ files 11 | *.ctxt 12 | 13 | # Mobile Tools for Java (J2ME) 14 | .mtj.tmp/ 15 | 16 | # Package Files # 17 | *.jar 18 | *.war 19 | *.nar 20 | *.ear 21 | *.zip 22 | *.tar.gz 23 | *.rar 24 | 25 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 26 | hs_err_pid* 27 | 28 | 29 | # ------------- 30 | # Gradle ignore 31 | 32 | 33 | .gradle 34 | /build/ 35 | 36 | # Ignore Gradle GUI config 37 | gradle-app.setting 38 | 39 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 40 | !gradle-wrapper.jar 41 | 42 | # Cache of project 43 | .gradletasknamecache 44 | 45 | # ----------- 46 | # Idea ignore 47 | 48 | 49 | # Ignore Idea Projects directory 50 | .idea 51 | /target/ 52 | .project 53 | .classpath 54 | /.settings/ 55 | 56 | docs/ 57 | tdlib.iml 58 | tdlight.iml 59 | tdlight-java.iml 60 | example/example-java.iml 61 | 62 | src/main/jni/jnibuild/ 63 | src/main/jni/jtdlib/jnibuild/ 64 | src/main/jni/jtdlib/build/ 65 | 66 | src/main/jni/bin/ 67 | 68 | /tdapi-build 69 | /bin-td 70 | /build-td 71 | /build-tdlight 72 | /dependencies/tdlight/bin-td-cross 73 | /src/main/java/it/tdlight/jni/TdApi.java 74 | tdlib/.flattened-pom.xml 75 | tdlight/.flattened-pom.xml 76 | example/target/ 77 | 78 | .ci-friendly-pom.xml 79 | 80 | bom/.settings/ 81 | bom/.project 82 | bom/.classpath 83 | parent/.settings/ 84 | parent/.project 85 | parent/.classpath 86 | /.settings/ 87 | /.project 88 | /.classpath 89 | 90 | .flattened-pom.xml 91 | 92 | *.iml 93 | 94 | 95 | tdlight-java/target-snapshot/ 96 | 97 | tdlight-java-8/target-snapshot/ 98 | /example-tdlight-session/ 99 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdlight-team/tdlight-java/cf489dce474f58c815022a7ae268e55b9076e97e/.gitmodules -------------------------------------------------------------------------------- /.media/tdlight-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tdlight-team/tdlight-java/cf489dce474f58c815022a7ae268e55b9076e97e/.media/tdlight-logo.png -------------------------------------------------------------------------------- /JTDLIB_LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2018 Ernesto Castellotti 2 | 3 | Ernesto Castellotti is the copyright holder of this software, for 4 | any need, do not hesitate to contact him. 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU Lesser General Public License as published by 8 | the Free Software Foundation, either version 3 of the License. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public License 16 | along with this program in the file called "LICENSE". If not, contact the copyright holder. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | TDLight logo 3 |

TDLight Java

4 |

Complete Bot and Userbot Telegram library written in Java, based on TDLib

5 | 6 | Maven package 7 | 8 | Release 9 | 10 | 11 | JProfiler 12 | 13 |
14 |
15 | 16 | ## 💻 Supported platforms 17 | 18 | **Java versions**: from Java 17 to Java 21+ (Java 8 to 16 is supported if you use the following dependency classifier: `jdk8`) 19 | 20 | **Operating systems**: Linux, Windows, MacOS 21 | 22 | **CPU architectures**: 23 | 24 | - amd64 (Linux, Windows, MacOS) 25 | - armhf (Linux) 26 | - arm64 (Linux, MacOS) 27 | - ppc64el (Linux) 28 | - riscv64 (linux) 29 | 30 | ## 📚 Required libraries 31 | - **Linux: OpenSSL1/OpenSSL3, zlib, (libc++ if you use clang)** 32 | - **MacOS: OpenSSL** 33 | - **Windows: [Microsoft Visual C++ Redistributable](https://aka.ms/vs/17/release/vc_redist.x64.exe)** 34 | 35 | ### Install OpenSSL on macOS 36 | 37 | You must install `openssl@3` using the brew package manager , then link openssl 38 | to `/usr/local/opt/openssl` 39 | 40 | If you don't know how to do this, type the following commands in your terminal: 41 | 42 | ```bash 43 | brew install openssl@3 44 | ln -sf /usr/local/Cellar/openssl@3/3.0.0 /usr/local/opt/openssl 45 | ``` 46 | 47 | ## 📚 How to use the library 48 | 49 | ### Setting up the library using Maven 50 | 51 | If you are using Maven, edit your `pom.xml` file as below: 52 | 53 | ```xml 54 | 55 | 56 | 57 | 58 | 59 | 60 | mchv 61 | MCHV Apache Maven Packages 62 | https://mvn.mchv.eu/repository/mchv/ 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | it.tdlight 73 | tdlight-java-bom 74 | VERSION 75 | pom 76 | import 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | it.tdlight 86 | tdlight-java 87 | 88 | 89 | 90 | 91 | 92 | it.tdlight 93 | tdlight-natives 94 | linux_amd64_gnu_ssl1 95 | 96 | 97 | 98 | 99 | it.tdlight 100 | tdlight-natives 101 | windows_amd64 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | Replace `VERSION` with the latest release version, you can find 112 | it [here](https://github.com/tdlight-team/tdlight-java/releases). 113 | 114 | ## Setting up the library using Gradle 115 | 116 | If you are using Gradle, add the following lines into your `build.gradle` file 117 | 118 | ```groovy 119 | repositories { 120 | maven { url "https://mvn.mchv.eu/repository/mchv/" } 121 | } 122 | dependencies { 123 | // import the BOM 124 | implementation platform('it.tdlight:tdlight-java-bom:VERSION') 125 | 126 | // do not specify the versions on the dependencies below! 127 | implementation group: 'it.tdlight', name: 'tdlight-java' // Java 8 is supported if you use the following dependency classifier: `jdk8` 128 | implementation group: 'it.tdlight', name: 'tdlight-natives', classifier: 'linux_amd64_gnu_ssl1' 129 | // Include other native classifiers, for example linux_amd64_clang_ssl3, macos_amd64, ... --> 130 | } 131 | ``` 132 | 133 | Replace `VERSION` with the latest release version, you can find 134 | it [here](https://github.com/tdlight-team/tdlight-java/releases). 135 | 136 | ## ⚒ Native dependencies 137 | 138 | To use TDLight Java you need to include the native libraries, by specifying one of the following classifier for each tdlight-natives dependency: 139 | 140 | - `linux_amd64_clang_ssl3` 141 | - `linux_amd64_gnu_ssl1` 142 | - `linux_amd64_gnu_ssl3` 143 | - `linux_arm64_clang_ssl3` 144 | - `linux_arm64_gnu_ssl1` 145 | - `linux_arm64_gnu_ssl3` 146 | - `linux_armhf_gnu_ssl1` 147 | - `linux_armhf_gnu_ssl3` 148 | - `linux_ppc64el_gnu_ssl3` 149 | - `linux_riscv64_gnu_ssl3` 150 | - `windows_amd64` 151 | - `macos_arm64` 152 | - `macos_amd64` 153 | 154 | Advanced: If you want to use a different precompiled native, please set the java property `it.tdlight.native.workdir`. (Please note that you must build [this](https://github.com/tdlight-team/tdlight-java-natives), you can't put random precompiled tdlib binaries found on the internet) 155 | 156 | ## Usage 157 | 158 | An example on how to use TDLight Java can be found 159 | here: [Example.java](https://github.com/tdlight-team/tdlight-java/blob/master/example/src/main/java/it/tdlight/example/Example.java) 160 | 161 | ### Advanced usage 162 | 163 | If you want to disable the automatic runtime shutdown hook, you should set the property `it.tdlight.enableShutdownHooks` 164 | to `false` 165 | 166 | ### TDLight methods documentation 167 | 168 | [TdApi JavaDoc](https://tdlight-team.github.io/tdlight-docs) 169 | 170 | ### TDLight extended features 171 | 172 | TDLight has some extended features compared to TDLib, that you can see on 173 | the [TDLight official repository](https://github.com/tdlight-team/tdlight#tdlight-extra-features). 174 | 175 | ## About 176 | 177 | ### **License** 178 | 179 | TDLight is licensed by Andrea Cavalli under the terms of the GNU Lesser General Public License 3 180 | 181 | ### **Libraries licenses** 182 | 183 | JTDLib is licensed by Ernesto Castellotti under the terms of the GNU Lesser General Public 184 | License 3 185 | 186 | TDLib is licensed by Aliaksei Levin and Arseny Smirnov under the terms of the 187 | Boost Software License 188 | 189 | OpenSSL is licensed under the terms of Apache License v2 190 | 191 | Zlib is licensed under the terms of Zlib license 192 | -------------------------------------------------------------------------------- /bom/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | it.tdlight 5 | tdlight-java-bom 6 | ${revision} 7 | pom 8 | TDLight Java BOM 9 | 10 | 3.0.0.0-SNAPSHOT 11 | 4.0.518 12 | 4.0.488 13 | 1.8 14 | 1.8 15 | 16 | 17 | 18 | mchv-release 19 | MCHV Release Apache Maven Packages 20 | https://mvn.mchv.eu/repository/mchv 21 | 22 | 23 | mchv-snapshot 24 | MCHV Snapshot Apache Maven Packages 25 | https://mvn.mchv.eu/repository/mchv-snapshot 26 | 27 | 28 | 29 | 30 | mchv-release-distribution 31 | MCHV Release Apache Maven Packages Distribution 32 | https://mvn.mchv.eu/repository/mchv 33 | 34 | 35 | mchv-snapshot-distribution 36 | MCHV Snapshot Apache Maven Packages Distribution 37 | https://mvn.mchv.eu/repository/mchv-snapshot 38 | 39 | 40 | 41 | scm:git:https://git.ignuranza.net/tdlight-team/tdlight-java.git 42 | scm:git:https://git.ignuranza.net/tdlight-team/tdlight-java.git 43 | HEAD 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | it.tdlight 53 | tdlight-api 54 | ${tdlight.api.version} 55 | 56 | 57 | it.tdlight 58 | tdlight-api 59 | ${tdlight.api.version} 60 | legacy 61 | 62 | 63 | it.tdlight 64 | tdlight-java 65 | ${revision} 66 | 67 | 68 | it.tdlight 69 | tdlight-java 70 | ${revision} 71 | jdk8 72 | 73 | 74 | it.tdlight 75 | tdlight-java-parent 76 | ${revision} 77 | 78 | 79 | 80 | 81 | 82 | it.tdlight 83 | tdlight-natives 84 | ${tdlight.natives.version} 85 | linux_amd64_clang_ssl3 86 | 87 | 88 | it.tdlight 89 | tdlight-natives 90 | ${tdlight.natives.version} 91 | linux_amd64_gnu_ssl1 92 | 93 | 94 | it.tdlight 95 | tdlight-natives 96 | ${tdlight.natives.version} 97 | linux_amd64_gnu_ssl3 98 | 99 | 100 | it.tdlight 101 | tdlight-natives 102 | ${tdlight.natives.version} 103 | linux_arm64_clang_ssl3 104 | 105 | 106 | it.tdlight 107 | tdlight-natives 108 | ${tdlight.natives.version} 109 | linux_arm64_gnu_ssl1 110 | 111 | 112 | it.tdlight 113 | tdlight-natives 114 | ${tdlight.natives.version} 115 | linux_arm64_gnu_ssl3 116 | 117 | 118 | it.tdlight 119 | tdlight-natives 120 | ${tdlight.natives.version} 121 | linux_armhf_gnu_ssl1 122 | 123 | 124 | it.tdlight 125 | tdlight-natives 126 | ${tdlight.natives.version} 127 | linux_armhf_gnu_ssl3 128 | 129 | 130 | it.tdlight 131 | tdlight-natives 132 | ${tdlight.natives.version} 133 | linux_ppc64el_gnu_ssl3 134 | 135 | 136 | it.tdlight 137 | tdlight-natives 138 | ${tdlight.natives.version} 139 | linux_riscv64_gnu_ssl3 140 | 141 | 142 | it.tdlight 143 | tdlight-natives 144 | ${tdlight.natives.version} 145 | windows_amd64 146 | 147 | 148 | it.tdlight 149 | tdlight-natives 150 | ${tdlight.natives.version} 151 | macos_amd64 152 | 153 | 154 | it.tdlight 155 | tdlight-natives 156 | ${tdlight.natives.version} 157 | macos_arm64 158 | 159 | 160 | 161 | 162 | 163 | ../ 164 | 165 | 166 | 167 | 168 | 169 | org.codehaus.mojo 170 | flatten-maven-plugin 171 | 1.1.0 172 | 173 | true 174 | oss 175 | 176 | resolve 177 | resolve 178 | resolve 179 | 180 | 181 | 182 | 183 | flatten 184 | process-resources 185 | 186 | flatten 187 | 188 | 189 | 190 | flatten.clean 191 | clean 192 | 193 | clean 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | -------------------------------------------------------------------------------- /example/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | it.tdlight 5 | example-java 6 | 3.0.0.0-SNAPSHOT 7 | TDLight Java Example 8 | jar 9 | 10 | UTF-8 11 | 12 | 13 | 14 | mchv-release 15 | MCHV Release Apache Maven Packages 16 | https://mvn.mchv.eu/repository/mchv 17 | 18 | 19 | mchv-snapshot 20 | MCHV Snapshot Apache Maven Packages 21 | https://mvn.mchv.eu/repository/mchv-snapshot 22 | 23 | 24 | 25 | 26 | 27 | it.tdlight 28 | tdlight-java-bom 29 | 3.4.0+td.1.8.26 30 | pom 31 | import 32 | 33 | 34 | 35 | 36 | 37 | 38 | it.tdlight 39 | tdlight-java 40 | jdk8 41 | 42 | 43 | 44 | 45 | it.tdlight 46 | tdlight-natives 47 | linux_amd64_gnu_ssl1 48 | 49 | 50 | it.tdlight 51 | tdlight-natives 52 | linux_amd64_clang_ssl3 53 | 54 | 55 | it.tdlight 56 | tdlight-natives 57 | linux_amd64_gnu_ssl3 58 | 59 | 60 | it.tdlight 61 | tdlight-natives 62 | windows_amd64 63 | 64 | 65 | it.tdlight 66 | tdlight-natives 67 | macos_amd64 68 | 69 | 70 | it.tdlight 71 | tdlight-natives 72 | macos_arm64 73 | 74 | 75 | 76 | 77 | org.apache.logging.log4j 78 | log4j-core 79 | 2.22.1 80 | 81 | 82 | org.apache.logging.log4j 83 | log4j-slf4j2-impl 84 | 2.22.1 85 | 86 | 87 | junit 88 | junit 89 | 90 | 91 | org.hamcrest 92 | hamcrest-core 93 | 94 | 95 | 96 | 97 | com.lmax 98 | disruptor 99 | 3.4.4 100 | 101 | 102 | 103 | 104 | 105 | maven-clean-plugin 106 | 3.3.1 107 | 108 | 109 | maven-resources-plugin 110 | 3.3.1 111 | 112 | 113 | maven-compiler-plugin 114 | 3.11.0 115 | 116 | UTF-8 117 | 17 118 | 17 119 | 120 | 121 | 122 | maven-jar-plugin 123 | 3.3.0 124 | 125 | 126 | maven-install-plugin 127 | 3.1.1 128 | 129 | 130 | maven-deploy-plugin 131 | 3.1.1 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /example/src/main/java/it/tdlight/example/AdvancedExample.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.example; 2 | 3 | import it.tdlight.Init; 4 | import it.tdlight.Log; 5 | import it.tdlight.Slf4JLogMessageHandler; 6 | import it.tdlight.TelegramClient; 7 | import it.tdlight.jni.TdApi; 8 | import it.tdlight.ClientFactory; 9 | import it.tdlight.util.UnsupportedNativeLibraryException; 10 | 11 | /** 12 | * This is an advanced example that uses directly the native client without using the SimpleClient implementation 13 | */ 14 | public class AdvancedExample { 15 | 16 | public static void main(String[] args) throws UnsupportedNativeLibraryException { 17 | // Initialize TDLight native libraries 18 | Init.init(); 19 | 20 | // Set the log level 21 | Log.setLogMessageHandler(1, new Slf4JLogMessageHandler()); 22 | 23 | // Create a client manager, it should be closed before shutdown 24 | ClientFactory clientManager = ClientFactory.create(); 25 | 26 | // Create a client, it should be closed before shutdown 27 | TelegramClient client = clientManager.createClient(); 28 | 29 | // Initialize the client 30 | client.initialize(AdvancedExample::onUpdate, AdvancedExample::onUpdateError, AdvancedExample::onError); 31 | 32 | // Here you can use the client. 33 | 34 | // An example on how to use TDLight java can be found here: 35 | // https://github.com/tdlight-team/tdlight-java/blob/master/example/src/main/java/it.tdlight.example/Example.java 36 | 37 | // The documentation of the TDLight methods can be found here: 38 | // https://tdlight-team.github.io/tdlight-docs 39 | } 40 | 41 | private static void onUpdate(TdApi.Object object) { 42 | TdApi.Update update = (TdApi.Update) object; 43 | System.out.println("Received update: " + update); 44 | } 45 | 46 | private static void onUpdateError(Throwable exception) { 47 | System.out.println("Received an error from updates:"); 48 | exception.printStackTrace(); 49 | } 50 | 51 | private static void onError(Throwable exception) { 52 | System.out.println("Received an error:"); 53 | exception.printStackTrace(); 54 | } 55 | } -------------------------------------------------------------------------------- /example/src/main/java/it/tdlight/example/Example.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.example; 2 | 3 | import it.tdlight.Init; 4 | import it.tdlight.Log; 5 | import it.tdlight.Slf4JLogMessageHandler; 6 | import it.tdlight.client.APIToken; 7 | import it.tdlight.client.AuthenticationSupplier; 8 | import it.tdlight.client.SimpleAuthenticationSupplier; 9 | import it.tdlight.client.SimpleTelegramClient; 10 | import it.tdlight.client.SimpleTelegramClientBuilder; 11 | import it.tdlight.client.SimpleTelegramClientFactory; 12 | import it.tdlight.client.TDLibSettings; 13 | import it.tdlight.jni.TdApi; 14 | import it.tdlight.jni.TdApi.AuthorizationState; 15 | import it.tdlight.jni.TdApi.CreatePrivateChat; 16 | import it.tdlight.jni.TdApi.FormattedText; 17 | import it.tdlight.jni.TdApi.GetChat; 18 | import it.tdlight.jni.TdApi.InputMessageText; 19 | import it.tdlight.jni.TdApi.Message; 20 | import it.tdlight.jni.TdApi.MessageContent; 21 | import it.tdlight.jni.TdApi.MessageSenderUser; 22 | import it.tdlight.jni.TdApi.SendMessage; 23 | import it.tdlight.jni.TdApi.TextEntity; 24 | import java.nio.file.Path; 25 | import java.nio.file.Paths; 26 | import java.util.concurrent.TimeUnit; 27 | 28 | /** 29 | * Example class for TDLight Java 30 | *

31 | * The documentation of the TDLight functions can be found here 32 | */ 33 | public final class Example { 34 | 35 | public static void main(String[] args) throws Exception { 36 | long adminId = Integer.getInteger("it.tdlight.example.adminid", 667900586); 37 | 38 | // Initialize TDLight native libraries 39 | Init.init(); 40 | 41 | // Set the log level 42 | Log.setLogMessageHandler(1, new Slf4JLogMessageHandler()); 43 | 44 | // Create the client factory (You can create more than one client, 45 | // BUT only a single instance of ClientFactory is allowed globally! 46 | // You must reuse it if you want to create more than one client!) 47 | try (SimpleTelegramClientFactory clientFactory = new SimpleTelegramClientFactory()) { 48 | // Obtain the API token 49 | // 50 | // var apiToken = new APIToken(your-api-id-here, "your-api-hash-here"); 51 | // 52 | APIToken apiToken = APIToken.example(); 53 | 54 | 55 | // Configure the client 56 | TDLibSettings settings = TDLibSettings.create(apiToken); 57 | 58 | // Configure the session directory. 59 | // After you authenticate into a session, the authentication will be skipped from the next restart! 60 | // If you want to ensure to match the authentication supplier user/bot with your session user/bot, 61 | // you can name your session directory after your user id, for example: "tdlib-session-id12345" 62 | Path sessionPath = Paths.get("example-tdlight-session"); 63 | settings.setDatabaseDirectoryPath(sessionPath.resolve("data")); 64 | settings.setDownloadedFilesDirectoryPath(sessionPath.resolve("downloads")); 65 | 66 | // Prepare a new client builder 67 | SimpleTelegramClientBuilder clientBuilder = clientFactory.builder(settings); 68 | 69 | // Configure the authentication info 70 | // Replace with AuthenticationSupplier.consoleLogin(), or .user(xxx), or .bot(xxx); 71 | SimpleAuthenticationSupplier authenticationData = AuthenticationSupplier.testUser(7381); 72 | // This is an example, remove this line to use the real telegram datacenters! 73 | settings.setUseTestDatacenter(true); 74 | 75 | // Create and start the client 76 | try (var app = new ExampleApp(clientBuilder, authenticationData, adminId)) { 77 | // Get me 78 | TdApi.User me = app.getClient().getMeAsync().get(1, TimeUnit.MINUTES); 79 | 80 | // Create the "saved messages" chat 81 | var savedMessagesChat = app.getClient().send(new CreatePrivateChat(me.id, true)).get(1, TimeUnit.MINUTES); 82 | 83 | // Send a test message 84 | var req = new SendMessage(); 85 | req.chatId = savedMessagesChat.id; 86 | var txt = new InputMessageText(); 87 | txt.text = new FormattedText("TDLight test", new TextEntity[0]); 88 | req.inputMessageContent = txt; 89 | Message result = app.getClient().sendMessage(req, true).get(1, TimeUnit.MINUTES); 90 | System.out.println("Sent message:" + result); 91 | } 92 | } 93 | } 94 | 95 | public static class ExampleApp implements AutoCloseable { 96 | 97 | private final SimpleTelegramClient client; 98 | 99 | /** 100 | * Admin user id, used by the stop command example 101 | */ 102 | private final long adminId; 103 | 104 | public ExampleApp(SimpleTelegramClientBuilder clientBuilder, 105 | SimpleAuthenticationSupplier authenticationData, 106 | long adminId) { 107 | this.adminId = adminId; 108 | 109 | // Add an example update handler that prints when the bot is started 110 | clientBuilder.addUpdateHandler(TdApi.UpdateAuthorizationState.class, this::onUpdateAuthorizationState); 111 | 112 | // Add an example command handler that stops the bot 113 | clientBuilder.addCommandHandler("stop", this::onStopCommand); 114 | 115 | // Add an example update handler that prints every received message 116 | clientBuilder.addUpdateHandler(TdApi.UpdateNewMessage.class, this::onUpdateNewMessage); 117 | 118 | // Build the client 119 | this.client = clientBuilder.build(authenticationData); 120 | } 121 | 122 | @Override 123 | public void close() throws Exception { 124 | client.close(); 125 | } 126 | 127 | public SimpleTelegramClient getClient() { 128 | return client; 129 | } 130 | 131 | /** 132 | * Print the bot status 133 | */ 134 | private void onUpdateAuthorizationState(TdApi.UpdateAuthorizationState update) { 135 | AuthorizationState authorizationState = update.authorizationState; 136 | if (authorizationState instanceof TdApi.AuthorizationStateReady) { 137 | System.out.println("Logged in"); 138 | } else if (authorizationState instanceof TdApi.AuthorizationStateClosing) { 139 | System.out.println("Closing..."); 140 | } else if (authorizationState instanceof TdApi.AuthorizationStateClosed) { 141 | System.out.println("Closed"); 142 | } else if (authorizationState instanceof TdApi.AuthorizationStateLoggingOut) { 143 | System.out.println("Logging out..."); 144 | } 145 | } 146 | 147 | /** 148 | * Print new messages received via updateNewMessage 149 | */ 150 | private void onUpdateNewMessage(TdApi.UpdateNewMessage update) { 151 | // Get the message content 152 | MessageContent messageContent = update.message.content; 153 | 154 | // Get the message text 155 | String text; 156 | if (messageContent instanceof TdApi.MessageText messageText) { 157 | // Get the text of the text message 158 | text = messageText.text.text; 159 | } else { 160 | // We handle only text messages, the other messages will be printed as their type 161 | text = String.format("(%s)", messageContent.getClass().getSimpleName()); 162 | } 163 | 164 | long chatId = update.message.chatId; 165 | 166 | // Get the chat title 167 | client.send(new TdApi.GetChat(chatId)) 168 | // Use the async completion handler, to avoid blocking the TDLib response thread accidentally 169 | .whenCompleteAsync((chatIdResult, error) -> { 170 | if (error != null) { 171 | // Print error 172 | System.err.printf("Can't get chat title of chat %s%n", chatId); 173 | error.printStackTrace(System.err); 174 | } else { 175 | // Get the chat name 176 | String title = chatIdResult.title; 177 | // Print the message 178 | System.out.printf("Received new message from chat %s (%s): %s%n", title, chatId, text); 179 | } 180 | }); 181 | } 182 | 183 | /** 184 | * Close the bot if the /stop command is sent by the administrator 185 | */ 186 | private void onStopCommand(TdApi.Chat chat, TdApi.MessageSender commandSender, String arguments) { 187 | // Check if the sender is the admin 188 | if (isAdmin(commandSender)) { 189 | // Stop the client 190 | System.out.println("Received stop command. closing..."); 191 | client.sendClose(); 192 | } 193 | } 194 | 195 | /** 196 | * Check if the command sender is admin 197 | */ 198 | public boolean isAdmin(TdApi.MessageSender sender) { 199 | if (sender instanceof MessageSenderUser messageSenderUser) { 200 | return messageSenderUser.userId == adminId; 201 | } else { 202 | return false; 203 | } 204 | } 205 | 206 | } 207 | } -------------------------------------------------------------------------------- /example/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | jdk: 2 | - openjdk11 3 | before_install: 4 | - cd tdlight 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | tdlight-java-parent 6 | pom 7 | 8 | it.tdlight 9 | ${revision} 10 | tdlight-java-bom 11 | bom/pom.xml 12 | 13 | 14 | 3.0.0.0-SNAPSHOT 15 | 3.5.1 16 | 1.0.4 17 | 2.0.5 18 | 5.9.2 19 | 8.5.12 20 | 21 | 22 | tdlight-java 23 | 24 | 25 | 26 | 27 | 28 | org.codehaus.mojo 29 | flatten-maven-plugin 30 | 1.1.0 31 | 32 | true 33 | oss 34 | 35 | 36 | 37 | flatten 38 | process-resources 39 | 40 | flatten 41 | 42 | 43 | 44 | flatten.clean 45 | clean 46 | 47 | clean 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /tdlight-java/.gitignore: -------------------------------------------------------------------------------- 1 | # ---> Eclipse 2 | *.pydevproject 3 | .metadata 4 | .gradle 5 | bin/ 6 | tmp/ 7 | *.tmp 8 | *.bak 9 | *.swp 10 | *~.nib 11 | local.properties 12 | .settings/ 13 | .loadpath 14 | 15 | # Eclipse Core 16 | .project 17 | 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # JDT-specific (Eclipse Java Development Tools) 28 | .classpath 29 | 30 | # Java annotation processor (APT) 31 | .factorypath 32 | 33 | # PDT-specific 34 | .buildpath 35 | 36 | # sbteclipse plugin 37 | .target 38 | 39 | # TeXlipse plugin 40 | .texlipse 41 | 42 | # ---> Java 43 | *.class 44 | 45 | # Mobile Tools for Java (J2ME) 46 | .mtj.tmp/ 47 | 48 | # Package Files # 49 | *.jar 50 | *.war 51 | *.ear 52 | 53 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 54 | hs_err_pid* 55 | 56 | # ---> Maven 57 | target/ 58 | pom.xml.tag 59 | pom.xml.releaseBackup 60 | pom.xml.versionsBackup 61 | pom.xml.next 62 | release.properties 63 | dependency-reduced-pom.xml 64 | buildNumber.properties 65 | .mvn/timing.properties 66 | 67 | # Created by https://www.gitignore.io/api/intellij+all 68 | 69 | ### Intellij+all ### 70 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 71 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 72 | 73 | # User-specific stuff 74 | .idea/**/workspace.xml 75 | .idea/**/tasks.xml 76 | .idea/**/usage.statistics.xml 77 | .idea/**/dictionaries 78 | .idea/**/shelf 79 | 80 | # Generated files 81 | .idea/**/contentModel.xml 82 | 83 | # Sensitive or high-churn files 84 | .idea/**/dataSources/ 85 | .idea/**/dataSources.ids 86 | .idea/**/dataSources.local.xml 87 | .idea/**/sqlDataSources.xml 88 | .idea/**/dynamic.xml 89 | .idea/**/uiDesigner.xml 90 | .idea/**/dbnavigator.xml 91 | 92 | # Gradle 93 | .idea/**/gradle.xml 94 | .idea/**/libraries 95 | 96 | # Gradle and Maven with auto-import 97 | # When using Gradle or Maven with auto-import, you should exclude module files, 98 | # since they will be recreated, and may cause churn. Uncomment if using 99 | # auto-import. 100 | # .idea/modules.xml 101 | # .idea/*.iml 102 | # .idea/modules 103 | 104 | # CMake 105 | cmake-build-*/ 106 | 107 | # Mongo Explorer plugin 108 | .idea/**/mongoSettings.xml 109 | 110 | # File-based project format 111 | *.iws 112 | 113 | # IntelliJ 114 | out/ 115 | 116 | # mpeltonen/sbt-idea plugin 117 | .idea_modules/ 118 | 119 | # JIRA plugin 120 | atlassian-ide-plugin.xml 121 | 122 | # Cursive Clojure plugin 123 | .idea/replstate.xml 124 | 125 | # Crashlytics plugin (for Android Studio and IntelliJ) 126 | com_crashlytics_export_strings.xml 127 | crashlytics.properties 128 | crashlytics-build.properties 129 | fabric.properties 130 | 131 | # Editor-based Rest Client 132 | .idea/httpRequests 133 | 134 | # Android studio 3.1+ serialized cache file 135 | .idea/caches/build_file_checksums.ser 136 | 137 | ### Intellij+all Patch ### 138 | # Ignores the whole .idea folder and all .iml files 139 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 140 | 141 | .idea/ 142 | 143 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 144 | 145 | *.iml 146 | modules.xml 147 | .idea/misc.xml 148 | *.ipr 149 | 150 | 151 | # End of https://www.gitignore.io/api/intellij+all -------------------------------------------------------------------------------- /tdlight-java/src/main/java-templates/it/tdlight/util/LibraryVersion.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | public final class LibraryVersion { 4 | 5 | public static final String VERSION = "${project.version}"; 6 | public static final String NATIVES_VERSION = "${revision}"; 7 | public static final String IMPLEMENTATION_NAME = "tdlight"; 8 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ArrayUtil.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.util.IntSwapper; 4 | import java.util.Collection; 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | class ArrayUtil { 9 | 10 | public static int[] copyFromCollection(Collection list) { 11 | int[] result = new int[list.size()]; 12 | int i = 0; 13 | for (Integer item : list) { 14 | result[i++] = item; 15 | } 16 | return result; 17 | } 18 | 19 | public static Set toSet(int[] list) { 20 | Set set = new HashSet<>(list.length); 21 | for (int item : list) { 22 | set.add(item); 23 | } 24 | return set; 25 | } 26 | 27 | public interface IntComparator { 28 | int compare(int k1, int k2); 29 | } 30 | 31 | public static void mergeSort(int from, int to, IntComparator c, IntSwapper swapper) { 32 | int length = to - from; 33 | int i; 34 | if (length >= 16) { 35 | i = from + to >>> 1; 36 | mergeSort(from, i, c, swapper); 37 | mergeSort(i, to, c, swapper); 38 | if (c.compare(i - 1, i) > 0) { 39 | inPlaceMerge(from, i, to, c, swapper); 40 | } 41 | } else { 42 | for(i = from; i < to; ++i) { 43 | for(int j = i; j > from && c.compare(j - 1, j) > 0; --j) { 44 | swapper.swap(j, j - 1); 45 | } 46 | } 47 | 48 | } 49 | } 50 | 51 | private static void inPlaceMerge(int from, int mid, int to, IntComparator comp, IntSwapper swapper) { 52 | if (from < mid && mid < to) { 53 | if (to - from == 2) { 54 | if (comp.compare(mid, from) < 0) { 55 | swapper.swap(from, mid); 56 | } 57 | 58 | } else { 59 | int firstCut; 60 | int secondCut; 61 | if (mid - from > to - mid) { 62 | firstCut = from + (mid - from) / 2; 63 | secondCut = lowerBound(mid, to, firstCut, comp); 64 | } else { 65 | secondCut = mid + (to - mid) / 2; 66 | firstCut = upperBound(from, mid, secondCut, comp); 67 | } 68 | 69 | if (mid != firstCut && mid != secondCut) { 70 | int first1 = firstCut; 71 | int last1 = mid; 72 | 73 | label43: 74 | while(true) { 75 | --last1; 76 | if (first1 >= last1) { 77 | first1 = mid; 78 | last1 = secondCut; 79 | 80 | while(true) { 81 | --last1; 82 | if (first1 >= last1) { 83 | first1 = firstCut; 84 | last1 = secondCut; 85 | 86 | while(true) { 87 | --last1; 88 | if (first1 >= last1) { 89 | break label43; 90 | } 91 | 92 | swapper.swap(first1++, last1); 93 | } 94 | } 95 | 96 | swapper.swap(first1++, last1); 97 | } 98 | } 99 | 100 | swapper.swap(first1++, last1); 101 | } 102 | } 103 | 104 | mid = firstCut + (secondCut - mid); 105 | inPlaceMerge(from, firstCut, mid, comp, swapper); 106 | inPlaceMerge(mid, secondCut, to, comp, swapper); 107 | } 108 | } 109 | } 110 | 111 | private static int lowerBound(int from, int to, int pos, IntComparator comp) { 112 | int len = to - from; 113 | 114 | while(len > 0) { 115 | int half = len / 2; 116 | int middle = from + half; 117 | if (comp.compare(middle, pos) < 0) { 118 | from = middle + 1; 119 | len -= half + 1; 120 | } else { 121 | len = half; 122 | } 123 | } 124 | 125 | return from; 126 | } 127 | 128 | private static int upperBound(int from, int mid, int pos, IntComparator comp) { 129 | int len = mid - from; 130 | 131 | while(len > 0) { 132 | int half = len / 2; 133 | int middle = from + half; 134 | if (comp.compare(pos, middle) < 0) { 135 | len = half; 136 | } else { 137 | from = middle + 1; 138 | len -= half + 1; 139 | } 140 | } 141 | 142 | return from; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/AutoCleaningTelegramClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Function; 5 | import it.tdlight.jni.TdApi.Object; 6 | import it.tdlight.jni.TdApi.Update; 7 | import it.tdlight.util.CleanSupport; 8 | import it.tdlight.util.CleanSupport.CleanableSupport; 9 | import java.util.Map; 10 | import java.util.function.LongSupplier; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | import org.slf4j.MarkerFactory; 14 | 15 | class AutoCleaningTelegramClient implements TelegramClient { 16 | private final InternalClient client; 17 | private volatile CleanableSupport cleanable; 18 | 19 | AutoCleaningTelegramClient(InternalClientsState state) { 20 | this.client = new InternalClient(state, this::onClientRegistered); 21 | } 22 | 23 | public void onClientRegistered(int clientId, LongSupplier nextQueryIdSupplier) { 24 | Runnable shutDown = () -> { 25 | Logger logger = LoggerFactory.getLogger(TelegramClient.class); 26 | logger.debug(MarkerFactory.getMarker("TG"), "The client is being shut down automatically"); 27 | long reqId = nextQueryIdSupplier.getAsLong(); 28 | NativeClientAccess.send(clientId, reqId, new TdApi.Close()); 29 | }; 30 | Thread shutdownHook = new Thread(shutDown); 31 | Runtime.getRuntime().addShutdownHook(shutdownHook); 32 | cleanable = CleanSupport.register(this, shutDown); 33 | } 34 | 35 | @Override 36 | public void initialize(UpdatesHandler updatesHandler, 37 | ExceptionHandler updateExceptionHandler, 38 | ExceptionHandler defaultExceptionHandler) { 39 | client.initialize(updatesHandler, updateExceptionHandler, defaultExceptionHandler); 40 | } 41 | 42 | @Override 43 | public void initialize(ResultHandler updateHandler, 44 | ExceptionHandler updateExceptionHandler, 45 | ExceptionHandler defaultExceptionHandler) { 46 | client.initialize(updateHandler, updateExceptionHandler, defaultExceptionHandler); 47 | } 48 | 49 | @Override 50 | public void send(Function query, 51 | ResultHandler resultHandler, 52 | ExceptionHandler exceptionHandler) { 53 | client.send(query, resultHandler, exceptionHandler); 54 | } 55 | 56 | @Override 57 | public void send(Function query, ResultHandler resultHandler) { 58 | client.send(query, resultHandler); 59 | } 60 | 61 | @Override 62 | public Object execute(Function query) { 63 | return client.execute(query); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ClientEventsHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | 5 | public interface ClientEventsHandler { 6 | 7 | int getClientId(); 8 | 9 | void handleEvents(boolean isClosed, long[] eventIds, TdApi.Object[] events, int arrayOffset, int arrayLength); 10 | } 11 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ClientFactory.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.ClientFactoryImpl.CommonClientFactory; 4 | 5 | public interface ClientFactory extends AutoCloseable { 6 | 7 | /** 8 | * Get the common client factory, start it if needed. 9 | * Remember to call clientFactory.close() afterward to release it! 10 | * @return Common client factory 11 | */ 12 | static ClientFactory acquireCommonClientFactory() { 13 | CommonClientFactory common = ClientFactoryImpl.COMMON; 14 | if (common == null) { 15 | synchronized (ClientFactory.class) { 16 | if (ClientFactoryImpl.COMMON == null) { 17 | ClientFactoryImpl.COMMON = new CommonClientFactory(); 18 | } 19 | common = ClientFactoryImpl.COMMON; 20 | } 21 | } 22 | common.acquire(); 23 | return common; 24 | } 25 | 26 | /** 27 | * Create a new Client Factory 28 | */ 29 | static ClientFactory create() { 30 | return new ClientFactoryImpl(); 31 | } 32 | 33 | TelegramClient createClient(); 34 | 35 | ReactiveTelegramClient createReactive(); 36 | 37 | @Override 38 | void close(); 39 | } 40 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ClientFactoryImpl.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Object; 5 | import it.tdlight.util.UnsupportedNativeLibraryException; 6 | import it.tdlight.util.CleanSupport; 7 | import it.tdlight.util.CleanSupport.CleanableSupport; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.Objects; 11 | import java.util.concurrent.locks.StampedLock; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * TDLight client factory 17 | */ 18 | class ClientFactoryImpl implements ClientFactory { 19 | 20 | private static final Logger logger = LoggerFactory.getLogger(ClientFactoryImpl.class); 21 | 22 | static volatile CommonClientFactory COMMON; 23 | 24 | private final InternalClientsState state = new InternalClientsState() { 25 | @Override 26 | public void registerClient(int clientId, ClientEventsHandler internalClient) { 27 | startIfNeeded(); 28 | super.registerClient(clientId, internalClient); 29 | responseReceiver.registerClient(clientId); 30 | } 31 | }; 32 | 33 | private final ResponseReceiver responseReceiver = new NativeResponseReceiver(this::handleClientEvents); 34 | private volatile CleanableSupport cleanable; 35 | 36 | public ClientFactoryImpl() { 37 | try { 38 | Init.init(); 39 | } catch (UnsupportedNativeLibraryException e) { 40 | throw new RuntimeException("Can't load the client factory because TDLight can't be loaded", e); 41 | } 42 | } 43 | 44 | @Override 45 | public TelegramClient createClient() { 46 | return new AutoCleaningTelegramClient(state); 47 | } 48 | 49 | @Override 50 | public ReactiveTelegramClient createReactive() { 51 | return new InternalReactiveClient(state); 52 | } 53 | 54 | public void startIfNeeded() { 55 | if (state.shouldStartNow()) { 56 | try { 57 | Init.init(); 58 | responseReceiver.start(); 59 | this.cleanable = CleanSupport.register(this, () -> { 60 | try { 61 | this.responseReceiver.close(); 62 | } catch (InterruptedException e) { 63 | e.printStackTrace(); 64 | } 65 | }); 66 | state.setStarted(); 67 | } catch (Throwable ex) { 68 | state.setStopped(); 69 | logger.error("Failed to start TDLight", ex); 70 | } 71 | } 72 | } 73 | 74 | private void handleClientEvents(int clientId, 75 | boolean isClosed, 76 | long[] clientEventIds, 77 | TdApi.Object[] clientEvents, 78 | int arrayOffset, 79 | int arrayLength) { 80 | StampedLock eventsHandlingLock = state.getEventsHandlingLock(); 81 | boolean closeWriteLockAcquisitionFailed = false; 82 | long stamp = eventsHandlingLock.readLock(); 83 | try { 84 | ClientEventsHandler handler = state.getClientEventsHandler(clientId); 85 | 86 | if (handler != null) { 87 | handler.handleEvents(isClosed, clientEventIds, clientEvents, arrayOffset, arrayLength); 88 | } else { 89 | java.util.List droppedEvents = getEffectivelyDroppedEvents(clientEventIds, 90 | clientEvents, 91 | arrayOffset, 92 | arrayLength 93 | ); 94 | 95 | if (!droppedEvents.isEmpty()) { 96 | logger.error("Unknown client id \"{}\"! {} events have been dropped!", clientId, droppedEvents.size()); 97 | for (DroppedEvent droppedEvent : droppedEvents) { 98 | logger.error("The following event, with id \"{}\", has been dropped: {}", 99 | droppedEvent.id, 100 | droppedEvent.event 101 | ); 102 | } 103 | } 104 | } 105 | 106 | if (isClosed) { 107 | long writeLockStamp = eventsHandlingLock.tryConvertToWriteLock(stamp); 108 | if (writeLockStamp != 0L) { 109 | stamp = writeLockStamp; 110 | removeClientEventHandlers(clientId); 111 | } else { 112 | closeWriteLockAcquisitionFailed = true; 113 | } 114 | } 115 | } finally { 116 | eventsHandlingLock.unlock(stamp); 117 | } 118 | 119 | if (closeWriteLockAcquisitionFailed) { 120 | stamp = eventsHandlingLock.writeLock(); 121 | try { 122 | removeClientEventHandlers(clientId); 123 | } finally { 124 | eventsHandlingLock.unlockWrite(stamp); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Call this method only inside handleClientEvents! 131 | * Ensure that state has the eventsHandlingLock locked in write mode 132 | */ 133 | private void removeClientEventHandlers(int clientId) { 134 | logger.debug("Removing Client {} from event handlers", clientId); 135 | state.removeClientEventHandlers(clientId); 136 | logger.debug("Removed Client {} from event handlers", clientId); 137 | } 138 | 139 | /** 140 | * Get only events that have been dropped, ignoring synthetic errors related to the closure of a client 141 | */ 142 | private List getEffectivelyDroppedEvents(long[] clientEventIds, 143 | TdApi.Object[] clientEvents, 144 | int arrayOffset, 145 | int arrayLength) { 146 | java.util.List droppedEvents = new ArrayList<>(arrayLength); 147 | for (int i = arrayOffset; i < arrayOffset + arrayLength; i++) { 148 | long id = clientEventIds[i]; 149 | TdApi.Object event = clientEvents[i]; 150 | boolean mustPrintError = true; 151 | if (event instanceof TdApi.Error) { 152 | TdApi.Error errorEvent = (TdApi.Error) event; 153 | if (Objects.equals("Request aborted", errorEvent.message)) { 154 | mustPrintError = false; 155 | } 156 | } 157 | if (mustPrintError) { 158 | droppedEvents.add(new DroppedEvent(id, event)); 159 | } 160 | } 161 | return droppedEvents; 162 | } 163 | 164 | public void closeInternal() { 165 | if (state.shouldCloseNow()) { 166 | try { 167 | cleanable.clean(); 168 | } catch (Throwable e) { 169 | logger.error("Failed to close", e); 170 | } 171 | this.state.setStopped(); 172 | } 173 | } 174 | 175 | @Override 176 | public void close() { 177 | this.closeInternal(); 178 | } 179 | 180 | private static final class DroppedEvent { 181 | 182 | private final long id; 183 | private final TdApi.Object event; 184 | 185 | private DroppedEvent(long id, Object event) { 186 | this.id = id; 187 | this.event = event; 188 | } 189 | } 190 | 191 | static class CommonClientFactory implements ClientFactory { 192 | 193 | private int references; 194 | private ClientFactory clientFactory; 195 | 196 | void acquire() { 197 | synchronized (this) { 198 | references++; 199 | if (clientFactory == null) { 200 | clientFactory = new ClientFactoryImpl(); 201 | } 202 | } 203 | } 204 | 205 | private ClientFactory getClientFactory() { 206 | ClientFactory clientFactory; 207 | synchronized (this) { 208 | clientFactory = this.clientFactory; 209 | if (clientFactory == null) { 210 | throw new IllegalStateException("Common client factory is closed"); 211 | } 212 | } 213 | return clientFactory; 214 | } 215 | 216 | @Override 217 | public TelegramClient createClient() { 218 | return getClientFactory().createClient(); 219 | } 220 | 221 | @Override 222 | public ReactiveTelegramClient createReactive() { 223 | return getClientFactory().createReactive(); 224 | } 225 | 226 | @Override 227 | public void close() { 228 | ClientFactory clientFactoryToClose; 229 | synchronized (this) { 230 | references--; 231 | if (references == 0) { 232 | clientFactoryToClose = this.clientFactory; 233 | this.clientFactory = null; 234 | } else { 235 | clientFactoryToClose = null; 236 | } 237 | } 238 | if (clientFactoryToClose != null) { 239 | clientFactoryToClose.close(); 240 | } 241 | } 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ClientRegistrationEventHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import java.util.Map; 4 | import java.util.function.LongSupplier; 5 | 6 | interface ClientRegistrationEventHandler { 7 | 8 | void onClientRegistered(int clientId, LongSupplier nextQueryIdSupplier); 9 | } 10 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ConstructorDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Ernesto Castellotti 3 | * This file is part of JTdlib. 4 | * 5 | * JTdlib is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License. 8 | * 9 | * JTdlib is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with JTdlib. If not, see . 16 | */ 17 | 18 | package it.tdlight; 19 | 20 | import it.tdlight.jni.TdApi; 21 | import java.lang.reflect.Field; 22 | import java.util.HashMap; 23 | import java.util.IdentityHashMap; 24 | import java.util.Map; 25 | import java.util.Objects; 26 | 27 | /** 28 | * Identify the class by using the Constructor. 29 | */ 30 | @SuppressWarnings("rawtypes") 31 | public final class ConstructorDetector { 32 | 33 | private static Map constructorHashMap; 34 | private static IdentityHashMap constructorHashMapInverse; 35 | 36 | private static void tryInit() { 37 | // Call this to load static methods and prevent errors during startup! 38 | try { 39 | Init.init(); 40 | } catch (Throwable throwable) { 41 | throwable.printStackTrace(); 42 | } 43 | } 44 | 45 | /** 46 | * Initialize the ConstructorDetector, it is called from the Init class. 47 | */ 48 | static void init() { 49 | if (constructorHashMap != null) { 50 | return; 51 | } 52 | 53 | Class[] classes = TdApi.class.getDeclaredClasses(); 54 | setConstructorHashMap(classes); 55 | } 56 | 57 | /** 58 | * Identify the class. 59 | * 60 | * @param CONSTRUCTOR CONSTRUCTOR of the TDLight API. 61 | * @return The class related to CONSTRUCTOR. 62 | */ 63 | public static Class getClass(int CONSTRUCTOR) { 64 | tryInit(); 65 | return constructorHashMap.getOrDefault(CONSTRUCTOR, null); 66 | } 67 | 68 | /** 69 | * Identify the class. 70 | * 71 | * @param clazz class of the TDLight API. 72 | * @return The CONSTRUCTOR. 73 | */ 74 | public static int getConstructor(Class clazz) { 75 | tryInit(); 76 | return Objects.requireNonNull(constructorHashMapInverse.get(clazz)); 77 | } 78 | 79 | private static void setConstructorHashMap(Class[] tdApiClasses) { 80 | constructorHashMap = new HashMap<>(); 81 | constructorHashMapInverse = new IdentityHashMap<>(); 82 | 83 | for (Class apiClass : tdApiClasses) { 84 | Field CONSTRUCTORField; 85 | int CONSTRUCTOR; 86 | 87 | try { 88 | CONSTRUCTORField = apiClass.getDeclaredField("CONSTRUCTOR"); 89 | } catch (NoSuchFieldException e) { 90 | continue; 91 | } 92 | 93 | try { 94 | CONSTRUCTOR = CONSTRUCTORField.getInt(null); 95 | } catch (IllegalAccessException e) { 96 | continue; 97 | } 98 | 99 | constructorHashMap.put(CONSTRUCTOR, apiClass); 100 | constructorHashMapInverse.put(apiClass, CONSTRUCTOR); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/EventsHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | 5 | public interface EventsHandler { 6 | 7 | void handleClientEvents(int clientId, 8 | boolean isClosed, 9 | long[] clientEventIds, 10 | TdApi.Object[] clientEvents, 11 | int arrayOffset, 12 | int arrayLength); 13 | } 14 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | /** 4 | * Interface for handler of exceptions thrown while invoking ResultHandler. By default, all such exceptions are ignored. 5 | * All exceptions thrown from ExceptionHandler are ignored. 6 | */ 7 | public interface ExceptionHandler { 8 | 9 | /** 10 | * Callback called on exceptions thrown while invoking ResultHandler. 11 | * 12 | * @param e Exception thrown by ResultHandler. 13 | */ 14 | void onException(Throwable e); 15 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Handler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.ResultHandler; 5 | import it.tdlight.jni.TdApi; 6 | 7 | final class Handler { 8 | 9 | private final ResultHandler resultHandler; 10 | private final ExceptionHandler exceptionHandler; 11 | 12 | public Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) { 13 | this.resultHandler = resultHandler; 14 | this.exceptionHandler = exceptionHandler; 15 | } 16 | 17 | public ResultHandler getResultHandler() { 18 | return resultHandler; 19 | } 20 | 21 | public ExceptionHandler getExceptionHandler() { 22 | return exceptionHandler; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Init.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Ernesto Castellotti 3 | * This file is part of JTdlib. 4 | * 5 | * JTdlib is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License. 8 | * 9 | * JTdlib is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with JTdlib. If not, see . 16 | */ 17 | 18 | package it.tdlight; 19 | 20 | import it.tdlight.util.UnsupportedNativeLibraryException; 21 | import it.tdlight.util.Native; 22 | import it.tdlight.jni.TdApi.LogStreamEmpty; 23 | import it.tdlight.jni.TdApi.SetLogStream; 24 | import it.tdlight.jni.TdApi.SetLogVerbosityLevel; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | /** 29 | * Initialize TDLight 30 | */ 31 | public final class Init { 32 | 33 | public static final Logger LOG = LoggerFactory.getLogger("it.tdlight.TDLight"); 34 | 35 | private static volatile boolean started = false; 36 | 37 | /** 38 | * Initialize TDLight. 39 | * This method is idempotent. 40 | * 41 | * @throws UnsupportedNativeLibraryException An exception that is thrown when the LoadLibrary class fails to load the library. 42 | */ 43 | public static void init() throws UnsupportedNativeLibraryException { 44 | if (!started) { 45 | boolean shouldStart = false; 46 | synchronized (Init.class) { 47 | if (!started) { 48 | started = true; 49 | shouldStart = true; 50 | } 51 | } 52 | if (shouldStart) { 53 | Native.loadNativesInternal(); 54 | ConstructorDetector.init(); 55 | try { 56 | NativeClientAccess.execute(new SetLogVerbosityLevel(3)); 57 | Log.setLogMessageHandler(3, new Slf4JLogMessageHandler()); 58 | Log.setLogStream(null); 59 | NativeClientAccess.execute(new SetLogStream(new LogStreamEmpty())); 60 | } catch (Throwable ex) { 61 | LOG.error("Can't set verbosity level on startup", ex); 62 | } 63 | } 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/InternalClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Function; 5 | import it.tdlight.jni.TdApi.Object; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Objects; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | import java.util.concurrent.locks.StampedLock; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.slf4j.Marker; 17 | import org.slf4j.MarkerFactory; 18 | 19 | final class InternalClient implements ClientEventsHandler, TelegramClient { 20 | 21 | static final Marker TG_MARKER = MarkerFactory.getMarker("TG"); 22 | static final Logger logger = LoggerFactory.getLogger(TelegramClient.class); 23 | 24 | private ClientRegistrationEventHandler clientRegistrationEventHandler; 25 | private final Map> handlers = new ConcurrentHashMap<>(); 26 | 27 | private volatile Integer clientId = null; 28 | private final InternalClientsState clientManagerState; 29 | private Handler updateHandler; 30 | private MultiHandler updatesHandler; 31 | private ExceptionHandler defaultExceptionHandler; 32 | 33 | private final java.lang.Object closeLock = new java.lang.Object(); 34 | private volatile boolean closed = false; 35 | 36 | public InternalClient(InternalClientsState clientManagerState, 37 | ClientRegistrationEventHandler clientRegistrationEventHandler) { 38 | this.clientManagerState = clientManagerState; 39 | this.clientRegistrationEventHandler = clientRegistrationEventHandler; 40 | } 41 | 42 | @Override 43 | public int getClientId() { 44 | return Objects.requireNonNull(clientId, "Can't obtain the client id before initialization"); 45 | } 46 | 47 | @Override 48 | public void handleEvents(boolean isClosed, long[] eventIds, TdApi.Object[] events, int arrayOffset, int arrayLength) { 49 | if (updatesHandler != null) { 50 | List updatesList = new ArrayList<>(arrayLength); 51 | 52 | for (int i = (arrayOffset + arrayLength) - 1; i >= arrayOffset; i--) { 53 | if (eventIds[i] != 0) { 54 | long eventId = eventIds[i]; 55 | TdApi.Object event = events[i]; 56 | 57 | Handler handler = handlers.remove(eventId); 58 | handleResponse(eventId, event, handler); 59 | } else { 60 | updatesList.add(events[i]); 61 | } 62 | } 63 | 64 | try { 65 | updatesHandler.getUpdatesHandler().onUpdates(updatesList); 66 | } catch (Throwable cause) { 67 | handleException(updatesHandler.getExceptionHandler(), cause); 68 | } 69 | } else { 70 | for (int i = arrayOffset; i < (arrayOffset + arrayLength); i++) { 71 | handleEvent(eventIds[i], events[i]); 72 | } 73 | } 74 | 75 | if (isClosed && !closed) { 76 | synchronized (closeLock) { 77 | if (!closed) { 78 | closed = true; 79 | handleClose(); 80 | } 81 | } 82 | } 83 | } 84 | 85 | private void handleClose() { 86 | logger.debug(TG_MARKER, "Received close"); 87 | handlers.forEach((eventId, handler) -> 88 | handleResponse(eventId, new TdApi.Error(500, "Instance closed"), handler)); 89 | handlers.clear(); 90 | logger.debug(TG_MARKER, "Client closed {}", clientId); 91 | } 92 | 93 | /** 94 | * Handles only a response (not an update!) 95 | */ 96 | private void handleResponse(long eventId, TdApi.Object event, Handler handler) { 97 | if (handler != null) { 98 | try { 99 | handler.getResultHandler().onResult(event); 100 | } catch (Throwable cause) { 101 | handleException(handler.getExceptionHandler(), cause); 102 | } 103 | } else { 104 | logger.trace(TG_MARKER, "Client {}, request event id is not registered \"{}\", the following response has been dropped. {}", clientId, eventId, event); 105 | } 106 | } 107 | 108 | /** 109 | * Handles a response or an update 110 | */ 111 | private void handleEvent(long eventId, TdApi.Object event) { 112 | logger.trace(TG_MARKER, "Client {}, response received for request {}: {}", clientId, eventId, event); 113 | if (updatesHandler != null || updateHandler == null) { 114 | throw new IllegalStateException(); 115 | } 116 | Handler handler = eventId == 0 ? updateHandler : handlers.remove(eventId); 117 | handleResponse(eventId, event, handler); 118 | } 119 | 120 | private void handleException(ExceptionHandler exceptionHandler, Throwable cause) { 121 | if (exceptionHandler == null) { 122 | exceptionHandler = defaultExceptionHandler; 123 | } 124 | if (exceptionHandler != null) { 125 | try { 126 | exceptionHandler.onException(cause); 127 | } catch (Throwable ignored) { 128 | } 129 | } 130 | } 131 | 132 | @Override 133 | public void initialize(UpdatesHandler updatesHandler, 134 | ExceptionHandler updateExceptionHandler, 135 | ExceptionHandler defaultExceptionHandler) { 136 | this.updateHandler = null; 137 | this.updatesHandler = new MultiHandler(updatesHandler, updateExceptionHandler); 138 | this.defaultExceptionHandler = defaultExceptionHandler; 139 | createAndRegisterClient(); 140 | } 141 | 142 | @Override 143 | public void initialize(ResultHandler updateHandler, 144 | ExceptionHandler updateExceptionHandler, 145 | ExceptionHandler defaultExceptionHandler) { 146 | this.updateHandler = new Handler<>(updateHandler, updateExceptionHandler); 147 | this.updatesHandler = null; 148 | this.defaultExceptionHandler = defaultExceptionHandler; 149 | createAndRegisterClient(); 150 | } 151 | 152 | private void createAndRegisterClient() { 153 | InternalClientsState clientManagerState = this.clientManagerState; 154 | final StampedLock eventsHandlingLock = clientManagerState.getEventsHandlingLock(); 155 | long stamp = eventsHandlingLock.writeLock(); 156 | try { 157 | if (clientId != null) { 158 | throw new UnsupportedOperationException("Can't initialize the same client twice!"); 159 | } 160 | this.clientId = NativeClientAccess.create(); 161 | if (clientRegistrationEventHandler != null) { 162 | clientRegistrationEventHandler.onClientRegistered(clientId, clientManagerState::getNextQueryId); 163 | // Remove the event handler 164 | clientRegistrationEventHandler = null; 165 | } 166 | logger.debug(TG_MARKER, "Registering new client {}", clientId); 167 | clientManagerState.registerClient(clientId, this); 168 | logger.info(TG_MARKER, "Registered new client {}", clientId); 169 | } finally { 170 | eventsHandlingLock.unlockWrite(stamp); 171 | } 172 | 173 | // Send a dummy request to start TDLib 174 | logger.debug(TG_MARKER, "Sending dummy startup request as client {}", clientId); 175 | TdApi.Function dummyRequest = new TdApi.GetOption("version"); 176 | this.send(dummyRequest, null, null); 177 | // test Client.execute 178 | this.execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")); 179 | } 180 | 181 | @Override 182 | public void send(Function query, 183 | ResultHandler resultHandler, 184 | ExceptionHandler exceptionHandler) { 185 | logger.trace(TG_MARKER, "Trying to send async request {}", query); 186 | 187 | // Handle special requests 188 | TdApi.Object specialResult = tryHandleSpecial(query); 189 | if (specialResult != null) { 190 | logger.trace(TG_MARKER, "Handling special result for async request {}: {}", query, specialResult); 191 | if (resultHandler != null) { 192 | resultHandler.onResult(specialResult); 193 | } 194 | return; 195 | } 196 | 197 | long queryId = clientManagerState.getNextQueryId(); 198 | if (resultHandler != null) { 199 | handlers.put(queryId, new Handler<>(resultHandler, exceptionHandler)); 200 | } 201 | NativeClientAccess.send(clientId, queryId, query); 202 | } 203 | 204 | @Override 205 | public TdApi.Object execute(Function query) { 206 | logger.trace(TG_MARKER, "Trying to execute sync request {}", query); 207 | 208 | // Handle special requests 209 | TdApi.Object specialResult = tryHandleSpecial(query); 210 | if (specialResult != null) { 211 | logger.trace(TG_MARKER, "Handling special result for sync request {}: {}", query, specialResult); 212 | return specialResult; 213 | } 214 | 215 | return NativeClientAccess.execute(query); 216 | } 217 | 218 | /** 219 | * @param function function used to check if the check will be enforced or not. Can be null 220 | * @return not null if closed. The result, if present, must be sent to the client 221 | */ 222 | private TdApi.Object tryHandleSpecial(Function function) { 223 | if (this.closed) { 224 | if (function != null && function.getConstructor() == TdApi.Close.CONSTRUCTOR) { 225 | return new TdApi.Ok(); 226 | } else { 227 | return new TdApi.Error(503, "Client closed"); 228 | } 229 | } else if (clientId == null) { 230 | return new TdApi.Error(503, "Client not initialized. TDLib is not available until \"initialize\" is called!"); 231 | } else { 232 | return null; 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/InternalClientsState.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import io.atlassian.util.concurrent.CopyOnWriteMap; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | import java.util.concurrent.locks.StampedLock; 10 | 11 | public class InternalClientsState { 12 | static final int STATE_INITIAL = 0; 13 | static final int STATE_STARTING = 1; 14 | static final int STATE_STARTED = 2; 15 | static final int STATE_STOPPING = 3; 16 | static final int STATE_STOPPED = 4; 17 | private final AtomicInteger runState = new AtomicInteger(); 18 | private final AtomicLong currentQueryId = new AtomicLong(); 19 | private final Map registeredClientEventHandlers = new HashMap<>(); 20 | private final StampedLock eventsHandlingLock = new StampedLock(); 21 | 22 | 23 | public long getNextQueryId() { 24 | return currentQueryId.updateAndGet(value -> (value == Long.MAX_VALUE ? 0 : value) + 1); 25 | } 26 | 27 | /** 28 | * Before calling this method, lock using getEventsHandlingLock().writeLock() 29 | */ 30 | public void registerClient(int clientId, ClientEventsHandler internalClient) { 31 | boolean replaced = registeredClientEventHandlers.put(clientId, internalClient) != null; 32 | if (replaced) { 33 | throw new IllegalStateException("Client " + clientId + " already registered"); 34 | } 35 | } 36 | 37 | /** 38 | * Before calling this method, lock using getEventsHandlingLock().readLock() 39 | */ 40 | public ClientEventsHandler getClientEventsHandler(int clientId) { 41 | return registeredClientEventHandlers.get(clientId); 42 | } 43 | 44 | public StampedLock getEventsHandlingLock() { 45 | return eventsHandlingLock; 46 | } 47 | 48 | public boolean shouldStartNow() { 49 | return runState.compareAndSet(STATE_INITIAL, STATE_STARTING); 50 | } 51 | 52 | public void setStopped() { 53 | runState.set(STATE_STOPPED); 54 | } 55 | 56 | public void setStarted() { 57 | if (!runState.compareAndSet(STATE_STARTING, STATE_STARTED)) { 58 | throw new IllegalStateException(); 59 | } 60 | } 61 | 62 | /** 63 | * Before calling this method, lock using getEventsHandlingLock().writeLock() 64 | */ 65 | public void removeClientEventHandlers(int clientId) { 66 | registeredClientEventHandlers.remove(clientId); 67 | } 68 | 69 | public boolean shouldCloseNow() { 70 | int prevS = runState.getAndUpdate(prev -> { 71 | if (prev == STATE_INITIAL) { 72 | return STATE_STOPPED; 73 | } else if (prev == STATE_STARTED) { 74 | return STATE_STOPPING; 75 | } else { 76 | return prev; 77 | } 78 | }); 79 | return prevS == STATE_STARTED; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Log.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.LogStream; 5 | import it.tdlight.jni.TdApi.LogStreamDefault; 6 | import it.tdlight.jni.TdApi.LogStreamEmpty; 7 | import it.tdlight.jni.TdApi.LogStreamFile; 8 | import it.tdlight.jni.TdApi.SetLogStream; 9 | import it.tdlight.jni.TdApi.SetLogVerbosityLevel; 10 | import it.tdlight.tdnative.NativeClient.LogMessageHandler; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | /** 15 | * Class used for managing internal TDLib logging. 16 | */ 17 | public final class Log { 18 | 19 | static { 20 | try { 21 | Init.init(); 22 | } catch (Throwable throwable) { 23 | throwable.printStackTrace(); 24 | System.exit(0); 25 | } 26 | } 27 | 28 | private static final AtomicReference LOG_FILE_PATH = new AtomicReference<>(null); 29 | private static final AtomicLong LOG_MAX_FILE_SIZE = new AtomicLong(10 * 1024 * 1024); 30 | 31 | private static boolean updateLog() { 32 | try { 33 | String path = LOG_FILE_PATH.get(); 34 | long maxSize = LOG_MAX_FILE_SIZE.get(); 35 | if (path == null) { 36 | NativeClientAccess.execute(new TdApi.SetLogStream(new LogStreamDefault())); 37 | } else { 38 | NativeClientAccess.execute(new TdApi.SetLogStream(new LogStreamFile(path, maxSize, false))); 39 | } 40 | } catch (Throwable ex) { 41 | ex.printStackTrace(); 42 | return false; 43 | } 44 | return true; 45 | } 46 | 47 | /** 48 | * Sets file path for writing TDLib internal log. By default TDLib writes logs to the System.err. Use this method to 49 | * write the log to a file instead. 50 | * 51 | * @param filePath Path to a file for writing TDLib internal log. Use an empty path to switch back to logging to the 52 | * System.err. 53 | * @return whether opening the log file succeeded. 54 | * @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future. 55 | */ 56 | @Deprecated 57 | public static boolean setFilePath(String filePath) { 58 | LOG_FILE_PATH.set(filePath); 59 | return updateLog(); 60 | } 61 | 62 | /** 63 | * Changes the maximum size of TDLib log file. 64 | * 65 | * @param maxFileSize The maximum size of the file to where the internal TDLib log is written before the file will be 66 | * auto-rotated. Must be positive. Defaults to 10 MB. 67 | * @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogStream}, to be removed in the future. 68 | */ 69 | @Deprecated 70 | public static void setMaxFileSize(long maxFileSize) { 71 | LOG_MAX_FILE_SIZE.set(maxFileSize); 72 | updateLog(); 73 | } 74 | 75 | /** 76 | * Changes TDLib log verbosity. 77 | * 78 | * @param verbosityLevel New value of log verbosity level. Must be non-negative. Value 0 corresponds to fatal errors, 79 | * value 1 corresponds to java.util.logging.Level.SEVERE, value 2 corresponds to 80 | * java.util.logging.Level.WARNING, value 3 corresponds to java.util.logging.Level.INFO, value 4 81 | * corresponds to java.util.logging.Level.FINE, value 5 corresponds to 82 | * java.util.logging.Level.FINER, value greater than 5 can be used to enable even more logging. 83 | * Default value of the log verbosity level is 5. 84 | * @deprecated As of TDLib 1.4.0 in favor of {@link TdApi.SetLogVerbosityLevel}, to be removed in the future. 85 | */ 86 | @Deprecated 87 | public static void setVerbosityLevel(int verbosityLevel) { 88 | NativeClientAccess.execute(new SetLogVerbosityLevel(verbosityLevel)); 89 | updateLog(); 90 | } 91 | 92 | public static void disable() { 93 | setLogMessageHandler(0, null); 94 | setLogStream(null); 95 | } 96 | 97 | /** 98 | * 99 | * Sets the log message handler 100 | * 101 | * @param maxVerbosityLevel Log verbosity level with which the message was added from -1 up to 1024. 102 | * If 0, then TDLib will crash as soon as the callback returns. 103 | * None of the TDLib methods can be called from the callback. 104 | * @param logMessageHandler handler 105 | */ 106 | public static void setLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler) { 107 | NativeClientAccess.setLogMessageHandler(logMessageHandler != null ? maxVerbosityLevel : Math.min(maxVerbosityLevel, 1), 108 | logMessageHandler != null ? logMessageHandler : new Slf4JLogMessageHandler()); 109 | } 110 | 111 | /** 112 | * Sets the log stream 113 | */ 114 | public static void setLogStream(LogStream logStream) { 115 | NativeClientAccess.execute(new SetLogStream(logStream != null ? logStream : new LogStreamEmpty())); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/MultiHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.UpdatesHandler; 5 | 6 | final class MultiHandler { 7 | 8 | private final UpdatesHandler updatesHandler; 9 | private final ExceptionHandler exceptionHandler; 10 | 11 | public MultiHandler(UpdatesHandler updatesHandler, ExceptionHandler exceptionHandler) { 12 | this.updatesHandler = updatesHandler; 13 | this.exceptionHandler = exceptionHandler; 14 | } 15 | 16 | public UpdatesHandler getUpdatesHandler() { 17 | return updatesHandler; 18 | } 19 | 20 | public ExceptionHandler getExceptionHandler() { 21 | return exceptionHandler; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/NativeClientAccess.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Function; 5 | import it.tdlight.tdnative.NativeClient; 6 | 7 | final class NativeClientAccess extends NativeClient { 8 | 9 | static int create() { 10 | return NativeClientAccess.createNativeClient(); 11 | } 12 | 13 | public static TdApi.Object execute(Function function) { 14 | return NativeClientAccess.nativeClientExecute(function); 15 | } 16 | 17 | static void send(int nativeClientId, long eventId, TdApi.Function function) { 18 | NativeClientAccess.nativeClientSend(nativeClientId, eventId, function); 19 | } 20 | 21 | static int receive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout) { 22 | return NativeClientAccess.nativeClientReceive(clientIds, eventIds, events, timeout); 23 | } 24 | 25 | public static void setLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler) { 26 | NativeClientAccess.nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/NativeResponseReceiver.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi.Object; 4 | 5 | class NativeResponseReceiver extends ResponseReceiver { 6 | 7 | public NativeResponseReceiver(EventsHandler eventsHandler) { 8 | super(eventsHandler); 9 | } 10 | 11 | @Override 12 | public int receive(int[] clientIds, long[] eventIds, Object[] events, double timeout) { 13 | return NativeClientAccess.receive(clientIds, eventIds, events, timeout); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ReactiveTelegramClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import java.time.Duration; 5 | import org.reactivestreams.Publisher; 6 | 7 | public interface ReactiveTelegramClient { 8 | 9 | /** 10 | * Creates and registers the client 11 | */ 12 | void createAndRegisterClient(); 13 | 14 | /** 15 | * Sends a request to the TDLib. 16 | * 17 | * @param query Object representing a query to the TDLib. 18 | * @param timeout Response timeout 19 | * @return a publisher that will emit exactly one item, or an error 20 | * @throws NullPointerException if query is null. 21 | */ 22 | Publisher send(TdApi.Function query, Duration timeout); 23 | 24 | /** 25 | * Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously. 26 | * 27 | * @param query Object representing a query to the TDLib. 28 | * @return request result or {@link TdApi.Error}. 29 | * @throws NullPointerException if query is null. 30 | */ 31 | TdApi.Object execute(TdApi.Function query); 32 | 33 | void setListener(SignalListener listener); 34 | 35 | /** 36 | * Send close signal but don't remove the listener 37 | */ 38 | void cancel(); 39 | 40 | /** 41 | * Remove the listener 42 | */ 43 | void dispose(); 44 | } 45 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Ernesto Castellotti 3 | * This file is part of JTdlib. 4 | * 5 | * JTdlib is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License. 8 | * 9 | * JTdlib is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with JTdlib. If not, see . 16 | */ 17 | 18 | package it.tdlight; 19 | 20 | import it.tdlight.jni.TdApi; 21 | import java.util.Objects; 22 | import java.util.StringJoiner; 23 | 24 | /** 25 | * A response to a request, or an incoming update from TDLib. 26 | */ 27 | @SuppressWarnings("unused") 28 | public final class Response { 29 | 30 | private final long id; 31 | private final TdApi.Object object; 32 | 33 | /** 34 | * Creates a response with eventId and object, do not create answers explicitly! you must receive the reply through a 35 | * client. 36 | * 37 | * @param id TDLib request identifier, which corresponds to the response or 0 for incoming updates from TDLib. 38 | * @param object TDLib API object representing a response to a TDLib request or an incoming update. 39 | */ 40 | public Response(long id, TdApi.Object object) { 41 | this.id = id; 42 | this.object = object; 43 | } 44 | 45 | /** 46 | * Get TDLib request identifier. 47 | * 48 | * @return TDLib request identifier, which corresponds to the response or 0 for incoming updates from TDLib. 49 | */ 50 | public long getId() { 51 | return this.id; 52 | } 53 | 54 | /** 55 | * Get TDLib API object. 56 | * 57 | * @return TDLib API object representing a response to a TDLib request or an incoming update. 58 | */ 59 | public TdApi.Object getObject() { 60 | return this.object; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (this == o) { 66 | return true; 67 | } 68 | if (o == null || getClass() != o.getClass()) { 69 | return false; 70 | } 71 | Response response = (Response) o; 72 | return id == response.id && Objects.equals(object, response.object); 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | return Objects.hash(id, object); 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return new StringJoiner(", ", Response.class.getSimpleName() + "[", "]").add("object=" + object).toString(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/ResultHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Object; 5 | 6 | /** 7 | * Interface for handler for results of queries to TDLib and incoming updates from TDLib. 8 | */ 9 | @SuppressWarnings("unused") 10 | public interface ResultHandler { 11 | 12 | /** 13 | * Callback called on result of query to TDLib or incoming update from TDLib. 14 | * 15 | * @param object Result of type r, error of type TdApi.Error, or update of type TdApi.Update. 16 | */ 17 | void onResult(TdApi.Object object); 18 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Signal.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import java.util.Objects; 5 | import java.util.StringJoiner; 6 | 7 | public final class Signal { 8 | 9 | private final TdApi.Object item; 10 | private final Throwable ex; 11 | private final SignalType signalType; 12 | 13 | private Signal(SignalType signalType, TdApi.Object item, Throwable ex) { 14 | this.signalType = signalType; 15 | this.item = item; 16 | this.ex = ex; 17 | } 18 | 19 | public static Signal ofUpdateException(Throwable ex) { 20 | return new Signal(SignalType.EXCEPTION, null, Objects.requireNonNull(ex, "Exception is null")); 21 | } 22 | 23 | public static Signal ofUpdate(TdApi.Object item) { 24 | return new Signal(SignalType.UPDATE, Objects.requireNonNull(item, "Update is null"), null); 25 | } 26 | 27 | public static Signal ofClosed() { 28 | return new Signal(SignalType.CLOSE, null, null); 29 | } 30 | 31 | public boolean isUpdate() { 32 | return signalType == SignalType.UPDATE; 33 | } 34 | 35 | public boolean isException() { 36 | return signalType == SignalType.EXCEPTION; 37 | } 38 | 39 | public boolean isClosed() { 40 | return signalType == SignalType.CLOSE; 41 | } 42 | 43 | public boolean isNotClosed() { 44 | return signalType != SignalType.CLOSE; 45 | } 46 | 47 | public TdApi.Object getUpdate() { 48 | return Objects.requireNonNull(item, "This is not an update"); 49 | } 50 | 51 | public Throwable getException() { 52 | return Objects.requireNonNull(ex, "This is not an exception"); 53 | } 54 | 55 | public void getClosed() { 56 | if (signalType != SignalType.CLOSE) { 57 | throw new IllegalStateException("Expected signal type closed, but the type is " + signalType); 58 | } 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | Signal signal = (Signal) o; 70 | return Objects.equals(item, signal.item) && Objects.equals(ex, signal.ex) && signalType == signal.signalType; 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(item, ex, signalType); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return new StringJoiner(", ", Signal.class.getSimpleName() + "[", "]") 81 | .add("item=" + item) 82 | .add("ex=" + ex) 83 | .add("signalType=" + signalType) 84 | .toString(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/SignalListener.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | public interface SignalListener { 4 | 5 | void onSignal(Signal signal); 6 | } 7 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/SignalType.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | public enum SignalType { 4 | UPDATE, EXCEPTION, CLOSE 5 | } 6 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/Slf4JLogMessageHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.tdnative.NativeClient; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public class Slf4JLogMessageHandler implements NativeClient.LogMessageHandler { 8 | 9 | public static final Logger LOG = LoggerFactory.getLogger("it.tdlight.TDLight"); 10 | 11 | @Override 12 | public void onLogMessage(int verbosityLevel, String message) { 13 | switch (verbosityLevel) { 14 | case -1: 15 | case 0: 16 | case 1: 17 | LOG.error(message); 18 | break; 19 | case 2: 20 | LOG.warn(message); 21 | break; 22 | case 3: 23 | LOG.info(message); 24 | break; 25 | case 4: 26 | LOG.debug(message); 27 | break; 28 | default: 29 | LOG.trace(message); 30 | break; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/TelegramClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | 5 | public interface TelegramClient { 6 | 7 | /** 8 | * Initialize the client synchronously. 9 | * 10 | * @param updatesHandler Handler in which the updates are received 11 | * @param updateExceptionHandler Handler in which the errors from updates are received 12 | * @param defaultExceptionHandler Handler that receives exceptions triggered in a handler 13 | */ 14 | void initialize(UpdatesHandler updatesHandler, 15 | ExceptionHandler updateExceptionHandler, 16 | ExceptionHandler defaultExceptionHandler); 17 | 18 | /** 19 | * Initialize the client synchronously. 20 | * 21 | * @param updateHandler Handler in which the updates are received 22 | * @param updateExceptionHandler Handler in which the errors from updates are received 23 | * @param defaultExceptionHandler Handler that receives exceptions triggered in a handler 24 | */ 25 | default void initialize(ResultHandler updateHandler, 26 | ExceptionHandler updateExceptionHandler, 27 | ExceptionHandler defaultExceptionHandler) { 28 | this.initialize((UpdatesHandler) updates -> updates.forEach(updateHandler::onResult), 29 | updateExceptionHandler, 30 | defaultExceptionHandler 31 | ); 32 | } 33 | 34 | /** 35 | * Sends a request to the TDLib. 36 | * 37 | * @param query Object representing a query to the TDLib. 38 | * @param resultHandler Result handler with onResult method which will be called with result of the query or with 39 | * TdApi.Error as parameter. If it is null, nothing will be called. 40 | * @param exceptionHandler Exception handler with onException method which will be called on exception thrown from 41 | * resultHandler. If it is null, then defaultExceptionHandler will be called. 42 | * @throws NullPointerException if query is null. 43 | */ 44 | void send(TdApi.Function query, 45 | ResultHandler resultHandler, 46 | ExceptionHandler exceptionHandler); 47 | 48 | /** 49 | * Sends a request to the TDLib with an empty ExceptionHandler. 50 | * 51 | * @param query Object representing a query to the TDLib. 52 | * @param resultHandler Result handler with onResult method which will be called with result of the query or with 53 | * TdApi.Error as parameter. If it is null, then defaultExceptionHandler will be called. 54 | * @throws NullPointerException if query is null. 55 | */ 56 | default void send(TdApi.Function query, ResultHandler resultHandler) { 57 | send(query, resultHandler, null); 58 | } 59 | 60 | /** 61 | * Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously. 62 | * 63 | * @param query Object representing a query to the TDLib. 64 | * @return request result or {@link TdApi.Error}. 65 | * @throws NullPointerException if query is null. 66 | */ 67 | TdApi.Object execute(TdApi.Function query); 68 | } 69 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/UpdatesHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import java.util.List; 5 | 6 | /** 7 | * Interface for handler for incoming updates from TDLib. 8 | */ 9 | public interface UpdatesHandler { 10 | 11 | /** 12 | * Callback called on incoming update from TDLib. 13 | * 14 | * @param object Updates of type {@link it.tdlight.jni.TdApi.Update} about new events, or {@link 15 | * it.tdlight.jni.TdApi.Error}. 16 | */ 17 | void onUpdates(List object); 18 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/APIToken.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.Objects; 4 | import java.util.StringJoiner; 5 | 6 | public final class APIToken { 7 | 8 | private int apiID; 9 | private String apiHash; 10 | 11 | /** 12 | * Obtain your API token here: https://my.telegram.org/auth?to=apps 13 | */ 14 | public APIToken(int apiID, String apiHash) { 15 | this.apiID = apiID; 16 | this.apiHash = apiHash; 17 | } 18 | 19 | /** 20 | * Example token 21 | */ 22 | public static APIToken example() { 23 | int apiID = 94575; 24 | String apiHash = "a3406de8d171bb422bb6ddf3bbd800e2"; 25 | return new APIToken(apiID, apiHash); 26 | } 27 | 28 | public int getApiID() { 29 | return apiID; 30 | } 31 | 32 | public void setApiID(int apiID) { 33 | this.apiID = apiID; 34 | } 35 | 36 | public String getApiHash() { 37 | return apiHash; 38 | } 39 | 40 | public void setApiHash(String apiHash) { 41 | this.apiHash = apiHash; 42 | } 43 | 44 | @Override 45 | public boolean equals(Object o) { 46 | if (this == o) { 47 | return true; 48 | } 49 | if (o == null || getClass() != o.getClass()) { 50 | return false; 51 | } 52 | APIToken apiData = (APIToken) o; 53 | return apiID == apiData.apiID && Objects.equals(apiHash, apiData.apiHash); 54 | } 55 | 56 | @Override 57 | public int hashCode() { 58 | return Objects.hash(apiID, apiHash); 59 | } 60 | 61 | @Override 62 | public String toString() { 63 | return new StringJoiner(", ", APIToken.class.getSimpleName() + "[", "]") 64 | .add("apiID=" + apiID) 65 | .add("apiHash='" + apiHash + "'") 66 | .toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/Authenticable.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.function.Consumer; 4 | 5 | public interface Authenticable { 6 | 7 | AuthenticationSupplier getAuthenticationSupplier(); 8 | } 9 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthenticationData.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public interface AuthenticationData { 4 | 5 | boolean isQrCode(); 6 | 7 | boolean isBot(); 8 | 9 | String getUserPhoneNumber(); 10 | 11 | String getBotToken(); 12 | } 13 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthenticationDataImpl.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import static java.util.concurrent.CompletableFuture.completedFuture; 4 | 5 | import java.util.Objects; 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | @SuppressWarnings("unused") 9 | final class AuthenticationDataImpl implements SimpleAuthenticationSupplier { 10 | 11 | private final String userPhoneNumber; 12 | private final String botToken; 13 | private final boolean test; 14 | /** 15 | * Safe string representation of the bot token 16 | */ 17 | private final String botTokenId; 18 | 19 | AuthenticationDataImpl(String userPhoneNumber, String botToken, boolean test) { 20 | if ((userPhoneNumber == null) == (botToken == null)) { 21 | throw new IllegalArgumentException("Please use either a bot token or a phone number"); 22 | } 23 | if (botToken != null) { 24 | if (botToken.length() < 5 || botToken.length() > 200) { 25 | throw new IllegalArgumentException("Bot token is invalid: " + botToken); 26 | } 27 | } 28 | this.userPhoneNumber = userPhoneNumber; 29 | this.botToken = botToken; 30 | this.test = test; 31 | if (botToken != null) { 32 | String[] parts = botToken.split(":", 2); 33 | if (parts.length > 0) { 34 | botTokenId = parts[0]; 35 | } else { 36 | botTokenId = ""; 37 | } 38 | } else { 39 | botTokenId = ""; 40 | } 41 | } 42 | 43 | @Override 44 | public boolean isQrCode() { 45 | return false; 46 | } 47 | 48 | @Override 49 | public boolean isBot() { 50 | return botToken != null; 51 | } 52 | 53 | public boolean isTest() { 54 | return test; 55 | } 56 | 57 | @Override 58 | public String getUserPhoneNumber() { 59 | if (userPhoneNumber == null) { 60 | throw new UnsupportedOperationException("This is not a user"); 61 | } 62 | return userPhoneNumber; 63 | } 64 | 65 | @Override 66 | public String getBotToken() { 67 | if (botToken == null) { 68 | throw new UnsupportedOperationException("This is not a bot"); 69 | } 70 | return botToken; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | String value; 76 | if (userPhoneNumber != null) { 77 | value = userPhoneNumber; 78 | } else { 79 | value = botTokenId; 80 | } 81 | if (test) { 82 | return value + " (test)"; 83 | } else { 84 | return value; 85 | } 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (this == o) { 91 | return true; 92 | } 93 | if (o == null || getClass() != o.getClass()) { 94 | return false; 95 | } 96 | AuthenticationDataImpl that = (AuthenticationDataImpl) o; 97 | return Objects.equals(userPhoneNumber, that.userPhoneNumber) && Objects.equals(botToken, that.botToken) 98 | && Objects.equals(test, that.test); 99 | } 100 | 101 | @Override 102 | public int hashCode() { 103 | return Objects.hash(userPhoneNumber, botToken, test); 104 | } 105 | 106 | @Override 107 | public CompletableFuture get() { 108 | return completedFuture(this); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthenticationDataQrCode.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | class AuthenticationDataQrCode implements SimpleAuthenticationSupplier { 6 | 7 | @Override 8 | public boolean isQrCode() { 9 | return true; 10 | } 11 | 12 | @Override 13 | public boolean isBot() { 14 | return false; 15 | } 16 | 17 | @Override 18 | public String getUserPhoneNumber() { 19 | throw new UnsupportedOperationException("This is not a user"); 20 | } 21 | 22 | @Override 23 | public String getBotToken() { 24 | throw new UnsupportedOperationException("This is not a bot"); 25 | } 26 | 27 | @Override 28 | public CompletableFuture get() { 29 | return CompletableFuture.completedFuture(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthenticationSupplier.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | public interface AuthenticationSupplier { 6 | 7 | /** 8 | * User used in Telegram Test Servers 9 | * @param value any number from 0001 to 9999 10 | */ 11 | static SimpleAuthenticationSupplier testUser(int value) { 12 | if (value < 1) { 13 | throw new IllegalArgumentException("value must be greater than 0"); 14 | } 15 | if (value > 9999) { 16 | throw new IllegalArgumentException("value must be lower than 10000"); 17 | } 18 | return new AuthenticationDataImpl("999662" + value, null, true); 19 | } 20 | 21 | CompletableFuture get(); 22 | 23 | static SimpleAuthenticationSupplier qrCode() { 24 | return new AuthenticationDataQrCode(); 25 | } 26 | 27 | /** 28 | * Deprecated, use {@link #user(String)} instead 29 | */ 30 | @Deprecated 31 | static SimpleAuthenticationSupplier user(long userPhoneNumber) { 32 | return user(String.valueOf(userPhoneNumber)); 33 | } 34 | 35 | static SimpleAuthenticationSupplier user(String userPhoneNumber) { 36 | return new AuthenticationDataImpl(userPhoneNumber, null, false); 37 | } 38 | 39 | static SimpleAuthenticationSupplier bot(String botToken) { 40 | return new AuthenticationDataImpl(null, botToken, false); 41 | } 42 | 43 | static ConsoleInteractiveAuthenticationData consoleLogin() { 44 | return new ConsoleInteractiveAuthenticationData(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateReadyGetMe.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.TelegramClient; 4 | import it.tdlight.jni.TdApi.AuthorizationStateReady; 5 | import it.tdlight.jni.TdApi.GetMe; 6 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 7 | import it.tdlight.jni.TdApi.User; 8 | import it.tdlight.jni.TdApi.Error; 9 | import it.tdlight.jni.TdApi.UserTypeRegular; 10 | import java.util.concurrent.CompletableFuture; 11 | import java.util.concurrent.atomic.AtomicReference; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | final class AuthorizationStateReadyGetMe implements GenericUpdateHandler { 16 | 17 | private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateReadyGetMe.class); 18 | 19 | private final TelegramClient client; 20 | private final CompletableFuture meReceived = new CompletableFuture<>(); 21 | private final AtomicReference me = new AtomicReference<>(); 22 | private final AuthorizationStateReadyLoadChats mainChatsLoader; 23 | private final AuthorizationStateReadyLoadChats archivedChatsLoader; 24 | 25 | public AuthorizationStateReadyGetMe(TelegramClient client, 26 | AuthorizationStateReadyLoadChats mainChatsLoader, 27 | AuthorizationStateReadyLoadChats archivedChatsLoader) { 28 | this.client = client; 29 | this.mainChatsLoader = mainChatsLoader; 30 | this.archivedChatsLoader = archivedChatsLoader; 31 | } 32 | 33 | @Override 34 | public void onUpdate(UpdateAuthorizationState update) { 35 | if (update.authorizationState.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) { 36 | client.send(new GetMe(), me -> { 37 | try { 38 | if (me.getConstructor() == Error.CONSTRUCTOR) { 39 | throw new TelegramError((Error) me); 40 | } 41 | this.me.set((User) me); 42 | } finally { 43 | this.meReceived.complete(null); 44 | } 45 | if (((User) me).type.getConstructor() == UserTypeRegular.CONSTRUCTOR) { 46 | mainChatsLoader.onUpdate(update); 47 | archivedChatsLoader.onUpdate(update); 48 | } 49 | }, error -> logger.warn("Failed to execute TdApi.GetMe()")); 50 | } 51 | } 52 | 53 | public User getMe() { 54 | return me.get(); 55 | } 56 | 57 | public CompletableFuture getMeAsync() { 58 | return meReceived.thenApply(v -> me.get()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateReadyLoadChats.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.TelegramClient; 4 | import it.tdlight.jni.TdApi.AuthorizationStateReady; 5 | import it.tdlight.jni.TdApi.ChatList; 6 | import it.tdlight.jni.TdApi.Error; 7 | import it.tdlight.jni.TdApi.LoadChats; 8 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | final class AuthorizationStateReadyLoadChats implements GenericUpdateHandler { 13 | 14 | private static final Logger logger = LoggerFactory.getLogger(AuthorizationStateReadyLoadChats.class); 15 | 16 | private final TelegramClient client; 17 | private final ChatList chatList; 18 | 19 | private boolean loaded; 20 | 21 | public AuthorizationStateReadyLoadChats(TelegramClient client, ChatList chatList) { 22 | this.client = client; 23 | this.chatList = chatList; 24 | } 25 | 26 | @Override 27 | public void onUpdate(UpdateAuthorizationState update) { 28 | if (update.authorizationState.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) { 29 | client.send(new LoadChats(chatList, 2000), ok -> { 30 | if (ok.getConstructor() == Error.CONSTRUCTOR) { 31 | Error error = (Error) ok; 32 | if (error.code != 404) { 33 | throw new TelegramError((Error) ok); 34 | } 35 | logger.debug("All {} chats have already been loaded", chatList); 36 | } else { 37 | logger.debug("All chats have been loaded"); 38 | } 39 | }, error -> logger.warn("Failed to execute TdApi.LoadChats()")); 40 | } 41 | } 42 | 43 | public boolean isLoaded() { 44 | return loaded; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitAuthenticationDataHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.PhoneNumberAuthenticationSettings; 7 | import it.tdlight.jni.TdApi.SetAuthenticationPhoneNumber; 8 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 9 | 10 | final class AuthorizationStateWaitAuthenticationDataHandler implements GenericUpdateHandler { 11 | 12 | private final TelegramClient client; 13 | private final Authenticable authenticable; 14 | private final ExceptionHandler exceptionHandler; 15 | 16 | public AuthorizationStateWaitAuthenticationDataHandler(TelegramClient client, 17 | Authenticable authenticable, 18 | ExceptionHandler exceptionHandler) { 19 | this.client = client; 20 | this.authenticable = authenticable; 21 | this.exceptionHandler = exceptionHandler; 22 | } 23 | 24 | @Override 25 | public void onUpdate(UpdateAuthorizationState update) { 26 | if (update.authorizationState.getConstructor() == TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR) { 27 | authenticable.getAuthenticationSupplier().get().whenComplete((authData, ex) -> { 28 | if (ex != null) { 29 | exceptionHandler.onException(ex); 30 | return; 31 | } 32 | this.onAuthData(authData); 33 | }); 34 | } 35 | } 36 | 37 | public void onAuthData(AuthenticationData authenticationData) { 38 | if (authenticationData.isBot()) { 39 | String botToken = authenticationData.getBotToken(); 40 | TdApi.CheckAuthenticationBotToken response = new TdApi.CheckAuthenticationBotToken(botToken); 41 | client.send(response, ok -> { 42 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 43 | throw new TelegramError((TdApi.Error) ok); 44 | } 45 | }, exceptionHandler); 46 | } else if (authenticationData.isQrCode()) { 47 | TdApi.RequestQrCodeAuthentication response = new TdApi.RequestQrCodeAuthentication(); 48 | client.send(response, ok -> { 49 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 50 | throw new TelegramError((TdApi.Error) ok); 51 | } 52 | }, exceptionHandler); 53 | } else { 54 | PhoneNumberAuthenticationSettings phoneSettings = new PhoneNumberAuthenticationSettings(false, 55 | false, 56 | false, 57 | false, 58 | false, 59 | null, 60 | null 61 | ); 62 | 63 | String phoneNumber = authenticationData.getUserPhoneNumber(); 64 | SetAuthenticationPhoneNumber response = new SetAuthenticationPhoneNumber(phoneNumber, phoneSettings); 65 | client.send(response, ok -> { 66 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 67 | throw new TelegramError((TdApi.Error) ok); 68 | } 69 | }, exceptionHandler); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitCodeHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.AuthorizationStateWaitCode; 7 | import it.tdlight.jni.TdApi.CheckAuthenticationCode; 8 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 9 | 10 | final class AuthorizationStateWaitCodeHandler implements GenericUpdateHandler { 11 | 12 | private final TelegramClient client; 13 | private final ClientInteraction clientInteraction; 14 | private final String testCode; 15 | private final ExceptionHandler exceptionHandler; 16 | 17 | public AuthorizationStateWaitCodeHandler(TelegramClient client, 18 | ClientInteraction clientInteraction, 19 | String testCode, 20 | ExceptionHandler exceptionHandler) { 21 | this.client = client; 22 | this.clientInteraction = clientInteraction; 23 | this.testCode = testCode; 24 | this.exceptionHandler = exceptionHandler; 25 | } 26 | 27 | @Override 28 | public void onUpdate(UpdateAuthorizationState update) { 29 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitCode.CONSTRUCTOR) { 30 | AuthorizationStateWaitCode authorizationState = (AuthorizationStateWaitCode) update.authorizationState; 31 | if (testCode != null) { 32 | sendCode(testCode); 33 | } else { 34 | ParameterInfo parameterInfo = new ParameterInfoCode(authorizationState.codeInfo.phoneNumber, 35 | authorizationState.codeInfo.nextType, 36 | authorizationState.codeInfo.timeout, 37 | authorizationState.codeInfo.type 38 | ); 39 | clientInteraction.onParameterRequest(InputParameter.ASK_CODE, parameterInfo).whenComplete((code, ex) -> { 40 | if (ex != null) { 41 | exceptionHandler.onException(ex); 42 | return; 43 | } 44 | sendCode(code); 45 | }); 46 | } 47 | } 48 | } 49 | 50 | private void sendCode(String code) { 51 | CheckAuthenticationCode response = new CheckAuthenticationCode(code); 52 | client.send(response, ok -> { 53 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 54 | throw new TelegramError((TdApi.Error) ok); 55 | } 56 | }, exceptionHandler); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitEmailAddressHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.AuthorizationStateWaitEmailAddress; 7 | import it.tdlight.jni.TdApi.CheckAuthenticationCode; 8 | import it.tdlight.jni.TdApi.CheckAuthenticationEmailCode; 9 | import it.tdlight.jni.TdApi.SetAuthenticationEmailAddress; 10 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 11 | 12 | final class AuthorizationStateWaitEmailAddressHandler implements GenericUpdateHandler { 13 | 14 | private final TelegramClient client; 15 | private final ClientInteraction clientInteraction; 16 | private final ExceptionHandler exceptionHandler; 17 | 18 | public AuthorizationStateWaitEmailAddressHandler(TelegramClient client, 19 | ClientInteraction clientInteraction, 20 | ExceptionHandler exceptionHandler) { 21 | this.client = client; 22 | this.clientInteraction = clientInteraction; 23 | this.exceptionHandler = exceptionHandler; 24 | } 25 | 26 | @Override 27 | public void onUpdate(UpdateAuthorizationState update) { 28 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitEmailAddress.CONSTRUCTOR) { 29 | AuthorizationStateWaitEmailAddress authorizationState = (AuthorizationStateWaitEmailAddress) update.authorizationState; 30 | ParameterInfo parameterInfo = new ParameterInfoEmailAddress(authorizationState.allowAppleId, authorizationState.allowGoogleId); 31 | clientInteraction.onParameterRequest(InputParameter.ASK_EMAIL_ADDRESS, parameterInfo).whenComplete((emailAddress, ex) -> { 32 | if (ex != null) { 33 | exceptionHandler.onException(ex); 34 | return; 35 | } 36 | sendEmailAddress(emailAddress); 37 | }); 38 | } 39 | } 40 | 41 | private void sendEmailAddress(String emailAddress) { 42 | SetAuthenticationEmailAddress response = new SetAuthenticationEmailAddress(emailAddress); 43 | client.send(response, ok -> { 44 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 45 | throw new TelegramError((TdApi.Error) ok); 46 | } 47 | }, exceptionHandler); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitEmailCodeHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import java.util.Locale; 7 | 8 | final class AuthorizationStateWaitEmailCodeHandler implements GenericUpdateHandler { 9 | 10 | private final TelegramClient client; 11 | private final ClientInteraction clientInteraction; 12 | private final ExceptionHandler exceptionHandler; 13 | 14 | public AuthorizationStateWaitEmailCodeHandler(TelegramClient client, 15 | ClientInteraction clientInteraction, 16 | ExceptionHandler exceptionHandler) { 17 | this.client = client; 18 | this.clientInteraction = clientInteraction; 19 | this.exceptionHandler = exceptionHandler; 20 | } 21 | 22 | @Override 23 | public void onUpdate(TdApi.UpdateAuthorizationState update) { 24 | if (update.authorizationState.getConstructor() == TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR) { 25 | TdApi.AuthorizationStateWaitEmailCode authorizationState = (TdApi.AuthorizationStateWaitEmailCode) update.authorizationState; 26 | ParameterInfo parameterInfo = new ParameterInfoEmailCode(authorizationState.allowAppleId, 27 | authorizationState.allowGoogleId, 28 | authorizationState.codeInfo.emailAddressPattern, 29 | authorizationState.codeInfo.length, 30 | EmailAddressResetState.valueOf(authorizationState.emailAddressResetState.getClass().getSimpleName().substring("EmailAddressResetState".length()).toUpperCase(Locale.ROOT)) 31 | ); 32 | clientInteraction.onParameterRequest(InputParameter.ASK_EMAIL_CODE, parameterInfo).whenComplete((emailAddress, ex) -> { 33 | if (ex != null) { 34 | exceptionHandler.onException(ex); 35 | return; 36 | } 37 | sendEmailCode(emailAddress); 38 | }); 39 | } 40 | } 41 | 42 | private void sendEmailCode(String code) { 43 | TdApi.CheckAuthenticationEmailCode response = new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)); 44 | client.send(response, ok -> { 45 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 46 | throw new TelegramError((TdApi.Error) ok); 47 | } 48 | }, exceptionHandler); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitForExit.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.AuthorizationStateClosed; 5 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 6 | import java.util.concurrent.CountDownLatch; 7 | 8 | final class AuthorizationStateWaitForExit implements GenericUpdateHandler { 9 | 10 | private final Runnable setClosed; 11 | 12 | public AuthorizationStateWaitForExit(Runnable setClosed) { 13 | this.setClosed = setClosed; 14 | } 15 | 16 | @Override 17 | public void onUpdate(UpdateAuthorizationState update) { 18 | if (update.authorizationState.getConstructor() == AuthorizationStateClosed.CONSTRUCTOR) { 19 | setClosed.run(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitOtherDeviceConfirmationHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.jni.TdApi.AuthorizationStateWaitOtherDeviceConfirmation; 5 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 6 | 7 | final class AuthorizationStateWaitOtherDeviceConfirmationHandler implements 8 | GenericUpdateHandler { 9 | 10 | private final ClientInteraction clientInteraction; 11 | private final ExceptionHandler exceptionHandler; 12 | 13 | public AuthorizationStateWaitOtherDeviceConfirmationHandler(ClientInteraction clientInteraction, 14 | ExceptionHandler exceptionHandler) { 15 | this.clientInteraction = clientInteraction; 16 | this.exceptionHandler = exceptionHandler; 17 | } 18 | 19 | @Override 20 | public void onUpdate(UpdateAuthorizationState update) { 21 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR) { 22 | AuthorizationStateWaitOtherDeviceConfirmation authorizationState 23 | = (AuthorizationStateWaitOtherDeviceConfirmation) update.authorizationState; 24 | ParameterInfo parameterInfo = new ParameterInfoNotifyLink(authorizationState.link); 25 | clientInteraction.onParameterRequest(InputParameter.NOTIFY_LINK, parameterInfo).whenComplete((ignored, ex) -> { 26 | if (ex != null) { 27 | exceptionHandler.onException(ex); 28 | } 29 | }); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitPasswordHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.AuthorizationStateWaitPassword; 7 | import it.tdlight.jni.TdApi.CheckAuthenticationPassword; 8 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 9 | 10 | final class AuthorizationStateWaitPasswordHandler implements GenericUpdateHandler { 11 | 12 | private final TelegramClient client; 13 | private final ClientInteraction clientInteraction; 14 | private final ExceptionHandler exceptionHandler; 15 | 16 | public AuthorizationStateWaitPasswordHandler(TelegramClient client, 17 | ClientInteraction clientInteraction, 18 | ExceptionHandler exceptionHandler) { 19 | this.client = client; 20 | this.clientInteraction = clientInteraction; 21 | this.exceptionHandler = exceptionHandler; 22 | } 23 | 24 | @Override 25 | public void onUpdate(UpdateAuthorizationState update) { 26 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitPassword.CONSTRUCTOR) { 27 | AuthorizationStateWaitPassword authorizationState = (AuthorizationStateWaitPassword) update.authorizationState; 28 | ParameterInfo parameterInfo = new ParameterInfoPasswordHint(authorizationState.passwordHint, 29 | authorizationState.hasRecoveryEmailAddress, 30 | authorizationState.recoveryEmailAddressPattern 31 | ); 32 | clientInteraction.onParameterRequest(InputParameter.ASK_PASSWORD, parameterInfo).whenComplete((password, ex) -> { 33 | if (ex != null) { 34 | exceptionHandler.onException(ex); 35 | return; 36 | } 37 | CheckAuthenticationPassword response = new CheckAuthenticationPassword(password); 38 | client.send(response, ok -> { 39 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 40 | throw new TelegramError((TdApi.Error) ok); 41 | } 42 | }, exceptionHandler); 43 | }); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitReady.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.AuthorizationStateReady; 4 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 5 | 6 | final class AuthorizationStateWaitReady implements GenericUpdateHandler { 7 | 8 | private final Runnable setReady; 9 | 10 | public AuthorizationStateWaitReady(Runnable setReady) { 11 | this.setReady = setReady; 12 | } 13 | 14 | @Override 15 | public void onUpdate(UpdateAuthorizationState update) { 16 | if (update.authorizationState.getConstructor() == AuthorizationStateReady.CONSTRUCTOR) { 17 | setReady.run(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitRegistrationHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.AuthorizationStateWaitRegistration; 7 | import it.tdlight.jni.TdApi.RegisterUser; 8 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 9 | 10 | final class AuthorizationStateWaitRegistrationHandler implements GenericUpdateHandler { 11 | 12 | private final TelegramClient client; 13 | private final ClientInteraction clientInteraction; 14 | private final ExceptionHandler exceptionHandler; 15 | 16 | public AuthorizationStateWaitRegistrationHandler(TelegramClient client, 17 | ClientInteraction clientInteraction, 18 | ExceptionHandler exceptionHandler) { 19 | this.client = client; 20 | this.clientInteraction = clientInteraction; 21 | this.exceptionHandler = exceptionHandler; 22 | } 23 | 24 | @Override 25 | public void onUpdate(UpdateAuthorizationState update) { 26 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitRegistration.CONSTRUCTOR) { 27 | TdApi.AuthorizationStateWaitRegistration authorizationState = (TdApi.AuthorizationStateWaitRegistration) update.authorizationState; 28 | ParameterInfoTermsOfService tos = new ParameterInfoTermsOfService(authorizationState.termsOfService); 29 | clientInteraction 30 | .onParameterRequest(InputParameter.TERMS_OF_SERVICE, tos) 31 | .thenCompose(ignored -> clientInteraction 32 | .onParameterRequest(InputParameter.ASK_FIRST_NAME, new EmptyParameterInfo())) 33 | .thenCompose(firstName -> clientInteraction 34 | .onParameterRequest(InputParameter.ASK_LAST_NAME, new EmptyParameterInfo()).thenAccept(lastName -> { 35 | if (firstName == null || firstName.isEmpty()) { 36 | exceptionHandler.onException(new IllegalArgumentException("First name must not be null or empty")); 37 | return; 38 | } 39 | if (firstName.length() > 64) { 40 | exceptionHandler.onException(new IllegalArgumentException("First name must be under 64 characters")); 41 | return; 42 | } 43 | if (lastName == null) { 44 | exceptionHandler.onException(new IllegalArgumentException("Last name must not be null")); 45 | return; 46 | } 47 | if (lastName.length() > 64) { 48 | exceptionHandler.onException(new IllegalArgumentException("Last name must be under 64 characters")); 49 | return; 50 | } 51 | RegisterUser response = new RegisterUser(firstName, lastName, true); 52 | client.send(response, ok -> { 53 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 54 | throw new TelegramError((TdApi.Error) ok); 55 | } 56 | }, exceptionHandler); 57 | })) 58 | .whenComplete((ignored, ex) -> { 59 | if (ex != null) { 60 | exceptionHandler.onException(ex); 61 | } 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/AuthorizationStateWaitTdlibParametersHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.AuthorizationStateWaitTdlibParameters; 7 | import it.tdlight.jni.TdApi.UpdateAuthorizationState; 8 | 9 | final class AuthorizationStateWaitTdlibParametersHandler implements GenericUpdateHandler { 10 | 11 | private final TelegramClient client; 12 | private final TDLibSettings settings; 13 | private final ExceptionHandler exceptionHandler; 14 | 15 | public AuthorizationStateWaitTdlibParametersHandler(TelegramClient client, 16 | TDLibSettings settings, 17 | ExceptionHandler exceptionHandler) { 18 | this.client = client; 19 | this.settings = settings; 20 | this.exceptionHandler = exceptionHandler; 21 | } 22 | 23 | @Override 24 | public void onUpdate(UpdateAuthorizationState update) { 25 | if (update.authorizationState.getConstructor() == AuthorizationStateWaitTdlibParameters.CONSTRUCTOR) { 26 | TdApi.SetTdlibParameters params = new TdApi.SetTdlibParameters(); 27 | params.useTestDc = settings.isUsingTestDatacenter(); 28 | params.databaseDirectory = settings.getDatabaseDirectoryPath().toString(); 29 | params.filesDirectory = settings.getDownloadedFilesDirectoryPath().toString(); 30 | params.useFileDatabase = settings.isFileDatabaseEnabled(); 31 | params.useChatInfoDatabase = settings.isChatInfoDatabaseEnabled(); 32 | params.useMessageDatabase = settings.isMessageDatabaseEnabled(); 33 | params.useSecretChats = false; 34 | params.apiId = settings.getApiToken().getApiID(); 35 | params.apiHash = settings.getApiToken().getApiHash(); 36 | params.systemLanguageCode = settings.getSystemLanguageCode(); 37 | params.deviceModel = settings.getDeviceModel(); 38 | params.systemVersion = settings.getSystemVersion(); 39 | params.applicationVersion = settings.getApplicationVersion(); 40 | params.databaseEncryptionKey = null; 41 | client.send(params, ok -> { 42 | if (ok.getConstructor() == TdApi.Error.CONSTRUCTOR) { 43 | throw new TelegramError((TdApi.Error) ok); 44 | } 45 | }, exceptionHandler); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ClientInteraction.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.function.Consumer; 5 | 6 | public interface ClientInteraction { 7 | 8 | CompletableFuture onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo); 9 | } 10 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Chat; 5 | 6 | public interface CommandHandler { 7 | 8 | void onCommand(Chat chat, TdApi.MessageSender commandSender, String arguments); 9 | } 10 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/CommandsHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import io.atlassian.util.concurrent.CopyOnWriteMap; 4 | import it.tdlight.TelegramClient; 5 | import it.tdlight.jni.TdApi; 6 | import it.tdlight.jni.TdApi.Chat; 7 | import it.tdlight.jni.TdApi.Message; 8 | import it.tdlight.jni.TdApi.MessageText; 9 | import it.tdlight.jni.TdApi.UpdateNewMessage; 10 | import it.tdlight.jni.TdApi.User; 11 | import java.util.Optional; 12 | import java.util.function.Supplier; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | final class CommandsHandler implements GenericUpdateHandler { 17 | 18 | private static final Logger logger = LoggerFactory.getLogger(CommandsHandler.class); 19 | private static final CopyOnWriteMap EMPTY_COW_MAP = CopyOnWriteMap.newHashMap(); 20 | 21 | private final TelegramClient client; 22 | private final CopyOnWriteMap> commandHandlers; 23 | private final Supplier me; 24 | 25 | public CommandsHandler(TelegramClient client, 26 | CopyOnWriteMap> commandHandlers, 27 | Supplier me) { 28 | this.client = client; 29 | this.commandHandlers = commandHandlers; 30 | this.me = me; 31 | } 32 | 33 | @Override 34 | public void onUpdate(UpdateNewMessage update) { 35 | if (update.getConstructor() == UpdateNewMessage.CONSTRUCTOR) { 36 | Message message = update.message; 37 | if (message.forwardInfo == null && !message.isChannelPost && (message.authorSignature == null 38 | || message.authorSignature.isEmpty()) && message.content.getConstructor() == MessageText.CONSTRUCTOR) { 39 | MessageText messageText = (MessageText) message.content; 40 | String text = messageText.text.text; 41 | if (text.startsWith("/")) { 42 | String[] parts = text.split(" ", 2); 43 | if (parts.length == 1) { 44 | parts = new String[]{parts[0], ""}; 45 | } 46 | if (parts.length == 2) { 47 | String currentUnsplittedCommandName = parts[0].substring(1); 48 | String arguments = parts[1].trim(); 49 | String[] commandParts = currentUnsplittedCommandName.split("@", 2); 50 | String currentCommandName = null; 51 | boolean correctTarget = false; 52 | if (commandParts.length == 2) { 53 | String myUsername = this.getMyUsername().orElse(null); 54 | if (myUsername != null) { 55 | currentCommandName = commandParts[0].trim(); 56 | String currentTargetUsername = commandParts[1]; 57 | if (myUsername.equalsIgnoreCase(currentTargetUsername)) { 58 | correctTarget = true; 59 | } 60 | } 61 | } else if (commandParts.length == 1) { 62 | currentCommandName = commandParts[0].trim(); 63 | correctTarget = true; 64 | } 65 | 66 | if (correctTarget) { 67 | String commandName = currentCommandName; 68 | CopyOnWriteMap handlers = commandHandlers.getOrDefault(currentCommandName, EMPTY_COW_MAP); 69 | 70 | for (CommandHandler handler : handlers.keySet()) { 71 | client.send(new TdApi.GetChat(message.chatId), response -> { 72 | if (response.getConstructor() == TdApi.Error.CONSTRUCTOR) { 73 | throw new TelegramError((TdApi.Error) response); 74 | } 75 | handler.onCommand((Chat) response, message.senderId, arguments); 76 | }, error -> logger.warn("Error when handling the command {}", commandName, error)); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | } 84 | 85 | private Optional getMyUsername() { 86 | User user = this.me.get(); 87 | if (user == null || user.usernames == null) { 88 | return Optional.empty(); 89 | } else { 90 | return Optional.of(user.usernames.editableUsername); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ConsoleInteractiveAuthenticationData.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.util.ScannerUtils; 4 | import java.util.Locale; 5 | import java.util.concurrent.CompletableFuture; 6 | import java.util.concurrent.atomic.AtomicReference; 7 | 8 | public final class ConsoleInteractiveAuthenticationData implements AuthenticationSupplier { 9 | 10 | private static final class State implements AuthenticationData { 11 | final boolean isQr; 12 | final boolean isBot; 13 | final String botToken; 14 | final String phoneNumber; 15 | 16 | State(boolean isQr, boolean isBot, String botToken, String phoneNumber) { 17 | this.isQr = isQr; 18 | this.isBot = isBot; 19 | this.botToken = botToken; 20 | this.phoneNumber = phoneNumber; 21 | } 22 | 23 | @Override 24 | public boolean isQrCode() { 25 | return isQr; 26 | } 27 | 28 | @Override 29 | public boolean isBot() { 30 | return isBot; 31 | } 32 | 33 | @Override 34 | public String getUserPhoneNumber() { 35 | if (isBot || isQr) { 36 | throw new UnsupportedOperationException("This is not a user"); 37 | } 38 | return phoneNumber; 39 | } 40 | 41 | @Override 42 | public String getBotToken() { 43 | if (!isBot || isQr) { 44 | throw new UnsupportedOperationException("This is not a bot"); 45 | } 46 | return botToken; 47 | } 48 | } 49 | 50 | private final AtomicReference> state = new AtomicReference<>(); 51 | 52 | ConsoleInteractiveAuthenticationData() { 53 | 54 | } 55 | 56 | public CompletableFuture askData() { 57 | return get(); 58 | } 59 | 60 | public boolean isInitialized() { 61 | CompletableFuture cf = state.get(); 62 | return cf != null && cf.isDone(); 63 | } 64 | 65 | @Override 66 | public CompletableFuture get() { 67 | CompletableFuture cf = new CompletableFuture(); 68 | if (state.compareAndSet(null, cf)) { 69 | SequentialRequestsExecutor.getInstance().execute(() -> { 70 | try { 71 | String choice; 72 | 73 | // Choose login type 74 | String mode; 75 | do { 76 | String response = ScannerUtils.askParameter("login", 77 | "Do you want to login using a bot [token], a [phone] number, or a [qr] code? [token/phone/qr]"); 78 | if (response != null) { 79 | choice = response.trim().toLowerCase(Locale.ROOT); 80 | switch (choice) { 81 | case "phone": 82 | mode = "PHONE"; 83 | break; 84 | case "token": 85 | mode = "TOKEN"; 86 | break; 87 | case "qr": 88 | mode = "QR"; 89 | break; 90 | default: 91 | mode = null; 92 | break; 93 | } 94 | } else { 95 | mode = null; 96 | } 97 | } while (mode == null); 98 | 99 | if ("TOKEN".equals(mode)) { 100 | String token; 101 | do { 102 | token = ScannerUtils.askParameter("login", "Please type the bot token"); 103 | } while (token.length() < 5 || !token.contains(":")); 104 | 105 | cf.complete(new State(false, true, token, null)); 106 | } else if ("PHONE".equals(mode)) { 107 | String phoneNumber; 108 | do { 109 | phoneNumber = ScannerUtils.askParameter("login", "Please type your phone number"); 110 | } while (phoneNumber.length() < 3); 111 | 112 | cf.complete(new State(false, false, null, phoneNumber)); 113 | } else { 114 | cf.complete(new State(true, false, null, null)); 115 | } 116 | } catch (Throwable ex) { 117 | cf.completeExceptionally(ex); 118 | throw ex; 119 | } 120 | }); 121 | return cf; 122 | } else { 123 | return state.get(); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/EmailAddressResetState.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public enum EmailAddressResetState { 4 | AVAILABLE, 5 | PENDING 6 | } 7 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/EmptyParameterInfo.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public final class EmptyParameterInfo implements ParameterInfo {} 4 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/GenericResultHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | 5 | /** 6 | * Interface for incoming responses from TDLib. 7 | */ 8 | @FunctionalInterface 9 | public interface GenericResultHandler { 10 | 11 | /** 12 | * Callback called when TDLib responds. 13 | */ 14 | void onResult(Result result); 15 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/GenericUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.Update; 4 | 5 | /** 6 | * Interface for incoming updates from TDLib. 7 | */ 8 | @FunctionalInterface 9 | public interface GenericUpdateHandler { 10 | 11 | /** 12 | * Callback called on incoming update from TDLib. 13 | * 14 | * @param update Update of type TdApi.Update about new events. 15 | */ 16 | void onUpdate(T update); 17 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/InputParameter.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public enum InputParameter { 4 | ASK_FIRST_NAME, 5 | ASK_LAST_NAME, 6 | ASK_CODE, 7 | ASK_PASSWORD, 8 | NOTIFY_LINK, 9 | TERMS_OF_SERVICE, 10 | ASK_EMAIL_ADDRESS, 11 | ASK_EMAIL_CODE 12 | } 13 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/MutableTelegramClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ExceptionHandler; 4 | import it.tdlight.jni.TdApi; 5 | 6 | public interface MutableTelegramClient { 7 | 8 | void setClientInteraction(ClientInteraction clientInteraction); 9 | 10 | void addCommandHandler(String commandName, CommandHandler handler); 11 | 12 | void addUpdateHandler(Class updateType, GenericUpdateHandler handler); 13 | 14 | void addUpdatesHandler(GenericUpdateHandler handler); 15 | 16 | void addUpdateExceptionHandler(ExceptionHandler updateExceptionHandler); 17 | 18 | void addDefaultExceptionHandler(ExceptionHandler defaultExceptionHandlers); 19 | } 20 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfo.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public interface ParameterInfo {} 4 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoCode.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.AuthenticationCodeType; 4 | import java.util.Objects; 5 | import java.util.StringJoiner; 6 | 7 | public final class ParameterInfoCode implements ParameterInfo { 8 | 9 | private final String phoneNumber; 10 | private final AuthenticationCodeType nextType; 11 | private final int timeout; 12 | private final AuthenticationCodeType type; 13 | 14 | public ParameterInfoCode(String phoneNumber, 15 | AuthenticationCodeType nextType, 16 | int timeout, 17 | AuthenticationCodeType type) { 18 | this.phoneNumber = phoneNumber; 19 | this.nextType = nextType; 20 | this.timeout = timeout; 21 | this.type = type; 22 | } 23 | 24 | public String getPhoneNumber() { 25 | return phoneNumber; 26 | } 27 | 28 | public AuthenticationCodeType getNextType() { 29 | return nextType; 30 | } 31 | 32 | public int getTimeout() { 33 | return timeout; 34 | } 35 | 36 | public AuthenticationCodeType getType() { 37 | return type; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) { 43 | return true; 44 | } 45 | if (o == null || getClass() != o.getClass()) { 46 | return false; 47 | } 48 | ParameterInfoCode that = (ParameterInfoCode) o; 49 | return timeout == that.timeout && Objects.equals(phoneNumber, that.phoneNumber) && Objects.equals(nextType, 50 | that.nextType 51 | ) && Objects.equals(type, that.type); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(phoneNumber, nextType, timeout, type); 57 | } 58 | 59 | @Override 60 | public String toString() { 61 | return new StringJoiner(", ", ParameterInfoCode.class.getSimpleName() + "[", "]") 62 | .add("phoneNumber='" + phoneNumber + "'") 63 | .add("nextType=" + nextType) 64 | .add("timeout=" + timeout) 65 | .add("type=" + type) 66 | .toString(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoEmailAddress.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.AuthenticationCodeType; 4 | import java.util.Objects; 5 | import java.util.StringJoiner; 6 | 7 | public final class ParameterInfoEmailAddress implements ParameterInfo { 8 | 9 | private final boolean allowGoogleId; 10 | private final boolean allowAppleId; 11 | 12 | public ParameterInfoEmailAddress(boolean allowGoogleId, 13 | boolean allowAppleId) { 14 | this.allowGoogleId = allowGoogleId; 15 | this.allowAppleId = allowAppleId; 16 | } 17 | 18 | public boolean isAllowGoogleId() { 19 | return allowGoogleId; 20 | } 21 | 22 | public boolean isAllowAppleId() { 23 | return allowAppleId; 24 | } 25 | 26 | @Override 27 | public boolean equals(Object o) { 28 | if (this == o) { 29 | return true; 30 | } 31 | if (o == null || getClass() != o.getClass()) { 32 | return false; 33 | } 34 | ParameterInfoEmailAddress that = (ParameterInfoEmailAddress) o; 35 | return allowGoogleId == that.allowGoogleId && allowAppleId == that.allowAppleId; 36 | } 37 | 38 | @Override 39 | public int hashCode() { 40 | return Objects.hash(allowGoogleId, allowAppleId); 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return new StringJoiner(", ", ParameterInfoEmailAddress.class.getSimpleName() + "[", "]") 46 | .add("allowGoogleId='" + allowGoogleId + "'") 47 | .add("allowAppleId=" + allowAppleId) 48 | .toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoEmailCode.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.EmailAddressAuthenticationCodeInfo; 4 | import it.tdlight.jni.TdApi.EmailAddressResetStateAvailable; 5 | import it.tdlight.jni.TdApi.EmailAddressResetStatePending; 6 | import java.util.Objects; 7 | import java.util.StringJoiner; 8 | 9 | public final class ParameterInfoEmailCode implements ParameterInfo { 10 | 11 | private final boolean allowGoogleId; 12 | private final boolean allowAppleId; 13 | private final String emailAddressPattern; 14 | private final int emailLength; 15 | private final EmailAddressResetState emailAddressResetState; 16 | 17 | public ParameterInfoEmailCode(boolean allowGoogleId, 18 | boolean allowAppleId, 19 | String emailAddressPattern, 20 | int emailLength, 21 | EmailAddressResetState emailAddressResetState) { 22 | this.allowGoogleId = allowGoogleId; 23 | this.allowAppleId = allowAppleId; 24 | this.emailAddressPattern = emailAddressPattern; 25 | this.emailLength = emailLength; 26 | this.emailAddressResetState = emailAddressResetState; 27 | } 28 | 29 | public boolean isAllowGoogleId() { 30 | return allowGoogleId; 31 | } 32 | 33 | public boolean isAllowAppleId() { 34 | return allowAppleId; 35 | } 36 | 37 | public String getEmailAddressPattern() { 38 | return emailAddressPattern; 39 | } 40 | 41 | public int getEmailLength() { 42 | return emailLength; 43 | } 44 | 45 | public EmailAddressResetState getEmailAddressResetState() { 46 | return emailAddressResetState; 47 | } 48 | 49 | @Override 50 | public boolean equals(Object o) { 51 | if (this == o) { 52 | return true; 53 | } 54 | if (o == null || getClass() != o.getClass()) { 55 | return false; 56 | } 57 | ParameterInfoEmailCode that = (ParameterInfoEmailCode) o; 58 | return allowGoogleId == that.allowGoogleId && allowAppleId == that.allowAppleId && emailLength == that.emailLength 59 | && Objects.equals(emailAddressPattern, that.emailAddressPattern) 60 | && emailAddressResetState == that.emailAddressResetState; 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | return Objects.hash(allowGoogleId, allowAppleId, emailAddressPattern, emailLength, emailAddressResetState); 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return new StringJoiner(", ", ParameterInfoEmailCode.class.getSimpleName() + "[", "]") 71 | .add("allowGoogleId=" + allowGoogleId) 72 | .add("allowAppleId=" + allowAppleId) 73 | .add("emailAddressPattern='" + emailAddressPattern + "'") 74 | .add("emailLength=" + emailLength) 75 | .add("emailAddressResetState=" + emailAddressResetState) 76 | .toString(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoNotifyLink.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.Objects; 4 | import java.util.StringJoiner; 5 | 6 | public final class ParameterInfoNotifyLink implements ParameterInfo { 7 | 8 | private final String link; 9 | 10 | public ParameterInfoNotifyLink(String link) { 11 | this.link = link; 12 | } 13 | 14 | public String getLink() { 15 | return link; 16 | } 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) { 21 | return true; 22 | } 23 | if (o == null || getClass() != o.getClass()) { 24 | return false; 25 | } 26 | ParameterInfoNotifyLink that = (ParameterInfoNotifyLink) o; 27 | return Objects.equals(link, that.link); 28 | } 29 | 30 | @Override 31 | public int hashCode() { 32 | return Objects.hash(link); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return new StringJoiner(", ", ParameterInfoNotifyLink.class.getSimpleName() + "[", "]") 38 | .add("link='" + link + "'") 39 | .toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoPasswordHint.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.Objects; 4 | import java.util.StringJoiner; 5 | 6 | public final class ParameterInfoPasswordHint implements ParameterInfo { 7 | 8 | private final String hint; 9 | private final boolean hasRecoveryEmailAddress; 10 | private final String recoveryEmailAddressPattern; 11 | 12 | public ParameterInfoPasswordHint(String hint, boolean hasRecoveryEmailAddress, String recoveryEmailAddressPattern) { 13 | this.hint = hint; 14 | this.hasRecoveryEmailAddress = hasRecoveryEmailAddress; 15 | this.recoveryEmailAddressPattern = recoveryEmailAddressPattern; 16 | } 17 | 18 | public String getHint() { 19 | return hint; 20 | } 21 | 22 | public String getRecoveryEmailAddressPattern() { 23 | return recoveryEmailAddressPattern; 24 | } 25 | 26 | public boolean hasRecoveryEmailAddress() { 27 | return hasRecoveryEmailAddress; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) { 33 | return true; 34 | } 35 | if (o == null || getClass() != o.getClass()) { 36 | return false; 37 | } 38 | ParameterInfoPasswordHint that = (ParameterInfoPasswordHint) o; 39 | return hasRecoveryEmailAddress == that.hasRecoveryEmailAddress && Objects.equals(hint, that.hint) && Objects.equals( 40 | recoveryEmailAddressPattern, 41 | that.recoveryEmailAddressPattern 42 | ); 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return Objects.hash(hint, hasRecoveryEmailAddress, recoveryEmailAddressPattern); 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return new StringJoiner(", ", ParameterInfoPasswordHint.class.getSimpleName() + "[", "]") 53 | .add("hint='" + hint + "'") 54 | .add("hasRecoveryEmailAddress=" + hasRecoveryEmailAddress) 55 | .add("recoveryEmailAddressPattern='" + recoveryEmailAddressPattern + "'") 56 | .toString(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ParameterInfoTermsOfService.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi.TermsOfService; 4 | import java.util.Objects; 5 | import java.util.StringJoiner; 6 | 7 | public final class ParameterInfoTermsOfService implements ParameterInfo { 8 | 9 | private final TermsOfService termsOfService; 10 | 11 | public ParameterInfoTermsOfService(TermsOfService termsOfService) { 12 | this.termsOfService = termsOfService; 13 | } 14 | 15 | public TermsOfService getTermsOfService() { 16 | return termsOfService; 17 | } 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) { 22 | return true; 23 | } 24 | if (o == null || getClass() != o.getClass()) { 25 | return false; 26 | } 27 | ParameterInfoTermsOfService that = (ParameterInfoTermsOfService) o; 28 | return Objects.equals(termsOfService, that.termsOfService); 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return Objects.hash(termsOfService); 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return new StringJoiner(", ", ParameterInfoTermsOfService.class.getSimpleName() + "[", "]") 39 | .add("termsOfService=" + termsOfService) 40 | .toString(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/QrCodeTerminal.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import com.google.zxing.BarcodeFormat; 4 | import com.google.zxing.EncodeHintType; 5 | import com.google.zxing.MultiFormatWriter; 6 | import com.google.zxing.WriterException; 7 | import com.google.zxing.common.BitMatrix; 8 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; 9 | import java.util.Hashtable; 10 | 11 | public class QrCodeTerminal { 12 | 13 | public static String getQr(String url) { 14 | int width = 40; 15 | int height = 40; 16 | Hashtable qrParam = new Hashtable<>(); 17 | qrParam.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L); 18 | qrParam.put(EncodeHintType.CHARACTER_SET, "utf-8"); 19 | try { 20 | BitMatrix bitMatrix = new MultiFormatWriter().encode(url, BarcodeFormat.QR_CODE, width, height, qrParam); 21 | return toAscii(bitMatrix); 22 | } catch (WriterException ex) { 23 | throw new IllegalStateException("Can't encode QR code", ex); 24 | } 25 | } 26 | 27 | static String toAscii(BitMatrix bitMatrix) { 28 | StringBuilder sb = new StringBuilder(); 29 | for (int rows = 0; rows < bitMatrix.getHeight(); rows++) { 30 | for (int cols = 0; cols < bitMatrix.getWidth(); cols++) { 31 | boolean x = bitMatrix.get(rows, cols); 32 | sb.append(x ? " " : "██"); 33 | } 34 | sb.append("\n"); 35 | } 36 | return sb.toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/Result.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Error; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | import java.util.StringJoiner; 8 | 9 | public final class Result { 10 | 11 | private final T object; 12 | private final Error error; 13 | private final TelegramError telegramError; 14 | 15 | private Result(T object, TdApi.Error error, TelegramError telegramError) { 16 | this.object = object; 17 | this.error = error; 18 | this.telegramError = telegramError; 19 | } 20 | 21 | public static Result of(TdApi.Object response) { 22 | if (response instanceof TdApi.Error) { 23 | return new Result<>(null, (TdApi.Error) response, null); 24 | } else { 25 | //noinspection unchecked 26 | return new Result<>((T) response, null, null); 27 | } 28 | } 29 | 30 | public static Result ofError(Throwable response) { 31 | return new Result<>(null, null, new TelegramError(response)); 32 | } 33 | 34 | public T get() { 35 | if (error != null) { 36 | throw new TelegramError(error); 37 | } else if (telegramError != null) { 38 | throw telegramError; 39 | } 40 | return Objects.requireNonNull(object); 41 | } 42 | 43 | public boolean isError() { 44 | return error != null || telegramError != null; 45 | } 46 | 47 | public TdApi.Error getError() { 48 | if (telegramError != null) { 49 | return telegramError.getError(); 50 | } 51 | return Objects.requireNonNull(error); 52 | } 53 | 54 | public Optional error() { 55 | if (error != null) { 56 | return Optional.of(error); 57 | } else if (telegramError != null) { 58 | return Optional.of(telegramError.getError()); 59 | } else { 60 | return Optional.empty(); 61 | } 62 | } 63 | 64 | @Override 65 | public boolean equals(Object o) { 66 | if (this == o) { 67 | return true; 68 | } 69 | if (o == null || getClass() != o.getClass()) { 70 | return false; 71 | } 72 | Result result = (Result) o; 73 | return Objects.equals(object, result.object) && Objects.equals(error, result.error) && Objects.equals(telegramError, 74 | result.telegramError 75 | ); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | return Objects.hash(object, error, telegramError); 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return new StringJoiner(", ", Result.class.getSimpleName() + "[", "]") 86 | .add("object=" + object) 87 | .add("error=" + error) 88 | .add("telegramError=" + telegramError) 89 | .toString(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/ScannerClientInteraction.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.util.ScannerUtils; 4 | import it.tdlight.jni.TdApi.TermsOfService; 5 | import java.util.Objects; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.Executor; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | final class ScannerClientInteraction implements ClientInteraction { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(ScannerClientInteraction.class); 14 | 15 | private final Executor blockingExecutor; 16 | private final Authenticable authenticable; 17 | 18 | public ScannerClientInteraction(Executor blockingExecutor, Authenticable authenticable) { 19 | this.blockingExecutor = blockingExecutor; 20 | this.authenticable = authenticable; 21 | } 22 | 23 | @Override 24 | public CompletableFuture onParameterRequest(InputParameter parameter, ParameterInfo parameterInfo) { 25 | AuthenticationSupplier authSupplier = authenticable.getAuthenticationSupplier(); 26 | AuthenticationData authData = getAuthDataNowOrNull(authSupplier); 27 | return CompletableFuture.supplyAsync(() -> { 28 | String who; 29 | boolean useRealWho = authData != null; 30 | if (!useRealWho) { 31 | who = "login"; 32 | } else if (authData.isQrCode()) { 33 | who = "QR login"; 34 | } else if (authData.isBot()) { 35 | who = authData.getBotToken().split(":", 2)[0]; 36 | } else { 37 | who = "+" + authData.getUserPhoneNumber(); 38 | } 39 | String question; 40 | boolean trim = false; 41 | switch (parameter) { 42 | case ASK_FIRST_NAME: 43 | question = "Enter first name"; 44 | trim = true; 45 | break; 46 | case ASK_LAST_NAME: 47 | question = "Enter last name"; 48 | trim = true; 49 | break; 50 | case ASK_CODE: 51 | question = "Enter authentication code"; 52 | ParameterInfoCode codeInfo = ((ParameterInfoCode) parameterInfo); 53 | question += "\n\tPhone number: " + codeInfo.getPhoneNumber(); 54 | question += "\n\tTimeout: " + codeInfo.getTimeout() + " seconds"; 55 | question += "\n\tCode type: " + codeInfo.getType().getClass().getSimpleName() 56 | .replace("AuthenticationCodeType", ""); 57 | if (codeInfo.getNextType() != null) { 58 | question += "\n\tNext code type: " + codeInfo 59 | .getNextType() 60 | .getClass() 61 | .getSimpleName() 62 | .replace("AuthenticationCodeType", ""); 63 | } 64 | trim = true; 65 | break; 66 | case ASK_PASSWORD: 67 | question = "Enter your password"; 68 | String passwordMessage = "Password authorization:"; 69 | String hint = ((ParameterInfoPasswordHint) parameterInfo).getHint(); 70 | if (hint != null && !hint.isEmpty()) { 71 | passwordMessage += "\n\tHint: " + hint; 72 | } 73 | boolean hasRecoveryEmailAddress = ((ParameterInfoPasswordHint) parameterInfo) 74 | .hasRecoveryEmailAddress(); 75 | passwordMessage += "\n\tHas recovery email: " + hasRecoveryEmailAddress; 76 | String recoveryEmailAddressPattern = ((ParameterInfoPasswordHint) parameterInfo) 77 | .getRecoveryEmailAddressPattern(); 78 | if (recoveryEmailAddressPattern != null && !recoveryEmailAddressPattern.isEmpty()) { 79 | passwordMessage += "\n\tRecovery email address pattern: " + recoveryEmailAddressPattern; 80 | } 81 | System.out.println(passwordMessage); 82 | break; 83 | case NOTIFY_LINK: 84 | String link = ((ParameterInfoNotifyLink) parameterInfo).getLink(); 85 | System.out.println("Please confirm this login link on another device: " + link); 86 | System.out.println(); 87 | try { 88 | System.out.println(QrCodeTerminal.getQr(link)); 89 | System.out.println(); 90 | } catch (NoClassDefFoundError ex) { 91 | LOG.warn("QR code library is missing!" 92 | + " Please add the following dependency to your project: com.google.zxing:core"); 93 | } 94 | return ""; 95 | case TERMS_OF_SERVICE: 96 | TermsOfService tos = ((ParameterInfoTermsOfService) parameterInfo).getTermsOfService(); 97 | question = "Terms of service:\n\t" + tos.text.text; 98 | if (tos.minUserAge > 0) { 99 | question += "\n\tMinimum user age: " + tos.minUserAge; 100 | } 101 | if (tos.showPopup) { 102 | question += "\nPlease press enter."; 103 | trim = true; 104 | } else { 105 | System.out.println(question); 106 | return ""; 107 | } 108 | break; 109 | default: 110 | question = parameter.toString(); 111 | break; 112 | } 113 | String result = ScannerUtils.askParameter(who, question); 114 | if (trim) { 115 | return result.trim(); 116 | } else { 117 | return Objects.requireNonNull(result); 118 | } 119 | }, blockingExecutor); 120 | } 121 | 122 | private AuthenticationData getAuthDataNowOrNull(AuthenticationSupplier authSupplier) { 123 | try { 124 | return authSupplier.get().getNow(null); 125 | } catch (Throwable ignored) { 126 | return null; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SequentialRequestsExecutor.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.Executors; 5 | 6 | class SequentialRequestsExecutor implements Executor { 7 | 8 | private static volatile SequentialRequestsExecutor INSTANCE; 9 | 10 | private final Executor executor = Executors.newSingleThreadExecutor(r -> { 11 | final Thread thread = new Thread(r); 12 | thread.setName("TDLight user input request"); 13 | thread.setDaemon(true); 14 | return thread; 15 | }); 16 | 17 | private SequentialRequestsExecutor() { 18 | 19 | } 20 | 21 | public static SequentialRequestsExecutor getInstance() { 22 | SequentialRequestsExecutor instance = INSTANCE; 23 | if (instance == null) { 24 | synchronized (SequentialRequestsExecutor.class) { 25 | if (INSTANCE == null) { 26 | INSTANCE = new SequentialRequestsExecutor(); 27 | } 28 | instance = INSTANCE; 29 | } 30 | } 31 | return instance; 32 | } 33 | 34 | @Override 35 | public void execute(Runnable command) { 36 | executor.execute(command); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SimpleAuthenticationSupplier.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | public interface SimpleAuthenticationSupplier extends AuthenticationSupplier, 4 | AuthenticationData {} 5 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SimpleResultHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ConstructorDetector; 4 | import it.tdlight.ResultHandler; 5 | import it.tdlight.jni.TdApi.Object; 6 | import it.tdlight.jni.TdApi.Update; 7 | 8 | class SimpleResultHandler implements ResultHandler { 9 | 10 | private final int updateConstructor; 11 | private final GenericUpdateHandler handler; 12 | 13 | public SimpleResultHandler(Class type, GenericUpdateHandler handler) { 14 | this.updateConstructor = ConstructorDetector.getConstructor(type); 15 | this.handler = handler; 16 | } 17 | 18 | @Override 19 | public void onResult(Object update) { 20 | if (update.getConstructor() == updateConstructor) { 21 | //noinspection unchecked 22 | handler.onUpdate((T) update); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClientBuilder.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ClientFactory; 4 | import it.tdlight.ExceptionHandler; 5 | import it.tdlight.ResultHandler; 6 | import it.tdlight.jni.TdApi.Update; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | public final class SimpleTelegramClientBuilder implements MutableTelegramClient { 15 | 16 | private static final Logger LOG = LoggerFactory.getLogger(SimpleTelegramClientBuilder.class); 17 | private final ClientFactory clientManager; 18 | private final TDLibSettings clientSettings; 19 | private ClientInteraction clientInteraction; 20 | 21 | private final Map> commandHandlers = new HashMap<>(); 22 | private final Set> updateHandlers = new HashSet<>(); 23 | private final Set updateExceptionHandlers = new HashSet<>(); 24 | private final Set defaultExceptionHandlers = new HashSet<>(); 25 | 26 | SimpleTelegramClientBuilder(ClientFactory clientManager, TDLibSettings clientSettings) { 27 | this.clientManager = clientManager; 28 | this.clientSettings = clientSettings; 29 | } 30 | 31 | @Override 32 | public void setClientInteraction(ClientInteraction clientInteraction) { 33 | this.clientInteraction = clientInteraction; 34 | } 35 | 36 | @Override 37 | public void addCommandHandler(String commandName, CommandHandler handler) { 38 | commandHandlers.computeIfAbsent(commandName, k -> new HashSet<>()).add(handler); 39 | } 40 | 41 | @Override 42 | public void addUpdateHandler(Class updateType, GenericUpdateHandler handler) { 43 | this.updateHandlers.add(new SimpleResultHandler<>(updateType, handler)); 44 | } 45 | 46 | @Override 47 | public void addUpdatesHandler(GenericUpdateHandler handler) { 48 | this.updateHandlers.add(new SimpleUpdateHandler(handler, LOG)); 49 | } 50 | 51 | @Override 52 | public void addUpdateExceptionHandler(ExceptionHandler updateExceptionHandler) { 53 | this.updateExceptionHandlers.add(updateExceptionHandler); 54 | } 55 | 56 | @Override 57 | public void addDefaultExceptionHandler(ExceptionHandler defaultExceptionHandlers) { 58 | this.defaultExceptionHandlers.add(defaultExceptionHandlers); 59 | } 60 | 61 | /** 62 | * Build and start the client 63 | * @return Telegram client 64 | */ 65 | public SimpleTelegramClient build(AuthenticationSupplier authenticationData) { 66 | return new SimpleTelegramClient(clientManager, 67 | clientSettings, 68 | commandHandlers, 69 | updateHandlers, 70 | updateExceptionHandlers, 71 | defaultExceptionHandlers, 72 | clientInteraction, 73 | authenticationData 74 | ); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SimpleTelegramClientFactory.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ClientFactory; 4 | 5 | public class SimpleTelegramClientFactory implements AutoCloseable { 6 | private final ClientFactory clientFactory; 7 | 8 | public SimpleTelegramClientFactory() { 9 | this(null); 10 | } 11 | 12 | public SimpleTelegramClientFactory(ClientFactory clientFactory) { 13 | if (clientFactory == null) { 14 | this.clientFactory = ClientFactory.acquireCommonClientFactory(); 15 | } else { 16 | this.clientFactory = clientFactory; 17 | } 18 | } 19 | 20 | public SimpleTelegramClientBuilder builder(TDLibSettings clientSettings) { 21 | return new SimpleTelegramClientBuilder(clientFactory, clientSettings); 22 | } 23 | 24 | @Override 25 | public void close() { 26 | clientFactory.close(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/SimpleUpdateHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.ResultHandler; 4 | import it.tdlight.jni.TdApi; 5 | import it.tdlight.jni.TdApi.Object; 6 | import it.tdlight.jni.TdApi.Update; 7 | import org.slf4j.Logger; 8 | 9 | public class SimpleUpdateHandler implements ResultHandler { 10 | 11 | private final GenericUpdateHandler handler; 12 | private final Logger logger; 13 | 14 | public SimpleUpdateHandler(GenericUpdateHandler handler, Logger logger) { 15 | this.handler = handler; 16 | this.logger = logger; 17 | } 18 | 19 | @Override 20 | public void onResult(Object update) { 21 | if (update instanceof TdApi.Update) { 22 | handler.onUpdate((TdApi.Update) update); 23 | } else { 24 | logger.warn("Unknown update type: {}", update); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/TDLibSettings.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.util.LibraryVersion; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | import java.util.Locale; 7 | import java.util.Objects; 8 | import java.util.StringJoiner; 9 | 10 | @SuppressWarnings("unused") 11 | public final class TDLibSettings { 12 | 13 | private static final Path USER_HOME_PATH = Paths.get(System.getProperty("user.home")); 14 | private static final String DISPLAY_LANGUAGE = Locale.getDefault().getDisplayLanguage(); 15 | private static final String OS_NAME = System.getProperty("os.name", "unknown"); 16 | private static final String OS_VERSION = System.getProperty("os.version", "unknown"); 17 | private static final Path TDLIGHT_SESSION_PATH = USER_HOME_PATH.resolve("tdlight-session"); 18 | private static final Path TDLIGHT_SESSION_DATA_PATH = TDLIGHT_SESSION_PATH.resolve("data"); 19 | private static final Path TDLIGHT_SESSION_DOWNLOADS_PATH = TDLIGHT_SESSION_PATH.resolve("downloads"); 20 | 21 | private boolean useTestDatacenter; 22 | private Path databaseDirectoryPath; 23 | private Path downloadedFilesDirectoryPath; 24 | private boolean fileDatabaseEnabled; 25 | private boolean chatInfoDatabaseEnabled; 26 | private boolean messageDatabaseEnabled; 27 | private APIToken apiToken; 28 | private String systemLanguageCode; 29 | private String deviceModel; 30 | private String systemVersion; 31 | private String applicationVersion; 32 | 33 | @Deprecated 34 | private TDLibSettings(boolean useTestDatacenter, 35 | Path databaseDirectoryPath, 36 | Path downloadedFilesDirectoryPath, 37 | boolean fileDatabaseEnabled, 38 | boolean chatInfoDatabaseEnabled, 39 | boolean messageDatabaseEnabled, 40 | APIToken apiToken, 41 | String systemLanguageCode, 42 | String deviceModel, 43 | String systemVersion, 44 | String applicationVersion, 45 | boolean enableStorageOptimizer, 46 | boolean ignoreFileNames) { 47 | this(useTestDatacenter, 48 | databaseDirectoryPath, 49 | downloadedFilesDirectoryPath, 50 | fileDatabaseEnabled, 51 | chatInfoDatabaseEnabled, 52 | messageDatabaseEnabled, 53 | apiToken, 54 | systemLanguageCode, 55 | deviceModel, 56 | systemVersion, 57 | applicationVersion 58 | ); 59 | } 60 | 61 | private TDLibSettings(boolean useTestDatacenter, 62 | Path databaseDirectoryPath, 63 | Path downloadedFilesDirectoryPath, 64 | boolean fileDatabaseEnabled, 65 | boolean chatInfoDatabaseEnabled, 66 | boolean messageDatabaseEnabled, 67 | APIToken apiToken, 68 | String systemLanguageCode, 69 | String deviceModel, 70 | String systemVersion, 71 | String applicationVersion) { 72 | this.useTestDatacenter = useTestDatacenter; 73 | this.databaseDirectoryPath = databaseDirectoryPath; 74 | this.downloadedFilesDirectoryPath = downloadedFilesDirectoryPath; 75 | this.fileDatabaseEnabled = fileDatabaseEnabled; 76 | this.chatInfoDatabaseEnabled = chatInfoDatabaseEnabled; 77 | this.messageDatabaseEnabled = messageDatabaseEnabled; 78 | this.apiToken = apiToken; 79 | this.systemLanguageCode = systemLanguageCode; 80 | this.deviceModel = deviceModel; 81 | this.systemVersion = systemVersion; 82 | this.applicationVersion = applicationVersion; 83 | } 84 | 85 | public static TDLibSettings create(APIToken apiToken) { 86 | return new TDLibSettings(false, 87 | TDLIGHT_SESSION_DATA_PATH, 88 | TDLIGHT_SESSION_DOWNLOADS_PATH, 89 | true, 90 | true, 91 | true, 92 | apiToken, 93 | DISPLAY_LANGUAGE, 94 | OS_NAME, 95 | OS_VERSION, 96 | LibraryVersion.VERSION, 97 | true, 98 | false 99 | ); 100 | } 101 | 102 | public boolean isUsingTestDatacenter() { 103 | return useTestDatacenter; 104 | } 105 | 106 | public void setUseTestDatacenter(boolean useTestDatacenter) { 107 | this.useTestDatacenter = useTestDatacenter; 108 | } 109 | 110 | public Path getDatabaseDirectoryPath() { 111 | return databaseDirectoryPath; 112 | } 113 | 114 | public void setDatabaseDirectoryPath(Path databaseDirectoryPath) { 115 | this.databaseDirectoryPath = databaseDirectoryPath; 116 | } 117 | 118 | public Path getDownloadedFilesDirectoryPath() { 119 | return downloadedFilesDirectoryPath; 120 | } 121 | 122 | public void setDownloadedFilesDirectoryPath(Path downloadedFilesDirectoryPath) { 123 | this.downloadedFilesDirectoryPath = downloadedFilesDirectoryPath; 124 | } 125 | 126 | public boolean isFileDatabaseEnabled() { 127 | return fileDatabaseEnabled; 128 | } 129 | 130 | public void setFileDatabaseEnabled(boolean fileDatabaseEnabled) { 131 | this.fileDatabaseEnabled = fileDatabaseEnabled; 132 | } 133 | 134 | public boolean isChatInfoDatabaseEnabled() { 135 | return chatInfoDatabaseEnabled; 136 | } 137 | 138 | public void setChatInfoDatabaseEnabled(boolean chatInfoDatabaseEnabled) { 139 | this.chatInfoDatabaseEnabled = chatInfoDatabaseEnabled; 140 | } 141 | 142 | public boolean isMessageDatabaseEnabled() { 143 | return messageDatabaseEnabled; 144 | } 145 | 146 | public void setMessageDatabaseEnabled(boolean messageDatabaseEnabled) { 147 | this.messageDatabaseEnabled = messageDatabaseEnabled; 148 | } 149 | 150 | public APIToken getApiToken() { 151 | return apiToken; 152 | } 153 | 154 | public void setApiToken(APIToken apiToken) { 155 | this.apiToken = apiToken; 156 | } 157 | 158 | public String getSystemLanguageCode() { 159 | return systemLanguageCode; 160 | } 161 | 162 | public void setSystemLanguageCode(String systemLanguageCode) { 163 | this.systemLanguageCode = systemLanguageCode; 164 | } 165 | 166 | public String getDeviceModel() { 167 | return deviceModel; 168 | } 169 | 170 | public void setDeviceModel(String deviceModel) { 171 | this.deviceModel = deviceModel; 172 | } 173 | 174 | public String getSystemVersion() { 175 | return systemVersion; 176 | } 177 | 178 | public void setSystemVersion(String systemVersion) { 179 | this.systemVersion = systemVersion; 180 | } 181 | 182 | public String getApplicationVersion() { 183 | return applicationVersion; 184 | } 185 | 186 | public void setApplicationVersion(String applicationVersion) { 187 | this.applicationVersion = applicationVersion; 188 | } 189 | 190 | @Deprecated 191 | public boolean isStorageOptimizerEnabled() { 192 | return false; 193 | } 194 | 195 | @Deprecated 196 | public void setEnableStorageOptimizer(boolean enableStorageOptimizer) { 197 | } 198 | 199 | @Deprecated 200 | public boolean isIgnoreFileNames() { 201 | return false; 202 | } 203 | 204 | @Deprecated 205 | public void setIgnoreFileNames(boolean ignoreFileNames) { 206 | } 207 | 208 | @Override 209 | public boolean equals(Object o) { 210 | if (this == o) { 211 | return true; 212 | } 213 | if (o == null || getClass() != o.getClass()) { 214 | return false; 215 | } 216 | TDLibSettings that = (TDLibSettings) o; 217 | return useTestDatacenter == that.useTestDatacenter && fileDatabaseEnabled == that.fileDatabaseEnabled 218 | && chatInfoDatabaseEnabled == that.chatInfoDatabaseEnabled 219 | && messageDatabaseEnabled == that.messageDatabaseEnabled 220 | && Objects.equals(databaseDirectoryPath, that.databaseDirectoryPath) && Objects.equals( 221 | downloadedFilesDirectoryPath, 222 | that.downloadedFilesDirectoryPath 223 | ) && Objects.equals(apiToken, that.apiToken) && Objects.equals(systemLanguageCode, that.systemLanguageCode) 224 | && Objects.equals(deviceModel, that.deviceModel) && Objects.equals(systemVersion, that.systemVersion) 225 | && Objects.equals(applicationVersion, that.applicationVersion); 226 | } 227 | 228 | @Override 229 | public int hashCode() { 230 | return Objects.hash(useTestDatacenter, 231 | databaseDirectoryPath, 232 | downloadedFilesDirectoryPath, 233 | fileDatabaseEnabled, 234 | chatInfoDatabaseEnabled, 235 | messageDatabaseEnabled, 236 | apiToken, 237 | systemLanguageCode, 238 | deviceModel, 239 | systemVersion, 240 | applicationVersion 241 | ); 242 | } 243 | 244 | @Override 245 | public String toString() { 246 | return new StringJoiner(", ", TDLibSettings.class.getSimpleName() + "[", "]") 247 | .add("useTestDatacenter=" + useTestDatacenter) 248 | .add("databaseDirectoryPath=" + databaseDirectoryPath) 249 | .add("downloadedFilesDirectoryPath=" + downloadedFilesDirectoryPath) 250 | .add("fileDatabaseEnabled=" + fileDatabaseEnabled) 251 | .add("chatInfoDatabaseEnabled=" + chatInfoDatabaseEnabled) 252 | .add("messageDatabaseEnabled=" + messageDatabaseEnabled) 253 | .add("apiData=" + apiToken) 254 | .add("systemLanguageCode='" + systemLanguageCode + "'") 255 | .add("deviceModel='" + deviceModel + "'") 256 | .add("systemVersion='" + systemVersion + "'") 257 | .add("applicationVersion='" + applicationVersion + "'") 258 | .toString(); 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/TelegramError.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import it.tdlight.jni.TdApi.Error; 5 | import java.util.Objects; 6 | 7 | public final class TelegramError extends RuntimeException { 8 | 9 | private final int code; 10 | private final String message; 11 | 12 | public TelegramError(Error error) { 13 | super(error.code + ": " + error.message); 14 | this.code = error.code; 15 | this.message = error.message; 16 | } 17 | 18 | public TelegramError(Error error, Throwable cause) { 19 | super(error.code + ": " + error.message, cause); 20 | this.code = error.code; 21 | this.message = error.message; 22 | } 23 | 24 | public TelegramError(Throwable cause) { 25 | super(cause); 26 | this.code = 500; 27 | if (cause.getMessage() != null) { 28 | this.message = cause.getMessage(); 29 | } else { 30 | this.message = "Error"; 31 | } 32 | } 33 | 34 | public int getErrorCode() { 35 | return code; 36 | } 37 | 38 | public String getErrorMessage() { 39 | return message; 40 | } 41 | 42 | public Error getError() { 43 | return new TdApi.Error(code, message); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) { 49 | return true; 50 | } 51 | if (o == null || getClass() != o.getClass()) { 52 | return false; 53 | } 54 | TelegramError that = (TelegramError) o; 55 | return code == that.code && Objects.equals(message, that.message); 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return Objects.hash(code, message); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/TemporaryMessageHandler.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.ConcurrentMap; 8 | 9 | class TemporaryMessageHandler implements GenericUpdateHandler { 10 | 11 | private static final Logger LOG = LoggerFactory.getLogger(TemporaryMessageHandler.class); 12 | 13 | private final ConcurrentMap> temporaryMessages; 14 | 15 | public TemporaryMessageHandler(ConcurrentMap> temporaryMessages) { 16 | this.temporaryMessages = temporaryMessages; 17 | } 18 | 19 | @Override 20 | public void onUpdate(TdApi.Update update) { 21 | switch (update.getConstructor()) { 22 | case TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR: onUpdateSuccess(((TdApi.UpdateMessageSendSucceeded) update)); 23 | break; 24 | case TdApi.UpdateMessageSendFailed.CONSTRUCTOR: onUpdateFailed(((TdApi.UpdateMessageSendFailed) update)); 25 | break; 26 | } 27 | } 28 | 29 | private void onUpdateSuccess(TdApi.UpdateMessageSendSucceeded updateMessageSendSucceeded) { 30 | TemporaryMessageURL tempUrl 31 | = new TemporaryMessageURL(updateMessageSendSucceeded.message.chatId, updateMessageSendSucceeded.oldMessageId); 32 | CompletableFuture future = temporaryMessages.remove(tempUrl); 33 | if (future == null) { 34 | logNotHandled(tempUrl); 35 | } else { 36 | future.complete(updateMessageSendSucceeded.message); 37 | } 38 | } 39 | 40 | private void onUpdateFailed(TdApi.UpdateMessageSendFailed updateMessageSendFailed) { 41 | TemporaryMessageURL tempUrl 42 | = new TemporaryMessageURL(updateMessageSendFailed.message.chatId, updateMessageSendFailed.oldMessageId); 43 | CompletableFuture future = temporaryMessages.remove(tempUrl); 44 | if (future == null) { 45 | logNotHandled(tempUrl); 46 | } else { 47 | TdApi.Error error = updateMessageSendFailed.error; 48 | future.completeExceptionally(new TelegramError(error)); 49 | } 50 | } 51 | 52 | private void logNotHandled(TemporaryMessageURL tempUrl) { 53 | LOG.debug("The message {} is not being handled by the client", tempUrl); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/client/TemporaryMessageURL.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.client; 2 | 3 | import java.util.Objects; 4 | 5 | final class TemporaryMessageURL { 6 | 7 | private final long chatId; 8 | private final long messageId; 9 | 10 | TemporaryMessageURL(long chatId, long messageId) { 11 | this.chatId = chatId; 12 | this.messageId = messageId; 13 | } 14 | 15 | public long chatId() { 16 | return chatId; 17 | } 18 | 19 | public long messageId() { 20 | return messageId; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object obj) { 25 | if (obj == this) { 26 | return true; 27 | } 28 | if (obj == null || obj.getClass() != this.getClass()) { 29 | return false; 30 | } 31 | TemporaryMessageURL that = (TemporaryMessageURL) obj; 32 | return this.chatId == that.chatId && this.messageId == that.messageId; 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return Objects.hash(chatId, messageId); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "TemporaryMessageURL[" + "chatId=" + chatId + ", " + "messageId=" + messageId + ']'; 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/tdnative/NativeClient.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.tdnative; 2 | 3 | import it.tdlight.jni.TdApi; 4 | 5 | @SuppressWarnings("rawtypes") 6 | public class NativeClient { 7 | 8 | protected static native int createNativeClient(); 9 | 10 | protected static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function); 11 | 12 | protected static native int nativeClientReceive(int[] clientIds, 13 | long[] eventIds, 14 | TdApi.Object[] events, 15 | double timeout); 16 | 17 | protected static native TdApi.Object nativeClientExecute(TdApi.Function function); 18 | 19 | protected static native void nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler); 20 | 21 | /** 22 | * Interface for handler of messages that are added to the internal TDLib log. 23 | */ 24 | public interface LogMessageHandler { 25 | /** 26 | * Callback called on messages that are added to the internal TDLib log. 27 | * 28 | * @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024. 29 | * If 0, then TDLib will crash as soon as the callback returns. 30 | * None of the TDLib methods can be called from the callback. 31 | * @param message The message added to the internal TDLib log. 32 | */ 33 | void onLogMessage(int verbosityLevel, String message); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/CleanSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | public class CleanSupport { 6 | 7 | public static CleanableSupport register(Object object, Runnable cleanAction) { 8 | //noinspection removal 9 | return new CleanableSupport() { 10 | private final AtomicBoolean clean = new AtomicBoolean(false); 11 | @Override 12 | public void clean() { 13 | if (clean.compareAndSet(false, true)) { 14 | cleanAction.run(); 15 | } 16 | } 17 | 18 | @Override 19 | protected void finalize() { 20 | this.clean(); 21 | } 22 | }; 23 | } 24 | 25 | 26 | public interface CleanableSupport { 27 | public void clean(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/FutureSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | public class FutureSupport { 6 | 7 | public static CompletableFuture copy(CompletableFuture future) { 8 | return CompletableFuture.completedFuture(true).thenCompose(ignored -> future); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/IntSwapper.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | public final class IntSwapper { 4 | 5 | private final int[] array; 6 | int tmp; 7 | 8 | public IntSwapper(int[] array) { 9 | this.array = array; 10 | tmp = 0; 11 | } 12 | 13 | public void swap(int a, int b) { 14 | tmp = array[a]; 15 | array[a] = array[b]; 16 | array[b] = tmp; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/MapUtils.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | 6 | public class MapUtils { 7 | public static void addAllKeys(Map map, Collection keys, U placeholderValue) { 8 | keys.forEach(k -> map.put(k, placeholderValue)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/Native.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Ernesto Castellotti 3 | * This file is part of JTdlib. 4 | * 5 | * JTdlib is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License. 8 | * 9 | * JTdlib is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with JTdlib. If not, see . 16 | */ 17 | package it.tdlight.util; 18 | 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import java.nio.ByteOrder; 22 | import java.util.LinkedHashSet; 23 | import java.util.List; 24 | import java.util.Set; 25 | import java.util.stream.Collectors; 26 | import java.util.stream.Stream; 27 | 28 | /** 29 | * The class to load the libraries needed to run Tdlib 30 | */ 31 | public final class Native { 32 | 33 | /** 34 | * Internal util 35 | */ 36 | public static void loadNativesInternal() throws UnsupportedNativeLibraryException { 37 | loadLibrary("tdjni"); 38 | } 39 | 40 | private static final Logger logger = LoggerFactory.getLogger(Native.class); 41 | 42 | /** 43 | * Load a native library 44 | * @param libraryName Library name 45 | * @throws UnsupportedNativeLibraryException The library can't be loaded 46 | */ 47 | private static void loadLibrary(String libraryName) throws UnsupportedNativeLibraryException { 48 | ClassLoader cl = Native.class.getClassLoader(); 49 | String staticLibName = libraryName; 50 | List sharedLibNames = getNormalizedArchitectures().map(suffix -> staticLibName + "." + suffix).collect(Collectors.toList()); 51 | if (sharedLibNames.isEmpty()) { 52 | throw new IllegalStateException(); 53 | } 54 | try { 55 | NativeLibraryLoader.loadFirstAvailable(cl, sharedLibNames.toArray(new String[0])); 56 | } catch (IllegalArgumentException | UnsatisfiedLinkError e1) { 57 | try { 58 | NativeLibraryLoader.load(staticLibName, cl); 59 | logger.debug("Failed to load {}", String.join(", ", sharedLibNames), e1); 60 | } catch (UnsatisfiedLinkError e2) { 61 | if (e2.getMessage().contains("libc++.so.1: cannot open shared")) { 62 | throw new UnsupportedNativeLibraryException("Install \"libc++\" to use TDLight Java!"); 63 | } else { 64 | e1.addSuppressed(e2); 65 | throw new UnsupportedNativeLibraryException(e1); 66 | } 67 | } 68 | } 69 | } 70 | 71 | private static Stream getNormalizedArchitectures() { 72 | String os = getOs(); 73 | String arch = getCpuArch(); 74 | if (os.equals("unknown") || arch.equals("unknown")) { 75 | return getAllNormalizedArchitectures(); 76 | } 77 | return getNormalizedArchitectures(os, arch); 78 | } 79 | 80 | private static Stream getAllNormalizedArchitectures() { 81 | Set all = new LinkedHashSet<>(); 82 | for (String os : new String[]{"windows"}) { 83 | for (String arch : new String[]{"arm64", "amd64", "armhf", "i386", "s390x", "ppc64el", "riscv64"}) { 84 | getNormalizedArchitectures(os, arch).forEach(all::add); 85 | } 86 | } 87 | return all.stream(); 88 | } 89 | 90 | private static Stream getNormalizedArchitectures(String os, String arch) { 91 | switch (os) { 92 | case "linux": { 93 | return Stream.of("linux_" + arch + "_clang_ssl1", "linux_" + arch + "_clang_ssl3", "linux_" + arch + "_gnu_ssl1", "linux_" + arch + "_gnu_ssl3"); 94 | } 95 | case "windows": { 96 | return Stream.of("windows_" + arch); 97 | } 98 | case "macos": { 99 | return Stream.of("macos_" + arch); 100 | } 101 | default: { 102 | throw new UnsupportedOperationException(); 103 | } 104 | } 105 | } 106 | 107 | private static String getCpuArch() { 108 | String architecture = System.getProperty("os.arch").trim(); 109 | switch (architecture) { 110 | case "amd64": 111 | case "x86_64": 112 | return "amd64"; 113 | case "i386": 114 | case "x86": 115 | case "386": 116 | case "i686": 117 | case "686": 118 | return "i386"; 119 | case "armv6": 120 | case "arm": 121 | case "armhf": 122 | case "aarch32": 123 | case "armv7": 124 | case "armv7l": 125 | return "armhf"; 126 | case "arm64": 127 | case "aarch64": 128 | case "armv8": 129 | case "armv8l": 130 | return "arm64"; 131 | case "s390x": 132 | return "s390x"; 133 | case "riscv64": 134 | return "riscv64"; 135 | case "powerpc": 136 | case "powerpc64": 137 | case "powerpc64le": 138 | case "powerpc64el": 139 | case "ppc": 140 | case "ppc64": 141 | case "ppc64le": 142 | case "ppc64el": 143 | if (ByteOrder 144 | .nativeOrder() 145 | .equals(ByteOrder.LITTLE_ENDIAN)) // Java always returns ppc64 for all 64-bit powerpc but 146 | { 147 | return "ppc64el"; // powerpc64le (our target) is very different, it uses this condition to accurately identify the architecture 148 | } else { 149 | return "unknown"; 150 | } 151 | default: 152 | return "unknown"; 153 | } 154 | } 155 | 156 | public static String getOs() { 157 | String os = System.getProperty("os.name").toLowerCase().trim(); 158 | if (os.contains("linux")) { 159 | return "linux"; 160 | } 161 | if (os.contains("windows")) { 162 | return "windows"; 163 | } 164 | if (os.contains("mac")) { 165 | return "macos"; 166 | } 167 | if (os.contains("darwin")) { 168 | return "macos"; 169 | } 170 | return "unknown"; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/NativeLibraryUtil.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | /** 4 | * A Utility to Call the {@link System#load(String)} or {@link System#loadLibrary(String)}. 5 | * Because the {@link System#load(String)} and {@link System#loadLibrary(String)} are both 6 | * CallerSensitive, it will load the native library into its caller's {@link ClassLoader}. 7 | * In OSGi environment, we need this helper to delegate the calling to {@link System#load(String)} 8 | * and it should be as simple as possible. It will be injected into the native library's 9 | * ClassLoader when it is undefined. And therefore, when the defined new helper is invoked, 10 | * the native library would be loaded into the native library's ClassLoader, not the 11 | * caller's ClassLoader. 12 | */ 13 | final class NativeLibraryUtil { 14 | /** 15 | * Delegate the calling to {@link System#load(String)} or {@link System#loadLibrary(String)}. 16 | * @param libName - The native library path or name 17 | * @param absolute - Whether the native library will be loaded by path or by name 18 | */ 19 | public static void loadLibrary(String libName, boolean absolute) { 20 | if (absolute) { 21 | System.load(libName); 22 | } else { 23 | System.loadLibrary(libName); 24 | } 25 | } 26 | 27 | private NativeLibraryUtil() { 28 | // Utility 29 | } 30 | } -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/ScannerUtils.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.io.Console; 4 | import java.io.IOException; 5 | import java.io.InputStreamReader; 6 | import java.io.Reader; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public final class ScannerUtils { 11 | 12 | private static final Object LOCK = new Object(); 13 | private static InputStreamReader scanner = null; 14 | 15 | public static String askParameter(String displayName, String question) { 16 | synchronized (LOCK) { 17 | Console console = System.console(); 18 | if (console != null) { 19 | return console.readLine("[%s] %s: ", displayName, question); 20 | } else { 21 | if (scanner == null) { 22 | scanner = new InputStreamReader(System.in); 23 | } 24 | System.out.printf("[%s] %s: ", displayName, question); 25 | try { 26 | return interruptibleReadLine(scanner); 27 | } catch (InterruptedException | IOException e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | } 32 | } 33 | 34 | /** 35 | * https://stackoverflow.com/questions/3595926/how-to-interrupt-bufferedreaders-readline 36 | */ 37 | private static String interruptibleReadLine(Reader reader) throws InterruptedException, IOException { 38 | Pattern line = Pattern.compile("^(.*)\\R"); 39 | Matcher matcher; 40 | boolean interrupted; 41 | 42 | StringBuilder result = new StringBuilder(); 43 | int chr = -1; 44 | do { 45 | if (reader.ready()) { 46 | chr = reader.read(); 47 | } 48 | if (chr > -1) { 49 | result.append((char) chr); 50 | } 51 | matcher = line.matcher(result.toString()); 52 | interrupted = Thread.interrupted(); // resets flag, call only once 53 | } while (!interrupted && !matcher.matches()); 54 | if (interrupted) { 55 | throw new InterruptedException(); 56 | } 57 | return (matcher.matches() ? matcher.group(1) : ""); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/SimpleIntQueue.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.function.IntConsumer; 4 | 5 | public final class SimpleIntQueue { 6 | public int size = 0; 7 | public int[] a = new int[16]; 8 | 9 | public void add(int i) { 10 | if (size >= a.length) { 11 | int[] prev = a; 12 | a = new int[a.length << 1]; 13 | System.arraycopy(prev, 0, a, 0, prev.length); 14 | } 15 | a[size++] = i; 16 | } 17 | 18 | public void drain(IntConsumer consumer) { 19 | for (int i = 0; i < size; i++) { 20 | consumer.accept(a[i]); 21 | } 22 | reset(); 23 | } 24 | 25 | public void reset() { 26 | size = 0; 27 | } 28 | 29 | public boolean isContentful() { 30 | return size > 0; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/SpinWaitSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.concurrent.locks.LockSupport; 4 | 5 | public class SpinWaitSupport { 6 | 7 | private SpinWaitSupport() { 8 | } 9 | 10 | public static void onSpinWait() { 11 | // park for 10ms 12 | LockSupport.parkNanos(10 * 1000L * 1000L); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/TDLightBlockHoundIntegration.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | 4 | import reactor.blockhound.BlockHound.Builder; 5 | import reactor.blockhound.integration.BlockHoundIntegration; 6 | 7 | public class TDLightBlockHoundIntegration implements BlockHoundIntegration { 8 | 9 | @Override 10 | public void applyTo(Builder builder) { 11 | builder.nonBlockingThreadPredicate(current -> current.or(t -> { 12 | if (t.getName() == null) { 13 | return false; 14 | } 15 | return t.getName().equals("TDLib thread"); 16 | })); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/it/tdlight/util/UnsupportedNativeLibraryException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018. Ernesto Castellotti 3 | * This file is part of JTdlib. 4 | * 5 | * JTdlib is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU Lesser General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License. 8 | * 9 | * JTdlib is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU Lesser General Public License 15 | * along with JTdlib. If not, see . 16 | */ 17 | 18 | package it.tdlight.util; 19 | 20 | /** 21 | * An exception that is thrown when a native library can't be loaded. 22 | */ 23 | public final class UnsupportedNativeLibraryException extends Exception { 24 | 25 | /** 26 | * Creates a new UnsupportedNativeLibraryException. 27 | */ 28 | UnsupportedNativeLibraryException() { 29 | super("Failed to load TDLight native libraries"); 30 | } 31 | 32 | public UnsupportedNativeLibraryException(String message) { 33 | super(message); 34 | } 35 | 36 | public UnsupportedNativeLibraryException(String message, Exception cause) { 37 | super(message, cause); 38 | } 39 | 40 | public UnsupportedNativeLibraryException(Throwable cause) { 41 | super("Failed to load TDLight native libraries", cause); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module tdlight.java { 2 | requires tdlight.api; 3 | requires org.reactivestreams; 4 | requires org.slf4j; 5 | requires atlassian.util.concurrent; 6 | requires static com.google.zxing; 7 | requires static reactor.blockhound; 8 | exports it.tdlight.tdnative; 9 | exports it.tdlight; 10 | exports it.tdlight.util; 11 | exports it.tdlight.client; 12 | } 13 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java11/it/tdlight/util/CleanSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.lang.ref.Cleaner; 4 | 5 | public class CleanSupport { 6 | 7 | private static final Cleaner cleaner = Cleaner.create(); 8 | 9 | public static CleanableSupport register(Object object, Runnable cleanAction) { 10 | var c = cleaner.register(object, cleanAction); 11 | return c::clean; 12 | } 13 | 14 | public interface CleanableSupport { 15 | public void clean(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java11/it/tdlight/util/FutureSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | 5 | public class FutureSupport { 6 | 7 | public static CompletableFuture copy(CompletableFuture future) { 8 | return future.copy(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tdlight-java/src/main/java11/it/tdlight/util/SpinWaitSupport.java: -------------------------------------------------------------------------------- 1 | package it.tdlight.util; 2 | 3 | public class SpinWaitSupport { 4 | 5 | private SpinWaitSupport() { 6 | } 7 | 8 | public static void onSpinWait() { 9 | java.lang.Thread.onSpinWait(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /tdlight-java/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration: -------------------------------------------------------------------------------- 1 | it.tdlight.util.TDLightBlockHoundIntegration 2 | -------------------------------------------------------------------------------- /tdlight-java/src/test/java/it/tdlight/Event.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import java.util.Objects; 5 | 6 | public final class Event { 7 | 8 | private final int clientId; 9 | private final long eventId; 10 | private final TdApi.Object event; 11 | 12 | Event(int clientId, long eventId, TdApi.Object event) { 13 | this.clientId = clientId; 14 | this.eventId = eventId; 15 | this.event = event; 16 | } 17 | 18 | public int clientId() { 19 | return clientId; 20 | } 21 | 22 | public long eventId() { 23 | return eventId; 24 | } 25 | 26 | public TdApi.Object event() { 27 | return event; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object obj) { 32 | if (obj == this) { 33 | return true; 34 | } 35 | if (obj == null || obj.getClass() != this.getClass()) { 36 | return false; 37 | } 38 | Event that = (Event) obj; 39 | return this.clientId == that.clientId && this.eventId == that.eventId && Objects.equals(this.event, that.event); 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(clientId, eventId, event); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "Event[" + "clientId=" + clientId + ", " + "eventId=" + eventId + ", " + "event=" + event + ']'; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tdlight-java/src/test/java/it/tdlight/HandleEventsTest.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import it.tdlight.ResponseReceiver; 6 | import it.tdlight.jni.TdApi; 7 | import it.tdlight.jni.TdApi.Object; 8 | import it.unimi.dsi.fastutil.longs.LongArraySet; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Set; 14 | import java.util.concurrent.ArrayBlockingQueue; 15 | import java.util.concurrent.CountDownLatch; 16 | import java.util.stream.Collectors; 17 | import org.junit.jupiter.api.Test; 18 | 19 | public class HandleEventsTest { 20 | 21 | @Test 22 | public void test() throws InterruptedException { 23 | System.setProperty("tdlight.dispatcher.use_optimized_dispatcher", "false"); 24 | List initialEvents = new ArrayList<>(); 25 | initialEvents.add(new Event(2, 0, new TdApi.LogVerbosityLevel(0))); 26 | initialEvents.add(new Event(1, 1, new TdApi.LogVerbosityLevel(1))); 27 | initialEvents.add(new Event(7, 3, new TdApi.LogVerbosityLevel(2))); 28 | initialEvents.add(new Event(7, 0, new TdApi.LogVerbosityLevel(3))); 29 | initialEvents.add(new Event(1, 7, new TdApi.LogVerbosityLevel(4))); 30 | initialEvents.add(new Event(2, 398, new TdApi.LogVerbosityLevel(5))); 31 | initialEvents.add(new Event(7, 98832, new TdApi.LogVerbosityLevel(6))); 32 | initialEvents.add(new Event(2, 32832, new TdApi.LogVerbosityLevel(7))); 33 | initialEvents.add(new Event(1, 39484, new TdApi.LogVerbosityLevel(8))); 34 | initialEvents.add(new Event(1, 39485, new TdApi.LogVerbosityLevel(9))); 35 | initialEvents.add(new Event(1, 39486, new TdApi.LogVerbosityLevel(10))); 36 | initialEvents.add(new Event(1, 39487, new TdApi.LogVerbosityLevel(11))); 37 | initialEvents.add(new Event(1, 39488, new TdApi.LogVerbosityLevel(12))); 38 | CountDownLatch eventsQueueEmptied = new CountDownLatch(1); 39 | ArrayBlockingQueue eventsQueue = new ArrayBlockingQueue<>(1024); 40 | eventsQueue.addAll(initialEvents); 41 | ArrayBlockingQueue results = new ArrayBlockingQueue<>(1024); 42 | ResponseReceiver responseReceiver = new ResponseReceiver((clientId, isClosed, clientEventIds, clientEvents, arrayOffset, arrayLength) -> { 43 | results.add(new Result(clientId, 44 | isClosed, 45 | Arrays.copyOf(clientEventIds, clientEventIds.length), 46 | Arrays.copyOf(clientEvents, clientEvents.length), 47 | arrayOffset, 48 | arrayLength 49 | )); 50 | }) { 51 | @Override 52 | public int receive(int[] clientIds, long[] eventIds, Object[] events, double timeout) { 53 | int i = 0; 54 | while (!eventsQueue.isEmpty() && i < clientIds.length) { 55 | Event event = eventsQueue.poll(); 56 | clientIds[i] = event.clientId(); 57 | eventIds[i] = event.eventId(); 58 | events[i] = event.event(); 59 | i++; 60 | } 61 | if (eventsQueue.isEmpty()) { 62 | eventsQueueEmptied.countDown(); 63 | } 64 | return i; 65 | } 66 | }; 67 | responseReceiver.registerClient(2); 68 | responseReceiver.registerClient(1); 69 | responseReceiver.registerClient(7); 70 | responseReceiver.start(); 71 | eventsQueueEmptied.await(); 72 | responseReceiver.interrupt(); 73 | responseReceiver.close(); 74 | HashMap resultsMap = new HashMap<>(); 75 | while (!results.isEmpty()) { 76 | Result part = results.poll(); 77 | if (part.arrayLength() > 0) { 78 | Result prev = resultsMap.get(part.clientId()); 79 | if (prev == null) { 80 | prev = new Result(part.clientId(), false, new long[0], new TdApi.Object[0], 0, 0); 81 | } 82 | int newSize = part.arrayLength() + prev.arrayLength(); 83 | long[] newIds = new long[newSize]; 84 | TdApi.Object[] newEvents = new TdApi.Object[newSize]; 85 | int i = 0; 86 | for (int i1 = 0; i1 < prev.arrayLength(); i1++, i++) { 87 | newIds[i] = prev.clientEventIds()[i1 + prev.arrayOffset()]; 88 | newEvents[i] = prev.clientEvents()[i1 + prev.arrayOffset()]; 89 | } 90 | for (int i1 = 0; i1 < part.arrayLength(); i1++, i++) { 91 | newIds[i] = part.clientEventIds()[i1 + part.arrayOffset()]; 92 | newEvents[i] = part.clientEvents()[i1 + part.arrayOffset()]; 93 | } 94 | resultsMap.put(part.clientId(), new Result(part.clientId(), part.isClosed() || prev.isClosed(), newIds, newEvents, 0, newSize)); 95 | } 96 | } 97 | Result client2Results = resultsMap.remove(2); 98 | Result client1Results = resultsMap.remove(1); 99 | Result client7Results = resultsMap.remove(7); 100 | assertTrue(resultsMap.isEmpty()); 101 | assertEquals(0, results.size()); 102 | 103 | assertEquals(2, client2Results.clientId()); 104 | assertEquals(1, client1Results.clientId()); 105 | assertEquals(7, client7Results.clientId()); 106 | 107 | assertEquals(getClientEventIds(initialEvents, 2), LongArraySet.of(Arrays.copyOfRange(client2Results.clientEventIds(), client2Results.arrayOffset(), client2Results.arrayOffset() + client2Results.arrayLength()))); 108 | assertEquals(getClientEventIds(initialEvents, 1), LongArraySet.of(Arrays.copyOfRange(client1Results.clientEventIds(), client1Results.arrayOffset(), client1Results.arrayOffset() + client1Results.arrayLength()))); 109 | assertEquals(getClientEventIds(initialEvents, 7), LongArraySet.of(Arrays.copyOfRange(client7Results.clientEventIds(), client7Results.arrayOffset(), client7Results.arrayOffset() + client7Results.arrayLength()))); 110 | 111 | assertEquals(initialEvents.size(), client2Results.arrayLength() + client1Results.arrayLength() + client7Results.arrayLength()); 112 | 113 | assertEquals(3, client2Results.arrayLength()); 114 | assertEquals(7, client1Results.arrayLength()); 115 | assertEquals(3, client7Results.arrayLength()); 116 | } 117 | 118 | private Set getClientEventIds(List initialEvents, long clientId) { 119 | return initialEvents.stream().filter(e -> e.clientId() == clientId).map(Event::eventId).collect(Collectors.toSet()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tdlight-java/src/test/java/it/tdlight/Result.java: -------------------------------------------------------------------------------- 1 | package it.tdlight; 2 | 3 | import it.tdlight.jni.TdApi; 4 | import java.util.Objects; 5 | 6 | public final class Result { 7 | 8 | private final int clientId; 9 | private final boolean isClosed; 10 | private final long[] clientEventIds; 11 | private final TdApi.Object[] clientEvents; 12 | private final int arrayOffset; 13 | private final int arrayLength; 14 | 15 | public Result(int clientId, 16 | boolean isClosed, 17 | long[] clientEventIds, 18 | TdApi.Object[] clientEvents, 19 | int arrayOffset, 20 | int arrayLength) { 21 | this.clientId = clientId; 22 | this.isClosed = isClosed; 23 | this.clientEventIds = clientEventIds; 24 | this.clientEvents = clientEvents; 25 | this.arrayOffset = arrayOffset; 26 | this.arrayLength = arrayLength; 27 | } 28 | 29 | public int clientId() { 30 | return clientId; 31 | } 32 | 33 | public boolean isClosed() { 34 | return isClosed; 35 | } 36 | 37 | public long[] clientEventIds() { 38 | return clientEventIds; 39 | } 40 | 41 | public TdApi.Object[] clientEvents() { 42 | return clientEvents; 43 | } 44 | 45 | public int arrayOffset() { 46 | return arrayOffset; 47 | } 48 | 49 | public int arrayLength() { 50 | return arrayLength; 51 | } 52 | 53 | @Override 54 | public boolean equals(Object obj) { 55 | if (obj == this) { 56 | return true; 57 | } 58 | if (obj == null || obj.getClass() != this.getClass()) { 59 | return false; 60 | } 61 | Result that = (Result) obj; 62 | return this.clientId == that.clientId && this.isClosed == that.isClosed && Objects.equals(this.clientEventIds, 63 | that.clientEventIds 64 | ) && Objects.equals(this.clientEvents, that.clientEvents) && this.arrayOffset == that.arrayOffset 65 | && this.arrayLength == that.arrayLength; 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return Objects.hash(clientId, isClosed, clientEventIds, clientEvents, arrayOffset, arrayLength); 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return "Result[" + "clientId=" + clientId + ", " + "isClosed=" + isClosed + ", " + "clientEventIds=" 76 | + clientEventIds + ", " + "clientEvents=" + clientEvents + ", " + "arrayOffset=" + arrayOffset + ", " 77 | + "arrayLength=" + arrayLength + ']'; 78 | } 79 | } 80 | --------------------------------------------------------------------------------