This commit is contained in:
2024-10-10 19:05:48 +00:00
commit cffdcba6af
1880 changed files with 813614 additions and 0 deletions

321
td/example/README.md Normal file
View File

@@ -0,0 +1,321 @@
# TDLib usage and build examples
This directory contains basic examples of TDLib usage from different programming languages and examples of library building for different platforms.
If you are looking for documentation of all available TDLib methods, see the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or the
automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available TDLib
[methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
Also, take a look at our [Getting Started](https://core.telegram.org/tdlib/getting-started) tutorial for a description of basic TDLib concepts.
TDLib can be easily used from almost any programming language on any platform. See a [TDLib build instructions generator](https://tdlib.github.io/td/build.html) for detailed instructions on how to build TDLib.
Choose your preferred programming language to see examples of usage and a detailed description:
- [Python](#python)
- [JavaScript](#javascript)
- [Go](#go)
- [Java](#java)
- [Kotlin](#kotlin)
- [C#](#csharp)
- [C++](#cxx)
- [Swift](#swift)
- [Objective-C](#objective-c)
- [Object Pascal](#object-pascal)
- [Dart](#dart)
- [Rust](#rust)
- [Erlang](#erlang)
- [PHP](#php)
- [Lua](#lua)
- [Ruby](#ruby)
- [Crystal](#crystal)
- [Haskell](#haskell)
- [Nim](#nim)
- [Clojure](#clojure)
- [Emacs Lisp](#emacslisp)
- [D](#d)
- [Elixir](#elixir)
- [Vala](#vala)
- [1С](#1s)
- [C](#c)
- [G](#g)
- [Other](#other)
<a name="python"></a>
## Using TDLib in Python projects
TDLib can be used from Python through the [JSON](https://github.com/tdlib/td#using-json) interface.
Convenient Python wrappers already exist for our JSON interface.
If you use Python >= 3.6, take a look at [python-telegram](https://github.com/alexander-akhmetov/python-telegram).
The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container.
You can also try a fork [python-telegram](https://github.com/iTeam-co/pytglib) of this library.
If you want to use TDLib with asyncio and Python >= 3.9, take a look at [aiotdlib](https://github.com/pylakey/aiotdlib) or [Pytdbot](https://github.com/pytdbot/client).
For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib).
This wrapper contains generator for TDLib API classes and basic interface for interaction with TDLib.
You can also check out [example/python/tdjson_example.py](https://github.com/tdlib/td/blob/master/example/python/tdjson_example.py),
[tdlib-python](https://github.com/JunaidBabu/tdlib-python), or [Python Wrapper TDLib](https://github.com/alvhix/pywtdlib) for some basic examples of TDLib JSON interface integration with Python.
<a name="javascript"></a>
## Using TDLib in JavaScript projects
TDLib can be compiled to WebAssembly or asm.js and used in a browser from JavaScript. See [tdweb](https://github.com/tdlib/td/tree/master/example/web) as a convenient wrapper for TDLib in a browser
and [telegram-react](https://github.com/evgeny-nadymov/telegram-react) as an example of a TDLib-based Telegram client.
See also [Svelte-tdweb-starter](https://github.com/gennadypolakov/svelte-tdweb-starter) - Svelte wrapper for tdweb, and [Telegram-Photoframe](https://github.com/lukefx/telegram-photoframe) - a web application that displays your preferred group or channel as Photoframe.
TDLib can be used from Node.js through the [JSON](https://github.com/tdlib/td#using-json) interface.
Convenient Node.js wrappers already exist for our JSON interface.
For example, take a look at [Airgram](https://github.com/airgram/airgram) modern TDLib framework for TypeScript/JavaScript, or
at [tdl](https://github.com/Bannerets/tdl), which provides a convenient, fully-asynchronous interface for interaction with TDLib and contains a bunch of examples.
You can also see [TdNode](https://github.com/puppy0cam/TdNode), [tglib](https://github.com/nodegin/tglib), [node-tdlib](https://github.com/wfjsw/node-tdlib), [tdlnode](https://github.com/fonbah/tdlnode),
[Paper Plane](https://github.com/par6n/paper-plane), or [node-tlg](https://github.com/dilongfa/node-tlg) for other examples of TDLib JSON interface integration with Node.js.
See also the source code of [DIBgram](https://github.com/DIBgram/DIBgram) - an unofficial Telegram web application which looks like Telegram Desktop.
TDLib can be used also from NativeScript through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [nativescript-tglib](https://github.com/arpit2438735/nativescript-tglib) as an example of a NativeScript library for building Telegram clients.
<a name="go"></a>
## Using TDLib in Go projects
TDLib can be used from the Go programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and Cgo, and can be linked either statically or dynamically.
Convenient Go wrappers already exist for our JSON interface.
For example, take a look at [github.com/zelenin/go-tdlib](https://github.com/zelenin/go-tdlib) or [github.com/Arman92/go-tdlib](https://github.com/Arman92/go-tdlib), which provide a convenient TDLib client, a generator for TDLib API classes and contain many examples.
You can also see [github.com/aliforever/go-tdlib](https://github.com/aliforever/go-tdlib) or [github.com/L11R/go-tdjson](https://github.com/L11R/go-tdjson) for another examples of TDLib JSON interface integration with Go.
<a name="java"></a>
## Using TDLib in Java projects
TDLib can be used from the Java programming language through native [JNI](https://github.com/tdlib/td#using-java) binding.
We provide a generator for JNI bridge methods and Java classes for all TDLib API methods and objects.
See [example/java](https://github.com/tdlib/td/tree/master/example/java) for an example of using TDLib from desktop Java along with detailed building and usage instructions.
See [example/android](https://github.com/tdlib/td/tree/master/example/android) for detailed build instructions for Android.
<a name="kotlin"></a>
## Using TDLib in Kotlin projects
TDLib can be used from the Kotlin/JVM programming language through same way as in [Java](#java).
You can also use [ktd](https://github.com/whyoleg/ktd) library with Kotlin-specific bindings.
See also [td-ktx](https://github.com/tdlibx/td-ktx) - Kotlin coroutines wrapper for TDLib.
<a name="csharp"></a>
## Using TDLib in C# projects
TDLib provides a native [.NET](https://github.com/tdlib/td#using-dotnet) interface through `C++/CLI` and `C++/CX`.
See [tdlib-netcore](https://github.com/dantmnf/tdlib-netcore) for a SWIG-like binding with automatically generated classes for TDLib API.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for an example of building TDLib SDK for the Universal Windows Platform and an example of its usage from C#.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C# on Windows.
If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface,
provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples.
Also, see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
[egram.tel](https://github.com/egramtel/egram.tel) a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or
[telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches.
<a name="cxx"></a>
## Using TDLib in C++ projects
TDLib has a simple and convenient C++11-interface for sending and receiving requests and can be statically linked to your application.
See [example/cpp](https://github.com/tdlib/td/tree/master/example/cpp) for an example of TDLib usage from C++.
[td_example.cpp](https://github.com/tdlib/td/blob/master/example/cpp/td_example.cpp) contains an example of authorization, processing new incoming messages, getting a list of chats and sending a text message.
See also the source code of [Fernschreiber](https://github.com/Wunderfitz/harbour-fernschreiber) and [Depecher](https://github.com/blacksailer/depecher) Telegram apps for Sailfish OS,
[TELEports](https://gitlab.com/ubports/development/apps/teleports) a Qt-client for Ubuntu Touch, [tdlib-purple](https://github.com/ars3niy/tdlib-purple) - Telegram plugin for Pidgin,
or [MeeGram](https://github.com/qtinsider/meegram2) - a Telegram client for Nokia N9,
[TDLib Native Sciter Extension](https://github.com/EricKotato/TDLibNSE) - a Sciter native extension for TDLib's JSON interface, all of which are based on TDLib.
<a name="swift"></a>
## Using TDLib in Swift projects
TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS.
See [TDLibKit](https://github.com/Swiftgram/TDLibKit), [tdlib-swift](https://github.com/modestman/tdlib-swift), or [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
See also the source code of [Moc](https://github.com/mock-foundation/moc) - a native and powerful macOS and iPadOS Telegram client, optimized for moderating large communities and personal use.
See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for an example of a macOS Swift application.
<a name="objective-c"></a>
## Using TDLib in Objective-C projects
TDLib can be used from the Objective-C programming language through [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, visionOS, and macOS.
<a name="object-pascal"></a>
## Using TDLib in Object Pascal projects with Delphi and Lazarus
TDLib can be used from the Object Pascal programming language through the [JSON](https://github.com/tdlib/td#using-json).
See [tdlib-delphi](https://github.com/dieletro/tdlib-delphi) for an example of TDLib usage from Delphi.
See [tdlib-lazarus](https://github.com/dieletro/tdlib-lazarus) for an example of TDLib usage from Lazarus.
<a name="dart"></a>
## Using TDLib in Dart projects
TDLib can be used from the Dart programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and a Dart Native Extension or Dart FFI.
See [tdlib-dart](https://github.com/ivk1800/tdlib-dart), which provide convenient TDLib client with automatically generated and fully-documented classes for all TDLib API methods and objects.
See also [dart_tdlib](https://github.com/periodicaidan/dart_tdlib), [flutter_libtdjson](https://github.com/up9cloud/flutter_libtdjson), [Dart wrapper for TDLib](https://github.com/tdlib/td/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49), or [tdlib_bindings](https://github.com/lesnitsky/tdlib_bindings) for an example of a TDLib Dart bindings through FFI.
See [Telegram Client library](https://github.com/azkadev/telegram_client), [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib),
[tdlib-dart](https://github.com/drewpayment/tdlib-dart), [FluGram](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart.
See also [telegram-flutter](https://github.com/ivk1800/telegram-flutter) - Telegram client written in Dart, and [f-Telegram](https://github.com/evgfilim1/ftg) - Flutter Telegram client.
<a name="rust"></a>
## Using TDLib in Rust projects
TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [rust-tdlib](https://github.com/antonio-antuan/rust-tdlib), or [tdlib](https://github.com/paper-plane-developers/tdlib-rs), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures),
[tdlib-sys](https://github.com/nuxeh/tdlib-sys), [tdjson-rs](https://github.com/mersinvald/tdjson-rs), [rust-tdlib](https://github.com/vhaoran/rust-tdlib), or [tdlib-json-sys](https://github.com/aykxt/tdlib-json-sys) for examples of TDLib Rust bindings.
Also, see [Paper Plane](https://github.com/paper-plane-developers/paper-plane) a Telegram client written in Rust and GTK.
<a name="erlang"></a>
## Using TDLib in Erlang projects
TDLib can be used from the Erlang programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [erl-tdlib](https://github.com/lattenwald/erl-tdlib) for an example of TDLib Erlang bindings.
<a name="php"></a>
## Using TDLib in PHP projects
If you use modern PHP >= 7.4, you can use TDLib via a PHP FFI extension. For example, take a look at [ffi-tdlib](https://github.com/aurimasniekis/php-ffi-tdlib), or [tdlib-php-ffi](https://github.com/thisismzm/tdlib-php-ffi) - FFI-based TDLib wrappers.
See also [tdlib-schema](https://github.com/aurimasniekis/php-tdlib-schema) - a generator for TDLib API classes.
For older PHP versions you can use TDLib by wrapping its functionality in a PHP extension.
See [phptdlib](https://github.com/yaroslavche/phptdlib), [tdlib](https://github.com/aurimasniekis/php-ext-tdlib), or [PIF-TDPony](https://github.com/danog/pif-tdpony)
for examples of such extensions which provide access to TDLib from PHP.
See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) a Symfony bundle based on [phptdlib](https://github.com/yaroslavche/phptdlib).
<a name="lua"></a>
## Using TDLib in Lua projects
TDLib can be used from the Lua programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [luajit-tdlib](https://github.com/Rami-Sabbagh/luajit-tdlib), [tdlua](https://github.com/giuseppeM99/tdlua), or
[luajit-tdlib](https://github.com/Playermet/luajit-tdlib) for examples of TDLib Lua bindings and basic usage examples.
See also [tdbot](https://github.com/vysheng/tdbot), which makes all TDLib features available from Lua scripts.
<a name="d"></a>
## Using TDLib in D projects
TDLib can be used from the D programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [d-tdlib-service](https://github.com/Lord-Evil/d-tdlib-service) for an example of TDLib D bindings.
<a name="ruby"></a>
## Using TDLib in Ruby projects
TDLib can be used from the Ruby programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [tdlib-ruby](https://github.com/southbridgeio/tdlib-ruby) for examples of Ruby bindings and a client for TDLib.
<a name="Crystal"></a>
## Using TDLib in Crystal projects
TDLib can be used from the Crystal programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [Proton](https://github.com/protoncr/proton) for examples of Crystal bindings with automatically generated types for all TDLib API methods and objects.
<a name="haskell"></a>
## Using TDLib in Haskell projects
TDLib can be used from the Haskell programming language.
See [haskell-tdlib](https://github.com/mejgun/haskell-tdlib) or [tdlib](https://github.com/poscat0x04/tdlib) for examples of such usage and Haskell wrappers for TDLib.
This library contains automatically generated Haskell types for all TDLib API methods and objects.
<a name="nim"></a>
## Using TDLib in Nim projects
TDLib can be used from the Nim programming language.
See [telenim](https://github.com/Yardanico/telenim) for example of such usage and a Nim wrapper for TDLib.
<a name="clojure"></a>
## Using TDLib in Clojure projects
TDLib can be used from the Clojure programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [clojure-tdlib-json-wrapper](https://github.com/MityaSaray/clojure-tdlib-json) for an example of TDLib Clojure bindings.
<a name="emacslisp"></a>
## Using TDLib in Emacs Lisp projects
TDLib can be used from the Emacs Lisp programming language.
See [telega.el](https://github.com/zevlg/telega.el) for an example of a GNU Emacs Telegram client.
<a name="elixir"></a>
## Using TDLib in Elixir projects
TDLib can be used from the Elixir programming language.
See [Elixir TDLib](https://github.com/QuantLayer/elixir-tdlib) for an example of such usage and an Elixir client for TDLib.
The library contains automatically generated and fully-documented classes for all TDLib API methods and objects.
<a name="vala"></a>
## Using TDLib in Vala projects
TDLib can be used from the Vala programming language.
See [TDLib Vala](https://github.com/AYMENJD/td-vala) for an example of such usage.
<a name="1s"></a>
## Using TDLib from 1С:Enterprise
TDLib can be used from the 1С programming language.
See [TDLib bindings for 1С:Enterprise](https://github.com/Infactum/telegram-native) and [e1c.tAgents](https://github.com/fedbka/e1c.tAgents) for examples of such usage.
<a name="c"></a>
## Using TDLib in C projects
TDLib can be used from the C programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [easy-tg](https://github.com/Trumeet/easy-tg) for an example of such usage.
You can also try to use our [C](https://github.com/tdlib/td/blob/master/td/telegram/td_c_client.h) client, which was used by the private TDLib-based version of [telegram-cli](https://github.com/vysheng/tg).
<a name="g"></a>
## Using TDLib from G projects
TDLib can be used from the G graphical programming language in LabVIEW development environment.
See [TDLib bindings for LabVIEW](https://github.com/IvanLisRus/Telegram-Client_TDLib) for examples of such usage.
<a name="other"></a>
## Using TDLib from other programming languages
You can use TDLib from any other programming language using [tdbot](https://github.com/vysheng/tdbot) or [TDLib JSON CLI](https://github.com/oott123/tdlib-json-cli),
which provide a command line tool for interaction with TDLIb using the [JSON](https://github.com/tdlib/td#using-json) interface through stdin and stdout.
You can use this method to use TDLib, for example, from Brainfuck (unfortunately, we haven't seen examples of sending a Telegram message through TDLib on Brainfuck yet).
Alternatively, you can use the TDLib [JSON](https://github.com/tdlib/td#using-json) interface directly from your programming language.
Feel free to create an issue, if you have created a valuable TDLib binding or a TDLib client in some programming language and want it to be added to this list of examples.

3
td/example/android/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/SDK*
/tdlib*
/third-party*

View File

@@ -0,0 +1,51 @@
<?php
if ($argc !== 2) {
exit();
}
$file = file_get_contents($argv[1]);
if (strpos($file, 'androidx.annotation.IntDef') !== false) {
exit();
}
$file = str_replace('import androidx.annotation.Nullable;', 'import androidx.annotation.IntDef;'.PHP_EOL.
'import androidx.annotation.Nullable;'.PHP_EOL.
PHP_EOL.
'import java.lang.annotation.Retention;'.PHP_EOL.
'import java.lang.annotation.RetentionPolicy;'.PHP_EOL, $file);
preg_match_all('/public static class ([A-Za-z0-9]+) extends ([A-Za-z0-9]+)/', $file, $matches, PREG_SET_ORDER);
$children = [];
foreach ($matches as $val) {
if ($val[2] === 'Object') {
continue;
}
$children[$val[2]][] = ' '.$val[1].'.CONSTRUCTOR';
}
$file = preg_replace_callback('/public abstract static class ([A-Za-z0-9]+)(<R extends Object>)? extends Object [{]/',
function ($val) use ($children) {
$values = implode(','.PHP_EOL, $children[$val[1]]);
return $val[0].<<<EOL
/**
* Describes possible values returned by getConstructor().
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({
$values
})
public @interface Constructors {}
/**
* @return identifier uniquely determining type of the object.
*/
@Constructors
@Override
public abstract int getConstructor();
EOL;
},
$file);
file_put_contents($argv[1], $file);

View File

@@ -0,0 +1,72 @@
cmake_minimum_required(VERSION 3.4.1 FATAL_ERROR)
project(TdAndroid VERSION 1.0 LANGUAGES CXX)
set(TD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..)
option(TD_ANDROID_JSON "Use \"ON\" to build JSON interface.")
option(TD_ANDROID_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
if (TD_ANDROID_JSON)
if (CMAKE_CROSSCOMPILING)
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz")
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
endif()
add_subdirectory(${TD_DIR} td)
return()
endif()
if (NOT TD_ANDROID_JSON_JAVA)
option(TD_ENABLE_JNI "Enable JNI-compatible TDLib API" ON)
endif()
if (CMAKE_CROSSCOMPILING)
set(CMAKE_MODULE_PATH "${TD_DIR}/CMake")
include(TdSetUpCompiler)
td_set_up_compiler()
string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -flto=thin -Oz")
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
add_subdirectory(${TD_DIR} td)
add_library(tdjni SHARED "${TD_DIR}/example/java/td_jni.cpp")
if (TD_ANDROID_JSON_JAVA)
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
else()
target_link_libraries(tdjni PRIVATE Td::TdStatic)
endif()
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="org/drinkless/tdlib")
add_custom_command(TARGET tdjni POST_BUILD
COMMAND ${CMAKE_COMMAND} -E rename $<TARGET_FILE:tdjni> $<TARGET_FILE:tdjni>.debug
COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded $<TARGET_FILE:tdjni>.debug -o $<TARGET_FILE:tdjni>)
else()
add_subdirectory(${TD_DIR} td)
if (TD_ANDROID_JSON_JAVA)
return()
endif()
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
set(TD_API_JAVA_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${TD_API_JAVA_PACKAGE}/TdApi.java")
set(TD_API_TLO_PATH "${TD_DIR}/td/generate/auto/tlo/td_api.tlo")
set(TD_API_TL_PATH "${TD_DIR}/td/generate/scheme/td_api.tl")
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH "${TD_DIR}/td/generate/JavadocTlDocumentationGenerator.php")
set(GENERATE_JAVA_CMD td_generate_java_api TdApi ${TD_API_TLO_PATH} ${CMAKE_CURRENT_SOURCE_DIR} ${TD_API_JAVA_PACKAGE})
if (PHP_EXECUTABLE)
set(GENERATE_JAVA_CMD ${GENERATE_JAVA_CMD} &&
${PHP_EXECUTABLE} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH} ${TD_API_TL_PATH} ${TD_API_JAVA_PATH} androidx.annotation.Nullable @Nullable &&
${PHP_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/AddIntDef.php ${TD_API_JAVA_PATH})
endif()
file(MAKE_DIRECTORY ${TD_API_JAVA_PACKAGE})
add_custom_target(tl_generate_java
COMMAND ${GENERATE_JAVA_CMD}
COMMENT "Generate Java TL source files"
DEPENDS td_generate_java_api tl_generate_tlo ${TD_API_TLO_PATH} ${TD_API_TL_PATH}
)
endif()

View File

@@ -0,0 +1,28 @@
FROM --platform=linux/amd64 ubuntu:24.04 AS build
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -yq default-jdk g++ git gperf make perl php-cli unzip wget && rm -rf /var/lib/apt/lists/*
WORKDIR /home
ARG ANDROID_NDK_VERSION=23.2.8568313
COPY ./check-environment.sh ./fetch-sdk.sh ./
RUN ./fetch-sdk.sh SDK "$ANDROID_NDK_VERSION"
ARG OPENSSL_VERSION=OpenSSL_1_1_1w
ARG BUILD_SHARED_OPENSSL_LIBS=
COPY ./build-openssl.sh ./
RUN ./build-openssl.sh SDK "$ANDROID_NDK_VERSION" openssl "$OPENSSL_VERSION" "$BUILD_SHARED_OPENSSL_LIBS"
ADD "https://api.github.com/repos/tdlib/td/git/refs/heads/master" version.json
ARG COMMIT_HASH=master
RUN git clone https://github.com/tdlib/td.git && cd td && git checkout "$COMMIT_HASH"
RUN cd td && git merge-base --is-ancestor 872d8ebd3ba9d922169839e6a24cee08b02b328a "$COMMIT_HASH"
ARG ANDROID_STL=c++_static
ARG TDLIB_INTERFACE=Java
RUN td/example/android/build-tdlib.sh SDK "$ANDROID_NDK_VERSION" openssl "$ANDROID_STL" "$TDLIB_INTERFACE" && rm -rf td/example/android/build-*
FROM scratch
COPY --from=build /home/td/example/android/tdlib/tdlib* /

View File

@@ -0,0 +1,32 @@
# TDLib Android example
This is an example of building `TDLib` for Android.
You need a Bash shell on Linux, macOS, or Windows with some common tools, a C++ compiler, JDK, PHP, perl, and gperf pre-installed.
## Building TDLib for Android
* Run the script `./check-environment.sh` to check that you have all required Unix tools and Java utilities. If the script exits with an error message, install the missing tool.
* Run the script `./fetch-sdk.sh` to download Android SDK to a local directory.
* Run the script `./build-openssl.sh` to download and build OpenSSL for Android.
* Run the script `./build-tdlib.sh` to build TDLib for Android.
* The built libraries are now located in the `tdlib/libs` directory. If [Java](https://github.com/tdlib/td#using-java) interface was built, then corresponding Java code is located in the `tdlib/java` directory, and standalone Java documentation can be found in the `tdlib/javadoc` directory. You can also use archives `tdlib/tdlib.zip` and `tdlib/tdlib-debug.zip`, which contain all aforementioned data.
If you already have installed Android SDK and NDK, you can skip the second step and specify existing Android SDK root path and Android NDK version as the first and the second parameters to the subsequent scripts. Make sure that the SDK includes android-34 platform and CMake 3.22.1.
If you already have prebuilt OpenSSL, you can skip the third step and specify path to the prebuild OpenSSL as the third parameter to the script `./build-tdlib.sh`.
If you want to update TDLib to a newer version, you need to run only the script `./build-tdlib.sh`.
You can specify different OpenSSL version as the fourth parameter to the script `./build-openssl.sh`. By default OpenSSL 1.1.1 is used because of much smaller binary footprint and better performance than newer OpenSSL versions.
You can build shared OpenSSL libraries instead of static ones by passing any non-empty string as the fifth parameter to the script `./build-openssl.sh`. This can reduce total application size if you have a lot of other code that uses OpenSSL and want it to use the same shared library.
You can build TDLib against shared standard C++ library by specifying "c++_shared" as the fourth parameter to the script `./build-tdlib.sh`. This can reduce total application size if you have a lot of other C++ code and want it to use the same shared library.
You can also build TDLib with [JSON interface](https://github.com/tdlib/td#using-json) instead of [Java](https://github.com/tdlib/td#using-java) interface by passing "JSON" as the fifth parameter to the script `./build-tdlib.sh`.
You can also build TDLib with [JSON interface](https://github.com/tdlib/td#using-json) that can be called from Java by passing "JSONJava" as the fifth parameter to the script `./build-tdlib.sh`.
You can pass an empty string instead of any script parameter to use its default value. For example, you can use the command `./build-tdlib.sh '' '' '' '' 'JSON'` to build TDLib with [JSON interface](https://github.com/tdlib/td#using-json) using default values for other parameters.
Alternatively, you can use Docker to build TDLib for Android. Use `docker build --output tdlib .` to build the latest TDLib commit from Github, or `docker build --build-arg COMMIT_HASH=<commit-hash> --output tdlib .` to build specific commit. The output archives will be placed in the directory "tdlib" as specified. Additionally, you can specify build arguments "TDLIB_INTERFACE", "ANDROID_NDK_VERSION", "OPENSSL_VERSION", "BUILD_SHARED_OPENSSL_LIBS", and "ANDROID_STL" to the provided Dockerfile. For example, use `docker build --build-arg TDLIB_INTERFACE=JSON --output tdlib .` to build the latest TDLib with JSON interface.

View File

@@ -0,0 +1,93 @@
#!/usr/bin/env bash
ANDROID_SDK_ROOT=${1:-SDK}
ANDROID_NDK_VERSION=${2:-23.2.8568313}
OPENSSL_INSTALL_DIR=${3:-third-party/openssl}
OPENSSL_VERSION=${4:-OpenSSL_1_1_1w} # openssl-3.3.0
BUILD_SHARED_LIBS=$5
if [ ! -d "$ANDROID_SDK_ROOT" ] ; then
echo "Error: directory \"$ANDROID_SDK_ROOT\" doesn't exist. Run ./fetch-sdk.sh first, or provide a valid path to Android SDK."
exit 1
fi
if [ -e "$OPENSSL_INSTALL_DIR" ] ; then
echo "Error: file or directory \"$OPENSSL_INSTALL_DIR\" already exists. Delete it manually to proceed."
exit 1
fi
source ./check-environment.sh || exit 1
if [[ "$OS_NAME" == "win" ]] && [[ "$BUILD_SHARED_LIBS" ]] ; then
echo "Error: OpenSSL shared libraries can't be built on Windows because of 'The command line is too long.' error during build. You can run the script in WSL instead."
exit 1
fi
mkdir -p $OPENSSL_INSTALL_DIR || exit 1
ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$(basename -- "$ANDROID_SDK_ROOT")"
OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")"
cd $(dirname $0)
echo "Downloading OpenSSL sources..."
rm -f $OPENSSL_VERSION.tar.gz || exit 1
$WGET https://github.com/openssl/openssl/archive/refs/tags/$OPENSSL_VERSION.tar.gz || exit 1
rm -rf ./openssl-$OPENSSL_VERSION || exit 1
tar xzf $OPENSSL_VERSION.tar.gz || exit 1
rm $OPENSSL_VERSION.tar.gz || exit 1
cd openssl-$OPENSSL_VERSION
export ANDROID_NDK_ROOT=$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION # for OpenSSL 3.*.*
export ANDROID_NDK_HOME=$ANDROID_NDK_ROOT # for OpenSSL 1.1.1
PATH=$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin:$PATH
if ! clang --help >/dev/null 2>&1 ; then
echo "Error: failed to run clang from Android NDK."
if [[ "$OS_NAME" == "linux" ]] ; then
echo "Prebuilt Android NDK binaries are linked against glibc, so glibc must be installed."
fi
exit 1
fi
ANDROID_API32=16
ANDROID_API64=21
if [[ ${ANDROID_NDK_VERSION%%.*} -ge 24 ]] ; then
ANDROID_API32=19
fi
if [[ ${ANDROID_NDK_VERSION%%.*} -ge 26 ]] ; then
ANDROID_API32=21
fi
SHARED_BUILD_OPTION=$([ "$BUILD_SHARED_LIBS" ] && echo "shared" || echo "no-shared")
for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do
if [[ $ABI == "x86" ]] ; then
./Configure android-x86 ${SHARED_BUILD_OPTION} -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API32 || exit 1
elif [[ $ABI == "x86_64" ]] ; then
./Configure android-x86_64 ${SHARED_BUILD_OPTION} -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API64 || exit 1
elif [[ $ABI == "armeabi-v7a" ]] ; then
./Configure android-arm ${SHARED_BUILD_OPTION} -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API32 -D__ARM_MAX_ARCH__=8 || exit 1
elif [[ $ABI == "arm64-v8a" ]] ; then
./Configure android-arm64 ${SHARED_BUILD_OPTION} -U__ANDROID_API__ -D__ANDROID_API__=$ANDROID_API64 || exit 1
fi
sed -i.bak 's/-O3/-O3 -ffunction-sections -fdata-sections/g' Makefile || exit 1
make depend -s || exit 1
make -j4 -s || exit 1
mkdir -p $OPENSSL_INSTALL_DIR/$ABI/lib/ || exit 1
if [ "$BUILD_SHARED_LIBS" ] ; then
cp libcrypto.so libssl.so $OPENSSL_INSTALL_DIR/$ABI/lib/ || exit 1
else
cp libcrypto.a libssl.a $OPENSSL_INSTALL_DIR/$ABI/lib/ || exit 1
fi
cp -r include $OPENSSL_INSTALL_DIR/$ABI/ || exit 1
make distclean || exit 1
done
cd ..
rm -rf ./openssl-$OPENSSL_VERSION || exit 1

117
td/example/android/build-tdlib.sh Executable file
View File

@@ -0,0 +1,117 @@
#!/usr/bin/env bash
ANDROID_SDK_ROOT=${1:-SDK}
ANDROID_NDK_VERSION=${2:-23.2.8568313}
OPENSSL_INSTALL_DIR=${3:-third-party/openssl}
ANDROID_STL=${4:-c++_static}
TDLIB_INTERFACE=${5:-Java}
if [ "$ANDROID_STL" != "c++_static" ] && [ "$ANDROID_STL" != "c++_shared" ] ; then
echo 'Error: ANDROID_STL must be either "c++_static" or "c++_shared".'
exit 1
fi
if [ "$TDLIB_INTERFACE" != "Java" ] && [ "$TDLIB_INTERFACE" != "JSON" ] && [ "$TDLIB_INTERFACE" != "JSONJava" ] ; then
echo 'Error: TDLIB_INTERFACE must be either "Java", "JSON", or "JSONJava".'
exit 1
fi
source ./check-environment.sh || exit 1
if [ ! -d "$ANDROID_SDK_ROOT" ] ; then
echo "Error: directory \"$ANDROID_SDK_ROOT\" doesn't exist. Run ./fetch-sdk.sh first, or provide a valid path to Android SDK."
exit 1
fi
if [ ! -d "$OPENSSL_INSTALL_DIR" ] ; then
echo "Error: directory \"$OPENSSL_INSTALL_DIR\" doesn't exists. Run ./build-openssl.sh first."
exit 1
fi
ANDROID_SDK_ROOT="$(cd "$(dirname -- "$ANDROID_SDK_ROOT")" >/dev/null; pwd -P)/$(basename -- "$ANDROID_SDK_ROOT")"
ANDROID_NDK_ROOT="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION"
OPENSSL_INSTALL_DIR="$(cd "$(dirname -- "$OPENSSL_INSTALL_DIR")" >/dev/null; pwd -P)/$(basename -- "$OPENSSL_INSTALL_DIR")"
PATH=$ANDROID_SDK_ROOT/cmake/3.22.1/bin:$PATH
TDLIB_INTERFACE_OPTION=$([ "$TDLIB_INTERFACE" == "JSON" ] && echo "-DTD_ANDROID_JSON=ON" || [ "$TDLIB_INTERFACE" == "JSONJava" ] && echo "-DTD_ANDROID_JSON_JAVA=ON" || echo "")
cd $(dirname $0)
echo "Generating TDLib source files..."
mkdir -p build-native-$TDLIB_INTERFACE || exit 1
cd build-native-$TDLIB_INTERFACE
cmake $TDLIB_INTERFACE_OPTION -DTD_GENERATE_SOURCE_FILES=ON .. || exit 1
cmake --build . || exit 1
cd ..
rm -rf tdlib || exit 1
if [ "$TDLIB_INTERFACE" == "Java" ] ; then
echo "Downloading annotation Java package..."
rm -f android.jar annotation-1.4.0.jar || exit 1
$WGET https://maven.google.com/androidx/annotation/annotation/1.4.0/annotation-1.4.0.jar || exit 1
echo "Generating Java source files..."
cmake --build build-native-$TDLIB_INTERFACE --target tl_generate_java || exit 1
php AddIntDef.php org/drinkless/tdlib/TdApi.java || exit 1
mkdir -p tdlib/java/org/drinkless/tdlib || exit 1
cp -p {..,tdlib}/java/org/drinkless/tdlib/Client.java || exit 1
mv {,tdlib/java/}org/drinkless/tdlib/TdApi.java || exit 1
rm -rf org || exit 1
echo "Generating Javadoc documentation..."
cp "$ANDROID_SDK_ROOT/platforms/android-34/android.jar" . || exit 1
JAVADOC_SEPARATOR=$([ "$OS_NAME" == "win" ] && echo ";" || echo ":")
javadoc -d tdlib/javadoc -encoding UTF-8 -charset UTF-8 -classpath "android.jar${JAVADOC_SEPARATOR}annotation-1.4.0.jar" -quiet -sourcepath tdlib/java org.drinkless.tdlib || exit 1
rm android.jar annotation-1.4.0.jar || exit 1
fi
if [ "$TDLIB_INTERFACE" == "JSONJava" ] ; then
mkdir -p tdlib/java/org/drinkless/tdlib || exit 1
cp -p {..,tdlib}/java/org/drinkless/tdlib/JsonClient.java || exit 1
fi
echo "Building TDLib..."
for ABI in arm64-v8a armeabi-v7a x86_64 x86 ; do
mkdir -p tdlib/libs/$ABI/ || exit 1
mkdir -p build-$ABI-$TDLIB_INTERFACE || exit 1
cd build-$ABI-$TDLIB_INTERFACE
cmake -DCMAKE_TOOLCHAIN_FILE="$ANDROID_NDK_ROOT/build/cmake/android.toolchain.cmake" -DOPENSSL_ROOT_DIR="$OPENSSL_INSTALL_DIR/$ABI" -DCMAKE_BUILD_TYPE=RelWithDebInfo -GNinja -DANDROID_ABI=$ABI -DANDROID_STL=$ANDROID_STL -DANDROID_PLATFORM=android-16 $TDLIB_INTERFACE_OPTION .. || exit 1
if [ "$TDLIB_INTERFACE" == "Java" ] || [ "$TDLIB_INTERFACE" == "JSONJava" ] ; then
cmake --build . --target tdjni || exit 1
cp -p libtd*.so* ../tdlib/libs/$ABI/ || exit 1
fi
if [ "$TDLIB_INTERFACE" == "JSON" ] ; then
cmake --build . --target tdjson || exit 1
cp -p td/libtdjson.so ../tdlib/libs/$ABI/libtdjson.so.debug || exit 1
"$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" --strip-debug --strip-unneeded ../tdlib/libs/$ABI/libtdjson.so.debug -o ../tdlib/libs/$ABI/libtdjson.so || exit 1
fi
cd ..
if [[ "$ANDROID_STL" == "c++_shared" ]] ; then
if [[ "$ABI" == "arm64-v8a" ]] ; then
FULL_ABI="aarch64-linux-android"
elif [[ "$ABI" == "armeabi-v7a" ]] ; then
FULL_ABI="arm-linux-androideabi"
elif [[ "$ABI" == "x86_64" ]] ; then
FULL_ABI="x86_64-linux-android"
elif [[ "$ABI" == "x86" ]] ; then
FULL_ABI="i686-linux-android"
fi
cp "$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/sysroot/usr/lib/$FULL_ABI/libc++_shared.so" tdlib/libs/$ABI/ || exit 1
"$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" tdlib/libs/$ABI/libc++_shared.so || exit 1
fi
if [ -e "$OPENSSL_INSTALL_DIR/$ABI/lib/libcrypto.so" ] ; then
cp "$OPENSSL_INSTALL_DIR/$ABI/lib/libcrypto.so" "$OPENSSL_INSTALL_DIR/$ABI/lib/libssl.so" tdlib/libs/$ABI/ || exit 1
"$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" tdlib/libs/$ABI/libcrypto.so || exit 1
"$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/$HOST_ARCH/bin/llvm-strip" tdlib/libs/$ABI/libssl.so || exit 1
fi
done
echo "Compressing..."
rm -f tdlib.zip tdlib-debug.zip || exit 1
jar -cMf tdlib-debug.zip tdlib || exit 1
rm tdlib/libs/*/*.debug || exit 1
jar -cMf tdlib.zip tdlib || exit 1
mv tdlib.zip tdlib-debug.zip tdlib || exit 1
echo "Done."

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# The script checks that all needed tools are installed and sets OS_NAME, HOST_ARCH, and WGET variables
if [[ "$OSTYPE" == "linux"* ]] ; then
OS_NAME="linux"
HOST_ARCH="linux-x86_64"
elif [[ "$OSTYPE" == "darwin"* ]] ; then
OS_NAME="mac"
HOST_ARCH="darwin-x86_64"
elif [[ "$OSTYPE" == "msys" ]] ; then
OS_NAME="win"
HOST_ARCH="windows-x86_64"
else
echo "Error: this script supports only Bash shell on Linux, macOS, or Windows."
exit 1
fi
if which wget >/dev/null 2>&1 ; then
WGET="wget -q"
elif which curl >/dev/null 2>&1 ; then
WGET="curl -sfLO"
else
echo "Error: this script requires either curl or wget tool installed."
exit 1
fi
for TOOL_NAME in gperf jar java javadoc make perl php sed tar yes unzip ; do
if ! which "$TOOL_NAME" >/dev/null 2>&1 ; then
echo "Error: this script requires $TOOL_NAME tool installed."
exit 1
fi
done
if [[ $(which make) = *" "* ]] ; then
echo "Error: OpenSSL expects that full path to make tool doesn't contain spaces. Move it to some other place."
exit 1
fi
if ! perl -MExtUtils::MakeMaker -MLocale::Maketext::Simple -MPod::Usage -e '' >/dev/null 2>&1 ; then
echo "Error: Perl installation is broken."
if [[ "$OSTYPE" == "msys" ]] ; then
echo "For Git Bash you need to manually copy ExtUtils, Locale and Pod modules to /usr/share/perl5/core_perl from any compatible Perl installation."
fi
exit 1
fi
if ! java --help >/dev/null 2>&1 ; then
echo "Error: Java installation is broken. Install JDK from https://www.oracle.com/java/technologies/downloads/ or via the package manager."
exit 1
fi

30
td/example/android/fetch-sdk.sh Executable file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env bash
ANDROID_SDK_ROOT=${1:-SDK}
ANDROID_NDK_VERSION=${2:-23.2.8568313}
if [ -e "$ANDROID_SDK_ROOT" ] ; then
echo "Error: file or directory \"$ANDROID_SDK_ROOT\" already exists. Delete it manually to proceed."
exit 1
fi
source ./check-environment.sh || exit 1
SDKMANAGER="./sdkmanager"
if [[ "$OS_NAME" == "win" ]] ; then
SDKMANAGER="./sdkmanager.bat"
fi
echo "Downloading SDK Manager..."
mkdir -p "$ANDROID_SDK_ROOT" || exit 1
cd "$ANDROID_SDK_ROOT" || exit 1
$WGET "https://dl.google.com/android/repository/commandlinetools-$OS_NAME-11076708_latest.zip" || exit 1
mkdir -p cmdline-tools || exit 1
unzip -qq "commandlinetools-$OS_NAME-11076708_latest.zip" -d cmdline-tools || exit 1
rm "commandlinetools-$OS_NAME-11076708_latest.zip" || exit 1
mv cmdline-tools/* cmdline-tools/latest/ || exit 1
echo "Installing required SDK tools..."
cd cmdline-tools/latest/bin/ || exit 1
yes | $SDKMANAGER --licenses >/dev/null || exit 1
$SDKMANAGER --install "ndk;$ANDROID_NDK_VERSION" "cmake;3.22.1" "build-tools;34.0.0" "platforms;android-34" > /dev/null || exit 1

1
td/example/cpp/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/td/

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.4 FATAL_ERROR)
project(TdExample VERSION 1.0 LANGUAGES CXX)
find_package(Td 1.8.37 REQUIRED)
add_executable(tdjson_example tdjson_example.cpp)
target_link_libraries(tdjson_example PRIVATE Td::TdJson)
set_property(TARGET tdjson_example PROPERTY CXX_STANDARD 11)
add_executable(td_example td_example.cpp)
target_link_libraries(td_example PRIVATE Td::TdStatic)
set_property(TARGET td_example PROPERTY CXX_STANDARD 14)

24
td/example/cpp/README.md Normal file
View File

@@ -0,0 +1,24 @@
# TDLib C++ basic usage examples
TDLib should be prebuilt and installed to local subdirectory `td/`:
```
cd <path to TDLib sources>
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=../example/cpp/td ..
cmake --build . --target install
```
Also, see [building](https://github.com/tdlib/td#building) for additional details on TDLib building.
After this you can build the examples:
```
cd <path to TDLib sources>/example/cpp
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/example/cpp/td/lib/cmake/Td ..
cmake --build .
```
Documentation for all available classes and methods can be found at https://core.telegram.org/tdlib/docs.
To run the examples you may need to manually copy needed shared libraries from `td/bin` to a directory containing built binaries.

View File

@@ -0,0 +1,337 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <td/telegram/Client.h>
#include <td/telegram/td_api.h>
#include <td/telegram/td_api.hpp>
#include <cstdint>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
// Simple single-threaded example of TDLib usage.
// Real world programs should use separate thread for the user input.
// Example includes user authentication, receiving updates, getting chat list and sending text messages.
// overloaded
namespace detail {
template <class... Fs>
struct overload;
template <class F>
struct overload<F> : public F {
explicit overload(F f) : F(f) {
}
};
template <class F, class... Fs>
struct overload<F, Fs...>
: public overload<F>
, public overload<Fs...> {
overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) {
}
using overload<F>::operator();
using overload<Fs...>::operator();
};
} // namespace detail
template <class... F>
auto overloaded(F... f) {
return detail::overload<F...>(f...);
}
namespace td_api = td::td_api;
class TdExample {
public:
TdExample() {
td::ClientManager::execute(td_api::make_object<td_api::setLogVerbosityLevel>(1));
client_manager_ = std::make_unique<td::ClientManager>();
client_id_ = client_manager_->create_client_id();
send_query(td_api::make_object<td_api::getOption>("version"), {});
}
void loop() {
while (true) {
if (need_restart_) {
restart();
} else if (!are_authorized_) {
process_response(client_manager_->receive(10));
} else {
std::cout << "Enter action [q] quit [u] check for updates and request results [c] show chats [m <chat_id> "
"<text>] send message [me] show self [l] logout: "
<< std::endl;
std::string line;
std::getline(std::cin, line);
std::istringstream ss(line);
std::string action;
if (!(ss >> action)) {
continue;
}
if (action == "q") {
return;
}
if (action == "u") {
std::cout << "Checking for updates..." << std::endl;
while (true) {
auto response = client_manager_->receive(0);
if (response.object) {
process_response(std::move(response));
} else {
break;
}
}
} else if (action == "close") {
std::cout << "Closing..." << std::endl;
send_query(td_api::make_object<td_api::close>(), {});
} else if (action == "me") {
send_query(td_api::make_object<td_api::getMe>(),
[this](Object object) { std::cout << to_string(object) << std::endl; });
} else if (action == "l") {
std::cout << "Logging out..." << std::endl;
send_query(td_api::make_object<td_api::logOut>(), {});
} else if (action == "m") {
std::int64_t chat_id;
ss >> chat_id;
ss.get();
std::string text;
std::getline(ss, text);
std::cout << "Sending message to chat " << chat_id << "..." << std::endl;
auto send_message = td_api::make_object<td_api::sendMessage>();
send_message->chat_id_ = chat_id;
auto message_content = td_api::make_object<td_api::inputMessageText>();
message_content->text_ = td_api::make_object<td_api::formattedText>();
message_content->text_->text_ = std::move(text);
send_message->input_message_content_ = std::move(message_content);
send_query(std::move(send_message), {});
} else if (action == "c") {
std::cout << "Loading chat list..." << std::endl;
send_query(td_api::make_object<td_api::getChats>(nullptr, 20), [this](Object object) {
if (object->get_id() == td_api::error::ID) {
return;
}
auto chats = td::move_tl_object_as<td_api::chats>(object);
for (auto chat_id : chats->chat_ids_) {
std::cout << "[chat_id:" << chat_id << "] [title:" << chat_title_[chat_id] << "]" << std::endl;
}
});
}
}
}
}
private:
using Object = td_api::object_ptr<td_api::Object>;
std::unique_ptr<td::ClientManager> client_manager_;
std::int32_t client_id_{0};
td_api::object_ptr<td_api::AuthorizationState> authorization_state_;
bool are_authorized_{false};
bool need_restart_{false};
std::uint64_t current_query_id_{0};
std::uint64_t authentication_query_id_{0};
std::map<std::uint64_t, std::function<void(Object)>> handlers_;
std::map<std::int64_t, td_api::object_ptr<td_api::user>> users_;
std::map<std::int64_t, std::string> chat_title_;
void restart() {
client_manager_.reset();
*this = TdExample();
}
void send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler) {
auto query_id = next_query_id();
if (handler) {
handlers_.emplace(query_id, std::move(handler));
}
client_manager_->send(client_id_, query_id, std::move(f));
}
void process_response(td::ClientManager::Response response) {
if (!response.object) {
return;
}
//std::cout << response.request_id << " " << to_string(response.object) << std::endl;
if (response.request_id == 0) {
return process_update(std::move(response.object));
}
auto it = handlers_.find(response.request_id);
if (it != handlers_.end()) {
it->second(std::move(response.object));
handlers_.erase(it);
}
}
std::string get_user_name(std::int64_t user_id) const {
auto it = users_.find(user_id);
if (it == users_.end()) {
return "unknown user";
}
return it->second->first_name_ + " " + it->second->last_name_;
}
std::string get_chat_title(std::int64_t chat_id) const {
auto it = chat_title_.find(chat_id);
if (it == chat_title_.end()) {
return "unknown chat";
}
return it->second;
}
void process_update(td_api::object_ptr<td_api::Object> update) {
td_api::downcast_call(
*update, overloaded(
[this](td_api::updateAuthorizationState &update_authorization_state) {
authorization_state_ = std::move(update_authorization_state.authorization_state_);
on_authorization_state_update();
},
[this](td_api::updateNewChat &update_new_chat) {
chat_title_[update_new_chat.chat_->id_] = update_new_chat.chat_->title_;
},
[this](td_api::updateChatTitle &update_chat_title) {
chat_title_[update_chat_title.chat_id_] = update_chat_title.title_;
},
[this](td_api::updateUser &update_user) {
auto user_id = update_user.user_->id_;
users_[user_id] = std::move(update_user.user_);
},
[this](td_api::updateNewMessage &update_new_message) {
auto chat_id = update_new_message.message_->chat_id_;
std::string sender_name;
td_api::downcast_call(*update_new_message.message_->sender_id_,
overloaded(
[this, &sender_name](td_api::messageSenderUser &user) {
sender_name = get_user_name(user.user_id_);
},
[this, &sender_name](td_api::messageSenderChat &chat) {
sender_name = get_chat_title(chat.chat_id_);
}));
std::string text;
if (update_new_message.message_->content_->get_id() == td_api::messageText::ID) {
text = static_cast<td_api::messageText &>(*update_new_message.message_->content_).text_->text_;
}
std::cout << "Receive message: [chat_id:" << chat_id << "] [from:" << sender_name << "] ["
<< text << "]" << std::endl;
},
[](auto &update) {}));
}
auto create_authentication_query_handler() {
return [this, id = authentication_query_id_](Object object) {
if (id == authentication_query_id_) {
check_authentication_error(std::move(object));
}
};
}
void on_authorization_state_update() {
authentication_query_id_++;
td_api::downcast_call(*authorization_state_,
overloaded(
[this](td_api::authorizationStateReady &) {
are_authorized_ = true;
std::cout << "Authorization is completed" << std::endl;
},
[this](td_api::authorizationStateLoggingOut &) {
are_authorized_ = false;
std::cout << "Logging out" << std::endl;
},
[this](td_api::authorizationStateClosing &) { std::cout << "Closing" << std::endl; },
[this](td_api::authorizationStateClosed &) {
are_authorized_ = false;
need_restart_ = true;
std::cout << "Terminated" << std::endl;
},
[this](td_api::authorizationStateWaitPhoneNumber &) {
std::cout << "Enter phone number: " << std::flush;
std::string phone_number;
std::cin >> phone_number;
send_query(
td_api::make_object<td_api::setAuthenticationPhoneNumber>(phone_number, nullptr),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitEmailAddress &) {
std::cout << "Enter email address: " << std::flush;
std::string email_address;
std::cin >> email_address;
send_query(td_api::make_object<td_api::setAuthenticationEmailAddress>(email_address),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitEmailCode &) {
std::cout << "Enter email authentication code: " << std::flush;
std::string code;
std::cin >> code;
send_query(td_api::make_object<td_api::checkAuthenticationEmailCode>(
td_api::make_object<td_api::emailAddressAuthenticationCode>(code)),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitCode &) {
std::cout << "Enter authentication code: " << std::flush;
std::string code;
std::cin >> code;
send_query(td_api::make_object<td_api::checkAuthenticationCode>(code),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitRegistration &) {
std::string first_name;
std::string last_name;
std::cout << "Enter your first name: " << std::flush;
std::cin >> first_name;
std::cout << "Enter your last name: " << std::flush;
std::cin >> last_name;
send_query(td_api::make_object<td_api::registerUser>(first_name, last_name, false),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitPassword &) {
std::cout << "Enter authentication password: " << std::flush;
std::string password;
std::getline(std::cin, password);
send_query(td_api::make_object<td_api::checkAuthenticationPassword>(password),
create_authentication_query_handler());
},
[this](td_api::authorizationStateWaitOtherDeviceConfirmation &state) {
std::cout << "Confirm this login link on another device: " << state.link_ << std::endl;
},
[this](td_api::authorizationStateWaitTdlibParameters &) {
auto request = td_api::make_object<td_api::setTdlibParameters>();
request->database_directory_ = "tdlib";
request->use_message_database_ = true;
request->use_secret_chats_ = true;
request->api_id_ = 94575;
request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
request->system_language_code_ = "en";
request->device_model_ = "Desktop";
request->application_version_ = "1.0";
send_query(std::move(request), create_authentication_query_handler());
}));
}
void check_authentication_error(Object object) {
if (object->get_id() == td_api::error::ID) {
auto error = td::move_tl_object_as<td_api::error>(object);
std::cout << "Error: " << to_string(error) << std::flush;
on_authorization_state_update();
}
}
std::uint64_t next_query_id() {
return ++current_query_id_;
}
};
int main() {
TdExample example;
example.loop();
}

View File

@@ -0,0 +1,55 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <td/telegram/td_json_client.h>
// Basic example of TDLib JSON interface usage.
// Native interface should be preferred instead in C++, so here is only an example of
// the main event cycle, which should be essentially the same for all languages.
int main() {
// disable TDLib logging
td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":0}");
int client_id = td_create_client_id();
// somehow share the client_id with other threads, which will be able to send requests via td_send
// start the client by sending request to it
td_send(client_id, "{\"@type\":\"getOption\", \"name\":\"version\"}");
const bool test_incorrect_queries = false;
if (test_incorrect_queries) {
td_execute("{\"@type\":\"setLogVerbosityLevel\", \"new_verbosity_level\":1}");
td_execute("");
td_execute("test");
td_execute("\"test\"");
td_execute("{\"@type\":\"test\", \"@extra\":1}");
td_send(client_id, "{\"@type\":\"getFileMimeType\"}");
td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":1}");
td_send(client_id, "{\"@type\":\"getFileMimeType\", \"@extra\":null}");
td_send(client_id, "{\"@type\":\"test\"}");
td_send(client_id, "[]");
td_send(client_id, "{\"@type\":\"test\", \"@extra\":1}");
td_send(client_id, "{\"@type\":\"sendMessage\", \"chat_id\":true, \"@extra\":1}");
td_send(client_id, "test");
}
const double WAIT_TIMEOUT = 10.0; // seconds
while (true) {
const char *result = td_receive(WAIT_TIMEOUT);
if (result != nullptr) {
// parse the result as a JSON object and process it as an incoming update or an answer to a previously sent request
// if (result is UpdateAuthorizationState with authorizationStateClosed) {
// break;
// }
std::cout << result << std::endl;
}
}
}

5
td/example/csharp/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/.vs/
/bin/
/obj/
/project.lock.json
/TdExample.csproj.user

View File

@@ -0,0 +1,40 @@
# TDLib C# example
This is an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C#.
## Building TDLib
* Download and install Microsoft Visual Studio 2015 or later.
* Download and install [CMake](https://cmake.org/download/); choose "Add CMake to the system PATH" option while installing.
* Install `gperf`, `zlib`, and `openssl` using [vcpkg](https://github.com/Microsoft/vcpkg#quick-start):
```
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
git checkout cd5e746ec203c8c3c61647e0886a8df8c1e78e41
.\bootstrap-vcpkg.bat
.\vcpkg.exe install gperf:x64-windows gperf:x86-windows openssl:x64-windows openssl:x86-windows zlib:x64-windows zlib:x86-windows
```
* (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download). Add the path to php.exe to the PATH environment variable.
* Build `TDLib` with CMake enabling `.NET` support and specifying correct path to `vcpkg` toolchain file:
```
cd <path to TDLib sources>/example/csharp
mkdir build
cd build
cmake -A Win32 -DTD_ENABLE_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=<path to vcpkg>/scripts/buildsystems/vcpkg.cmake ../../..
cmake --build . --config Release
cmake --build . --config Debug
cd ..
mkdir build64
cd build64
cmake -A x64 -DTD_ENABLE_DOTNET=ON -DCMAKE_TOOLCHAIN_FILE=<path to vcpkg>/scripts/buildsystems/vcpkg.cmake ../../..
cmake --build . --config Release
cmake --build . --config Debug
```
## Example of usage
After `TDLib` is built you can open and run TdExample project.
It contains a simple console C# application with implementation of authorization and message sending.
Just open it with Visual Studio 2015 or later and run.
Also, see TdExample.csproj for example of including TDLib in C# project with all native shared library dependencies.

View File

@@ -0,0 +1,293 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
using Td = Telegram.Td;
using TdApi = Telegram.Td.Api;
using System;
using System.Threading;
namespace TdExample
{
/// <summary>
/// Example class for TDLib usage from C#.
/// </summary>
class Example
{
private static Td.Client _client = null;
private readonly static Td.ClientResultHandler _defaultHandler = new DefaultHandler();
private static TdApi.AuthorizationState _authorizationState = null;
private static volatile bool _haveAuthorization = false;
private static volatile bool _needQuit = false;
private static volatile bool _canQuit = false;
private static volatile AutoResetEvent _gotAuthorization = new AutoResetEvent(false);
private static readonly string _newLine = Environment.NewLine;
private static readonly string _commandsLine = "Enter command (gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, r - Restart, q - Quit): ";
private static volatile string _currentPrompt = null;
private static Td.Client CreateTdClient()
{
return Td.Client.Create(new UpdateHandler());
}
private static void Print(string str)
{
if (_currentPrompt != null)
{
Console.WriteLine();
}
Console.WriteLine(str);
if (_currentPrompt != null)
{
Console.Write(_currentPrompt);
}
}
private static string ReadLine(string str)
{
Console.Write(str);
_currentPrompt = str;
var result = Console.ReadLine();
_currentPrompt = null;
return result;
}
private static void OnAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState)
{
if (authorizationState != null)
{
_authorizationState = authorizationState;
}
if (_authorizationState is TdApi.AuthorizationStateWaitTdlibParameters)
{
TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters();
request.DatabaseDirectory = "tdlib";
request.UseMessageDatabase = true;
request.UseSecretChats = true;
request.ApiId = 94575;
request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
request.SystemLanguageCode = "en";
request.DeviceModel = "Desktop";
request.ApplicationVersion = "1.0";
_client.Send(request, new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitPhoneNumber)
{
string phoneNumber = ReadLine("Please enter phone number: ");
_client.Send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitEmailAddress)
{
string emailAddress = ReadLine("Please enter email address: ");
_client.Send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitEmailCode)
{
string code = ReadLine("Please enter email authentication code: ");
_client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitOtherDeviceConfirmation state)
{
Console.WriteLine("Please confirm this login link on another device: " + state.Link);
}
else if (_authorizationState is TdApi.AuthorizationStateWaitCode)
{
string code = ReadLine("Please enter authentication code: ");
_client.Send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitRegistration)
{
string firstName = ReadLine("Please enter your first name: ");
string lastName = ReadLine("Please enter your last name: ");
_client.Send(new TdApi.RegisterUser(firstName, lastName, false), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateWaitPassword)
{
string password = ReadLine("Please enter password: ");
_client.Send(new TdApi.CheckAuthenticationPassword(password), new AuthorizationRequestHandler());
}
else if (_authorizationState is TdApi.AuthorizationStateReady)
{
_haveAuthorization = true;
_gotAuthorization.Set();
}
else if (_authorizationState is TdApi.AuthorizationStateLoggingOut)
{
_haveAuthorization = false;
Print("Logging out");
}
else if (_authorizationState is TdApi.AuthorizationStateClosing)
{
_haveAuthorization = false;
Print("Closing");
}
else if (_authorizationState is TdApi.AuthorizationStateClosed)
{
Print("Closed");
if (!_needQuit)
{
_client = CreateTdClient(); // recreate _client after previous has closed
} else {
_canQuit = true;
}
}
else
{
Print("Unsupported authorization state:" + _newLine + _authorizationState);
}
}
private static long GetChatId(string arg)
{
long chatId = 0;
try
{
chatId = Convert.ToInt64(arg);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
return chatId;
}
private static void GetCommand()
{
string command = ReadLine(_commandsLine);
string[] commands = command.Split(new char[] { ' ' }, 2);
try
{
switch (commands[0])
{
case "gc":
_client.Send(new TdApi.GetChat(GetChatId(commands[1])), _defaultHandler);
break;
case "me":
_client.Send(new TdApi.GetMe(), _defaultHandler);
break;
case "sm":
string[] args = commands[1].Split(new char[] { ' ' }, 2);
sendMessage(GetChatId(args[0]), args[1]);
break;
case "lo":
_haveAuthorization = false;
_client.Send(new TdApi.LogOut(), _defaultHandler);
break;
case "r":
_haveAuthorization = false;
_client.Send(new TdApi.Close(), _defaultHandler);
break;
case "q":
_needQuit = true;
_haveAuthorization = false;
_client.Send(new TdApi.Close(), _defaultHandler);
break;
default:
Print("Unsupported command: " + command);
break;
}
}
catch (IndexOutOfRangeException)
{
Print("Not enough arguments");
}
}
private static void sendMessage(long chatId, string message)
{
// initialize reply markup just for testing
TdApi.InlineKeyboardButton[] row = { new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl()) };
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][] { row, row, row });
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), null, true);
_client.Send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), _defaultHandler);
}
static void Main()
{
// disable TDLib log
Td.Client.Execute(new TdApi.SetLogVerbosityLevel(0));
if (Td.Client.Execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false))) is TdApi.Error)
{
throw new System.IO.IOException("Write access to the current directory is required");
}
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
Td.Client.Run();
}).Start();
// create Td.Client
_client = CreateTdClient();
// test Client.Execute
_defaultHandler.OnResult(Td.Client.Execute(new TdApi.GetTextEntities("@telegram /test_command https://telegram.org telegram.me @gif @test")));
// main loop
while (!_needQuit)
{
// await authorization
_gotAuthorization.Reset();
_gotAuthorization.WaitOne();
_client.Send(new TdApi.LoadChats(null, 100), _defaultHandler); // preload main chat list
while (_haveAuthorization)
{
GetCommand();
}
}
while (!_canQuit) {
Thread.Sleep(1);
}
}
private class DefaultHandler : Td.ClientResultHandler
{
void Td.ClientResultHandler.OnResult(TdApi.BaseObject @object)
{
Print(@object.ToString());
}
}
private class UpdateHandler : Td.ClientResultHandler
{
void Td.ClientResultHandler.OnResult(TdApi.BaseObject @object)
{
if (@object is TdApi.UpdateAuthorizationState)
{
OnAuthorizationStateUpdated((@object as TdApi.UpdateAuthorizationState).AuthorizationState);
}
else
{
// Print("Unsupported update: " + @object);
}
}
}
private class AuthorizationRequestHandler : Td.ClientResultHandler
{
void Td.ClientResultHandler.OnResult(TdApi.BaseObject @object)
{
if (@object is TdApi.Error)
{
Print("Receive an error:" + _newLine + @object);
OnAuthorizationStateUpdated(null); // repeat last action
}
else
{
// result is already received through UpdateAuthorizationState, nothing to do
}
}
}
}
}

View File

@@ -0,0 +1,119 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}</ProjectGuid>
<OutputType>Exe</OutputType>
<NoStandardLibraries>false</NoStandardLibraries>
<AssemblyName>ConsoleApplication</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<RootNamespace>TdExample</RootNamespace>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Telegram.Td, Version=0.0.0.0, Culture=neutral, processorArchitecture=AMD64">
<SpecificVersion>False</SpecificVersion>
<HintPath Condition=" '$(Platform)' == 'x86' ">build\$(Configuration)\Telegram.Td.dll</HintPath>
<HintPath Condition=" '$(Platform)' == 'x64' ">build64\$(Configuration)\Telegram.Td.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="TdExample.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<Content Include="build\Debug\zlibd1.dll">
<Link>zlibd1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<Content Include="build\Release\zlib1.dll">
<Link>zlib1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
<Content Include="build64\Debug\zlibd1.dll">
<Link>zlibd1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
<Content Include="build64\Release\zlib1.dll">
<Link>zlib1.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(Platform)' == 'x86' ">
<Content Include="build\$(Configuration)\libcrypto-3.dll">
<Link>libcrypto-3.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="build\$(Configuration)\libssl-3.dll">
<Link>libssl-3.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup Condition=" '$(Platform)' == 'x64' ">
<Content Include="build64\$(Configuration)\libcrypto-3-x64.dll">
<Link>libcrypto-3-x64.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="build64\$(Configuration)\libssl-3-x64.dll">
<Link>libssl-3-x64.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSHARP.Targets" />
<ProjectExtensions>
<VisualStudio AllowExistingFolder="true" />
</ProjectExtensions>
</Project>

View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28010.2046
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TdExample", "TdExample.csproj", "{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Debug|x64.ActiveCfg = Debug|x64
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Debug|x64.Build.0 = Debug|x64
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Debug|x86.ActiveCfg = Debug|x86
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Debug|x86.Build.0 = Debug|x86
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Release|x64.ActiveCfg = Release|x64
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Release|x64.Build.0 = Release|x64
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Release|x86.ActiveCfg = Release|x86
{3F9A93EA-DC26-4F8B-ACE0-131B33B00CA7}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {865856BA-F733-45DF-B35F-12607605B023}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,118 @@
diff --git a/Makefile b/Makefile
index a1d13e9..8efcf20 100644
--- a/Makefile
+++ b/Makefile
@@ -18,8 +18,13 @@
# - OpenSSL - build OpenSSL for all platforms
# - OpenSSL-macOS - build OpenSSL for macOS
# - OpenSSL-iOS - build OpenSSL for iOS
+# - OpenSSL-iOS-simulator - build OpenSSL for iOS-simulator
# - OpenSSL-tvOS - build OpenSSL for tvOS
+# - OpenSSL-tvOS-simulator - build OpenSSL for tvOS-simulator
# - OpenSSL-watchOS - build OpenSSL for watchOS
+# - OpenSSL-watchOS-simulator - build OpenSSL for watchOS-simulator
+# - OpenSSL-visionOS - build OpenSSL for visionOS
+# - OpenSSL-visionOS-simulator - build OpenSSL for visionOS-simulator
# - libFFI - build libFFI for all platforms (except macOS)
# - libFFI-iOS - build libFFI for iOS
# - libFFI-tvOS - build libFFI for tvOS
@@ -50,7 +55,7 @@ XZ_VERSION=5.4.2
# Preference is to use OpenSSL 3; however, Cryptography 3.4.8 (and
# probably some other packages as well) only works with 1.1.1, so
# we need to preserve the ability to build the older OpenSSL (for now...)
-OPENSSL_VERSION=3.1.0
+OPENSSL_VERSION=3.1.5
# OPENSSL_VERSION_NUMBER=1.1.1
# OPENSSL_REVISION=q
# OPENSSL_VERSION=$(OPENSSL_VERSION_NUMBER)$(OPENSSL_REVISION)
@@ -59,7 +64,7 @@ LIBFFI_VERSION=3.4.2
# Supported OS and dependencies
DEPENDENCIES=BZip2 XZ OpenSSL libFFI
-OS_LIST=macOS iOS tvOS watchOS
+OS_LIST=macOS iOS iOS-simulator tvOS tvOS-simulator watchOS watchOS-simulator visionOS visionOS-simulator
CURL_FLAGS=--disable --fail --location --create-dirs --progress-bar
@@ -69,22 +74,41 @@ VERSION_MIN-macOS=10.15
CFLAGS-macOS=-mmacosx-version-min=$(VERSION_MIN-macOS)
# iOS targets
-TARGETS-iOS=iphonesimulator.x86_64 iphonesimulator.arm64 iphoneos.arm64
+TARGETS-iOS=iphoneos.arm64
VERSION_MIN-iOS=12.0
CFLAGS-iOS=-mios-version-min=$(VERSION_MIN-iOS)
+# iOS-simulator targets
+TARGETS-iOS-simulator=iphonesimulator.x86_64 iphonesimulator.arm64
+CFLAGS-iOS-simulator=-mios-simulator-version-min=$(VERSION_MIN-iOS)
+
# tvOS targets
-TARGETS-tvOS=appletvsimulator.x86_64 appletvsimulator.arm64 appletvos.arm64
+TARGETS-tvOS=appletvos.arm64
VERSION_MIN-tvOS=9.0
CFLAGS-tvOS=-mtvos-version-min=$(VERSION_MIN-tvOS)
PYTHON_CONFIGURE-tvOS=ac_cv_func_sigaltstack=no
+# tvOS-simulator targets
+TARGETS-tvOS-simulator=appletvsimulator.x86_64 appletvsimulator.arm64
+CFLAGS-tvOS-simulator=-mtvos-simulator-version-min=$(VERSION_MIN-tvOS)
+
# watchOS targets
-TARGETS-watchOS=watchsimulator.x86_64 watchsimulator.arm64 watchos.arm64_32
+TARGETS-watchOS=watchos.armv7k watchos.arm64_32 watchos.arm64
VERSION_MIN-watchOS=4.0
CFLAGS-watchOS=-mwatchos-version-min=$(VERSION_MIN-watchOS)
PYTHON_CONFIGURE-watchOS=ac_cv_func_sigaltstack=no
+# watchOS-simulator targets
+TARGETS-watchOS-simulator=watchsimulator.i386 watchsimulator.x86_64 watchsimulator.arm64
+CFLAGS-watchOS-simulator=-mwatchos-simulator-version-min=$(VERSION_MIN-watchOS)
+
+# visionOS targets
+TARGETS-visionOS=xros.arm64
+PYTHON_CONFIGURE-visionOS=ac_cv_func_sigaltstack=no
+
+# visionOS-simulator targets
+TARGETS-visionOS-simulator=xrsimulator.x86_64 xrsimulator.arm64
+
# The architecture of the machine doing the build
HOST_ARCH=$(shell uname -m)
HOST_PYTHON=install/macOS/macosx/python-$(PYTHON_VERSION)
@@ -212,6 +236,10 @@ ARCH-$(target)=$$(subst .,,$$(suffix $(target)))
ifeq ($(os),macOS)
TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-darwin
+else ifeq ($(os),visionOS)
+TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-xros
+else ifeq ($(os),visionOS-simulator)
+TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-xros-simulator
else
ifeq ($$(findstring simulator,$$(SDK-$(target))),)
TARGET_TRIPLE-$(target)=$$(ARCH-$(target))-apple-$$(OS_LOWER-$(target))
@@ -662,7 +690,7 @@ BZIP2_FATLIB-$(sdk)=$$(BZIP2_MERGE-$(sdk))/lib/libbz2.a
XZ_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/$(sdk)/xz-$(XZ_VERSION)
XZ_FATLIB-$(sdk)=$$(XZ_MERGE-$(sdk))/lib/liblzma.a
-OPENSSL_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION)
+OPENSSL_MERGE-$(sdk)=$(PROJECT_DIR)/merge/$(os)/openssl
OPENSSL_FATINCLUDE-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/include
OPENSSL_SSL_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libssl.a
OPENSSL_CRYPTO_FATLIB-$(sdk)=$$(OPENSSL_MERGE-$(sdk))/lib/libcrypto.a
@@ -716,14 +744,14 @@ $$(OPENSSL_SSL_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENS
mkdir -p $$(OPENSSL_MERGE-$(sdk))/lib
lipo -create -output $$@ \
$$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_SSL_LIB-$$(target))) \
- 2>&1 | tee -a merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION).ssl.lipo.log
+ 2>&1 | tee -a merge/$(os)/openssl-$(OPENSSL_VERSION).ssl.lipo.log
$$(OPENSSL_CRYPTO_FATLIB-$(sdk)): $$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_CRYPTO_LIB-$$(target)))
@echo ">>> Build OpenSSL crypto fat library for $(sdk)"
mkdir -p $$(OPENSSL_MERGE-$(sdk))/lib
lipo -create -output $$@ \
$$(foreach target,$$(SDK_TARGETS-$(sdk)),$$(OPENSSL_CRYPTO_LIB-$$(target))) \
- 2>&1 | tee -a merge/$(os)/$(sdk)/openssl-$(OPENSSL_VERSION).crypto.lipo.log
+ 2>&1 | tee -a merge/$(os)/openssl-$(OPENSSL_VERSION).crypto.lipo.log
###########################################################################
# SDK: libFFI

46
td/example/ios/README.md Normal file
View File

@@ -0,0 +1,46 @@
# Universal XCFramework build example
Below are instructions for building TDLib for iOS, watchOS, tvOS, visionOS, and also macOS.
If you need only a macOS build for the current architecture, take a look at [TDLib build instructions generator](https://tdlib.github.io/td/build.html).
For example of usage take a look at our [Swift example](https://github.com/tdlib/td/tree/master/example/swift).
To compile `TDLib` you will need to:
* Install the latest Xcode via `xcode-select --install` or downloading it from [Xcode website](https://developer.apple.com/xcode/).
It is not enough to install only command line developer tools to build `TDLib` for iOS. Xcode >= 14.0 is required to build TDLib for watchOS.
* Install other build dependencies using [Homebrew](https://brew.sh):
```
brew install gperf cmake coreutils
```
* If you don't want to build `TDLib` for macOS first, you **must** pregenerate required source code files in the following way:
```
cd <path to TDLib sources>
mkdir native-build
cd native-build
cmake -DTD_GENERATE_SOURCE_FILES=ON ..
cmake --build .
```
* Build OpenSSL for iOS, watchOS, tvOS, visionOS, and macOS:
```
cd <path to TDLib sources>/example/ios
./build-openssl.sh
```
Here we use scripts from [Python Apple support](https://github.com/beeware/Python-Apple-support), but any other OpenSSL build should work too.
[Python Apple support](https://github.com/beeware/Python-Apple-support) has known problems with spaces in the path to the current directory, so
you need to ensure that there are no spaces in the path.
Built OpenSSL libraries should be stored in the directory `third_party/openssl/<platform>`, because the next script will rely on this location.
* Build TDLib for iOS, watchOS, tvOS, visionOS, and macOS:
```
cd <path to TDLib sources>/example/ios
./build.sh
```
This may take a while, because TDLib will be built about 16 times.
Resulting XCFramework will work on any architecture and even on a simulator.
We use [CMake/iOS.cmake](https://github.com/tdlib/td/blob/master/CMake/iOS.cmake) toolchain, other toolchains may work too.
Built libraries and XCFramework will be stored in `tdjson` directory.
Documentation for all available classes and methods can be found at https://core.telegram.org/tdlib/docs.
If you receive an "error: SDK "appletvsimulator" cannot be located", you need to run the command "sudo xcode-select -s /Applications/Xcode.app/Contents/Developer" before running ./build.sh.

37
td/example/ios/build-openssl.sh Executable file
View File

@@ -0,0 +1,37 @@
#!/bin/sh
cd $(dirname $0)
git clone https://github.com/beeware/Python-Apple-support
cd Python-Apple-support
git checkout 6f43aba0ddd5a9f52f39775d0141bd4363614020 || exit 1
git reset --hard || exit 1
git apply ../Python-Apple-support.patch || exit 1
cd ..
platforms="macOS iOS watchOS tvOS visionOS"
for platform in $platforms;
do
if [[ $platform = "macOS" ]]; then
simulators="0"
else
simulators="0 1"
fi
for simulator in $simulators;
do
if [[ $simulator = "1" ]]; then
platform="${platform}-simulator"
fi
echo $platform
cd Python-Apple-support
#NB: -j will fail
make OpenSSL-$platform || exit 1
cd ..
rm -rf third_party/openssl/$platform || exit 1
mkdir -p third_party/openssl/$platform/lib || exit 1
cp ./Python-Apple-support/merge/$platform/openssl/lib/libcrypto.a third_party/openssl/$platform/lib/ || exit 1
cp ./Python-Apple-support/merge/$platform/openssl/lib/libssl.a third_party/openssl/$platform/lib/ || exit 1
cp -r ./Python-Apple-support/merge/$platform/openssl/include/ third_party/openssl/$platform/include || exit 1
done
done

92
td/example/ios/build.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/bin/sh
cd $(dirname $0)
td_path=$(grealpath ../..)
rm -rf build
mkdir -p build
cd build
set_cmake_options () {
# Set CMAKE options depending on platform passed $1
openssl_path=$(grealpath ../third_party/openssl/$1)
echo "OpenSSL path = ${openssl_path}"
openssl_crypto_library="${openssl_path}/lib/libcrypto.a"
openssl_ssl_library="${openssl_path}/lib/libssl.a"
options=""
options="$options -DOPENSSL_FOUND=1"
options="$options -DOPENSSL_CRYPTO_LIBRARY=${openssl_crypto_library}"
options="$options -DOPENSSL_SSL_LIBRARY=${openssl_ssl_library}"
options="$options -DOPENSSL_INCLUDE_DIR=${openssl_path}/include"
options="$options -DOPENSSL_LIBRARIES=${openssl_crypto_library};${openssl_ssl_library}"
options="$options -DCMAKE_BUILD_TYPE=Release"
}
platforms="macOS iOS watchOS tvOS visionOS"
#platforms="watchOS"
for platform in $platforms;
do
echo "Platform = ${platform}"
if [[ $platform = "macOS" ]]; then
simulators="0"
else
simulators="0 1"
fi
for simulator in $simulators;
do
if [[ $platform = "macOS" ]]; then
other_options="-DCMAKE_OSX_ARCHITECTURES='x86_64;arm64'"
else
if [[ $platform = "watchOS" ]]; then
ios_platform="WATCH"
elif [[ $platform = "tvOS" ]]; then
ios_platform="TV"
elif [[ $platform = "visionOS" ]]; then
ios_platform="VISION"
else
ios_platform=""
fi
if [[ $simulator = "1" ]]; then
platform="${platform}-simulator"
ios_platform="${ios_platform}SIMULATOR"
else
ios_platform="${ios_platform}OS"
fi
echo "iOS platform = ${ios_platform}"
other_options="-DIOS_PLATFORM=${ios_platform} -DCMAKE_TOOLCHAIN_FILE=${td_path}/CMake/iOS.cmake"
fi
set_cmake_options $platform
build="build-${platform}"
install="install-${platform}"
rm -rf $build
mkdir -p $build
mkdir -p $install
cd $build
cmake $td_path $options $other_options -DCMAKE_INSTALL_PREFIX=../${install}
make -j3 install || exit
cd ..
install_name_tool -id @rpath/libtdjson.dylib ${install}/lib/libtdjson.dylib
mkdir -p ../tdjson/${platform}/include
rsync --recursive ${install}/include/ ../tdjson/${platform}/include/
mkdir -p ../tdjson/${platform}/lib
cp ${install}/lib/libtdjson.dylib ../tdjson/${platform}/lib/
done
done
produced_dylibs=(install-*/lib/libtdjson.dylib)
xcodebuild_frameworks=()
for dylib in "${produced_dylibs[@]}";
do
xcodebuild_frameworks+=(-library $(grealpath "${dylib}"))
done
# Make xcframework
xcodebuild -create-xcframework \
"${xcodebuild_frameworks[@]}" \
-output "libtdjson.xcframework"
rsync --recursive libtdjson.xcframework ../tdjson/

5
td/example/java/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
**/*build/
/bin/
/docs/
/org/drinkless/tdlib/TdApi.java
/td/

View File

@@ -0,0 +1,155 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
if (POLICY CMP0065)
# do not export symbols from executables
# affects compiler checks in project(), so must be set before it
cmake_policy(SET CMP0065 NEW)
endif()
project(TdJavaExample VERSION 1.0 LANGUAGES CXX)
option(TD_JSON_JAVA "Use \"ON\" to build Java wrapper for JSON API.")
if (POLICY CMP0054)
# do not expand quoted arguments
cmake_policy(SET CMP0054 NEW)
endif()
if (POLICY CMP0060)
# link libraries by full path
cmake_policy(SET CMP0060 NEW)
endif()
if (POLICY CMP0074)
# use environment variables to find libraries
cmake_policy(SET CMP0074 NEW)
endif()
find_package(Td REQUIRED)
if (NOT JNI_FOUND)
find_package(JNI REQUIRED COMPONENTS JVM)
endif()
message(STATUS "Found JNI: ${JNI_INCLUDE_DIRS} ${JNI_LIBRARIES}")
if (NOT Java_FOUND)
find_package(Java REQUIRED)
endif()
message(STATUS "Found Java: ${Java_JAVAC_EXECUTABLE} ${Java_JAVADOC_EXECUTABLE}")
# Generating TdApi.java
set(TD_API_JAVA_PACKAGE "org/drinkless/tdlib")
set(TD_API_JAVA_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(JAVA_SOURCE_PATH "${TD_API_JAVA_PATH}/${TD_API_JAVA_PACKAGE}")
if (TD_JSON_JAVA)
add_custom_target(td_generate_java_api
COMMAND cmake -E echo ""
COMMENT "Skip generation of Java TDLib API source files"
)
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/JsonExample.java")
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/JsonClient.java")
else()
find_program(PHP_EXECUTABLE php)
if ((CMAKE_SYSTEM_NAME MATCHES "FreeBSD") AND (CMAKE_SYSTEM_VERSION MATCHES "HBSD"))
set(PHP_EXECUTABLE "PHP_EXECUTABLE-NOTFOUND")
endif()
set(TD_API_TLO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tlo)
set(TD_API_TL_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/scheme/td_api.tl)
set(JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td/generate/JavadocTlDocumentationGenerator.php)
set(GENERATE_JAVA_API_CMD ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api TdApi "${TD_API_TLO_PATH}" "${TD_API_JAVA_PATH}" "${TD_API_JAVA_PACKAGE}")
if (PHP_EXECUTABLE)
set(GENERATE_JAVA_API_CMD ${GENERATE_JAVA_API_CMD} && ${PHP_EXECUTABLE} "${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}" "${TD_API_TL_PATH}" "${JAVA_SOURCE_PATH}/TdApi.java")
endif()
add_custom_target(td_generate_java_api
COMMAND ${GENERATE_JAVA_API_CMD}
COMMENT "Generating Java TDLib API source files"
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/td/bin/td_generate_java_api ${TD_API_TLO_PATH} ${TD_API_TL_PATH} ${JAVADOC_TL_DOCUMENTATION_GENERATOR_PATH}
)
set(JAVA_EXAMPLE_FILES "${JAVA_SOURCE_PATH}/example/Example.java")
set(JAVA_SOURCE_FILES "${JAVA_SOURCE_PATH}/Client.java" "${JAVA_SOURCE_PATH}/TdApi.java")
endif()
if (CMAKE_VERSION VERSION_LESS "3.17")
set(CMAKE_RM_COMMAND remove_directory)
else()
set(CMAKE_RM_COMMAND rm -rf --)
endif()
get_filename_component(JAVA_OUTPUT_DIRECTORY ${CMAKE_INSTALL_PREFIX}/bin REALPATH BASE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
file(MAKE_DIRECTORY ${JAVA_OUTPUT_DIRECTORY})
add_custom_target(build_java
COMMAND ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/${TD_API_JAVA_PACKAGE}"
COMMAND ${Java_JAVAC_EXECUTABLE} -encoding UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}" ${JAVA_EXAMPLE_FILES} ${JAVA_SOURCE_FILES}
COMMENT "Building Java code"
DEPENDS td_generate_java_api
)
add_custom_target(generate_javadoc
COMMAND ${CMAKE_COMMAND} -E ${CMAKE_RM_COMMAND} "${JAVA_OUTPUT_DIRECTORY}/../docs"
COMMAND ${Java_JAVADOC_EXECUTABLE} -encoding UTF-8 -charset UTF-8 -d "${JAVA_OUTPUT_DIRECTORY}/../docs" ${JAVA_SOURCE_FILES}
WORKING_DIRECTORY ${TD_API_JAVA_PATH}
COMMENT "Generating Javadoc documentation"
DEPENDS td_generate_java_api
)
# Building shared library
add_library(tdjni SHARED
td_jni.cpp
)
target_include_directories(tdjni PRIVATE ${JAVA_INCLUDE_PATH} ${JAVA_INCLUDE_PATH2})
target_link_libraries(tdjni PRIVATE ${JAVA_JVM_LIBRARY})
target_compile_definitions(tdjni PRIVATE PACKAGE_NAME="${TD_API_JAVA_PACKAGE}")
if (TD_JSON_JAVA)
target_link_libraries(tdjni PRIVATE Td::TdJsonStatic)
target_compile_definitions(tdjni PRIVATE TD_JSON_JAVA=1)
set_target_properties(tdjni PROPERTIES OUTPUT_NAME "tdjsonjava")
else()
target_link_libraries(tdjni PRIVATE Td::TdStatic)
endif()
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(GCC 1)
elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CLANG 1)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
set(INTEL 1)
elseif (NOT MSVC)
message(FATAL_ERROR "Compiler isn't supported")
endif()
include(CheckCXXCompilerFlag)
if (GCC OR CLANG OR INTEL)
if (WIN32 AND INTEL)
set(STD14_FLAG /Qstd=c++14)
else()
set(STD14_FLAG -std=c++14)
endif()
check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD14)
if (NOT HAVE_STD14)
string(REPLACE "c++14" "c++1y" STD14_FLAG "${STD14_FLAG}")
check_cxx_compiler_flag(${STD14_FLAG} HAVE_STD1Y)
set(HAVE_STD14 ${HAVE_STD1Y})
endif()
target_compile_options(tdjni PRIVATE "${STD14_FLAG}")
elseif (MSVC)
set(HAVE_STD14 MSVC_VERSION>=1900)
endif()
if (NOT HAVE_STD14)
message(FATAL_ERROR "No C++14 support in the compiler. Please upgrade the compiler.")
endif()
add_dependencies(tdjni td_generate_java_api build_java generate_javadoc)
install(TARGETS tdjni
LIBRARY DESTINATION bin
RUNTIME DESTINATION bin
)
if (MSVC AND VCPKG_TOOLCHAIN)
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION bin FILES_MATCHING PATTERN "*.dll" PATTERN "*.pdb")
endif()

47
td/example/java/README.md Normal file
View File

@@ -0,0 +1,47 @@
# TDLib Java example
To run this example, you will need installed JDK >= 1.6.
For Javadoc documentation generation PHP is needed.
You can find complete build instructions for your operating system at https://tdlib.github.io/td/build.html?language=Java.
In general, the build process looks as follows.
TDLib should be prebuilt with JNI bindings and installed to local subdirectory `td/` as follows:
```
cd <path to TDLib sources>
mkdir jnibuild
cd jnibuild
cmake -DCMAKE_BUILD_TYPE=Release -DTD_ENABLE_JNI=ON -DCMAKE_INSTALL_PREFIX:PATH=../example/java/td ..
cmake --build . --target install
```
If you want to compile TDLib for 32-bit/64-bit Java on Windows using MSVC, you will also need to add `-A Win32`/`-A x64` option to CMake.
In Windows, use vcpkg toolchain file by adding parameter -DCMAKE_TOOLCHAIN_FILE=<VCPKG_DIR>/scripts/buildsystems/vcpkg.cmake
If you want to build JsonClient.java wrapper for JSON interface instead of the native JNI interface, add `-DTD_JSON_JAVA=ON` option to CMake.
After this you can compile the example source code:
```
cd <path to TDLib sources>/example/java
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DTd_DIR=<full path to TDLib sources>/example/java/td/lib/cmake/Td -DCMAKE_INSTALL_PREFIX:PATH=.. ..
cmake --build . --target install
```
Compiled TDLib shared library and Java example after that will be placed in `bin/` and Javadoc documentation in `docs/`.
After this you can run the Java example:
```
cd <path to TDLib sources>/example/java/bin
java '-Djava.library.path=.' org/drinkless/tdlib/example/Example
```
If you built JSON interface example using `-DTD_JSON_JAVA=ON` option, then use the command `java '-Djava.library.path=.' org/drinkless/tdlib/example/JsonExample` instead.
If you receive "Could NOT find JNI ..." error from CMake, you need to specify to CMake path to the installed JDK, for example, "-DJAVA_HOME=/usr/lib/jvm/java-8-oracle/".
If you receive java.lang.UnsatisfiedLinkError with "Can't find dependent libraries", you may also need to copy some dependent shared OpenSSL and zlib libraries to `bin/`.
Make sure that you compiled the example for the same architecture as your JVM.

View File

@@ -0,0 +1,259 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
package org.drinkless.tdlib;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* Main class for interaction with the TDLib.
*/
public final class Client {
static {
try {
System.loadLibrary("tdjni");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
/**
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
*/
public interface ResultHandler {
/**
* Callback called on result of query to TDLib or incoming update from TDLib.
*
* @param object Result of query or update of type TdApi.Update about new events.
*/
void onResult(TdApi.Object object);
}
/**
* Interface for handler of exceptions thrown while invoking ResultHandler.
* By default, all such exceptions are ignored.
* All exceptions thrown from ExceptionHandler are ignored.
*/
public interface ExceptionHandler {
/**
* Callback called on exceptions thrown while invoking ResultHandler.
*
* @param e Exception thrown by ResultHandler.
*/
void onException(Throwable e);
}
/**
* Interface for handler of messages that are added to the internal TDLib log.
*/
public interface LogMessageHandler {
/**
* Callback called on messages that are added to the internal TDLib log.
*
* @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024.
* If 0, then TDLib will crash as soon as the callback returns.
* None of the TDLib methods can be called from the callback.
* @param message The message added to the internal TDLib log.
*/
void onLogMessage(int verbosityLevel, String message);
}
/**
* Exception class thrown when TDLib error occurred while performing {@link #execute(TdApi.Function)}.
*/
public static class ExecutionException extends Exception {
/**
* Original TDLib error occurred when performing one of the synchronous functions.
*/
public final TdApi.Error error;
/**
* @param error TDLib error occurred while performing {@link #execute(TdApi.Function)}.
*/
ExecutionException (TdApi.Error error) {
super(error.code + ": " + error.message);
this.error = error;
}
}
/**
* Sends a request to the TDLib.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, nothing
* will be called.
* @param exceptionHandler Exception handler with onException method which will be called on
* exception thrown from resultHandler. If it is null, then
* defaultExceptionHandler will be called.
*/
public void send(TdApi.Function query, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
long queryId = currentQueryId.incrementAndGet();
if (resultHandler != null) {
handlers.put(queryId, new Handler(resultHandler, exceptionHandler));
}
nativeClientSend(nativeClientId, queryId, query);
}
/**
* Sends a request to the TDLib with an empty ExceptionHandler.
*
* @param query Object representing a query to the TDLib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TdApi.Error as parameter. If it is null, then
* defaultExceptionHandler will be called.
*/
public void send(TdApi.Function query, ResultHandler resultHandler) {
send(query, resultHandler, null);
}
/**
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
*
* @param query Object representing a query to the TDLib.
* @param <T> Automatically deduced return type of the query.
* @return request result.
* @throws ExecutionException if query execution fails.
*/
@SuppressWarnings("unchecked")
public static <T extends TdApi.Object> T execute(TdApi.Function<T> query) throws ExecutionException {
TdApi.Object object = nativeClientExecute(query);
if (object instanceof TdApi.Error) {
throw new ExecutionException((TdApi.Error) object);
}
return (T) object;
}
/**
* Creates new Client.
*
* @param updateHandler Handler for incoming updates.
* @param updateExceptionHandler Handler for exceptions thrown from updateHandler. If it is null, exceptions will be ignored.
* @param defaultExceptionHandler Default handler for exceptions thrown from all ResultHandler. If it is null, exceptions will be ignored.
* @return created Client
*/
public static Client create(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
Client client = new Client(updateHandler, updateExceptionHandler, defaultExceptionHandler);
synchronized (responseReceiver) {
if (!responseReceiver.isRun) {
responseReceiver.isRun = true;
Thread receiverThread = new Thread(responseReceiver, "TDLib thread");
receiverThread.setDaemon(true);
receiverThread.start();
}
}
return client;
}
/**
* Sets the handler for messages that are added to the internal TDLib log.
* None of the TDLib methods can be called from the callback.
*
* @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
* @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
public static void setLogMessageHandler(int maxVerbosityLevel, Client.LogMessageHandler logMessageHandler) {
nativeClientSetLogMessageHandler(maxVerbosityLevel, logMessageHandler);
}
private static class ResponseReceiver implements Runnable {
public boolean isRun = false;
@Override
public void run() {
while (true) {
int resultN = nativeClientReceive(clientIds, eventIds, events, 100000.0 /*seconds*/);
for (int i = 0; i < resultN; i++) {
processResult(clientIds[i], eventIds[i], events[i]);
events[i] = null;
}
}
}
private void processResult(int clientId, long id, TdApi.Object object) {
boolean isClosed = false;
if (id == 0 && object instanceof TdApi.UpdateAuthorizationState) {
TdApi.AuthorizationState authorizationState = ((TdApi.UpdateAuthorizationState) object).authorizationState;
if (authorizationState instanceof TdApi.AuthorizationStateClosed) {
isClosed = true;
}
}
Handler handler = id == 0 ? updateHandlers.get(clientId) : handlers.remove(id);
if (handler != null) {
try {
handler.resultHandler.onResult(object);
} catch (Throwable cause) {
ExceptionHandler exceptionHandler = handler.exceptionHandler;
if (exceptionHandler == null) {
exceptionHandler = defaultExceptionHandlers.get(clientId);
}
if (exceptionHandler != null) {
try {
exceptionHandler.onException(cause);
} catch (Throwable ignored) {
}
}
}
}
if (isClosed) {
updateHandlers.remove(clientId); // there will be no more updates
defaultExceptionHandlers.remove(clientId); // ignore further exceptions
clientCount.decrementAndGet();
}
}
private static final int MAX_EVENTS = 1000;
private final int[] clientIds = new int[MAX_EVENTS];
private final long[] eventIds = new long[MAX_EVENTS];
private final TdApi.Object[] events = new TdApi.Object[MAX_EVENTS];
}
private final int nativeClientId;
private static final ConcurrentHashMap<Integer, ExceptionHandler> defaultExceptionHandlers = new ConcurrentHashMap<Integer, ExceptionHandler>();
private static final ConcurrentHashMap<Integer, Handler> updateHandlers = new ConcurrentHashMap<Integer, Handler>();
private static final ConcurrentHashMap<Long, Handler> handlers = new ConcurrentHashMap<Long, Handler>();
private static final AtomicLong currentQueryId = new AtomicLong();
private static final AtomicLong clientCount = new AtomicLong();
private static final ResponseReceiver responseReceiver = new ResponseReceiver();
private static class Handler {
final ResultHandler resultHandler;
final ExceptionHandler exceptionHandler;
Handler(ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
this.resultHandler = resultHandler;
this.exceptionHandler = exceptionHandler;
}
}
private Client(ResultHandler updateHandler, ExceptionHandler updateExceptionHandler, ExceptionHandler defaultExceptionHandler) {
clientCount.incrementAndGet();
nativeClientId = createNativeClient();
if (updateHandler != null) {
updateHandlers.put(nativeClientId, new Handler(updateHandler, updateExceptionHandler));
}
if (defaultExceptionHandler != null) {
defaultExceptionHandlers.put(nativeClientId, defaultExceptionHandler);
}
send(new TdApi.GetOption("version"), null, null);
}
private static native int createNativeClient();
private static native void nativeClientSend(int nativeClientId, long eventId, TdApi.Function function);
private static native int nativeClientReceive(int[] clientIds, long[] eventIds, TdApi.Object[] events, double timeout);
private static native TdApi.Object nativeClientExecute(TdApi.Function function);
private static native void nativeClientSetLogMessageHandler(int maxVerbosityLevel, LogMessageHandler logMessageHandler);
}

View File

@@ -0,0 +1,79 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
package org.drinkless.tdlib;
/**
* Main class for interaction with the TDLib using JSON interface.
*/
public final class JsonClient {
static {
try {
System.loadLibrary("tdjsonjava");
} catch (UnsatisfiedLinkError e) {
e.printStackTrace();
}
}
/**
* Returns an opaque identifier of a new TDLib instance.
* The TDLib instance will not send updates until the first request is sent to it.
* @return Opaque identifier of a new TDLib instance.
*/
public static native int createClientId();
/**
* Sends request to the TDLib client. May be called from any thread.
* @param clientId TDLib client identifier.
* @param request JSON-serialized request.
*/
public static native void send(int clientId, String request);
/**
* Receives incoming updates and request responses. Must not be called simultaneously from two different threads.
* @param timeout The maximum number of seconds allowed for this function to wait for new data.
* @return JSON-serialized incoming update or request response. May be null if the timeout expired before new data received.
*/
public static native String receive(double timeout);
/**
* Synchronously executes a TDLib request.
* A request can be executed synchronously, only if it is documented with "Can be called synchronously".
* @param request JSON-serialized request.
* @return JSON-serialized request response. May be null if the request is invalid.
*/
public static native String execute(String request);
/**
* Interface for handler of messages that are added to the internal TDLib log.
*/
public interface LogMessageHandler {
/**
* Callback called on messages that are added to the internal TDLib log.
*
* @param verbosityLevel Log verbosity level with which the message was added from -1 up to 1024.
* If 0, then TDLib will crash as soon as the callback returns.
* None of the TDLib methods can be called from the callback.
* @param message The message added to the internal TDLib log.
*/
void onLogMessage(int verbosityLevel, String message);
}
/**
* Sets the handler for messages that are added to the internal TDLib log.
* None of the TDLib methods can be called from the callback.
*
* @param maxVerbosityLevel The maximum verbosity level of messages for which the callback will be called.
* @param logMessageHandler Handler for messages that are added to the internal TDLib log. Pass null to remove the handler.
*/
public static native void setLogMessageHandler(int maxVerbosityLevel, JsonClient.LogMessageHandler logMessageHandler);
/**
* The class can't be instantiated.
*/
private JsonClient() {
}
}

View File

@@ -0,0 +1,780 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
package org.drinkless.tdlib.example;
import org.drinkless.tdlib.Client;
import org.drinkless.tdlib.TdApi;
import java.io.BufferedReader;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Example class for TDLib usage from Java.
*/
public final class Example {
private static Client client = null;
private static TdApi.AuthorizationState authorizationState = null;
private static volatile boolean haveAuthorization = false;
private static volatile boolean needQuit = false;
private static volatile boolean canQuit = false;
private static final Client.ResultHandler defaultHandler = new DefaultHandler();
private static final Lock authorizationLock = new ReentrantLock();
private static final Condition gotAuthorization = authorizationLock.newCondition();
private static final ConcurrentMap<Long, TdApi.User> users = new ConcurrentHashMap<Long, TdApi.User>();
private static final ConcurrentMap<Long, TdApi.BasicGroup> basicGroups = new ConcurrentHashMap<Long, TdApi.BasicGroup>();
private static final ConcurrentMap<Long, TdApi.Supergroup> supergroups = new ConcurrentHashMap<Long, TdApi.Supergroup>();
private static final ConcurrentMap<Integer, TdApi.SecretChat> secretChats = new ConcurrentHashMap<Integer, TdApi.SecretChat>();
private static final ConcurrentMap<Long, TdApi.Chat> chats = new ConcurrentHashMap<Long, TdApi.Chat>();
private static final NavigableSet<OrderedChat> mainChatList = new TreeSet<OrderedChat>();
private static boolean haveFullMainChatList = false;
private static final ConcurrentMap<Long, TdApi.UserFullInfo> usersFullInfo = new ConcurrentHashMap<Long, TdApi.UserFullInfo>();
private static final ConcurrentMap<Long, TdApi.BasicGroupFullInfo> basicGroupsFullInfo = new ConcurrentHashMap<Long, TdApi.BasicGroupFullInfo>();
private static final ConcurrentMap<Long, TdApi.SupergroupFullInfo> supergroupsFullInfo = new ConcurrentHashMap<Long, TdApi.SupergroupFullInfo>();
private static final String newLine = System.getProperty("line.separator");
private static final String commandsLine = "Enter command (gcs - GetChats, gc <chatId> - GetChat, me - GetMe, sm <chatId> <message> - SendMessage, lo - LogOut, q - Quit): ";
private static volatile String currentPrompt = null;
private static void print(String str) {
if (currentPrompt != null) {
System.out.println("");
}
System.out.println(str);
if (currentPrompt != null) {
System.out.print(currentPrompt);
}
}
private static void setChatPositions(TdApi.Chat chat, TdApi.ChatPosition[] positions) {
synchronized (mainChatList) {
synchronized (chat) {
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isRemoved = mainChatList.remove(new OrderedChat(chat.id, position));
assert isRemoved;
}
}
chat.positions = positions;
for (TdApi.ChatPosition position : chat.positions) {
if (position.list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
boolean isAdded = mainChatList.add(new OrderedChat(chat.id, position));
assert isAdded;
}
}
}
}
}
private static void onAuthorizationStateUpdated(TdApi.AuthorizationState authorizationState) {
if (authorizationState != null) {
Example.authorizationState = authorizationState;
}
switch (Example.authorizationState.getConstructor()) {
case TdApi.AuthorizationStateWaitTdlibParameters.CONSTRUCTOR:
TdApi.SetTdlibParameters request = new TdApi.SetTdlibParameters();
request.databaseDirectory = "tdlib";
request.useMessageDatabase = true;
request.useSecretChats = true;
request.apiId = 94575;
request.apiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
request.systemLanguageCode = "en";
request.deviceModel = "Desktop";
request.applicationVersion = "1.0";
client.send(request, new AuthorizationRequestHandler());
break;
case TdApi.AuthorizationStateWaitPhoneNumber.CONSTRUCTOR: {
String phoneNumber = promptString("Please enter phone number: ");
client.send(new TdApi.SetAuthenticationPhoneNumber(phoneNumber, null), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitOtherDeviceConfirmation.CONSTRUCTOR: {
String link = ((TdApi.AuthorizationStateWaitOtherDeviceConfirmation) Example.authorizationState).link;
System.out.println("Please confirm this login link on another device: " + link);
break;
}
case TdApi.AuthorizationStateWaitEmailAddress.CONSTRUCTOR: {
String emailAddress = promptString("Please enter email address: ");
client.send(new TdApi.SetAuthenticationEmailAddress(emailAddress), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitEmailCode.CONSTRUCTOR: {
String code = promptString("Please enter email authentication code: ");
client.send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(code)), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitCode.CONSTRUCTOR: {
String code = promptString("Please enter authentication code: ");
client.send(new TdApi.CheckAuthenticationCode(code), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitRegistration.CONSTRUCTOR: {
String firstName = promptString("Please enter your first name: ");
String lastName = promptString("Please enter your last name: ");
client.send(new TdApi.RegisterUser(firstName, lastName, false), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateWaitPassword.CONSTRUCTOR: {
String password = promptString("Please enter password: ");
client.send(new TdApi.CheckAuthenticationPassword(password), new AuthorizationRequestHandler());
break;
}
case TdApi.AuthorizationStateReady.CONSTRUCTOR:
haveAuthorization = true;
authorizationLock.lock();
try {
gotAuthorization.signal();
} finally {
authorizationLock.unlock();
}
break;
case TdApi.AuthorizationStateLoggingOut.CONSTRUCTOR:
haveAuthorization = false;
print("Logging out");
break;
case TdApi.AuthorizationStateClosing.CONSTRUCTOR:
haveAuthorization = false;
print("Closing");
break;
case TdApi.AuthorizationStateClosed.CONSTRUCTOR:
print("Closed");
if (!needQuit) {
client = Client.create(new UpdateHandler(), null, null); // recreate client after previous has closed
} else {
canQuit = true;
}
break;
default:
System.err.println("Unsupported authorization state:" + newLine + Example.authorizationState);
}
}
private static int toInt(String arg) {
int result = 0;
try {
result = Integer.parseInt(arg);
} catch (NumberFormatException ignored) {
}
return result;
}
private static long getChatId(String arg) {
long chatId = 0;
try {
chatId = Long.parseLong(arg);
} catch (NumberFormatException ignored) {
}
return chatId;
}
private static String promptString(String prompt) {
System.out.print(prompt);
currentPrompt = prompt;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String str = "";
try {
str = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
currentPrompt = null;
return str;
}
private static void getCommand() {
String command = promptString(commandsLine);
String[] commands = command.split(" ", 2);
try {
switch (commands[0]) {
case "gcs": {
int limit = 20;
if (commands.length > 1) {
limit = toInt(commands[1]);
}
getMainChatList(limit);
break;
}
case "gc":
client.send(new TdApi.GetChat(getChatId(commands[1])), defaultHandler);
break;
case "me":
client.send(new TdApi.GetMe(), defaultHandler);
break;
case "sm": {
String[] args = commands[1].split(" ", 2);
sendMessage(getChatId(args[0]), args[1]);
break;
}
case "lo":
haveAuthorization = false;
client.send(new TdApi.LogOut(), defaultHandler);
break;
case "q":
needQuit = true;
haveAuthorization = false;
client.send(new TdApi.Close(), defaultHandler);
break;
default:
System.err.println("Unsupported command: " + command);
}
} catch (ArrayIndexOutOfBoundsException e) {
print("Not enough arguments");
}
}
private static void getMainChatList(final int limit) {
synchronized (mainChatList) {
if (!haveFullMainChatList && limit > mainChatList.size()) {
// send LoadChats request if there are some unknown chats and have not enough known chats
client.send(new TdApi.LoadChats(new TdApi.ChatListMain(), limit - mainChatList.size()), new Client.ResultHandler() {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.Error.CONSTRUCTOR:
if (((TdApi.Error) object).code == 404) {
synchronized (mainChatList) {
haveFullMainChatList = true;
}
} else {
System.err.println("Receive an error for LoadChats:" + newLine + object);
}
break;
case TdApi.Ok.CONSTRUCTOR:
// chats had already been received through updates, let's retry request
getMainChatList(limit);
break;
default:
System.err.println("Receive wrong response from TDLib:" + newLine + object);
}
}
});
return;
}
java.util.Iterator<OrderedChat> iter = mainChatList.iterator();
System.out.println();
System.out.println("First " + limit + " chat(s) out of " + mainChatList.size() + " known chat(s):");
for (int i = 0; i < limit && i < mainChatList.size(); i++) {
long chatId = iter.next().chatId;
TdApi.Chat chat = chats.get(chatId);
synchronized (chat) {
System.out.println(chatId + ": " + chat.title);
}
}
print("");
}
}
private static void sendMessage(long chatId, String message) {
// initialize reply markup just for testing
TdApi.InlineKeyboardButton[] row = {new TdApi.InlineKeyboardButton("https://telegram.org?1", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?2", new TdApi.InlineKeyboardButtonTypeUrl()), new TdApi.InlineKeyboardButton("https://telegram.org?3", new TdApi.InlineKeyboardButtonTypeUrl())};
TdApi.ReplyMarkup replyMarkup = new TdApi.ReplyMarkupInlineKeyboard(new TdApi.InlineKeyboardButton[][]{row, row, row});
TdApi.InputMessageContent content = new TdApi.InputMessageText(new TdApi.FormattedText(message, null), null, true);
client.send(new TdApi.SendMessage(chatId, 0, null, null, replyMarkup, content), defaultHandler);
}
public static void main(String[] args) throws InterruptedException {
// set log message handler to handle only fatal errors (0) and plain log messages (-1)
Client.setLogMessageHandler(0, new LogMessageHandler());
// disable TDLib log and redirect fatal errors and plain log messages to a file
try {
Client.execute(new TdApi.SetLogVerbosityLevel(0));
Client.execute(new TdApi.SetLogStream(new TdApi.LogStreamFile("tdlib.log", 1 << 27, false)));
} catch (Client.ExecutionException error) {
throw new IOError(new IOException("Write access to the current directory is required"));
}
// create client
client = Client.create(new UpdateHandler(), null, null);
// main loop
while (!needQuit) {
// await authorization
authorizationLock.lock();
try {
while (!haveAuthorization) {
gotAuthorization.await();
}
} finally {
authorizationLock.unlock();
}
while (haveAuthorization) {
getCommand();
}
}
while (!canQuit) {
Thread.sleep(1);
}
}
private static class OrderedChat implements Comparable<OrderedChat> {
final long chatId;
final TdApi.ChatPosition position;
OrderedChat(long chatId, TdApi.ChatPosition position) {
this.chatId = chatId;
this.position = position;
}
@Override
public int compareTo(OrderedChat o) {
if (this.position.order != o.position.order) {
return o.position.order < this.position.order ? -1 : 1;
}
if (this.chatId != o.chatId) {
return o.chatId < this.chatId ? -1 : 1;
}
return 0;
}
@Override
public boolean equals(Object obj) {
OrderedChat o = (OrderedChat) obj;
return this.chatId == o.chatId && this.position.order == o.position.order;
}
}
private static class DefaultHandler implements Client.ResultHandler {
@Override
public void onResult(TdApi.Object object) {
print(object.toString());
}
}
private static class UpdateHandler implements Client.ResultHandler {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.UpdateAuthorizationState.CONSTRUCTOR:
onAuthorizationStateUpdated(((TdApi.UpdateAuthorizationState) object).authorizationState);
break;
case TdApi.UpdateUser.CONSTRUCTOR:
TdApi.UpdateUser updateUser = (TdApi.UpdateUser) object;
users.put(updateUser.user.id, updateUser.user);
break;
case TdApi.UpdateUserStatus.CONSTRUCTOR: {
TdApi.UpdateUserStatus updateUserStatus = (TdApi.UpdateUserStatus) object;
TdApi.User user = users.get(updateUserStatus.userId);
synchronized (user) {
user.status = updateUserStatus.status;
}
break;
}
case TdApi.UpdateBasicGroup.CONSTRUCTOR:
TdApi.UpdateBasicGroup updateBasicGroup = (TdApi.UpdateBasicGroup) object;
basicGroups.put(updateBasicGroup.basicGroup.id, updateBasicGroup.basicGroup);
break;
case TdApi.UpdateSupergroup.CONSTRUCTOR:
TdApi.UpdateSupergroup updateSupergroup = (TdApi.UpdateSupergroup) object;
supergroups.put(updateSupergroup.supergroup.id, updateSupergroup.supergroup);
break;
case TdApi.UpdateSecretChat.CONSTRUCTOR:
TdApi.UpdateSecretChat updateSecretChat = (TdApi.UpdateSecretChat) object;
secretChats.put(updateSecretChat.secretChat.id, updateSecretChat.secretChat);
break;
case TdApi.UpdateNewChat.CONSTRUCTOR: {
TdApi.UpdateNewChat updateNewChat = (TdApi.UpdateNewChat) object;
TdApi.Chat chat = updateNewChat.chat;
synchronized (chat) {
chats.put(chat.id, chat);
TdApi.ChatPosition[] positions = chat.positions;
chat.positions = new TdApi.ChatPosition[0];
setChatPositions(chat, positions);
}
break;
}
case TdApi.UpdateChatTitle.CONSTRUCTOR: {
TdApi.UpdateChatTitle updateChat = (TdApi.UpdateChatTitle) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.title = updateChat.title;
}
break;
}
case TdApi.UpdateChatPhoto.CONSTRUCTOR: {
TdApi.UpdateChatPhoto updateChat = (TdApi.UpdateChatPhoto) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.photo = updateChat.photo;
}
break;
}
case TdApi.UpdateChatPermissions.CONSTRUCTOR: {
TdApi.UpdateChatPermissions update = (TdApi.UpdateChatPermissions) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.permissions = update.permissions;
}
break;
}
case TdApi.UpdateChatLastMessage.CONSTRUCTOR: {
TdApi.UpdateChatLastMessage updateChat = (TdApi.UpdateChatLastMessage) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastMessage = updateChat.lastMessage;
setChatPositions(chat, updateChat.positions);
}
break;
}
case TdApi.UpdateChatPosition.CONSTRUCTOR: {
TdApi.UpdateChatPosition updateChat = (TdApi.UpdateChatPosition) object;
if (updateChat.position.list.getConstructor() != TdApi.ChatListMain.CONSTRUCTOR) {
break;
}
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
int i;
for (i = 0; i < chat.positions.length; i++) {
if (chat.positions[i].list.getConstructor() == TdApi.ChatListMain.CONSTRUCTOR) {
break;
}
}
TdApi.ChatPosition[] new_positions = new TdApi.ChatPosition[chat.positions.length + (updateChat.position.order == 0 ? 0 : 1) - (i < chat.positions.length ? 1 : 0)];
int pos = 0;
if (updateChat.position.order != 0) {
new_positions[pos++] = updateChat.position;
}
for (int j = 0; j < chat.positions.length; j++) {
if (j != i) {
new_positions[pos++] = chat.positions[j];
}
}
assert pos == new_positions.length;
setChatPositions(chat, new_positions);
}
break;
}
case TdApi.UpdateChatReadInbox.CONSTRUCTOR: {
TdApi.UpdateChatReadInbox updateChat = (TdApi.UpdateChatReadInbox) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastReadInboxMessageId = updateChat.lastReadInboxMessageId;
chat.unreadCount = updateChat.unreadCount;
}
break;
}
case TdApi.UpdateChatReadOutbox.CONSTRUCTOR: {
TdApi.UpdateChatReadOutbox updateChat = (TdApi.UpdateChatReadOutbox) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.lastReadOutboxMessageId = updateChat.lastReadOutboxMessageId;
}
break;
}
case TdApi.UpdateChatActionBar.CONSTRUCTOR: {
TdApi.UpdateChatActionBar updateChat = (TdApi.UpdateChatActionBar) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.actionBar = updateChat.actionBar;
}
break;
}
case TdApi.UpdateChatAvailableReactions.CONSTRUCTOR: {
TdApi.UpdateChatAvailableReactions updateChat = (TdApi.UpdateChatAvailableReactions) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.availableReactions = updateChat.availableReactions;
}
break;
}
case TdApi.UpdateChatDraftMessage.CONSTRUCTOR: {
TdApi.UpdateChatDraftMessage updateChat = (TdApi.UpdateChatDraftMessage) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.draftMessage = updateChat.draftMessage;
setChatPositions(chat, updateChat.positions);
}
break;
}
case TdApi.UpdateChatMessageSender.CONSTRUCTOR: {
TdApi.UpdateChatMessageSender updateChat = (TdApi.UpdateChatMessageSender) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.messageSenderId = updateChat.messageSenderId;
}
break;
}
case TdApi.UpdateChatMessageAutoDeleteTime.CONSTRUCTOR: {
TdApi.UpdateChatMessageAutoDeleteTime updateChat = (TdApi.UpdateChatMessageAutoDeleteTime) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.messageAutoDeleteTime = updateChat.messageAutoDeleteTime;
}
break;
}
case TdApi.UpdateChatNotificationSettings.CONSTRUCTOR: {
TdApi.UpdateChatNotificationSettings update = (TdApi.UpdateChatNotificationSettings) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.notificationSettings = update.notificationSettings;
}
break;
}
case TdApi.UpdateChatPendingJoinRequests.CONSTRUCTOR: {
TdApi.UpdateChatPendingJoinRequests update = (TdApi.UpdateChatPendingJoinRequests) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.pendingJoinRequests = update.pendingJoinRequests;
}
break;
}
case TdApi.UpdateChatReplyMarkup.CONSTRUCTOR: {
TdApi.UpdateChatReplyMarkup updateChat = (TdApi.UpdateChatReplyMarkup) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.replyMarkupMessageId = updateChat.replyMarkupMessageId;
}
break;
}
case TdApi.UpdateChatBackground.CONSTRUCTOR: {
TdApi.UpdateChatBackground updateChat = (TdApi.UpdateChatBackground) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.background = updateChat.background;
}
break;
}
case TdApi.UpdateChatTheme.CONSTRUCTOR: {
TdApi.UpdateChatTheme updateChat = (TdApi.UpdateChatTheme) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.themeName = updateChat.themeName;
}
break;
}
case TdApi.UpdateChatUnreadMentionCount.CONSTRUCTOR: {
TdApi.UpdateChatUnreadMentionCount updateChat = (TdApi.UpdateChatUnreadMentionCount) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadMentionCount = updateChat.unreadMentionCount;
}
break;
}
case TdApi.UpdateChatUnreadReactionCount.CONSTRUCTOR: {
TdApi.UpdateChatUnreadReactionCount updateChat = (TdApi.UpdateChatUnreadReactionCount) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadReactionCount = updateChat.unreadReactionCount;
}
break;
}
case TdApi.UpdateChatVideoChat.CONSTRUCTOR: {
TdApi.UpdateChatVideoChat updateChat = (TdApi.UpdateChatVideoChat) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.videoChat = updateChat.videoChat;
}
break;
}
case TdApi.UpdateChatDefaultDisableNotification.CONSTRUCTOR: {
TdApi.UpdateChatDefaultDisableNotification update = (TdApi.UpdateChatDefaultDisableNotification) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.defaultDisableNotification = update.defaultDisableNotification;
}
break;
}
case TdApi.UpdateChatHasProtectedContent.CONSTRUCTOR: {
TdApi.UpdateChatHasProtectedContent updateChat = (TdApi.UpdateChatHasProtectedContent) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.hasProtectedContent = updateChat.hasProtectedContent;
}
break;
}
case TdApi.UpdateChatIsTranslatable.CONSTRUCTOR: {
TdApi.UpdateChatIsTranslatable update = (TdApi.UpdateChatIsTranslatable) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.isTranslatable = update.isTranslatable;
}
break;
}
case TdApi.UpdateChatIsMarkedAsUnread.CONSTRUCTOR: {
TdApi.UpdateChatIsMarkedAsUnread update = (TdApi.UpdateChatIsMarkedAsUnread) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.isMarkedAsUnread = update.isMarkedAsUnread;
}
break;
}
case TdApi.UpdateChatBlockList.CONSTRUCTOR: {
TdApi.UpdateChatBlockList update = (TdApi.UpdateChatBlockList) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.blockList = update.blockList;
}
break;
}
case TdApi.UpdateChatHasScheduledMessages.CONSTRUCTOR: {
TdApi.UpdateChatHasScheduledMessages update = (TdApi.UpdateChatHasScheduledMessages) object;
TdApi.Chat chat = chats.get(update.chatId);
synchronized (chat) {
chat.hasScheduledMessages = update.hasScheduledMessages;
}
break;
}
case TdApi.UpdateMessageMentionRead.CONSTRUCTOR: {
TdApi.UpdateMessageMentionRead updateChat = (TdApi.UpdateMessageMentionRead) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadMentionCount = updateChat.unreadMentionCount;
}
break;
}
case TdApi.UpdateMessageUnreadReactions.CONSTRUCTOR: {
TdApi.UpdateMessageUnreadReactions updateChat = (TdApi.UpdateMessageUnreadReactions) object;
TdApi.Chat chat = chats.get(updateChat.chatId);
synchronized (chat) {
chat.unreadReactionCount = updateChat.unreadReactionCount;
}
break;
}
case TdApi.UpdateUserFullInfo.CONSTRUCTOR:
TdApi.UpdateUserFullInfo updateUserFullInfo = (TdApi.UpdateUserFullInfo) object;
usersFullInfo.put(updateUserFullInfo.userId, updateUserFullInfo.userFullInfo);
break;
case TdApi.UpdateBasicGroupFullInfo.CONSTRUCTOR:
TdApi.UpdateBasicGroupFullInfo updateBasicGroupFullInfo = (TdApi.UpdateBasicGroupFullInfo) object;
basicGroupsFullInfo.put(updateBasicGroupFullInfo.basicGroupId, updateBasicGroupFullInfo.basicGroupFullInfo);
break;
case TdApi.UpdateSupergroupFullInfo.CONSTRUCTOR:
TdApi.UpdateSupergroupFullInfo updateSupergroupFullInfo = (TdApi.UpdateSupergroupFullInfo) object;
supergroupsFullInfo.put(updateSupergroupFullInfo.supergroupId, updateSupergroupFullInfo.supergroupFullInfo);
break;
default:
// print("Unsupported update:" + newLine + object);
}
}
}
private static class AuthorizationRequestHandler implements Client.ResultHandler {
@Override
public void onResult(TdApi.Object object) {
switch (object.getConstructor()) {
case TdApi.Error.CONSTRUCTOR:
System.err.println("Receive an error:" + newLine + object);
onAuthorizationStateUpdated(null); // repeat last action
break;
case TdApi.Ok.CONSTRUCTOR:
// result is already received through UpdateAuthorizationState, nothing to do
break;
default:
System.err.println("Receive wrong response from TDLib:" + newLine + object);
}
}
}
private static class LogMessageHandler implements Client.LogMessageHandler {
@Override
public void onLogMessage(int verbosityLevel, String message) {
if (verbosityLevel == 0) {
onFatalError(message);
return;
}
System.err.println(message);
}
}
private static void onFatalError(String errorMessage) {
final class ThrowError implements Runnable {
private final String errorMessage;
private final AtomicLong errorThrowTime;
private ThrowError(String errorMessage, AtomicLong errorThrowTime) {
this.errorMessage = errorMessage;
this.errorThrowTime = errorThrowTime;
}
@Override
public void run() {
if (isDatabaseBrokenError(errorMessage) || isDiskFullError(errorMessage) || isDiskError(errorMessage)) {
processExternalError();
return;
}
errorThrowTime.set(System.currentTimeMillis());
throw new ClientError("TDLib fatal error: " + errorMessage);
}
private void processExternalError() {
errorThrowTime.set(System.currentTimeMillis());
throw new ExternalClientError("Fatal error: " + errorMessage);
}
final class ClientError extends Error {
private ClientError(String message) {
super(message);
}
}
final class ExternalClientError extends Error {
public ExternalClientError(String message) {
super(message);
}
}
private boolean isDatabaseBrokenError(String message) {
return message.contains("Wrong key or database is corrupted") ||
message.contains("SQL logic error or missing database") ||
message.contains("database disk image is malformed") ||
message.contains("file is encrypted or is not a database") ||
message.contains("unsupported file format") ||
message.contains("Database was corrupted and deleted during execution and can't be recreated");
}
private boolean isDiskFullError(String message) {
return message.contains("PosixError : No space left on device") ||
message.contains("database or disk is full");
}
private boolean isDiskError(String message) {
return message.contains("I/O error") || message.contains("Structure needs cleaning");
}
}
final AtomicLong errorThrowTime = new AtomicLong(Long.MAX_VALUE);
new Thread(new ThrowError(errorMessage, errorThrowTime), "TDLib fatal error thread").start();
// wait at least 10 seconds after the error is thrown
while (errorThrowTime.get() >= System.currentTimeMillis() - 10000) {
try {
Thread.sleep(1000 /* milliseconds */);
} catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
}
}
}
}

View File

@@ -0,0 +1,47 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
package org.drinkless.tdlib.example;
import org.drinkless.tdlib.JsonClient;
/**
* Example class for TDLib usage from Java using JSON interface.
*/
public final class JsonExample {
public static void main(String[] args) throws InterruptedException {
// set log message handler to handle only fatal errors (0) and plain log messages (-1)
JsonClient.setLogMessageHandler(0, new LogMessageHandler());
// disable TDLib log and redirect fatal errors and plain log messages to a file
JsonClient.execute("{\"@type\":\"setLogVerbosityLevel\",\"new_verbosity_level\":0}");
JsonClient.execute("{\"@type\":\"setLogStream\",\"log_stream\":{\"@type\":\"logStreamFile\",\"path\":\"tdlib.log\",\"max_file_size\":128000000}}");
// create client identifier
int clientId = JsonClient.createClientId();
// send first request to activate the client
JsonClient.send(clientId, "{\"@type\":\"getOption\",\"name\":\"version\"}");
// main loop
while (true) {
String result = JsonClient.receive(100.0);
if (result != null) {
System.out.println(result);
}
}
}
private static class LogMessageHandler implements JsonClient.LogMessageHandler {
@Override
public void onLogMessage(int verbosityLevel, String message) {
System.err.print(message);
if (verbosityLevel == 0) {
System.err.println("Receive fatal error; the process will crash now");
}
}
}
}

241
td/example/java/td_jni.cpp Normal file
View File

@@ -0,0 +1,241 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#ifdef TD_JSON_JAVA
#include <td/telegram/td_json_client.h>
#else
#include <td/telegram/Client.h>
#include <td/telegram/td_api.h>
#endif
#include <td/tl/tl_jni_object.h>
#include <cstdint>
#include <cstdlib>
#include <string>
#include <utility>
namespace td_jni {
#ifdef TD_JSON_JAVA
static jint JsonClient_createClientId(JNIEnv *env, jclass clazz) {
return static_cast<jint>(td_create_client_id());
}
static void JsonClient_send(JNIEnv *env, jclass clazz, jint client_id, jstring request) {
td_send(static_cast<int>(client_id), td::jni::from_jstring(env, request).c_str());
}
static jstring JsonClient_receive(JNIEnv *env, jclass clazz, jdouble timeout) {
auto result = td_receive(timeout);
if (result == nullptr) {
return nullptr;
}
return td::jni::to_jstring(env, result);
}
static jstring JsonClient_execute(JNIEnv *env, jclass clazz, jstring request) {
auto result = td_execute(td::jni::from_jstring(env, request).c_str());
if (result == nullptr) {
return nullptr;
}
return td::jni::to_jstring(env, result);
}
#else
static td::td_api::object_ptr<td::td_api::Function> fetch_function(JNIEnv *env, jobject function) {
td::jni::reset_parse_error();
auto result = td::td_api::Function::fetch(env, function);
if (td::jni::have_parse_error()) {
std::abort();
}
return result;
}
static td::ClientManager *get_manager() {
return td::ClientManager::get_manager_singleton();
}
static jint Client_createNativeClient(JNIEnv *env, jclass clazz) {
return static_cast<jint>(get_manager()->create_client_id());
}
static void Client_nativeClientSend(JNIEnv *env, jclass clazz, jint client_id, jlong id, jobject function) {
get_manager()->send(static_cast<std::int32_t>(client_id), static_cast<std::uint64_t>(id),
fetch_function(env, function));
}
static jint Client_nativeClientReceive(JNIEnv *env, jclass clazz, jintArray client_ids, jlongArray ids,
jobjectArray events, jdouble timeout) {
jsize events_size = env->GetArrayLength(ids); // client_ids, ids and events must be of equal size
if (events_size == 0) {
return 0;
}
jsize result_size = 0;
auto *manager = get_manager();
auto response = manager->receive(timeout);
while (response.object) {
auto client_id = static_cast<jint>(response.client_id);
env->SetIntArrayRegion(client_ids, result_size, 1, &client_id);
auto request_id = static_cast<jlong>(response.request_id);
env->SetLongArrayRegion(ids, result_size, 1, &request_id);
jobject object;
response.object->store(env, object);
env->SetObjectArrayElement(events, result_size, object);
env->DeleteLocalRef(object);
result_size++;
if (result_size == events_size) {
break;
}
response = manager->receive(0);
}
return result_size;
}
static jobject Client_nativeClientExecute(JNIEnv *env, jclass clazz, jobject function) {
jobject result;
td::ClientManager::execute(fetch_function(env, function))->store(env, result);
return result;
}
static jstring Object_toString(JNIEnv *env, jobject object) {
return td::jni::to_jstring(env, to_string(td::td_api::Object::fetch(env, object)));
}
static jstring Function_toString(JNIEnv *env, jobject object) {
return td::jni::to_jstring(env, to_string(td::td_api::Function::fetch(env, object)));
}
#endif
static constexpr jint JAVA_VERSION = JNI_VERSION_1_6;
static JavaVM *java_vm;
static jobject log_message_handler;
static void on_log_message(int verbosity_level, const char *log_message) {
auto env = td::jni::get_jni_env(java_vm, JAVA_VERSION);
if (env == nullptr) {
return;
}
jobject handler = env->NewLocalRef(log_message_handler);
if (!handler) {
return;
}
jclass handler_class = env->GetObjectClass(handler);
if (handler_class) {
jmethodID on_log_message_method = env->GetMethodID(handler_class, "onLogMessage", "(ILjava/lang/String;)V");
if (on_log_message_method) {
jstring log_message_str = td::jni::to_jstring(env.get(), log_message);
if (log_message_str) {
env->CallVoidMethod(handler, on_log_message_method, static_cast<jint>(verbosity_level), log_message_str);
env->DeleteLocalRef((jobject)log_message_str);
}
}
env->DeleteLocalRef((jobject)handler_class);
}
env->DeleteLocalRef(handler);
}
static void Client_nativeClientSetLogMessageHandler(JNIEnv *env, jclass clazz, jint max_verbosity_level,
jobject new_log_message_handler) {
if (log_message_handler) {
#ifdef TD_JSON_JAVA
td_set_log_message_callback(0, nullptr);
#else
td::ClientManager::set_log_message_callback(0, nullptr);
#endif
jobject old_log_message_handler = log_message_handler;
log_message_handler = jobject();
env->DeleteGlobalRef(old_log_message_handler);
}
if (new_log_message_handler) {
log_message_handler = env->NewGlobalRef(new_log_message_handler);
if (!log_message_handler) {
// out of memory
return;
}
#ifdef TD_JSON_JAVA
td_set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
#else
td::ClientManager::set_log_message_callback(static_cast<int>(max_verbosity_level), on_log_message);
#endif
}
}
static jint register_native(JavaVM *vm) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JAVA_VERSION) != JNI_OK) {
return -1;
}
java_vm = vm;
auto register_method = [env](jclass clazz, std::string name, std::string signature, auto function_ptr) {
td::jni::register_native_method(env, clazz, std::move(name), std::move(signature),
reinterpret_cast<void *>(function_ptr));
};
#ifdef TD_JSON_JAVA
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/JsonClient");
register_method(client_class, "createClientId", "()I", JsonClient_createClientId);
register_method(client_class, "send", "(ILjava/lang/String;)V", JsonClient_send);
register_method(client_class, "receive", "(D)Ljava/lang/String;", JsonClient_receive);
register_method(client_class, "execute", "(Ljava/lang/String;)Ljava/lang/String;", JsonClient_execute);
register_method(client_class, "setLogMessageHandler", "(IL" PACKAGE_NAME "/JsonClient$LogMessageHandler;)V",
Client_nativeClientSetLogMessageHandler);
#else
auto td_api_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi");
jfieldID commit_hash_field_id =
td::jni::get_static_field_id(env, td_api_class, "GIT_COMMIT_HASH", "Ljava/lang/String;");
std::string td_api_version = td::jni::fetch_static_string(env, td_api_class, commit_hash_field_id);
std::string tdjni_version = td::td_api::get_git_commit_hash();
if (tdjni_version != td_api_version) {
td::jni::set_fatal_error(
env, "Mismatched TdApi.java (" + td_api_version + ") and tdjni shared library (" + tdjni_version + ") versions");
return JAVA_VERSION;
}
auto client_class = td::jni::get_jclass(env, PACKAGE_NAME "/Client");
auto object_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Object");
auto function_class = td::jni::get_jclass(env, PACKAGE_NAME "/TdApi$Function");
#define TD_OBJECT "L" PACKAGE_NAME "/TdApi$Object;"
#define TD_FUNCTION "L" PACKAGE_NAME "/TdApi$Function;"
register_method(client_class, "createNativeClient", "()I", Client_createNativeClient);
register_method(client_class, "nativeClientSend", "(IJ" TD_FUNCTION ")V", Client_nativeClientSend);
register_method(client_class, "nativeClientReceive", "([I[J[" TD_OBJECT "D)I", Client_nativeClientReceive);
register_method(client_class, "nativeClientExecute", "(" TD_FUNCTION ")" TD_OBJECT, Client_nativeClientExecute);
register_method(client_class, "nativeClientSetLogMessageHandler", "(IL" PACKAGE_NAME "/Client$LogMessageHandler;)V",
Client_nativeClientSetLogMessageHandler);
register_method(object_class, "toString", "()Ljava/lang/String;", Object_toString);
register_method(function_class, "toString", "()Ljava/lang/String;", Function_toString);
#undef TD_FUNCTION
#undef TD_OBJECT
td::jni::init_vars(env, PACKAGE_NAME);
td::td_api::get_package_name_ref() = PACKAGE_NAME;
#endif
return JAVA_VERSION;
}
} // namespace td_jni
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
static jint jni_version = td_jni::register_native(vm); // call_once
return jni_version;
}

3
td/example/python/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/td/
/tdlib/
/*.dll

View File

@@ -0,0 +1,11 @@
# TDLib Python example
To run this example you need to [build](https://github.com/tdlib/td#building) TDLib and copy built tdjson shared library to this directory.
After this you can run the example:
```
python tdjson_example.py
```
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@@ -0,0 +1,145 @@
#
# Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com),
# Pellegrino Prevete (pellegrinoprevete@gmail.com) 2014-2024
#
# Distributed under the Boost Software License, Version 1.0. (See accompanying
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#
from ctypes.util import find_library
from ctypes import *
import json
import os
import sys
# load shared library
tdjson_path = find_library('tdjson')
if tdjson_path is None:
if os.name == 'nt':
tdjson_path = os.path.join(os.path.dirname(__file__), 'tdjson.dll')
else:
sys.exit("Can't find 'tdjson' library")
tdjson = CDLL(tdjson_path)
# load TDLib functions from shared library
_td_create_client_id = tdjson.td_create_client_id
_td_create_client_id.restype = c_int
_td_create_client_id.argtypes = []
_td_receive = tdjson.td_receive
_td_receive.restype = c_char_p
_td_receive.argtypes = [c_double]
_td_send = tdjson.td_send
_td_send.restype = None
_td_send.argtypes = [c_int, c_char_p]
_td_execute = tdjson.td_execute
_td_execute.restype = c_char_p
_td_execute.argtypes = [c_char_p]
log_message_callback_type = CFUNCTYPE(None, c_int, c_char_p)
_td_set_log_message_callback = tdjson.td_set_log_message_callback
_td_set_log_message_callback.restype = None
_td_set_log_message_callback.argtypes = [c_int, log_message_callback_type]
# initialize TDLib log with desired parameters
@log_message_callback_type
def on_log_message_callback(verbosity_level, message):
if verbosity_level == 0:
sys.exit('TDLib fatal error: %r' % message)
def td_execute(query):
query = json.dumps(query).encode('utf-8')
result = _td_execute(query)
if result:
result = json.loads(result.decode('utf-8'))
return result
_td_set_log_message_callback(2, on_log_message_callback)
# setting TDLib log verbosity level to 1 (errors)
print(str(td_execute({'@type': 'setLogVerbosityLevel', 'new_verbosity_level': 1, '@extra': 1.01234})).encode('utf-8'))
# create client
client_id = _td_create_client_id()
# simple wrappers for client usage
def td_send(query):
query = json.dumps(query).encode('utf-8')
_td_send(client_id, query)
def td_receive():
result = _td_receive(1.0)
if result:
result = json.loads(result.decode('utf-8'))
return result
# another test for TDLib execute method
print(str(td_execute({'@type': 'getTextEntities', 'text': '@telegram /test_command https://telegram.org telegram.me', '@extra': ['5', 7.0, 'a']})).encode('utf-8'))
# start the client by sending a request to it
td_send({'@type': 'getOption', 'name': 'version', '@extra': 1.01234})
# main events cycle
while True:
event = td_receive()
if event:
# process authorization states
if event['@type'] == 'updateAuthorizationState':
auth_state = event['authorization_state']
# if client is closed, we need to destroy it and create new client
if auth_state['@type'] == 'authorizationStateClosed':
break
# set TDLib parameters
# you MUST obtain your own api_id and api_hash at https://my.telegram.org
# and use them in the setTdlibParameters call
if auth_state['@type'] == 'authorizationStateWaitTdlibParameters':
td_send({'@type': 'setTdlibParameters',
'database_directory': 'tdlib',
'use_message_database': True,
'use_secret_chats': True,
'api_id': 94575,
'api_hash': 'a3406de8d171bb422bb6ddf3bbd800e2',
'system_language_code': 'en',
'device_model': 'Desktop',
'application_version': '1.0'})
# enter phone number to log in
if auth_state['@type'] == 'authorizationStateWaitPhoneNumber':
phone_number = input('Please enter your phone number: ')
td_send({'@type': 'setAuthenticationPhoneNumber', 'phone_number': phone_number})
# enter email address to log in
if auth_state['@type'] == 'authorizationStateWaitEmailAddress':
email_address = input('Please enter your email address: ')
td_send({'@type': 'setAuthenticationEmailAddress', 'email_address': email_address})
# wait for email authorization code
if auth_state['@type'] == 'authorizationStateWaitEmailCode':
code = input('Please enter the email authentication code you received: ')
td_send({'@type': 'checkAuthenticationEmailCode',
'code': {'@type': 'emailAddressAuthenticationCode', 'code' : code}})
# wait for authorization code
if auth_state['@type'] == 'authorizationStateWaitCode':
code = input('Please enter the authentication code you received: ')
td_send({'@type': 'checkAuthenticationCode', 'code': code})
# wait for first and last name for new users
if auth_state['@type'] == 'authorizationStateWaitRegistration':
first_name = input('Please enter your first name: ')
last_name = input('Please enter your last name: ')
td_send({'@type': 'registerUser', 'first_name': first_name, 'last_name': last_name})
# wait for password if present
if auth_state['@type'] == 'authorizationStateWaitPassword':
password = input('Please enter your password: ')
td_send({'@type': 'checkAuthenticationPassword', 'password': password})
# handle an incoming update or an answer to a previously sent request
print(str(event).encode('utf-8'))
sys.stdout.flush()

3
td/example/swift/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/xcuserdata/
/*workspace/
/td/

View File

@@ -0,0 +1,15 @@
# TDLib swift macOS example
TDLib should be prebuilt and installed to local subdirectory `td/`:
```
cd <path to TDLib sources>
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX:PATH=../example/swift/td ..
cmake --build . --target install
```
After this you can open and build the example with the latest Xcode.
Description of all available classes and methods can be found at [td_json_client](https://core.telegram.org/tdlib/docs/td__json__client_8h.html)
and [td_api](https://core.telegram.org/tdlib/docs/td__api_8h.html) documentation.

View File

@@ -0,0 +1,189 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
import Foundation
// TDLib Client Swift binding
class TdClient {
var client_id = td_create_client_id()
let tdlibMainLoop = DispatchQueue(label: "TDLib")
let tdlibQueryQueue = DispatchQueue(label: "TDLibQuery")
var queryF = Dictionary<Int64, (Dictionary<String,Any>)->()>()
var updateF: ((Dictionary<String,Any>)->())?
var queryId: Int64 = 0
func queryAsync(query: [String: Any], f: ((Dictionary<String,Any>)->())? = nil) {
tdlibQueryQueue.async {
var newQuery = query
if f != nil {
let nextQueryId = self.queryId + 1
newQuery["@extra"] = nextQueryId
self.queryF[nextQueryId] = f
self.queryId = nextQueryId
}
td_send(self.client_id, to_json(newQuery))
}
}
func querySync(query: [String: Any]) -> Dictionary<String,Any> {
let semaphore = DispatchSemaphore(value:0)
var result = Dictionary<String,Any>()
queryAsync(query: query) {
result = $0
semaphore.signal()
}
semaphore.wait()
return result
}
init() {
}
deinit {
}
func run(updateHandler: @escaping (Dictionary<String,Any>)->()) {
updateF = updateHandler
tdlibMainLoop.async { [weak self] in
while (true) {
if let s = self {
if let res = td_receive(10) {
let event = String(cString: res)
s.queryResultAsync(event)
}
} else {
break
}
}
}
}
private func queryResultAsync(_ result: String) {
tdlibQueryQueue.async {
let json = try? JSONSerialization.jsonObject(with: result.data(using: .utf8)!, options:[])
if let dictionary = json as? [String:Any] {
if let extra = dictionary["@extra"] as? Int64 {
let index = self.queryF.index(forKey: extra)!
self.queryF[index].value(dictionary)
self.queryF.remove(at: index)
} else {
self.updateF!(dictionary)
}
}
}
}
}
func to_json(_ obj: Any) -> String {
do {
let obj = try JSONSerialization.data(withJSONObject: obj)
return String(data: obj, encoding: .utf8)!
} catch {
return ""
}
}
var client = TdClient()
// start the client by sending request to it
client.queryAsync(query: ["@type":"getOption", "name":"version"])
func myReadLine() -> String {
while (true) {
if let line = readLine() {
return line
}
}
}
func updateAuthorizationState(authorizationState: Dictionary<String, Any>) {
switch(authorizationState["@type"] as! String) {
case "authorizationStateWaitTdlibParameters":
client.queryAsync(query:[
"@type":"setTdlibParameters",
"database_directory":"tdlib",
"use_message_database":true,
"use_secret_chats":true,
"api_id":94575,
"api_hash":"a3406de8d171bb422bb6ddf3bbd800e2",
"system_language_code":"en",
"device_model":"Desktop",
"application_version":"1.0"
]);
case "authorizationStateWaitPhoneNumber":
print("Enter your phone number: ")
let phone_number = myReadLine()
client.queryAsync(query:["@type":"setAuthenticationPhoneNumber", "phone_number":phone_number], f:checkAuthenticationError)
case "authorizationStateWaitEmailAddress":
print("Enter your email address: ")
let email_address = myReadLine()
client.queryAsync(query:["@type":"setAuthenticationEmailAddress", "email_address":email_address], f:checkAuthenticationError)
case "authorizationStateWaitEmailCode":
var code: String = ""
print("Enter email code: ")
code = myReadLine()
client.queryAsync(query:["@type":"checkAuthenticationEmailCode", "code":["@type":"emailAddressAuthenticationCode", "code":code]], f:checkAuthenticationError)
case "authorizationStateWaitCode":
var code: String = ""
print("Enter (SMS) code: ")
code = myReadLine()
client.queryAsync(query:["@type":"checkAuthenticationCode", "code":code], f:checkAuthenticationError)
case "authorizationStateWaitRegistration":
var first_name: String = ""
var last_name: String = ""
print("Enter your first name: ")
first_name = myReadLine()
print("Enter your last name: ")
last_name = myReadLine()
client.queryAsync(query:["@type":"registerUser", "first_name":first_name, "last_name":last_name], f:checkAuthenticationError)
case "authorizationStateWaitPassword":
print("Enter password: ")
let password = myReadLine()
client.queryAsync(query:["@type":"checkAuthenticationPassword", "password":password], f:checkAuthenticationError)
case "authorizationStateReady":
()
case "authorizationStateLoggingOut":
print("Logging out...")
case "authorizationStateClosing":
print("Closing...")
case "authorizationStateClosed":
print("Closed.")
default:
assert(false, "TODO: Unexpected authorization state");
}
}
func checkAuthenticationError(error: Dictionary<String, Any>) {
if (error["@type"] as! String == "error") {
client.queryAsync(query:["@type":"getAuthorizationState"], f:updateAuthorizationState)
}
}
client.run {
let update = $0
print(update)
if update["@type"] as! String == "updateAuthorizationState" {
updateAuthorizationState(authorizationState: update["authorization_state"] as! Dictionary<String, Any>)
}
}
while true {
sleep(1)
}

View File

@@ -0,0 +1,9 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/td_json_client.h"
#include "td/telegram/td_log.h"

View File

@@ -0,0 +1,312 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1F65E3A92031C0F000F79763 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F65E3A82031C0F000F79763 /* main.swift */; };
425922F020C8353500F06B38 /* libtdjson.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 1F65E3A32031BF6A00F79763 /* libtdjson.dylib */; };
425922F120C844E700F06B38 /* libtdjson.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1F65E3A32031BF6A00F79763 /* libtdjson.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
1FCE2CEF1EC5E1B50061661A /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 8;
dstPath = "";
dstSubfolderSpec = 11;
files = (
425922F120C844E700F06B38 /* libtdjson.dylib in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 1;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1F65E3A32031BF6A00F79763 /* libtdjson.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libtdjson.dylib; path = td/lib/libtdjson.dylib; sourceTree = "<group>"; };
1F65E3A82031C0F000F79763 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = main.swift; path = src/main.swift; sourceTree = SOURCE_ROOT; };
1F65E3AA2031C14300F79763 /* td-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "td-Bridging-Header.h"; path = "src/td-Bridging-Header.h"; sourceTree = SOURCE_ROOT; };
1FCE2CF11EC5E1B50061661A /* td */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = td; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1FCE2CEE1EC5E1B50061661A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
425922F020C8353500F06B38 /* libtdjson.dylib in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1FCE2CE81EC5E1B50061661A = {
isa = PBXGroup;
children = (
1FCE2CF31EC5E1B50061661A /* src */,
1FCE2CF21EC5E1B50061661A /* Products */,
1FCE2CFB1EC5E1EE0061661A /* Frameworks */,
);
sourceTree = "<group>";
};
1FCE2CF21EC5E1B50061661A /* Products */ = {
isa = PBXGroup;
children = (
1FCE2CF11EC5E1B50061661A /* td */,
);
name = Products;
sourceTree = "<group>";
};
1FCE2CF31EC5E1B50061661A /* src */ = {
isa = PBXGroup;
children = (
1F65E3AA2031C14300F79763 /* td-Bridging-Header.h */,
1F65E3A82031C0F000F79763 /* main.swift */,
);
name = src;
path = td;
sourceTree = "<group>";
};
1FCE2CFB1EC5E1EE0061661A /* Frameworks */ = {
isa = PBXGroup;
children = (
1F65E3A32031BF6A00F79763 /* libtdjson.dylib */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1FCE2CF01EC5E1B50061661A /* td */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1FCE2CF81EC5E1B50061661A /* Build configuration list for PBXNativeTarget "td" */;
buildPhases = (
1FCE2CED1EC5E1B50061661A /* Sources */,
1FCE2CEE1EC5E1B50061661A /* Frameworks */,
1FCE2CEF1EC5E1B50061661A /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = td;
productName = td;
productReference = 1FCE2CF11EC5E1B50061661A /* td */;
productType = "com.apple.product-type.tool";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
1FCE2CE91EC5E1B50061661A /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0920;
ORGANIZATIONNAME = "Arseny Smirnov ";
TargetAttributes = {
1FCE2CF01EC5E1B50061661A = {
CreatedOnToolsVersion = 8.3.2;
LastSwiftMigration = 0920;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 1FCE2CEC1EC5E1B50061661A /* Build configuration list for PBXProject "td" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
);
mainGroup = 1FCE2CE81EC5E1B50061661A;
productRefGroup = 1FCE2CF21EC5E1B50061661A /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1FCE2CF01EC5E1B50061661A /* td */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
1FCE2CED1EC5E1B50061661A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1F65E3A92031C0F000F79763 /* main.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1FCE2CF61EC5E1B50061661A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
};
name = Debug;
};
1FCE2CF71EC5E1B50061661A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.12;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
};
name = Release;
};
1FCE2CF91EC5E1B50061661A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/td/lib";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/td/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "src/td-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = td/include/;
};
name = Debug;
};
1FCE2CFA1EC5E1B50061661A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CLANG_ENABLE_MODULES = YES;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks $(PROJECT_DIR)/td/lib";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/td/lib",
);
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "src/td-Bridging-Header.h";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
USER_HEADER_SEARCH_PATHS = td/include/;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1FCE2CEC1EC5E1B50061661A /* Build configuration list for PBXProject "td" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1FCE2CF61EC5E1B50061661A /* Debug */,
1FCE2CF71EC5E1B50061661A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1FCE2CF81EC5E1B50061661A /* Build configuration list for PBXNativeTarget "td" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1FCE2CF91EC5E1B50061661A /* Debug */,
1FCE2CFA1EC5E1B50061661A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 1FCE2CE91EC5E1B50061661A /* Project object */;
}

View File

@@ -0,0 +1,23 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

33
td/example/uwp/README.md Normal file
View File

@@ -0,0 +1,33 @@
# TDLib Universal Windows Platform example
This is an example of building TDLib SDK for Universal Windows Platform and an example of its usage from C#.
## Building SDK
* Download and install Microsoft Visual Studio 2015+ with Windows 10 SDK. We recommend to use the latest available versions of Microsoft Visual Studio and Windows 10 SDK.
* Download and install [CMake](https://cmake.org/download/).
* Install `zlib` and `openssl` for all UWP architectures and `gperf` for x86 using [vcpkg](https://github.com/Microsoft/vcpkg#quick-start):
```
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
git checkout cd5e746ec203c8c3c61647e0886a8df8c1e78e41
.\bootstrap-vcpkg.bat
.\vcpkg.exe install gperf:x86-windows openssl:arm-uwp openssl:arm64-uwp openssl:x64-uwp openssl:x86-uwp zlib:arm-uwp zlib:arm64-uwp zlib:x64-uwp zlib:x86-uwp
```
* (Optional. For XML documentation generation.) Download [PHP](https://windows.php.net/download). Add the path to php.exe to the PATH environment variable.
* Download and install [7-Zip](http://www.7-zip.org/download.html) archiver, which is used by the `build.ps1` script to create a Telegram.Td.UWP Visual Studio Extension. Add the path to 7z.exe to the PATH environment variable.
Alternatively `build.ps1` supports compressing using [WinRAR](https://en.wikipedia.org/wiki/WinRAR) with option `-compress winrar` and compressing using [zip](http://gnuwin32.sourceforge.net/packages/zip.htm) with `-compress zip`.
* Build `TDLib` using provided `build.ps1` script (TDLib should be built 6 times for multiple platforms in Debug and Release configurations, so it make take few hours). Pass path to vcpkg.exe as `-vcpkg-root` argument, for example:
```
powershell -ExecutionPolicy ByPass .\build.ps1 -vcpkg_root C:\vcpkg
```
If you need to restart the build from scratch, call `.\build.ps1 -vcpkg_root C:\vcpkg -mode clean` first.
* Install Visual Studio Extension "TDLib for Universal Windows Platform" located at `build-uwp\vsix\tdlib.vsix`, which was created on the previous step by `build.ps1` script.
After this `TDLib` can be used from any UWP project, built in Visual Studio.
Alternatively, you can build `TDLib` as a NuGet package, adding the option `-nupkg` to the `.\build.ps1` script invocation. The resulting package will be placed in the directory `build-uwp\nupkg`.
## Example of usage
The `app/` directory contains a simple example of a C# application for Universal Windows Platform. Just open it with Visual Studio 2015 or later and run.

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?>
<FileList
DisplayName="TDLib for Universal Windows Platform"
ProductFamilyName="Telegram.Td.UWP"
MoreInfo="https://core.telegram.org/tdlib"
MinVSVersion="14.0"
AppliesTo="WindowsAppContainer"
DependsOn="Microsoft.VCLibs, version=14.0"
SupportsMultipleVersions="Error"
SupportedArchitectures="x86;x64;ARM">
<File Reference="Telegram.Td.winmd" Implementation="Telegram.Td.dll" />
</FileList>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata>
<id>Telegram.Td.UWP</id>
<version>1.8.37</version>
<title>TDLib for Universal Windows Platform</title>
<authors>Telegram</authors>
<owners>Telegram</owners>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="file">LICENSE_1_0.txt</license>
<projectUrl>https://core.telegram.org/tdlib</projectUrl>
<releaseNotes>https://github.com/tdlib/td/blob/master/CHANGELOG.md</releaseNotes>
<description>TDLib for Universal Windows Platform</description>
<copyright>© Telegram FZ-LLC. All rights reserved.</copyright>
<dependencies>
<group targetFramework="UAP10.0" />
</dependencies>
</metadata>
</package>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TelegramTdPlatform Condition="'$(Platform)' == 'Win32'">x86</TelegramTdPlatform>
<TelegramTdPlatform Condition="'$(Platform)' != 'Win32'">$(Platform)</TelegramTdPlatform>
<OpenSSLPlatform Condition="'$(Platform)' == 'Win32'"></OpenSSLPlatform>
<OpenSSLPlatform Condition="'$(Platform)' != 'Win32'">-$(Platform)</OpenSSLPlatform>
</PropertyGroup>
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'UAP'">
<Reference Include="$(MSBuildThisFileDirectory)..\..\lib\uap10.0\Telegram.Td.winmd">
<Implementation>Telegram.Td.dll</Implementation>
</Reference>
<ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win10-$(TelegramTdPlatform)\native\Telegram.Td.dll" />
<ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win10-$(TelegramTdPlatform)\native\libcrypto-3$(OpenSSLPlatform).dll" />
<ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win10-$(TelegramTdPlatform)\native\libssl-3$(OpenSSLPlatform).dll" />
<ReferenceCopyLocalPaths Include="$(MSBuildThisFileDirectory)..\..\runtimes\win10-$(TelegramTdPlatform)\native\zlib1.dll" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="winmd" ContentType="application/octet-stream" />
<Default Extension="pri" ContentType="application/octet-stream" />
<Default Extension="dll" ContentType="application/octet-stream" />
<Default Extension="h" ContentType="application/octet-stream" />
<Default Extension="lib" ContentType="application/octet-stream" />
<Default Extension="pdb" ContentType="application/octet-stream" />
<Default Extension="png" ContentType="application/octet-stream" />
<Default Extension="props" ContentType="application/octet-stream" />
<Default Extension="txt" ContentType="text/plain" />
<Default Extension="vsixmanifest" ContentType="text/xml" />
<Default Extension="xml" ContentType="text/xml" />
</Types>

5
td/example/uwp/app/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/.vs/
/bin/
/obj/
/project.lock.json
/TdApp.csproj.user

View File

@@ -0,0 +1,7 @@
<Application
x:Class="TdApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TdApp"
RequestedTheme="Light">
</Application>

View File

@@ -0,0 +1,104 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace TdApp
{
/// <summary>
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App : Application
{
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
/// </summary>
public App()
{
Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync(
Microsoft.ApplicationInsights.WindowsCollectors.Metadata |
Microsoft.ApplicationInsights.WindowsCollectors.Session);
this.InitializeComponent();
this.Suspending += OnSuspending;
}
/// <summary>
/// Invoked when the application is launched normally by the end user. Other entry points
/// will be used such as when the application is launched to open a specific file.
/// </summary>
/// <param name="e">Details about the launch request and process.</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
#if DEBUG
if (System.Diagnostics.Debugger.IsAttached)
{
this.DebugSettings.EnableFrameRateCounter = true;
}
#endif
Frame rootFrame = Window.Current.Content as Frame;
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == null)
{
// Create a Frame to act as the navigation context and navigate to the first page
rootFrame = new Frame();
rootFrame.NavigationFailed += OnNavigationFailed;
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
//TODO: Load state from previously suspended application
}
// Place the frame in the current Window
Window.Current.Content = rootFrame;
}
if (rootFrame.Content == null)
{
// When the navigation stack isn't restored navigate to the first page,
// configuring the new page by passing required information as a navigation
// parameter
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
// Ensure the current window is active
Window.Current.Activate();
}
/// <summary>
/// Invoked when Navigation to a certain page fails
/// </summary>
/// <param name="sender">The Frame which failed navigation</param>
/// <param name="e">Details about the navigation failure</param>
void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception("Failed to load Page " + e.SourcePageType.FullName);
}
/// <summary>
/// Invoked when application execution is being suspended. Application state is saved
/// without knowing whether the application will be terminated or resumed with the contents
/// of memory still intact.
/// </summary>
/// <param name="sender">The source of the suspend request.</param>
/// <param name="e">Details about the suspend request.</param>
private void OnSuspending(object sender, SuspendingEventArgs e)
{
var deferral = e.SuspendingOperation.GetDeferral();
//TODO: Save application state and stop any background activity
deferral.Complete();
}
}
}

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<ApplicationInsights xmlns = "http://schemas.microsoft.com/ApplicationInsights/2013/Settings">
</ApplicationInsights>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,29 @@
<Page
x:Class="TdApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TdApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Name="Self">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="Input"/>
<Button Grid.Column="1" x:Name="Send" Content="send" Click="Button_Click"/>
</Grid>
<ListBox Grid.Row="1" x:Name="ItemsControl" ItemsSource="{Binding Items, ElementName=Self}">
</ListBox>
<!--<Button Content="Test" Click="Button_Click"/>-->
</Grid>
</Page>

View File

@@ -0,0 +1,186 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
using System;
using System.IO;
using Td = Telegram.Td;
using TdApi = Telegram.Td.Api;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace TdApp
{
public sealed partial class MainPage : Page
{
public System.Collections.ObjectModel.ObservableCollection<string> Items { get; set; }
private MyClientResultHandler _handler;
public MainPage()
{
InitializeComponent();
Items = new System.Collections.ObjectModel.ObservableCollection<string>();
_handler = new MyClientResultHandler(this);
Td.Client.Execute(new TdApi.SetLogVerbosityLevel(0));
Td.Client.Execute(new TdApi.SetLogStream(new TdApi.LogStreamFile(Path.Combine(Windows.Storage.ApplicationData.Current.LocalFolder.Path, "log"), 1 << 27, false)));
Td.Client.SetLogMessageCallback(100, LogMessageCallback);
System.Threading.Tasks.Task.Run(() =>
{
Td.Client.Run();
});
_client = Td.Client.Create(_handler);
var request = new TdApi.SetTdlibParameters();
request.DatabaseDirectory = Windows.Storage.ApplicationData.Current.LocalFolder.Path;
request.UseSecretChats = true;
request.UseMessageDatabase = true;
request.ApiId = 94575;
request.ApiHash = "a3406de8d171bb422bb6ddf3bbd800e2";
request.SystemLanguageCode = "en";
request.DeviceModel = "Desktop";
request.ApplicationVersion = "1.0.0";
_client.Send(request, null);
}
public void Print(String str)
{
var delayTask = Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
Items.Insert(0, str.Substring(0, Math.Min(1024, str.Length)));
});
}
private void LogMessageCallback(int verbosity_level, String str)
{
if (verbosity_level < 0) {
return;
}
Print(verbosity_level + ": " + str);
}
private Td.Client _client;
private void AcceptCommand(String command)
{
Input.Text = string.Empty;
Items.Insert(0, string.Format(">>{0}", command));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var command = Input.Text;
if (command.StartsWith("DESTROY"))
{
AcceptCommand("Destroy");
_client.Send(new TdApi.Destroy(), _handler);
}
else if (command.StartsWith("lo"))
{
AcceptCommand("LogOut");
_client.Send(new TdApi.LogOut(), _handler);
}
else if (command.StartsWith("sap"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.SetAuthenticationPhoneNumber(args[1], null), _handler);
}
else if (command.StartsWith("sae"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.SetAuthenticationEmailAddress(args[1]), _handler);
}
else if (command.StartsWith("caec"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.CheckAuthenticationEmailCode(new TdApi.EmailAddressAuthenticationCode(args[1])), _handler);
}
else if (command.StartsWith("cac"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.CheckAuthenticationCode(args[1]), _handler);
}
else if (command.StartsWith("cap"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.CheckAuthenticationPassword(args[1]), _handler);
}
else if (command.StartsWith("alm"))
{
var args = command.Split(" ".ToCharArray(), 3);
AcceptCommand(command);
_client.Send(new TdApi.AddLogMessage(Int32.Parse(args[1]), args[2]), _handler);
}
else if (command.StartsWith("gco"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.SearchContacts(), _handler);
}
else if (command.StartsWith("df"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
_client.Send(new TdApi.DownloadFile(Int32.Parse(args[1]), 1, 0, 0, false), _handler);
}
else if (command.StartsWith("bench"))
{
var args = command.Split(" ".ToCharArray(), 2);
AcceptCommand(command);
var cnt = Int32.Parse(args[1]);
var handler = new BenchSimpleHandler(this, cnt);
for (int i = 0; i < cnt; i++)
{
_client.Send(new TdApi.TestSquareInt(123), handler);
}
}
}
}
class MyClientResultHandler : Td.ClientResultHandler
{
private MainPage _page;
public MyClientResultHandler(MainPage page)
{
_page = page;
}
public void OnResult(TdApi.BaseObject obj)
{
var str = obj.ToString();
_page.Print(str);
}
}
class BenchSimpleHandler : Td.ClientResultHandler
{
private MainPage _page;
private int _cnt;
public BenchSimpleHandler(MainPage page, int cnt)
{
_page = page;
_cnt = cnt;
}
public void OnResult(TdApi.BaseObject obj)
{
_cnt--;
if (_cnt == 0)
{
_page.Print("DONE");
}
}
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" IgnorableNamespaces="uap mp">
<Identity Name="1c9ba6de-23cf-4aef-803b-91fcc451b69a" Publisher="CN=arseny30" Version="1.0.0.0" />
<mp:PhoneIdentity PhoneProductId="1c9ba6de-23cf-4aef-803b-91fcc451b69a" PhonePublisherId="00000000-0000-0000-0000-000000000000" />
<Properties>
<DisplayName>TdApp</DisplayName>
<PublisherDisplayName>arseny30</PublisherDisplayName>
<Logo>Assets\StoreLogo.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.0.0" MaxVersionTested="10.0.0.0" />
</Dependencies>
<Resources>
<Resource Language="x-generate" />
</Resources>
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="TdApp.App">
<uap:VisualElements DisplayName="TdApp" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png" Description="TdApp" BackgroundColor="transparent">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png">
</uap:DefaultTile>
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name="internetClient" />
</Capabilities>
</Package>

View File

@@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("App2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("App2")]
[assembly: AssemblyCopyright("Copyright © 2015-2024")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: ComVisible(false)]

View File

@@ -0,0 +1,31 @@
<!--
This file contains Runtime Directives used by .NET Native. The defaults here are suitable for most
developers. However, you can modify these parameters to modify the behavior of the .NET Native
optimizer.
Runtime Directives are documented at http://go.microsoft.com/fwlink/?LinkID=391919
To fully enable reflection for App1.MyClass and all of its public/private members
<Type Name="App1.MyClass" Dynamic="Required All"/>
To enable dynamic creation of the specific instantiation of AppClass<T> over System.Int32
<TypeInstantiation Name="App1.AppClass" Arguments="System.Int32" Activate="Required Public" />
Using the Namespace directive to apply reflection policy to all the types in a particular namespace
<Namespace Name="DataClasses.ViewModels" Seralize="All" />
-->
<Directives xmlns="http://schemas.microsoft.com/netfx/2013/01/metadata">
<Application>
<!--
An Assembly element with Name="*Application*" applies to all assemblies in
the application package. The asterisks are not wildcards.
-->
<Assembly Name="*Application*" Dynamic="Required All" />
<!-- Add your application specific runtime directives here. -->
</Application>
</Directives>

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<ProjectGuid>{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}</ProjectGuid>
<OutputType>AppContainerExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TdApp</RootNamespace>
<AssemblyName>TdApp</AssemblyName>
<DefaultLanguage>en-US</DefaultLanguage>
<TargetPlatformIdentifier>UAP</TargetPlatformIdentifier>
<TargetPlatformVersion>10.0.10586.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.10240.0</TargetPlatformMinVersion>
<MinimumVisualStudioVersion>14</MinimumVisualStudioVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<PackageCertificateKeyFile>TdApp_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM'">
<OutputPath>bin\ARM\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>ARM</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<NoWarn>;2008</NoWarn>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE;NETFX_CORE;WINDOWS_UWP</DefineConstants>
<Optimize>true</Optimize>
<NoWarn>;2008</NoWarn>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<UseVSHostingProcess>false</UseVSHostingProcess>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<UseDotNetNativeToolchain>true</UseDotNetNativeToolchain>
</PropertyGroup>
<ItemGroup>
<!-- A reference to the entire .NET Framework and Windows SDK are automatically included -->
<Content Include="ApplicationInsights.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
<None Include="TdApp_TemporaryKey.pfx" />
</ItemGroup>
<ItemGroup>
<Content Include="Properties\Default.rd.xml" />
<Content Include="Assets\LockScreenLogo.scale-200.png" />
<Content Include="Assets\SplashScreen.scale-200.png" />
<Content Include="Assets\Square150x150Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.scale-200.png" />
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Assets\StoreLogo.png" />
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
</ItemGroup>
<ItemGroup>
<ApplicationDefinition Include="App.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainPage.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup>
<SDKReference Include="Microsoft.VCLibs, Version=14.0">
<Name>Visual C++ 2015 Runtime for Universal Windows Platform Apps</Name>
</SDKReference>
<SDKReference Include="Telegram.Td.UWP, Version=1.0">
<Name>TDLib for Universal Windows Platform</Name>
</SDKReference>
</ItemGroup>
<PropertyGroup Condition=" '$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '14.0' ">
<VisualStudioVersion>14.0</VisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\WindowsXaml\v$(VisualStudioVersion)\Microsoft.Windows.UI.Xaml.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,40 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.24720.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TdApp", "TdApp.csproj", "{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|ARM.ActiveCfg = Debug|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|ARM.Build.0 = Debug|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|ARM.Deploy.0 = Debug|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x64.ActiveCfg = Debug|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x64.Build.0 = Debug|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x64.Deploy.0 = Debug|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x86.ActiveCfg = Debug|x86
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x86.Build.0 = Debug|x86
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Debug|x86.Deploy.0 = Debug|x86
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|ARM.ActiveCfg = Release|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|ARM.Build.0 = Release|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|ARM.Deploy.0 = Release|ARM
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x64.ActiveCfg = Release|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x64.Build.0 = Release|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x64.Deploy.0 = Release|x64
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x86.ActiveCfg = Release|x86
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x86.Build.0 = Release|x86
{0B971A4C-EC00-4FED-BCC2-FCD03B78D644}.Release|x86.Deploy.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

Binary file not shown.

View File

@@ -0,0 +1,19 @@
{
"dependencies": {
"Microsoft.ApplicationInsights": "1.0.0",
"Microsoft.ApplicationInsights.PersistenceChannel": "1.0.0",
"Microsoft.ApplicationInsights.WindowsApps": "1.0.0",
"Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0"
},
"frameworks": {
"uap10.0": {}
},
"runtimes": {
"win10-arm": {},
"win10-arm-aot": {},
"win10-x86": {},
"win10-x86-aot": {},
"win10-x64": {},
"win10-x64-aot": {}
}
}

174
td/example/uwp/build.ps1 Normal file
View File

@@ -0,0 +1,174 @@
param (
[string]$vcpkg_root = $(throw "-vcpkg_root=<path to vcpkg> is required"),
[string]$arch = "",
[string]$mode = "all",
[string]$compress = "7z",
[switch]$release_only = $false,
[switch]$nupkg = $false
)
$ErrorActionPreference = "Stop"
$vcpkg_root = Resolve-Path $vcpkg_root
$vcpkg_cmake="${vcpkg_root}\scripts\buildsystems\vcpkg.cmake"
$arch_list = @( "x86", "x64", "ARM" )
if ($arch) {
$arch_list = @(, $arch)
}
$config_list = @( "Debug", "Release" )
if ($release_only -or $nupkg) {
$config_list = @(, "RelWithDebInfo")
}
$targets = @{ Debug = "Debug"; Release = "Retail"; RelWithDebInfo = "CommonConfiguration"}
$td_root = Resolve-Path "../.."
function CheckLastExitCode {
if ($LastExitCode -ne 0) {
$msg = @"
EXE RETURNED EXIT CODE $LastExitCode
CALLSTACK:$(Get-PSCallStack | Out-String)
"@
throw $msg
}
}
function clean {
Remove-Item build-* -Force -Recurse -ErrorAction SilentlyContinue
}
function prepare {
New-Item -ItemType Directory -Force -Path build-native
cd build-native
cmake -A Win32 -DTD_GENERATE_SOURCE_FILES=ON -DTD_ENABLE_DOTNET=ON "$td_root"
CheckLastExitCode
cmake --build .
CheckLastExitCode
cd ..
}
function config {
New-Item -ItemType Directory -Force -Path build-uwp
cd build-uwp
ForEach ($arch in $arch_list) {
echo "Config Arch = [$arch]"
New-Item -ItemType Directory -Force -Path $arch
cd $arch
echo "${td_root}"
$fixed_arch = $arch
if ($arch -eq "x86") {
$fixed_arch = "win32"
}
cmake -A $fixed_arch -DCMAKE_SYSTEM_VERSION="10.0" -DCMAKE_SYSTEM_NAME="WindowsStore" -DCMAKE_TOOLCHAIN_FILE="$vcpkg_cmake" -DTD_ENABLE_DOTNET=ON "$td_root"
CheckLastExitCode
cd ..
}
echo "done"
cd ..
}
function build {
cd build-uwp
ForEach ($arch in $arch_list) {
echo "Build Arch = [$arch]"
cd $arch
ForEach ($config in $config_list) {
cmake --build . --config $config --target tddotnet
CheckLastExitCode
}
cd ..
}
cd ..
}
function export-vsix {
cd build-uwp
Remove-Item vsix -Force -Recurse -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path vsix
cp ../SDKManifest.xml vsix
cp ../extension.vsixmanifest vsix
cp '../`[Content_Types`].xml' vsix
cp ../LICENSE_1_0.txt vsix
ForEach ($arch in $arch_list) {
New-Item -ItemType Directory -Force -Path vsix/References/CommonConfiguration/${arch}
ForEach ($config in $config_list) {
$target = $targets[$config]
New-Item -ItemType Directory -Force -Path vsix/DesignTime/${target}/${arch}
cp ${arch}/${config}/Telegram.Td.lib vsix/DesignTime/${target}/${arch}/
New-Item -ItemType Directory -Force -Path vsix/Redist/${target}/${arch}
cp ${arch}/${config}/* -include "SSLEAY*","LIBEAY*","libcrypto*","libssl*","zlib*","Telegram.Td.pdb","Telegram.Td.dll" vsix/Redist/${target}/${arch}/
cp ${arch}/${config}/* -include "Telegram.Td.pri","Telegram.Td.winmd","Telegram.Td.xml" vsix/References/CommonConfiguration/${arch}/
}
}
cd vsix
if ($compress -eq "zip") {
zip -r tdlib.vsix *
} elseif ($compress -eq "winrar") {
WinRAR.exe a -afzip -r -ep1 tdlib.vsix *
} else {
7z.exe a -tzip -r tdlib.vsix *
}
cd ..
}
function export-nupkg {
cd build-uwp
Remove-Item nupkg -Force -Recurse -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force -Path nupkg/build/native
cp ../LICENSE_1_0.txt nupkg
cp ../Telegram.Td.UWP.nuspec nupkg
cp ../Telegram.Td.UWP.targets nupkg/build/native
ForEach ($arch in $arch_list) {
New-Item -ItemType Directory -Force -Path nupkg/runtimes/win10-${arch}/native
New-Item -ItemType Directory -Force -Path nupkg/lib/uap10.0
ForEach ($config in $config_list) {
cp ${arch}/${config}/* -include "SSLEAY*","LIBEAY*","libcrypto*","libssl*","zlib*","Telegram.Td.pdb","Telegram.Td.pri","Telegram.Td.dll" nupkg/runtimes/win10-${arch}/native
cp ${arch}/${config}/* -include "Telegram.Td.winmd","Telegram.Td.xml" nupkg/lib/uap10.0
}
}
cd nupkg
nuget pack Telegram.Td.UWP.nuspec
cd ..
}
function run {
Push-Location
Try {
if ($mode -eq "clean") {
clean
}
if (($mode -eq "prepare") -or ($mode -eq "all")) {
prepare
}
if (($mode -eq "config") -or ( $mode -eq "all")) {
config
}
if (($mode -eq "build") -or ($mode -eq "all")) {
build
}
if (($mode -eq "export") -or ($mode -eq "all")) {
if ($nupkg) {
export-nupkg
} else {
export-vsix
}
}
} Finally {
Pop-Location
}
}
run

View File

@@ -0,0 +1,17 @@
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011">
<Metadata>
<Identity Id="Telegram.Td.UWP" Version="1.8.37" Language="en-US" Publisher="Telegram LLC" />
<DisplayName>TDLib for Universal Windows Platform</DisplayName>
<Description>TDLib is a library for building Telegram clients</Description>
<MoreInfo>https://core.telegram.org/tdlib</MoreInfo>
<Tags>Telegram, TDLib, library, client, API</Tags>
<License>LICENSE_1_0.txt</License>
<ReleaseNotes>https://github.com/tdlib/td/blob/master/CHANGELOG.md</ReleaseNotes>
</Metadata>
<Installation Scope="Global">
<InstallationTarget Id="Microsoft.ExtensionSDK" TargetPlatformIdentifier="UAP" TargetPlatformVersion="v0.8.0.0" SdkName="Telegram.Td.UWP" SdkVersion="1.0" />
</Installation>
<Assets>
<Asset Type="Microsoft.ExtensionSDK" Path="SDKManifest.xml" />
</Assets>
</PackageManifest>

5
td/example/web/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
/OpenSSL_*.tar.gz
/openssl-OpenSSL_*/
/tdweb/dist/
/tdweb/node_modules/
/tdweb/src/prebuilt/

29
td/example/web/README.md Normal file
View File

@@ -0,0 +1,29 @@
# TDLib Web example
This is an example of building `TDLib` for browsers using [Emscripten](https://github.com/kripken/emscripten).
These scripts build `TDLib` and create an [NPM](https://www.npmjs.com/) package [tdweb](https://www.npmjs.com/package/tdweb).
You need a Unix shell with `sed`, `tar` and `wget` utilities to run the provided scripts.
## Building tdweb NPM package
* Install the 3.1.1 [emsdk](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html), which is known to work. Do not use the system-provided `emscripten` package, because it contains a too old emsdk version.
* Install all `TDLib` build dependencies described in [Building](https://github.com/tdlib/td#building) and `sed`, `tar` and `wget` utilities.
* Run `source ./emsdk_env.sh` from `emsdk` directory to set up the correct build environment.
* On `macOS`, install the `coreutils` [Homebrew](https://brew.sh) package and replace `realpath` in scripts with `grealpath`:
```
brew install coreutils
sed -i.bak 's/[(]realpath/(grealpath/g' build-tdlib.sh
```
* Run build scripts in the following order:
```
cd <path to TDLib sources>/example/web
./build-openssl.sh
./build-tdlib.sh
./copy-tdlib.sh
./build-tdweb.sh
```
* The built package is now located in the `tdweb` directory.
## Using tdweb NPM package
See [tdweb](https://www.npmjs.com/package/tdweb) or [README.md](https://github.com/tdlib/td/tree/master/example/web/tdweb/README.md) for package documentation.

28
td/example/web/build-openssl.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
cd $(dirname $0)
emconfigure true 2> /dev/null || { echo 'emconfigure not found. Install emsdk and add emconfigure and emmake to PATH environment variable. See instruction at https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html. Do not forget to add `emconfigure` and `emmake` to the PATH environment variable via `emsdk/emsdk_env.sh` script.'; exit 1; }
OPENSSL=OpenSSL_1_1_0l
if [ ! -f $OPENSSL.tar.gz ]; then
echo "Downloading OpenSSL sources..."
curl -sfLO https://github.com/openssl/openssl/archive/$OPENSSL.tar.gz
fi
rm -rf ./openssl-$OPENSSL
echo "Unpacking OpenSSL sources..."
tar xzf $OPENSSL.tar.gz || exit 1
cd openssl-$OPENSSL
emconfigure ./Configure linux-generic32 no-shared no-threads no-dso no-engine no-unit-test no-ui || exit 1
sed -i.bak 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile || exit 1
sed -i.bak 's/-ldl //g' Makefile || exit 1
sed -i.bak 's/-O3/-Os/g' Makefile || exit 1
echo "Building OpenSSL..."
emmake make depend || exit 1
emmake make -j 4 || exit 1
rm -rf ../build/crypto || exit 1
mkdir -p ../build/crypto/lib || exit 1
cp libcrypto.a libssl.a ../build/crypto/lib/ || exit 1
cp -r include ../build/crypto/ || exit 1
cd ..

44
td/example/web/build-tdlib.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/sh
cd $(dirname $0)
emcmake true 2> /dev/null || { echo 'emcmake not found. Install emsdk and add emcmake and emmake to PATH environment variable. See instruction at https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html. Do not forget to add `emcmake` and `emmake` to the PATH environment variable via `emsdk/emsdk_env.sh` script.'; exit 1; }
rm -rf build/generate
rm -rf build/asmjs
rm -rf build/wasm
mkdir -p build/generate
mkdir -p build/asmjs
mkdir -p build/wasm
TD_ROOT=$(realpath ../../)
OPENSSL_ROOT=$(realpath ./build/crypto/)
OPENSSL_CRYPTO_LIBRARY=$OPENSSL_ROOT/lib/libcrypto.a
OPENSSL_SSL_LIBRARY=$OPENSSL_ROOT/lib/libssl.a
OPENSSL_OPTIONS="-DOPENSSL_FOUND=1 \
-DOPENSSL_ROOT_DIR=\"$OPENSSL_ROOT\" \
-DOPENSSL_INCLUDE_DIR=\"$OPENSSL_ROOT/include\" \
-DOPENSSL_CRYPTO_LIBRARY=\"$OPENSSL_CRYPTO_LIBRARY\" \
-DOPENSSL_SSL_LIBRARY=\"$OPENSSL_SSL_LIBRARY\" \
-DOPENSSL_LIBRARIES=\"$OPENSSL_SSL_LIBRARY;$OPENSSL_CRYPTO_LIBRARY\" \
-DOPENSSL_VERSION=\"1.1.0l\""
cd build/generate
cmake -DTD_GENERATE_SOURCE_FILES=ON $TD_ROOT || exit 1
cd ../..
cd build/wasm
eval emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel $OPENSSL_OPTIONS $TD_ROOT || exit 1
cd ../..
cd build/asmjs
eval emcmake cmake -DCMAKE_BUILD_TYPE=MinSizeRel $OPENSSL_OPTIONS -DASMJS=1 $TD_ROOT || exit 1
cd ../..
echo "Generating TDLib autogenerated source files..."
cmake --build build/generate || exit 1
echo "Building TDLib to WebAssembly..."
cmake --build build/wasm --target td_wasm || exit 1
echo "Building TDLib to asm.js..."
cmake --build build/asmjs --target td_asmjs || exit 1

7
td/example/web/build-tdweb.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
cd $(dirname $0)
cd tdweb || exit 1
npm install --no-save || exit 1
npm run build || exit 1
cd ..

7
td/example/web/copy-tdlib.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
cd $(dirname $0)
DEST=tdweb/src/prebuilt/release/
mkdir -p $DEST || exit 1
cp build/wasm/td_wasm.js build/wasm/td_wasm.wasm $DEST || exit 1
cp build/asmjs/td_asmjs.js build/asmjs/td_asmjs.js.mem $DEST || exit 1

View File

@@ -0,0 +1,29 @@
## tdweb - TDLib in a browser
[TDLib](https://github.com/tdlib/td) is a library for building Telegram clients. tdweb is a convenient wrapper for TDLib in a browser which controls TDLib instance creation,
handles interaction with TDLib and manages a filesystem for persistent TDLib data.
For interaction with TDLib, you need to create an instance of the class `TdClient`, providing a handler for incoming updates and other options if needed.
Once this is done, you can send queries to the TDLib instance using the method `TdClient.send` which returns a Promise object representing the result of the query.
See [Getting Started](https://core.telegram.org/tdlib/getting-started) for a description of basic TDLib concepts and a short introduction to TDLib usage.
See the [td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme or
the automatically generated [HTML documentation](https://core.telegram.org/tdlib/docs/td__api_8h.html) for a list of all available
TDLib [methods](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html) and [classes](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html).
The JSON representation of TDLib API objects is straightforward: all API objects are represented as JSON objects with the same keys as the API object field names in the
[td_api.tl](https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl) scheme. Note that in the automatically generated C++ documentation all fields have an additional terminating underscore
which shouldn't be used in the JSON interface. The object type name is stored in the special field '@type' which is optional in places where type is uniquely determined by the context.
Fields of Bool type are stored as Boolean, fields of int32, int53, and double types are stored as Number, fields of int64 and string types are stored as String,
fields of bytes type are base64 encoded and then stored as String, fields of array type are stored as Array.
You can also add the field '@extra' to any query to TDLib and the response will contain the field '@extra' with exactly the same value.
## Installation
As usual, add npm tdweb package into your project:
```
npm install tdweb
```
All files will be installed into `node_modules/tdweb/dist/` folder. For now, it is your responsibility to make
those files loadable from your server. For example, [telegram-react](https://github.com/evgeny-nadymov/telegram-react)
manually copies these files into the `public` folder. If you know how to avoid this problem, please tell us.

7681
td/example/web/tdweb/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,102 @@
{
"name": "tdweb",
"version": "1.8.1",
"description": "JavaScript interface for TDLib (Telegram library)",
"main": "dist/tdweb.js",
"repository": {
"type": "git",
"url": "https://github.com/tdlib/td.git",
"directory": "example/web/tdweb"
},
"files": [
"dist"
],
"scripts": {
"build": "webpack --mode production",
"start": "webpack-dev-server --open"
},
"keywords": [
"telegram"
],
"author": "Arseny Smirnov",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.4.3",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.4.3",
"@babel/preset-env": "^7.4.3",
"@typescript-eslint/eslint-plugin": "^1.7.0",
"acorn": "^6.4.1",
"babel-eslint": "^10.0.1",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^2.0.1",
"eslint": "^5.16.0",
"eslint-config-react-app": "^4.0.0",
"eslint-loader": "^2.1.2",
"eslint-plugin-flowtype": "^2.0.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-hooks": "^1.6.0",
"file-loader": "^3.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.5",
"prettier": "^1.17.0",
"typescript": "^3.4.5",
"webpack": "^4.44.1",
"webpack-cli": "^3.3.12",
"worker-loader": "^2.0.0"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"linters": {
"webpack.config.json": [
"prettier --single-quote --write",
"git add"
],
"package.json": [
"prettier --single-quote --write",
"git add"
],
"src/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
}
},
"dependencies": {
"@babel/runtime": "^7.4.3",
"broadcast-channel": "^2.1.12",
"localforage": "^1.7.3",
"uuid": "^3.3.2"
},
"babel": {
"presets": [
"@babel/env"
],
"plugins": [
"@babel/syntax-dynamic-import",
"@babel/transform-runtime"
]
},
"eslintConfig": {
"extends": "eslint-config-react-app",
"env": {
"worker": true,
"node": true,
"browser": true
},
"globals": {
"WebAssembly": true
},
"settings": {
"react": {
"version": "999.999.999"
}
}
}
}

View File

@@ -0,0 +1,680 @@
import MyWorker from './worker.js';
//import localforage from 'localforage';
import BroadcastChannel from 'broadcast-channel';
import uuid4 from 'uuid/v4';
import log from './logger.js';
const sleep = ms => new Promise(res => setTimeout(res, ms));
/**
* TDLib in a browser
*
* TDLib can be compiled to WebAssembly or asm.js using Emscripten compiler and used in a browser from JavaScript.
* This is a convenient wrapper for TDLib in a browser which controls TDLib instance creation, handles interaction
* with TDLib and manages a filesystem for persistent TDLib data.
* TDLib instance is created in a Web Worker to run it in a separate thread.
* TdClient just sends queries to the Web Worker and receives updates and results from it.
* <br>
* <br>
* Differences from the TDLib JSON API:<br>
* 1. Added the update <code>updateFatalError error:string = Update;</code> which is sent whenever TDLib encounters a fatal error.<br>
* 2. Added the method <code>setJsLogVerbosityLevel new_verbosity_level:string = Ok;</code>, which allows to change the verbosity level of tdweb logging.<br>
* 3. Added the possibility to use blobs as input files via the constructor <code>inputFileBlob data:<JavaScript blob> = InputFile;</code>.<br>
* 4. The class <code>filePart</code> contains data as a JavaScript blob instead of a base64-encoded string.<br>
* 5. The methods <code>getStorageStatistics</code>, <code>getStorageStatisticsFast</code>, <code>optimizeStorage</code>, and <code>addProxy</code> are not supported.<br>
* <br>
*/
class TdClient {
/**
* @callback TdClient~updateCallback
* @param {Object} update The update.
*/
/**
* Create TdClient.
* @param {Object} options - Options for TDLib instance creation.
* @param {TdClient~updateCallback} options.onUpdate - Callback for all incoming updates.
* @param {string} [options.instanceName=tdlib] - Name of the TDLib instance. Currently, only one instance of TdClient with a given name is allowed. All but one instances with the same name will be automatically closed. Usually, the newest non-background instance is kept alive. Files will be stored in an IndexedDb table with the same name.
* @param {boolean} [options.isBackground=false] - Pass true if the instance is opened from the background.
* @param {string} [options.jsLogVerbosityLevel=info] - The initial verbosity level of the JavaScript part of the code (one of 'error', 'warning', 'info', 'log', 'debug').
* @param {number} [options.logVerbosityLevel=2] - The initial verbosity level for the TDLib internal logging (0-1023).
* @param {boolean} [options.useDatabase=true] - Pass false to use TDLib without database and secret chats. It significantly improves loading time, but some functionality is unavailable without the database.
* @param {boolean} [options.readOnly=false] - For debug only. Pass true to open TDLib database in read-only mode
* @param {string} [options.mode=auto] - For debug only. The type of the TDLib build to use. 'asmjs' for asm.js and 'wasm' for WebAssembly. If mode == 'auto' WebAbassembly is used if supported by browser; otherwise, asm.js is used.
*/
constructor(options) {
log.setVerbosity(options.jsLogVerbosityLevel);
this.worker = new MyWorker();
this.worker.onmessage = e => {
this.onResponse(e.data);
};
this.query_id = 0;
this.query_callbacks = new Map();
if ('onUpdate' in options) {
this.onUpdate = options.onUpdate;
delete options.onUpdate;
}
options.instanceName = options.instanceName || 'tdlib';
this.fileManager = new FileManager(options.instanceName, this);
this.worker.postMessage({ '@type': 'init', options: options });
this.closeOtherClients(options);
}
/**
* Send a query to TDLib.
*
* If the query contains the field '@extra', the same field will be added into the result.
*
* @param {Object} query - The query for TDLib. See the [td_api.tl]{@link https://github.com/tdlib/td/blob/master/td/generate/scheme/td_api.tl} scheme or
* the automatically generated [HTML documentation]{@link https://core.telegram.org/tdlib/docs/td__api_8h.html}
* for a list of all available TDLib [methods]{@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_function.html} and
* [classes]{@link https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1_object.html}.
* @returns {Promise} Promise object represents the result of the query.
*/
send(query) {
return this.doSend(query, true);
}
/** @private */
sendInternal(query) {
return this.doSend(query, false);
}
/** @private */
doSend(query, isExternal) {
this.query_id++;
if (query['@extra']) {
query['@extra'] = {
'@old_extra': JSON.parse(JSON.stringify(query['@extra'])),
query_id: this.query_id
};
} else {
query['@extra'] = {
query_id: this.query_id
};
}
if (query['@type'] === 'setJsLogVerbosityLevel') {
log.setVerbosity(query.new_verbosity_level);
}
log.debug('send to worker: ', query);
const res = new Promise((resolve, reject) => {
this.query_callbacks.set(this.query_id, [resolve, reject]);
});
if (isExternal) {
this.externalPostMessage(query);
} else {
this.worker.postMessage(query);
}
return res;
}
/** @private */
externalPostMessage(query) {
const unsupportedMethods = [
'getStorageStatistics',
'getStorageStatisticsFast',
'optimizeStorage',
'addProxy',
'init',
'start'
];
if (unsupportedMethods.includes(query['@type'])) {
this.onResponse({
'@type': 'error',
'@extra': query['@extra'],
code: 400,
message: "Method '" + query['@type'] + "' is not supported"
});
return;
}
if (query['@type'] === 'readFile' || query['@type'] === 'readFilePart') {
this.readFile(query);
return;
}
if (query['@type'] === 'deleteFile') {
this.deleteFile(query);
return;
}
this.worker.postMessage(query);
}
/** @private */
async readFile(query) {
const response = await this.fileManager.readFile(query);
this.onResponse(response);
}
/** @private */
async deleteFile(query) {
const response = this.fileManager.deleteFile(query);
try {
if (response.idb_key) {
await this.sendInternal({
'@type': 'deleteIdbKey',
idb_key: response.idb_key
});
delete response.idb_key;
}
await this.sendInternal({
'@type': 'deleteFile',
file_id: query.file_id
});
} catch (e) {}
this.onResponse(response);
}
/** @private */
onResponse(response) {
log.debug(
'receive from worker: ',
JSON.parse(
JSON.stringify(response, (key, value) => {
if (key === 'arr' || key === 'data') {
return undefined;
}
return value;
})
)
);
// for FileManager
response = this.prepareResponse(response);
if ('@extra' in response) {
const query_id = response['@extra'].query_id;
const [resolve, reject] = this.query_callbacks.get(query_id);
this.query_callbacks.delete(query_id);
if ('@old_extra' in response['@extra']) {
response['@extra'] = response['@extra']['@old_extra'];
}
if (resolve) {
if (response['@type'] === 'error') {
reject(response);
} else {
resolve(response);
}
}
} else {
if (response['@type'] === 'inited') {
this.onInited();
return;
}
if (response['@type'] === 'fsInited') {
this.onFsInited();
return;
}
if (
response['@type'] === 'updateAuthorizationState' &&
response.authorization_state['@type'] === 'authorizationStateClosed'
) {
this.onClosed();
}
this.onUpdate(response);
}
}
/** @private */
prepareFile(file) {
return this.fileManager.registerFile(file);
}
/** @private */
prepareResponse(response) {
if (response['@type'] === 'file') {
if (false && Math.random() < 0.1) {
(async () => {
log.warn('DELETE FILE', response.id);
try {
await this.send({ '@type': 'deleteFile', file_id: response.id });
} catch (e) {}
})();
}
return this.prepareFile(response);
}
for (const key in response) {
const field = response[key];
if (
field &&
typeof field === 'object' &&
key !== 'data' &&
key !== 'arr'
) {
response[key] = this.prepareResponse(field);
}
}
return response;
}
/** @private */
onBroadcastMessage(e) {
//const message = e.data;
const message = e;
if (message.uid === this.uid) {
log.info('ignore self broadcast message: ', message);
return;
}
log.info('receive broadcast message: ', message);
if (message.isBackground && !this.isBackground) {
// continue
} else if (
(!message.isBackground && this.isBackground) ||
message.timestamp > this.timestamp
) {
this.close();
return;
}
if (message.state === 'closed') {
this.waitSet.delete(message.uid);
if (this.waitSet.size === 0) {
log.info('onWaitSetEmpty');
this.onWaitSetEmpty();
this.onWaitSetEmpty = () => {};
}
} else {
this.waitSet.add(message.uid);
if (message.state !== 'closing') {
this.postState();
}
}
}
/** @private */
postState() {
const state = {
uid: this.uid,
state: this.state,
timestamp: this.timestamp,
isBackground: this.isBackground
};
log.info('Post state: ', state);
this.channel.postMessage(state);
}
/** @private */
onWaitSetEmpty() {
// nop
}
/** @private */
onFsInited() {
this.fileManager.init();
}
/** @private */
onInited() {
this.isInited = true;
this.doSendStart();
}
/** @private */
sendStart() {
this.wantSendStart = true;
this.doSendStart();
}
/** @private */
doSendStart() {
if (!this.isInited || !this.wantSendStart || this.state !== 'start') {
return;
}
this.wantSendStart = false;
this.state = 'active';
const query = { '@type': 'start' };
log.info('send to worker: ', query);
this.worker.postMessage(query);
}
/** @private */
onClosed() {
this.isClosing = true;
this.worker.terminate();
log.info('worker is terminated');
this.state = 'closed';
this.postState();
}
/** @private */
close() {
if (this.isClosing) {
return;
}
this.isClosing = true;
log.info('close state: ', this.state);
if (this.state === 'start') {
this.onClosed();
this.onUpdate({
'@type': 'updateAuthorizationState',
authorization_state: {
'@type': 'authorizationStateClosed'
}
});
return;
}
const query = { '@type': 'close' };
log.info('send to worker: ', query);
this.worker.postMessage(query);
this.state = 'closing';
this.postState();
}
/** @private */
async closeOtherClients(options) {
this.uid = uuid4();
this.state = 'start';
this.isBackground = !!options.isBackground;
this.timestamp = Date.now();
this.waitSet = new Set();
log.info('close other clients');
this.channel = new BroadcastChannel(options.instanceName, {
webWorkerSupport: false
});
this.postState();
this.channel.onmessage = message => {
this.onBroadcastMessage(message);
};
await sleep(300);
if (this.waitSet.size !== 0) {
await new Promise(resolve => {
this.onWaitSetEmpty = resolve;
});
}
this.sendStart();
}
/** @private */
onUpdate(update) {
log.info('ignore onUpdate');
//nop
}
}
/** @private */
class ListNode {
constructor(value) {
this.value = value;
this.clear();
}
erase() {
this.prev.connect(this.next);
this.clear();
}
clear() {
this.prev = this;
this.next = this;
}
connect(other) {
this.next = other;
other.prev = this;
}
onUsed(other) {
other.usedAt = Date.now();
other.erase();
other.connect(this.next);
log.debug('LRU: used file_id: ', other.value);
this.connect(other);
}
getLru() {
if (this === this.next) {
throw new Error('popLru from empty list');
}
return this.prev;
}
}
/** @private */
class FileManager {
constructor(instanceName, client) {
this.instanceName = instanceName;
this.cache = new Map();
this.pending = [];
this.transaction_id = 0;
this.totalSize = 0;
this.lru = new ListNode(-1);
this.client = client;
}
init() {
this.idb = new Promise((resolve, reject) => {
const request = indexedDB.open(this.instanceName);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
//this.store = localforage.createInstance({
//name: instanceName
//});
this.isInited = true;
}
unload(info) {
if (info.arr) {
log.debug(
'LRU: delete file_id: ',
info.node.value,
' with arr.length: ',
info.arr.length
);
this.totalSize -= info.arr.length;
delete info.arr;
}
if (info.node) {
info.node.erase();
delete info.node;
}
}
registerFile(file) {
if (file.idb_key || file.arr) {
file.local.is_downloading_completed = true;
} else {
file.local.is_downloading_completed = false;
}
let info = {};
const cached_info = this.cache.get(file.id);
if (cached_info) {
info = cached_info;
} else {
this.cache.set(file.id, info);
}
if (file.idb_key) {
info.idb_key = file.idb_key;
delete file.idb_key;
} else {
delete info.idb_key;
}
if (file.arr) {
const now = Date.now();
while (this.totalSize > 100000000) {
const node = this.lru.getLru();
// immunity for 60 seconds
if (node.usedAt + 60 * 1000 > now) {
break;
}
const lru_info = this.cache.get(node.value);
this.unload(lru_info);
}
if (info.arr) {
log.warn('Receive file.arr at least twice for the same file');
this.totalSize -= info.arr.length;
}
info.arr = file.arr;
delete file.arr;
this.totalSize += info.arr.length;
if (!info.node) {
log.debug(
'LRU: create file_id: ',
file.id,
' with arr.length: ',
info.arr.length
);
info.node = new ListNode(file.id);
}
this.lru.onUsed(info.node);
log.info('Total file.arr size: ', this.totalSize);
}
info.file = file;
return file;
}
async flushLoad() {
const pending = this.pending;
this.pending = [];
const idb = await this.idb;
const transaction_id = this.transaction_id++;
const read = idb
.transaction(['keyvaluepairs'], 'readonly')
.objectStore('keyvaluepairs');
log.debug('Load group of files from idb', pending.length);
for (const query of pending) {
const request = read.get(query.key);
request.onsuccess = event => {
const blob = event.target.result;
if (blob) {
if (blob.size === 0) {
log.error('Receive empty blob from db ', query.key);
}
query.resolve({ data: blob, transaction_id: transaction_id });
} else {
query.reject();
}
};
request.onerror = () => query.reject(request.error);
}
}
load(key, resolve, reject) {
if (this.pending.length === 0) {
setTimeout(() => {
this.flushLoad();
}, 1);
}
this.pending.push({ key: key, resolve: resolve, reject: reject });
}
async doLoadFull(info) {
if (info.arr) {
return { data: new Blob([info.arr]), transaction_id: -1 };
}
if (info.idb_key) {
const idb_key = info.idb_key;
//return this.store.getItem(idb_key);
return await new Promise((resolve, reject) => {
this.load(idb_key, resolve, reject);
});
}
throw new Error('File is not loaded');
}
async doLoad(info, offset, size) {
if (!info.arr && !info.idb_key && info.file.local.path) {
try {
const count = await this.client.sendInternal({
'@type': 'getFileDownloadedPrefixSize',
file_id: info.file.id,
offset: offset
});
//log.error(count, size);
if (!size) {
size = count.count;
} else if (size > count.count) {
throw new Error('File not loaded yet');
}
const res = await this.client.sendInternal({
'@type': 'readFilePart',
path: info.file.local.path,
offset: offset,
count: size
});
res.data = new Blob([res.data]);
res.transaction_id = -2;
//log.error(res);
return res;
} catch (e) {
log.info('readFilePart failed', info, offset, size, e);
}
}
const res = await this.doLoadFull(info);
// return slice(size, offset + size)
const data_size = res.data.size;
if (!size) {
size = data_size;
}
if (offset > data_size) {
offset = data_size;
}
res.data = res.data.slice(offset, offset + size);
return res;
}
doDelete(info) {
this.unload(info);
return info.idb_key;
}
async readFile(query) {
try {
if (!this.isInited) {
throw new Error('FileManager is not inited');
}
const info = this.cache.get(query.file_id);
if (!info) {
throw new Error('File is not loaded');
}
if (info.node) {
this.lru.onUsed(info.node);
}
query.offset = query.offset || 0;
query.size = query.count || query.size || 0;
const response = await this.doLoad(info, query.offset, query.size);
return {
'@type': 'filePart',
'@extra': query['@extra'],
data: response.data,
transaction_id: response.transaction_id
};
} catch (e) {
return {
'@type': 'error',
'@extra': query['@extra'],
code: 400,
message: e
};
}
}
deleteFile(query) {
const res = {
'@type': 'ok',
'@extra': query['@extra']
};
try {
if (!this.isInited) {
throw new Error('FileManager is not inited');
}
const info = this.cache.get(query.file_id);
if (!info) {
throw new Error('File is not loaded');
}
const idb_key = this.doDelete(info);
if (idb_key) {
res.idb_key = idb_key;
}
} catch (e) {}
return res;
}
}
export default TdClient;

View File

@@ -0,0 +1,47 @@
class Logger {
constructor() {
this.setVerbosity('WARNING');
}
debug(...str) {
if (this.checkVerbosity(4)) {
console.log(...str);
}
}
log(...str) {
if (this.checkVerbosity(4)) {
console.log(...str);
}
}
info(...str) {
if (this.checkVerbosity(3)) {
console.info(...str);
}
}
warn(...str) {
if (this.checkVerbosity(2)) {
console.warn(...str);
}
}
error(...str) {
if (this.checkVerbosity(1)) {
console.error(...str);
}
}
setVerbosity(level, default_level = 'info') {
if (level === undefined) {
level = default_level;
}
if (typeof level === 'string') {
level =
{ ERROR: 1, WARNING: 2, INFO: 3, LOG: 4, DEBUG: 4 }[
level.toUpperCase()
] || 2;
}
this.level = level;
}
checkVerbosity(level) {
return this.level >= level;
}
}
let log = new Logger();
export default log;

View File

@@ -0,0 +1,136 @@
// 1. +++ fetchAndInstantiate() +++ //
// This library function fetches the wasm module at 'url', instantiates it with
// the given 'importObject', and returns the instantiated object instance
export async function instantiateStreaming(url, importObject) {
let result = await WebAssembly.instantiateStreaming(fetch(url), importObject);
return result.instance;
}
export function fetchAndInstantiate(url, importObject) {
return fetch(url)
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes, importObject))
.then(results => results.instance);
}
// 2. +++ instantiateCachedURL() +++ //
// This library function fetches the wasm Module at 'url', instantiates it with
// the given 'importObject', and returns a Promise resolving to the finished
// wasm Instance. Additionally, the function attempts to cache the compiled wasm
// Module in IndexedDB using 'url' as the key. The entire site's wasm cache (not
// just the given URL) is versioned by dbVersion and any change in dbVersion on
// any call to instantiateCachedURL() will conservatively clear out the entire
// cache to avoid stale modules.
export function instantiateCachedURL(dbVersion, url, importObject) {
const dbName = 'wasm-cache';
const storeName = 'wasm-cache';
// This helper function Promise-ifies the operation of opening an IndexedDB
// database and clearing out the cache when the version changes.
function openDatabase() {
return new Promise((resolve, reject) => {
var request = indexedDB.open(dbName, dbVersion);
request.onerror = reject.bind(null, 'Error opening wasm cache database');
request.onsuccess = () => {
resolve(request.result);
};
request.onupgradeneeded = event => {
var db = request.result;
if (db.objectStoreNames.contains(storeName)) {
console.log(`Clearing out version ${event.oldVersion} wasm cache`);
db.deleteObjectStore(storeName);
}
console.log(`Creating version ${event.newVersion} wasm cache`);
db.createObjectStore(storeName);
};
});
}
// This helper function Promise-ifies the operation of looking up 'url' in the
// given IDBDatabase.
function lookupInDatabase(db) {
return new Promise((resolve, reject) => {
var store = db.transaction([storeName]).objectStore(storeName);
var request = store.get(url);
request.onerror = reject.bind(null, `Error getting wasm module ${url}`);
request.onsuccess = event => {
if (request.result) resolve(request.result);
else reject(`Module ${url} was not found in wasm cache`);
};
});
}
// This helper function fires off an async operation to store the given wasm
// Module in the given IDBDatabase.
function storeInDatabase(db, module) {
var store = db.transaction([storeName], 'readwrite').objectStore(storeName);
var request = store.put(module, url);
request.onerror = err => {
console.log(`Failed to store in wasm cache: ${err}`);
};
request.onsuccess = err => {
console.log(`Successfully stored ${url} in wasm cache`);
};
}
// This helper function fetches 'url', compiles it into a Module,
// instantiates the Module with the given import object.
function fetchAndInstantiate() {
return fetch(url)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.instantiate(buffer, importObject));
}
// With all the Promise helper functions defined, we can now express the core
// logic of an IndexedDB cache lookup. We start by trying to open a database.
return openDatabase().then(
db => {
// Now see if we already have a compiled Module with key 'url' in 'db':
return lookupInDatabase(db).then(
module => {
// We do! Instantiate it with the given import object.
console.log(`Found ${url} in wasm cache`);
return WebAssembly.instantiate(module, importObject);
},
errMsg => {
// Nope! Compile from scratch and then store the compiled Module in 'db'
// with key 'url' for next time.
console.log(errMsg);
return fetchAndInstantiate().then(results => {
try {
storeInDatabase(db, results.module);
} catch (e) {
console.log('Failed to store module into db');
}
return results.instance;
});
}
);
},
errMsg => {
// If opening the database failed (due to permissions or quota), fall back
// to simply fetching and compiling the module and don't try to store the
// results.
console.log(errMsg);
return fetchAndInstantiate().then(results => results.instance);
}
);
}
export async function instantiateAny(version, url, importObject) {
console.log("instantiate");
try {
return await instantiateStreaming(url, importObject);
} catch (e) {
console.log("instantiateStreaming failed", e);
}
try {
return await instantiateCachedURL(version, url, importObject);
} catch (e) {
console.log("instantiateCachedURL failed", e);
}
throw new Error("can't instantiate wasm");
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,86 @@
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: ['./src/index.js'],
output: {
filename: 'tdweb.js',
path: path.resolve(__dirname, 'dist'),
library: 'tdweb',
libraryTarget: 'umd',
umdNamedDefine: true,
globalObject: 'this'
},
devServer: {
contentBase: './dist'
},
plugins: [
// new HtmlWebpackPlugin(),
new CleanWebpackPlugin({})
//, new UglifyJSPlugin()
],
optimization:{
minimize: false, // <---- disables uglify.
},
module: {
noParse: /td_asmjs\.js$/,
rules: [
{
test: /\.(js|jsx)$/,
exclude: /prebuilt/,
enforce: 'pre',
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: require.resolve('eslint-loader')
}
]
},
{
test: /worker\.(js|jsx)$/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: require.resolve('worker-loader')
}
]
},
{
test: /\.(js|jsx)$/,
exclude: /prebuilt/,
include: [path.resolve(__dirname, 'src')],
use: [
{
loader: require.resolve('babel-loader')
}
]
},
{
test: /\.(wasm|mem)$/,
include: [path.resolve(__dirname, 'src')],
type: "javascript/auto",
use: [
{
loader: require.resolve('file-loader')
}
]
}
]
},
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
crypto: 'empty',
child_process: 'empty'
},
performance: {
maxAssetSize: 30000000
},
resolve: {
alias: {
ws$: 'fs'
}
}
};