diff --git a/.gitignore b/.gitignore index 2ce38f5..4cf28a6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,8 @@ compile_commands.json *sqlite documents libpif.* +build +depend.* +.ninja* +*.swp +examples/madelinex diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 3bf031e..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "td"] - path = td - url = https://github.com/danog/td diff --git a/CMakeLists.txt b/CMakeLists.txt index 523020e..49622bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,26 @@ - cmake_minimum_required(VERSION 3.1 FATAL_ERROR) project(PifTDPony VERSION 1.0 LANGUAGES CXX) -add_subdirectory(td) -include_directories(td/td/generate/auto) -include_directories(td/tdutils) +set(CMAKE_BUILD_TYPE RelWithDebInfo) + +find_package(Td 1.1.1 REQUIRED) add_library(pif SHARED main.cpp) -target_link_libraries(pif PRIVATE Td::TdJsonStatic phpcpp) +target_link_libraries(pif PRIVATE Td::TdJson phpcpp) set_property(TARGET pif PROPERTY CXX_STANDARD 14) + +add_custom_command(TARGET pif POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/pif.ini + $ + ) + +execute_process(COMMAND bash "-c" "php --ini | sed '/Scan for additional .ini files in: /!d;s/Scan for additional .ini files in: //'" OUTPUT_VARIABLE INI_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process(COMMAND php-config --extension-dir OUTPUT_VARIABLE EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + + +install(TARGETS pif DESTINATION ${EXTENSION_DIR}) +install(FILES pif.ini DESTINATION ${INI_DIR}) + diff --git a/README.md b/README.md index 09b6049..8baa5a3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ # PIF-TDPony This is a PHP interface for [tdlib](https://core.telegram.org). + +# Installation + + +``` +# Installing PHP +sudo apt-get install python-software-properties software-properties-common +sudo LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php +sudo apt-get update +sudo apt-get install php7.2 php7.2-dev php7.2-fpm php7.2-curl php7.2-xml php7.2-gmp php7.1-zip -y + +# Installing tdlib +sudo apt-get install gperf cmake clang ninja-build libssl-dev +git clone https://github.com/tdlib/td +cd td +mkdir build +cd build +cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=/usr .. +sudo cmake --build . --target install +cd ../.. + +# Installing PHP-CPP +git clone https://github.com/CopernicaMarketingSoftware/PHP-CPP +cd PHP-CPP +make -j$(nproc) +sudo make install +cd .. + +# Installing PIF-TDPony +git clone https://github.com/danog/pif-tdpony +cd pif-tdpony +mkdir build +cd build +cmake .. +sudo cmake --build . --target install +cd ../../ +``` + + +Usage: + + diff --git a/examples/example.php b/examples/example.php new file mode 100644 index 0000000..1161089 --- /dev/null +++ b/examples/example.php @@ -0,0 +1,31 @@ + true, + "api_id" => 6, + "api_hash" => "eb06d4abfb49dc3eeb1aeb98ae0f581e", + "system_language_code" => "en", + "device_model" => "MadelineProto X", + "system_version" => "unk", + "application_version" => "1.1", + "enable_storage_optimizer" => true, + "use_pfs" => true, + "database_directory" => "./madelinex" + ]; + $a = new \danog\MadelineProto\X\API($params); +} +\danog\Madelineproto\X\Logger::set_verbosity_level(5); + +$a->serialize('session.madelinex'); +$a->setDatabaseEncryptionKey(); // no password for tdlib database + + +while (true) { +readline(); + $res = $a->receive(1); + var_dumP($res); +} diff --git a/main.cpp b/main.cpp index c93290f..ea65f98 100644 --- a/main.cpp +++ b/main.cpp @@ -10,70 +10,200 @@ If not, see . */ #include -#include +#include #include #include +#include class API : public Php::Base { public: API() = default; virtual ~API() = default; - + void __construct(Php::Parameters ¶ms) { - Php::Value self(this); + self = Php::Value(this); + if (params.size() != 1) { + throw Php::Exception("Invalid parameter count"); + } + if (params[0].isString()) { + if (!Php::call("file_exists", params[0])) { + throw Php::Exception("Provided file does not exist!"); + } + Php::Value unserialized = Php::call("unserialize", Php::call("file_get_contents", params[0])); + if (!unserialized.instanceOf("\\danog\\MadelineProto\\X\\TD")) { + throw Php::Exception("An error occurred during deserialization!"); + } + self["API"] = unserialized; + self["session"] = params[0]; + } else { + self["API"] = Php::Object("\\danog\\MadelineProto\\X\\TD", params[0]); + } + } + void serialize(Php::Parameters ¶ms) { + Php::Value path; + if (params.size() == 1 && params[0].isString()) { + path = params[0]; + } else if (self.contains("session") && self.get("session").isString()) { + path = self.get("session"); + } else { + throw Php::Exception("No valid path was provided"); + } + last_serialized = Php::call("time"); + Php::call("file_put_contents", path, Php::call("serialize", self.get("API"))); + } + void __destruct() { + if (self.contains("session") && self.get("session").isString()) { + last_serialized = Php::call("time"); + Php::call("file_put_contents", self.get("session"), Php::call("serialize", self.get("API"))); + } + } + Php::Value __actualcall(Php::Parameters ¶ms) { + if (last_serialized < Php::call("time") - 30 && self.contains("session") && self.get("session").isString()) { + last_serialized = Php::call("time"); + Php::call("file_put_contents", self.get("session"), Php::call("serialize", self.get("API"))); + } + if (params[1].size() == 1) { + return self.get("API").call(params[0], params[1].get(0)); + } + return self.get("API").call(params[0]); + } + + private: + Php::Value self; + Php::Value last_serialized = 0; +}; + +class TD : public Php::Base { + public: + TD() = default; + virtual ~TD() = default; + + void __construct(Php::Parameters ¶ms) { + self = Php::Value(this); self["tdlibParameters"]["@type"] = "setTdlibParameters"; self["tdlibParameters"]["parameters"] = params[0]; - initTdlib(); } void __wakeup() { + self = Php::Value(this); initTdlib(); } void __destruct() { deinitTdlib(); } + Php::Value __sleep() { + Php::Array buffer; + while (updates.size()) { + buffer[buffer.size()] = updates.front(); + updates.pop(); + } + for (auto &item : buffer) { + updates.push(item.second); + } + self["updatesBuffer"] = buffer; + Php::Array result({"updatesBuffer", "tdlibParameters"}); + return result; + } + void initTdlib() { client = td_json_client_create(); - Php::Value self(this); - td_json_client_execute(client, json_encode(self["tdlibParameters"])); + if (self["updatesBuffer"]) { + Php::Value buffer = self["updatesBuffer"]; + for (auto &item : buffer) { + updates.push(item.second); + } + Php::Array empty; + self["updatesBuffer"] = empty; + } + + Php::Value update; + while (true) { + update = json_decode(td_json_client_receive(client, 0)); + if (update) { + if (update.get("@type") == "updateAuthorizationState" && update.get("authorization_state").get("@type") == "authorizationStateWaitTdlibParameters") { + execute(self["tdlibParameters"]); + break; + } + updates.push(update); + } + } } void deinitTdlib() { td_json_client_destroy(client); } + + void send(Php::Parameters ¶ms) { Php::Value value = params[0]; if (value.get("@type") == "setTdlibParameters") { - Php::Value self(this); self["tdlibParameters"] = value; } td_json_client_send(client, json_encode(value)); } - + Php::Value receive(Php::Parameters ¶ms) { + if (updates.size()) { + Php::Value res = updates.front(); + updates.pop(); + return res; + } return json_decode(td_json_client_receive(client, params[0])); } - + Php::Value execute(Php::Parameters ¶ms) { - Php::Value value = params[0]; - if (value.get("@type") == "setTdlibParameters") { - Php::Value self(this); + return execute(params[0]); + } + + Php::Value execute(Php::Value value) { + if (value["@type"] && value.get("@type") == "setTdlibParameters") { self["tdlibParameters"] = value; } - return json_decode(td_json_client_execute(client, json_encode(value))); - } - - + std::int64_t query_id = next_query_id(); + value["@extra"] = query_id; + td_json_client_send(client, json_encode(value)); + + Php::Value update; + while (true) { + update = json_decode(td_json_client_receive(client, 0)); + if (update) { + if (update["@extra"] == query_id) { + return update; + } + + updates.push(update); + } + } + } + Php::Value __call(const char *name, Php::Parameters ¶ms) { + Php::Array value; + if (params.size() == 1) { + value = params[0]; + } + value["@type"] = name; + return execute(value); + } private: void *client; + std::queue updates; + Php::Value self; + + std::int64_t query_id = 0; + std::int64_t next_query_id() { + return ++query_id; + } const char *json_encode(Php::Value value) { return strdup(Php::call("json_encode", value)); } - Php::Value json_decode(std::string value) { - return Php::call("json_decode", value, true); + Php::Value json_decode(const char *value) { + Php::Value result; + if (value) { + result = Php::call("json_decode", value, true); + } + return result; } }; @@ -85,13 +215,14 @@ class Logger : public Php::Base virtual ~Logger() = default; static Php::Value set_file_path(Php::Parameters ¶ms) { - return td::Log::set_file_path(params[0]); + return td_set_log_file_path(params[0]); } static void set_max_file_size(Php::Parameters ¶ms) { - td::Log::set_max_file_size(params[0]); + std::int64_t size = params[0]; + td_set_log_max_file_size(size); } static void set_verbosity_level(Php::Parameters ¶ms) { - td::Log::set_verbosity_level(params[0]); + td_set_log_verbosity_level(params[0]); } }; @@ -114,17 +245,27 @@ extern "C" { // description of the class so that PHP knows which methods are accessible Php::Class api("API"); - api.method<&API::__construct>("__construct", Php::Public | Php::Final, {Php::ByVal("tdlibParameters", Php::Type::Array)}); - api.method<&API::__wakeup>("__wakeup", Php::Public | Php::Final, {}); + api.method<&API::__construct>("__construct", Php::Public | Php::Final); api.method<&API::__destruct>("__destruct", Php::Public | Php::Final, {}); - api.method<&API::send>("send", Php::Public | Php::Final); - api.method<&API::receive>("receive", Php::Public | Php::Final, {Php::ByVal("timeout", Php::Type::Float)}); - api.method<&API::execute>("execute", Php::Public | Php::Final); - - api.property("tdlibParameters", nullptr, Php::Private); + api.method<&API::__actualcall>("__call", Php::Public | Php::Final, {Php::ByVal("method", Php::Type::String), Php::ByVal("args", Php::Type::Array)}); + api.method<&API::serialize>("serialize", Php::Public | Php::Final, {Php::ByVal("path", Php::Type::String)}); api.constant("PIF_TDPONY_VERSION", "1.0"); + + Php::Class td("TD"); + td.method<&TD::__construct>("__construct", Php::Public | Php::Final); + td.method<&TD::__destruct>("__destruct", Php::Public | Php::Final); + td.method<&TD::__wakeup>("__wakeup", Php::Public | Php::Final); + td.method<&TD::__sleep>("__sleep", Php::Public | Php::Final); + td.method<&TD::send>("send", Php::Public | Php::Final); + td.method<&TD::receive>("receive", Php::Public | Php::Final, {Php::ByVal("timeout", Php::Type::Float)}); + td.method<&TD::execute>("execute", Php::Public | Php::Final); + + td.property("tdlibParameters", nullptr, Php::Private); + td.property("updatesBuffer", nullptr, Php::Private); + + Php::Namespace danog("danog"); Php::Namespace MadelineProto("MadelineProto"); Php::Namespace X("X"); @@ -136,6 +277,7 @@ extern "C" { logger.method<&Logger::set_verbosity_level>("set_verbosity_level", Php::Public, {Php::ByVal("verbosity_level", Php::Type::Numeric)}); X.add(std::move(api)); + X.add(std::move(td)); X.add(std::move(logger)); MadelineProto.add(std::move(X)); danog.add(std::move(MadelineProto)); diff --git a/pif.ini b/pif.ini new file mode 100644 index 0000000..4e00652 --- /dev/null +++ b/pif.ini @@ -0,0 +1 @@ +extension=libpif.so diff --git a/td b/td deleted file mode 160000 index b84ed67..0000000 --- a/td +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b84ed67dfbc7797ffce54490594cc8e3fb4df7ba