From a00c23fa27c3718c5c55856b0258a6676be804c9 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 29 Nov 2025 19:56:36 +0100 Subject: [PATCH 01/24] Upgraded libcpr from v1.11.3 to v1.14.1 --- lib/cpr/CMakeLists.txt | 26 +++-- lib/cpr/README.md | 30 ++--- lib/cpr/cmake/clang-tidy.cmake | 21 ++-- lib/cpr/cmake/cppcheck.cmake | 18 ++- lib/cpr/cmake/cprConfig-ssl.cmake.in | 9 ++ lib/cpr/cmake/libpsl.cmake | 118 ++++++++++++++++++++ lib/cpr/cmake/sanitizer.cmake | 77 +++++++------ lib/cpr/cpr-config.cmake | 26 ----- lib/cpr/cpr/CMakeLists.txt | 11 +- lib/cpr/cpr/auth.cpp | 5 - lib/cpr/cpr/bearer.cpp | 17 --- lib/cpr/cpr/connection_pool.cpp | 39 +++++++ lib/cpr/cpr/cookies.cpp | 5 +- lib/cpr/cpr/curl_container.cpp | 38 ++++++- lib/cpr/cpr/curlholder.cpp | 15 +-- lib/cpr/cpr/multiperform.cpp | 92 +++++++++------- lib/cpr/cpr/proxyauth.cpp | 33 ++++-- lib/cpr/cpr/response.cpp | 14 +++ lib/cpr/cpr/session.cpp | 94 +++++++++++++++- lib/cpr/cpr/sse.cpp | 122 +++++++++++++++++++++ lib/cpr/cpr/util.cpp | 61 +++-------- lib/cpr/include/CMakeLists.txt | 2 + lib/cpr/include/cpr/api.h | 16 +-- lib/cpr/include/cpr/async.h | 10 +- lib/cpr/include/cpr/async_wrapper.h | 139 ++++++++++++++---------- lib/cpr/include/cpr/auth.h | 10 +- lib/cpr/include/cpr/bearer.h | 12 +- lib/cpr/include/cpr/body.h | 1 - lib/cpr/include/cpr/body_view.h | 36 ++++++ lib/cpr/include/cpr/callback.h | 40 +++++-- lib/cpr/include/cpr/connection_pool.h | 80 ++++++++++++++ lib/cpr/include/cpr/cpr.h | 2 + lib/cpr/include/cpr/curl_container.h | 10 ++ lib/cpr/include/cpr/curlholder.h | 8 +- lib/cpr/include/cpr/error.h | 77 +++++++++++++ lib/cpr/include/cpr/http_version.h | 9 +- lib/cpr/include/cpr/low_speed.h | 9 +- lib/cpr/include/cpr/multiperform.h | 4 +- lib/cpr/include/cpr/proxyauth.h | 21 ++-- lib/cpr/include/cpr/response.h | 2 + lib/cpr/include/cpr/secure_string.h | 66 +++++++++++ lib/cpr/include/cpr/session.h | 22 +++- lib/cpr/include/cpr/sse.h | 102 +++++++++++++++++ lib/cpr/include/cpr/ssl_options.h | 90 ++++++++++++--- lib/cpr/include/cpr/status_codes.h | 151 +++++++++++++------------- lib/cpr/include/cpr/util.h | 18 ++- 46 files changed, 1357 insertions(+), 451 deletions(-) create mode 100644 lib/cpr/cmake/cprConfig-ssl.cmake.in create mode 100644 lib/cpr/cmake/libpsl.cmake delete mode 100644 lib/cpr/cpr-config.cmake delete mode 100644 lib/cpr/cpr/bearer.cpp create mode 100644 lib/cpr/cpr/connection_pool.cpp create mode 100644 lib/cpr/cpr/sse.cpp create mode 100644 lib/cpr/include/cpr/body_view.h create mode 100644 lib/cpr/include/cpr/connection_pool.h create mode 100644 lib/cpr/include/cpr/secure_string.h create mode 100644 lib/cpr/include/cpr/sse.h diff --git a/lib/cpr/CMakeLists.txt b/lib/cpr/CMakeLists.txt index b6bff96c9..152bf9318 100644 --- a/lib/cpr/CMakeLists.txt +++ b/lib/cpr/CMakeLists.txt @@ -1,10 +1,10 @@ cmake_minimum_required(VERSION 3.15) -project(cpr VERSION 1.11.3 LANGUAGES CXX) +project(cpr VERSION 1.14.1 LANGUAGES CXX) math(EXPR cpr_VERSION_NUM "${cpr_VERSION_MAJOR} * 0x10000 + ${cpr_VERSION_MINOR} * 0x100 + ${cpr_VERSION_PATCH}" OUTPUT_FORMAT HEXADECIMAL) configure_file("${cpr_SOURCE_DIR}/cmake/cprver.h.in" "${cpr_BINARY_DIR}/cpr_generated_includes/cpr/cprver.h") -# Only change the folder behaviour if cpr is not a subproject +# Only change the folder behavior if cpr is not a subproject if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME}) set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMake") @@ -56,6 +56,9 @@ cpr_option(CPR_CURL_NOSIGNAL "Set to ON to disable use of signals in libcurl." O cpr_option(CURL_VERBOSE_LOGGING "Curl verbose logging during building curl" OFF) cpr_option(CPR_USE_SYSTEM_GTEST "If ON, this project will look in the system paths for an installed gtest library. If none is found it will use the built-in one." OFF) cpr_option(CPR_USE_SYSTEM_CURL "If enabled we will use the curl lib already installed on this system." OFF) +cpr_option(CPR_USE_EXISTING_CURL_TARGET "Use an existing libcurl cmake target instead of having the cpr project source the dependency itself." OFF) +cpr_option(CPR_CURL_USE_LIBPSL "Since curl 8.13 curl depends on libpsl (https://everything.curl.dev/build/deps.html#libpsl). By default cpr keeps this as a secure default enabled wich in turn requires meson as build dependency. If set to OFF, psl support inside curl will be disabled." ON) +cpr_option(CPR_USE_SYSTEM_LIB_PSL "If enabled we will use the psl lib already installed on this system. Else meson is required as build dependency. Only relevant in case 'CPR_CURL_USE_LIBPSL' is set to ON." ${CPR_USE_SYSTEM_CURL}) cpr_option(CPR_ENABLE_CURL_HTTP_ONLY "If enabled we will only use the HTTP/HTTPS protocols from CURL. If disabled, all the CURL protocols are enabled. This is useful if your project uses libcurl and you need support for other CURL features e.g. sending emails." ON) cpr_option(CPR_ENABLE_SSL "Enables or disables the SSL backend. Required to perform HTTPS requests." ON) cpr_option(CPR_FORCE_OPENSSL_BACKEND "Force to use the OpenSSL backend. If CPR_FORCE_OPENSSL_BACKEND, CPR_FORCE_DARWINSSL_BACKEND, CPR_FORCE_MBEDTLS_BACKEND, and CPR_FORCE_WINSSL_BACKEND are set to to OFF, cpr will try to automatically detect the best available SSL backend (WinSSL - Windows, OpenSSL - Linux, DarwinSSL - Mac ...)." OFF) @@ -188,7 +191,9 @@ find_package(OpenSSL REQUIRED) endif() # Curl configuration -if(CPR_USE_SYSTEM_CURL) +if(CPR_USE_EXISTING_CURL_TARGET) + message(STATUS "cpr skipping management of curl dependency (CPR_USE_EXISTING_CURL_TARGET is set to ON).") +elseif(CPR_USE_SYSTEM_CURL) if(CPR_ENABLE_SSL) find_package(CURL COMPONENTS HTTP HTTPS) if(CURL_FOUND) @@ -294,10 +299,15 @@ else() if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") cmake_policy(SET CMP0135 NEW) endif() - FetchContent_Declare(curl - URL https://github.com/curl/curl/releases/download/curl-8_10_1/curl-8.10.1.tar.xz - URL_HASH SHA256=73a4b0e99596a09fa5924a4fb7e4b995a85fda0d18a2c02ab9cf134bebce04ee # the file hash for curl-8.10.1.tar.xz - USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress + + # Since curl 8.13, curl depends on lib psl + set(CURL_USE_LIBPSL ${CPR_CURL_USE_LIBPSL} CACHE INTERNAL "" FORCE) + if(CPR_CURL_USE_LIBPSL AND NOT CPR_USE_SYSTEM_LIB_PSL) + include(libpsl) + endif() + + FetchContent_Declare(curl URL https://github.com/curl/curl/releases/download/curl-8_13_0/curl-8.13.0.tar.xz + URL_HASH SHA256=4a093979a3c2d02de2fbc00549a32771007f2e78032c6faa5ecd2f7a9e152025) # the file hash for curl-8.13.0.tar.xz FetchContent_MakeAvailable(curl) restore_variable(DESTINATION CMAKE_CXX_CLANG_TIDY BACKUP CMAKE_CXX_CLANG_TIDY_BKP) @@ -377,7 +387,7 @@ if(CPR_BUILD_TESTS) USES_TERMINAL_DOWNLOAD TRUE) # <---- This is needed only for Ninja to show download progress # We can not use FetchContent_MakeAvailable, since we need to patch mongoose to use CMake if (NOT mongoose_POPULATED) - FetchContent_POPULATE(mongoose) + FetchContent_MakeAvailable(mongoose) file(INSTALL cmake/mongoose.CMakeLists.txt DESTINATION ${mongoose_SOURCE_DIR}) file(RENAME ${mongoose_SOURCE_DIR}/mongoose.CMakeLists.txt ${mongoose_SOURCE_DIR}/CMakeLists.txt) diff --git a/lib/cpr/README.md b/lib/cpr/README.md index 9cfe749d1..5e13f5b24 100644 --- a/lib/cpr/README.md +++ b/lib/cpr/README.md @@ -1,6 +1,6 @@ # C++ Requests: Curl for People -[![Documentation](https://img.shields.io/badge/docs-online-informational?style=flat&link=https://docs.libcpr.org/)](https://docs.libcpr.org/) +[![Documentation](https://img.shields.io/badge/docs-online-informational?style=flat&link=https://docs.libcpr.dev/)](https://docs.libcpr.dev/) ![CI](https://github.com/libcpr/cpr/workflows/CI/badge.svg) [![Gitter](https://badges.gitter.im/libcpr/community.svg)](https://gitter.im/libcpr/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) @@ -10,12 +10,12 @@ * For quick help, and discussion libcpr also offers a [gitter](https://gitter.im/libcpr/community?utm_source=share-link&utm_medium=link&utm_campaign=share-link) chat. ## Supported Releases -| Release | Min. C++ Standard | Status | Notes | -|----------|-------------------|--------|-------| -| master | `cpp17` | ![alt text][preview] | | -| 1.11.x | `cpp17` | ![alt text][supported] | | -| 1.10.x | `cpp17` | ![alt text][unsupported] | | -| <= 1.9.x | `cpp11` | ![alt text][unsupported] | | +| Release | Min. C++ Standard | Status | Notes | +|---------------------------|-------------------|--------------------------|-------| +| master | `cpp17` | ![alt text][preview] | | +| 1.14.x | `cpp17` | ![alt text][supported] | | +| 1.10.x - 1.13.x | `cpp17` | ![alt text][unsupported] | | +| <= 1.9.x | `cpp11` | ![alt text][unsupported] | | [unsupported]: https://img.shields.io/badge/-unsupported-red "unsupported" [supported]: https://img.shields.io/badge/-supported-green "supported" @@ -47,8 +47,8 @@ And here's [less functional, more complicated code, without cpr](https://gist.gi ## Documentation -[![Documentation](https://img.shields.io/badge/docs-online-informational?style=for-the-badge&link=https://docs.libcpr.org/)](https://docs.libcpr.org/) -You can find the latest documentation [here](https://docs.libcpr.org/). It's a work in progress, but it should give you a better idea of how to use the library than the [tests](https://github.com/libcpr/cpr/tree/master/test) currently do. +[![Documentation](https://img.shields.io/badge/docs-online-informational?style=for-the-badge&link=https://docs.libcpr.dev/)](https://docs.libcpr.dev/) +You can find the latest documentation [here](https://docs.libcpr.dev/). It's a work in progress, but it should give you a better idea of how to use the library than the [tests](https://github.com/libcpr/cpr/tree/master/test) currently do. ## Features @@ -76,6 +76,7 @@ C++ Requests currently supports: * PATCH methods * Thread Safe access to [libCurl](https://curl.haxx.se/libcurl/c/threadsafe.html) * OpenSSL and WinSSL support for HTTPS requests +* Server Sent Events (SSE) handling ## Planned @@ -93,7 +94,7 @@ Add the following to your `CMakeLists.txt`. ```cmake include(FetchContent) FetchContent_Declare(cpr GIT_REPOSITORY https://github.com/libcpr/cpr.git - GIT_TAG dec9422db3af470641f8b0d90e4b451c4daebf64) # Replace with your desired git commit from: https://github.com/libcpr/cpr/releases + GIT_TAG 99f140d222152fa7332b0ae433ad899086ece3fd.12.0) # Replace with your desired git commit from: https://github.com/libcpr/cpr/releases FetchContent_MakeAvailable(cpr) ``` @@ -146,7 +147,7 @@ ctest -VV # -VV is optional since it enables verbose output ``` ### Bazel -Please refer to [hedronvision/bazel-make-cc-https-easy](https://github.com/hedronvision/bazel-make-cc-https-easy) or +Please refer to [hedronvision/bazel-make-cc-https-easy](https://github.com/hedronvision/bazel-make-cc-https-easy) or `cpr` can be added as an extension by adding the following lines to your bazel MODULE file (tested with Bazel 8). Edit the versions as needed. ```starlark @@ -218,10 +219,11 @@ On FreeBSD, you can issue `pkg install cpr` or use the Ports tree to install it. The only explicit requirements are: -* a `C++17` compatible compiler such as Clang or GCC. The minimum required version of GCC is unknown, so if anyone has trouble building this library with a specific version of GCC, do let us know -* in case you only have a `C++11` compatible compiler available, all versions below cpr 1.9.x are for you. The 1.10.0 release of cpr switches to `C++17` as a requirement. +* A `C++17` compatible compiler such as Clang or GCC. The minimum required version of GCC is unknown, so if anyone has trouble building this library with a specific version of GCC, do let us know. +* In case you only have a `C++11` compatible compiler available, all versions below cpr 1.9.x are for you. The 1.10.0 release of cpr switches to `C++17` as a requirement. * If you would like to perform https requests `OpenSSL` and its development libraries are required. -* If you do not use the built-in version of [curl](https://github.com/curl/curl) but instead use your systems version, make sure you use a version `>= 7.64.0`. Lower versions are not supported. This means you need Debian `>= 10` or Ubuntu `>= 20.04 LTS`. +* If you do not use the built-in version of [curl](https://github.com/curl/curl) but instead use your systems version, make sure you use a version `>= 7.71.0`. Lower versions are not supported. This means you need Debian `>= 11` or Ubuntu `>= 22.04 LTS`. +* [`The Meson Build System`](https://mesonbuild.com/) is required build PSL from source ([PSL support for curl](https://everything.curl.dev/build/deps.html#libpsl)). For more information take a look at the `CPR_CURL_USE_LIBPSL` and `CPR_USE_SYSTEM_LIB_PSL` CMake options. ## Building cpr - Using vcpkg diff --git a/lib/cpr/cmake/clang-tidy.cmake b/lib/cpr/cmake/clang-tidy.cmake index 26defad2e..9866fc5a5 100644 --- a/lib/cpr/cmake/clang-tidy.cmake +++ b/lib/cpr/cmake/clang-tidy.cmake @@ -1,13 +1,16 @@ +# Include this file if and only if you want to use clang-tidy linter. + if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Windows") - find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy) + if (NOT ${CPR_LINTER_PATH} STREQUAL "") + get_filename_component(CLANG_TIDY_HINT_FILENAME ${CPR_LINTER_PATH} NAME) + get_filename_component(CLANG_TIDY_HINT_PATH ${CPR_LINTER_PATH} DIRECTORY) + endif () + + find_program(CLANG_TIDY_EXECUTABLE NAMES clang-tidy ${CLANG_TIDY_HINT_FILENAME} HINTS ${CLANG_TIDY_HINT_PATH} REQUIRED) mark_as_advanced(CLANG_TIDY_EXECUTABLE) - if (${CLANG_TIDY_EXECUTABLE}) - message(FATAL_ERROR "Clang-tidy not found") - else() - message(STATUS "Enabling clang-tidy") - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE};-warnings-as-errors=*") - endif() -else() + message(STATUS "Enabling clang-tidy: ${CLANG_TIDY_EXECUTABLE}") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXECUTABLE};-warnings-as-errors=*") +else () message(FATAL_ERROR "Clang-tidy is not supported when building for windows") -endif() +endif () diff --git a/lib/cpr/cmake/cppcheck.cmake b/lib/cpr/cmake/cppcheck.cmake index 9ed9ab014..dc0ac5b45 100644 --- a/lib/cpr/cmake/cppcheck.cmake +++ b/lib/cpr/cmake/cppcheck.cmake @@ -1,7 +1,15 @@ -find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck) +# Include this file if and only if you want to use cppcheck. -if(CMAKE_CXX_CPPCHECK) - list(APPEND CMAKE_CXX_CPPCHECK "--xml" +if (NOT ${CPR_CPPCHECK_PATH} STREQUAL "") + get_filename_component(CPPCHECK_HINT_FILENAME ${CPR_CPPCHECK_PATH} NAME) + get_filename_component(CPPCHECK_HINT_PATH ${CPR_CPPCHECK_PATH} DIRECTORY) +endif () + +find_program(CMAKE_CXX_CPPCHECK NAMES cppcheck ${CPPCHECK_HINT_FILENAME} HINTS ${CPPCHECK_HINT_PATH} REQUIRED) +message(STATUS "Found cppcheck: ${CMAKE_CXX_CPPCHECK}") + +list(APPEND CMAKE_CXX_CPPCHECK + "--xml" "--error-exitcode=1" "--enable=warning,style" "--force" @@ -11,5 +19,5 @@ if(CMAKE_CXX_CPPCHECK) "--cppcheck-build-dir=${PROJECT_BINARY_DIR}" "--suppress-xml=${PROJECT_SOURCE_DIR}/cppcheck-suppressions.xml" "--output-file=${PROJECT_BINARY_DIR}/cppcheck.xml" - "--check-level=normal") -endif() + "--check-level=normal" +) diff --git a/lib/cpr/cmake/cprConfig-ssl.cmake.in b/lib/cpr/cmake/cprConfig-ssl.cmake.in new file mode 100644 index 000000000..89483fc3d --- /dev/null +++ b/lib/cpr/cmake/cprConfig-ssl.cmake.in @@ -0,0 +1,9 @@ +include(CMakeFindDependencyMacro) +@PACKAGE_INIT@ + +find_dependency(CURL REQUIRED) +find_dependency(OpenSSL REQUIRED) + +include(${CMAKE_CURRENT_LIST_DIR}/cprTargets.cmake) + +check_required_components(cpr) \ No newline at end of file diff --git a/lib/cpr/cmake/libpsl.cmake b/lib/cpr/cmake/libpsl.cmake new file mode 100644 index 000000000..0041a116a --- /dev/null +++ b/lib/cpr/cmake/libpsl.cmake @@ -0,0 +1,118 @@ +# Builds libpsl which is especially necessary on Windows since there it is not available via e.g. a package manager. + +include(ExternalProject) +find_program(MESON_PATH meson) + +if(MESON_PATH STREQUAL "MESON_PATH-NOTFOUND") + message(FATAL_ERROR "meson not found. Please make sure you have meson installed on your system (https://mesonbuild.com/Getting-meson.html). Meson is required for building libpsl for curl on Windows.") + return() +endif() + +FetchContent_Declare(libpsl_src GIT_REPOSITORY https://github.com/rockdaboot/libpsl.git + GIT_TAG 0.21.5) +FetchContent_MakeAvailable(libpsl_src) # sets libpsl_src_SOURCE_DIR / _BINARY_DIR + +set(LIBPSL_SOURCE_DIR "${libpsl_src_SOURCE_DIR}") +set(LIBPSL_BUILD_DIR "${libpsl_src_BINARY_DIR}") +set(LIBPSL_INSTALL_DIR "${CMAKE_BINARY_DIR}/libpsl_src-install") +file(MAKE_DIRECTORY "${LIBPSL_BUILD_DIR}") + +string(TOLOWER "${CMAKE_SYSTEM_NAME}" MESON_TARGET_HOST_SYSTEM_NAME) +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" MESON_TARGET_SYSTEM_PROCESSOR_LOWER) +if(MESON_TARGET_SYSTEM_PROCESSOR_LOWER MATCHES "^(x86_64|amd64)$") + set(MESON_TARGET_HOST_CPU_FAMILY "x86_64") +elseif(MESON_TARGET_SYSTEM_PROCESSOR_LOWER MATCHES "^(i.86|x86)$") + set(MESON_TARGET_HOST_CPU_FAMILY "x86") +elseif(MESON_TARGET_SYSTEM_PROCESSOR_LOWER MATCHES "^(armv7|armv6|arm)$") + set(MESON_TARGET_HOST_CPU_FAMILY "arm") +elseif(MESON_TARGET_SYSTEM_PROCESSOR_LOWER MATCHES "^(aarch64|arm64)$") + set(MESON_TARGET_HOST_CPU_FAMILY "aarch64") +else() + set(MESON_TARGET_HOST_CPU_FAMILY "${MESON_TARGET_SYSTEM_PROCESSOR_LOWER}") +endif() + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(MESON_BUILD_TYPE debug) +elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + set(MESON_BUILD_TYPE debugoptimized) +else() + set(MESON_BUILD_TYPE release) +endif() + +include (TestBigEndian) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +if(IS_BIG_ENDIAN) + set(MESON_ENDIAN "big") +else() + set(MESON_ENDIAN "little") +endif() + +# libpsl is plain C. Make sure CMake initializes a C tool-chain. +if(NOT CMAKE_C_COMPILER) + enable_language(C) # initializes CMAKE_C_COMPILER, CMAKE_AR, … +endif() + +# Write a meson cross compilation file to allow cross compiling +# for example for building NuGet packages although usually it is not required. +file(WRITE "${CMAKE_BINARY_DIR}/libpsl-meson-cross.txt" "[binaries] +c = '${CMAKE_C_COMPILER}' +cpp = '${CMAKE_CXX_COMPILER}' +ar = '${CMAKE_AR}' +strip = '${CMAKE_STRIP}' + +[host_machine] +system = '${MESON_TARGET_HOST_SYSTEM_NAME}' +cpu_family = '${MESON_TARGET_HOST_CPU_FAMILY}' +cpu = '${MESON_TARGET_HOST_CPU_FAMILY}' +endian = '${MESON_ENDIAN}' +") + +# Meson configure +# We only care about static libraries of psl. In case you need a dynamic version, feel free to add support for it. +message(STATUS "Configuring libpsl...") +execute_process(COMMAND "${MESON_PATH}" setup + "${LIBPSL_BUILD_DIR}" + "${LIBPSL_SOURCE_DIR}" + -Dtests=false + -Ddocs=false + --cross-file "${CMAKE_BINARY_DIR}/libpsl-meson-cross.txt" + --buildtype=${MESON_BUILD_TYPE} + --prefix "${LIBPSL_INSTALL_DIR}" + --default-library=static + RESULT_VARIABLE MESON_SETUP_RC) +if(MESON_SETUP_RC) + message(FATAL_ERROR "Meson setup for libpsl failed!") +endif() + +# Meson build +message(STATUS "Building libpsl...") +execute_process(COMMAND "${MESON_PATH}" compile -C "${LIBPSL_BUILD_DIR}" + RESULT_VARIABLE MESON_COMPILE_RC +) +if(MESON_COMPILE_RC) + message(FATAL_ERROR "Meson compile for libpsl failed!") +endif() + +# Meson install +message(STATUS "Installing libpsl...") +execute_process(COMMAND "${MESON_PATH}" install -C "${LIBPSL_BUILD_DIR}" + RESULT_VARIABLE MESON_INSTALL_RC) +if(MESON_INSTALL_RC) + message(FATAL_ERROR "Meson install for libpsl failed!") +endif() + +list(APPEND CMAKE_INCLUDE_PATH "${LIBPSL_INSTALL_DIR}/include") + +if(EXISTS "${LIBPSL_INSTALL_DIR}/lib64") + set(LIBPSL_LIBRARY "${LIBPSL_INSTALL_DIR}/lib/libpsl.a") + list(APPEND CMAKE_LIBRARY_PATH "${LIBPSL_INSTALL_DIR}/lib64") +else() + set(LIBPSL_LIBRARY "${LIBPSL_INSTALL_DIR}/lib/libpsl.a") + list(APPEND CMAKE_LIBRARY_PATH "${LIBPSL_INSTALL_DIR}/lib") +endif() + +set(LIBPSL_INCLUDE_DIR "${LIBPSL_INSTALL_DIR}/include") + +# Workaround for Windows compilation. +# Ref: https://github.com/microsoft/vcpkg/pull/38847/files#diff-922fe829582a7e5acf5b0c35181daa63064fc12a2c889c5d89a19e5e02113f1bL44 +add_compile_definitions(PSL_STATIC=1) diff --git a/lib/cpr/cmake/sanitizer.cmake b/lib/cpr/cmake/sanitizer.cmake index 10cdc4624..f49db4d71 100644 --- a/lib/cpr/cmake/sanitizer.cmake +++ b/lib/cpr/cmake/sanitizer.cmake @@ -1,4 +1,3 @@ -include(CheckCXXCompilerFlag) include(CheckCXXSourceRuns) set(ALL_SAN_FLAGS "") @@ -6,62 +5,76 @@ set(ALL_SAN_FLAGS "") # No sanitizers when cross compiling to prevent stuff like this: https://github.com/whoshuu/cpr/issues/582 if(NOT CMAKE_CROSSCOMPILING) # Thread sanitizer - set(THREAD_SAN_FLAGS "-fsanitize=thread") - set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${THREAD_SAN_FLAGS}") - check_cxx_source_runs("int main() { return 0; }" THREAD_SANITIZER_AVAILABLE) - set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) - # Do not add the ThreadSanitizer for builds with all sanitizers enabled because it is incompatible with other sanitizers. + if(CPR_DEBUG_SANITIZER_FLAG_THREAD) + set(THREAD_SAN_FLAGS "-fsanitize=thread") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${THREAD_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" THREAD_SANITIZER_AVAILABLE_AND_ENABLED) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + # Do not add the ThreadSanitizer for builds with all sanitizers enabled because it is incompatible with other sanitizers. + endif() # Address sanitizer - set(ADDR_SAN_FLAGS "-fsanitize=address") - set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) - set(CMAKE_REQUIRED_FLAGS "${ADDR_SAN_FLAGS}") - check_cxx_source_runs("int main() { return 0; }" ADDRESS_SANITIZER_AVAILABLE) - set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) - if(ADDRESS_SANITIZER_AVAILABLE) - set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${ADDR_SAN_FLAGS}") + if(CPR_DEBUG_SANITIZER_FLAG_ADDR) + set(ADDR_SAN_FLAGS "-fsanitize=address") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${ADDR_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" ADDRESS_SANITIZER_AVAILABLE_AND_ENABLED) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(ADDRESS_SANITIZER_AVAILABLE_AND_ENABLED) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${ADDR_SAN_FLAGS}") + endif() endif() # Leak sanitizer - set(LEAK_SAN_FLAGS "-fsanitize=leak") - check_cxx_compiler_flag(${LEAK_SAN_FLAGS} LEAK_SANITIZER_AVAILABLE) - if(LEAK_SANITIZER_AVAILABLE) - set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${LEAK_SAN_FLAGS}") + if(CPR_DEBUG_SANITIZER_FLAG_LEAK) + set(LEAK_SAN_FLAGS "-fsanitize=leak") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${LEAK_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" LEAK_SANITIZER_AVAILABLE_AND_ENABLED) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(LEAK_SANITIZER_AVAILABLE_AND_ENABLED) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${LEAK_SAN_FLAGS}") + endif() endif() # Undefined behavior sanitizer - set(UDEF_SAN_FLAGS "-fsanitize=undefined") - check_cxx_compiler_flag(${UDEF_SAN_FLAGS} UNDEFINED_BEHAVIOUR_SANITIZER_AVAILABLE) - if(UNDEFINED_BEHAVIOUR_SANITIZER_AVAILABLE) - set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${UDEF_SAN_FLAGS}") + if(CPR_DEBUG_SANITIZER_FLAG_UB) + set(UDEF_SAN_FLAGS "-fsanitize=undefined") + set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_FLAGS "${UDEF_SAN_FLAGS}") + check_cxx_source_runs("int main() { return 0; }" UNDEFINED_BEHAVIOR_SANITIZER_AVAILABLE_AND_ENABLED) + set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) + if(UNDEFINED_BEHAVIOR_SANITIZER_AVAILABLE_AND_ENABLED) + set(ALL_SAN_FLAGS "${ALL_SAN_FLAGS} ${UDEF_SAN_FLAGS}") + endif() endif() # All sanitizer (without thread sanitizer) - if(NOT ALL_SAN_FLAGS STREQUAL "") + if(CPR_DEBUG_SANITIZER_FLAG_ALL AND NOT ALL_SAN_FLAGS STREQUAL "") set(PREV_FLAG ${CMAKE_REQUIRED_FLAGS}) set(CMAKE_REQUIRED_FLAGS "${ALL_SAN_FLAGS}") - check_cxx_source_runs("int main() { return 0; }" ALL_SANITIZERS_AVAILABLE) + check_cxx_source_runs("int main() { return 0; }" ALL_SANITIZERS_AVAILABLE_AND_ENABLED) set(CMAKE_REQUIRED_FLAGS ${PREV_FLAG}) endif() - if(CPR_DEBUG_SANITIZER_FLAG_THREAD AND THREAD_SANITIZER_AVAILABLE) + if(THREAD_SANITIZER_AVAILABLE_AND_ENABLED) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${THREAD_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C compiler during thread sanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${THREAD_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C++ compiler during thread sanitizer builds." FORCE) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during thread sanitizer builds" FORCE) - elseif(CPR_DEBUG_SANITIZER_FLAG_ADDR AND ADDRESS_SANITIZER_AVAILABLE) + elseif(ADDRESS_SANITIZER_AVAILABLE_AND_ENABLED) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${ADDR_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C compiler during address sanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${ADDR_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C++ compiler during address sanitizer builds." FORCE) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during address sanitizer builds" FORCE) - elseif(CPR_DEBUG_SANITIZER_FLAG_LEAK AND LEAK_SANITIZER_AVAILABLE) + elseif(LEAK_SANITIZER_AVAILABLE_AND_ENABLED) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${LEAK_SAN_FLAGS} -fno-omit-frame-pointer" CACHE INTERNAL "Flags used by the C compiler during leak sanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${LEAK_SAN_FLAGS} -fno-omit-frame-pointer" CACHE INTERNAL "Flags used by the C++ compiler during leak sanitizer builds." FORCE) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during leak sanitizer builds" FORCE) - elseif(CPR_DEBUG_SANITIZER_FLAG_UB AND UNDEFINED_BEHAVIOUR_SANITIZER_AVAILABLE) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C compiler during undefined behaviour sanitizer builds." FORCE) - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C++ compiler during undefined behaviour sanitizer builds." FORCE) - set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during undefined behaviour sanitizer builds" FORCE) - elseif(CPR_DEBUG_SANITIZER_FLAG_ALL AND ALL_SANITIZERS_AVAILABLE) + elseif(UNDEFINED_BEHAVIOR_SANITIZER_AVAILABLE_AND_ENABLED) + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C compiler during undefined behavior sanitizer builds." FORCE) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${UDEF_SAN_FLAGS}" CACHE INTERNAL "Flags used by the C++ compiler during undefined behavior sanitizer builds." FORCE) + set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during undefined behavior sanitizer builds" FORCE) + elseif(ALL_SANITIZERS_AVAILABLE_AND_ENABLED) set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} ${ALL_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C compiler during most possible sanitizer builds." FORCE) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${ALL_SAN_FLAGS} -fno-omit-frame-pointer -fno-optimize-sibling-calls" CACHE INTERNAL "Flags used by the C++ compiler during most possible sanitizer builds." FORCE) set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}" CACHE INTERNAL "Flags used for the linker during most possible sanitizer builds" FORCE) diff --git a/lib/cpr/cpr-config.cmake b/lib/cpr/cpr-config.cmake deleted file mode 100644 index 58ab48320..000000000 --- a/lib/cpr/cpr-config.cmake +++ /dev/null @@ -1,26 +0,0 @@ -# - C++ Requests, Curl for People -# This module is a libcurl wrapper written in modern C++. -# It provides an easy, intuitive, and efficient interface to -# a host of networking methods. -# -# Finding this module will define the following variables: -# CPR_FOUND - True if the core library has been found -# CPR_LIBRARIES - Path to the core library archive -# CPR_INCLUDE_DIRS - Path to the include directories. Gives access -# to cpr.h, which must be included in every -# file that uses this interface - -find_path(CPR_INCLUDE_DIR - NAMES cpr.h) - -find_library(CPR_LIBRARY - NAMES cpr - HINTS ${CPR_LIBRARY_ROOT}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CPR REQUIRED_VARS CPR_LIBRARY CPR_INCLUDE_DIR) - -if(CPR_FOUND) - set(CPR_LIBRARIES ${CPR_LIBRARY}) - set(CPR_INCLUDE_DIRS ${CPR_INCLUDE_DIR}) -endif() diff --git a/lib/cpr/cpr/CMakeLists.txt b/lib/cpr/cpr/CMakeLists.txt index 0c7083c4e..d25e65af2 100644 --- a/lib/cpr/cpr/CMakeLists.txt +++ b/lib/cpr/cpr/CMakeLists.txt @@ -4,9 +4,9 @@ add_library(cpr accept_encoding.cpp async.cpp auth.cpp - bearer.cpp callback.cpp cert_info.cpp + connection_pool.cpp cookies.cpp cprtypes.cpp curl_container.cpp @@ -19,6 +19,7 @@ add_library(cpr proxies.cpp proxyauth.cpp session.cpp + sse.cpp threadpool.cpp timeout.cpp unix_socket.cpp @@ -65,9 +66,15 @@ if(CPR_USE_SYSTEM_CURL) VERSION ${${PROJECT_NAME}_VERSION} COMPATIBILITY ExactVersion) - configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/cprConfig.cmake.in + if(SSL_BACKEND_USED STREQUAL "OpenSSL") + configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/cprConfig-ssl.cmake.in "${PROJECT_BINARY_DIR}/cpr/cprConfig.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr) + else() + configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/cprConfig.cmake.in + "${PROJECT_BINARY_DIR}/cpr/cprConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr) + endif() install(FILES ${PROJECT_BINARY_DIR}/cpr/cprConfig.cmake ${PROJECT_BINARY_DIR}/cpr/cprConfigVersion.cmake DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpr) diff --git a/lib/cpr/cpr/auth.cpp b/lib/cpr/cpr/auth.cpp index dfda86263..16f61e88a 100644 --- a/lib/cpr/cpr/auth.cpp +++ b/lib/cpr/cpr/auth.cpp @@ -1,5 +1,4 @@ #include "cpr/auth.h" -#include "cpr/util.h" #include @@ -12,10 +11,6 @@ Authentication::Authentication(std::string_view username, std::string_view passw auth_string_ += password; } -Authentication::~Authentication() noexcept { - util::secureStringClear(auth_string_); -} - const char* Authentication::GetAuthString() const noexcept { return auth_string_.c_str(); } diff --git a/lib/cpr/cpr/bearer.cpp b/lib/cpr/cpr/bearer.cpp deleted file mode 100644 index 041b6c187..000000000 --- a/lib/cpr/cpr/bearer.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "cpr/bearer.h" -#include "cpr/util.h" -#include - -namespace cpr { -// Only supported with libcurl >= 7.61.0. -// As an alternative use SetHeader and add the token manually. -#if LIBCURL_VERSION_NUM >= 0x073D00 -Bearer::~Bearer() noexcept { - util::secureStringClear(token_string_); -} - -const char* Bearer::GetToken() const noexcept { - return token_string_.c_str(); -} -#endif -} // namespace cpr diff --git a/lib/cpr/cpr/connection_pool.cpp b/lib/cpr/cpr/connection_pool.cpp new file mode 100644 index 000000000..6afb8643e --- /dev/null +++ b/lib/cpr/cpr/connection_pool.cpp @@ -0,0 +1,39 @@ +#include "cpr/connection_pool.h" +#include +#include +#include + +namespace cpr { +ConnectionPool::ConnectionPool() { + CURLSH* curl_share = curl_share_init(); + this->connection_mutex_ = std::make_shared(); + + auto lock_f = +[](CURL* /*handle*/, curl_lock_data /*data*/, curl_lock_access /*access*/, void* userptr) { + std::mutex* lock = static_cast(userptr); + lock->lock(); // cppcheck-suppress localMutex // False positive: mutex is used as callback for libcurl, not local scope + }; + + auto unlock_f = +[](CURL* /*handle*/, curl_lock_data /*data*/, void* userptr) { + std::mutex* lock = static_cast(userptr); + lock->unlock(); + }; + + curl_share_setopt(curl_share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); + curl_share_setopt(curl_share, CURLSHOPT_USERDATA, this->connection_mutex_.get()); + curl_share_setopt(curl_share, CURLSHOPT_LOCKFUNC, lock_f); + curl_share_setopt(curl_share, CURLSHOPT_UNLOCKFUNC, unlock_f); + + this->curl_sh_ = std::shared_ptr(curl_share, + [](CURLSH* ptr) { + // Make sure to reset callbacks before cleanup to avoid deadlocks + curl_share_setopt(ptr, CURLSHOPT_LOCKFUNC, nullptr); + curl_share_setopt(ptr, CURLSHOPT_UNLOCKFUNC, nullptr); + curl_share_cleanup(ptr); + }); +} + +void ConnectionPool::SetupHandler(CURL* easy_handler) const { + curl_easy_setopt(easy_handler, CURLOPT_SHARE, this->curl_sh_.get()); +} + +} // namespace cpr \ No newline at end of file diff --git a/lib/cpr/cpr/cookies.cpp b/lib/cpr/cpr/cookies.cpp index 2cbbce5ba..a79d8c7d7 100644 --- a/lib/cpr/cpr/cookies.cpp +++ b/lib/cpr/cpr/cookies.cpp @@ -5,6 +5,7 @@ #include #include #include +#include namespace cpr { const std::string& Cookie::GetDomain() const { @@ -53,7 +54,7 @@ std::string Cookies::GetEncoded(const CurlHolder& holder) const { std::stringstream stream; for (const cpr::Cookie& item : cookies_) { // Depending on if encoding is set to "true", we will URL-encode cookies - stream << (encode ? holder.urlEncode(item.GetName()) : item.GetName()) << "="; + stream << (encode ? std::string_view{holder.urlEncode(item.GetName())} : std::string_view{item.GetName()}) << "="; // special case version 1 cookies, which can be distinguished by // beginning and trailing quotes @@ -61,7 +62,7 @@ std::string Cookies::GetEncoded(const CurlHolder& holder) const { stream << item.GetValue(); } else { // Depending on if encoding is set to "true", we will URL-encode cookies - stream << (encode ? holder.urlEncode(item.GetValue()) : item.GetValue()); + stream << (encode ? std::string_view{holder.urlEncode(item.GetValue())} : std::string_view{item.GetValue()}); } stream << "; "; } diff --git a/lib/cpr/cpr/curl_container.cpp b/lib/cpr/cpr/curl_container.cpp index de0c9c3b5..7576d3e22 100644 --- a/lib/cpr/cpr/curl_container.cpp +++ b/lib/cpr/cpr/curl_container.cpp @@ -27,11 +27,11 @@ const std::string CurlContainer::GetContent(const CurlHolder& holder) content += "&"; } - const std::string escapedKey = encode ? holder.urlEncode(parameter.key) : parameter.key; + const std::string escapedKey = encode ? std::string{holder.urlEncode(parameter.key)} : parameter.key; if (parameter.value.empty()) { content += escapedKey; } else { - const std::string escapedValue = encode ? holder.urlEncode(parameter.value) : parameter.value; + const std::string escapedValue = encode ? std::string{holder.urlEncode(parameter.value)} : parameter.value; content += escapedKey + "="; content += escapedValue; } @@ -40,6 +40,25 @@ const std::string CurlContainer::GetContent(const CurlHolder& holder) return content; } +template <> +const std::string CurlContainer::GetContent() const { + std::string content{}; + for (const Parameter& parameter : containerList_) { + if (!content.empty()) { + content += "&"; + } + + if (parameter.value.empty()) { + content += parameter.key; + } else { + content += parameter.key + "="; + content += parameter.value; + } + } + + return content; +} + template <> const std::string CurlContainer::GetContent(const CurlHolder& holder) const { std::string content{}; @@ -47,13 +66,26 @@ const std::string CurlContainer::GetContent(const CurlHolder& holder) cons if (!content.empty()) { content += "&"; } - const std::string escaped = encode ? holder.urlEncode(element.value) : element.value; + const std::string escaped = encode ? std::string{holder.urlEncode(element.value)} : element.value; content += element.key + "=" + escaped; } return content; } +template <> +const std::string CurlContainer::GetContent() const { + std::string content{}; + for (const cpr::Pair& element : containerList_) { + if (!content.empty()) { + content += "&"; + } + content += element.key + "=" + element.value; + } + + return content; +} + template class CurlContainer; template class CurlContainer; diff --git a/lib/cpr/cpr/curlholder.cpp b/lib/cpr/cpr/curlholder.cpp index cee8897c0..3e80ad17e 100644 --- a/lib/cpr/cpr/curlholder.cpp +++ b/lib/cpr/cpr/curlholder.cpp @@ -1,8 +1,9 @@ #include "cpr/curlholder.h" +#include "cpr/secure_string.h" #include #include #include -#include +#include namespace cpr { CurlHolder::CurlHolder() { @@ -28,22 +29,22 @@ CurlHolder::~CurlHolder() { curl_easy_cleanup(handle); } -std::string CurlHolder::urlEncode(const std::string& s) const { +util::SecureString CurlHolder::urlEncode(std::string_view s) const { assert(handle); - char* output = curl_easy_escape(handle, s.c_str(), static_cast(s.length())); + char* output = curl_easy_escape(handle, s.data(), static_cast(s.length())); if (output) { - std::string result = output; + util::SecureString result = output; curl_free(output); return result; } return ""; } -std::string CurlHolder::urlDecode(const std::string& s) const { +util::SecureString CurlHolder::urlDecode(std::string_view s) const { assert(handle); - char* output = curl_easy_unescape(handle, s.c_str(), static_cast(s.length()), nullptr); + char* output = curl_easy_unescape(handle, s.data(), static_cast(s.length()), nullptr); if (output) { - std::string result = output; + util::SecureString result = output; curl_free(output); return result; } diff --git a/lib/cpr/cpr/multiperform.cpp b/lib/cpr/cpr/multiperform.cpp index 6c6ea0c80..272d6c09f 100644 --- a/lib/cpr/cpr/multiperform.cpp +++ b/lib/cpr/cpr/multiperform.cpp @@ -27,16 +27,28 @@ MultiPerform::MultiPerform() : multicurl_(new CurlMultiHolder()) { first_interceptor_ = interceptors_.end(); } +MultiPerform::MultiPerform(MultiPerform&& old) noexcept { + *this = std::move(old); +} + +MultiPerform& MultiPerform::operator=(MultiPerform&& old) noexcept { + sessions_ = std::move(old.sessions_); + multicurl_ = std::move(old.multicurl_); + interceptors_ = std::move(old.interceptors_); + current_interceptor_ = interceptors_.end(); + first_interceptor_ = interceptors_.end(); + return *this; +} + MultiPerform::~MultiPerform() { // Unlock all sessions - for (const std::pair, HttpMethod>& pair : sessions_) { - pair.first->isUsedInMultiPerform = false; + for (const auto& [session, method] : sessions_) { + session->isUsedInMultiPerform = false; // Remove easy handle from multi handle - const CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, pair.first->curl_->handle); + const CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, session->curl_->handle); if (error_code) { std::cerr << "curl_multi_remove_handle() failed, code " << static_cast(error_code) << '\n'; - return; } } } @@ -53,13 +65,6 @@ void MultiPerform::AddSession(std::shared_ptr& session, HttpMethod meth is_download_multi_perform = true; } - // Add easy handle to multi handle - const CURLMcode error_code = curl_multi_add_handle(multicurl_->handle, session->curl_->handle); - if (error_code) { - std::cerr << "curl_multi_add_handle() failed, code " << static_cast(error_code) << '\n'; - return; - } - // Lock session to the multihandle session->isUsedInMultiPerform = true; @@ -68,18 +73,10 @@ void MultiPerform::AddSession(std::shared_ptr& session, HttpMethod meth } void MultiPerform::RemoveSession(const std::shared_ptr& session) { - // Has to be handled before calling curl_multi_remove_handle to avoid it returning something != CURLM_OK. if (sessions_.empty()) { throw std::invalid_argument("Failed to find session!"); } - // Remove easy handle from multihandle - const CURLMcode error_code = curl_multi_remove_handle(multicurl_->handle, session->curl_->handle); - if (error_code != CURLM_OK) { - std::cerr << "curl_multi_remove_handle() failed, code " << static_cast(error_code) << '\n'; - return; - } - // Unlock session session->isUsedInMultiPerform = false; @@ -107,6 +104,12 @@ const std::vector, MultiPerform::HttpMethod>> void MultiPerform::DoMultiPerform() { // Do multi perform until every handle has finished int still_running{0}; + for (const auto& [session, _] : sessions_) { + const CURLMcode error_code = curl_multi_add_handle(multicurl_->handle, session->curl_->handle); + if (error_code && error_code != CURLM_ADDED_ALREADY) { + std::cerr << "curl_multi_add_handle() failed, code " << static_cast(error_code) << '\n'; + } + } do { CURLMcode error_code = curl_multi_perform(multicurl_->handle, &still_running); if (error_code) { @@ -157,17 +160,23 @@ std::vector MultiPerform::ReadMultiInfo(const std::functionhandle, session->curl_->handle); + if (error_code) { + std::cerr << "curl_multi_remove_handle() failed, code " << static_cast(error_code) << '\n'; + } + } + // Sort response objects to match order of added sessions std::vector sorted_responses; - for (const std::pair, HttpMethod>& pair : sessions_) { - Session& current_session = *(pair.first); + for (const auto& [session, _] : sessions_) { + Session& current_session = *session; auto it = std::find_if(responses.begin(), responses.end(), [¤t_session](const Response& response) { return current_session.curl_->handle == response.curl_->handle; }); - const Response current_response = *it; // NOLINT (performance-unnecessary-copy-initialization) False possible + const Response current_response = *it; // NOLINT (performance-unnecessary-copy-initialization) False positive // Erase response from original vector to increase future search speed responses.erase(it); sorted_responses.push_back(current_response); } - return sorted_responses; } @@ -192,28 +201,28 @@ std::vector MultiPerform::MakeDownloadRequest() { } void MultiPerform::PrepareSessions() { - for (const std::pair, HttpMethod>& pair : sessions_) { - switch (pair.second) { + for (const auto& [session, method] : sessions_) { + switch (method) { case HttpMethod::GET_REQUEST: - pair.first->PrepareGet(); + session->PrepareGet(); break; case HttpMethod::POST_REQUEST: - pair.first->PreparePost(); + session->PreparePost(); break; case HttpMethod::PUT_REQUEST: - pair.first->PreparePut(); + session->PreparePut(); break; case HttpMethod::DELETE_REQUEST: - pair.first->PrepareDelete(); + session->PrepareDelete(); break; case HttpMethod::PATCH_REQUEST: - pair.first->PreparePatch(); + session->PreparePatch(); break; case HttpMethod::HEAD_REQUEST: - pair.first->PrepareHead(); + session->PrepareHead(); break; case HttpMethod::OPTIONS_REQUEST: - pair.first->PrepareOptions(); + session->PrepareOptions(); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or download without arguments!" << '\n'; @@ -223,10 +232,10 @@ void MultiPerform::PrepareSessions() { } void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCallback& write) { - const std::pair, HttpMethod>& pair = sessions_[sessions_index]; - switch (pair.second) { + const auto& [session, method] = sessions_[sessions_index]; + switch (method) { case HttpMethod::DOWNLOAD_REQUEST: - pair.first->PrepareDownload(write); + session->PrepareDownload(write); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << '\n'; @@ -235,10 +244,10 @@ void MultiPerform::PrepareDownloadSession(size_t sessions_index, const WriteCall } void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& file) { - const std::pair, HttpMethod>& pair = sessions_[sessions_index]; - switch (pair.second) { + const auto& [session, method] = sessions_[sessions_index]; + switch (method) { case HttpMethod::DOWNLOAD_REQUEST: - pair.first->PrepareDownload(file); + session->PrepareDownload(file); break; default: std::cerr << "PrepareSessions failed: Undefined HttpMethod or non download method with arguments!" << '\n'; @@ -247,8 +256,8 @@ void MultiPerform::PrepareDownloadSession(size_t sessions_index, std::ofstream& } void MultiPerform::SetHttpMethod(HttpMethod method) { - for (std::pair, HttpMethod>& pair : sessions_) { - pair.second = method; + for (auto& [_, session_method] : sessions_) { + session_method = method; } } @@ -332,8 +341,7 @@ std::vector MultiPerform::proceed() { if (!sessions_.empty()) { const bool new_is_download_multi_perform = sessions_.front().second == HttpMethod::DOWNLOAD_REQUEST; - for (const std::pair, HttpMethod>& s : sessions_) { - const HttpMethod method = s.second; + for (const auto& [_, method] : sessions_) { if ((new_is_download_multi_perform && method != HttpMethod::DOWNLOAD_REQUEST) || (!new_is_download_multi_perform && method == HttpMethod::DOWNLOAD_REQUEST)) { throw std::invalid_argument("Failed to proceed with session: Cannot mix download and non-download methods!"); } diff --git a/lib/cpr/cpr/proxyauth.cpp b/lib/cpr/cpr/proxyauth.cpp index b9ed0e92b..78d82919d 100644 --- a/lib/cpr/cpr/proxyauth.cpp +++ b/lib/cpr/cpr/proxyauth.cpp @@ -1,18 +1,23 @@ #include "cpr/proxyauth.h" -#include "cpr/util.h" +#include "cpr/secure_string.h" #include +#include namespace cpr { -EncodedAuthentication::~EncodedAuthentication() noexcept { - util::secureStringClear(username); - util::secureStringClear(password); + +std::string_view EncodedAuthentication::GetUsername() const { + return username; +} + +std::string_view EncodedAuthentication::GetPassword() const { + return password; } -const std::string& EncodedAuthentication::GetUsername() const { +const util::SecureString& EncodedAuthentication::GetUsernameUnderlying() const { return username; } -const std::string& EncodedAuthentication::GetPassword() const { +const util::SecureString& EncodedAuthentication::GetPasswordUnderlying() const { return password; } @@ -20,12 +25,20 @@ bool ProxyAuthentication::has(const std::string& protocol) const { return proxyAuth_.count(protocol) > 0; } -const char* ProxyAuthentication::GetUsername(const std::string& protocol) { - return proxyAuth_[protocol].username.c_str(); +std::string_view ProxyAuthentication::GetUsername(const std::string& protocol) { + return proxyAuth_[protocol].GetUsername(); +} + +std::string_view ProxyAuthentication::GetPassword(const std::string& protocol) { + return proxyAuth_[protocol].GetPassword(); +} + +const util::SecureString& ProxyAuthentication::GetUsernameUnderlying(const std::string& protocol) const { + return proxyAuth_.at(protocol).GetUsernameUnderlying(); } -const char* ProxyAuthentication::GetPassword(const std::string& protocol) { - return proxyAuth_[protocol].password.c_str(); +const util::SecureString& ProxyAuthentication::GetPasswordUnderlying(const std::string& protocol) const { + return proxyAuth_.at(protocol).GetPasswordUnderlying(); } } // namespace cpr diff --git a/lib/cpr/cpr/response.cpp b/lib/cpr/cpr/response.cpp index a711ef27a..4bfa3d5ed 100644 --- a/lib/cpr/cpr/response.cpp +++ b/lib/cpr/cpr/response.cpp @@ -35,6 +35,20 @@ Response::Response(std::shared_ptr curl, std::string&& p_text, std:: uploaded_bytes = uploaded_bytes_double; #endif curl_easy_getinfo(curl_->handle, CURLINFO_REDIRECT_COUNT, &redirect_count); +#if LIBCURL_VERSION_NUM >= 0x071300 // 7.19.0 + char* ip_ptr{nullptr}; + if (curl_easy_getinfo(curl_->handle, CURLINFO_PRIMARY_IP, &ip_ptr) == CURLE_OK && ip_ptr) { + primary_ip = ip_ptr; + } +#endif +#if LIBCURL_VERSION_NUM >= 0x071500 // 7.21.0 + // Ignored here since libcurl uses a long for this. + // NOLINTNEXTLINE(google-runtime-int) + long port = 0; + if (curl_easy_getinfo(curl_->handle, CURLINFO_PRIMARY_PORT, &port) == CURLE_OK) { + primary_port = port; + } +#endif } std::vector Response::GetCertInfos() const { diff --git a/lib/cpr/cpr/session.cpp b/lib/cpr/cpr/session.cpp index 7b9fab33d..a62c8dd53 100644 --- a/lib/cpr/cpr/session.cpp +++ b/lib/cpr/cpr/session.cpp @@ -1,5 +1,6 @@ #include "cpr/session.h" +#include #include #include #include @@ -11,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -25,8 +28,10 @@ #include "cpr/auth.h" #include "cpr/bearer.h" #include "cpr/body.h" +#include "cpr/body_view.h" #include "cpr/callback.h" #include "cpr/connect_timeout.h" +#include "cpr/connection_pool.h" #include "cpr/cookies.h" #include "cpr/cprtypes.h" #include "cpr/curlholder.h" @@ -120,8 +125,8 @@ void Session::prepareProxy() { if (proxies_.has(protocol)) { curl_easy_setopt(curl_->handle, CURLOPT_PROXY, proxies_[protocol].c_str()); if (proxyAuth_.has(protocol)) { - curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsername(protocol)); - curl_easy_setopt(curl_->handle, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPassword(protocol)); + curl_easy_setopt(curl_->handle, CURLOPT_PROXYUSERNAME, proxyAuth_.GetUsernameUnderlying(protocol).c_str()); + curl_easy_setopt(curl_->handle, CURLOPT_PROXYPASSWORD, proxyAuth_.GetPasswordUnderlying(protocol).c_str()); } } } @@ -185,6 +190,16 @@ void Session::prepareCommonShared() { // Proxy: prepareProxy(); + // handle NO_PROXY override passed through Proxies object + // Example: Proxies{"no_proxy": ""} will override environment variable definition with an empty list + const std::array no_proxy{"no_proxy", "NO_PROXY"}; + for (const auto& item : no_proxy) { // cppcheck-suppress useStlAlgorithm + if (proxies_.has(item)) { // cppcheck-suppress useStlAlgorithm + curl_easy_setopt(curl_->handle, CURLOPT_NOPROXY, proxies_[item].c_str()); + break; + } + } + #if LIBCURL_VERSION_NUM >= 0x071506 // 7.21.6 if (acceptEncoding_.empty()) { // Enable all supported built-in compressions @@ -218,7 +233,7 @@ void Session::prepareCommon() { // Set Content: prepareBodyPayloadOrMultipart(); - if (!cbs_->writecb_.callback) { + if (!cbs_->writecb_.callback && !cbs_->ssecb_.callback) { curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeFunction); curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &response_string_); } @@ -278,6 +293,10 @@ void Session::RemoveContent() { curl_mime_free(curl_->multipart); curl_->multipart = nullptr; } + } else if (std::holds_alternative(content_)) { + // set default values, so curl does not send a body in subsequent requests + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, -1); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, nullptr); } content_ = std::monostate{}; } @@ -303,6 +322,12 @@ void Session::SetWriteCallback(const WriteCallback& write) { curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &cbs_->writecb_); } +void Session::SetServerSentEventCallback(const ServerSentEventCallback& sse) { + curl_easy_setopt(curl_->handle, CURLOPT_WRITEFUNCTION, cpr::util::writeSSEFunction); + cbs_->ssecb_ = sse; + curl_easy_setopt(curl_->handle, CURLOPT_WRITEDATA, &cbs_->ssecb_); +} + void Session::SetProgressCallback(const ProgressCallback& progress) { cbs_->progresscb_ = progress; if (isCancellable) { @@ -379,6 +404,11 @@ void Session::SetConnectTimeout(const ConnectTimeout& timeout) { curl_easy_setopt(curl_->handle, CURLOPT_CONNECTTIMEOUT_MS, timeout.Milliseconds()); } +void Session::SetConnectionPool(const ConnectionPool& pool) { + CURL* curl = curl_->handle; + pool.SetupHandler(curl); +} + void Session::SetAuth(const Authentication& auth) { // Ignore here since this has been defined by libcurl. switch (auth.GetAuthMode()) { @@ -398,6 +428,14 @@ void Session::SetAuth(const Authentication& auth) { curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_NEGOTIATE); curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); break; + case AuthMode::ANY: + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_ANY); + curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); + break; + case AuthMode::ANYSAFE: + curl_easy_setopt(curl_->handle, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); + curl_easy_setopt(curl_->handle, CURLOPT_USERPWD, auth.GetAuthString()); + break; } } @@ -469,9 +507,15 @@ void Session::SetBody(Body&& body) { content_ = std::move(body); } +// cppcheck-suppress passedByValue +void Session::SetBodyView(BodyView body) { + static_assert(std::is_trivially_copyable_v, "BodyView expected to be trivially copyable otherwise will need some std::move across codebase"); + content_ = body; +} + void Session::SetLowSpeed(const LowSpeed& low_speed) { curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_LIMIT, static_cast(low_speed.limit)); - curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_TIME, static_cast(low_speed.time)); // cppcheck-suppress y2038-unsafe-call + curl_easy_setopt(curl_->handle, CURLOPT_LOW_SPEED_TIME, static_cast(low_speed.time.count())); // cppcheck-suppress y2038-unsafe-call } void Session::SetVerifySsl(const VerifySsl& verify) { @@ -490,6 +534,20 @@ void Session::SetSslOptions(const SslOptions& options) { curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str()); } } +#if SUPPORT_CURLOPT_SSLCERT_BLOB + else if (!options.cert_blob.empty()) { + std::string cert_blob(options.cert_blob); + curl_blob blob{}; + // NOLINTNEXTLINE (readability-container-data-pointer) + blob.data = &cert_blob[0]; + blob.len = cert_blob.length(); + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERT_BLOB, &blob); + if (!options.cert_type.empty()) { + curl_easy_setopt(curl_->handle, CURLOPT_SSLCERTTYPE, options.cert_type.c_str()); + } + } +#endif if (!options.key_file.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_SSLKEY, options.key_file.c_str()); if (!options.key_type.empty()) { @@ -556,6 +614,16 @@ void Session::SetSslOptions(const SslOptions& options) { if (!options.ca_info.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_CAINFO, options.ca_info.c_str()); } +#if SUPPORT_CURLOPT_CAINFO_BLOB + if (!options.ca_info_blob.empty()) { + std::string cainfo_blob(options.ca_info_blob); + curl_blob blob{}; + blob.data = cainfo_blob.data(); + blob.len = cainfo_blob.length(); + blob.flags = CURL_BLOB_COPY; + curl_easy_setopt(curl_->handle, CURLOPT_CAINFO_BLOB, &blob); + } +#endif if (!options.ca_path.empty()) { curl_easy_setopt(curl_->handle, CURLOPT_CAPATH, options.ca_path.c_str()); } @@ -640,6 +708,11 @@ void Session::SetHttpVersion(const HttpVersion& version) { curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3); break; #endif +#if LIBCURL_VERSION_NUM >= 0x075701 // 7.87.1, but corresponds to 7.88.0 tag + case HttpVersionCode::VERSION_3_0_ONLY: + curl_easy_setopt(curl_->handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY); + break; +#endif default: // Should not happen throw std::invalid_argument("Invalid/Unknown HTTP version type."); @@ -918,7 +991,7 @@ const std::optional Session::intercept() { if (current_interceptor_ == interceptors_.end()) { current_interceptor_ = first_interceptor_; } else { - current_interceptor_++; + ++current_interceptor_; } if (current_interceptor_ != interceptors_.end()) { @@ -947,6 +1020,11 @@ void Session::prepareBodyPayloadOrMultipart() const { const std::string& body = std::get(content_).str(); curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(body.length())); curl_easy_setopt(curl_->handle, CURLOPT_COPYPOSTFIELDS, body.c_str()); + } else if (std::holds_alternative(content_)) { + const std::string_view body = std::get(content_).str(); + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDSIZE_LARGE, static_cast(body.length())); + // NOLINTNEXTLINE (bugprone-suspicious-stringview-data-usage) + curl_easy_setopt(curl_->handle, CURLOPT_POSTFIELDS, body.data()); } else if (std::holds_alternative(content_)) { // Make sure, we have a empty multipart to start with: if (curl_->multipart) { @@ -996,7 +1074,7 @@ void Session::prepareBodyPayloadOrMultipart() const { } [[nodiscard]] bool Session::hasBodyOrPayload() const { - return std::holds_alternative(content_) || std::holds_alternative(content_); + return std::holds_alternative(content_) || std::holds_alternative(content_) || std::holds_alternative(content_); } // clang-format off @@ -1007,6 +1085,7 @@ void Session::SetOption(const HeaderCallback& header) { SetHeaderCallback(header void Session::SetOption(const WriteCallback& write) { SetWriteCallback(write); } void Session::SetOption(const ProgressCallback& progress) { SetProgressCallback(progress); } void Session::SetOption(const DebugCallback& debug) { SetDebugCallback(debug); } +void Session::SetOption(const ServerSentEventCallback& sse) { SetServerSentEventCallback(sse); } void Session::SetOption(const Url& url) { SetUrl(url); } void Session::SetOption(const Parameters& parameters) { SetParameters(parameters); } void Session::SetOption(Parameters&& parameters) { SetParameters(std::move(parameters)); } @@ -1033,6 +1112,8 @@ void Session::SetOption(const Redirect& redirect) { SetRedirect(redirect); } void Session::SetOption(const Cookies& cookies) { SetCookies(cookies); } void Session::SetOption(const Body& body) { SetBody(body); } void Session::SetOption(Body&& body) { SetBody(std::move(body)); } +// cppcheck-suppress passedByValue +void Session::SetOption(BodyView body) { SetBodyView(body); } void Session::SetOption(const LowSpeed& low_speed) { SetLowSpeed(low_speed); } void Session::SetOption(const VerifySsl& verify) { SetVerifySsl(verify); } void Session::SetOption(const Verbose& verbose) { SetVerbose(verbose); } @@ -1047,6 +1128,7 @@ void Session::SetOption(const MultiRange& multi_range) { SetMultiRange(multi_ran void Session::SetOption(const ReserveSize& reserve_size) { SetReserveSize(reserve_size.size); } void Session::SetOption(const AcceptEncoding& accept_encoding) { SetAcceptEncoding(accept_encoding); } void Session::SetOption(AcceptEncoding&& accept_encoding) { SetAcceptEncoding(std::move(accept_encoding)); } +void Session::SetOption(const ConnectionPool& pool) { SetConnectionPool(pool); } // clang-format on void Session::SetCancellationParam(std::shared_ptr param) { diff --git a/lib/cpr/cpr/sse.cpp b/lib/cpr/cpr/sse.cpp new file mode 100644 index 000000000..05ab06fe8 --- /dev/null +++ b/lib/cpr/cpr/sse.cpp @@ -0,0 +1,122 @@ +#include "cpr/sse.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace cpr { + +bool ServerSentEventParser::parse(std::string_view data, const std::function& callback) { + // Append incoming data to buffer + buffer_.append(data); + + // Process complete lines + size_t pos = 0; + while ((pos = buffer_.find('\n')) != std::string::npos) { + std::string line = buffer_.substr(0, pos); + buffer_.erase(0, pos + 1); + + // Remove trailing \r if present (handles both \n and \r\n) + if (!line.empty() && line.back() == '\r') { + line.pop_back(); + } + + if (!processLine(line, callback)) { + return false; + } + } + + return true; +} + +void ServerSentEventParser::reset() { + buffer_.clear(); + current_event_ = ServerSentEvent(); +} + +bool ServerSentEventParser::processLine(const std::string& line, const std::function& callback) { + // Empty line means end of event + if (line.empty()) { + return dispatchEvent(callback); + } + + // Lines starting with ':' are comments, ignore them + if (line[0] == ':') { + return true; + } + + // Find the colon separator + const size_t colon_pos = line.find(':'); + + std::string field; + std::string value; + + if (colon_pos == std::string::npos) { + // No colon, entire line is the field name + field = line; + value = ""; + } else { + field = line.substr(0, colon_pos); + // Skip the colon and optional leading space + size_t value_start = colon_pos + 1; + if (value_start < line.size() && line[value_start] == ' ') { + value_start++; + } + value = line.substr(value_start); + } + + // Process the field + if (field == "event") { + current_event_.event = value; + } else if (field == "data") { + // Multiple data fields are concatenated with newlines + if (!current_event_.data.empty()) { + current_event_.data += '\n'; + } + current_event_.data += value; + } else if (field == "id") { + // Only set id if the value doesn't contain null character + if (value.find('\0') == std::string::npos) { + current_event_.id = value; + } + } else if (field == "retry") { + // Parse retry value as integer + size_t retry_value = 0; + const std::string_view sv(value); + const char* begin = sv.data(); + const char* end = begin + sv.size(); // NOLINT (cppcoreguidelines-pro-bounds-pointer-arithmetic) Required here since Windows and Clang/GCC have different std::string_view iterator implementations + auto [ptr, ec] = std::from_chars(begin, end, retry_value); + if (ec == std::errc()) { + current_event_.retry = retry_value; + } + } + // Unknown fields are ignored per spec + + return true; +} + +bool ServerSentEventParser::dispatchEvent(const std::function& callback) { + // Don't dispatch if data is empty + if (current_event_.data.empty()) { + current_event_ = ServerSentEvent(); + return true; + } + + // Invoke callback with the current event + const bool continue_parsing = callback(std::move(current_event_)); + + // Reset for next event (but keep event type as "message") + current_event_ = ServerSentEvent(); + + return continue_parsing; +} + +bool ServerSentEventCallback::handleData(std::string_view data) { + return parser_.parse(data, [this](ServerSentEvent&& event) { return (*this)(std::move(event)); }); +} + +} // namespace cpr diff --git a/lib/cpr/cpr/util.cpp b/lib/cpr/cpr/util.cpp index a33c138d1..8bf5426a2 100644 --- a/lib/cpr/cpr/util.cpp +++ b/lib/cpr/cpr/util.cpp @@ -3,6 +3,8 @@ #include "cpr/cookies.h" #include "cpr/cprtypes.h" #include "cpr/curlholder.h" +#include "cpr/secure_string.h" +#include "cpr/sse.h" #include #include #include @@ -134,9 +136,9 @@ size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCall return (*header)({ptr, size}) ? size : 0; } -size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data) { +size_t writeFunction(char* ptr, size_t size, size_t nmemb, void* data) { size *= nmemb; - data->append(ptr, size); + static_cast(data)->append(ptr, size); return size; } @@ -151,6 +153,11 @@ size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallba return (*write)({ptr, size}) ? size : 0; } +size_t writeSSEFunction(char* ptr, size_t size, size_t nmemb, ServerSentEventCallback* sse) { + size *= nmemb; + return sse->handleData({ptr, size}) ? size : 0; +} + int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t size, const DebugCallback* debug) { (*debug)(static_cast(type), std::string(data, size)); return 0; @@ -166,7 +173,7 @@ int debugUserFunction(CURL* /*handle*/, curl_infotype type, char* data, size_t s * std::string input = "Hello World!"; * std::string result = holder.urlEncode(input); **/ -std::string urlEncode(const std::string& s) { +util::SecureString urlEncode(std::string_view s) { const CurlHolder holder; // Create a temporary new holder for URL encoding return holder.urlEncode(s); } @@ -181,55 +188,15 @@ std::string urlEncode(const std::string& s) { * std::string input = "Hello%20World%21"; * std::string result = holder.urlDecode(input); **/ -std::string urlDecode(const std::string& s) { +util::SecureString urlDecode(std::string_view s) { const CurlHolder holder; // Create a temporary new holder for URL decoding return holder.urlDecode(s); } -#if defined(__STDC_LIB_EXT1__) -void secureStringClear(std::string& s) { - if (s.empty()) { - return; - } - memset_s(&s.front(), s.length(), 0, s.length()); - s.clear(); -} -#elif defined(_WIN32) -void secureStringClear(std::string& s) { - if (s.empty()) { - return; - } - SecureZeroMemory(&s.front(), s.length()); - s.clear(); -} -#else -#if defined(__clang__) -#pragma clang optimize off // clang -#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__) -#pragma GCC push_options // g++ -#pragma GCC optimize("O0") // g++ -#endif -void secureStringClear(std::string& s) { - if (s.empty()) { - return; - } - // NOLINTNEXTLINE (readability-container-data-pointer) - char* ptr = &(s[0]); - memset(ptr, '\0', s.length()); - s.clear(); -} - -#if defined(__clang__) -#pragma clang optimize on // clang -#elif defined(__GNUC__) || defined(__MINGW32__) || defined(__MINGW32__) || defined(__MINGW64__) -#pragma GCC pop_options // g++ -#endif -#endif - bool isTrue(const std::string& s) { - std::string temp_string{s}; - std::transform(temp_string.begin(), temp_string.end(), temp_string.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); - return temp_string == "true"; + constexpr std::string_view tmp = "true"; + auto [s_it, tmp_it] = std::mismatch(s.begin(), s.end(), tmp.begin(), tmp.end(), [](auto s_c, auto t_c) { return std::tolower(s_c) == t_c; }); + return s_it == s.end() && tmp_it == tmp.end(); } time_t sTimestampToT(const std::string& st) { diff --git a/lib/cpr/include/CMakeLists.txt b/lib/cpr/include/CMakeLists.txt index dc704d56a..74eedd722 100644 --- a/lib/cpr/include/CMakeLists.txt +++ b/lib/cpr/include/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(cpr PRIVATE cpr/auth.h cpr/bearer.h cpr/body.h + cpr/body_view.h cpr/buffer.h cpr/cert_info.h cpr/cookies.h @@ -32,6 +33,7 @@ target_sources(cpr PRIVATE cpr/proxies.h cpr/proxyauth.h cpr/response.h + cpr/secure_string.h cpr/session.h cpr/singleton.h cpr/ssl_ctx.h diff --git a/lib/cpr/include/cpr/api.h b/lib/cpr/include/cpr/api.h index a53418bb2..fa700bed1 100644 --- a/lib/cpr/include/cpr/api.h +++ b/lib/cpr/include/cpr/api.h @@ -40,7 +40,7 @@ template void set_option_internal(Session& session, CurrentType&& current_option, Ts&&... ts) { set_option_internal(session, std::forward(current_option)); - if (std::is_same_v) { + if constexpr (std::is_same_v) { set_option_internal(session, std::forward(ts)...); } else { set_option_internal(session, std::forward(ts)...); @@ -131,7 +131,7 @@ AsyncResponse GetAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto GetCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Get(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Get(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Post methods @@ -152,7 +152,7 @@ AsyncResponse PostAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto PostCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Post(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Post(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Put methods @@ -173,7 +173,7 @@ AsyncResponse PutAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto PutCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Put(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Put(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Head methods @@ -194,7 +194,7 @@ AsyncResponse HeadAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto HeadCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Head(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Head(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Delete methods @@ -215,7 +215,7 @@ AsyncResponse DeleteAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto DeleteCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Delete(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Delete(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Options methods @@ -236,7 +236,7 @@ AsyncResponse OptionsAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto OptionsCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Options(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Options(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Patch methods @@ -257,7 +257,7 @@ AsyncResponse PatchAsync(Ts... ts) { template // NOLINTNEXTLINE(fuchsia-trailing-return) auto PatchCallback(Then then, Ts... ts) { - return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Patch(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); + return cpr::async([](Then then_inner, Ts... ts_inner) { return then_inner(Patch(std::move(ts_inner)...)); }, std::move(then), std::move(ts)...); } // Download methods diff --git a/lib/cpr/include/cpr/async.h b/lib/cpr/include/cpr/async.h index 1305834fb..03a35354b 100644 --- a/lib/cpr/include/cpr/async.h +++ b/lib/cpr/include/cpr/async.h @@ -22,9 +22,15 @@ class GlobalThreadPool : public ThreadPool { * async(std::bind(&Class::mem_fn, &obj)) * async(std::mem_fn(&Class::mem_fn, &obj)) **/ -template +template auto async(Fn&& fn, Args&&... args) { - return AsyncWrapper{GlobalThreadPool::GetInstance()->Submit(std::forward(fn), std::forward(args)...)}; + std::future future = GlobalThreadPool::GetInstance()->Submit(std::forward(fn), std::forward(args)...); + using async_wrapper_t = AsyncWrapper; + if constexpr (isCancellable) { + return async_wrapper_t{std::move(future), std::make_shared(false)}; + } else { + return async_wrapper_t{std::move(future)}; + } } class async { diff --git a/lib/cpr/include/cpr/async_wrapper.h b/lib/cpr/include/cpr/async_wrapper.h index a9fd7be4e..c90ea06c6 100644 --- a/lib/cpr/include/cpr/async_wrapper.h +++ b/lib/cpr/include/cpr/async_wrapper.h @@ -5,10 +5,8 @@ #include #include -#include "cpr/response.h" - namespace cpr { -enum class [[nodiscard]] CancellationResult{failure, success, invalid_operation}; +enum class [[nodiscard]] CancellationResult : uint8_t { failure, success, invalid_operation }; /** * A class template intended to wrap results of async operations (instances of std::future) @@ -17,15 +15,24 @@ enum class [[nodiscard]] CancellationResult{failure, success, invalid_operation} * The RAII semantics are the same as std::future - moveable, not copyable. */ template -class AsyncWrapper { +class AsyncWrapper; + +template +class AsyncWrapper { private: + friend class AsyncWrapper; std::future future; - std::shared_ptr is_cancelled; + + void throw_if_invalid(const char* error) const { + if (!future.valid()) { + throw std::logic_error{error}; + } + } public: // Constructors + AsyncWrapper() = default; explicit AsyncWrapper(std::future&& f) : future{std::move(f)} {} - AsyncWrapper(std::future&& f, std::shared_ptr&& cancelledState) : future{std::move(f)}, is_cancelled{std::move(cancelledState)} {} // Copy Semantics AsyncWrapper(const AsyncWrapper&) = delete; @@ -36,82 +43,100 @@ class AsyncWrapper { AsyncWrapper& operator=(AsyncWrapper&&) noexcept = default; // Destructor - ~AsyncWrapper() { - if constexpr (isCancellable) { - if (is_cancelled) { - is_cancelled->store(true); - } - } - } + ~AsyncWrapper() = default; + // These methods replicate the behaviour of std::future [[nodiscard]] T get() { - if constexpr (isCancellable) { - if (IsCancelled()) { - throw std::logic_error{"Calling AsyncWrapper::get on a cancelled request!"}; - } - } - if (!future.valid()) { - throw std::logic_error{"Calling AsyncWrapper::get when the associated future instance is invalid!"}; - } + throw_if_invalid("Calling AsyncWrapper::get when the associated future instance is invalid!"); return future.get(); } [[nodiscard]] bool valid() const noexcept { - if constexpr (isCancellable) { - return !is_cancelled->load() && future.valid(); - } else { - return future.valid(); - } + return future.valid(); } void wait() const { - if constexpr (isCancellable) { - if (is_cancelled->load()) { - throw std::logic_error{"Calling AsyncWrapper::wait when the associated future is invalid or cancelled!"}; - } - } - if (!future.valid()) { - throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; - } + throw_if_invalid("Calling AsyncWrapper::wait when the associated future is invalid!"); future.wait(); } template std::future_status wait_for(const std::chrono::duration& timeout_duration) const { - if constexpr (isCancellable) { - if (IsCancelled()) { - throw std::logic_error{"Calling AsyncWrapper::wait_for when the associated future is cancelled!"}; - } - } - if (!future.valid()) { - throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; - } + throw_if_invalid("Calling AsyncWrapper::wait_for when the associated future is invalid!"); return future.wait_for(timeout_duration); } template std::future_status wait_until(const std::chrono::time_point& timeout_time) const { - if constexpr (isCancellable) { - if (IsCancelled()) { - throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is cancelled!"}; - } - } - if (!future.valid()) { - throw std::logic_error{"Calling AsyncWrapper::wait_until when the associated future is invalid!"}; - } + throw_if_invalid("Calling AsyncWrapper::wait_until when the associated future is invalid!"); return future.wait_until(timeout_time); } std::shared_future share() noexcept { return future.share(); } +}; + +template +class AsyncWrapper : public AsyncWrapper { + private: + using base = AsyncWrapper; + std::shared_ptr is_cancelled; + + void throw_if_cancelled(const char* error) const { + if (is_cancelled->load()) { + throw std::logic_error{error}; + } + } + + public: + // Constructors + AsyncWrapper(std::future&& f, std::shared_ptr&& cancelledState) : base{std::move(f)}, is_cancelled{std::move(cancelledState)} {} + + // Copy Semantics + AsyncWrapper(const AsyncWrapper&) = delete; + AsyncWrapper& operator=(const AsyncWrapper&) = delete; + + // Move Semantics + AsyncWrapper(AsyncWrapper&&) noexcept = default; + AsyncWrapper& operator=(AsyncWrapper&&) noexcept = default; + + // Destructor + ~AsyncWrapper() { + if (is_cancelled) { + is_cancelled->store(true); + } + } + + [[nodiscard]] T get() { + throw_if_cancelled("Calling AsyncWrapper::get on a cancelled request!"); + return base::get(); + } + + [[nodiscard]] bool valid() const noexcept { + return !is_cancelled->load() && base::future.valid(); + } + + void wait() const { + throw_if_cancelled("Calling AsyncWrapper::wait when the associated future is invalid or cancelled!"); + base::wait(); + } + + template + std::future_status wait_for(const std::chrono::duration& timeout_duration) const { + throw_if_cancelled("Calling AsyncWrapper::wait_for when the associated future is cancelled!"); + return base::wait_for(timeout_duration); + } + + template + std::future_status wait_until(const std::chrono::time_point& timeout_time) const { + throw_if_cancelled("Calling AsyncWrapper::wait_until when the associated future is cancelled!"); + return base::wait_until(timeout_time); + } // Cancellation-related methods CancellationResult Cancel() { - if constexpr (!isCancellable) { - return CancellationResult::invalid_operation; - } - if (!future.valid() || is_cancelled->load()) { + if (!base::future.valid() || is_cancelled->load()) { return CancellationResult::invalid_operation; } is_cancelled->store(true); @@ -119,11 +144,7 @@ class AsyncWrapper { } [[nodiscard]] bool IsCancelled() const { - if constexpr (isCancellable) { - return is_cancelled->load(); - } else { - return false; - } + return is_cancelled->load(); } }; diff --git a/lib/cpr/include/cpr/auth.h b/lib/cpr/include/cpr/auth.h index effa2858a..830b964a9 100644 --- a/lib/cpr/include/cpr/auth.h +++ b/lib/cpr/include/cpr/auth.h @@ -4,23 +4,21 @@ #include #include +#include "cpr/util.h" + namespace cpr { -enum class AuthMode { BASIC, DIGEST, NTLM, NEGOTIATE }; +enum class AuthMode { BASIC, DIGEST, NTLM, NEGOTIATE, ANY, ANYSAFE }; class Authentication { public: Authentication(std::string_view username, std::string_view password, AuthMode auth_mode); - Authentication(const Authentication& other) = default; - ~Authentication() noexcept; - - Authentication& operator=(const Authentication& other) = default; const char* GetAuthString() const noexcept; AuthMode GetAuthMode() const noexcept; private: - std::string auth_string_; + util::SecureString auth_string_; AuthMode auth_mode_; }; diff --git a/lib/cpr/include/cpr/bearer.h b/lib/cpr/include/cpr/bearer.h index 5e58a7d93..7792dc704 100644 --- a/lib/cpr/include/cpr/bearer.h +++ b/lib/cpr/include/cpr/bearer.h @@ -6,6 +6,8 @@ #include +#include "cpr/util.h" + namespace cpr { // Only supported with libcurl >= 7.61.0. @@ -14,18 +16,20 @@ namespace cpr { class Bearer { public: // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - Bearer(std::string token) : token_string_{std::move(token)} {} + Bearer(std::string_view token) : token_string_{token} {} Bearer(const Bearer& other) = default; Bearer(Bearer&& old) noexcept = default; - virtual ~Bearer() noexcept; + virtual ~Bearer() noexcept = default; Bearer& operator=(Bearer&& old) noexcept = default; Bearer& operator=(const Bearer& other) = default; - virtual const char* GetToken() const noexcept; + virtual const char* GetToken() const noexcept { + return token_string_.c_str(); + } protected: - std::string token_string_; + util::SecureString token_string_; }; #endif diff --git a/lib/cpr/include/cpr/body.h b/lib/cpr/include/cpr/body.h index f691d9c62..cea7cdff6 100644 --- a/lib/cpr/include/cpr/body.h +++ b/lib/cpr/include/cpr/body.h @@ -5,7 +5,6 @@ #include #include #include -#include #include "cpr/buffer.h" #include "cpr/cprtypes.h" diff --git a/lib/cpr/include/cpr/body_view.h b/lib/cpr/include/cpr/body_view.h new file mode 100644 index 000000000..3bbf73da0 --- /dev/null +++ b/lib/cpr/include/cpr/body_view.h @@ -0,0 +1,36 @@ +#ifndef CPR_BODY_VIEW_H +#define CPR_BODY_VIEW_H + +#include + +#include "cpr/buffer.h" + +namespace cpr { + +class BodyView final { + public: + BodyView() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + BodyView(std::string_view body) : m_body(body) {} + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + BodyView(const char* body) : m_body(body) {} + BodyView(const char* str, size_t len) : m_body(str, len) {} + // NOLINTNEXTLINE(google-explicit-constructor, cppcoreguidelines-pro-type-reinterpret-cast) + BodyView(const Buffer& buffer) : m_body(reinterpret_cast(buffer.data), static_cast(buffer.datalen)) {} + + BodyView(const BodyView& other) = default; + BodyView(BodyView&& old) noexcept = default; + ~BodyView() = default; + + BodyView& operator=(BodyView&& old) noexcept = default; + BodyView& operator=(const BodyView& other) = default; + + [[nodiscard]] std::string_view str() const { return m_body; } + + private: + std::string_view m_body; +}; + +} // namespace cpr + +#endif diff --git a/lib/cpr/include/cpr/callback.h b/lib/cpr/include/cpr/callback.h index b20ce2ad0..ababb87f7 100644 --- a/lib/cpr/include/cpr/callback.h +++ b/lib/cpr/include/cpr/callback.h @@ -18,6 +18,10 @@ class ReadCallback { ReadCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), size{-1}, callback{std::move(p_callback)} {} ReadCallback(cpr_off_t p_size, std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), size{p_size}, callback{std::move(p_callback)} {} bool operator()(char* buffer, size_t& buffer_size) const { + if(!callback) + { + return true; + } return callback(buffer, buffer_size, userdata); } @@ -30,26 +34,34 @@ class HeaderCallback { public: HeaderCallback() = default; // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - HeaderCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} - bool operator()(const std::string_view& header) const { + HeaderCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + bool operator()(std::string_view header) const { + if(!callback) + { + return true; + } return callback(header, userdata); } intptr_t userdata{}; - std::function callback; + std::function callback; }; class WriteCallback { public: WriteCallback() = default; // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - WriteCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} - bool operator()(const std::string_view& data) const { + WriteCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + bool operator()(std::string_view data) const { + if(!callback) + { + return true; + } return callback(data, userdata); } intptr_t userdata{}; - std::function callback; + std::function callback; }; class ProgressCallback { @@ -58,6 +70,10 @@ class ProgressCallback { // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) ProgressCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} bool operator()(cpr_pf_arg_t downloadTotal, cpr_pf_arg_t downloadNow, cpr_pf_arg_t uploadTotal, cpr_pf_arg_t uploadNow) const { + if(!callback) + { + return true; + } return callback(downloadTotal, downloadNow, uploadTotal, uploadNow, userdata); } @@ -78,13 +94,17 @@ class DebugCallback { }; DebugCallback() = default; // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) - DebugCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} - void operator()(InfoType type, std::string data) const { - callback(type, std::move(data), userdata); + DebugCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + void operator()(InfoType type, std::string_view data) const { + if(!callback) + { + return; + } + callback(type, data, userdata); } intptr_t userdata{}; - std::function callback; + std::function callback; }; /** diff --git a/lib/cpr/include/cpr/connection_pool.h b/lib/cpr/include/cpr/connection_pool.h new file mode 100644 index 000000000..b4a758b4f --- /dev/null +++ b/lib/cpr/include/cpr/connection_pool.h @@ -0,0 +1,80 @@ +#ifndef CPR_CONNECTION_POOL_H +#define CPR_CONNECTION_POOL_H + +#include +#include +#include + +namespace cpr { +/** + * cpr connection pool implementation for sharing connections between HTTP requests. + * + * The ConnectionPool enables connection reuse across multiple HTTP requests to the same host, + * which can significantly improve performance by avoiding the overhead of establishing new + * connections for each request. It uses libcurl's CURLSH (share) interface to manage + * connection sharing in a thread-safe manner. + * + * Example: + * ```cpp + * // Create a connection pool + * cpr::ConnectionPool pool; + * + * // Use the pool with requests to reuse connections + * cpr::Response r1 = cpr::Get(cpr::Url{"http://example.com/api/data"}, pool); + * cpr::Response r2 = cpr::Get(cpr::Url{"http://example.com/api/more"}, pool); + * + * // Or with async requests + * auto future1 = cpr::GetAsync(cpr::Url{"http://example.com/api/data"}, pool); + * auto future2 = cpr::GetAsync(cpr::Url{"http://example.com/api/more"}, pool); + * ``` + **/ +class ConnectionPool { + public: + /** + * Creates a new connection pool with shared connection state. + * Initializes the underlying CURLSH handle and sets up thread-safe locking mechanisms. + **/ + ConnectionPool(); + + /** + * Copy constructor - creates a new connection pool sharing the same connection state. + * Multiple ConnectionPool instances can share the same underlying connection pool. + **/ + ConnectionPool(const ConnectionPool&) = default; + + /** + * Copy assignment operator is deleted to prevent accidental copying. + * Use the copy constructor if you need to share the connection pool. + **/ + ConnectionPool& operator=(const ConnectionPool&) = delete; + + /** + * Configures a CURL easy handle to use this connection pool. + * This method sets up the easy handle to participate in connection sharing + * managed by this pool. + * + * @param easy_handler The CURL easy handle to configure for connection sharing. + **/ + void SetupHandler(CURL* easy_handler) const; + + private: + /** + * Thread-safe mutex used for synchronizing access to shared connections. + * This mutex is passed to libcurl's locking callbacks to ensure thread safety + * when multiple threads access the same connection pool. It's declared first + * to ensure it's destroyed last, after the CURLSH handle that references it. + **/ + std::shared_ptr connection_mutex_; + + /** + * Shared CURL handle (CURLSH) that manages the actual connection sharing. + * This handle maintains the pool of reusable connections and is configured + * with appropriate locking callbacks for thread safety. The shared_ptr uses + * a custom deleter that safely resets the lock/unlock callbacks before + * calling curl_share_cleanup() to prevent use-after-free issues during destruction. + * Declared last to ensure it's destroyed first, before the mutex it references. + **/ + std::shared_ptr curl_sh_; +}; +} // namespace cpr +#endif \ No newline at end of file diff --git a/lib/cpr/include/cpr/cpr.h b/lib/cpr/include/cpr/cpr.h index fbad1726a..a42058fbd 100644 --- a/lib/cpr/include/cpr/cpr.h +++ b/lib/cpr/include/cpr/cpr.h @@ -7,6 +7,7 @@ #include "cpr/callback.h" #include "cpr/cert_info.h" #include "cpr/connect_timeout.h" +#include "cpr/connection_pool.h" #include "cpr/cookies.h" #include "cpr/cprtypes.h" #include "cpr/cprver.h" @@ -32,6 +33,7 @@ #include "cpr/resolve.h" #include "cpr/response.h" #include "cpr/session.h" +#include "cpr/sse.h" #include "cpr/ssl_ctx.h" #include "cpr/ssl_options.h" #include "cpr/status_codes.h" diff --git a/lib/cpr/include/cpr/curl_container.h b/lib/cpr/include/cpr/curl_container.h index c2409b226..3f80586a4 100644 --- a/lib/cpr/include/cpr/curl_container.h +++ b/lib/cpr/include/cpr/curl_container.h @@ -40,8 +40,18 @@ class CurlContainer { void Add(const std::initializer_list&); void Add(const T&); + /** + * Returns the URL using curl_easy_escape(...) for escaping the given parameters. + * Requires `CurlHolder`. + **/ const std::string GetContent(const CurlHolder&) const; + /** + * Returns the URL while ignoring `encode`. This allows calling without + * active `CurlHolder`. + **/ + const std::string GetContent() const; + protected: std::vector containerList_; }; diff --git a/lib/cpr/include/cpr/curlholder.h b/lib/cpr/include/cpr/curlholder.h index cfd0b7137..19ca2ba09 100644 --- a/lib/cpr/include/cpr/curlholder.h +++ b/lib/cpr/include/cpr/curlholder.h @@ -4,9 +4,11 @@ #include #include #include -#include + +#include "cpr/secure_string.h" namespace cpr { + struct CurlHolder { private: /** @@ -41,12 +43,12 @@ struct CurlHolder { /** * Uses curl_easy_escape(...) for escaping the given string. **/ - [[nodiscard]] std::string urlEncode(const std::string& s) const; + [[nodiscard]] util::SecureString urlEncode(std::string_view s) const; /** * Uses curl_easy_unescape(...) for unescaping the given string. **/ - [[nodiscard]] std::string urlDecode(const std::string& s) const; + [[nodiscard]] util::SecureString urlDecode(std::string_view s) const; }; } // namespace cpr diff --git a/lib/cpr/include/cpr/error.h b/lib/cpr/include/cpr/error.h index faacf6b3c..0548ba9e4 100644 --- a/lib/cpr/include/cpr/error.h +++ b/lib/cpr/include/cpr/error.h @@ -3,6 +3,7 @@ #include #include +#include #include "cpr/cprtypes.h" #include @@ -89,6 +90,75 @@ enum class ErrorCode { UNKNOWN_ERROR = 1000, }; +inline const std::unordered_map& get_error_code_to_string_mapping() { + // Use a function-local static rather than inline global objects to avoid the 'double-destructor' problem in MSVC when using /MT flags. + static const std::unordered_map mapping = {{ErrorCode::OK, "OK"}, + {ErrorCode::UNSUPPORTED_PROTOCOL, "UNSUPPORTED_PROTOCOL"}, + {ErrorCode::FAILED_INIT, "FAILED_INIT"}, + {ErrorCode::URL_MALFORMAT, "URL_MALFORMAT"}, + {ErrorCode::NOT_BUILT_IN, "NOT_BUILT_IN"}, + {ErrorCode::COULDNT_RESOLVE_PROXY, "COULDNT_RESOLVE_PROXY"}, + {ErrorCode::COULDNT_RESOLVE_HOST, "COULDNT_RESOLVE_HOST"}, + {ErrorCode::COULDNT_CONNECT, "COULDNT_CONNECT"}, + {ErrorCode::WEIRD_SERVER_REPLY, "WEIRD_SERVER_REPLY"}, + {ErrorCode::REMOTE_ACCESS_DENIED, "REMOTE_ACCESS_DENIED"}, + {ErrorCode::HTTP2, "HTTP2"}, + {ErrorCode::PARTIAL_FILE, "PARTIAL_FILE"}, + {ErrorCode::QUOTE_ERROR, "QUOTE_ERROR"}, + {ErrorCode::HTTP_RETURNED_ERROR, "HTTP_RETURNED_ERROR"}, + {ErrorCode::WRITE_ERROR, "WRITE_ERROR"}, + {ErrorCode::UPLOAD_FAILED, "UPLOAD_FAILED"}, + {ErrorCode::READ_ERROR, "READ_ERROR"}, + {ErrorCode::OUT_OF_MEMORY, "OUT_OF_MEMORY"}, + {ErrorCode::OPERATION_TIMEDOUT, "OPERATION_TIMEDOUT"}, + {ErrorCode::RANGE_ERROR, "RANGE_ERROR"}, + {ErrorCode::HTTP_POST_ERROR, "HTTP_POST_ERROR"}, + {ErrorCode::SSL_CONNECT_ERROR, "SSL_CONNECT_ERROR"}, + {ErrorCode::BAD_DOWNLOAD_RESUME, "BAD_DOWNLOAD_RESUME"}, + {ErrorCode::FILE_COULDNT_READ_FILE, "FILE_COULDNT_READ_FILE"}, + {ErrorCode::FUNCTION_NOT_FOUND, "FUNCTION_NOT_FOUND"}, + {ErrorCode::ABORTED_BY_CALLBACK, "ABORTED_BY_CALLBACK"}, + {ErrorCode::BAD_FUNCTION_ARGUMENT, "BAD_FUNCTION_ARGUMENT"}, + {ErrorCode::INTERFACE_FAILED, "INTERFACE_FAILED"}, + {ErrorCode::TOO_MANY_REDIRECTS, "TOO_MANY_REDIRECTS"}, + {ErrorCode::UNKNOWN_OPTION, "UNKNOWN_OPTION"}, + {ErrorCode::SETOPT_OPTION_SYNTAX, "SETOPT_OPTION_SYNTAX"}, + {ErrorCode::GOT_NOTHING, "GOT_NOTHING"}, + {ErrorCode::SSL_ENGINE_NOTFOUND, "SSL_ENGINE_NOTFOUND"}, + {ErrorCode::SSL_ENGINE_SETFAILED, "SSL_ENGINE_SETFAILED"}, + {ErrorCode::SEND_ERROR, "SEND_ERROR"}, + {ErrorCode::RECV_ERROR, "RECV_ERROR"}, + {ErrorCode::SSL_CERTPROBLEM, "SSL_CERTPROBLEM"}, + {ErrorCode::SSL_CIPHER, "SSL_CIPHER"}, + {ErrorCode::PEER_FAILED_VERIFICATION, "PEER_FAILED_VERIFICATION"}, + {ErrorCode::BAD_CONTENT_ENCODING, "BAD_CONTENT_ENCODING"}, + {ErrorCode::FILESIZE_EXCEEDED, "FILESIZE_EXCEEDED"}, + {ErrorCode::USE_SSL_FAILED, "USE_SSL_FAILED"}, + {ErrorCode::SEND_FAIL_REWIND, "SEND_FAIL_REWIND"}, + {ErrorCode::SSL_ENGINE_INITFAILED, "SSL_ENGINE_INITFAILED"}, + {ErrorCode::LOGIN_DENIED, "LOGIN_DENIED"}, + {ErrorCode::SSL_CACERT_BADFILE, "SSL_CACERT_BADFILE"}, + {ErrorCode::SSL_SHUTDOWN_FAILED, "SSL_SHUTDOWN_FAILED"}, + {ErrorCode::AGAIN, "AGAIN"}, + {ErrorCode::SSL_CRL_BADFILE, "SSL_CRL_BADFILE"}, + {ErrorCode::SSL_ISSUER_ERROR, "SSL_ISSUER_ERROR"}, + {ErrorCode::CHUNK_FAILED, "CHUNK_FAILED"}, + {ErrorCode::NO_CONNECTION_AVAILABLE, "NO_CONNECTION_AVAILABLE"}, + {ErrorCode::SSL_PINNEDPUBKEYNOTMATCH, "SSL_PINNEDPUBKEYNOTMATCH"}, + {ErrorCode::SSL_INVALIDCERTSTATUS, "SSL_INVALIDCERTSTATUS"}, + {ErrorCode::HTTP2_STREAM, "HTTP2_STREAM"}, + {ErrorCode::RECURSIVE_API_CALL, "RECURSIVE_API_CALL"}, + {ErrorCode::AUTH_ERROR, "AUTH_ERROR"}, + {ErrorCode::HTTP3, "HTTP3"}, + {ErrorCode::QUIC_CONNECT_ERROR, "QUIC_CONNECT_ERROR"}, + {ErrorCode::PROXY, "PROXY"}, + {ErrorCode::SSL_CLIENTCERT, "SSL_CLIENTCERT"}, + {ErrorCode::UNRECOVERABLE_POLL, "UNRECOVERABLE_POLL"}, + {ErrorCode::TOO_LARGE, "TOO_LARGE"}, + {ErrorCode::UNKNOWN_ERROR, "UNKNOWN_ERROR"}}; + return mapping; +} + class Error { public: ErrorCode code = ErrorCode::OK; @@ -108,4 +178,11 @@ class Error { } // namespace cpr +namespace std { +inline std::string to_string(const cpr::ErrorCode& code) { + return cpr::get_error_code_to_string_mapping().at(code); +} +} // namespace std + + #endif diff --git a/lib/cpr/include/cpr/http_version.h b/lib/cpr/include/cpr/http_version.h index 45b502873..3d4d58c6c 100644 --- a/lib/cpr/include/cpr/http_version.h +++ b/lib/cpr/include/cpr/http_version.h @@ -46,7 +46,14 @@ enum class HttpVersionCode { * Requires prior knowledge that the server supports HTTP 3.0 since there is no gracefully downgrade. * Fallback to HTTP 1.1 if negotiation fails. **/ - VERSION_3_0 + VERSION_3_0, +#endif +#if LIBCURL_VERSION_NUM >= 0x075701 // 7.87.1, but corresponds to 7.88.0 tag + /** + * Enforce HTTP 3.0 requests without fallback. + * Requires prior knowledge that the server supports HTTP 3.0 since there is no gracefully downgrade. + **/ + VERSION_3_0_ONLY #endif }; diff --git a/lib/cpr/include/cpr/low_speed.h b/lib/cpr/include/cpr/low_speed.h index ff77fd2ed..8f9eb86ab 100644 --- a/lib/cpr/include/cpr/low_speed.h +++ b/lib/cpr/include/cpr/low_speed.h @@ -2,15 +2,20 @@ #define CPR_LOW_SPEED_H #include +#include namespace cpr { class LowSpeed { public: - LowSpeed(const std::int32_t p_limit, const std::int32_t p_time) : limit(p_limit), time(p_time) {} + + [[deprecated("Will be removed in CPR 2.x - Use the constructor with std::chrono::seconds instead of std::int32_t")]] + LowSpeed(const std::int32_t p_limit, const std::int32_t p_time) : limit(p_limit), time(std::chrono::seconds(p_time)) {} + + LowSpeed(const std::int32_t p_limit, const std::chrono::seconds p_time) : limit(p_limit), time(p_time) {} std::int32_t limit; - std::int32_t time; + std::chrono::seconds time; }; } // namespace cpr diff --git a/lib/cpr/include/cpr/multiperform.h b/lib/cpr/include/cpr/multiperform.h index d4ac445ee..a48123792 100644 --- a/lib/cpr/include/cpr/multiperform.h +++ b/lib/cpr/include/cpr/multiperform.h @@ -30,11 +30,11 @@ class MultiPerform { MultiPerform(); MultiPerform(const MultiPerform& other) = delete; - MultiPerform(MultiPerform&& old) = default; + MultiPerform(MultiPerform&& old) noexcept; ~MultiPerform(); MultiPerform& operator=(const MultiPerform& other) = delete; - MultiPerform& operator=(MultiPerform&& old) noexcept = default; + MultiPerform& operator=(MultiPerform&& old) noexcept; std::vector Get(); std::vector Delete(); diff --git a/lib/cpr/include/cpr/proxyauth.h b/lib/cpr/include/cpr/proxyauth.h index 521a0c589..6a00c920e 100644 --- a/lib/cpr/include/cpr/proxyauth.h +++ b/lib/cpr/include/cpr/proxyauth.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "cpr/auth.h" #include "cpr/util.h" @@ -16,20 +17,22 @@ class EncodedAuthentication { public: EncodedAuthentication() = default; - EncodedAuthentication(const std::string& p_username, const std::string& p_password) : username(util::urlEncode(p_username)), password(util::urlEncode(p_password)) {} + EncodedAuthentication(std::string_view p_username, std::string_view p_password) : username(util::urlEncode(p_username)), password(util::urlEncode(p_password)) {} EncodedAuthentication(const EncodedAuthentication& other) = default; EncodedAuthentication(EncodedAuthentication&& old) noexcept = default; - virtual ~EncodedAuthentication() noexcept; + virtual ~EncodedAuthentication() noexcept = default; EncodedAuthentication& operator=(EncodedAuthentication&& old) noexcept = default; EncodedAuthentication& operator=(const EncodedAuthentication& other) = default; - [[nodiscard]] const std::string& GetUsername() const; - [[nodiscard]] const std::string& GetPassword() const; + [[nodiscard]] std::string_view GetUsername() const; + [[nodiscard]] std::string_view GetPassword() const; + [[nodiscard]] const util::SecureString& GetUsernameUnderlying() const; + [[nodiscard]] const util::SecureString& GetPasswordUnderlying() const; private: - std::string username; - std::string password; + util::SecureString username; + util::SecureString password; }; class ProxyAuthentication { @@ -39,8 +42,10 @@ class ProxyAuthentication { explicit ProxyAuthentication(const std::map& auths) : proxyAuth_{auths} {} [[nodiscard]] bool has(const std::string& protocol) const; - const char* GetUsername(const std::string& protocol); - const char* GetPassword(const std::string& protocol); + [[nodiscard]] std::string_view GetUsername(const std::string& protocol); + [[nodiscard]] std::string_view GetPassword(const std::string& protocol); + [[nodiscard]] const util::SecureString& GetUsernameUnderlying(const std::string& protocol) const; + [[nodiscard]] const util::SecureString& GetPasswordUnderlying(const std::string& protocol) const; private: std::map proxyAuth_; diff --git a/lib/cpr/include/cpr/response.h b/lib/cpr/include/cpr/response.h index 3550d0910..5606253b9 100644 --- a/lib/cpr/include/cpr/response.h +++ b/lib/cpr/include/cpr/response.h @@ -42,6 +42,8 @@ class Response { // Ignored here since libcurl uses a long for this. // NOLINTNEXTLINE(google-runtime-int) long redirect_count{}; + std::string primary_ip{}; + std::uint16_t primary_port{}; Response() = default; Response(std::shared_ptr curl, std::string&& p_text, std::string&& p_header_string, Cookies&& p_cookies, Error&& p_error); diff --git a/lib/cpr/include/cpr/secure_string.h b/lib/cpr/include/cpr/secure_string.h new file mode 100644 index 000000000..86c38c6b4 --- /dev/null +++ b/lib/cpr/include/cpr/secure_string.h @@ -0,0 +1,66 @@ +#ifndef CPR_SECURE_STRING_H +#define CPR_SECURE_STRING_H + +#include +#include +#include + +namespace cpr::util { + +// This is an allocator that overwrites memory with zero values before +// deallocating the memory, so as to not leave secrets in unallocated memory +// sections. +template +struct SecureAllocator : private std::allocator { + template + friend struct SecureAllocator; + SecureAllocator() = default; + template + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + SecureAllocator(const SecureAllocator& rhs) noexcept : std::allocator(static_cast&>(rhs)) {} + template + // NOLINTNEXTLINE(google-explicit-constructor,hicpp-explicit-conversions) + SecureAllocator(SecureAllocator&& rhs) noexcept : std::allocator(static_cast&&>(rhs)) {} + template + SecureAllocator& operator=(const SecureAllocator& rhs) noexcept { + static_cast&>(*this) = static_cast&>(rhs); + return *this; + } + template + SecureAllocator& operator=(SecureAllocator&& rhs) noexcept { + static_cast&>(*this) = static_cast&&>(rhs); + return *this; + } + + using value_type = T; + + // NOLINTNEXTLINE(readability-identifier-naming) + T* allocate(std::size_t n) { + return static_cast&>(*this).allocate(n); + } + + // NOLINTNEXTLINE(readability-identifier-naming) + void deallocate(T* p, std::size_t n) { + std::fill_n(p, n, T{}); + static_cast&>(*this).deallocate(p, n); + } + + template + [[nodiscard]] bool IsEqual(const SecureAllocator& rhs) const noexcept { + return static_cast&>(*this) == static_cast&>(rhs); + } +}; +template +bool operator==(const SecureAllocator& lhs, const SecureAllocator& rhs) noexcept { + return lhs.IsEqual(rhs); +} +template +bool operator!=(const SecureAllocator& lhs, const SecureAllocator& rhs) noexcept { + return !lhs.IsEqual(rhs); +} + +using SecureString = std::basic_string, SecureAllocator>; + +} // namespace cpr::util + +#endif // CPR_SECURE_STRING_H diff --git a/lib/cpr/include/cpr/session.h b/lib/cpr/include/cpr/session.h index ee5e45877..0781559f9 100644 --- a/lib/cpr/include/cpr/session.h +++ b/lib/cpr/include/cpr/session.h @@ -15,8 +15,10 @@ #include "cpr/auth.h" #include "cpr/bearer.h" #include "cpr/body.h" +#include "cpr/body_view.h" #include "cpr/callback.h" #include "cpr/connect_timeout.h" +#include "cpr/connection_pool.h" #include "cpr/cookies.h" #include "cpr/cprtypes.h" #include "cpr/curlholder.h" @@ -36,6 +38,7 @@ #include "cpr/reserve_size.h" #include "cpr/resolve.h" #include "cpr/response.h" +#include "cpr/sse.h" #include "cpr/ssl_options.h" #include "cpr/timeout.h" #include "cpr/unix_socket.h" @@ -46,7 +49,7 @@ namespace cpr { using AsyncResponse = AsyncWrapper; -using Content = std::variant; +using Content = std::variant; class Interceptor; class MultiPerform; @@ -71,6 +74,7 @@ class Session : public std::enable_shared_from_this { [[nodiscard]] const Header& GetHeader() const; void SetTimeout(const Timeout& timeout); void SetConnectTimeout(const ConnectTimeout& timeout); + void SetConnectionPool(const ConnectionPool& pool); void SetAuth(const Authentication& auth); // Only supported with libcurl >= 7.61.0. // As an alternative use SetHeader and add the token manually. @@ -90,6 +94,7 @@ class Session : public std::enable_shared_from_this { void SetCookies(const Cookies& cookies); void SetBody(Body&& body); void SetBody(const Body& body); + void SetBodyView(BodyView body); void SetLowSpeed(const LowSpeed& low_speed); void SetVerifySsl(const VerifySsl& verify); void SetUnixSocket(const UnixSocket& unix_socket); @@ -99,6 +104,7 @@ class Session : public std::enable_shared_from_this { void SetWriteCallback(const WriteCallback& write); void SetProgressCallback(const ProgressCallback& progress); void SetDebugCallback(const DebugCallback& debug); + void SetServerSentEventCallback(const ServerSentEventCallback& sse); void SetVerbose(const Verbose& verbose); void SetInterface(const Interface& iface); void SetLocalPort(const LocalPort& local_port); @@ -114,14 +120,14 @@ class Session : public std::enable_shared_from_this { void SetLimitRate(const LimitRate& limit_rate); /** - * Returns a reference to the content sent in previous request. - **/ + * Returns a reference to the content sent in previous request. + **/ [[nodiscard]] const Content& GetContent() const; /** - * Removes the content sent in previous request from internal state, so it will not be sent with the next request. - * Call this before doing a request that is specified not to send a body, e.g. GET. - **/ + * Removes the content sent in previous request from internal state, so it will not be sent with the next request. + * Call this before doing a request that is specified not to send a body, e.g. GET. + **/ void RemoveContent(); // For cancellable requests @@ -135,6 +141,7 @@ class Session : public std::enable_shared_from_this { void SetOption(const Timeout& timeout); void SetOption(const ConnectTimeout& timeout); void SetOption(const Authentication& auth); + void SetOption(const ConnectionPool& pool); // Only supported with libcurl >= 7.61.0. // As an alternative use SetHeader and add the token manually. #if LIBCURL_VERSION_NUM >= 0x073D00 @@ -154,11 +161,13 @@ class Session : public std::enable_shared_from_this { void SetOption(const Cookies& cookies); void SetOption(Body&& body); void SetOption(const Body& body); + void SetOption(BodyView body); void SetOption(const ReadCallback& read); void SetOption(const HeaderCallback& header); void SetOption(const WriteCallback& write); void SetOption(const ProgressCallback& progress); void SetOption(const DebugCallback& debug); + void SetOption(const ServerSentEventCallback& sse); void SetOption(const LowSpeed& low_speed); void SetOption(const VerifySsl& verify); void SetOption(const Verbose& verbose); @@ -270,6 +279,7 @@ class Session : public std::enable_shared_from_this { ProgressCallback progresscb_; DebugCallback debugcb_; CancellationCallback cancellationcb_; + ServerSentEventCallback ssecb_; }; std::unique_ptr cbs_{std::make_unique()}; diff --git a/lib/cpr/include/cpr/sse.h b/lib/cpr/include/cpr/sse.h new file mode 100644 index 000000000..2a137b361 --- /dev/null +++ b/lib/cpr/include/cpr/sse.h @@ -0,0 +1,102 @@ +#ifndef CPR_SSE_H +#define CPR_SSE_H + +#include +#include +#include +#include +#include +#include + +namespace cpr { + +/** + * Represents a Server-Sent Event (SSE) as defined in the HTML5 specification. + * https://html.spec.whatwg.org/multipage/server-sent-events.html + */ +struct ServerSentEvent { + /** + * The event ID. Can be used to track the last received event and resume from there. + */ + std::optional id; + + /** + * The event type. If not specified, defaults to "message". + */ + std::string event{"message"}; + + /** + * The event data. Multiple data fields are concatenated with newlines. + */ + std::string data; + + /** + * The retry time in milliseconds. Used to set the reconnection time. + */ + std::optional retry; + + ServerSentEvent() = default; +}; + +/** + * Parser for Server-Sent Events (SSE) streams. + * This parser handles incoming SSE data according to the HTML5 specification. + */ +class ServerSentEventParser { + public: + ServerSentEventParser() = default; + + /** + * Parse incoming SSE data and invoke the callback for each complete event. + * @param data The incoming data chunk + * @param callback The callback to invoke for each parsed event + * @return true to continue receiving data, false to abort + */ + bool parse(std::string_view data, const std::function& callback); + + /** + * Reset the parser state. + */ + void reset(); + + private: + std::string buffer_; + ServerSentEvent current_event_; + + bool processLine(const std::string& line, const std::function& callback); + bool dispatchEvent(const std::function& callback); +}; + +/** + * Callback for handling Server-Sent Events. + * The callback receives each parsed SSE event and can return false to abort the connection. + */ +class ServerSentEventCallback { + public: + ServerSentEventCallback() = default; + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + ServerSentEventCallback(std::function p_callback, intptr_t p_userdata = 0) : userdata(p_userdata), callback(std::move(p_callback)) {} + + bool operator()(ServerSentEvent&& event) const { + if (!callback) { + return true; + } + return callback(std::move(event), userdata); + } + + /** + * Internal function used to handle raw data chunks and parse them into SSE events. + * This is called by the underlying write callback mechanism. + */ + bool handleData(std::string_view data); + + intptr_t userdata{}; + std::function callback; + + private: + ServerSentEventParser parser_; +}; + +} // namespace cpr + +#endif diff --git a/lib/cpr/include/cpr/ssl_options.h b/lib/cpr/include/cpr/ssl_options.h index 41942a486..d7d43d60c 100644 --- a/lib/cpr/include/cpr/ssl_options.h +++ b/lib/cpr/include/cpr/ssl_options.h @@ -9,6 +9,7 @@ #include #include "cpr/util.h" +#include "util.h" #include #ifndef SUPPORT_ALPN @@ -66,6 +67,12 @@ #ifndef SUPPORT_CURLOPT_SSL_CTX_FUNCTION #define SUPPORT_CURLOPT_SSL_CTX_FUNCTION LIBCURL_VERSION_NUM >= 0x070B00 // 7.11.0 #endif +#ifndef SUPPORT_CURLOPT_CAINFO_BLOB +#define SUPPORT_CURLOPT_CAINFO_BLOB LIBCURL_VERSION_NUM >= 0x074D00 // 7.77.0 +#endif +#ifndef SUPPORT_CURLOPT_SSLCERT_BLOB +#define SUPPORT_CURLOPT_SSLCERT_BLOB LIBCURL_VERSION_NUM >= 0x074700 // 7.71.0 +#endif namespace cpr { @@ -113,6 +120,38 @@ class DerCert : public CertFile { } }; + +#if SUPPORT_CURLOPT_SSLCERT_BLOB +class CertBlob { +public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CertBlob(std::string&& p_blob) : blob(std::move(p_blob)) {} + + virtual ~CertBlob() = default; + + std::string blob; + + virtual const char* GetCertType() const { + return "PEM"; + } +}; + +using PemBlob = CertBlob; + +class DerBlob : public CertBlob { +public: + template + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + DerBlob(BlobType&& p_blob) : CertBlob(std::move(p_blob)) {} + + ~DerBlob() override = default; + + const char* GetCertType() const override { + return "DER"; + } +}; +#endif + // specify private keyfile for TLS and SSL client cert class KeyFile { public: @@ -122,12 +161,10 @@ class KeyFile { template KeyFile(FileType&& p_filename, PassType p_password) : filename(std::forward(p_filename)), password(std::move(p_password)) {} - virtual ~KeyFile() { - util::secureStringClear(password); - } + virtual ~KeyFile() = default; fs::path filename; - std::string password; + util::SecureString password; virtual const char* GetKeyType() const { return "PEM"; @@ -143,12 +180,10 @@ class KeyBlob { template KeyBlob(BlobType&& p_blob, PassType p_password) : blob(std::forward(p_blob)), password(std::move(p_password)) {} - virtual ~KeyBlob() { - util::secureStringClear(password); - } + virtual ~KeyBlob() = default; std::string blob; - std::string password; + util::SecureString password; virtual const char* GetKeyType() const { return "PEM"; @@ -315,6 +350,17 @@ class CaInfo { fs::path filename; }; +#if SUPPORT_CURLOPT_CAINFO_BLOB +// Certificate Authority (CA) bundle as blob +class CaInfoBlob { +public: + // NOLINTNEXTLINE(google-explicit-constructor, hicpp-explicit-conversions) + CaInfoBlob(std::string&& p_blob) : blob(std::move(p_blob)) {} + + std::string blob; +}; +#endif + // specify directory holding CA certificates class CaPath { public: @@ -412,14 +458,17 @@ class NoRevoke { struct SslOptions { // We don't use fs::path here, as this leads to problems using windows std::string cert_file; +#if SUPPORT_CURLOPT_SSLCERT_BLOB + util::SecureString cert_blob; +#endif std::string cert_type; // We don't use fs::path here, as this leads to problems using windows std::string key_file; #if SUPPORT_CURLOPT_SSLKEY_BLOB - std::string key_blob; + util::SecureString key_blob; #endif std::string key_type; - std::string key_pass; + util::SecureString key_pass; std::string pinned_public_key; #if SUPPORT_ALPN bool enable_alpn = true; @@ -439,6 +488,9 @@ struct SslOptions { #endif // We don't use fs::path here, as this leads to problems using windows std::string ca_info; +#if SUPPORT_CURLOPT_CAINFO_BLOB + std::string ca_info_blob; +#endif // We don't use fs::path here, as this leads to problems using windows std::string ca_path; #if SUPPORT_CURLOPT_SSL_CTX_FUNCTION @@ -454,17 +506,16 @@ struct SslOptions { bool session_id_cache = true; #endif - ~SslOptions() noexcept { -#if SUPPORT_CURLOPT_SSLKEY_BLOB - util::secureStringClear(key_blob); -#endif - util::secureStringClear(key_pass); - } - void SetOption(const ssl::CertFile& opt) { cert_file = opt.filename.string(); cert_type = opt.GetCertType(); } +#if SUPPORT_CURLOPT_SSLCERT_BLOB + void SetOption(const ssl::CertBlob& opt) { + cert_blob = opt.blob; + cert_type = opt.GetCertType(); + } +#endif void SetOption(const ssl::KeyFile& opt) { key_file = opt.filename.string(); key_type = opt.GetKeyType(); @@ -566,6 +617,11 @@ struct SslOptions { void SetOption(const ssl::CaInfo& opt) { ca_info = opt.filename.string(); } +#if SUPPORT_CURLOPT_CAINFO_BLOB + void SetOption(const ssl::CaInfoBlob& opt) { + ca_info_blob = opt.blob; + } +#endif void SetOption(const ssl::CaPath& opt) { ca_path = opt.filename.string(); } diff --git a/lib/cpr/include/cpr/status_codes.h b/lib/cpr/include/cpr/status_codes.h index 3a2e9cbf8..53e0d251d 100644 --- a/lib/cpr/include/cpr/status_codes.h +++ b/lib/cpr/include/cpr/status_codes.h @@ -1,100 +1,99 @@ #ifndef CPR_STATUS_CODES #define CPR_STATUS_CODES -#include namespace cpr { namespace status { // Information responses -constexpr std::int32_t HTTP_CONTINUE = 100; -constexpr std::int32_t HTTP_SWITCHING_PROTOCOL = 101; -constexpr std::int32_t HTTP_PROCESSING = 102; -constexpr std::int32_t HTTP_EARLY_HINTS = 103; +constexpr long HTTP_CONTINUE = 100; +constexpr long HTTP_SWITCHING_PROTOCOL = 101; +constexpr long HTTP_PROCESSING = 102; +constexpr long HTTP_EARLY_HINTS = 103; // Successful responses -constexpr std::int32_t HTTP_OK = 200; -constexpr std::int32_t HTTP_CREATED = 201; -constexpr std::int32_t HTTP_ACCEPTED = 202; -constexpr std::int32_t HTTP_NON_AUTHORITATIVE_INFORMATION = 203; -constexpr std::int32_t HTTP_NO_CONTENT = 204; -constexpr std::int32_t HTTP_RESET_CONTENT = 205; -constexpr std::int32_t HTTP_PARTIAL_CONTENT = 206; -constexpr std::int32_t HTTP_MULTI_STATUS = 207; -constexpr std::int32_t HTTP_ALREADY_REPORTED = 208; -constexpr std::int32_t HTTP_IM_USED = 226; +constexpr long HTTP_OK = 200; +constexpr long HTTP_CREATED = 201; +constexpr long HTTP_ACCEPTED = 202; +constexpr long HTTP_NON_AUTHORITATIVE_INFORMATION = 203; +constexpr long HTTP_NO_CONTENT = 204; +constexpr long HTTP_RESET_CONTENT = 205; +constexpr long HTTP_PARTIAL_CONTENT = 206; +constexpr long HTTP_MULTI_STATUS = 207; +constexpr long HTTP_ALREADY_REPORTED = 208; +constexpr long HTTP_IM_USED = 226; // Redirection messages -constexpr std::int32_t HTTP_MULTIPLE_CHOICE = 300; -constexpr std::int32_t HTTP_MOVED_PERMANENTLY = 301; -constexpr std::int32_t HTTP_FOUND = 302; -constexpr std::int32_t HTTP_SEE_OTHER = 303; -constexpr std::int32_t HTTP_NOT_MODIFIED = 304; -constexpr std::int32_t HTTP_USE_PROXY = 305; -constexpr std::int32_t HTTP_UNUSED = 306; -constexpr std::int32_t HTTP_TEMPORARY_REDIRECT = 307; -constexpr std::int32_t HTTP_PERMANENT_REDIRECT = 308; +constexpr long HTTP_MULTIPLE_CHOICE = 300; +constexpr long HTTP_MOVED_PERMANENTLY = 301; +constexpr long HTTP_FOUND = 302; +constexpr long HTTP_SEE_OTHER = 303; +constexpr long HTTP_NOT_MODIFIED = 304; +constexpr long HTTP_USE_PROXY = 305; +constexpr long HTTP_UNUSED = 306; +constexpr long HTTP_TEMPORARY_REDIRECT = 307; +constexpr long HTTP_PERMANENT_REDIRECT = 308; // Client error responses -constexpr std::int32_t HTTP_BAD_REQUEST = 400; -constexpr std::int32_t HTTP_UNAUTHORIZED = 401; -constexpr std::int32_t HTTP_PAYMENT_REQUIRED = 402; -constexpr std::int32_t HTTP_FORBIDDEN = 403; -constexpr std::int32_t HTTP_NOT_FOUND = 404; -constexpr std::int32_t HTTP_METHOD_NOT_ALLOWED = 405; -constexpr std::int32_t HTTP_NOT_ACCEPTABLE = 406; -constexpr std::int32_t HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; -constexpr std::int32_t HTTP_REQUEST_TIMEOUT = 408; -constexpr std::int32_t HTTP_CONFLICT = 409; -constexpr std::int32_t HTTP_GONE = 410; -constexpr std::int32_t HTTP_LENGTH_REQUIRED = 411; -constexpr std::int32_t HTTP_PRECONDITION_FAILED = 412; -constexpr std::int32_t HTTP_PAYLOAD_TOO_LARGE = 413; -constexpr std::int32_t HTTP_URI_TOO_LONG = 414; -constexpr std::int32_t HTTP_UNSUPPORTED_MEDIA_TYPE = 415; -constexpr std::int32_t HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; -constexpr std::int32_t HTTP_EXPECTATION_FAILED = 417; -constexpr std::int32_t HTTP_IM_A_TEAPOT = 418; -constexpr std::int32_t HTTP_MISDIRECTED_REQUEST = 421; -constexpr std::int32_t HTTP_UNPROCESSABLE_ENTITY = 422; -constexpr std::int32_t HTTP_LOCKED = 423; -constexpr std::int32_t HTTP_FAILED_DEPENDENCY = 424; -constexpr std::int32_t HTTP_TOO_EARLY = 425; -constexpr std::int32_t HTTP_UPGRADE_REQUIRED = 426; -constexpr std::int32_t HTTP_PRECONDITION_REQUIRED = 428; -constexpr std::int32_t HTTP_TOO_MANY_REQUESTS = 429; -constexpr std::int32_t HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; -constexpr std::int32_t HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; +constexpr long HTTP_BAD_REQUEST = 400; +constexpr long HTTP_UNAUTHORIZED = 401; +constexpr long HTTP_PAYMENT_REQUIRED = 402; +constexpr long HTTP_FORBIDDEN = 403; +constexpr long HTTP_NOT_FOUND = 404; +constexpr long HTTP_METHOD_NOT_ALLOWED = 405; +constexpr long HTTP_NOT_ACCEPTABLE = 406; +constexpr long HTTP_PROXY_AUTHENTICATION_REQUIRED = 407; +constexpr long HTTP_REQUEST_TIMEOUT = 408; +constexpr long HTTP_CONFLICT = 409; +constexpr long HTTP_GONE = 410; +constexpr long HTTP_LENGTH_REQUIRED = 411; +constexpr long HTTP_PRECONDITION_FAILED = 412; +constexpr long HTTP_PAYLOAD_TOO_LARGE = 413; +constexpr long HTTP_URI_TOO_LONG = 414; +constexpr long HTTP_UNSUPPORTED_MEDIA_TYPE = 415; +constexpr long HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416; +constexpr long HTTP_EXPECTATION_FAILED = 417; +constexpr long HTTP_IM_A_TEAPOT = 418; +constexpr long HTTP_MISDIRECTED_REQUEST = 421; +constexpr long HTTP_UNPROCESSABLE_ENTITY = 422; +constexpr long HTTP_LOCKED = 423; +constexpr long HTTP_FAILED_DEPENDENCY = 424; +constexpr long HTTP_TOO_EARLY = 425; +constexpr long HTTP_UPGRADE_REQUIRED = 426; +constexpr long HTTP_PRECONDITION_REQUIRED = 428; +constexpr long HTTP_TOO_MANY_REQUESTS = 429; +constexpr long HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; +constexpr long HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // Server response errors -constexpr std::int32_t HTTP_INTERNAL_SERVER_ERROR = 500; -constexpr std::int32_t HTTP_NOT_IMPLEMENTED = 501; -constexpr std::int32_t HTTP_BAD_GATEWAY = 502; -constexpr std::int32_t HTTP_SERVICE_UNAVAILABLE = 503; -constexpr std::int32_t HTTP_GATEWAY_TIMEOUT = 504; -constexpr std::int32_t HTTP_HTTP_VERSION_NOT_SUPPORTED = 505; -constexpr std::int32_t HTTP_VARIANT_ALSO_NEGOTIATES = 506; -constexpr std::int32_t HTTP_INSUFFICIENT_STORAGE = 507; -constexpr std::int32_t HTTP_LOOP_DETECTED = 508; -constexpr std::int32_t HTTP_NOT_EXTENDED = 510; -constexpr std::int32_t HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; +constexpr long HTTP_INTERNAL_SERVER_ERROR = 500; +constexpr long HTTP_NOT_IMPLEMENTED = 501; +constexpr long HTTP_BAD_GATEWAY = 502; +constexpr long HTTP_SERVICE_UNAVAILABLE = 503; +constexpr long HTTP_GATEWAY_TIMEOUT = 504; +constexpr long HTTP_HTTP_VERSION_NOT_SUPPORTED = 505; +constexpr long HTTP_VARIANT_ALSO_NEGOTIATES = 506; +constexpr long HTTP_INSUFFICIENT_STORAGE = 507; +constexpr long HTTP_LOOP_DETECTED = 508; +constexpr long HTTP_NOT_EXTENDED = 510; +constexpr long HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; -constexpr std::int32_t INFO_CODE_OFFSET = 100; -constexpr std::int32_t SUCCESS_CODE_OFFSET = 200; -constexpr std::int32_t REDIRECT_CODE_OFFSET = 300; -constexpr std::int32_t CLIENT_ERROR_CODE_OFFSET = 400; -constexpr std::int32_t SERVER_ERROR_CODE_OFFSET = 500; -constexpr std::int32_t MISC_CODE_OFFSET = 600; +constexpr long INFO_CODE_OFFSET = 100; +constexpr long SUCCESS_CODE_OFFSET = 200; +constexpr long REDIRECT_CODE_OFFSET = 300; +constexpr long CLIENT_ERROR_CODE_OFFSET = 400; +constexpr long SERVER_ERROR_CODE_OFFSET = 500; +constexpr long MISC_CODE_OFFSET = 600; -constexpr bool is_informational(const std::int32_t code) { +constexpr bool is_informational(const long code) { return (code >= INFO_CODE_OFFSET && code < SUCCESS_CODE_OFFSET); } -constexpr bool is_success(const std::int32_t code) { +constexpr bool is_success(const long code) { return (code >= SUCCESS_CODE_OFFSET && code < REDIRECT_CODE_OFFSET); } -constexpr bool is_redirect(const std::int32_t code) { +constexpr bool is_redirect(const long code) { return (code >= REDIRECT_CODE_OFFSET && code < CLIENT_ERROR_CODE_OFFSET); } -constexpr bool is_client_error(const std::int32_t code) { +constexpr bool is_client_error(const long code) { return (code >= CLIENT_ERROR_CODE_OFFSET && code < SERVER_ERROR_CODE_OFFSET); } -constexpr bool is_server_error(const std::int32_t code) { +constexpr bool is_server_error(const long code) { return (code >= SERVER_ERROR_CODE_OFFSET && code < MISC_CODE_OFFSET); } } // namespace status } // namespace cpr -#endif \ No newline at end of file +#endif diff --git a/lib/cpr/include/cpr/util.h b/lib/cpr/include/cpr/util.h index cc3d81923..4ffd02df6 100644 --- a/lib/cpr/include/cpr/util.h +++ b/lib/cpr/include/cpr/util.h @@ -2,13 +2,15 @@ #define CPR_UTIL_H #include +#include #include #include #include "cpr/callback.h" #include "cpr/cookies.h" #include "cpr/cprtypes.h" -#include "cpr/curlholder.h" +#include "cpr/secure_string.h" +#include "cpr/sse.h" namespace cpr::util { @@ -16,9 +18,10 @@ Header parseHeader(const std::string& headers, std::string* status_line = nullpt Cookies parseCookies(curl_slist* raw_cookies); size_t readUserFunction(char* ptr, size_t size, size_t nitems, const ReadCallback* read); size_t headerUserFunction(char* ptr, size_t size, size_t nmemb, const HeaderCallback* header); -size_t writeFunction(char* ptr, size_t size, size_t nmemb, std::string* data); +size_t writeFunction(char* ptr, size_t size, size_t nmemb, void* data); size_t writeFileFunction(char* ptr, size_t size, size_t nmemb, std::ofstream* file); size_t writeUserFunction(char* ptr, size_t size, size_t nmemb, const WriteCallback* write); +size_t writeSSEFunction(char* ptr, size_t size, size_t nmemb, ServerSentEventCallback* sse); template int progressUserFunction(const T* progress, cpr_pf_arg_t dltotal, cpr_pf_arg_t dlnow, cpr_pf_arg_t ultotal, cpr_pf_arg_t ulnow) { @@ -30,16 +33,9 @@ int progressUserFunction(const T* progress, cpr_pf_arg_t dltotal, cpr_pf_arg_t d } int debugUserFunction(CURL* handle, curl_infotype type, char* data, size_t size, const DebugCallback* debug); std::vector split(const std::string& to_split, char delimiter); -std::string urlEncode(const std::string& s); -std::string urlDecode(const std::string& s); +util::SecureString urlEncode(std::string_view s); +util::SecureString urlDecode(std::string_view s); -/** - * Override the content of the provided string to hide sensitive data. The - * string content after invocation is undefined. The string size is reset to zero. - * impl. based on: - * https://github.com/ojeda/secure_clear/blob/master/example-implementation/secure_clear.h - **/ -void secureStringClear(std::string& s); bool isTrue(const std::string& s); /** From 464a55cd3d164c1bb2d4e47ec34719355eccd30d Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 29 Nov 2025 19:57:08 +0100 Subject: [PATCH 02/24] Update lib version from v1.1.9.2 to v1.2.9.2 --- CMakeLists.txt | 3 ++- README.md | 2 +- examples/Buttons/InlineKeyboardButton/CMakeLists.txt | 2 +- examples/Buttons/ReplyKeyboardMarkup/CMakeLists.txt | 2 +- examples/EarthquakeBot/CMakeLists.txt | 2 +- examples/EchoBot/CMakeLists.txt | 2 +- examples/PaidSubscriptionBot/CMakeLists.txt | 2 +- examples/PingBot/CMakeLists.txt | 2 +- examples/QrCodeBot/CMakeLists.txt | 2 +- examples/ThreadPoolBot/CMakeLists.txt | 2 +- examples/UrlShortenerBot/CMakeLists.txt | 2 +- examples/WeatherBot/CMakeLists.txt | 2 +- examples/receiveAudio/CMakeLists.txt | 2 +- examples/receiveDocument/CMakeLists.txt | 2 +- examples/receivePhoto/CMakeLists.txt | 2 +- examples/receiveVideo/CMakeLists.txt | 2 +- examples/sendAudio/CMakeLists.txt | 2 +- examples/sendDocument/CMakeLists.txt | 2 +- examples/sendPhoto/CMakeLists.txt | 2 +- examples/sendPoll/CMakeLists.txt | 2 +- examples/sendVideo/CMakeLists.txt | 2 +- 21 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index faed58d52..d1f4b1d4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.20) -project(tgbotxx VERSION "1.1.9.2" DESCRIPTION "Telegram Bot C++ Library" LANGUAGES C CXX) +project(tgbotxx VERSION "1.2.9.2" DESCRIPTION "Telegram Bot C++ Library" LANGUAGES C CXX) # Note: tgbotxx version's patch & build is based on Telegram Bot Api version for compatibility. See https://core.telegram.org/bots/api-changelog # ... @@ -15,6 +15,7 @@ option(BUILD_SHARED_LIBS "Build tgbotxx as a shared/static library." OFF) set(USE_LIBIDN2 OFF CACHE BOOL "Curl option - Use libidn2 for IDN support" FORCE) set(CURL_USE_LIBPSL OFF CACHE BOOL "Curl option - Use libpsl" FORCE) set(BUILD_CURL_EXE OFF CACHE BOOL "Curl option - Build curl executable" FORCE) +set(CPR_CURL_USE_LIBPSL OFF CACHE BOOL "Since curl 8.13 curl depends on libpsl (https://everything.curl.dev/build/deps.html#libpsl). By default cpr keeps this as a secure default enabled wich in turn requires meson as build dependency. If set to OFF, psl support inside curl will be disabled." FORCE) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lib/cpr) ###################################################### diff --git a/README.md b/README.md index 1ffd55d4f..c8c7eb5cd 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" # Compatible with Telegram Api 9.2 + GIT_TAG "v1.2.9.2" # Compatible with Telegram Api 9.2 GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/Buttons/InlineKeyboardButton/CMakeLists.txt b/examples/Buttons/InlineKeyboardButton/CMakeLists.txt index 353c4d0aa..45d5fa86a 100644 --- a/examples/Buttons/InlineKeyboardButton/CMakeLists.txt +++ b/examples/Buttons/InlineKeyboardButton/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/Buttons/ReplyKeyboardMarkup/CMakeLists.txt b/examples/Buttons/ReplyKeyboardMarkup/CMakeLists.txt index 0f97ea33c..22e2b8cb1 100644 --- a/examples/Buttons/ReplyKeyboardMarkup/CMakeLists.txt +++ b/examples/Buttons/ReplyKeyboardMarkup/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/EarthquakeBot/CMakeLists.txt b/examples/EarthquakeBot/CMakeLists.txt index 81e0a018b..a4edd8743 100644 --- a/examples/EarthquakeBot/CMakeLists.txt +++ b/examples/EarthquakeBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/EchoBot/CMakeLists.txt b/examples/EchoBot/CMakeLists.txt index 579a8dc1d..aa5913712 100644 --- a/examples/EchoBot/CMakeLists.txt +++ b/examples/EchoBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/PaidSubscriptionBot/CMakeLists.txt b/examples/PaidSubscriptionBot/CMakeLists.txt index 3753b5091..b48e76c3f 100644 --- a/examples/PaidSubscriptionBot/CMakeLists.txt +++ b/examples/PaidSubscriptionBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/PingBot/CMakeLists.txt b/examples/PingBot/CMakeLists.txt index 3a05451c3..53317ddf3 100644 --- a/examples/PingBot/CMakeLists.txt +++ b/examples/PingBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/QrCodeBot/CMakeLists.txt b/examples/QrCodeBot/CMakeLists.txt index fadb4876b..f053887ec 100644 --- a/examples/QrCodeBot/CMakeLists.txt +++ b/examples/QrCodeBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/ThreadPoolBot/CMakeLists.txt b/examples/ThreadPoolBot/CMakeLists.txt index a684a2c9b..1a13a5c5d 100644 --- a/examples/ThreadPoolBot/CMakeLists.txt +++ b/examples/ThreadPoolBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/UrlShortenerBot/CMakeLists.txt b/examples/UrlShortenerBot/CMakeLists.txt index 0bb265ded..e12c1b525 100644 --- a/examples/UrlShortenerBot/CMakeLists.txt +++ b/examples/UrlShortenerBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/WeatherBot/CMakeLists.txt b/examples/WeatherBot/CMakeLists.txt index 1635f7fca..5feef58a3 100644 --- a/examples/WeatherBot/CMakeLists.txt +++ b/examples/WeatherBot/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/receiveAudio/CMakeLists.txt b/examples/receiveAudio/CMakeLists.txt index 29af6b34a..87985b315 100644 --- a/examples/receiveAudio/CMakeLists.txt +++ b/examples/receiveAudio/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/receiveDocument/CMakeLists.txt b/examples/receiveDocument/CMakeLists.txt index 0581d0f94..bd8c165da 100644 --- a/examples/receiveDocument/CMakeLists.txt +++ b/examples/receiveDocument/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/receivePhoto/CMakeLists.txt b/examples/receivePhoto/CMakeLists.txt index 5ff8fbd56..2563c6680 100644 --- a/examples/receivePhoto/CMakeLists.txt +++ b/examples/receivePhoto/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/receiveVideo/CMakeLists.txt b/examples/receiveVideo/CMakeLists.txt index 749748c6e..f81cc7b3a 100644 --- a/examples/receiveVideo/CMakeLists.txt +++ b/examples/receiveVideo/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/sendAudio/CMakeLists.txt b/examples/sendAudio/CMakeLists.txt index 4ca425dd5..2a3e31cf9 100644 --- a/examples/sendAudio/CMakeLists.txt +++ b/examples/sendAudio/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/sendDocument/CMakeLists.txt b/examples/sendDocument/CMakeLists.txt index 022c7ada4..daa592c9e 100644 --- a/examples/sendDocument/CMakeLists.txt +++ b/examples/sendDocument/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/sendPhoto/CMakeLists.txt b/examples/sendPhoto/CMakeLists.txt index f33c58759..754e72df3 100644 --- a/examples/sendPhoto/CMakeLists.txt +++ b/examples/sendPhoto/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/sendPoll/CMakeLists.txt b/examples/sendPoll/CMakeLists.txt index 9c566f98c..c2f820c8a 100644 --- a/examples/sendPoll/CMakeLists.txt +++ b/examples/sendPoll/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL diff --git a/examples/sendVideo/CMakeLists.txt b/examples/sendVideo/CMakeLists.txt index 7fdd955ec..c50adcc59 100644 --- a/examples/sendVideo/CMakeLists.txt +++ b/examples/sendVideo/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) include(FetchContent) FetchContent_Declare(tgbotxx GIT_REPOSITORY "https://github.com/baderouaich/tgbotxx" - GIT_TAG "v1.1.9.2" + GIT_TAG "v1.2.9.2" GIT_SHALLOW TRUE GIT_PROGRESS TRUE EXCLUDE_FROM_ALL From 5c25016364d072c40e8e0462f4dcd6ffb7acbc22 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 29 Nov 2025 19:57:33 +0100 Subject: [PATCH 03/24] Updated object ChatMemberUpdated --- include/tgbotxx/objects/ChatMemberUpdated.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/tgbotxx/objects/ChatMemberUpdated.hpp b/include/tgbotxx/objects/ChatMemberUpdated.hpp index 689f18e92..b6d4ff54c 100644 --- a/include/tgbotxx/objects/ChatMemberUpdated.hpp +++ b/include/tgbotxx/objects/ChatMemberUpdated.hpp @@ -34,6 +34,9 @@ namespace tgbotxx { /// @brief Optional. Chat invite link, which was used by the user to join the chat; for joining by invite link events only. Ptr inviteLink; + /// @brief Optional. True, if the user joined the chat after sending a direct join request without using an invite link and being approved by an administrator + bool viaJoinRequest{}; + /// @brief Optional. True, if the user joined the chat via a chat folder invite link bool viaChatFolderInviteLink{}; @@ -47,6 +50,7 @@ namespace tgbotxx { OBJECT_SERIALIZE_FIELD_PTR(json, "old_chat_member", oldChatMember); OBJECT_SERIALIZE_FIELD_PTR(json, "new_chat_member", newChatMember); OBJECT_SERIALIZE_FIELD_PTR(json, "invite_link", inviteLink); + OBJECT_SERIALIZE_FIELD(json, "via_join_request", viaJoinRequest); OBJECT_SERIALIZE_FIELD(json, "via_chat_folder_invite_link", viaChatFolderInviteLink); return json; } @@ -59,6 +63,7 @@ namespace tgbotxx { OBJECT_DESERIALIZE_FIELD_PTR(json, "old_chat_member", oldChatMember, false); OBJECT_DESERIALIZE_FIELD_PTR(json, "new_chat_member", newChatMember, false); OBJECT_DESERIALIZE_FIELD_PTR(json, "invite_link", inviteLink, true); + OBJECT_DESERIALIZE_FIELD(json, "via_join_request", viaJoinRequest, false, true); OBJECT_DESERIALIZE_FIELD(json, "via_chat_folder_invite_link", viaChatFolderInviteLink, false, true); } }; From 9289af8f9108bce378f4569e44a29019494d1ead Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 29 Nov 2025 20:43:39 +0100 Subject: [PATCH 04/24] Mark Bot example classes final --- README.md | 2 +- examples/Buttons/InlineKeyboardButton/main.cpp | 2 +- examples/Buttons/ReplyKeyboardMarkup/main.cpp | 2 +- examples/EarthquakeBot/main.cpp | 2 +- examples/EchoBot/main.cpp | 2 +- examples/ErrorHandling/README.md | 2 +- examples/PingBot/main.cpp | 2 +- examples/QrCodeBot/main.cpp | 2 +- examples/ThreadPoolBot/main.cpp | 2 +- examples/UrlShortenerBot/main.cpp | 2 +- examples/WeatherBot/main.cpp | 2 +- examples/receiveAudio/main.cpp | 2 +- examples/receiveDocument/main.cpp | 2 +- examples/receivePhoto/main.cpp | 2 +- examples/receiveVideo/main.cpp | 2 +- examples/sendAudio/main.cpp | 2 +- examples/sendDocument/main.cpp | 2 +- examples/sendPhoto/main.cpp | 2 +- examples/sendPoll/main.cpp | 2 +- examples/sendVideo/main.cpp | 2 +- tests/manual_tests.cpp | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index c8c7eb5cd..66852e961 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Telegram Bot C++ Library #include using namespace tgbotxx; -class MyBot : public Bot { +class MyBot final : public Bot { public: MyBot() : Bot("Bot token here from @BotFather") {} diff --git a/examples/Buttons/InlineKeyboardButton/main.cpp b/examples/Buttons/InlineKeyboardButton/main.cpp index 0b6002e0c..3a038b49f 100644 --- a/examples/Buttons/InlineKeyboardButton/main.cpp +++ b/examples/Buttons/InlineKeyboardButton/main.cpp @@ -4,7 +4,7 @@ using namespace tgbotxx; -class InlineButtonsBot : public Bot { +class InlineButtonsBot final : public Bot { public: InlineButtonsBot(const std::string &token) : Bot(token) {} diff --git a/examples/Buttons/ReplyKeyboardMarkup/main.cpp b/examples/Buttons/ReplyKeyboardMarkup/main.cpp index 3e4c8784e..6181c2f45 100644 --- a/examples/Buttons/ReplyKeyboardMarkup/main.cpp +++ b/examples/Buttons/ReplyKeyboardMarkup/main.cpp @@ -4,7 +4,7 @@ using namespace tgbotxx; -class KeyboardButtonsBot : public Bot { +class KeyboardButtonsBot final : public Bot { public: KeyboardButtonsBot(const std::string &token) : Bot(token) {} diff --git a/examples/EarthquakeBot/main.cpp b/examples/EarthquakeBot/main.cpp index b631d8b4a..423f6203e 100644 --- a/examples/EarthquakeBot/main.cpp +++ b/examples/EarthquakeBot/main.cpp @@ -7,7 +7,7 @@ #include using namespace tgbotxx; -class EarthQuakeBot : public Bot { +class EarthQuakeBot final : public Bot { public: EarthQuakeBot(const std::string &token) : Bot(token) {} diff --git a/examples/EchoBot/main.cpp b/examples/EchoBot/main.cpp index 37c8bc01f..880d6ae8b 100644 --- a/examples/EchoBot/main.cpp +++ b/examples/EchoBot/main.cpp @@ -3,7 +3,7 @@ #include using namespace tgbotxx; -class EchoBot : public Bot { +class EchoBot final : public Bot { public: EchoBot(const std::string& token) : Bot(token) { } diff --git a/examples/ErrorHandling/README.md b/examples/ErrorHandling/README.md index 423f2de07..fbeb96cf1 100644 --- a/examples/ErrorHandling/README.md +++ b/examples/ErrorHandling/README.md @@ -9,7 +9,7 @@ using namespace tgbotxx; using namespace std::chrono_literals; -class MyBot : public Bot { +class MyBot final : public Bot { public: void onLongPollError(const std::string& errorMessage, ErrorCode errorCode) override { // Handle long polling error diff --git a/examples/PingBot/main.cpp b/examples/PingBot/main.cpp index a928b5c54..0f435edf3 100644 --- a/examples/PingBot/main.cpp +++ b/examples/PingBot/main.cpp @@ -3,7 +3,7 @@ #include using namespace tgbotxx; -class PingBot : public Bot { +class PingBot final : public Bot { public: PingBot(const std::string &token) : Bot(token) {} diff --git a/examples/QrCodeBot/main.cpp b/examples/QrCodeBot/main.cpp index 8b677c2b3..3a7c3a1d4 100644 --- a/examples/QrCodeBot/main.cpp +++ b/examples/QrCodeBot/main.cpp @@ -15,7 +15,7 @@ using namespace tgbotxx; #define STB_IMAGE_WRITE_IMPLEMENTATION #include -class QrCodeBot : public Bot { +class QrCodeBot final : public Bot { public: QrCodeBot(const std::string& token) : Bot(token) {} diff --git a/examples/ThreadPoolBot/main.cpp b/examples/ThreadPoolBot/main.cpp index 5e0148578..fa89e777a 100644 --- a/examples/ThreadPoolBot/main.cpp +++ b/examples/ThreadPoolBot/main.cpp @@ -7,7 +7,7 @@ using namespace tgbotxx; using namespace std::chrono_literals; -class ThreadPoolBot : public Bot { +class ThreadPoolBot final : public Bot { public: ThreadPoolBot(const std::string &token) : Bot(token) {} diff --git a/examples/UrlShortenerBot/main.cpp b/examples/UrlShortenerBot/main.cpp index 72cce5855..6d22d6a20 100644 --- a/examples/UrlShortenerBot/main.cpp +++ b/examples/UrlShortenerBot/main.cpp @@ -5,7 +5,7 @@ #include using namespace tgbotxx; -class UrlShortenerBot : public Bot { +class UrlShortenerBot final : public Bot { public: UrlShortenerBot(const std::string& token) : Bot(token) {} diff --git a/examples/WeatherBot/main.cpp b/examples/WeatherBot/main.cpp index ae79d8150..e1f99a067 100644 --- a/examples/WeatherBot/main.cpp +++ b/examples/WeatherBot/main.cpp @@ -12,7 +12,7 @@ using namespace tgbotxx; -class WeatherBot : public Bot { +class WeatherBot final : public Bot { public: WeatherBot(const std::string &token, const std::string &weatherApiKey) : Bot(token), m_weatherApiKey(weatherApiKey) {} diff --git a/examples/receiveAudio/main.cpp b/examples/receiveAudio/main.cpp index 7456a5c71..63796fd3d 100644 --- a/examples/receiveAudio/main.cpp +++ b/examples/receiveAudio/main.cpp @@ -5,7 +5,7 @@ using namespace tgbotxx; #include namespace fs = std::filesystem; -class ReceiveAudioBot : public Bot { +class ReceiveAudioBot final : public Bot { public: ReceiveAudioBot(const std::string& token) : Bot(token) {} diff --git a/examples/receiveDocument/main.cpp b/examples/receiveDocument/main.cpp index 309df3a52..7a177b7c9 100644 --- a/examples/receiveDocument/main.cpp +++ b/examples/receiveDocument/main.cpp @@ -5,7 +5,7 @@ using namespace tgbotxx; #include namespace fs = std::filesystem; -class ReceiveDocumentBot : public Bot { +class ReceiveDocumentBot final : public Bot { public: ReceiveDocumentBot(const std::string& token) : Bot(token) {} diff --git a/examples/receivePhoto/main.cpp b/examples/receivePhoto/main.cpp index d37f3afe5..b85bd7b49 100644 --- a/examples/receivePhoto/main.cpp +++ b/examples/receivePhoto/main.cpp @@ -5,7 +5,7 @@ using namespace tgbotxx; #include namespace fs = std::filesystem; -class ReceivePhotoBot : public Bot { +class ReceivePhotoBot final : public Bot { public: ReceivePhotoBot(const std::string& token) : Bot(token) {} diff --git a/examples/receiveVideo/main.cpp b/examples/receiveVideo/main.cpp index bc5718b00..1fba9112d 100644 --- a/examples/receiveVideo/main.cpp +++ b/examples/receiveVideo/main.cpp @@ -5,7 +5,7 @@ using namespace tgbotxx; #include namespace fs = std::filesystem; -class ReceiveVideoBot : public Bot { +class ReceiveVideoBot final : public Bot { public: ReceiveVideoBot(const std::string& token) : Bot(token) {} diff --git a/examples/sendAudio/main.cpp b/examples/sendAudio/main.cpp index 4a242dd69..36e067c03 100644 --- a/examples/sendAudio/main.cpp +++ b/examples/sendAudio/main.cpp @@ -6,7 +6,7 @@ #include using namespace tgbotxx; -class AudioBot : public Bot { +class AudioBot final : public Bot { public: AudioBot(const std::string &token) : Bot(token) {} diff --git a/examples/sendDocument/main.cpp b/examples/sendDocument/main.cpp index 05464711b..07a75b1a5 100644 --- a/examples/sendDocument/main.cpp +++ b/examples/sendDocument/main.cpp @@ -4,7 +4,7 @@ #include using namespace tgbotxx; -class DocumentBot : public Bot { +class DocumentBot final : public Bot { public: DocumentBot(const std::string &token) : Bot(token) {} diff --git a/examples/sendPhoto/main.cpp b/examples/sendPhoto/main.cpp index 6a4d7fd81..1c02c846d 100644 --- a/examples/sendPhoto/main.cpp +++ b/examples/sendPhoto/main.cpp @@ -6,7 +6,7 @@ #include using namespace tgbotxx; -class PhotoBot : public Bot { +class PhotoBot final : public Bot { public: PhotoBot(const std::string &token) : Bot(token) {} diff --git a/examples/sendPoll/main.cpp b/examples/sendPoll/main.cpp index d155acb92..b516d142b 100644 --- a/examples/sendPoll/main.cpp +++ b/examples/sendPoll/main.cpp @@ -4,7 +4,7 @@ using namespace tgbotxx; -class PollBot : public Bot { +class PollBot final : public Bot { public: explicit PollBot(const std::string &token) : Bot(token) {} diff --git a/examples/sendVideo/main.cpp b/examples/sendVideo/main.cpp index 54e52e3ca..8250733ff 100644 --- a/examples/sendVideo/main.cpp +++ b/examples/sendVideo/main.cpp @@ -6,7 +6,7 @@ #include using namespace tgbotxx; -class VideoBot : public Bot { +class VideoBot final : public Bot { public: VideoBot(const std::string &token) : Bot(token) {} diff --git a/tests/manual_tests.cpp b/tests/manual_tests.cpp index 959bf4316..134990f45 100644 --- a/tests/manual_tests.cpp +++ b/tests/manual_tests.cpp @@ -15,7 +15,7 @@ using namespace tgbotxx; -class MyBot : public Bot { +class MyBot final : public Bot { using CommandHandler = void (MyBot::*)(const Ptr&); const std::map, CommandHandler> commandHandlers = { // command, description: handler From fbc770af66c8bfbf239a93d2d0e61669e8096ff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=8B?= <​> Date: Tue, 16 Dec 2025 13:29:16 +0200 Subject: [PATCH 05/24] Stop getUpdates longpolling on Bot::stop Formerly when `Bot::stop` gets called the former `Bot::m_running` variable is set to false, but any underlying `cpr::Session::Get/Post` will block until timing out. Currently, we set an optional cancellation token from `Bot` in `Api::sendRequest` so that for `Api::getUpdates` we can exit immediately after `Bot::stop` is called, without having to call `std::exit`. --- include/tgbotxx/Api.hpp | 11 ++++++++--- include/tgbotxx/Bot.hpp | 2 +- src/Api.cpp | 16 +++++++++++----- src/Bot.cpp | 16 ++++++++-------- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/include/tgbotxx/Api.hpp b/include/tgbotxx/Api.hpp index a7830c2f1..4ecb0320c 100644 --- a/include/tgbotxx/Api.hpp +++ b/include/tgbotxx/Api.hpp @@ -2214,13 +2214,16 @@ namespace tgbotxx { /// with an offset higher than its update_id. The negative offset can be specified to retrieve updates /// starting from -offset update from the end of the updates queue. All previous updates will be forgotten. /// @param limit Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. + /// @param stop Flag used to stop long polling. /// @returns an Array of Update objects. /// @throws Exception on failure /// @note Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time. /// @note This method will not work if an outgoing webhook is set up. /// @note In order to avoid getting duplicate updates, recalculate offset after each server response. /// @link ref https://core.telegram.org/bots/api#getupdates @endlink - std::vector> getUpdates(std::int32_t offset, std::int32_t limit = 100) const; + std::vector> getUpdates(std::int32_t offset, + std::int32_t limit = 100, + std::shared_ptr> stop = {}) const; /// @brief Use this method to specify a URL and receive incoming updates via an outgoing webhook. /// Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized Update. @@ -2823,6 +2826,8 @@ namespace tgbotxx { const Cache& getCache() const noexcept; private: - nl::json sendRequest(const std::string& endpoint, const cpr::Multipart& data = cpr::Multipart({})) const; + nl::json sendRequest(const std::string& endpoint, + const cpr::Multipart& data = cpr::Multipart({}), + std::shared_ptr> stop = {}) const; }; -} \ No newline at end of file +} diff --git a/include/tgbotxx/Bot.hpp b/include/tgbotxx/Bot.hpp index baccac259..d45cd6713 100644 --- a/include/tgbotxx/Bot.hpp +++ b/include/tgbotxx/Bot.hpp @@ -32,7 +32,7 @@ namespace tgbotxx { Ptr m_api{}; std::vector> m_updates{}; std::int32_t m_lastUpdateId{}; - std::atomic m_running{}; + std::shared_ptr> m_stopped{}; public: /// @brief Constructs a new Bot object diff --git a/src/Api.cpp b/src/Api.cpp index d6cd21f47..1c756bc97 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -69,7 +70,7 @@ const cpr::Timeout Api::DEFAULT_LONG_POLL_TIMEOUT = std::chrono::seconds(60); const cpr::Timeout Api::DEFAULT_UPLOAD_FILES_TIMEOUT = std::chrono::seconds(15 * 60); /// 15min (Files can take longer time to upload. Setting a shorter timeout will stop the request even if the file isn't fully uploaded) const cpr::Timeout Api::DEFAULT_DOWNLOAD_FILES_TIMEOUT = std::chrono::seconds(30 * 60); /// 30min (Files can take longer time to download. Setting a shorter timeout will stop the request even if the file isn't fully downloaded) -void Api::Cache::refresh(const Api* api) { +void Api::Cache::refresh(const Api *api) { // Cache Bot's username & commands botUsername = api->getMe()->username; if (const auto cmds = api->getMyCommands(); !cmds.empty()) { @@ -84,10 +85,15 @@ Api::Api(const std::string& token) : m_token(token) { m_cache.refresh(this); } -nl::json Api::sendRequest(const std::string& endpoint, const cpr::Multipart& data) const { +nl::json Api::sendRequest(const std::string& endpoint, const cpr::Multipart& data, std::shared_ptr> stop) const { cpr::Session session{}; // Note: Why not have one session as a class member to use for all requests ? // You can initiate multiple concurrent requests to the Telegram API, which means // You can call sendMessage while getUpdates long polling is still pending, and you can't do that with a single cpr::Session instance. + + if (stop) { + session.SetCancellationParam(stop); + } + const bool hasFiles = std::ranges::any_of(data.parts, [](const cpr::Part& part) noexcept { return part.is_file; }); session.SetProxies(m_proxies); session.SetConnectTimeout(m_connectTimeout); @@ -1995,7 +2001,7 @@ bool Api::answerCallbackQuery(const std::string& callbackQueryId, } Ptr Api::getUserChatBoosts(const std::variant& chatId, - std::int64_t userId) const { + std::int64_t userId) const { cpr::Multipart data{}; data.parts.reserve(2); data.parts.emplace_back("chat_id", chatId.index() == 0 ? std::to_string(std::get<0>(chatId)) : std::get<1>(chatId)); @@ -2589,7 +2595,7 @@ bool Api::deleteWebhook(bool dropPendingUpdates) const { } /// Called every LONG_POOL_TIMEOUT seconds -std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit) const { +std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit, std::shared_ptr> running) const { std::vector> updates; const cpr::Multipart data = { {"offset", offset}, @@ -2597,7 +2603,7 @@ std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit {"timeout", static_cast(std::chrono::duration_cast(m_longPollTimeout.ms).count())}, {"allowed_updates", nl::json(m_allowedUpdates).dump()}, }; - const nl::json updatesJson = sendRequest("getUpdates", data); + const nl::json updatesJson = sendRequest("getUpdates", data, running); updates.reserve(updatesJson.size()); for (const nl::json& updateObj: updatesJson) { Ptr update = makePtr(updateObj); diff --git a/src/Bot.cpp b/src/Bot.cpp index 3aa8a97c3..1cf83b4ff 100644 --- a/src/Bot.cpp +++ b/src/Bot.cpp @@ -10,7 +10,7 @@ using namespace tgbotxx; Bot::Bot(const std::string& token) - : m_api{new Api(token)}, m_updates{}, m_lastUpdateId{0}, m_running{false} { + : m_api{new Api(token)}, m_updates{}, m_lastUpdateId{0}, m_stopped{std::make_shared>(true)} { } Bot::~Bot() { @@ -18,17 +18,17 @@ Bot::~Bot() { } void Bot::start() { - if (m_running) return; - m_running = true; + if (not *m_stopped) return; + *m_stopped = false; /// Callback -> onStart this->onStart(); /// Start the Long Polling loop... - while (m_running) { + while (not *m_stopped) { try { // Get updates from Telegram (any new events such as messages, commands, files, ...) - m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId); + m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId, {}, m_stopped); } catch (const Exception& err) { /// Callback -> onLongPollError std::string errStr{err.what()}; @@ -57,8 +57,8 @@ void Bot::start() { } void Bot::stop() { - if (not m_running) return; - m_running = false; + if (*m_stopped) return; + *m_stopped = true; /// Callback -> onStop this->onStop(); @@ -66,7 +66,7 @@ void Bot::stop() { const Ptr& Bot::getApi() const noexcept { return m_api; } const Ptr& Bot::api() const noexcept { return m_api; } -bool Bot::isRunning() const noexcept { return m_running; } +bool Bot::isRunning() const noexcept { return not *m_stopped; } void Bot::dispatch(const Ptr& update) { this->dispatchMessages(update); From b2f8b8a829e1013e1f89d67cdb4faef9cecfdb18 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Tue, 16 Dec 2025 20:30:08 +0100 Subject: [PATCH 06/24] Added `ErrorCode::REQUEST_CANCELLED` for detecting long polling cancellations --- include/tgbotxx/Exception.hpp | 5 +++++ src/Api.cpp | 3 +++ src/Bot.cpp | 2 +- tests/manual_tests.cpp | 41 ++++++++++++++++++++++++++++------- 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/include/tgbotxx/Exception.hpp b/include/tgbotxx/Exception.hpp index 7a094cd62..3d26132fd 100644 --- a/include/tgbotxx/Exception.hpp +++ b/include/tgbotxx/Exception.hpp @@ -43,6 +43,9 @@ namespace tgbotxx { /// Bad Gateway BAD_GATEWAY = cpr::status::HTTP_BAD_GATEWAY, + /// Request is cancelled by the sender (e.g. Bot::stop() cancelled long polling request) + REQUEST_CANCELLED = 499, + /// An internal server error occurred while a request was being processed; for example, /// there was a disruption while accessing a database or file storage. /// If a client receives a 500 error, or you believe this error should not have occurred, @@ -62,6 +65,7 @@ namespace tgbotxx { case ErrorCode::CONFLICT: return os << "CONFLICT"; case ErrorCode::TOO_MANY_REQUESTS: return os << "TOO_MANY_REQUESTS"; case ErrorCode::BAD_GATEWAY: return os << "BAD_GATEWAY"; + case ErrorCode::REQUEST_CANCELLED: return os << "REQUEST_CANCELLED"; case ErrorCode::INTERNAL: return os << "INTERNAL"; default: return os << "Unknown ErrorCode (" << static_cast(errorCode) << ')'; } @@ -80,6 +84,7 @@ namespace tgbotxx { case static_cast(ErrorCode::CONFLICT): case static_cast(ErrorCode::TOO_MANY_REQUESTS): case static_cast(ErrorCode::BAD_GATEWAY): + case static_cast(ErrorCode::REQUEST_CANCELLED): case static_cast(ErrorCode::INTERNAL): return true; default: diff --git a/src/Api.cpp b/src/Api.cpp index 1c756bc97..0e806c0bb 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -114,6 +114,9 @@ nl::json Api::sendRequest(const std::string& endpoint, const cpr::Multipart& dat } const cpr::Response res = isMultipart ? session.Post() : session.Get(); + if (res.error) [[unlikely]] { + throw Exception(endpoint + ": " + res.error.message, (res.error.code == cpr::ErrorCode::ABORTED_BY_CALLBACK) ? ErrorCode::REQUEST_CANCELLED : ErrorCode::OTHER); + } if (res.status_code == 0) [[unlikely]] { throw Exception(endpoint + ": Failed to connect to Telegram API with status code: 0. Perhaps you are not connected to the internet?", ErrorCode::OTHER); } diff --git a/src/Bot.cpp b/src/Bot.cpp index 1cf83b4ff..e3e0bebf0 100644 --- a/src/Bot.cpp +++ b/src/Bot.cpp @@ -28,7 +28,7 @@ void Bot::start() { while (not *m_stopped) { try { // Get updates from Telegram (any new events such as messages, commands, files, ...) - m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId, {}, m_stopped); + m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId, 100, m_stopped); } catch (const Exception& err) { /// Callback -> onLongPollError std::string errStr{err.what()}; diff --git a/tests/manual_tests.cpp b/tests/manual_tests.cpp index 959bf4316..6f7e0480e 100644 --- a/tests/manual_tests.cpp +++ b/tests/manual_tests.cpp @@ -15,7 +15,7 @@ using namespace tgbotxx; -class MyBot : public Bot { +class MyBot final : public Bot { using CommandHandler = void (MyBot::*)(const Ptr&); const std::map, CommandHandler> commandHandlers = { // command, description: handler @@ -63,6 +63,7 @@ class MyBot : public Bot { // Drop awaiting updates (when Bot is not running, updates will remain 24 hours // in Telegram server before they get deleted or retrieved by BOT) api()->deleteWebhook(true); + api()->setLongPollTimeout(cpr::Timeout(std::chrono::seconds(300))); // api()->setAllowedUpdates({ // "message_reaction", // "message_reaction_count" @@ -122,7 +123,27 @@ class MyBot : public Bot { } void onLongPollError(const std::string& errorMessage, ErrorCode errorCode) override { - std::cerr << "Long polling error: " << errorMessage << " (" << static_cast(errorCode) << ") (" << errorCode << ")" << std::endl; + switch (errorCode) { + case ErrorCode::OTHER: + std::cerr << "Long polling error: " << errorMessage << " (" << static_cast(errorCode) << ") (" << errorCode << ")" << std::endl; + break; + case ErrorCode::SEE_OTHER: + case ErrorCode::BAD_REQUEST: + case ErrorCode::UNAUTHORIZED: + case ErrorCode::FORBIDDEN: + case ErrorCode::NOT_FOUND: + case ErrorCode::NOT_ACCEPTABLE: + case ErrorCode::FLOOD: + case ErrorCode::CONFLICT: + case ErrorCode::TOO_MANY_REQUESTS: + case ErrorCode::BAD_GATEWAY: + case ErrorCode::INTERNAL: + std::cerr << "Long polling error: " << errorMessage << " (" << static_cast(errorCode) << ") (" << errorCode << ")" << std::endl; + break; + case ErrorCode::REQUEST_CANCELLED: + std::cout << "Long polling cancelled" << std::endl; + break; + } } /// Called when a new command is received (messages with leading '/' char). @@ -577,7 +598,7 @@ class MyBot : public Bot { // add local video to the order list Ptr video(new InputPaidMediaVideo()); video->media = cpr::File{(fs::path(__FILE__).parent_path().parent_path() / "examples/sendVideo/videos/video.mp4").string()}; // Local - video->startTimestamp = 3; // start from 3rd second + video->startTimestamp = 3; // start from 3rd second video->supportsStreaming = true; paidMedia.push_back(video); @@ -604,15 +625,19 @@ static std::string getToken() { } int main() try { - static MyBot bot(getToken()); + // Create the bot + static auto bot = std::make_unique(getToken()); + // Listen to stop signals for graceful bot exit for (const int sig: {SIGINT, SIGTERM}) { std::signal(sig, [](int sig) { - std::cout << "Signal (" << sig << ") received! Stopping Bot. Please wait...\n"; - bot.stop(); - std::exit(EXIT_SUCCESS); + std::cout << "Signal (" << sig << ") received! Stopping Bot. Please wait..." << std::endl; + bot->stop(); }); } - bot.start(); + // Start the bot + std::cout << "Bot Starting..." << std::endl; + bot->start(); + std::cout << "Bot Stopped" << std::endl; return EXIT_SUCCESS; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; From 61cec4428fcce239d605b9c23c268f9aea7b4fc2 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Fri, 19 Dec 2025 22:34:20 +0100 Subject: [PATCH 07/24] Rename cancellationParam flag --- include/tgbotxx/Api.hpp | 6 +++--- include/tgbotxx/Bot.hpp | 1 - src/Api.cpp | 10 +++++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/include/tgbotxx/Api.hpp b/include/tgbotxx/Api.hpp index 4ecb0320c..c79c49a23 100644 --- a/include/tgbotxx/Api.hpp +++ b/include/tgbotxx/Api.hpp @@ -2214,7 +2214,7 @@ namespace tgbotxx { /// with an offset higher than its update_id. The negative offset can be specified to retrieve updates /// starting from -offset update from the end of the updates queue. All previous updates will be forgotten. /// @param limit Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. - /// @param stop Flag used to stop long polling. + /// @param cancellationParam Flag used to stop long polling. /// @returns an Array of Update objects. /// @throws Exception on failure /// @note Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time. @@ -2223,7 +2223,7 @@ namespace tgbotxx { /// @link ref https://core.telegram.org/bots/api#getupdates @endlink std::vector> getUpdates(std::int32_t offset, std::int32_t limit = 100, - std::shared_ptr> stop = {}) const; + const std::shared_ptr>& cancellationParam = nullptr) const; /// @brief Use this method to specify a URL and receive incoming updates via an outgoing webhook. /// Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized Update. @@ -2828,6 +2828,6 @@ namespace tgbotxx { private: nl::json sendRequest(const std::string& endpoint, const cpr::Multipart& data = cpr::Multipart({}), - std::shared_ptr> stop = {}) const; + const std::shared_ptr>& cancellationParam = nullptr) const; }; } diff --git a/include/tgbotxx/Bot.hpp b/include/tgbotxx/Bot.hpp index d45cd6713..b1e69bb1f 100644 --- a/include/tgbotxx/Bot.hpp +++ b/include/tgbotxx/Bot.hpp @@ -28,7 +28,6 @@ namespace tgbotxx { struct PaidMediaPurchased; class Bot { - private: Ptr m_api{}; std::vector> m_updates{}; std::int32_t m_lastUpdateId{}; diff --git a/src/Api.cpp b/src/Api.cpp index 0e806c0bb..878957eea 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -85,13 +85,13 @@ Api::Api(const std::string& token) : m_token(token) { m_cache.refresh(this); } -nl::json Api::sendRequest(const std::string& endpoint, const cpr::Multipart& data, std::shared_ptr> stop) const { +nl::json Api::sendRequest(const std::string& endpoint, const cpr::Multipart& data, const std::shared_ptr>& cancellationParam) const { cpr::Session session{}; // Note: Why not have one session as a class member to use for all requests ? // You can initiate multiple concurrent requests to the Telegram API, which means // You can call sendMessage while getUpdates long polling is still pending, and you can't do that with a single cpr::Session instance. - if (stop) { - session.SetCancellationParam(stop); + if (cancellationParam) { + session.SetCancellationParam(cancellationParam); } const bool hasFiles = std::ranges::any_of(data.parts, [](const cpr::Part& part) noexcept { return part.is_file; }); @@ -2598,7 +2598,7 @@ bool Api::deleteWebhook(bool dropPendingUpdates) const { } /// Called every LONG_POOL_TIMEOUT seconds -std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit, std::shared_ptr> running) const { +std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit, const std::shared_ptr>& cancellationParam) const { std::vector> updates; const cpr::Multipart data = { {"offset", offset}, @@ -2606,7 +2606,7 @@ std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit {"timeout", static_cast(std::chrono::duration_cast(m_longPollTimeout.ms).count())}, {"allowed_updates", nl::json(m_allowedUpdates).dump()}, }; - const nl::json updatesJson = sendRequest("getUpdates", data, running); + const nl::json updatesJson = sendRequest("getUpdates", data, cancellationParam); updates.reserve(updatesJson.size()); for (const nl::json& updateObj: updatesJson) { Ptr update = makePtr(updateObj); From bb2c6d28cdbf998b2384a2ce2b6a7215a752aefc Mon Sep 17 00:00:00 2001 From: baderouaich Date: Fri, 19 Dec 2025 22:50:22 +0100 Subject: [PATCH 08/24] Remove std::exit() from examples - Now the bot cancels the long polling request when its stopped for a graceful exit --- README.md | 3 +-- examples/Buttons/InlineKeyboardButton/main.cpp | 4 ++-- examples/Buttons/ReplyKeyboardMarkup/main.cpp | 4 ++-- examples/EarthquakeBot/main.cpp | 8 +++----- examples/EchoBot/main.cpp | 4 ++-- examples/PaidSubscriptionBot/src/main.cpp | 3 ++- examples/PingBot/main.cpp | 4 ++-- examples/QrCodeBot/main.cpp | 6 +++--- examples/ThreadPoolBot/main.cpp | 8 +++++--- examples/UrlShortenerBot/main.cpp | 2 +- examples/WeatherBot/main.cpp | 3 ++- examples/receiveAudio/main.cpp | 3 +-- examples/receiveDocument/main.cpp | 3 +-- examples/receivePhoto/main.cpp | 3 +-- examples/receiveVideo/main.cpp | 4 ++-- examples/sendAudio/main.cpp | 4 ++-- examples/sendDocument/main.cpp | 4 ++-- examples/sendPhoto/main.cpp | 4 ++-- examples/sendPoll/main.cpp | 4 ++-- examples/sendVideo/main.cpp | 4 ++-- 20 files changed, 40 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 66852e961..2120bd839 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,7 @@ private: // Called when Bot receives a new message of any kind // NB: Ptr = std::shared_ptr void onAnyMessage(const Ptr& message) override { - std::string reply = "Hi " + message->from->firstName - + "!, got your message!"; + std::string reply = "Hi " + message->from->firstName + ", got your message!"; api()->sendMessage(message->chat->id, reply); } diff --git a/examples/Buttons/InlineKeyboardButton/main.cpp b/examples/Buttons/InlineKeyboardButton/main.cpp index 3a038b49f..5ab4c43c0 100644 --- a/examples/Buttons/InlineKeyboardButton/main.cpp +++ b/examples/Buttons/InlineKeyboardButton/main.cpp @@ -67,11 +67,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new InlineButtonsBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/Buttons/ReplyKeyboardMarkup/main.cpp b/examples/Buttons/ReplyKeyboardMarkup/main.cpp index 6181c2f45..8f040c325 100644 --- a/examples/Buttons/ReplyKeyboardMarkup/main.cpp +++ b/examples/Buttons/ReplyKeyboardMarkup/main.cpp @@ -67,11 +67,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new KeyboardButtonsBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/EarthquakeBot/main.cpp b/examples/EarthquakeBot/main.cpp index 423f6203e..74a9af370 100644 --- a/examples/EarthquakeBot/main.cpp +++ b/examples/EarthquakeBot/main.cpp @@ -140,7 +140,6 @@ class EarthQuakeBot final : public Bot { }; -static std::unique_ptr BOT; int main(int argc, const char *argv[]) { if (argc < 2) { @@ -148,15 +147,14 @@ int main(int argc, const char *argv[]) { return EXIT_FAILURE; } + static std::unique_ptr BOT = std::make_unique(argv[1]); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); - - BOT = std::make_unique(argv[1]); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/EchoBot/main.cpp b/examples/EchoBot/main.cpp index 880d6ae8b..bc330070c 100644 --- a/examples/EchoBot/main.cpp +++ b/examples/EchoBot/main.cpp @@ -70,11 +70,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new EchoBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/PaidSubscriptionBot/src/main.cpp b/examples/PaidSubscriptionBot/src/main.cpp index e4d91738d..3848efe73 100644 --- a/examples/PaidSubscriptionBot/src/main.cpp +++ b/examples/PaidSubscriptionBot/src/main.cpp @@ -19,13 +19,14 @@ int main(int argc, const char *argv[]) { { std::signal(sig, [](int s) { if(BOT) { + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(s == SIGINT ? EXIT_SUCCESS : EXIT_FAILURE); }); } // Start the bot BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/PingBot/main.cpp b/examples/PingBot/main.cpp index 0f435edf3..5f1f6feb6 100644 --- a/examples/PingBot/main.cpp +++ b/examples/PingBot/main.cpp @@ -46,11 +46,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new PingBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/QrCodeBot/main.cpp b/examples/QrCodeBot/main.cpp index 3a7c3a1d4..5c72d28d0 100644 --- a/examples/QrCodeBot/main.cpp +++ b/examples/QrCodeBot/main.cpp @@ -135,15 +135,15 @@ int main(int argc, const char *argv[]) { std::cerr << "Usage:\nqrcode_bot \"BOT_TOKEN\"\n"; return EXIT_FAILURE; } - static std::unique_ptr BOT; + static std::unique_ptr BOT = std::make_unique(argv[1]); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); - BOT = std::make_unique(argv[1]); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/ThreadPoolBot/main.cpp b/examples/ThreadPoolBot/main.cpp index fa89e777a..2ca84c1c4 100644 --- a/examples/ThreadPoolBot/main.cpp +++ b/examples/ThreadPoolBot/main.cpp @@ -9,7 +9,7 @@ using namespace std::chrono_literals; class ThreadPoolBot final : public Bot { public: - ThreadPoolBot(const std::string &token) : Bot(token) {} + explicit ThreadPoolBot(const std::string &token) : Bot(token) {} private: cpr::ThreadPool threadPool; @@ -30,9 +30,10 @@ class ThreadPoolBot final : public Bot { } void onStop() override { - std::cout << "Bot Stopped\n"; + std::cout << "Stopping thread pool...\n"; // Stop the thread pool threadPool.Stop(); + std::cout << "Thread pool stopped.\n"; } @@ -78,12 +79,13 @@ int main(int argc, const char *argv[]) { // Graceful Bot exit CTRL+C std::signal(SIGINT, [](int s) { if (BOT) { + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); // Start the bot BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/UrlShortenerBot/main.cpp b/examples/UrlShortenerBot/main.cpp index 6d22d6a20..4dde026aa 100644 --- a/examples/UrlShortenerBot/main.cpp +++ b/examples/UrlShortenerBot/main.cpp @@ -56,8 +56,8 @@ int main(int argc, const char *argv[]) { std::cout << "Stopping Bot. Please wait...\n"; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/WeatherBot/main.cpp b/examples/WeatherBot/main.cpp index e1f99a067..d610fa287 100644 --- a/examples/WeatherBot/main.cpp +++ b/examples/WeatherBot/main.cpp @@ -120,12 +120,13 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT; std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT = std::make_unique(argv[1], argv[2]); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/receiveAudio/main.cpp b/examples/receiveAudio/main.cpp index 63796fd3d..5a10db8f1 100644 --- a/examples/receiveAudio/main.cpp +++ b/examples/receiveAudio/main.cpp @@ -45,10 +45,9 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new ReceiveAudioBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); return EXIT_SUCCESS; diff --git a/examples/receiveDocument/main.cpp b/examples/receiveDocument/main.cpp index 7a177b7c9..67a732e46 100644 --- a/examples/receiveDocument/main.cpp +++ b/examples/receiveDocument/main.cpp @@ -45,10 +45,9 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new ReceiveDocumentBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); return EXIT_SUCCESS; diff --git a/examples/receivePhoto/main.cpp b/examples/receivePhoto/main.cpp index b85bd7b49..1a435a34c 100644 --- a/examples/receivePhoto/main.cpp +++ b/examples/receivePhoto/main.cpp @@ -47,10 +47,9 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new ReceivePhotoBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); return EXIT_SUCCESS; diff --git a/examples/receiveVideo/main.cpp b/examples/receiveVideo/main.cpp index 1fba9112d..ab69eb328 100644 --- a/examples/receiveVideo/main.cpp +++ b/examples/receiveVideo/main.cpp @@ -48,11 +48,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new ReceiveVideoBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/sendAudio/main.cpp b/examples/sendAudio/main.cpp index 36e067c03..c263688ac 100644 --- a/examples/sendAudio/main.cpp +++ b/examples/sendAudio/main.cpp @@ -65,11 +65,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new AudioBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/sendDocument/main.cpp b/examples/sendDocument/main.cpp index 07a75b1a5..282e7cb34 100644 --- a/examples/sendDocument/main.cpp +++ b/examples/sendDocument/main.cpp @@ -56,11 +56,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new DocumentBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/sendPhoto/main.cpp b/examples/sendPhoto/main.cpp index 1c02c846d..9c7e01d9f 100644 --- a/examples/sendPhoto/main.cpp +++ b/examples/sendPhoto/main.cpp @@ -72,11 +72,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new PhotoBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/sendPoll/main.cpp b/examples/sendPoll/main.cpp index b516d142b..232009325 100644 --- a/examples/sendPoll/main.cpp +++ b/examples/sendPoll/main.cpp @@ -93,11 +93,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new PollBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if (BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } diff --git a/examples/sendVideo/main.cpp b/examples/sendVideo/main.cpp index 8250733ff..1f0c75fbc 100644 --- a/examples/sendVideo/main.cpp +++ b/examples/sendVideo/main.cpp @@ -69,11 +69,11 @@ int main(int argc, const char *argv[]) { static std::unique_ptr BOT(new VideoBot(argv[1])); std::signal(SIGINT, [](int) { // Graceful Bot exit on CTRL+C if(BOT) { - std::cout << "Stopping Bot. Please wait...\n"; + std::cout << "Stopping Bot. Please wait..." << std::endl; BOT->stop(); } - std::exit(EXIT_SUCCESS); }); BOT->start(); + std::cout << "Bot Stopped." << std::endl; return EXIT_SUCCESS; } From 9bc0a432402571cab5bc254b893d810d24d662d1 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 20 Dec 2025 19:16:12 +0100 Subject: [PATCH 09/24] Allow users to specify api long polling updates limit to be able to reduce the number of updates being parsed in memory tight systems --- include/tgbotxx/Api.hpp | 13 +++++++++---- src/Api.cpp | 9 +++++++-- src/Bot.cpp | 2 +- tests/manual_tests.cpp | 4 +++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/include/tgbotxx/Api.hpp b/include/tgbotxx/Api.hpp index c79c49a23..50adf5a4c 100644 --- a/include/tgbotxx/Api.hpp +++ b/include/tgbotxx/Api.hpp @@ -87,6 +87,7 @@ namespace tgbotxx { cpr::ConnectTimeout m_connectTimeout = DEFAULT_CONNECT_TIMEOUT; /// Api connection timeout cpr::Timeout m_timeout = DEFAULT_TIMEOUT; /// Api requests timeout cpr::Timeout m_longPollTimeout = DEFAULT_LONG_POLL_TIMEOUT; /// Long polling timeout + std::int32_t m_updatesLimit = 100; /// Limits the number of long polling updates to be retrieved by getUpdates(). Values between 1-100 are accepted. Defaults to 100. cpr::Timeout m_uploadFilesTimeout = DEFAULT_UPLOAD_FILES_TIMEOUT; /// Api files upload timeout cpr::Timeout m_downloadFilesTimeout = DEFAULT_DOWNLOAD_FILES_TIMEOUT; /// Api files download timeout cpr::Proxies m_proxies = cpr::Proxies(); /// Api proxy to use for requests @@ -2213,17 +2214,15 @@ namespace tgbotxx { /// unconfirmed update are returned. An update is considered confirmed as soon as getUpdates is called /// with an offset higher than its update_id. The negative offset can be specified to retrieve updates /// starting from -offset update from the end of the updates queue. All previous updates will be forgotten. - /// @param limit Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults to 100. /// @param cancellationParam Flag used to stop long polling. /// @returns an Array of Update objects. /// @throws Exception on failure + /// @note Limit updates with setUpdatesLimit(limit) method. Default is 100. /// @note Please note that this parameter doesn't affect updates created before the call to the getUpdates, so unwanted updates may be received for a short period of time. /// @note This method will not work if an outgoing webhook is set up. /// @note In order to avoid getting duplicate updates, recalculate offset after each server response. /// @link ref https://core.telegram.org/bots/api#getupdates @endlink - std::vector> getUpdates(std::int32_t offset, - std::int32_t limit = 100, - const std::shared_ptr>& cancellationParam = nullptr) const; + std::vector> getUpdates(std::int32_t offset, const std::shared_ptr>& cancellationParam = nullptr) const; /// @brief Use this method to specify a URL and receive incoming updates via an outgoing webhook. /// Whenever there is an update for the bot, we will send an HTTPS POST request to the specified URL, containing a JSON-serialized Update. @@ -2788,6 +2787,12 @@ namespace tgbotxx { /// @brief Get long polling timeout. cpr::Timeout getLongPollTimeout() const noexcept; + /// @brief Set the number of long polling updates to be retrieved by getUpdates(). + /// Values between 1-100 are accepted. Defaults to 100. + void setUpdatesLimit(const std::int32_t limit) noexcept; + /// @brief Get Set the number of long polling updates to be retrieved by getUpdates(). + std::int32_t getUpdatesLimit() const noexcept; + /// @brief Set Api requests connection timeout. void setConnectTimeout(const cpr::ConnectTimeout& timeout) noexcept; /// @brief Get Api requests connection timeout. diff --git a/src/Api.cpp b/src/Api.cpp index 878957eea..25f64a35d 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -2598,11 +2598,11 @@ bool Api::deleteWebhook(bool dropPendingUpdates) const { } /// Called every LONG_POOL_TIMEOUT seconds -std::vector> Api::getUpdates(std::int32_t offset, std::int32_t limit, const std::shared_ptr>& cancellationParam) const { +std::vector> Api::getUpdates(std::int32_t offset, const std::shared_ptr>& cancellationParam) const { std::vector> updates; const cpr::Multipart data = { {"offset", offset}, - {"limit", std::max(1, std::min(100, limit))}, + {"limit", m_updatesLimit}, {"timeout", static_cast(std::chrono::duration_cast(m_longPollTimeout.ms).count())}, {"allowed_updates", nl::json(m_allowedUpdates).dump()}, }; @@ -3429,6 +3429,11 @@ void Api::setLongPollTimeout(const cpr::Timeout& longPollTimeout) { } cpr::Timeout Api::getLongPollTimeout() const noexcept { return m_longPollTimeout; } +void Api::setUpdatesLimit(const std::int32_t limit) noexcept { + m_updatesLimit = std::max(1, std::min(100, limit)); +} +std::int32_t Api::getUpdatesLimit() const noexcept { return m_updatesLimit; } + void Api::setConnectTimeout(const cpr::ConnectTimeout& timeout) noexcept { m_connectTimeout = timeout; } diff --git a/src/Bot.cpp b/src/Bot.cpp index e3e0bebf0..158a27e6e 100644 --- a/src/Bot.cpp +++ b/src/Bot.cpp @@ -28,7 +28,7 @@ void Bot::start() { while (not *m_stopped) { try { // Get updates from Telegram (any new events such as messages, commands, files, ...) - m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId, 100, m_stopped); + m_updates = m_api->getUpdates(/*offset=*/m_lastUpdateId, m_stopped); } catch (const Exception& err) { /// Callback -> onLongPollError std::string errStr{err.what()}; diff --git a/tests/manual_tests.cpp b/tests/manual_tests.cpp index 6f7e0480e..c8880a1fc 100644 --- a/tests/manual_tests.cpp +++ b/tests/manual_tests.cpp @@ -62,8 +62,10 @@ class MyBot final : public Bot { // api()->setLongPollTimeout(std::chrono::seconds(60 * 2)); // Drop awaiting updates (when Bot is not running, updates will remain 24 hours // in Telegram server before they get deleted or retrieved by BOT) - api()->deleteWebhook(true); + //api()->deleteWebhook(true); api()->setLongPollTimeout(cpr::Timeout(std::chrono::seconds(300))); + api()->setUpdatesLimit(3); // memory tight? process updates 3 by 3... + assert(api()->getUpdatesLimit() == 3); // api()->setAllowedUpdates({ // "message_reaction", // "message_reaction_count" From 2b2f45b23460ed1956c226805bbc11f2490c17b2 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Sat, 20 Dec 2025 20:02:39 +0100 Subject: [PATCH 10/24] Added long polling cancellation unit tests --- tests/src/TestApi.cpp | 49 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/tests/src/TestApi.cpp b/tests/src/TestApi.cpp index 51d0776d1..64078f4b0 100644 --- a/tests/src/TestApi.cpp +++ b/tests/src/TestApi.cpp @@ -3,6 +3,7 @@ #include #include using namespace tgbotxx; +using namespace std::chrono_literals; thread_local static Ptr API(new Api(std::getenv("TESTS_BOT_TOKEN") ?: "BOT_TOKEN")); @@ -22,6 +23,19 @@ TEST_CASE("Test Api", "methods") { REQUIRE(API->deleteWebhook(true)); } + SECTION("Cache") { + REQUIRE(API->getMe()->username == API->getCache().botUsername); + auto apiCommands = API->getMyCommands(); + auto cachedCommands = API->getCache().botCommands; + REQUIRE(apiCommands.size() == cachedCommands.size()); + if (apiCommands.size() == cachedCommands.size()) { + for (std::size_t i = 0; i < apiCommands.size(); ++i) { + // std::cout << cachedCommands[i] << std::endl; + REQUIRE(apiCommands[i]->command == cachedCommands[i]); + } + } + } + SECTION("timeouts") { std::srand(std::time(nullptr)); for (int i = 0; i < 100; i++) { @@ -39,19 +53,50 @@ TEST_CASE("Test Api", "methods") { REQUIRE(API->getLongPollTimeout().ms < API->getTimeout().ms); } } + + SECTION("Limit updates") { + REQUIRE(API->getUpdatesLimit() == 100); + API->setUpdatesLimit(30); + REQUIRE(API->getUpdatesLimit() == 30); + API->setUpdatesLimit(250); + REQUIRE(API->getUpdatesLimit() == 100); + API->setUpdatesLimit(-250); + REQUIRE(API->getUpdatesLimit() == 1); + } } TEST_CASE("Test Api ErrorCodes", "ErrorCode") { - SECTION("ErrorCode::BAD_REQUEST") { + SECTION("Invalid chat/user id - ErrorCode::BAD_REQUEST") { try { auto invalid_chat_id = 0; API->sendMessage(invalid_chat_id, "test msg"); } catch (const tgbotxx::Exception& e) { - std::cerr << e.what() << " (" << (int) e.errorCode() << ") " << std::endl; + std::cerr << e.what() << " (" << e.errorCode() << ") " << std::endl; REQUIRE(e.errorCode() == ErrorCode::BAD_REQUEST); REQUIRE(std::string(e.what()) == "Bad Request: chat not found"); } } + SECTION("getUpdates Long polling cancellation - ErrorCode::REQUEST_CANCELLED") { + std::shared_ptr> cancellationParam{new std::atomic{true}}; + + std::thread([cancellationParam]() { + std::cout << "Sleeping for 5s before cancelling long polling..." << std::endl; + std::this_thread::sleep_for(5s); + std::cout << "Cancelling long polling getUpdates()" << std::endl; + *cancellationParam = false; // cancels long polling after 5s + }).detach(); + + API->setUpdatesLimit(100); + API->setLongPollTimeout(5min); + try { + API->getUpdates(0, cancellationParam); + REQUIRE(false); + } catch (const tgbotxx::Exception& ex) { + std::cout << ex.what() << " - " << ex.errorCode() << std::endl; + REQUIRE(ex.errorCode() == tgbotxx::ErrorCode::REQUEST_CANCELLED); + } + } + // Other error codes to be tested in manual_tests.cpp } \ No newline at end of file From 87900acd299eac26ed185c670d68853f08a852e0 Mon Sep 17 00:00:00 2001 From: baderouaich Date: Tue, 30 Dec 2025 18:52:22 +0100 Subject: [PATCH 11/24] Fix MaybeInaccessibleMessage invalid variant index --- src/objects/CallbackQuery.cpp | 6 ++++-- src/objects/Message.cpp | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/objects/CallbackQuery.cpp b/src/objects/CallbackQuery.cpp index 4af56110c..4f4110792 100644 --- a/src/objects/CallbackQuery.cpp +++ b/src/objects/CallbackQuery.cpp @@ -27,10 +27,12 @@ void CallbackQuery::fromJson(const nl::json& json) { // date: Always 0. The field can be used to differentiate regular and inaccessible messages. const bool isInaccessibleMessage = json["message"].contains("date") && json["message"]["date"].get() == 0; if (isInaccessibleMessage) { - Ptr& inaccessibleMessage = std::get<1>(message); + message = makePtr(); + auto& inaccessibleMessage = std::get>(message); OBJECT_DESERIALIZE_FIELD_PTR(json, "message", inaccessibleMessage, true); } else { // Regular message - Ptr& regularMessage = std::get<0>(message); + message = makePtr(); + auto& regularMessage = std::get>(message); OBJECT_DESERIALIZE_FIELD_PTR(json, "message", regularMessage, true); } } diff --git a/src/objects/Message.cpp b/src/objects/Message.cpp index 0dfecfa09..26b1f60f3 100644 --- a/src/objects/Message.cpp +++ b/src/objects/Message.cpp @@ -189,10 +189,12 @@ void Message::fromJson(const nl::json& json) { // date: Always 0. The field can be used to differentiate regular and inaccessible messages. const bool isInaccessibleMessage = json["pinned_message"].contains("date") && json["pinned_message"]["date"].get() == 0; if (isInaccessibleMessage) { - Ptr& inaccessibleMessage = std::get<1>(pinnedMessage); + pinnedMessage = makePtr(); + auto& inaccessibleMessage = std::get>(pinnedMessage); OBJECT_DESERIALIZE_FIELD_PTR(json, "pinned_message", inaccessibleMessage, true); } else { // Regular message - Ptr& regularMessage = std::get<0>(pinnedMessage); + pinnedMessage = makePtr(); + auto& regularMessage = std::get>(pinnedMessage); OBJECT_DESERIALIZE_FIELD_PTR(json, "pinned_message", regularMessage, true); } } From fb19eab4297ad0722531a0ec007e328a4abff5cb Mon Sep 17 00:00:00 2001 From: baderouaich Date: Tue, 30 Dec 2025 19:16:38 +0100 Subject: [PATCH 12/24] Using std::monostate for optional variants --- include/tgbotxx/Api.hpp | 14 ++--- include/tgbotxx/objects/InputSticker.hpp | 14 +++-- src/Api.cpp | 70 ++++++++++++------------ 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/include/tgbotxx/Api.hpp b/include/tgbotxx/Api.hpp index 50adf5a4c..e05c8fb62 100644 --- a/include/tgbotxx/Api.hpp +++ b/include/tgbotxx/Api.hpp @@ -378,7 +378,7 @@ namespace tgbotxx { std::time_t duration = 0, const std::string& performer = "", const std::string& title = "", - const std::optional>& thumbnail = std::nullopt, + const std::variant& thumbnail = std::monostate{}, bool disableNotification = false, bool protectContent = false, const Ptr& replyMarkup = nullptr, @@ -422,7 +422,7 @@ namespace tgbotxx { Ptr sendDocument(const std::variant& chatId, const std::variant& document, std::int32_t messageThreadId = 0, - const std::optional>& thumbnail = std::nullopt, + const std::variant& thumbnail = std::monostate{}, const std::string& caption = "", const std::string& parseMode = "", const std::vector>& captionEntities = std::vector>(), @@ -481,8 +481,8 @@ namespace tgbotxx { std::time_t duration = 0, std::int32_t width = 0, std::int32_t height = 0, - const std::optional>& thumbnail = std::nullopt, - const std::optional>& cover = std::nullopt, + const std::variant& thumbnail = std::monostate{}, + const std::variant& cover = std::monostate{}, std::time_t startTimestamp = 0, const std::string& caption = "", const std::string& parseMode = "", @@ -537,7 +537,7 @@ namespace tgbotxx { std::time_t duration = 0, std::int32_t width = 0, std::int32_t height = 0, - const std::optional>& thumbnail = std::nullopt, + const std::variant& thumbnail = std::monostate{}, const std::string& caption = "", const std::string& parseMode = "", const std::vector>& captionEntities = std::vector>(), @@ -625,7 +625,7 @@ namespace tgbotxx { std::int32_t messageThreadId = 0, std::time_t duration = 0, std::int32_t length = 0, - const std::optional>& thumbnail = std::nullopt, + const std::variant& thumbnail = std::monostate{}, bool disableNotification = false, bool protectContent = false, const Ptr& replyMarkup = nullptr, @@ -2656,7 +2656,7 @@ namespace tgbotxx { bool setStickerSetThumbnail(const std::string& name, std::int64_t userId, const std::string& format, - const std::optional>& thumbnail = std::nullopt) const; + const std::variant& thumbnail = std::monostate{}) const; /// @brief Use this method to set the thumbnail of a custom emoji sticker set. /// @param name Sticker set name diff --git a/include/tgbotxx/objects/InputSticker.hpp b/include/tgbotxx/objects/InputSticker.hpp index f99b69eda..4f8a4d8f1 100644 --- a/include/tgbotxx/objects/InputSticker.hpp +++ b/include/tgbotxx/objects/InputSticker.hpp @@ -18,7 +18,7 @@ namespace tgbotxx { /// - Pass an HTTP URL as an std::string for Telegram to get a .WEBP sticker from the Internet, or /// - Pass a cpr::File to upload a new .WEBP or .TGS sticker /// Animated and video stickers can't be uploaded via HTTP URL. [More information on Sending Files »](https://core.telegram.org/bots/api#sending-files) - std::variant sticker{""}; + std::variant sticker{}; /// @brief Format of the added sticker, must be one of “static” for a .WEBP or .PNG image, “animated” for a .TGS animation, “video” for a .WEBM video std::string format; @@ -37,11 +37,13 @@ namespace tgbotxx { /// @returns JSON representation of this object nl::json toJson() const { nl::json json = nl::json::object(); - // sticker variant - if (auto idx = sticker.index(); idx == 0) - json["sticker"] = std::get(sticker).filepath; - else if (idx == 1) - json["sticker"] = std::get(sticker); + if (not std::holds_alternative(sticker)) { + // sticker variant + if (std::holds_alternative(sticker)) + json["sticker"] = std::get(sticker).filepath; + else + json["sticker"] = std::get(sticker); + } OBJECT_SERIALIZE_FIELD(json, "format", format); OBJECT_SERIALIZE_FIELD(json, "emoji_list", emojiList); OBJECT_SERIALIZE_FIELD_PTR(json, "mask_position", maskPosition); diff --git a/src/Api.cpp b/src/Api.cpp index 25f64a35d..1a2a34dfa 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -446,7 +446,7 @@ Ptr Api::sendAudio(const std::variant& chatI std::time_t duration, const std::string& performer, const std::string& title, - const std::optional>& thumbnail, + const std::variant& thumbnail, bool disableNotification, bool protectContent, const Ptr& replyMarkup, @@ -484,12 +484,12 @@ Ptr Api::sendAudio(const std::variant& chatI data.parts.emplace_back("performer", performer); if (not title.empty()) data.parts.emplace_back("title", title); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } @@ -520,7 +520,7 @@ Ptr Api::sendAudio(const std::variant& chatI Ptr Api::sendDocument(const std::variant& chatId, const std::variant& document, std::int32_t messageThreadId, - const std::optional>& thumbnail, + const std::variant& thumbnail, const std::string& caption, const std::string& parseMode, const std::vector>& captionEntities, @@ -546,12 +546,12 @@ Ptr Api::sendDocument(const std::variant& ch } if (messageThreadId) data.parts.emplace_back("message_thread_id", messageThreadId); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } @@ -598,8 +598,8 @@ Ptr Api::sendVideo(const std::variant& chatI std::time_t duration, std::int32_t width, std::int32_t height, - const std::optional>& thumbnail, - const std::optional>& cover, + const std::variant& thumbnail, + const std::variant& cover, std::time_t startTimestamp, const std::string& caption, const std::string& parseMode, @@ -634,21 +634,21 @@ Ptr Api::sendVideo(const std::variant& chatI data.parts.emplace_back("width", width); if (height) data.parts.emplace_back("height", height); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } - if (cover.has_value()) { - if (cover->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*cover); + if (not std::holds_alternative(cover)) { + if (std::holds_alternative(cover)) /* cpr::File */ { + const cpr::File& file = std::get(cover); data.parts.emplace_back("cover", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*cover); + const std::string& fileIdOrUrl = std::get(cover); data.parts.emplace_back("cover", fileIdOrUrl); } } @@ -700,7 +700,7 @@ Ptr Api::sendAnimation(const std::variant& c std::time_t duration, std::int32_t width, std::int32_t height, - const std::optional>& thumbnail, + const std::variant& thumbnail, const std::string& caption, const std::string& parseMode, const std::vector>& captionEntities, @@ -731,12 +731,12 @@ Ptr Api::sendAnimation(const std::variant& c data.parts.emplace_back("width", width); if (height) data.parts.emplace_back("height", height); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } @@ -844,7 +844,7 @@ Ptr Api::sendVideoNote(const std::variant& c std::int32_t messageThreadId, std::time_t duration, std::int32_t length, - const std::optional>& thumbnail, + const std::variant& thumbnail, bool disableNotification, bool protectContent, const Ptr& replyMarkup, @@ -870,12 +870,12 @@ Ptr Api::sendVideoNote(const std::variant& c data.parts.emplace_back("duration", duration); if (length) data.parts.emplace_back("length", duration); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } @@ -3265,18 +3265,18 @@ bool Api::setStickerSetTitle(const std::string& name, const std::string& title) bool Api::setStickerSetThumbnail(const std::string& name, std::int64_t userId, const std::string& format, - const std::optional>& thumbnail) const { + const std::variant& thumbnail) const { cpr::Multipart data{}; data.parts.reserve(4); data.parts.emplace_back("name", name); data.parts.emplace_back("user_id", std::to_string(userId)); data.parts.emplace_back("format", format); - if (thumbnail.has_value()) { - if (thumbnail->index() == 0) /* cpr::File */ { - const cpr::File& file = std::get(*thumbnail); + if (not std::holds_alternative(thumbnail)) { + if (std::holds_alternative(thumbnail)) /* cpr::File */ { + const cpr::File& file = std::get(thumbnail); data.parts.emplace_back("thumbnail", cpr::Files{file}); } else /* std::string (fileId or Url) */ { - const std::string& fileIdOrUrl = std::get(*thumbnail); + const std::string& fileIdOrUrl = std::get(thumbnail); data.parts.emplace_back("thumbnail", fileIdOrUrl); } } From c0b51f7a025d19b67bd3e94840701536ce9c22ab Mon Sep 17 00:00:00 2001 From: baderouaich Date: Tue, 30 Dec 2025 19:23:00 +0100 Subject: [PATCH 13/24] Improved object deserialization hierarchy --- include/tgbotxx/objects/BackgroundFill.hpp | 36 +- include/tgbotxx/objects/BackgroundType.hpp | 43 +- include/tgbotxx/objects/BotCommandScope.hpp | 262 +- include/tgbotxx/objects/CallbackQuery.hpp | 2 +- include/tgbotxx/objects/Chat.hpp | 3 - include/tgbotxx/objects/ChatBoostSource.hpp | 39 +- include/tgbotxx/objects/ChatFullInfo.hpp | 2 +- include/tgbotxx/objects/ChatMember.hpp | 80 +- include/tgbotxx/objects/ForceReply.hpp | 10 +- .../tgbotxx/objects/InlineKeyboardMarkup.hpp | 10 +- include/tgbotxx/objects/InlineQueryResult.hpp | 2249 ++++++++--------- include/tgbotxx/objects/InputMedia.hpp | 467 ++-- .../tgbotxx/objects/InputMessageContent.hpp | 612 +++-- include/tgbotxx/objects/InputPaidMedia.hpp | 33 +- include/tgbotxx/objects/InputProfilePhoto.hpp | 26 +- include/tgbotxx/objects/InputStoryContent.hpp | 31 +- .../objects/MaybeInaccessibleMessage.hpp | 5 +- include/tgbotxx/objects/MenuButton.hpp | 122 +- include/tgbotxx/objects/Message.hpp | 2 +- include/tgbotxx/objects/MessageOrigin.hpp | 43 +- include/tgbotxx/objects/OwnedGift.hpp | 25 +- include/tgbotxx/objects/PaidMedia.hpp | 42 +- .../tgbotxx/objects/PassportElementError.hpp | 501 ++-- include/tgbotxx/objects/ReactionType.hpp | 117 +- .../tgbotxx/objects/ReplyKeyboardMarkup.hpp | 10 +- .../tgbotxx/objects/ReplyKeyboardRemove.hpp | 8 +- include/tgbotxx/objects/ReplyParameters.hpp | 6 +- .../objects/RevenueWithdrawalState.hpp | 57 +- include/tgbotxx/objects/StoryAreaType.hpp | 64 +- .../tgbotxx/objects/TransactionPartner.hpp | 97 +- src/objects/Chat.cpp | 6 +- 31 files changed, 2458 insertions(+), 2552 deletions(-) diff --git a/include/tgbotxx/objects/BackgroundFill.hpp b/include/tgbotxx/objects/BackgroundFill.hpp index 9a8dfdb93..2674f31e7 100644 --- a/include/tgbotxx/objects/BackgroundFill.hpp +++ b/include/tgbotxx/objects/BackgroundFill.hpp @@ -11,14 +11,14 @@ namespace tgbotxx { struct BackgroundFill { BackgroundFill() = default; explicit BackgroundFill(const nl::json& json) { - _fromJson(json); + BackgroundFill::fromJson(json); } virtual ~BackgroundFill() = default; /// @brief Type of the background fill, one of "solid", "gradient", "freeform_gradient" std::string type; - virtual nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "type", type); return json; @@ -27,21 +27,15 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The background is filled using the selected solid color. /// @ref https://core.telegram.org/bots/api#backgroundfillsolid struct BackgroundFillSolid : BackgroundFill { - BackgroundFillSolid() { - BackgroundFill::type = "solid"; - } - explicit BackgroundFillSolid(const nl::json& json) : BackgroundFill(json) { - BackgroundFill::type = "solid"; + BackgroundFillSolid() = default; + explicit BackgroundFillSolid(const nl::json& json) { + BackgroundFillSolid::fromJson(json); + // BackgroundFill::type = "solid"; } /// @brief The color of the background fill in the RGB24 format @@ -62,11 +56,10 @@ namespace tgbotxx { /// @brief The background is a gradient fill. /// @ref https://core.telegram.org/bots/api#backgroundfillgradient struct BackgroundFillGradient : BackgroundFill { - BackgroundFillGradient() { - BackgroundFill::type = "gradient"; - } - explicit BackgroundFillGradient(const nl::json& json) : BackgroundFill(json) { - BackgroundFill::type = "gradient"; + BackgroundFillGradient() = default; + explicit BackgroundFillGradient(const nl::json& json) { + BackgroundFillGradient::fromJson(json); + // BackgroundFill::type = "gradient"; } /// @brief Top color of the gradient in the RGB24 format @@ -97,11 +90,10 @@ namespace tgbotxx { /// @brief The background is a freeform gradient that rotates after every message in the chat. /// @ref https://core.telegram.org/bots/api#backgroundfillfreeformgradient struct BackgroundFillFreeformGradient : BackgroundFill { - BackgroundFillFreeformGradient() { - BackgroundFill::type = "freeform_gradient"; - } - explicit BackgroundFillFreeformGradient(const nl::json& json) : BackgroundFill(json) { - BackgroundFill::type = "freeform_gradient"; + BackgroundFillFreeformGradient() = default; + explicit BackgroundFillFreeformGradient(const nl::json& json) { + BackgroundFillFreeformGradient::fromJson(json); + // BackgroundFill::type = "freeform_gradient"; } /// @brief A list of 3 or 4 base colors that are used to generate the freeform gradient in the RGB24 format diff --git a/include/tgbotxx/objects/BackgroundType.hpp b/include/tgbotxx/objects/BackgroundType.hpp index e680a12eb..8701f84e8 100644 --- a/include/tgbotxx/objects/BackgroundType.hpp +++ b/include/tgbotxx/objects/BackgroundType.hpp @@ -14,7 +14,7 @@ namespace tgbotxx { struct BackgroundType { BackgroundType() = default; explicit BackgroundType(const nl::json& json) { - _fromJson(json); + BackgroundType::fromJson(json); } virtual ~BackgroundType() = default; @@ -30,21 +30,15 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The background is automatically filled based on the selected colors. /// @ref https://core.telegram.org/bots/api#backgroundtypefill struct BackgroundTypeFill : BackgroundType { - BackgroundTypeFill() { - BackgroundType::type = "fill"; - } - explicit BackgroundTypeFill(const nl::json& json) : BackgroundType(json) { - BackgroundType::type = "fill"; + BackgroundTypeFill() = default; + explicit BackgroundTypeFill(const nl::json& json) { + BackgroundTypeFill::fromJson(json); + BackgroundTypeFill::type = "fill"; } /// @brief The background fill @@ -70,11 +64,10 @@ namespace tgbotxx { /// @brief The background is a wallpaper in JPEG format. /// @ref https://core.telegram.org/bots/api#backgroundtypewallpaper struct BackgroundTypeWallpaper : BackgroundType { - BackgroundTypeWallpaper() { - BackgroundType::type = "wallpaper"; - } - explicit BackgroundTypeWallpaper(const nl::json& json) : BackgroundType(json) { - BackgroundType::type = "wallpaper"; + BackgroundTypeWallpaper() = default; + explicit BackgroundTypeWallpaper(const nl::json& json){ + BackgroundTypeWallpaper::fromJson(json); + // BackgroundTypeWallpaper::type = "wallpaper"; } /// @brief Document with the wallpaper @@ -110,11 +103,10 @@ namespace tgbotxx { /// @brief The background is a PNG or TGV pattern combined with a fill. /// @ref https://core.telegram.org/bots/api#backgroundtypepattern struct BackgroundTypePattern : BackgroundType { - BackgroundTypePattern() { - BackgroundType::type = "pattern"; - } - explicit BackgroundTypePattern(const nl::json& json) : BackgroundType(json) { - BackgroundType::type = "pattern"; + BackgroundTypePattern() = default; + explicit BackgroundTypePattern(const nl::json& json) { + BackgroundTypePattern::fromJson(json); + // BackgroundTypePattern::type = "pattern"; } /// @brief Document with the pattern @@ -155,11 +147,10 @@ namespace tgbotxx { /// @brief The background is taken directly from a built-in chat theme. /// @ref https://core.telegram.org/bots/api#backgroundtypechattheme struct BackgroundTypeChatTheme : BackgroundType { - BackgroundTypeChatTheme() { - BackgroundType::type = "chat_theme"; - } - explicit BackgroundTypeChatTheme(const nl::json& json) : BackgroundType(json) { - BackgroundType::type = "chat_theme"; + BackgroundTypeChatTheme() = default; + explicit BackgroundTypeChatTheme(const nl::json& json) { + BackgroundTypeChatTheme::fromJson(json); + // BackgroundTypeChatTheme::type = "chat_theme"; } /// @brief Name of the chat theme, which is usually an emoji diff --git a/include/tgbotxx/objects/BotCommandScope.hpp b/include/tgbotxx/objects/BotCommandScope.hpp index b9fa4da86..52d7db908 100644 --- a/include/tgbotxx/objects/BotCommandScope.hpp +++ b/include/tgbotxx/objects/BotCommandScope.hpp @@ -13,145 +13,185 @@ namespace tgbotxx { /// BotCommandScopeChatMember /// @ref https://core.telegram.org/bots/api#botcommandscope struct BotCommandScope { - BotCommandScope() = default; - explicit BotCommandScope(const nl::json& json) { - _fromJson(json); - } - virtual ~BotCommandScope() = default; - - /// @brief Scope type. - std::string type; - - virtual nl::json toJson() const { - nl::json scope = nl::json::object(); - OBJECT_SERIALIZE_FIELD(scope, "type", type); - return scope; - } - - virtual void fromJson(const nl::json& json) { - OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); - } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } + BotCommandScope() = default; + explicit BotCommandScope(const nl::json& json) { + BotCommandScope::fromJson(json); + } + virtual ~BotCommandScope() = default; + + /// @brief Scope type. + std::string type; + + virtual nl::json toJson() const { + nl::json scope = nl::json::object(); + OBJECT_SERIALIZE_FIELD(scope, "type", type); + return scope; + } + + virtual void fromJson(const nl::json& json) { + OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); + } }; /// @brief Represents the default scope of bot commands. Default commands are used if no commands with a narrower /// scope are specified for the user. struct BotCommandScopeDefault : BotCommandScope { - BotCommandScopeDefault() { - BotCommandScope::type = "default"; - } - explicit BotCommandScopeDefault(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "default"; - } + BotCommandScopeDefault() = default; + explicit BotCommandScopeDefault(const nl::json& json) { + BotCommandScope::fromJson(json); + // BotCommandScope::type = "default"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = BotCommandScope::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + } }; /// @brief Represents the scope of bot commands, covering all private chats. struct BotCommandScopeAllPrivateChats : BotCommandScope { - BotCommandScopeAllPrivateChats() { - BotCommandScope::type = "all_private_chats"; - } - explicit BotCommandScopeAllPrivateChats(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "all_private_chats"; - } + BotCommandScopeAllPrivateChats() = default; + explicit BotCommandScopeAllPrivateChats(const nl::json& json) { + BotCommandScopeAllPrivateChats::fromJson(json); + // BotCommandScope::type = "all_private_chats"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = BotCommandScope::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + } + }; /// @brief Represents the scope of bot commands, covering all group and supergroup chats. struct BotCommandScopeAllGroupChats : BotCommandScope { - BotCommandScopeAllGroupChats() { - BotCommandScope::type = "all_group_chats"; - } - explicit BotCommandScopeAllGroupChats(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "all_group_chats"; - } + BotCommandScopeAllGroupChats() = default; + explicit BotCommandScopeAllGroupChats(const nl::json& json) { + BotCommandScopeAllGroupChats::fromJson(json); + // BotCommandScope::type = "all_group_chats"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = BotCommandScope::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + } + }; /// @brief Represents the scope of bot commands, covering all group and supergroup chat administrators. struct BotCommandScopeAllChatAdministrators : BotCommandScope { - BotCommandScopeAllChatAdministrators() { - BotCommandScope::type = "all_chat_administrators"; - } - explicit BotCommandScopeAllChatAdministrators(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "all_chat_administrators"; - } + BotCommandScopeAllChatAdministrators() = default; + explicit BotCommandScopeAllChatAdministrators(const nl::json& json) { + BotCommandScopeAllChatAdministrators::fromJson(json); + // BotCommandScope::type = "all_chat_administrators"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = BotCommandScope::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + } + }; /// @brief Represents the scope of bot commands, covering a specific chat. struct BotCommandScopeChat : BotCommandScope { - BotCommandScopeChat() { - BotCommandScope::type = "chat"; - } - explicit BotCommandScopeChat(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "chat"; - } - - /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - std::int64_t chatId{}; - - nl::json toJson() const override { - nl::json scope = BotCommandScope::toJson(); - OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); - return scope; - } - - void fromJson(const nl::json& json) override { - BotCommandScope::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); - } + BotCommandScopeChat() = default; + explicit BotCommandScopeChat(const nl::json& json) { + BotCommandScopeChat::fromJson(json); + // BotCommandScope::type = "chat"; + } + + /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) + std::int64_t chatId{}; + + [[nodiscard]] nl::json toJson() const override { + nl::json scope = BotCommandScope::toJson(); + OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); + return scope; + } + + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); + } }; /// @brief Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat. struct BotCommandScopeChatAdministrators : BotCommandScope { - BotCommandScopeChatAdministrators() { - BotCommandScope::type = "chat_administrators"; - } - explicit BotCommandScopeChatAdministrators(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "chat_administrators"; - } - - /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - std::int64_t chatId{}; - - nl::json toJson() const override { - nl::json scope = BotCommandScope::toJson(); - OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); - return scope; - } - - void fromJson(const nl::json& json) override { - BotCommandScope::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); - } + BotCommandScopeChatAdministrators() = default; + explicit BotCommandScopeChatAdministrators(const nl::json& json) { + BotCommandScopeChatAdministrators::fromJson(json); + // BotCommandScope::type = "chat_administrators"; + } + + /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) + std::int64_t chatId{}; + + [[nodiscard]] nl::json toJson() const override { + nl::json scope = BotCommandScope::toJson(); + OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); + return scope; + } + + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); + } }; /// @brief Represents the scope of bot commands, covering a specific member of a group or supergroup chat. struct BotCommandScopeChatMember : BotCommandScope { - BotCommandScopeChatMember() { - BotCommandScope::type = "chat_member"; - } - explicit BotCommandScopeChatMember(const nl::json& json) : BotCommandScope(json) { - BotCommandScope::type = "chat_member"; - } - - /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) - std::int64_t chatId{}; - /// @brief Unique identifier of the target user - std::int64_t userId{}; - - nl::json toJson() const override { - nl::json scope = BotCommandScope::toJson(); - OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); - OBJECT_SERIALIZE_FIELD(scope, "user_id", userId); - return scope; - } - - void fromJson(const nl::json& json) override { - BotCommandScope::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); - OBJECT_DESERIALIZE_FIELD(json, "user_id", userId, -1, false); - } + BotCommandScopeChatMember() = default; + explicit BotCommandScopeChatMember(const nl::json& json) { + BotCommandScopeChatMember::fromJson(json); + // BotCommandScope::type = "chat_member"; + } + + /// @brief Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername) + std::int64_t chatId{}; + + /// @brief Unique identifier of the target user + std::int64_t userId{}; + + [[nodiscard]] nl::json toJson() const override { + nl::json scope = BotCommandScope::toJson(); + OBJECT_SERIALIZE_FIELD(scope, "chat_id", chatId); + OBJECT_SERIALIZE_FIELD(scope, "user_id", userId); + return scope; + } + + void fromJson(const nl::json& json) override { + BotCommandScope::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "chat_id", chatId, -1, false); + OBJECT_DESERIALIZE_FIELD(json, "user_id", userId, -1, false); + } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/CallbackQuery.hpp b/include/tgbotxx/objects/CallbackQuery.hpp index 1ba96fdcf..eadad5c6f 100644 --- a/include/tgbotxx/objects/CallbackQuery.hpp +++ b/include/tgbotxx/objects/CallbackQuery.hpp @@ -30,7 +30,7 @@ namespace tgbotxx { Ptr from; /// @brief Optional. Message sent by the bot with the callback button that originated the query - MaybeInaccessibleMessage message; + MaybeInaccessibleMessage message{}; /// @brief Optional. Identifier of the message sent via the bot in inline mode, that originated the query. std::string inlineMessageId; diff --git a/include/tgbotxx/objects/Chat.hpp b/include/tgbotxx/objects/Chat.hpp index bb39a02f9..59080656f 100644 --- a/include/tgbotxx/objects/Chat.hpp +++ b/include/tgbotxx/objects/Chat.hpp @@ -56,8 +56,5 @@ namespace tgbotxx { /// @brief Deserializes this object from JSON virtual void fromJson(const nl::json& json); - - private: - void _fromJson(const nl::json& json); }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/ChatBoostSource.hpp b/include/tgbotxx/objects/ChatBoostSource.hpp index 435fa4108..867410621 100644 --- a/include/tgbotxx/objects/ChatBoostSource.hpp +++ b/include/tgbotxx/objects/ChatBoostSource.hpp @@ -11,7 +11,7 @@ namespace tgbotxx { struct ChatBoostSource { ChatBoostSource() = default; explicit ChatBoostSource(const nl::json& json) { - _fromJson(json); + ChatBoostSource::fromJson(json); } virtual ~ChatBoostSource() = default; @@ -21,7 +21,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - virtual nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "source", source); return json; @@ -31,28 +31,21 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "source", source, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The boost was obtained by subscribing to Telegram Premium or by gifting a Telegram Premium subscription to another user. /// @ref https://core.telegram.org/bots/api#chatboostsourcepremium struct ChatBoostSourcePremium : ChatBoostSource { - ChatBoostSourcePremium() { - ChatBoostSource::source = "premium"; - } + ChatBoostSourcePremium() = default; explicit ChatBoostSourcePremium(const nl::json& json) { - ChatBoostSource::fromJson(json); - ChatBoostSource::source = "premium"; + ChatBoostSourcePremium::fromJson(json); + // ChatBoostSource::source = "premium"; } /// @brief User that boosted the chat Ptr user; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatBoostSource::toJson(); OBJECT_SERIALIZE_FIELD_PTR(json, "user", user); return json; @@ -66,18 +59,16 @@ namespace tgbotxx { /// @brief The boost was obtained by subscribing to Telegram Premium or by gifting a Telegram Premium subscription to another user. /// @ref https://core.telegram.org/bots/api#chatboostsourcegiftcode struct ChatBoostSourceGiftCode : ChatBoostSource { - ChatBoostSourceGiftCode() { - ChatBoostSource::source = "gift_code"; - } + ChatBoostSourceGiftCode() = default; explicit ChatBoostSourceGiftCode(const nl::json& json) { - ChatBoostSource::fromJson(json); - ChatBoostSource::source = "gift_code"; + ChatBoostSourceGiftCode::fromJson(json); + // ChatBoostSourceGiftCode::source = "gift_code"; } /// @brief User for which the gift code was created Ptr user; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatBoostSource::toJson(); OBJECT_SERIALIZE_FIELD_PTR(json, "user", user); return json; @@ -92,12 +83,10 @@ namespace tgbotxx { /// This boosts the chat 4 times for the duration of the corresponding Telegram Premium subscription. /// @ref https://core.telegram.org/bots/api#chatboostsourcegiveaway struct ChatBoostSourceGiveaway : ChatBoostSource { - ChatBoostSourceGiveaway() { - ChatBoostSource::source = "giveaway"; - } + ChatBoostSourceGiveaway() = default; explicit ChatBoostSourceGiveaway(const nl::json& json) { - ChatBoostSource::fromJson(json); - ChatBoostSource::source = "giveaway"; + ChatBoostSourceGiveaway::fromJson(json); + // ChatBoostSourceGiveaway::source = "giveaway"; } /// @brief Identifier of a message in the chat with the giveaway; the message could have been deleted already. @@ -110,7 +99,7 @@ namespace tgbotxx { /// @brief Optional. True, if the giveaway was completed, but there was no user to win the prize bool isUnclaimed{}; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatBoostSource::toJson(); OBJECT_SERIALIZE_FIELD(json, "giveaway_message_id", giveawayMessageId); OBJECT_SERIALIZE_FIELD_PTR(json, "user", user); diff --git a/include/tgbotxx/objects/ChatFullInfo.hpp b/include/tgbotxx/objects/ChatFullInfo.hpp index 2b11a3352..a81936015 100644 --- a/include/tgbotxx/objects/ChatFullInfo.hpp +++ b/include/tgbotxx/objects/ChatFullInfo.hpp @@ -139,7 +139,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = Chat::toJson(); OBJECT_SERIALIZE_FIELD(json, "accent_color_id", accentColorId); OBJECT_SERIALIZE_FIELD(json, "max_reaction_count", maxReactionCount); diff --git a/include/tgbotxx/objects/ChatMember.hpp b/include/tgbotxx/objects/ChatMember.hpp index c721ca283..72c590354 100644 --- a/include/tgbotxx/objects/ChatMember.hpp +++ b/include/tgbotxx/objects/ChatMember.hpp @@ -15,7 +15,7 @@ namespace tgbotxx { struct ChatMember { ChatMember() = default; explicit ChatMember(const nl::json& json) { - fromJson(json); + ChatMember::fromJson(json); } virtual ~ChatMember() = default; /// @brief The member's status in the chat @@ -26,7 +26,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "status", status); OBJECT_SERIALIZE_FIELD_PTR(json, "user", user); @@ -34,7 +34,7 @@ namespace tgbotxx { } /// @brief Deserializes this object from JSON - void fromJson(const nl::json& json) { + virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "status", status, "", false); OBJECT_DESERIALIZE_FIELD_PTR(json, "user", user, false); } @@ -44,9 +44,9 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#chatmemberowner struct ChatMemberOwner : ChatMember { ChatMemberOwner() = default; - explicit ChatMemberOwner(const nl::json& json) : ChatMember(json) { - fromJson(json); - ChatMember::status = "creator"; + explicit ChatMemberOwner(const nl::json& json) { + ChatMemberOwner::fromJson(json); + // ChatMember::status = "creator"; } /// @brief True, if the user's presence in the chat is hidden @@ -57,7 +57,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatMember::toJson(); OBJECT_SERIALIZE_FIELD(json, "is_anonymous", isAnonymous); OBJECT_SERIALIZE_FIELD(json, "custom_title", customTitle); @@ -65,7 +65,7 @@ namespace tgbotxx { } /// @brief Deserializes this object from JSON - void fromJson(const nl::json& json) { + void fromJson(const nl::json& json) override { ChatMember::fromJson(json); OBJECT_DESERIALIZE_FIELD(json, "is_anonymous", isAnonymous, false, false); OBJECT_DESERIALIZE_FIELD(json, "custom_title", customTitle, "", true); @@ -76,10 +76,9 @@ namespace tgbotxx { /// @brief Represents a chat member that has some additional privileges. /// @ref https://core.telegram.org/bots/api#chatmemberadministrator struct ChatMemberAdministrator : ChatMember { - ChatMemberAdministrator() = default; - explicit ChatMemberAdministrator(const nl::json& json) : ChatMember(json) { - fromJson(json); - ChatMember::status = "administrator"; + explicit ChatMemberAdministrator(const nl::json& json) { + ChatMemberAdministrator::fromJson(json); + // ChatMember::status = "administrator"; } /// @brief True, if the bot is allowed to edit administrator privileges of that user @@ -139,7 +138,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatMember::toJson(); OBJECT_SERIALIZE_FIELD(json, "can_be_edited", canBeEdited); OBJECT_SERIALIZE_FIELD(json, "is_anonymous", isAnonymous); @@ -162,7 +161,7 @@ namespace tgbotxx { } /// @brief Deserializes this object from JSON - void fromJson(const nl::json& json) { + void fromJson(const nl::json& json) override { ChatMember::fromJson(json); OBJECT_DESERIALIZE_FIELD(json, "can_be_edited", canBeEdited, false, false); OBJECT_DESERIALIZE_FIELD(json, "is_anonymous", isAnonymous, false, false); @@ -189,8 +188,21 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#chatmembermember struct ChatMemberMember : ChatMember { ChatMemberMember() = default; - explicit ChatMemberMember(const nl::json& json) : ChatMember(json) { - ChatMember::status = "member"; + explicit ChatMemberMember(const nl::json& json) { + ChatMemberMember::fromJson(json); + // ChatMember::status = "member"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = ChatMember::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + ChatMember::fromJson(json); } }; @@ -199,9 +211,9 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#chatmemberrestricted struct ChatMemberRestricted : ChatMember { ChatMemberRestricted() = default; - explicit ChatMemberRestricted(const nl::json& json) : ChatMember(json) { - fromJson(json); - ChatMember::status = "restricted"; + explicit ChatMemberRestricted(const nl::json& json) { + ChatMemberRestricted::fromJson(json); + // ChatMember::status = "restricted"; } /// @brief True, if the user is a member of the chat at the moment of the request @@ -255,7 +267,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatMember::toJson(); OBJECT_SERIALIZE_FIELD(json, "is_member", isMember); OBJECT_SERIALIZE_FIELD(json, "can_send_messages", canSendMessages); @@ -277,7 +289,7 @@ namespace tgbotxx { } /// @brief Deserializes this object from JSON - void fromJson(const nl::json& json) { + void fromJson(const nl::json& json) override { ChatMember::fromJson(json); OBJECT_DESERIALIZE_FIELD(json, "is_member", isMember, false, false); OBJECT_DESERIALIZE_FIELD(json, "can_send_messages", canSendMessages, false, false); @@ -303,8 +315,21 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#chatmemberleft struct ChatMemberLeft : ChatMember { ChatMemberLeft() = default; - explicit ChatMemberLeft(const nl::json& json) : ChatMember(json) { - ChatMember::status = "left"; + explicit ChatMemberLeft(const nl::json& json) { + ChatMemberLeft::fromJson(json); + // ChatMember::status = "left"; + } + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] nl::json toJson() const override { + nl::json json = ChatMember::toJson(); + return json; + } + + /// @brief Deserializes this object from JSON + void fromJson(const nl::json& json) override { + ChatMember::fromJson(json); } }; @@ -313,8 +338,9 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#chatmemberbanned struct ChatMemberBanned : ChatMember { ChatMemberBanned() = default; - explicit ChatMemberBanned(const nl::json& json) : ChatMember(json) { - ChatMember::status = "left"; + explicit ChatMemberBanned(const nl::json& json) { + ChatMemberBanned::fromJson(json); + // ChatMember::status = "left"; } /// @brief Date when restrictions will be lifted for this user; Unix time. If 0, then the user is banned forever @@ -322,14 +348,14 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const { + [[nodiscard]] nl::json toJson() const override { nl::json json = ChatMember::toJson(); OBJECT_SERIALIZE_FIELD(json, "until_date", untilDate); return json; } /// @brief Deserializes this object from JSON - void fromJson(const nl::json& json) { + void fromJson(const nl::json& json) override { ChatMember::fromJson(json); OBJECT_DESERIALIZE_FIELD(json, "until_date", untilDate, 0, false); } diff --git a/include/tgbotxx/objects/ForceReply.hpp b/include/tgbotxx/objects/ForceReply.hpp index cda702629..445d3c288 100644 --- a/include/tgbotxx/objects/ForceReply.hpp +++ b/include/tgbotxx/objects/ForceReply.hpp @@ -15,7 +15,7 @@ namespace tgbotxx { struct ForceReply : IReplyMarkup { ForceReply() = default; explicit ForceReply(const nl::json& json) { - _fromJson(json); + ForceReply::fromJson(json); } /// @brief Shows reply interface to the user, as if they manually selected the bot's message and tapped 'Reply' @@ -33,7 +33,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "force_reply", forceReply); OBJECT_SERIALIZE_FIELD(json, "input_field_placeholder", inputFieldPlaceholder); @@ -47,11 +47,5 @@ namespace tgbotxx { OBJECT_DESERIALIZE_FIELD(json, "input_field_placeholder", inputFieldPlaceholder, "", true); OBJECT_DESERIALIZE_FIELD(json, "selective", selective, false, true); } - - private: - /// @brief Just so we don't invoke virtual method fromJson() from constructor - void _fromJson(const nl::json& json) { - fromJson(json); - } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/InlineKeyboardMarkup.hpp b/include/tgbotxx/objects/InlineKeyboardMarkup.hpp index 9854d9137..687446483 100644 --- a/include/tgbotxx/objects/InlineKeyboardMarkup.hpp +++ b/include/tgbotxx/objects/InlineKeyboardMarkup.hpp @@ -9,7 +9,7 @@ namespace tgbotxx { struct InlineKeyboardMarkup : IReplyMarkup { InlineKeyboardMarkup() = default; explicit InlineKeyboardMarkup(const nl::json& json) { - _fromJson(json); + InlineKeyboardMarkup::fromJson(json); } virtual ~InlineKeyboardMarkup() = default; @@ -18,7 +18,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD_PTR_ARRAY_ARRAY(json, "inline_keyboard", inlineKeyboard); return json; @@ -28,11 +28,5 @@ namespace tgbotxx { void fromJson(const nl::json& json) override { OBJECT_DESERIALIZE_FIELD_PTR_ARRAY_ARRAY(json, "inline_keyboard", inlineKeyboard, false); } - - private: - /// @brief Just so we don't invoke virtual method fromJson() from constructor - void _fromJson(const nl::json& json) { - fromJson(json); - } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/InlineQueryResult.hpp b/include/tgbotxx/objects/InlineQueryResult.hpp index a01ab460c..4663b11c6 100644 --- a/include/tgbotxx/objects/InlineQueryResult.hpp +++ b/include/tgbotxx/objects/InlineQueryResult.hpp @@ -30,247 +30,239 @@ namespace tgbotxx { /// @note All URLs passed in inline query results will be available to end users and therefore must be assumed to be public. /// @ref https://core.telegram.org/bots/api#inlinequeryresult struct InlineQueryResult { - InlineQueryResult() = default; - explicit InlineQueryResult(const nl::json& json) { - _fromJson(json); - } - virtual ~InlineQueryResult() = default; - - /// @brief Type of the result - std::string type; - - /// @brief Unique identifier for this result, 1-64 bytes - std::string id; - - /// @brief Optional. Inline keyboard attached to the message - Ptr replyMarkup; - - virtual nl::json toJson() const { - nl::json json = nl::json::object(); - OBJECT_SERIALIZE_FIELD(json, "type", type); - OBJECT_SERIALIZE_FIELD(json, "id", id); - OBJECT_SERIALIZE_FIELD_PTR(json, "reply_markup", replyMarkup); - return json; - } - - virtual void fromJson(const nl::json& json) { - OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); - OBJECT_DESERIALIZE_FIELD(json, "id", id, "", false); - OBJECT_DESERIALIZE_FIELD_PTR(json, "reply_markup", replyMarkup, true); - } - - private: - void _fromJson(const nl::json& json) { // to avoid calling virtual method fromJson() from constructor - fromJson(json); - } + InlineQueryResult() = default; + explicit InlineQueryResult(const nl::json& json) { + InlineQueryResult::fromJson(json); + } + virtual ~InlineQueryResult() = default; + + /// @brief Type of the result + std::string type; + + /// @brief Unique identifier for this result, 1-64 bytes + std::string id; + + /// @brief Optional. Inline keyboard attached to the message + Ptr replyMarkup; + + virtual nl::json toJson() const { + nl::json json = nl::json::object(); + OBJECT_SERIALIZE_FIELD(json, "type", type); + OBJECT_SERIALIZE_FIELD(json, "id", id); + OBJECT_SERIALIZE_FIELD_PTR(json, "reply_markup", replyMarkup); + return json; + } + + virtual void fromJson(const nl::json& json) { + OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); + OBJECT_DESERIALIZE_FIELD(json, "id", id, "", false); + OBJECT_DESERIALIZE_FIELD_PTR(json, "reply_markup", replyMarkup, true); + } }; /// @brief Represents a link to an article or web page. /// @ref https://core.telegram.org/bots/api#inlinequeryresultarticle struct InlineQueryResultArticle : InlineQueryResult { - InlineQueryResultArticle() { - InlineQueryResult::type = "article"; - } - explicit InlineQueryResultArticle(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "article"; - } - - /// @brief Title of the result - std::string title; - - /// @brief Content of the message to be sent - Ptr inputMessageContent{}; - - /// @brief Optional. URL of the result - std::string url; - - /// @brief Optional. Pass True if you don't want the URL to be shown in the message - bool hideUrl{}; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Url of the thumbnail for the result - std::string thumbnailUrl; - - /// @brief Optional. Thumbnail width - std::int32_t thumbnailWidth{}; - - /// @brief Optional. Thumbnail height - std::int32_t thumbnailHeight{}; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - OBJECT_SERIALIZE_FIELD(json, "url", url); - OBJECT_SERIALIZE_FIELD(json, "hide_url", hideUrl); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, false); - OBJECT_DESERIALIZE_FIELD(json, "url", url, "", true); - OBJECT_DESERIALIZE_FIELD(json, "hide_url", hideUrl, false, true); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); - } + InlineQueryResultArticle() = default; + explicit InlineQueryResultArticle(const nl::json& json) { + InlineQueryResultArticle::fromJson(json); + // InlineQueryResultArticle::type = "article"; + } + + /// @brief Title of the result + std::string title; + + /// @brief Content of the message to be sent + Ptr inputMessageContent{}; + + /// @brief Optional. URL of the result + std::string url; + + /// @brief Optional. Pass True if you don't want the URL to be shown in the message + bool hideUrl{}; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Url of the thumbnail for the result + std::string thumbnailUrl; + + /// @brief Optional. Thumbnail width + std::int32_t thumbnailWidth{}; + + /// @brief Optional. Thumbnail height + std::int32_t thumbnailHeight{}; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + OBJECT_SERIALIZE_FIELD(json, "url", url); + OBJECT_SERIALIZE_FIELD(json, "hide_url", hideUrl); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, false); + OBJECT_DESERIALIZE_FIELD(json, "url", url, "", true); + OBJECT_DESERIALIZE_FIELD(json, "hide_url", hideUrl, false, true); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); + } }; /// @brief Represents a link to a photo. By default, this photo will be sent by the user with optional caption. /// Alternatively, you can use input_message_content to send a message with the specified content instead of the photo. /// @ref https://core.telegram.org/bots/api#inlinequeryresultphoto struct InlineQueryResultPhoto : InlineQueryResult { - InlineQueryResultPhoto() { - InlineQueryResult::type = "photo"; - } - explicit InlineQueryResultPhoto(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "photo"; - } - - /// @brief A valid URL of the photo. Photo must be in JPEG format. Photo size must not exceed 5MB - std::string photoUrl; - - /// @brief URL of the thumbnail for the photo - std::string thumbnailUrl; - - /// @brief Optional. Width of the photo - std::int32_t photoWidth{}; - - /// @brief Optional. Height of the photo - std::int32_t photoHeight{}; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the photo caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the photo - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "photo_url", photoUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "photo_width", photoWidth); - OBJECT_SERIALIZE_FIELD(json, "photo_height", photoHeight); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "photo_url", photoUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "photo_width", photoWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "photo_height", photoHeight, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultPhoto() = default; + explicit InlineQueryResultPhoto(const nl::json& json) { + InlineQueryResultPhoto::fromJson(json); + // InlineQueryResultPhoto::type = "photo"; + } + + /// @brief A valid URL of the photo. Photo must be in JPEG format. Photo size must not exceed 5MB + std::string photoUrl; + + /// @brief URL of the thumbnail for the photo + std::string thumbnailUrl; + + /// @brief Optional. Width of the photo + std::int32_t photoWidth{}; + + /// @brief Optional. Height of the photo + std::int32_t photoHeight{}; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the photo caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the photo + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "photo_url", photoUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "photo_width", photoWidth); + OBJECT_SERIALIZE_FIELD(json, "photo_height", photoHeight); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "photo_url", photoUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "photo_width", photoWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "photo_height", photoHeight, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to an animated GIF file. By default, this animated GIF file will be sent by the user with optional caption. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the animation. /// @ref https://core.telegram.org/bots/api#inlinequeryresultgif struct InlineQueryResultGif : InlineQueryResult { - InlineQueryResultGif() { - InlineQueryResult::type = "gif"; - } - explicit InlineQueryResultGif(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "gif"; - } - - /// @brief A valid URL for the GIF file. File size must not exceed 1MB - std::string gifUrl; - - /// @brief Optional. Width of the GIF - std::int32_t gifWidth{}; - - /// @brief Optional. Height of the GIF - std::int32_t gifHeight{}; - - /// @brief Optional. Duration of the GIF in seconds - std::int32_t gifDuration{}; - - /// @brief URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result - std::string thumbnailUrl; - - /// @brief Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg” - std::string thumbnailMimeType; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the GIF animation - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "gif_url", gifUrl); - OBJECT_SERIALIZE_FIELD(json, "gif_width", gifWidth); - OBJECT_SERIALIZE_FIELD(json, "gif_height", gifHeight); - OBJECT_SERIALIZE_FIELD(json, "gif_duration", gifDuration); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "gif_url", gifUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "gif_width", gifWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "gif_height", gifHeight, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "gif_duration", gifDuration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType, "", true); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultGif() = default; + explicit InlineQueryResultGif(const nl::json& json) { + InlineQueryResultGif::fromJson(json); + // InlineQueryResultGif::type = "gif"; + } + + /// @brief A valid URL for the GIF file. File size must not exceed 1MB + std::string gifUrl; + + /// @brief Optional. Width of the GIF + std::int32_t gifWidth{}; + + /// @brief Optional. Height of the GIF + std::int32_t gifHeight{}; + + /// @brief Optional. Duration of the GIF in seconds + std::int32_t gifDuration{}; + + /// @brief URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result + std::string thumbnailUrl; + + /// @brief Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg” + std::string thumbnailMimeType; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the GIF animation + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "gif_url", gifUrl); + OBJECT_SERIALIZE_FIELD(json, "gif_width", gifWidth); + OBJECT_SERIALIZE_FIELD(json, "gif_height", gifHeight); + OBJECT_SERIALIZE_FIELD(json, "gif_duration", gifDuration); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "gif_url", gifUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "gif_width", gifWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "gif_height", gifHeight, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "gif_duration", gifDuration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType, "", true); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). @@ -278,76 +270,75 @@ namespace tgbotxx { /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the animation. /// @ref https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif struct InlineQueryResultMpeg4Gif : InlineQueryResult { - InlineQueryResultMpeg4Gif() { - InlineQueryResult::type = "mpeg4_gif"; - } - explicit InlineQueryResultMpeg4Gif(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "mpeg4_gif"; - } - - /// @brief A valid URL for the MPEG4 file. File size must not exceed 1MB - std::string mpeg4Url; - - /// @brief Optional. Video width - std::int32_t mpeg4Width{}; - - /// @brief Optional. Video height - std::int32_t mpeg4Height{}; - - /// @brief Optional. Video duration in seconds - std::int32_t mpeg4Duration{}; - - /// @brief URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result - std::string thumbnailUrl; - - /// @brief Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg” - std::string thumbnailMimeType; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the video animation - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "mpeg4_url", mpeg4Url); - OBJECT_SERIALIZE_FIELD(json, "mpeg4_width", mpeg4Width); - OBJECT_SERIALIZE_FIELD(json, "mpeg4_height", mpeg4Height); - OBJECT_SERIALIZE_FIELD(json, "mpeg4_duration", mpeg4Duration); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "mpeg4_url", mpeg4Url, "", false); - OBJECT_DESERIALIZE_FIELD(json, "mpeg4_width", mpeg4Width, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "mpeg4_height", mpeg4Height, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "mpeg4_duration", mpeg4Duration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType, "", true); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultMpeg4Gif() = default; + explicit InlineQueryResultMpeg4Gif(const nl::json& json) { + InlineQueryResultMpeg4Gif::fromJson(json); + // InlineQueryResultMpeg4Gif::type = "mpeg4_gif"; + } + + /// @brief A valid URL for the MPEG4 file. File size must not exceed 1MB + std::string mpeg4Url; + + /// @brief Optional. Video width + std::int32_t mpeg4Width{}; + + /// @brief Optional. Video height + std::int32_t mpeg4Height{}; + + /// @brief Optional. Video duration in seconds + std::int32_t mpeg4Duration{}; + + /// @brief URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result + std::string thumbnailUrl; + + /// @brief Optional. MIME type of the thumbnail, must be one of “image/jpeg”, “image/gif”, or “video/mp4”. Defaults to “image/jpeg” + std::string thumbnailMimeType; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the video animation + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "mpeg4_url", mpeg4Url); + OBJECT_SERIALIZE_FIELD(json, "mpeg4_width", mpeg4Width); + OBJECT_SERIALIZE_FIELD(json, "mpeg4_height", mpeg4Height); + OBJECT_SERIALIZE_FIELD(json, "mpeg4_duration", mpeg4Duration); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "mpeg4_url", mpeg4Url, "", false); + OBJECT_DESERIALIZE_FIELD(json, "mpeg4_width", mpeg4Width, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "mpeg4_height", mpeg4Height, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "mpeg4_duration", mpeg4Duration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_mime_type", thumbnailMimeType, "", true); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -357,82 +348,81 @@ namespace tgbotxx { /// @ref https://core.telegram.org/bots/api#inlinequeryresultvideo /// @note If an InlineQueryResultVideo message contains an embedded video (e.g., YouTube), you must replace its content using inputMessageContent. struct InlineQueryResultVideo : InlineQueryResult { - InlineQueryResultVideo() { - InlineQueryResult::type = "video"; - } - explicit InlineQueryResultVideo(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "video"; - } - - /// @brief A valid URL for the embedded video player or video file - std::string videoUrl; - - /// @brief MIME type of the content of the video URL, “text/html” or “video/mp4” - std::string mimeType; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief URL of the thumbnail (JPEG only) for the video - std::string thumbnailUrl; - - /// @brief Optional. Caption of the video to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the video caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Video width - std::int32_t videoWidth{}; - - /// @brief Optional. Video height - std::int32_t videoHeight{}; - - /// @brief Optional. Video duration in seconds - std::int32_t videoDuration{}; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Content of the message to be sent instead of the video. - /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "video_url", videoUrl); - OBJECT_SERIALIZE_FIELD(json, "mime_type", mimeType); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD(json, "video_width", videoWidth); - OBJECT_SERIALIZE_FIELD(json, "video_height", videoHeight); - OBJECT_SERIALIZE_FIELD(json, "video_duration", videoDuration); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "video_url", videoUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "mime_type", mimeType, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD(json, "video_width", videoWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "video_height", videoHeight, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "video_duration", videoDuration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultVideo() = default; + explicit InlineQueryResultVideo(const nl::json& json) { + InlineQueryResultVideo::fromJson(json); + // InlineQueryResultVideo::type = "video"; + } + + /// @brief A valid URL for the embedded video player or video file + std::string videoUrl; + + /// @brief MIME type of the content of the video URL, “text/html” or “video/mp4” + std::string mimeType; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief URL of the thumbnail (JPEG only) for the video + std::string thumbnailUrl; + + /// @brief Optional. Caption of the video to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the video caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Video width + std::int32_t videoWidth{}; + + /// @brief Optional. Video height + std::int32_t videoHeight{}; + + /// @brief Optional. Video duration in seconds + std::int32_t videoDuration{}; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Content of the message to be sent instead of the video. + /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "video_url", videoUrl); + OBJECT_SERIALIZE_FIELD(json, "mime_type", mimeType); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD(json, "video_width", videoWidth); + OBJECT_SERIALIZE_FIELD(json, "video_height", videoHeight); + OBJECT_SERIALIZE_FIELD(json, "video_duration", videoDuration); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "video_url", videoUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "mime_type", mimeType, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD(json, "video_width", videoWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "video_height", videoHeight, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "video_duration", videoDuration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -440,62 +430,61 @@ namespace tgbotxx { /// By default, this audio file will be sent by the user. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the audio. struct InlineQueryResultAudio : InlineQueryResult { - InlineQueryResultAudio() { - InlineQueryResult::type = "audio"; - } - explicit InlineQueryResultAudio(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "audio"; - } - - /// @brief A valid URL for the audio file - std::string audioUrl; - - /// @brief Title - std::string title; - - /// @brief Optional. Caption, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the audio caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Performer - std::string performer; - - /// @brief Optional. Audio duration in seconds - std::int32_t audioDuration{}; - - /// @brief Optional. Content of the message to be sent instead of the video. - /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "audio_url", audioUrl); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD(json, "performer", performer); - OBJECT_SERIALIZE_FIELD(json, "audio_duration", audioDuration); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "audio_url", audioUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); - OBJECT_DESERIALIZE_FIELD(json, "audio_duration", audioDuration, 0, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultAudio() = default; + explicit InlineQueryResultAudio(const nl::json& json) { + InlineQueryResultAudio::fromJson(json); + // InlineQueryResultAudio::type = "audio"; + } + + /// @brief A valid URL for the audio file + std::string audioUrl; + + /// @brief Title + std::string title; + + /// @brief Optional. Caption, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the audio caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Performer + std::string performer; + + /// @brief Optional. Audio duration in seconds + std::int32_t audioDuration{}; + + /// @brief Optional. Content of the message to be sent instead of the video. + /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "audio_url", audioUrl); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD(json, "performer", performer); + OBJECT_SERIALIZE_FIELD(json, "audio_duration", audioDuration); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "audio_url", audioUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); + OBJECT_DESERIALIZE_FIELD(json, "audio_duration", audioDuration, 0, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -504,62 +493,61 @@ namespace tgbotxx { /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the voice message. /// @note: This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. struct InlineQueryResultVoice : InlineQueryResult { - InlineQueryResultVoice() { - InlineQueryResult::type = "voice"; - } - explicit InlineQueryResultVoice(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "voice"; - } - - /// @brief A valid URL for the voice recording - std::string voiceUrl; - - /// @brief Recording title - std::string title; - - /// @brief Optional. Caption, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the voice caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Performer - std::string performer; - - /// @brief Optional. Recording duration in seconds - std::int32_t voiceDuration{}; - - /// @brief Optional. Content of the message to be sent instead of the video. - /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - Ptr inputMessageContent; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "voice_url", voiceUrl); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD(json, "performer", performer); - OBJECT_SERIALIZE_FIELD(json, "voice_duration", voiceDuration); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "audio_url", voiceUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); - OBJECT_DESERIALIZE_FIELD(json, "voice_duration", voiceDuration, 0, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultVoice() = default; + explicit InlineQueryResultVoice(const nl::json& json) { + InlineQueryResultVoice::fromJson(json); + // InlineQueryResultVoice::type = "voice"; + } + + /// @brief A valid URL for the voice recording + std::string voiceUrl; + + /// @brief Recording title + std::string title; + + /// @brief Optional. Caption, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the voice caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Performer + std::string performer; + + /// @brief Optional. Recording duration in seconds + std::int32_t voiceDuration{}; + + /// @brief Optional. Content of the message to be sent instead of the video. + /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). + Ptr inputMessageContent; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "voice_url", voiceUrl); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD(json, "performer", performer); + OBJECT_SERIALIZE_FIELD(json, "voice_duration", voiceDuration); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "audio_url", voiceUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); + OBJECT_DESERIALIZE_FIELD(json, "voice_duration", voiceDuration, 0, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to a file. By default, this file will be sent by the user with an optional caption. @@ -567,379 +555,373 @@ namespace tgbotxx { /// Currently, only .PDF and .ZIP files can be sent using this method. /// @note: This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. struct InlineQueryResultDocument : InlineQueryResult { - InlineQueryResultDocument() { - InlineQueryResult::type = "document"; - } - explicit InlineQueryResultDocument(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "document"; - } - - /// @brief Title for the result - std::string title; - - /// @brief Optional. Caption of the document to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief A valid URL for the file - std::string documentUrl; - - /// @brief MIME type of the content of the file, either “application/pdf” or “application/zip” - std::string mimeType; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Content of the message to be sent instead of the video. - /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - Ptr inputMessageContent; - - /// @brief Optional. URL of the thumbnail (JPEG only) for the file - std::string thumbnailUrl; - - /// @brief Optional. Thumbnail width; - std::int32_t thumbnailWidth{}; - - /// @brief Optional. Thumbnail height; - std::int32_t thumbnailHeight{}; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD(json, "document_url", documentUrl); - OBJECT_SERIALIZE_FIELD(json, "mime_type", mimeType); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD(json, "document_url", documentUrl, "", false); - OBJECT_DESERIALIZE_FIELD(json, "mime_type", mimeType, "", false); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); - } + InlineQueryResultDocument() = default; + explicit InlineQueryResultDocument(const nl::json& json) { + InlineQueryResultDocument::fromJson(json); + InlineQueryResultDocument::type = "document"; + } + + /// @brief Title for the result + std::string title; + + /// @brief Optional. Caption of the document to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief A valid URL for the file + std::string documentUrl; + + /// @brief MIME type of the content of the file, either “application/pdf” or “application/zip” + std::string mimeType; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Content of the message to be sent instead of the video. + /// This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). + Ptr inputMessageContent; + + /// @brief Optional. URL of the thumbnail (JPEG only) for the file + std::string thumbnailUrl; + + /// @brief Optional. Thumbnail width; + std::int32_t thumbnailWidth{}; + + /// @brief Optional. Thumbnail height; + std::int32_t thumbnailHeight{}; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD(json, "document_url", documentUrl); + OBJECT_SERIALIZE_FIELD(json, "mime_type", mimeType); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD(json, "document_url", documentUrl, "", false); + OBJECT_DESERIALIZE_FIELD(json, "mime_type", mimeType, "", false); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); + } }; /// @brief Represents a location on a map. /// By default, the location will be sent by the user. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the location. struct InlineQueryResultLocation : InlineQueryResult { - InlineQueryResultLocation() { - InlineQueryResult::type = "location"; - } - explicit InlineQueryResultLocation(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "location"; - } - - /// @brief Location latitude in degrees - float latitude{}; - - /// @brief Location longitude in degrees - float longitude{}; - - /// @brief Location title - std::string title; - - /// @brief Optional. The radius of uncertainty for the location, measured in meters; 0-1500 - float horizontalAccuracy{}; - - /// @brief Optional. Period in seconds for which the location can be updated, should be between 60 and 86400. - std::int32_t livePeriod{}; - - /// @brief Optional. For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. - std::int32_t heading{}; - - /// @brief Optional. For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. - /// Must be between 1 and 100000 if specified. - std::int32_t proximityAlertRadius{}; - - /// @brief Optional. Content of the message to be sent instead of the location. - Ptr inputMessageContent; - - /// @brief Optional. URL of the thumbnail (JPEG only) for the file - std::string thumbnailUrl; - - /// @brief Optional. Thumbnail width; - std::int32_t thumbnailWidth{}; - - /// @brief Optional. Thumbnail height; - std::int32_t thumbnailHeight{}; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); - OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy); - OBJECT_SERIALIZE_FIELD(json, "live_period", livePeriod); - OBJECT_SERIALIZE_FIELD(json, "heading", heading); - OBJECT_SERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy, 0.0f, true); - OBJECT_DESERIALIZE_FIELD(json, "live_period", livePeriod, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "heading", heading, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius, 0, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); - } + InlineQueryResultLocation() = default; + explicit InlineQueryResultLocation(const nl::json& json) { + InlineQueryResultLocation::fromJson(json); + // InlineQueryResultLocation::type = "location"; + } + + /// @brief Location latitude in degrees + float latitude{}; + + /// @brief Location longitude in degrees + float longitude{}; + + /// @brief Location title + std::string title; + + /// @brief Optional. The radius of uncertainty for the location, measured in meters; 0-1500 + float horizontalAccuracy{}; + + /// @brief Optional. Period in seconds for which the location can be updated, should be between 60 and 86400. + std::int32_t livePeriod{}; + + /// @brief Optional. For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. + std::int32_t heading{}; + + /// @brief Optional. For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. + /// Must be between 1 and 100000 if specified. + std::int32_t proximityAlertRadius{}; + + /// @brief Optional. Content of the message to be sent instead of the location. + Ptr inputMessageContent; + + /// @brief Optional. URL of the thumbnail (JPEG only) for the file + std::string thumbnailUrl; + + /// @brief Optional. Thumbnail width; + std::int32_t thumbnailWidth{}; + + /// @brief Optional. Thumbnail height; + std::int32_t thumbnailHeight{}; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); + OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy); + OBJECT_SERIALIZE_FIELD(json, "live_period", livePeriod); + OBJECT_SERIALIZE_FIELD(json, "heading", heading); + OBJECT_SERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy, 0.0f, true); + OBJECT_DESERIALIZE_FIELD(json, "live_period", livePeriod, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "heading", heading, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius, 0, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); + } }; /// @brief Represents a venue. By default, the venue will be sent by the user. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the venue. struct InlineQueryResultVenue : InlineQueryResult { - InlineQueryResultVenue() { - InlineQueryResult::type = "venue"; - } - explicit InlineQueryResultVenue(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "venue"; - } - - /// @brief Latitude of the venue location in degrees - float latitude{}; - - /// @brief Longitude of the venue location in degrees - float longitude{}; - - /// @brief Title of the venue - std::string title; - - /// @brief Address of the venue - std::string address; - - /// @brief Optional. Foursquare identifier of the venue if known - std::string foursquareId; - - /// @brief Optional. Foursquare type of the venue, if known. - /// (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.) - std::string foursquareType; - - /// @brief Optional. Google Places identifier of the venue - std::string googlePlaceId; - - /// @brief Optional. Google Places type of the venue. (See [supported types](https://developers.google.com/places/web-service/supported_types).) - std::string googlePlaceType; - - /// @brief Optional. Content of the message to be sent instead of the venue. - Ptr inputMessageContent; - - /// @brief Optional. URL of the thumbnail (JPEG only) for the file - std::string thumbnailUrl; - - /// @brief Optional. Thumbnail width; - std::int32_t thumbnailWidth{}; - - /// @brief Optional. Thumbnail height; - std::int32_t thumbnailHeight{}; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); - OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "address", address); - OBJECT_SERIALIZE_FIELD(json, "foursquare_id", foursquareId); - OBJECT_SERIALIZE_FIELD(json, "foursquare_type", foursquareType); - OBJECT_SERIALIZE_FIELD(json, "google_place_id", googlePlaceId); - OBJECT_SERIALIZE_FIELD(json, "google_place_type", googlePlaceType); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "address", address, "", false); - OBJECT_DESERIALIZE_FIELD(json, "foursquare_id", foursquareId, "", true); - OBJECT_DESERIALIZE_FIELD(json, "foursquare_type", foursquareType, "", true); - OBJECT_DESERIALIZE_FIELD(json, "google_place_id", googlePlaceId, "", true); - OBJECT_DESERIALIZE_FIELD(json, "google_place_type", googlePlaceType, "", true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); - } + InlineQueryResultVenue() = default; + explicit InlineQueryResultVenue(const nl::json& json) { + InlineQueryResultVenue::fromJson(json); + // InlineQueryResultVenue::type = "venue"; + } + + /// @brief Latitude of the venue location in degrees + float latitude{}; + + /// @brief Longitude of the venue location in degrees + float longitude{}; + + /// @brief Title of the venue + std::string title; + + /// @brief Address of the venue + std::string address; + + /// @brief Optional. Foursquare identifier of the venue if known + std::string foursquareId; + + /// @brief Optional. Foursquare type of the venue, if known. + /// (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.) + std::string foursquareType; + + /// @brief Optional. Google Places identifier of the venue + std::string googlePlaceId; + + /// @brief Optional. Google Places type of the venue. (See [supported types](https://developers.google.com/places/web-service/supported_types).) + std::string googlePlaceType; + + /// @brief Optional. Content of the message to be sent instead of the venue. + Ptr inputMessageContent; + + /// @brief Optional. URL of the thumbnail (JPEG only) for the file + std::string thumbnailUrl; + + /// @brief Optional. Thumbnail width; + std::int32_t thumbnailWidth{}; + + /// @brief Optional. Thumbnail height; + std::int32_t thumbnailHeight{}; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); + OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "address", address); + OBJECT_SERIALIZE_FIELD(json, "foursquare_id", foursquareId); + OBJECT_SERIALIZE_FIELD(json, "foursquare_type", foursquareType); + OBJECT_SERIALIZE_FIELD(json, "google_place_id", googlePlaceId); + OBJECT_SERIALIZE_FIELD(json, "google_place_type", googlePlaceType); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "address", address, "", false); + OBJECT_DESERIALIZE_FIELD(json, "foursquare_id", foursquareId, "", true); + OBJECT_DESERIALIZE_FIELD(json, "foursquare_type", foursquareType, "", true); + OBJECT_DESERIALIZE_FIELD(json, "google_place_id", googlePlaceId, "", true); + OBJECT_DESERIALIZE_FIELD(json, "google_place_type", googlePlaceType, "", true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); + } }; /// @brief Represents a venue. By default, the venue will be sent by the user. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the venue. struct InlineQueryResultContact : InlineQueryResult { - InlineQueryResultContact() { - InlineQueryResult::type = "contact"; - } - explicit InlineQueryResultContact(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "contact"; - } - - /// @brief Contact's phone number - std::string phoneNumber; - - /// @brief Contact's first name - std::string firstName; - - /// @brief Optional. Contact's last name - std::string lastName; - - /// @brief Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes - std::string vcard; - - /// @brief Optional. Content of the message to be sent instead of the contact. - Ptr inputMessageContent; - - /// @brief Optional. Url of the thumbnail for the result - std::string thumbnailUrl; - - /// @brief Optional. Thumbnail width; - std::int32_t thumbnailWidth{}; - - /// @brief Optional. Thumbnail height; - std::int32_t thumbnailHeight{}; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "phone_number", phoneNumber); - OBJECT_SERIALIZE_FIELD(json, "first_name", firstName); - OBJECT_SERIALIZE_FIELD(json, "last_name", lastName); - OBJECT_SERIALIZE_FIELD(json, "vcard", vcard); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); - OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "phone_number", phoneNumber, "", false); - OBJECT_DESERIALIZE_FIELD(json, "first_name", firstName, "", false); - OBJECT_DESERIALIZE_FIELD(json, "last_name", lastName, "", true); - OBJECT_DESERIALIZE_FIELD(json, "vcard", vcard, "", true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); - } + InlineQueryResultContact() = default; + explicit InlineQueryResultContact(const nl::json& json) { + InlineQueryResultContact::fromJson(json); + // InlineQueryResultContact::type = "contact"; + } + + /// @brief Contact's phone number + std::string phoneNumber; + + /// @brief Contact's first name + std::string firstName; + + /// @brief Optional. Contact's last name + std::string lastName; + + /// @brief Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes + std::string vcard; + + /// @brief Optional. Content of the message to be sent instead of the contact. + Ptr inputMessageContent; + + /// @brief Optional. Url of the thumbnail for the result + std::string thumbnailUrl; + + /// @brief Optional. Thumbnail width; + std::int32_t thumbnailWidth{}; + + /// @brief Optional. Thumbnail height; + std::int32_t thumbnailHeight{}; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "phone_number", phoneNumber); + OBJECT_SERIALIZE_FIELD(json, "first_name", firstName); + OBJECT_SERIALIZE_FIELD(json, "last_name", lastName); + OBJECT_SERIALIZE_FIELD(json, "vcard", vcard); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth); + OBJECT_SERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "phone_number", phoneNumber, "", false); + OBJECT_DESERIALIZE_FIELD(json, "first_name", firstName, "", false); + OBJECT_DESERIALIZE_FIELD(json, "last_name", lastName, "", true); + OBJECT_DESERIALIZE_FIELD(json, "vcard", vcard, "", true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_url", thumbnailUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_width", thumbnailWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail_height", thumbnailHeight, 0, true); + } }; /// @brief Represents a Game. /// @note This will only work in Telegram versions released after October 1, 2016. Older clients will not display any inline results if a game result is among them. struct InlineQueryResultGame : InlineQueryResult { - InlineQueryResultGame() { - InlineQueryResult::type = "game"; - } - explicit InlineQueryResultGame(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "game"; - } - - /// @brief Short name of the game - std::string gameShortName; - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "game_short_name", gameShortName); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "game_short_name", gameShortName, "", false); - } + InlineQueryResultGame() = default; + explicit InlineQueryResultGame(const nl::json& json) { + InlineQueryResultGame::fromJson(json); + // InlineQueryResultGame::type = "game"; + } + + /// @brief Short name of the game + std::string gameShortName; + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "game_short_name", gameShortName); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "game_short_name", gameShortName, "", false); + } }; /// @brief Represents a link to a photo stored on the Telegram servers. By default, this photo will be sent by the user with an optional caption. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the photo. struct InlineQueryResultCachedPhoto : InlineQueryResult { - InlineQueryResultCachedPhoto() { - InlineQueryResult::type = "photo"; - } - explicit InlineQueryResultCachedPhoto(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "photo"; - } - - /// @brief A valid file identifier of the photo - std::string photoFileId; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the photo caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the photo. - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "photo_file_id", photoFileId); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "photo_file_id", photoFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedPhoto() = default; + explicit InlineQueryResultCachedPhoto(const nl::json& json) { + InlineQueryResultCachedPhoto::fromJson(json); + // InlineQueryResultCachedPhoto::type = "photo"; + } + + /// @brief A valid file identifier of the photo + std::string photoFileId; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Caption of the photo to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the photo caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the photo. + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "photo_file_id", photoFileId); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "photo_file_id", photoFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -947,104 +929,102 @@ namespace tgbotxx { /// By default, this animated GIF file will be sent by the user with an optional caption. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the photo. struct InlineQueryResultCachedGif : InlineQueryResult { - InlineQueryResultCachedGif() { - InlineQueryResult::type = "gif"; - } - explicit InlineQueryResultCachedGif(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "gif"; - } - - /// @brief A valid file identifier for the GIF file - std::string gifFileId; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the GIF caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the GIF. - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "gif_file_id", gifFileId); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "gif_file_id", gifFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedGif() = default; + explicit InlineQueryResultCachedGif(const nl::json& json) { + InlineQueryResultCachedGif::fromJson(json); + // InlineQueryResultCachedGif::type = "gif"; + } + + /// @brief A valid file identifier for the GIF file + std::string gifFileId; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Caption of the GIF file to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the GIF caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the GIF. + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "gif_file_id", gifFileId); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "gif_file_id", gifFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the Telegram servers. /// By default, this animated MPEG-4 file will be sent by the user with an optional caption. /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the photo. struct InlineQueryResultCachedMpeg4Gif : InlineQueryResult { - InlineQueryResultCachedMpeg4Gif() { - InlineQueryResult::type = "mpeg4_gif"; - } - explicit InlineQueryResultCachedMpeg4Gif(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "mpeg4_gif"; - } - - /// @brief A valid file identifier for the MPEG4 file - std::string mpeg4FileId; - - /// @brief Optional. Title for the result - std::string title; - - /// @brief Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the MPEG-4 caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Optional. Content of the message to be sent instead of the video animation - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "mpeg4_file_id", mpeg4FileId); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "mpeg4_file_id", mpeg4FileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedMpeg4Gif() = default; + explicit InlineQueryResultCachedMpeg4Gif(const nl::json& json) { + InlineQueryResultCachedMpeg4Gif::fromJson(json); + // InlineQueryResultCachedMpeg4Gif::type = "mpeg4_gif"; + } + + /// @brief A valid file identifier for the MPEG4 file + std::string mpeg4FileId; + + /// @brief Optional. Title for the result + std::string title; + + /// @brief Optional. Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the MPEG-4 caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Optional. Content of the message to be sent instead of the video animation + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "mpeg4_file_id", mpeg4FileId); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "mpeg4_file_id", mpeg4FileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to a sticker stored on the Telegram servers. @@ -1052,32 +1032,31 @@ namespace tgbotxx { /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the photo. /// @note This will only work in Telegram versions released after 9 April, 2016 for static stickers and after 06 July, 2019 for animated stickers. Older clients will ignore them. struct InlineQueryResultCachedSticker : InlineQueryResult { - InlineQueryResultCachedSticker() { - InlineQueryResult::type = "sticker"; - } - explicit InlineQueryResultCachedSticker(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "sticker"; - } - - /// @brief A valid file identifier of the sticker - std::string stickerFileId; - - /// @brief Optional. Optional. Content of the message to be sent instead of the sticker - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "sticker_file_id", stickerFileId); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "sticker_file_id", stickerFileId, "", false); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedSticker() = default; + explicit InlineQueryResultCachedSticker(const nl::json& json) { + InlineQueryResultCachedSticker::fromJson(json); + // InlineQueryResultCachedSticker::type = "sticker"; + } + + /// @brief A valid file identifier of the sticker + std::string stickerFileId; + + /// @brief Optional. Optional. Content of the message to be sent instead of the sticker + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "sticker_file_id", stickerFileId); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "sticker_file_id", stickerFileId, "", false); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -1086,114 +1065,112 @@ namespace tgbotxx { /// Alternatively, you can use inputMessageContent to send a message with the specified content instead of the photo. /// @note This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. struct InlineQueryResultCachedDocument : InlineQueryResult { - InlineQueryResultCachedDocument() { - InlineQueryResult::type = "document"; - } - explicit InlineQueryResultCachedDocument(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "document"; - } - - /// @brief Title for the result - std::string title; - - /// @brief A valid file identifier for the file - std::string documentFileId; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Caption of the document to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the document. - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "document_file_id", documentFileId); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "document_file_id", documentFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedDocument() = default; + explicit InlineQueryResultCachedDocument(const nl::json& json) { + InlineQueryResultCachedDocument::fromJson(json); + // InlineQueryResultCachedDocument::type = "document"; + } + + /// @brief Title for the result + std::string title; + + /// @brief A valid file identifier for the file + std::string documentFileId; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Caption of the document to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the document. + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "document_file_id", documentFileId); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "document_file_id", documentFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; /// @brief Represents a link to a video file stored on the Telegram servers. /// By default, this video file will be sent by the user with an optional caption struct InlineQueryResultCachedVideo : InlineQueryResult { - InlineQueryResultCachedVideo() { - InlineQueryResult::type = "video"; - } - explicit InlineQueryResultCachedVideo(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "video"; - } - - /// @brief A valid file identifier for the video file - std::string videoFileId; - - /// @brief Title for the result - std::string title; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Caption of the video to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the video caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the video. - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "video_file_id", videoFileId); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "video_file_id", videoFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedVideo() = default; + explicit InlineQueryResultCachedVideo(const nl::json& json) { + InlineQueryResultCachedVideo::fromJson(json); + // InlineQueryResultCachedVideo::type = "video"; + } + + /// @brief A valid file identifier for the video file + std::string videoFileId; + + /// @brief Title for the result + std::string title; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Caption of the video to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the document caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the video caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the video. + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "video_file_id", videoFileId); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "video_file_id", videoFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -1201,57 +1178,56 @@ namespace tgbotxx { /// By default, this voice message will be sent by the user. /// @note This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. struct InlineQueryResultCachedVoice : InlineQueryResult { - InlineQueryResultCachedVoice() { - InlineQueryResult::type = "voice"; - } - explicit InlineQueryResultCachedVoice(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "voice"; - } - - /// @brief A valid file identifier for the voice message - std::string voiceFileId; - - /// @brief Voice message title - std::string title; - - /// @brief Optional. Short description of the result - std::string description; - - /// @brief Optional. Caption of the voice to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the voice caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the voice caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the voice. - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "voice_file_id", voiceFileId); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "voice_file_id", voiceFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedVoice() = default; + explicit InlineQueryResultCachedVoice(const nl::json& json) { + InlineQueryResultCachedVoice::fromJson(json); + // InlineQueryResultCachedVoice::type = "voice"; + } + + /// @brief A valid file identifier for the voice message + std::string voiceFileId; + + /// @brief Voice message title + std::string title; + + /// @brief Optional. Short description of the result + std::string description; + + /// @brief Optional. Caption of the voice to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the voice caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the voice caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the voice. + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "voice_file_id", voiceFileId); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "voice_file_id", voiceFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", true); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; @@ -1259,47 +1235,46 @@ namespace tgbotxx { /// By default, this audio file will be sent by the user. /// @note This will only work in Telegram versions released after 9 April, 2016. Older clients will ignore them. struct InlineQueryResultCachedAudio : InlineQueryResult { - InlineQueryResultCachedAudio() { - InlineQueryResult::type = "audio"; - } - explicit InlineQueryResultCachedAudio(const nl::json& json) : InlineQueryResult(json) { - InlineQueryResult::type = "audio"; - } - - /// @brief A valid file identifier for the audio file - std::string audioFileId; - - /// @brief Optional. Caption of the audio to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the audio caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the voice caption, which can be specified instead of parseMode - std::vector> captionEntities; - - /// @brief Optional. Content of the message to be sent instead of the audio - Ptr inputMessageContent; - - - nl::json toJson() const override { - nl::json json = InlineQueryResult::toJson(); - OBJECT_SERIALIZE_FIELD(json, "audio_file_id", audioFileId); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); - return json; - } - - void fromJson(const nl::json& json) override { - InlineQueryResult::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "audio_file_id", audioFileId, "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); - } + InlineQueryResultCachedAudio() = default; + explicit InlineQueryResultCachedAudio(const nl::json& json) { + InlineQueryResultCachedAudio::fromJson(json); + // InlineQueryResultCachedAudio::type = "audio"; + } + + /// @brief A valid file identifier for the audio file + std::string audioFileId; + + /// @brief Optional. Caption of the audio to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the audio caption. See [formatting options](https://core.telegram.org/bots/api#formatting-options) for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the voice caption, which can be specified instead of parseMode + std::vector> captionEntities; + + /// @brief Optional. Content of the message to be sent instead of the audio + Ptr inputMessageContent; + + + nl::json toJson() const override { + nl::json json = InlineQueryResult::toJson(); + OBJECT_SERIALIZE_FIELD(json, "audio_file_id", audioFileId); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + OBJECT_SERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent); + return json; + } + + void fromJson(const nl::json& json) override { + InlineQueryResult::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "audio_file_id", audioFileId, "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + OBJECT_DESERIALIZE_FIELD_PTR(json, "input_message_content", inputMessageContent, true); + } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/InputMedia.hpp b/include/tgbotxx/objects/InputMedia.hpp index 6a0808cab..1f6c63337 100644 --- a/include/tgbotxx/objects/InputMedia.hpp +++ b/include/tgbotxx/objects/InputMedia.hpp @@ -11,274 +11,263 @@ namespace tgbotxx { /// - InputMediaVideo /// @ref https://core.telegram.org/bots/api#inputmedia struct InputMedia { - InputMedia() = default; - explicit InputMedia(const nl::json& json) { - _fromJson(json); - } - virtual ~InputMedia() = default; - - /// @brief Type of the result. - std::string type; - - /// @brief File to send. - /// - Pass a file_id std::string to send a file that exists on the Telegram servers (recommended), - /// - Pass an HTTP URL std::string for Telegram to get a file from the Internet, or - /// - Pass a cpr::File to upload a local file - /// The latter will internally use “attach://” with multipart/form-data under name to upload the local file. - /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files - std::variant media{""}; - - /// @brief Optional. Caption of the media to be sent, 0-1024 characters after entities parsing - std::string caption; - - /// @brief Optional. Mode for parsing entities in the media caption. - /// See https://core.telegram.org/bots/api#formatting-options for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode - std::vector> captionEntities; - - - /// @brief Serializes this object to JSON - /// @returns JSON representation of this object - virtual nl::json toJson() const { - nl::json json = nl::json::object(); - OBJECT_SERIALIZE_FIELD(json, "type", type); - // media variant - if (auto idx = media.index(); idx == 0) - json["media"] = std::get(media).filepath; - else if (idx == 1) - json["media"] = std::get(media); - OBJECT_SERIALIZE_FIELD(json, "caption", caption); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); - return json; - } - - /// @brief Deserializes this object from JSON - virtual void fromJson(const nl::json& json) { - OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); - // media variant, we can't get a local file from remote, so it's always a URL or file id std::string. - OBJECT_DESERIALIZE_FIELD(json, "media", std::get(media), "", false); - OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); - } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } + InputMedia() = default; + explicit InputMedia(const nl::json& json) { + InputMedia::fromJson(json); + } + virtual ~InputMedia() = default; + + /// @brief Type of the result. + std::string type; + + /// @brief File to send. + /// - Pass a file_id std::string to send a file that exists on the Telegram servers (recommended), + /// - Pass an HTTP URL std::string for Telegram to get a file from the Internet, or + /// - Pass a cpr::File to upload a local file + /// The latter will internally use “attach://” with multipart/form-data under name to upload the local file. + /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files + std::variant media{""}; + + /// @brief Optional. Caption of the media to be sent, 0-1024 characters after entities parsing + std::string caption; + + /// @brief Optional. Mode for parsing entities in the media caption. + /// See https://core.telegram.org/bots/api#formatting-options for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in the caption, which can be specified instead of parseMode + std::vector> captionEntities; + + + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + [[nodiscard]] virtual nl::json toJson() const { + nl::json json = nl::json::object(); + OBJECT_SERIALIZE_FIELD(json, "type", type); + // media variant + if (const std::size_t idx = media.index(); idx == 0) + json["media"] = std::get(media).filepath; + else if (idx == 1) + json["media"] = std::get(media); + OBJECT_SERIALIZE_FIELD(json, "caption", caption); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities); + return json; + } + + /// @brief Deserializes this object from JSON + virtual void fromJson(const nl::json& json) { + OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); + // media variant, we can't get a local file from remote, so it's always a URL or file id std::string. + OBJECT_DESERIALIZE_FIELD(json, "media", std::get(media), "", false); + OBJECT_DESERIALIZE_FIELD(json, "caption", caption, "", true); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "caption_entities", captionEntities, true); + } }; /// @brief Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. /// @ref https://core.telegram.org/bots/api#inputmediaanimation struct InputMediaAnimation : InputMedia { - InputMediaAnimation() { - InputMedia::type = "animation"; - } - explicit InputMediaAnimation(const nl::json& json) { - InputMedia::fromJson(json); - InputMedia::type = "animation"; - } - - /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. - /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. - /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, - /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files - std::string thumbnail; - - /// @brief Optional. Animation width - std::int32_t width{}; - - /// @brief Optional. Animation height - std::int32_t height{}; - - /// @brief Optional. Animation duration in seconds - std::int32_t duration{}; - - /// @brief Optional. Pass True if the animation needs to be covered with a spoiler animation - bool hasSpoiler{}; - - - nl::json toJson() const override { - nl::json json = InputMedia::toJson(); - OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); - OBJECT_SERIALIZE_FIELD(json, "width", width); - OBJECT_SERIALIZE_FIELD(json, "height", height); - OBJECT_SERIALIZE_FIELD(json, "duration", duration); - OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); - return json; - } - void fromJson(const nl::json& json) override { - InputMedia::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); - OBJECT_DESERIALIZE_FIELD(json, "width", width, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "height", height, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); - } + InputMediaAnimation() = default; + explicit InputMediaAnimation(const nl::json& json) { + InputMediaAnimation::fromJson(json); + // InputMediaAnimation::type = "animation"; + } + + /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. + /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, + /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . + /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files + std::string thumbnail; + + /// @brief Optional. Animation width + std::int32_t width{}; + + /// @brief Optional. Animation height + std::int32_t height{}; + + /// @brief Optional. Animation duration in seconds + std::int32_t duration{}; + + /// @brief Optional. Pass True if the animation needs to be covered with a spoiler animation + bool hasSpoiler{}; + + + [[nodiscard]] nl::json toJson() const override { + nl::json json = InputMedia::toJson(); + OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); + OBJECT_SERIALIZE_FIELD(json, "width", width); + OBJECT_SERIALIZE_FIELD(json, "height", height); + OBJECT_SERIALIZE_FIELD(json, "duration", duration); + OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); + return json; + } + void fromJson(const nl::json& json) override { + InputMedia::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); + OBJECT_DESERIALIZE_FIELD(json, "width", width, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "height", height, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); + } }; /// @brief Represents a general file to be sent. /// @ref https://core.telegram.org/bots/api#inputmediadocument struct InputMediaDocument : InputMedia { - InputMediaDocument() { - InputMedia::type = "document"; - } - explicit InputMediaDocument(const nl::json& json) : InputMedia(json) { - InputMedia::type = "document"; - } - - /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. - /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. - /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, - /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files - std::string thumbnail; - - /// @brief Optional. Disables automatic server-side content type detection for files uploaded using multipart/form-data. - /// Always True, if the document is sent as part of an album. - bool disableContentTypeDetection{}; - - - nl::json toJson() const override { - nl::json json = InputMedia::toJson(); - OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); - OBJECT_SERIALIZE_FIELD(json, "disable_content_type_detection", disableContentTypeDetection); - return json; - } - void fromJson(const nl::json& json) override { - InputMedia::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); - OBJECT_DESERIALIZE_FIELD(json, "disable_content_type_detection", disableContentTypeDetection, false, true); - } + InputMediaDocument() = default; + explicit InputMediaDocument(const nl::json& json) { + InputMediaDocument::fromJson(json); + // InputMediaDocument::type = "document"; + } + + /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. + /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, + /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . + /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files + std::string thumbnail; + + /// @brief Optional. Disables automatic server-side content type detection for files uploaded using multipart/form-data. + /// Always True, if the document is sent as part of an album. + bool disableContentTypeDetection{}; + + + [[nodiscard]] nl::json toJson() const override { + nl::json json = InputMedia::toJson(); + OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); + OBJECT_SERIALIZE_FIELD(json, "disable_content_type_detection", disableContentTypeDetection); + return json; + } + void fromJson(const nl::json& json) override { + InputMedia::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); + OBJECT_DESERIALIZE_FIELD(json, "disable_content_type_detection", disableContentTypeDetection, false, true); + } }; /// @brief Represents an audio file to be treated as music to be sent. /// @ref https://core.telegram.org/bots/api#inputmediaaudio struct InputMediaAudio : InputMedia { - InputMediaAudio() { - InputMedia::type = "audio"; - } - explicit InputMediaAudio(const nl::json& json) : InputMedia(json) { - InputMedia::type = "audio"; - } - - /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. - /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. - /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, - /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files - std::string thumbnail; - - /// @brief Optional. Duration of the audio in seconds - std::int32_t duration{}; - - /// @brief Optional. Performer of the audio - std::string performer; - - /// @brief Optional. Title of the audio - std::string title; - - nl::json toJson() const override { - nl::json json = InputMedia::toJson(); - OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); - OBJECT_SERIALIZE_FIELD(json, "duration", duration); - OBJECT_SERIALIZE_FIELD(json, "performer", performer); - OBJECT_SERIALIZE_FIELD(json, "title", title); - return json; - } - void fromJson(const nl::json& json) override { - InputMedia::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); - OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); - } + InputMediaAudio() = default; + explicit InputMediaAudio(const nl::json& json) { + InputMediaAudio::fromJson(json); + // InputMediaAudio::type = "audio"; + } + + /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. + /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, + /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . + /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files + std::string thumbnail; + + /// @brief Optional. Duration of the audio in seconds + std::int32_t duration{}; + + /// @brief Optional. Performer of the audio + std::string performer; + + /// @brief Optional. Title of the audio + std::string title; + + [[nodiscard]] nl::json toJson() const override { + nl::json json = InputMedia::toJson(); + OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); + OBJECT_SERIALIZE_FIELD(json, "duration", duration); + OBJECT_SERIALIZE_FIELD(json, "performer", performer); + OBJECT_SERIALIZE_FIELD(json, "title", title); + return json; + } + void fromJson(const nl::json& json) override { + InputMedia::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); + OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "performer", performer, "", true); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", true); + } }; /// @brief Represents a photo to be sent. /// @ref https://core.telegram.org/bots/api#inputmediaphoto struct InputMediaPhoto : InputMedia { - InputMediaPhoto() { - InputMedia::type = "photo"; - } - explicit InputMediaPhoto(const nl::json& json) : InputMedia(json) { - InputMedia::type = "photo"; - } - - /// @brief Optional. Pass True if the photo needs to be covered with a spoiler animation - bool hasSpoiler{}; - - nl::json toJson() const override { - nl::json json = InputMedia::toJson(); - OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); - return json; - } - void fromJson(const nl::json& json) override { - InputMedia::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); - } + InputMediaPhoto() = default; + explicit InputMediaPhoto(const nl::json& json) { + InputMediaPhoto::fromJson(json); + // InputMediaPhoto::type = "photo"; + } + + /// @brief Optional. Pass True if the photo needs to be covered with a spoiler animation + bool hasSpoiler{}; + + [[nodiscard]] nl::json toJson() const override { + nl::json json = InputMedia::toJson(); + OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); + return json; + } + void fromJson(const nl::json& json) override { + InputMedia::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); + } }; /// @brief Represents a video to be sent. /// @ref https://core.telegram.org/bots/api#inputmediavideo struct InputMediaVideo : InputMedia { - InputMediaVideo() { - InputMedia::type = "video"; - } - explicit InputMediaVideo(const nl::json& json) : InputMedia(json) { - InputMedia::type = "video"; - } - - /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. - /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. - /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, - /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . - /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files - std::string thumbnail; - - /// @brief Optional. Video width - std::int32_t width{}; - - /// @brief Optional. Video height - std::int32_t height{}; - - /// @brief Optional. Video duration in seconds - std::int32_t duration{}; - - /// @brief Optional. Pass True if the uploaded video is suitable for streaming - bool supportsStreaming{}; - - /// @brief Optional. Pass True if the animation needs to be covered with a spoiler animation - bool hasSpoiler{}; - - - nl::json toJson() const override { - nl::json json = InputMedia::toJson(); - OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); - OBJECT_SERIALIZE_FIELD(json, "width", width); - OBJECT_SERIALIZE_FIELD(json, "height", height); - OBJECT_SERIALIZE_FIELD(json, "duration", duration); - OBJECT_SERIALIZE_FIELD(json, "supports_streaming", supportsStreaming); - OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); - return json; - } - void fromJson(const nl::json& json) override { - InputMedia::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); - OBJECT_DESERIALIZE_FIELD(json, "width", width, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "height", height, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "supports_streaming", supportsStreaming, false, true); - OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); - } + InputMediaVideo() = default; + explicit InputMediaVideo(const nl::json& json) { + InputMediaVideo::fromJson(json); + // InputMediaVideo::type = "video"; + } + + /// @brief Optional. Thumbnail of the file sent; can be ignored if thumbnail generation for the file is supported server-side. + /// The thumbnail should be in JPEG format and less than 200 kB in size. A thumbnail's width and height should not exceed 320. + /// Ignored if the file is not uploaded using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new file, + /// so you can pass “attach://” if the thumbnail was uploaded using multipart/form-data under . + /// More information on Sending Files » https://core.telegram.org/bots/api#sending-files + std::string thumbnail; + + /// @brief Optional. Video width + std::int32_t width{}; + + /// @brief Optional. Video height + std::int32_t height{}; + + /// @brief Optional. Video duration in seconds + std::int32_t duration{}; + + /// @brief Optional. Pass True if the uploaded video is suitable for streaming + bool supportsStreaming{}; + + /// @brief Optional. Pass True if the animation needs to be covered with a spoiler animation + bool hasSpoiler{}; + + + [[nodiscard]] nl::json toJson() const override { + nl::json json = InputMedia::toJson(); + OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); + OBJECT_SERIALIZE_FIELD(json, "width", width); + OBJECT_SERIALIZE_FIELD(json, "height", height); + OBJECT_SERIALIZE_FIELD(json, "duration", duration); + OBJECT_SERIALIZE_FIELD(json, "supports_streaming", supportsStreaming); + OBJECT_SERIALIZE_FIELD(json, "has_spoiler", hasSpoiler); + return json; + } + void fromJson(const nl::json& json) override { + InputMedia::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "thumbnail", thumbnail, "", true); + OBJECT_DESERIALIZE_FIELD(json, "width", width, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "height", height, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "duration", duration, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "supports_streaming", supportsStreaming, false, true); + OBJECT_DESERIALIZE_FIELD(json, "has_spoiler", hasSpoiler, false, true); + } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/InputMessageContent.hpp b/include/tgbotxx/objects/InputMessageContent.hpp index b22a9102e..db081975b 100644 --- a/include/tgbotxx/objects/InputMessageContent.hpp +++ b/include/tgbotxx/objects/InputMessageContent.hpp @@ -13,343 +13,333 @@ namespace tgbotxx { /// - InputInvoiceMessageContent /// @ref https://core.telegram.org/bots/api#inputmessagecontent struct InputMessageContent { - InputMessageContent() = default; - explicit InputMessageContent(const nl::json& json) { - _fromJson(json); - } - virtual ~InputMessageContent() = default; - - /// @brief Type of this content - std::string type; - - - virtual nl::json toJson() const { - nl::json json = nl::json::object(); - OBJECT_SERIALIZE_FIELD(json, "type", type); - return json; - } - - virtual void fromJson(const nl::json& json) { - OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); - } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } + InputMessageContent() = default; + explicit InputMessageContent(const nl::json& json) { + InputMessageContent::fromJson(json); + } + virtual ~InputMessageContent() = default; + + /// @brief Type of this content + std::string type; + + + [[nodiscard]] virtual nl::json toJson() const { + nl::json json = nl::json::object(); + OBJECT_SERIALIZE_FIELD(json, "type", type); + return json; + } + + virtual void fromJson(const nl::json& json) { + OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); + } }; /// @brief Represents the content of a text message to be sent as the result of an inline query. /// @ref https://core.telegram.org/bots/api#inputtextmessagecontent struct InputTextMessageContent : InputMessageContent { - InputTextMessageContent() { - InputMessageContent::type = "text"; - } - explicit InputTextMessageContent(const nl::json& json) : InputMessageContent(json) { - InputMessageContent::type = "text"; - } - - /// @brief Text of the message to be sent, 1-4096 characters - std::string messageText; - - /// @brief Optional. Mode for parsing entities in the message text. See formatting options for more details. - std::string parseMode; - - /// @brief Optional. List of special entities that appear in message text, which can be specified instead of parse_mode - std::vector> entities; - - /// @brief Optional. Disables link previews for links in the sent message - bool disableWebPagePreview{}; - - nl::json toJson() const override { - nl::json json = InputMessageContent::toJson(); - OBJECT_SERIALIZE_FIELD(json, "message_text", messageText); - OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "entities", entities); - OBJECT_SERIALIZE_FIELD(json, "disable_web_page_preview", disableWebPagePreview); - return json; - } - - void fromJson(const nl::json& json) override { - InputMessageContent::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "message_text", messageText, "", false); - OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "entities", entities, true); - OBJECT_DESERIALIZE_FIELD(json, "disable_web_page_preview", disableWebPagePreview, false, true); - } + InputTextMessageContent() = default; + explicit InputTextMessageContent(const nl::json& json) { + InputTextMessageContent::fromJson(json); + // InputTextMessageContent::type = "text"; + } + + /// @brief Text of the message to be sent, 1-4096 characters + std::string messageText; + + /// @brief Optional. Mode for parsing entities in the message text. See formatting options for more details. + std::string parseMode; + + /// @brief Optional. List of special entities that appear in message text, which can be specified instead of parse_mode + std::vector> entities; + + /// @brief Optional. Disables link previews for links in the sent message + bool disableWebPagePreview{}; + + nl::json toJson() const override { + nl::json json = InputMessageContent::toJson(); + OBJECT_SERIALIZE_FIELD(json, "message_text", messageText); + OBJECT_SERIALIZE_FIELD(json, "parse_mode", parseMode); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "entities", entities); + OBJECT_SERIALIZE_FIELD(json, "disable_web_page_preview", disableWebPagePreview); + return json; + } + + void fromJson(const nl::json& json) override { + InputMessageContent::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "message_text", messageText, "", false); + OBJECT_DESERIALIZE_FIELD(json, "parse_mode", parseMode, "", true); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "entities", entities, true); + OBJECT_DESERIALIZE_FIELD(json, "disable_web_page_preview", disableWebPagePreview, false, true); + } }; /// @brief Represents the content of a location message to be sent as the result of an inline query. /// @ref https://core.telegram.org/bots/api#inputlocationmessagecontent struct InputLocationMessageContent : InputMessageContent { - InputLocationMessageContent() { - InputMessageContent::type = "location"; - } - explicit InputLocationMessageContent(const nl::json& json) : InputMessageContent(json) { - InputMessageContent::type = "location"; - } - - /// @brief Latitude of the location in degrees - float latitude{}; - - /// @brief Longitude of the location in degrees - float longitude{}; - - /// @brief Optional. The radius of uncertainty for the location, measured in meters; 0-1500 - float horizontalAccuracy{}; - - /// @brief Optional. For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. - std::int32_t heading{}; - - /// @brief Optional. For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. - std::int32_t proximityAlertRadius{}; - - - nl::json toJson() const override { - nl::json json = InputMessageContent::toJson(); - OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); - OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); - OBJECT_SERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy); - OBJECT_SERIALIZE_FIELD(json, "heading", heading); - OBJECT_SERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius); - return json; - } - - void fromJson(const nl::json& json) override { - InputMessageContent::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy, 0.0f, true); - OBJECT_DESERIALIZE_FIELD(json, "heading", heading, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius, 0, true); - } + InputLocationMessageContent() = default; + explicit InputLocationMessageContent(const nl::json& json) { + InputLocationMessageContent::fromJson(json); + // InputLocationMessageContent::type = "location"; + } + + /// @brief Latitude of the location in degrees + float latitude{}; + + /// @brief Longitude of the location in degrees + float longitude{}; + + /// @brief Optional. The radius of uncertainty for the location, measured in meters; 0-1500 + float horizontalAccuracy{}; + + /// @brief Optional. For live locations, a direction in which the user is moving, in degrees. Must be between 1 and 360 if specified. + std::int32_t heading{}; + + /// @brief Optional. For live locations, a maximum distance for proximity alerts about approaching another chat member, in meters. Must be between 1 and 100000 if specified. + std::int32_t proximityAlertRadius{}; + + + nl::json toJson() const override { + nl::json json = InputMessageContent::toJson(); + OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); + OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); + OBJECT_SERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy); + OBJECT_SERIALIZE_FIELD(json, "heading", heading); + OBJECT_SERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius); + return json; + } + + void fromJson(const nl::json& json) override { + InputMessageContent::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "horizontal_accuracy", horizontalAccuracy, 0.0f, true); + OBJECT_DESERIALIZE_FIELD(json, "heading", heading, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "proximity_alert_radius", proximityAlertRadius, 0, true); + } }; /// @brief Represents the content of a venue message to be sent as the result of an inline query. /// @ref https://core.telegram.org/bots/api#inputvenuemessagecontent struct InputVenueMessageContent : InputMessageContent { - InputVenueMessageContent() { - InputMessageContent::type = "venue"; - } - explicit InputVenueMessageContent(const nl::json& json) : InputMessageContent(json) { - InputMessageContent::type = "venue"; - } - - /// @brief Latitude of the venue in degrees - float latitude{}; - - /// @brief Longitude of the venue in degrees - float longitude{}; - - /// @brief Name of the venue - std::string title; - - /// @brief Address of the venue - std::string address; - - /// @brief Optional. Foursquare identifier of the venue, if known - std::string foursquareId; - - /// @brief Optional. Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.) - std::string foursquareType; - - /// @brief Optional. Google Places identifier of the venue - std::string googlePlaceId; - - /// @brief Optional. Google Places type of the venue. (See supported types.) https://developers.google.com/places/web-service/supported_types - std::string googlePlaceType; - - nl::json toJson() const override { - nl::json json = InputMessageContent::toJson(); - OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); - OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "address", address); - OBJECT_SERIALIZE_FIELD(json, "foursquare_id", foursquareId); - OBJECT_SERIALIZE_FIELD(json, "foursquare_type", foursquareType); - OBJECT_SERIALIZE_FIELD(json, "google_place_id", googlePlaceId); - OBJECT_SERIALIZE_FIELD(json, "google_place_type", googlePlaceType); - return json; - } - - void fromJson(const nl::json& json) override { - InputMessageContent::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "address", address, "", false); - OBJECT_DESERIALIZE_FIELD(json, "foursquare_id", foursquareId, "", true); - OBJECT_DESERIALIZE_FIELD(json, "foursquare_type", foursquareType, "", true); - OBJECT_DESERIALIZE_FIELD(json, "google_place_id", googlePlaceId, "", true); - OBJECT_DESERIALIZE_FIELD(json, "google_place_type", googlePlaceType, "", true); - } + InputVenueMessageContent() = default; + explicit InputVenueMessageContent(const nl::json& json) { + InputVenueMessageContent::fromJson(json); + // InputVenueMessageContent::type = "venue"; + } + + /// @brief Latitude of the venue in degrees + float latitude{}; + + /// @brief Longitude of the venue in degrees + float longitude{}; + + /// @brief Name of the venue + std::string title; + + /// @brief Address of the venue + std::string address; + + /// @brief Optional. Foursquare identifier of the venue, if known + std::string foursquareId; + + /// @brief Optional. Foursquare type of the venue, if known. (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.) + std::string foursquareType; + + /// @brief Optional. Google Places identifier of the venue + std::string googlePlaceId; + + /// @brief Optional. Google Places type of the venue. (See supported types.) https://developers.google.com/places/web-service/supported_types + std::string googlePlaceType; + + nl::json toJson() const override { + nl::json json = InputMessageContent::toJson(); + OBJECT_SERIALIZE_FIELD(json, "latitude", latitude); + OBJECT_SERIALIZE_FIELD(json, "longitude", longitude); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "address", address); + OBJECT_SERIALIZE_FIELD(json, "foursquare_id", foursquareId); + OBJECT_SERIALIZE_FIELD(json, "foursquare_type", foursquareType); + OBJECT_SERIALIZE_FIELD(json, "google_place_id", googlePlaceId); + OBJECT_SERIALIZE_FIELD(json, "google_place_type", googlePlaceType); + return json; + } + + void fromJson(const nl::json& json) override { + InputMessageContent::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "latitude", latitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "longitude", longitude, 0.0f, false); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "address", address, "", false); + OBJECT_DESERIALIZE_FIELD(json, "foursquare_id", foursquareId, "", true); + OBJECT_DESERIALIZE_FIELD(json, "foursquare_type", foursquareType, "", true); + OBJECT_DESERIALIZE_FIELD(json, "google_place_id", googlePlaceId, "", true); + OBJECT_DESERIALIZE_FIELD(json, "google_place_type", googlePlaceType, "", true); + } }; /// @brief Represents the content of a contact message to be sent as the result of an inline query. /// @ref https://core.telegram.org/bots/api#inputcontactmessagecontent struct InputContactMessageContent : InputMessageContent { - InputContactMessageContent() { - InputMessageContent::type = "contact"; - } - explicit InputContactMessageContent(const nl::json& json) : InputMessageContent(json) { - InputMessageContent::type = "contact"; - } - - /// @brief Contact's phone number - std::string phoneNumber; - - /// @brief Contact's first name - std::string firstName; - - /// @brief Optional. Contact's last name - std::string lastName; - - /// @brief Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes - /// @link vCard https://en.wikipedia.org/wiki/VCard @endlink - std::string vcard; - - nl::json toJson() const override { - nl::json json = InputMessageContent::toJson(); - OBJECT_SERIALIZE_FIELD(json, "phone_number", phoneNumber); - OBJECT_SERIALIZE_FIELD(json, "first_name", firstName); - OBJECT_SERIALIZE_FIELD(json, "last_name", lastName); - OBJECT_SERIALIZE_FIELD(json, "vcard", vcard); - return json; - } - - void fromJson(const nl::json& json) override { - InputMessageContent::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "phone_number", phoneNumber, "", false); - OBJECT_DESERIALIZE_FIELD(json, "first_name", firstName, "", false); - OBJECT_DESERIALIZE_FIELD(json, "last_name", lastName, "", true); - OBJECT_DESERIALIZE_FIELD(json, "vcard", vcard, "", true); - } + InputContactMessageContent() = default; + explicit InputContactMessageContent(const nl::json& json) { + InputContactMessageContent::fromJson(json); + // InputContactMessageContent::type = "contact"; + } + + /// @brief Contact's phone number + std::string phoneNumber; + + /// @brief Contact's first name + std::string firstName; + + /// @brief Optional. Contact's last name + std::string lastName; + + /// @brief Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes + /// @link vcard https://en.wikipedia.org/wiki/VCard @endlink + std::string vcard; + + nl::json toJson() const override { + nl::json json = InputMessageContent::toJson(); + OBJECT_SERIALIZE_FIELD(json, "phone_number", phoneNumber); + OBJECT_SERIALIZE_FIELD(json, "first_name", firstName); + OBJECT_SERIALIZE_FIELD(json, "last_name", lastName); + OBJECT_SERIALIZE_FIELD(json, "vcard", vcard); + return json; + } + + void fromJson(const nl::json& json) override { + InputMessageContent::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "phone_number", phoneNumber, "", false); + OBJECT_DESERIALIZE_FIELD(json, "first_name", firstName, "", false); + OBJECT_DESERIALIZE_FIELD(json, "last_name", lastName, "", true); + OBJECT_DESERIALIZE_FIELD(json, "vcard", vcard, "", true); + } }; /// @brief Represents the content of a invoice message to be sent as the result of an inline query. /// @ref https://core.telegram.org/bots/api#inputinvoicemessagecontent struct InputInvoiceMessageContent : InputMessageContent { - InputInvoiceMessageContent() { - InputMessageContent::type = "invoice"; - } - explicit InputInvoiceMessageContent(const nl::json& json) : InputMessageContent(json) { - InputMessageContent::type = "invoice"; - } - - /// @brief Product name, 1-32 characters - std::string title; - - /// @brief Product description, 1-255 characters - std::string description; - - /// @brief Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. - std::string payload; - - /// @brief Payment provider token, obtained via \@BotFather - std::string providerToken; - - /// @brief Three-letter ISO 4217 currency code, see more on [currencies](https://core.telegram.org/bots/payments#supported-currencies) - std::string currency; - - /// @brief Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) - std::vector> prices; - - /// @brief Optional. The maximum accepted amount for tips in the smallest units of the currency (integer, not float/double). - /// For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in [currencies.json](https://core.telegram.org/bots/payments/currencies.json), - /// it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). - /// Defaults to 0 - std::int32_t maxTipAmount{}; - - /// @brief Optional. A JSON-serialized array of suggested amounts of tip in the smallest units of the currency (integer, not float/double). - /// At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed maxTipAmount. - std::vector suggestedTipAmounts; - - /// @brief Optional. A JSON-serialized object for data about the invoice, which will be shared with the payment provider. - /// A detailed description of the required fields should be provided by the payment provider. - std::string providerData; - - /// @brief Optional. URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. - std::string photoUrl; - - /// @brief Optional. Photo size in bytes - std::int64_t photoSize{}; - - /// @brief Optional. Photo width - std::int32_t photoWidth{}; - - /// @brief Optional. Photo height - std::int32_t photoHeight{}; - - /// @brief Optional. Pass True if you require the user's full name to complete the order - bool needName{}; - - /// @brief Optional. Pass True if you require the user's phone number to complete the order - bool needPhoneNumber{}; - - /// @brief Optional. Pass True if you require the user's email address to complete the order - bool needEmail{}; - - /// @brief Optional. Pass True if you require the user's shipping address to complete the order - bool needShippingAddress{}; - - /// @brief Optional. Pass True if the user's phone number should be sent to provider - bool sendPhoneNumberToProvider{}; - - /// @brief Optional. Pass True if the user's email address should be sent to provider - bool sendEmailToProvider{}; - - /// @brief Optional. Pass True if the final price depends on the shipping method - bool isFlexible{}; - - nl::json toJson() const override { - nl::json json = InputMessageContent::toJson(); - OBJECT_SERIALIZE_FIELD(json, "title", title); - OBJECT_SERIALIZE_FIELD(json, "description", description); - OBJECT_SERIALIZE_FIELD(json, "payload", payload); - OBJECT_SERIALIZE_FIELD(json, "provider_token", providerToken); - OBJECT_SERIALIZE_FIELD(json, "currency", currency); - OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "prices", prices); - OBJECT_SERIALIZE_FIELD(json, "max_tip_amount", maxTipAmount); - OBJECT_SERIALIZE_FIELD(json, "suggested_tip_amounts", suggestedTipAmounts); - OBJECT_SERIALIZE_FIELD(json, "provider_data", providerData); - OBJECT_SERIALIZE_FIELD(json, "photo_url", photoUrl); - OBJECT_SERIALIZE_FIELD(json, "photo_size", photoSize); - OBJECT_SERIALIZE_FIELD(json, "photo_width", photoWidth); - OBJECT_SERIALIZE_FIELD(json, "photo_height", photoHeight); - OBJECT_SERIALIZE_FIELD(json, "need_name", needName); - OBJECT_SERIALIZE_FIELD(json, "need_phone_number", needPhoneNumber); - OBJECT_SERIALIZE_FIELD(json, "need_email", needEmail); - OBJECT_SERIALIZE_FIELD(json, "need_shipping_address", needShippingAddress); - OBJECT_SERIALIZE_FIELD(json, "send_phone_number_to_provider", sendPhoneNumberToProvider); - OBJECT_SERIALIZE_FIELD(json, "send_email_to_provider", sendEmailToProvider); - OBJECT_SERIALIZE_FIELD(json, "is_flexible", isFlexible); - return json; - } - - void fromJson(const nl::json& json) override { - InputMessageContent::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); - OBJECT_DESERIALIZE_FIELD(json, "description", description, "", false); - OBJECT_DESERIALIZE_FIELD(json, "payload", payload, "", false); - OBJECT_DESERIALIZE_FIELD(json, "provider_token", providerToken, "", false); - OBJECT_DESERIALIZE_FIELD(json, "currency", currency, "", false); - OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "prices", prices, false); - OBJECT_DESERIALIZE_FIELD(json, "max_tip_amount", maxTipAmount, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "suggested_tip_amounts", suggestedTipAmounts, std::vector(), true); - OBJECT_DESERIALIZE_FIELD(json, "provider_data", providerData, "", true); - OBJECT_DESERIALIZE_FIELD(json, "photo_url", photoUrl, "", true); - OBJECT_DESERIALIZE_FIELD(json, "photo_size", photoSize, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "photo_width", photoWidth, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "photo_height", photoHeight, 0, true); - OBJECT_DESERIALIZE_FIELD(json, "need_name", needName, false, true); - OBJECT_DESERIALIZE_FIELD(json, "need_phone_number", needPhoneNumber, false, true); - OBJECT_DESERIALIZE_FIELD(json, "need_email", needEmail, false, true); - OBJECT_DESERIALIZE_FIELD(json, "need_shipping_address", needShippingAddress, false, true); - OBJECT_DESERIALIZE_FIELD(json, "send_phone_number_to_provider", sendPhoneNumberToProvider, false, true); - OBJECT_DESERIALIZE_FIELD(json, "send_email_to_provider", sendEmailToProvider, false, true); - OBJECT_DESERIALIZE_FIELD(json, "is_flexible", isFlexible, false, true); - } + InputInvoiceMessageContent() = default; + explicit InputInvoiceMessageContent(const nl::json& json) { + InputInvoiceMessageContent::fromJson(json); + // InputInvoiceMessageContent::type = "invoice"; + } + + /// @brief Product name, 1-32 characters + std::string title; + + /// @brief Product description, 1-255 characters + std::string description; + + /// @brief Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for your internal processes. + std::string payload; + + /// @brief Payment provider token, obtained via \@BotFather + std::string providerToken; + + /// @brief Three-letter ISO 4217 currency code, see more on [currencies](https://core.telegram.org/bots/payments#supported-currencies) + std::string currency; + + /// @brief Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + std::vector> prices; + + /// @brief Optional. The maximum accepted amount for tips in the smallest units of the currency (integer, not float/double). + /// For example, for a maximum tip of US$ 1.45 pass max_tip_amount = 145. See the exp parameter in [currencies.json](https://core.telegram.org/bots/payments/currencies.json), + /// it shows the number of digits past the decimal point for each currency (2 for the majority of currencies). + /// Defaults to 0 + std::int32_t maxTipAmount{}; + + /// @brief Optional. A JSON-serialized array of suggested amounts of tip in the smallest units of the currency (integer, not float/double). + /// At most 4 suggested tip amounts can be specified. The suggested tip amounts must be positive, passed in a strictly increased order and must not exceed maxTipAmount. + std::vector suggestedTipAmounts; + + /// @brief Optional. A JSON-serialized object for data about the invoice, which will be shared with the payment provider. + /// A detailed description of the required fields should be provided by the payment provider. + std::string providerData; + + /// @brief Optional. URL of the product photo for the invoice. Can be a photo of the goods or a marketing image for a service. + std::string photoUrl; + + /// @brief Optional. Photo size in bytes + std::int64_t photoSize{}; + + /// @brief Optional. Photo width + std::int32_t photoWidth{}; + + /// @brief Optional. Photo height + std::int32_t photoHeight{}; + + /// @brief Optional. Pass True if you require the user's full name to complete the order + bool needName{}; + + /// @brief Optional. Pass True if you require the user's phone number to complete the order + bool needPhoneNumber{}; + + /// @brief Optional. Pass True if you require the user's email address to complete the order + bool needEmail{}; + + /// @brief Optional. Pass True if you require the user's shipping address to complete the order + bool needShippingAddress{}; + + /// @brief Optional. Pass True if the user's phone number should be sent to provider + bool sendPhoneNumberToProvider{}; + + /// @brief Optional. Pass True if the user's email address should be sent to provider + bool sendEmailToProvider{}; + + /// @brief Optional. Pass True if the final price depends on the shipping method + bool isFlexible{}; + + nl::json toJson() const override { + nl::json json = InputMessageContent::toJson(); + OBJECT_SERIALIZE_FIELD(json, "title", title); + OBJECT_SERIALIZE_FIELD(json, "description", description); + OBJECT_SERIALIZE_FIELD(json, "payload", payload); + OBJECT_SERIALIZE_FIELD(json, "provider_token", providerToken); + OBJECT_SERIALIZE_FIELD(json, "currency", currency); + OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "prices", prices); + OBJECT_SERIALIZE_FIELD(json, "max_tip_amount", maxTipAmount); + OBJECT_SERIALIZE_FIELD(json, "suggested_tip_amounts", suggestedTipAmounts); + OBJECT_SERIALIZE_FIELD(json, "provider_data", providerData); + OBJECT_SERIALIZE_FIELD(json, "photo_url", photoUrl); + OBJECT_SERIALIZE_FIELD(json, "photo_size", photoSize); + OBJECT_SERIALIZE_FIELD(json, "photo_width", photoWidth); + OBJECT_SERIALIZE_FIELD(json, "photo_height", photoHeight); + OBJECT_SERIALIZE_FIELD(json, "need_name", needName); + OBJECT_SERIALIZE_FIELD(json, "need_phone_number", needPhoneNumber); + OBJECT_SERIALIZE_FIELD(json, "need_email", needEmail); + OBJECT_SERIALIZE_FIELD(json, "need_shipping_address", needShippingAddress); + OBJECT_SERIALIZE_FIELD(json, "send_phone_number_to_provider", sendPhoneNumberToProvider); + OBJECT_SERIALIZE_FIELD(json, "send_email_to_provider", sendEmailToProvider); + OBJECT_SERIALIZE_FIELD(json, "is_flexible", isFlexible); + return json; + } + + void fromJson(const nl::json& json) override { + InputMessageContent::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "title", title, "", false); + OBJECT_DESERIALIZE_FIELD(json, "description", description, "", false); + OBJECT_DESERIALIZE_FIELD(json, "payload", payload, "", false); + OBJECT_DESERIALIZE_FIELD(json, "provider_token", providerToken, "", false); + OBJECT_DESERIALIZE_FIELD(json, "currency", currency, "", false); + OBJECT_DESERIALIZE_FIELD_PTR_ARRAY(json, "prices", prices, false); + OBJECT_DESERIALIZE_FIELD(json, "max_tip_amount", maxTipAmount, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "suggested_tip_amounts", suggestedTipAmounts, std::vector(), true); + OBJECT_DESERIALIZE_FIELD(json, "provider_data", providerData, "", true); + OBJECT_DESERIALIZE_FIELD(json, "photo_url", photoUrl, "", true); + OBJECT_DESERIALIZE_FIELD(json, "photo_size", photoSize, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "photo_width", photoWidth, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "photo_height", photoHeight, 0, true); + OBJECT_DESERIALIZE_FIELD(json, "need_name", needName, false, true); + OBJECT_DESERIALIZE_FIELD(json, "need_phone_number", needPhoneNumber, false, true); + OBJECT_DESERIALIZE_FIELD(json, "need_email", needEmail, false, true); + OBJECT_DESERIALIZE_FIELD(json, "need_shipping_address", needShippingAddress, false, true); + OBJECT_DESERIALIZE_FIELD(json, "send_phone_number_to_provider", sendPhoneNumberToProvider, false, true); + OBJECT_DESERIALIZE_FIELD(json, "send_email_to_provider", sendEmailToProvider, false, true); + OBJECT_DESERIALIZE_FIELD(json, "is_flexible", isFlexible, false, true); + } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/InputPaidMedia.hpp b/include/tgbotxx/objects/InputPaidMedia.hpp index d2c46cfce..46ab4d7d7 100644 --- a/include/tgbotxx/objects/InputPaidMedia.hpp +++ b/include/tgbotxx/objects/InputPaidMedia.hpp @@ -10,7 +10,7 @@ namespace tgbotxx { struct InputPaidMedia { InputPaidMedia() = default; explicit InputPaidMedia(const nl::json& json) { - _fromJson(json); + InputPaidMedia::fromJson(json); } virtual ~InputPaidMedia() = default; @@ -26,7 +26,7 @@ namespace tgbotxx { std::variant media{""}; /// @brief Serializes this object to JSON - virtual nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "type", type); // media variant @@ -43,25 +43,17 @@ namespace tgbotxx { // media variant, we can't get a local file from remote, so it's always a URL or file id std::string. OBJECT_DESERIALIZE_FIELD(json, "media", std::get(media), "", false); } - - private: - /// @brief Avoid calling virtual fromJson in constructor - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The paid media to send is a photo struct InputPaidMediaPhoto : InputPaidMedia { - InputPaidMediaPhoto() { - InputPaidMedia::type = "photo"; + InputPaidMediaPhoto() = default; + explicit InputPaidMediaPhoto(const nl::json& json) { + InputPaidMediaPhoto::fromJson(json); + // InputPaidMedia::type = "photo"; } - explicit InputPaidMediaPhoto(const nl::json& json) : InputPaidMedia(json) { - InputPaidMedia::type = "photo"; - } - - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = InputPaidMedia::toJson(); return json; } @@ -73,11 +65,10 @@ namespace tgbotxx { /// @brief The paid media to send is a video struct InputPaidMediaVideo : InputPaidMedia { - InputPaidMediaVideo() { - InputPaidMedia::type = "video"; - } - explicit InputPaidMediaVideo(const nl::json& json) : InputPaidMedia(json) { - InputPaidMedia::type = "video"; + InputPaidMediaVideo() = default; + explicit InputPaidMediaVideo(const nl::json& json) { + InputPaidMediaVideo::fromJson(json); + // InputPaidMedia::type = "video"; } /// @brief Optional. Thumbnail of the file sent @@ -101,7 +92,7 @@ namespace tgbotxx { /// @brief Optional. True if the uploaded video is suitable for streaming bool supportsStreaming{}; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = InputPaidMedia::toJson(); OBJECT_SERIALIZE_FIELD(json, "thumbnail", thumbnail); OBJECT_SERIALIZE_FIELD(json, "cover", cover); diff --git a/include/tgbotxx/objects/InputProfilePhoto.hpp b/include/tgbotxx/objects/InputProfilePhoto.hpp index c2791ff92..9e3c3d874 100644 --- a/include/tgbotxx/objects/InputProfilePhoto.hpp +++ b/include/tgbotxx/objects/InputProfilePhoto.hpp @@ -8,7 +8,7 @@ namespace tgbotxx { struct InputProfilePhoto { InputProfilePhoto() = default; explicit InputProfilePhoto(const nl::json& json) { - _fromJson(json); + InputProfilePhoto::fromJson(json); } virtual ~InputProfilePhoto() = default; @@ -24,21 +24,15 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief A static profile photo in the .JPG format /// @ref https://core.telegram.org/bots/api#inputprofilephotostatic struct InputProfilePhotoStatic : InputProfilePhoto { - InputProfilePhotoStatic() { - InputProfilePhoto::type = "static"; - } - explicit InputProfilePhotoStatic(const nl::json& json) : InputProfilePhoto(json) { - InputProfilePhoto::type = "static"; + InputProfilePhotoStatic() = default; + explicit InputProfilePhotoStatic(const nl::json& json) { + InputProfilePhotoStatic::fromJson(json); + // InputProfilePhotoStatic::type = "static"; } /// @brief The static profile photo. Profile photos can't be reused and can only be uploaded as a new file, @@ -63,13 +57,13 @@ namespace tgbotxx { /// @brief An animated profile photo in the MPEG4 format /// @ref https://core.telegram.org/bots/api#inputprofilephotoanimated struct InputProfilePhotoAnimated : InputProfilePhoto { - InputProfilePhotoAnimated() { - InputProfilePhoto::type = "animated"; - } - explicit InputProfilePhotoAnimated(const nl::json& json) : InputProfilePhoto(json) { - InputProfilePhoto::type = "animated"; + InputProfilePhotoAnimated() = default; + explicit InputProfilePhotoAnimated(const nl::json& json) { + InputProfilePhotoAnimated::fromJson(json); + // InputProfilePhotoAnimated::type = "animated"; } + /// @brief The animated profile photo. Profile photos can't be reused and can only be uploaded as a new file, /// so you can pass “attach://” if the photo was uploaded using multipart/form-data std::string animation; diff --git a/include/tgbotxx/objects/InputStoryContent.hpp b/include/tgbotxx/objects/InputStoryContent.hpp index e372a8174..dba2fb391 100644 --- a/include/tgbotxx/objects/InputStoryContent.hpp +++ b/include/tgbotxx/objects/InputStoryContent.hpp @@ -8,14 +8,14 @@ namespace tgbotxx { struct InputStoryContent { InputStoryContent() = default; explicit InputStoryContent(const nl::json& json) { - _fromJson(json); + InputStoryContent::fromJson(json); } virtual ~InputStoryContent() = default; /// @brief Type of the story content std::string type; - virtual nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "type", type); return json; @@ -24,21 +24,15 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief Describes a photo to post as a story /// @ref https://core.telegram.org/bots/api#inputstorycontentphoto struct InputStoryContentPhoto : InputStoryContent { - InputStoryContentPhoto() { - InputStoryContent::type = "photo"; - } - explicit InputStoryContentPhoto(const nl::json& json) : InputStoryContent(json) { - InputStoryContent::type = "photo"; + InputStoryContentPhoto() = default; + explicit InputStoryContentPhoto(const nl::json& json) { + InputStoryContentPhoto::fromJson(json); + // InputStoryContentPhoto::type = "photo"; } /// @brief The photo to post as a story. Must be 1080x1920 and <= 10MB. Can't be reused. @@ -46,7 +40,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = InputStoryContent::toJson(); OBJECT_SERIALIZE_FIELD(json, "photo", photo); return json; @@ -62,11 +56,10 @@ namespace tgbotxx { /// @brief Describes a video to post as a story /// @ref https://core.telegram.org/bots/api#inputstorycontentvideo struct InputStoryContentVideo : InputStoryContent { - InputStoryContentVideo() { - InputStoryContent::type = "video"; - } - explicit InputStoryContentVideo(const nl::json& json) : InputStoryContent(json) { - InputStoryContent::type = "video"; + InputStoryContentVideo() = default; + explicit InputStoryContentVideo(const nl::json& json) { + InputStoryContentVideo::fromJson(json); + // InputStoryContentVideo::type = "video"; } /// @brief The video to post as a story. Must be 720x1280, H.265, streamable, <= 30MB. Can't be reused. @@ -83,7 +76,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = InputStoryContent::toJson(); OBJECT_SERIALIZE_FIELD(json, "video", video); OBJECT_SERIALIZE_FIELD(json, "duration", duration); diff --git a/include/tgbotxx/objects/MaybeInaccessibleMessage.hpp b/include/tgbotxx/objects/MaybeInaccessibleMessage.hpp index 60a1ad375..25a067f4b 100644 --- a/include/tgbotxx/objects/MaybeInaccessibleMessage.hpp +++ b/include/tgbotxx/objects/MaybeInaccessibleMessage.hpp @@ -8,8 +8,9 @@ namespace tgbotxx { /// @brief This object describes a message that can be inaccessible to the bot. It can be one of /// - Message /// - InaccessibleMessage - /// @tip: To find which type is held, use std::holds_alternative(variant) with std::get(variant), - /// or test variant.index() against 0 - 1 where 0 = Ptr and 1 = Ptr + /// @tip: To find which type is held, use: + /// - std::holds_alternative(variant) with std::get(variant) + /// - or test variant.index() against 0 or 1, where 0 = Ptr and 1 = Ptr /// @ref https://core.telegram.org/bots/api#maybeinaccessiblemessage using MaybeInaccessibleMessage = std::variant, Ptr>; } \ No newline at end of file diff --git a/include/tgbotxx/objects/MenuButton.hpp b/include/tgbotxx/objects/MenuButton.hpp index ff2a47515..98c4b9679 100644 --- a/include/tgbotxx/objects/MenuButton.hpp +++ b/include/tgbotxx/objects/MenuButton.hpp @@ -12,85 +12,93 @@ namespace tgbotxx { /// By default, the menu button opens the list of bot commands. /// @ref https://core.telegram.org/bots/api#menubuttondefault struct MenuButton { - MenuButton() = default; - explicit MenuButton(const nl::json& json) { - _fromJson(json); - } - virtual ~MenuButton() = default; + MenuButton() = default; + explicit MenuButton(const nl::json& json) { + MenuButton::fromJson(json); + } + virtual ~MenuButton() = default; - /// @brief Type of the button - std::string type; + /// @brief Type of the button + std::string type; - /// @brief Serializes this object to JSON - /// @returns JSON representation of this object - virtual nl::json toJson() const { - nl::json json = nl::json::object(); - OBJECT_SERIALIZE_FIELD(json, "type", type); - return json; - } + /// @brief Serializes this object to JSON + /// @returns JSON representation of this object + virtual nl::json toJson() const { + nl::json json = nl::json::object(); + OBJECT_SERIALIZE_FIELD(json, "type", type); + return json; + } - /// @brief Deserializes this object from JSON - virtual void fromJson(const nl::json& json) { - OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); - } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } + /// @brief Deserializes this object from JSON + virtual void fromJson(const nl::json& json) { + OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); + } }; /// @brief Describes that no specific value for the menu button was set. /// @ref https://core.telegram.org/bots/api#menubuttondefault struct MenuButtonDefault : MenuButton { - MenuButtonDefault() { - MenuButton::type = "default"; - } - explicit MenuButtonDefault(const nl::json& json) : MenuButton(json) { - MenuButton::type = "default"; - } + MenuButtonDefault() = default; + explicit MenuButtonDefault(const nl::json& json) { + MenuButtonDefault::fromJson(json); + // MenuButtonDefault::type = "default"; + } + nl::json toJson() const override { + nl::json json = MenuButton::toJson(); + return json; + } + + void fromJson(const nl::json& json) override { + MenuButton::fromJson(json); + } }; /// @brief Represents a menu button, which launches a Web App. https://core.telegram.org/bots/webapps /// @ref https://core.telegram.org/bots/api#menubuttonwebapp struct MenuButtonWebApp : MenuButton { - MenuButtonWebApp() { - MenuButton::type = "web_app"; - } - explicit MenuButtonWebApp(const nl::json& json) : MenuButton(json) { - MenuButton::type = "web_app"; - } + MenuButtonWebApp() = default; + explicit MenuButtonWebApp(const nl::json& json) { + MenuButtonWebApp::fromJson(json); + // MenuButtonWebApp::type = "web_app"; + } - /// @brief Text on the button - std::string text; + /// @brief Text on the button + std::string text; - /// @brief Description of the Web App that will be launched when the user presses the button. - /// The Web App will be able to send an arbitrary message on behalf of the user using the method answerWebAppQuery. https://core.telegram.org/bots/api#answerwebappquery - Ptr webApp; + /// @brief Description of the Web App that will be launched when the user presses the button. + /// The Web App will be able to send an arbitrary message on behalf of the user using the method answerWebAppQuery. https://core.telegram.org/bots/api#answerwebappquery + Ptr webApp; - nl::json toJson() const override { - nl::json json = MenuButton::toJson(); - OBJECT_SERIALIZE_FIELD(json, "text", text); - OBJECT_SERIALIZE_FIELD_PTR(json, "web_app", webApp); - return json; - } + nl::json toJson() const override { + nl::json json = MenuButton::toJson(); + OBJECT_SERIALIZE_FIELD(json, "text", text); + OBJECT_SERIALIZE_FIELD_PTR(json, "web_app", webApp); + return json; + } - void fromJson(const nl::json& json) override { - MenuButton::fromJson(json); - OBJECT_DESERIALIZE_FIELD(json, "text", text, "", false); - OBJECT_DESERIALIZE_FIELD_PTR(json, "web_app", webApp, false); - } + void fromJson(const nl::json& json) override { + MenuButton::fromJson(json); + OBJECT_DESERIALIZE_FIELD(json, "text", text, "", false); + OBJECT_DESERIALIZE_FIELD_PTR(json, "web_app", webApp, false); + } }; /// @brief Represents a menu button, which opens the bot's list of commands. /// @ref https://core.telegram.org/bots/api#menubuttoncommands struct MenuButtonCommands : MenuButton { - MenuButtonCommands() { - MenuButton::type = "commands"; - } - explicit MenuButtonCommands(const nl::json& json) : MenuButton(json) { - MenuButton::type = "commands"; - } + MenuButtonCommands() = default; + explicit MenuButtonCommands(const nl::json& json) { + MenuButtonCommands::fromJson(json); + // MenuButtonCommands::type = "commands"; + } + + nl::json toJson() const override { + nl::json json = MenuButton::toJson(); + return json; + } + void fromJson(const nl::json& json) override { + MenuButton::fromJson(json); + } }; } \ No newline at end of file diff --git a/include/tgbotxx/objects/Message.hpp b/include/tgbotxx/objects/Message.hpp index 218f384a5..de39ef4ab 100644 --- a/include/tgbotxx/objects/Message.hpp +++ b/include/tgbotxx/objects/Message.hpp @@ -284,7 +284,7 @@ namespace tgbotxx { std::int64_t migrateFromChatId{}; /// @brief Optional. Specified message was pinned. Note that the Message object in this field will not contain further reply_to_message fields even if it itself is a reply. - MaybeInaccessibleMessage pinnedMessage; + MaybeInaccessibleMessage pinnedMessage{}; /// @brief Optional. Message is an invoice for a payment, information about the invoice. /// @link More about payments » https://core.telegram.org/bots/api#payments @endlink diff --git a/include/tgbotxx/objects/MessageOrigin.hpp b/include/tgbotxx/objects/MessageOrigin.hpp index 1007d3f14..28deaf924 100644 --- a/include/tgbotxx/objects/MessageOrigin.hpp +++ b/include/tgbotxx/objects/MessageOrigin.hpp @@ -13,7 +13,7 @@ namespace tgbotxx { struct MessageOrigin { MessageOrigin() = default; explicit MessageOrigin(const nl::json& json) { - _fromJson(json); + MessageOrigin::fromJson(json); } virtual ~MessageOrigin() = default; @@ -37,21 +37,15 @@ namespace tgbotxx { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); OBJECT_DESERIALIZE_FIELD(json, "date", date, 0, false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The message was originally sent by a known user. /// @ref https://core.telegram.org/bots/api#messageoriginuser struct MessageOriginUser : MessageOrigin { - MessageOriginUser() { - MessageOrigin::type = "user"; - } - explicit MessageOriginUser(const nl::json& json) : MessageOrigin(json) { - MessageOrigin::type = "user"; + MessageOriginUser() = default; + explicit MessageOriginUser(const nl::json& json) { + MessageOriginUser::fromJson(json); + // MessageOrigin::type = "user"; } /// @brief User that sent the message originally @@ -73,11 +67,10 @@ namespace tgbotxx { /// @brief The message was originally sent by an unknown user. /// @ref https://core.telegram.org/bots/api#messageoriginhiddenuser struct MessageOriginHiddenUser : MessageOrigin { - MessageOriginHiddenUser() { - MessageOrigin::type = "hidden_user"; - } - explicit MessageOriginHiddenUser(const nl::json& json) : MessageOrigin(json) { - MessageOrigin::type = "hidden_user"; + MessageOriginHiddenUser() = default; + explicit MessageOriginHiddenUser(const nl::json& json) { + MessageOriginHiddenUser::fromJson(json); + // MessageOrigin::type = "hidden_user"; } /// @brief Name of the user that sent the message originally @@ -99,11 +92,10 @@ namespace tgbotxx { /// @brief The message was originally sent on behalf of a chat to a group chat. /// @ref https://core.telegram.org/bots/api#messageoriginchat struct MessageOriginChat : MessageOrigin { - MessageOriginChat() { - MessageOrigin::type = "chat"; - } - explicit MessageOriginChat(const nl::json& json) : MessageOrigin(json) { - MessageOrigin::type = "chat"; + MessageOriginChat() = default; + explicit MessageOriginChat(const nl::json& json) { + MessageOriginChat::fromJson(json); + // MessageOrigin::type = "chat"; } /// @brief Chat that sent the message originally @@ -129,11 +121,10 @@ namespace tgbotxx { /// @brief The message was originally sent to a channel chat. /// @ref https://core.telegram.org/bots/api#messageoriginchannel struct MessageOriginChannel : MessageOrigin { - MessageOriginChannel() { - MessageOrigin::type = "channel"; - } - explicit MessageOriginChannel(const nl::json& json) : MessageOrigin(json) { - MessageOrigin::type = "channel"; + MessageOriginChannel() = default; + explicit MessageOriginChannel(const nl::json& json) { + MessageOriginChannel::fromJson(json); + // MessageOrigin::type = "channel"; } /// @brief Channel chat to which the message was originally sent diff --git a/include/tgbotxx/objects/OwnedGift.hpp b/include/tgbotxx/objects/OwnedGift.hpp index b082cfa2f..5783f6567 100644 --- a/include/tgbotxx/objects/OwnedGift.hpp +++ b/include/tgbotxx/objects/OwnedGift.hpp @@ -12,7 +12,7 @@ namespace tgbotxx { struct OwnedGift { OwnedGift() = default; explicit OwnedGift(const nl::json& json) { - _fromJson(json); + OwnedGift::fromJson(json); } virtual ~OwnedGift() = default; @@ -31,19 +31,14 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief Describes a regular gift owned by a user or a chat struct OwnedGiftRegular : OwnedGift { - OwnedGiftRegular() { - OwnedGift::type = "regular"; - } - explicit OwnedGiftRegular(const nl::json& json) : OwnedGift(json) { - OwnedGift::type = "regular"; + OwnedGiftRegular() = default; + explicit OwnedGiftRegular(const nl::json& json) { + OwnedGiftRegular::fromJson(json); + // OwnedGift::type = "regular"; } ~OwnedGiftRegular() override = default; @@ -121,11 +116,9 @@ namespace tgbotxx { /// @brief Describes a unique gift owned by a user or a chat struct OwnedGiftUnique : OwnedGift { - OwnedGiftUnique() { - OwnedGift::type = "unique"; - } - explicit OwnedGiftUnique(const nl::json& json) : OwnedGift(json) { - OwnedGift::type = "unique"; + OwnedGiftUnique() = default; + explicit OwnedGiftUnique(const nl::json& json) { + OwnedGiftUnique::fromJson(json); } ~OwnedGiftUnique() override = default; @@ -154,7 +147,7 @@ namespace tgbotxx { std::int64_t nextTransferDate{}; /// @brief Serializes this object to JSON - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = OwnedGift::toJson(); OBJECT_SERIALIZE_FIELD_PTR(json, "gift", gift); OBJECT_SERIALIZE_FIELD(json, "owned_gift_id", ownedGiftId); diff --git a/include/tgbotxx/objects/PaidMedia.hpp b/include/tgbotxx/objects/PaidMedia.hpp index 72cd1aa5c..7b5d6347f 100644 --- a/include/tgbotxx/objects/PaidMedia.hpp +++ b/include/tgbotxx/objects/PaidMedia.hpp @@ -12,7 +12,7 @@ namespace tgbotxx { struct PaidMedia { PaidMedia() = default; explicit PaidMedia(const nl::json& json) { - _fromJson(json); + PaidMedia::fromJson(json); } virtual ~PaidMedia() = default; @@ -21,7 +21,7 @@ namespace tgbotxx { /// @brief Serializes this object to JSON /// @returns JSON representation of this object - virtual nl::json toJson() const { + [[nodiscard]] virtual nl::json toJson() const { nl::json json = nl::json::object(); OBJECT_SERIALIZE_FIELD(json, "type", type); return json; @@ -31,21 +31,15 @@ namespace tgbotxx { virtual void fromJson(const nl::json& json) { OBJECT_DESERIALIZE_FIELD(json, "type", type, "", false); } - - private: - void _fromJson(const nl::json& json) { - fromJson(json); - } }; /// @brief The paid media isn't available before the payment. /// @ref https://core.telegram.org/bots/api#paidmediapreview struct PaidMediaPreview : PaidMedia { - PaidMediaPreview() { - PaidMedia::type = "preview"; - } - explicit PaidMediaPreview(const nl::json& json) : PaidMedia(json) { - PaidMedia::type = "preview"; + PaidMediaPreview() = default; + explicit PaidMediaPreview(const nl::json& json) { + PaidMediaPreview::fromJson(json); + // PaidMedia::type = "preview"; } /// @brief Optional. Media width as defined by the sender @@ -57,7 +51,7 @@ namespace tgbotxx { /// @brief Optional. Duration of the media in seconds as defined by the sender std::int32_t duration{}; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = PaidMedia::toJson(); OBJECT_SERIALIZE_FIELD(json, "width", width); OBJECT_SERIALIZE_FIELD(json, "height", height); @@ -77,17 +71,16 @@ namespace tgbotxx { /// @brief The paid media is a photo. /// @ref https://core.telegram.org/bots/api#paidmediaphoto struct PaidMediaPhoto : PaidMedia { - PaidMediaPhoto() { - PaidMedia::type = "photo"; - } - explicit PaidMediaPhoto(const nl::json& json) : PaidMedia(json) { - PaidMedia::type = "photo"; + PaidMediaPhoto() = default; + explicit PaidMediaPhoto(const nl::json& json) { + PaidMediaPhoto::fromJson(json); + // PaidMedia::type = "photo"; } /// @brief The photo std::vector> photo; - nl::json toJson() const override { + [[nodiscard]] nl::json toJson() const override { nl::json json = PaidMedia::toJson(); OBJECT_SERIALIZE_FIELD_PTR_ARRAY(json, "photo", photo); return json; @@ -103,17 +96,16 @@ namespace tgbotxx { /// @brief The paid media is a video. /// @ref https://core.telegram.org/bots/api#paidmediavideo struct PaidMediaVideo : PaidMedia { - PaidMediaVideo() { - PaidMedia::type = "video"; - } - explicit PaidMediaVideo(const nl::json& json) : PaidMedia(json) { - PaidMedia::type = "video"; + PaidMediaVideo() = default; + explicit PaidMediaVideo(const nl::json& json) { + PaidMediaVideo::fromJson(json); + // PaidMedia::type = "video"; } /// @brief The video Ptr