diff --git a/.dockerignore b/.dockerignore index b7f79fab..469e9f39 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,6 +16,7 @@ !tests/** !CMakeLists.txt !conanfile.py +!coverage_summary.py !Makefile !poetry.lock !pyproject.toml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b31fd924..aeb5b58b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,10 @@ on: branches: - master +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87060829..9c37acfe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,10 @@ on: branches: - master +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -50,9 +54,26 @@ jobs: cache-from: type=gha,scope=test cache-to: type=gha,mode=max,scope=test - name: Test tgbot-cpp - run: make docker-test-only + run: | + docker run --name tgbot-cpp-coverage reo7sp/tgbot-cpp-test sh -eu -c ' + make test-only + make coverage + ' + docker cp tgbot-cpp-coverage:/usr/src/tgbot-cpp/build/Debug/coverage ./coverage + docker rm tgbot-cpp-coverage - name: Test codegen run: make docker-test-api-codegen + - name: Upload HTML coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-html + path: coverage + - name: Upload coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + file: coverage/coveralls.json + format: coveralls test-windows: runs-on: windows-2022 diff --git a/CMakeLists.txt b/CMakeLists.txt index caf82af3..ffe40418 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.16) project(TgBot LANGUAGES CXX) option(ENABLE_TESTS "Build tests" OFF) +option(ENABLE_SANITIZERS "Instrument the library and tests with address and undefined behavior sanitizers" OFF) +option(ENABLE_COVERAGE "Instrument the library and tests for code coverage" OFF) option(BUILD_SHARED_LIBS "Build shared libraries" OFF) option(BUILD_DOCUMENTATION "Build API documentation" OFF) @@ -39,6 +41,20 @@ target_compile_features(TgBot PUBLIC cxx_std_20) target_compile_options(TgBot PRIVATE $<$:-Wall> ) +if(ENABLE_SANITIZERS) + if(NOT CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") + message(FATAL_ERROR "ENABLE_SANITIZERS requires GCC or Clang") + endif() + target_compile_options(TgBot PUBLIC -fsanitize=address,undefined -fno-omit-frame-pointer) + target_link_options(TgBot PUBLIC -fsanitize=address,undefined) +endif() +if(ENABLE_COVERAGE) + if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + message(FATAL_ERROR "ENABLE_COVERAGE requires GCC") + endif() + target_compile_options(TgBot PRIVATE -O0 -g --coverage) + target_link_options(TgBot PUBLIC --coverage) +endif() target_include_directories(TgBot PUBLIC $ $ diff --git a/Dockerfile_test b/Dockerfile_test index 47e55f41..16aaf5a9 100644 --- a/Dockerfile_test +++ b/Dockerfile_test @@ -15,12 +15,11 @@ RUN apt-get -qq update && \ python3-venv >/dev/null && \ rm -rf /var/lib/apt/lists/* -ENV CONAN_BUILD_ARGS="--build=missing:gtest/*" - RUN python3 -m venv /opt/conan && \ /opt/conan/bin/pip install --quiet --disable-pip-version-check --no-cache-dir \ "clang-format==22.1.8" \ "conan==2.31.1" \ + "gcovr==8.6" \ "poetry==2.4.1" ENV PATH="/opt/conan/bin:${PATH}" @@ -28,6 +27,12 @@ WORKDIR /usr/src/tgbot-cpp COPY conanfile.py Makefile poetry.lock pyproject.toml ./ +ENV BUILD_TYPE=Debug +ENV ENABLE_SANITIZERS=ON +ENV ENABLE_COVERAGE=ON +ENV ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 +ENV UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 + RUN make dependencies-with-test COPY include include @@ -39,6 +44,7 @@ COPY api_codegen api_codegen COPY cmake cmake COPY .clang-format ./ COPY CMakeLists.txt ./ +COPY coverage_summary.py ./ RUN make build-with-test && \ make install-only diff --git a/Makefile b/Makefile index bc9f1345..2ee87e40 100644 --- a/Makefile +++ b/Makefile @@ -8,13 +8,15 @@ CONAN_RECIPE_ARGS ?= CMAKE_LOG_LEVEL ?= STATUS CMAKE_INSTALL_MESSAGE ?= ALWAYS CMAKE_BUILD_ARGS ?= +ENABLE_SANITIZERS ?= OFF +ENABLE_COVERAGE ?= OFF POETRY_INSTALL_ARGS ?= --no-interaction --quiet INSTALL_PREFIX ?= API_METHODS_CPP = src/ApiMethods.cpp API_METHODS_INC = include/tgbot/ApiMethods.inc.h API_METHODS_CLANG_FORMAT_CONFIG = api_codegen/clang-format-api-methods.yaml CPP_FILES = $(shell find include src tests examples -type f \( -name '*.h' -o -name '*.cpp' \) ! -name '*.inc.h' ! -path '$(API_METHODS_CPP)' | sort) -PYTHON_FILES = conanfile.py api_codegen +PYTHON_FILES = conanfile.py coverage_summary.py api_codegen DOCKER_IMAGE ?= reo7sp/tgbot-cpp DOCKER_TEST_IMAGE ?= reo7sp/tgbot-cpp-test DOCKER_PLATFORM ?= linux/amd64 @@ -23,6 +25,7 @@ EXAMPLES := $(sort $(notdir $(patsubst %/,%,$(dir $(wildcard examples/*/CMakeLis EXAMPLE_IMAGE ?= tgbot-cpp-example-$(EXAMPLE) PORT ?= 8080 DOCS_WORKTREE := $(abspath build/gh-pages) +COVERAGE_REPORT_DIR ?= $(BUILD_DIR)/coverage ifeq ($(OS),Windows_NT) export CMAKE_GENERATOR ?= Ninja @@ -46,6 +49,8 @@ endif test \ test-only \ test-api-codegen \ + coverage \ + coverage-python \ install \ install-with-system \ install-only \ @@ -94,6 +99,8 @@ configure: dependencies -DCMAKE_TOOLCHAIN_FILE=$(abspath $(BUILD_DIR)/generators/conan_toolchain.cmake) \ -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ -DCMAKE_INSTALL_MESSAGE=$(CMAKE_INSTALL_MESSAGE) \ + -DENABLE_SANITIZERS=$(ENABLE_SANITIZERS) \ + -DENABLE_COVERAGE=$(ENABLE_COVERAGE) \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DENABLE_TESTS=OFF @@ -101,6 +108,8 @@ configure-with-system: cmake --log-level=$(CMAKE_LOG_LEVEL) -S . -B $(BUILD_DIR) \ -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ -DCMAKE_INSTALL_MESSAGE=$(CMAKE_INSTALL_MESSAGE) \ + -DENABLE_SANITIZERS=$(ENABLE_SANITIZERS) \ + -DENABLE_COVERAGE=$(ENABLE_COVERAGE) \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DENABLE_TESTS=OFF @@ -109,6 +118,8 @@ configure-with-test: dependencies-with-test -DCMAKE_TOOLCHAIN_FILE=$(abspath $(BUILD_DIR)/generators/conan_toolchain.cmake) \ -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) \ -DCMAKE_INSTALL_MESSAGE=$(CMAKE_INSTALL_MESSAGE) \ + -DENABLE_SANITIZERS=$(ENABLE_SANITIZERS) \ + -DENABLE_COVERAGE=$(ENABLE_COVERAGE) \ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ -DENABLE_TESTS=ON @@ -147,6 +158,35 @@ test-only: test-api-codegen: poetry run pytest api_codegen/tests +coverage: + cmake -E make_directory $(COVERAGE_REPORT_DIR) + gcovr $(BUILD_DIR) \ + --root . \ + --filter 'include/tgbot/' \ + --filter 'src/' \ + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --html-details $(COVERAGE_REPORT_DIR)/coverage.html + gcovr $(BUILD_DIR) \ + --root . \ + --filter 'include/tgbot/' \ + --filter 'src/' \ + --exclude 'src/ApiMethods\.cpp' \ + --exclude 'src/Types\.cpp' \ + --gcov-ignore-parse-errors=negative_hits.warn_once_per_file \ + --exclude-throw-branches \ + --exclude-unreachable-branches \ + --coveralls $(COVERAGE_REPORT_DIR)/coveralls.json \ + --lcov $(COVERAGE_REPORT_DIR)/lcov.info + python3 coverage_summary.py $(COVERAGE_REPORT_DIR)/coveralls.json + +coverage-python: + cmake -E make_directory $(BUILD_DIR) + COVERAGE_FILE=$(BUILD_DIR)/.coverage-python poetry run pytest api_codegen/tests \ + --cov=api_codegen \ + --cov-report=term-missing + install: build $(MAKE) install-only @@ -230,8 +270,7 @@ docker-run-example-webhook: docker-example-image docker run --rm -it --init --platform=$(DOCKER_PLATFORM) -e TOKEN -e WEBHOOK_URL -p $(PORT):8080 $(EXAMPLE_IMAGE) docker-compose-run-examples: docker-image - DOCKER_PLATFORM=$(DOCKER_PLATFORM) TGBOT_CPP_IMAGE=$(DOCKER_IMAGE) \ - docker compose --env-file env -f docker-compose.test.yaml up --build + DOCKER_PLATFORM=$(DOCKER_PLATFORM) TGBOT_CPP_IMAGE=$(DOCKER_IMAGE) docker compose --env-file env -f docker-compose.test.yaml up --build docker-compose-stop-examples: docker compose --env-file env -f docker-compose.test.yaml down diff --git a/README.md b/README.md index de80eb71..747bfe88 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # tgbot-cpp [![GitHub contributors](https://img.shields.io/github/contributors/reo7sp/tgbot-cpp.svg)](https://github.com/reo7sp/tgbot-cpp/graphs/contributors) +[![Coverage Status](https://coveralls.io/repos/github/reo7sp/tgbot-cpp/badge.svg?branch=master)](https://coveralls.io/github/reo7sp/tgbot-cpp?branch=master) C++ library for Telegram bot API. diff --git a/api/codegen.yaml b/api/codegen.yaml index 37e42f60..affc1c06 100644 --- a/api/codegen.yaml +++ b/api/codegen.yaml @@ -52,6 +52,7 @@ types: api: addStickerToSet: args_order: [user_id, name, sticker] + supports_attach_references: true answerCallbackQuery: args_order: [callback_query_id, text, show_alert, url, cache_time] answerInlineQuery: @@ -80,6 +81,7 @@ api: provider_token: {declaration_required: true} createNewStickerSet: args_order: [user_id, name, title, stickers, sticker_type, needs_repainting] + supports_attach_references: true args: sticker_type: {type: "Sticker::Type", default: "Sticker::Type::Regular"} deleteMyCommands: @@ -97,12 +99,16 @@ api: editMessageMedia: return_type: std::shared_ptr args_order: [media, chat_id, message_id, inline_message_id, reply_markup] + supports_attach_references: true editMessageReplyMarkup: return_type: std::shared_ptr args_order: [chat_id, message_id, inline_message_id, reply_markup] editMessageText: return_type: std::shared_ptr args_order: [text, chat_id, message_id, inline_message_id, parse_mode, link_preview_options, reply_markup, entities] + supports_attach_references: true + editStory: + supports_attach_references: true forwardMessage: args_order: [chat_id, from_chat_id, message_id, disable_notification, protect_content, message_thread_id] forwardMessages: @@ -123,6 +129,7 @@ api: args_order: [chat_id, user_id, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users, can_pin_messages, can_promote_members, is_anonymous, can_manage_chat, can_manage_video_chats, can_restrict_members, can_manage_topics, can_post_stories, can_edit_stories, can_delete_stories] replaceStickerInSet: args_order: [user_id, name, old_sticker, sticker] + supports_attach_references: true restrictChatMember: args_order: [chat_id, user_id, permissions, until_date, use_independent_chat_permissions] sendAnimation: @@ -146,7 +153,12 @@ api: sendLocation: args_order: [chat_id, latitude, longitude, live_period, reply_parameters, reply_markup, disable_notification, horizontal_accuracy, heading, proximity_alert_radius, message_thread_id, protect_content, business_connection_id] sendMediaGroup: - args_order: [chat_id, media, disable_notification, reply_parameters, message_thread_id, protect_content, business_connection_id] + args_order: [chat_id, media, disable_notification, reply_parameters, message_thread_id, protect_content, business_connection_id, allow_paid_broadcast, direct_messages_topic_id, message_effect_id] + supports_attach_references: true + sendPaidMedia: + supports_attach_references: true + sendRichMessage: + supports_attach_references: true sendMessage: args_order: [chat_id, text, link_preview_options, reply_parameters, reply_markup, parse_mode, disable_notification, entities, message_thread_id, protect_content, business_connection_id] sendPhoto: @@ -165,11 +177,17 @@ api: args_order: [chat_id, video_note, reply_parameters, disable_notification, duration, length, thumbnail, reply_markup, message_thread_id, protect_content, business_connection_id] sendVoice: args_order: [chat_id, voice, caption, duration, reply_parameters, reply_markup, parse_mode, disable_notification, caption_entities, message_thread_id, protect_content, business_connection_id] + postStory: + supports_attach_references: true + setBusinessAccountProfilePhoto: + supports_attach_references: true setChatAdministratorCustomTitle: args_order: [chat_id, user_id, custom_title] setGameScore: return_type: std::shared_ptr args_order: [user_id, score, force, disable_edit_message, chat_id, message_id, inline_message_id] + setMyProfilePhoto: + supports_attach_references: true setMessageReaction: args_order: [chat_id, message_id, reaction, is_big] setMyCommands: diff --git a/api_codegen/generate.py b/api_codegen/generate.py index fb88d175..4e037139 100644 --- a/api_codegen/generate.py +++ b/api_codegen/generate.py @@ -24,6 +24,10 @@ CONFIG = yaml.safe_load(DEFAULT_CONFIG.read_text(encoding="utf-8")) TYPE_CONFIG = CONFIG.get("types", {}) API_CONFIG = CONFIG.get("api", {}) +ATTACH_REFERENCES_DESCRIPTION = ( + "Files uploaded as named multipart parts. Reference each file from a composite " + "Telegram API argument as attach:// and use the same name in InputFileAttachment." +) def run(schema_path: Path, root: Path) -> None: @@ -99,6 +103,7 @@ class _MethodModel: return_description: str description: tuple[str, ...] args: tuple[_ArgModel, ...] + compatibility_args: tuple[_ArgModel, ...] args_name: str | None @@ -364,11 +369,14 @@ def _build_method(self, operation: Schema) -> _MethodModel: properties = dict(request.get("properties", {})) for arg_name, arg in multipart.get("properties", {}).items(): properties.setdefault(arg_name, arg) + method_config = self._api_config.get(name, {}) + if method_config.get("supports_attach_references"): + properties["attachments"] = {"description": ATTACH_REFERENCES_DESCRIPTION} + properties.update(method_config.get("extra_args", {})) required = set(request.get("required", [])) | set(multipart.get("required", [])) binary = self._binary_args(operation) arg_names = self._ordered_arg_names(name, properties, required) response_type = _CppTypeResolver.api_type(self._response_schema(operation)) - method_config = self._api_config.get(name, {}) return_type = method_config.get("return_type", response_type) args = tuple( self._build_arg( @@ -380,6 +388,7 @@ def _build_method(self, operation: Schema) -> _MethodModel: ) for arg_name in arg_names ) + compatibility_without = set(method_config.get("compatibility_overload_without", ())) return _MethodModel( name=name, return_type=return_type, @@ -387,6 +396,7 @@ def _build_method(self, operation: Schema) -> _MethodModel: return_description=self._return_description(return_type, response_type), description=self._comment_lines(operation.get("description", ""), 88), args=args, + compatibility_args=tuple(arg for arg in args if arg.wire_name not in compatibility_without), args_name=f"{name[0].upper()}{name[1:]}Args" if args else None, ) @@ -411,7 +421,13 @@ def _ordered_arg_names( ), ) optional_names = [name for name in properties if name not in declaration_required] - optional_names.sort(key=lambda name: (args_order.get(name, len(args_order)), name)) + optional_names.sort( + key=lambda name: ( + name == "attachments" and method_config.get("supports_attach_references"), + args_order.get(name, len(args_order)), + name, + ) + ) return required_names + optional_names @@ -424,7 +440,9 @@ def _build_arg( binary: bool, ) -> _ArgModel: arg_config = self._api_config.get(method_name, {}).get("args", {}).get(name, {}) - override = arg_config.get("type") + method_config = self._api_config.get(method_name, {}) + attach_references_arg = name == "attachments" and method_config.get("supports_attach_references") + override = "std::vector" if attach_references_arg else arg_config.get("type") if override: cpp_type = override elif name in {"chat_id", "from_chat_id"}: @@ -499,7 +517,9 @@ def _return_description(return_type: str, response_type: str) -> str: @staticmethod def _arg_declaration_type(cpp_type: str) -> str: - if cpp_type == "std::string" or cpp_type.startswith("std::vector<"): + if cpp_type == "std::string": + return "std::string_view" + if cpp_type.startswith("std::vector<"): return f"const {cpp_type}&" if cpp_type == "nlohmann::json": return "const nlohmann::json&" diff --git a/api_codegen/templates/api_methods.cpp.j2 b/api_codegen/templates/api_methods.cpp.j2 index b12599a0..2caeaafe 100644 --- a/api_codegen/templates/api_methods.cpp.j2 +++ b/api_codegen/templates/api_methods.cpp.j2 @@ -14,12 +14,13 @@ ApiRequest::{{ factory }}("{{ arg.wire_name }}", {{ arg.cpp_name }}{{ wire_default }}) {%- endmacro %} -{% macro method_definition(method, argument_object=false) -%} +{% macro method_definition(method, argument_object=false, args=none) -%} +{% set definition_args = method.args if args is none else args -%} {{ method.return_type }} Api::{{ method.name }}( {% if argument_object %} const {{ method.args_name }}& args {% else %} -{% for arg in method.args %} +{% for arg in definition_args %} {{ arg.declaration_type }} {{ arg.cpp_name }}{{ "," if not loop.last else "" }} {% endfor %} {% endif %} @@ -47,6 +48,16 @@ namespace TgBot { ); } +{% if method.compatibility_args != method.args %} +{{ method_definition(method, args=method.compatibility_args) }} { + return {{ method.name }}( +{% for arg in method.args %} + {{ arg.cpp_name if arg in method.compatibility_args else "{ }" }}{{ "," if not loop.last else "" }} +{% endfor %} + ); +} + +{% endif %} {% if method.args_name %} {{ method_definition(method, argument_object=true) }} { return {{ method.name }}( diff --git a/api_codegen/templates/api_methods.inc.h.j2 b/api_codegen/templates/api_methods.inc.h.j2 index 0a22ed3c..797cebdc 100644 --- a/api_codegen/templates/api_methods.inc.h.j2 +++ b/api_codegen/templates/api_methods.inc.h.j2 @@ -5,7 +5,8 @@ {{ arg.declaration_type }} {{ arg.cpp_name }}{{ default }} {%- endmacro %} -{% macro documentation(method, argument_object=false) -%} +{% macro documentation(method, argument_object=false, args=none) -%} +{% set documented_args = method.args if args is none else args -%} /** * @brief{{ " " + method.description[0] if method.description else "" }} {% for line in method.description[1:] %} @@ -15,7 +16,7 @@ {% if argument_object %} * @param args Method arguments. {% else %} -{% for arg in method.args %} +{% for arg in documented_args %} * @param {{ arg.cpp_name }}{{ " " + arg.description[0] if arg.description else "" }} {% for line in arg.description[1:] %} * {{ line }} @@ -27,12 +28,13 @@ */ {%- endmacro %} -{% macro method_declaration(method, argument_object=false) -%} +{% macro method_declaration(method, argument_object=false, args=none) -%} +{% set declared_args = method.args if args is none else args -%} {{ method.return_type }} {{ method.name }}( {% if argument_object %} const {{ method.args_name }}& args {% else %} -{% for arg in method.args %} +{% for arg in declared_args %} {{ arg_declaration(arg) }}{{ "," if not loop.last else "" }} {% endfor %} {% endif %} @@ -44,6 +46,11 @@ {{ documentation(method) }} {{ method_declaration(method) }} +{% if method.compatibility_args != method.args %} +{{ documentation(method, args=method.compatibility_args) }} +{{ method_declaration(method, args=method.compatibility_args) }} + +{% endif %} {% if method.args_name %} {{ documentation(method, argument_object=true) }} {{ method_declaration(method, argument_object=true) }} diff --git a/api_codegen/templates/types.h.j2 b/api_codegen/templates/types.h.j2 index c9029863..192a2293 100644 --- a/api_codegen/templates/types.h.j2 +++ b/api_codegen/templates/types.h.j2 @@ -2,6 +2,7 @@ #pragma once +#include "tgbot/InputFile.h" #include "tgbot/export.h" #include @@ -15,8 +16,6 @@ namespace TgBot { -struct InputFile; - {% for type in types %} struct {{ type.name }}; {% endfor %} diff --git a/api_codegen/tests/test_generate.py b/api_codegen/tests/test_generate.py index 3a405767..fb419fc5 100644 --- a/api_codegen/tests/test_generate.py +++ b/api_codegen/tests/test_generate.py @@ -39,12 +39,28 @@ def test_direct_api_keeps_legacy_args_order_and_defaults() -> None: ] assert builder._arg_default("std::int32_t", "setWebhook", "max_connections") == "40" assert builder._arg_default("std::shared_ptr", "setWebhook", "certificate") == "nullptr" + assert builder._arg_declaration_type("std::string") == "std::string_view" def test_compatibility_config_is_grouped_by_telegram_entity() -> None: assert API_CONFIG["setStickerSetTitle"]["args_order"] == ["name", "title"] assert API_CONFIG["editMessageText"]["return_type"] == "std::shared_ptr" assert API_CONFIG["getUpdates"]["args"]["limit"]["default"] == 100 + assert API_CONFIG["sendMediaGroup"]["supports_attach_references"] is True + assert {name for name, config in API_CONFIG.items() if config.get("supports_attach_references")} == { + "addStickerToSet", + "createNewStickerSet", + "editMessageMedia", + "editMessageText", + "editStory", + "postStory", + "replaceStickerInSet", + "sendMediaGroup", + "sendPaidMedia", + "sendRichMessage", + "setBusinessAccountProfilePhoto", + "setMyProfilePhoto", + } assert TYPE_CONFIG["BotCommandScopeChatMember"]["fields"]["user_id"]["type"] == "std::int64_t" assert TYPE_CONFIG["Chat"]["fields"]["type"]["enum"]["Private"] == "private" @@ -112,6 +128,21 @@ def test_integer_widths_follow_schema_and_telegram_id_rules() -> None: assert message_id.cpp_type == "std::int32_t" +def test_type_resolver_handles_json_fallback_and_later_all_of_reference() -> None: + assert _CppTypeResolver.type({}) == "nlohmann::json" + assert ( + _CppTypeResolver.ref_name( + { + "allOf": [ + {"type": "object"}, + {"$ref": "#/components/schemas/Message"}, + ] + } + ) + == "Message" + ) + + def test_high_risk_methods_keep_legacy_args_order() -> None: builder = _MethodModelBuilder({}) properties = { @@ -163,6 +194,21 @@ def test_field_constant_recognizes_only_fixed_discriminators() -> None: ) +def test_field_constant_uses_single_enum_value() -> None: + constant = _TypeModelBuilder._field_constant( + "type", + { + "type": "string", + "enum": ["audio"], + }, + True, + ) + + assert constant is not None + assert constant.name == "TYPE" + assert constant.value == "audio" + + def test_union_header_includes_variant() -> None: assert "variant" in _TypeModelBuilder._standard_headers((), ("Message",)) @@ -213,6 +259,7 @@ def test_generator_renders_types_methods_and_documentation( assert "ApiRequest::makeFields(" in api_source assert "ApiResponse::decode>" in api_source assert "std::shared_ptr certificate = nullptr" in methods + assert "std::string_view url" in methods assert "std::int32_t maxConnections = 40" in methods assert 'ApiRequest::required("url", url)' in api_source assert 'ApiRequest::optional("certificate", certificate)' in api_source @@ -239,6 +286,22 @@ def test_generator_renders_types_methods_and_documentation( assert tmp_path.joinpath("src/ApiMethods.cpp").read_text(encoding="utf-8") == api_source +@pytest.mark.parametrize( + "method_name", + [name for name, config in API_CONFIG.items() if config.get("supports_attach_references")], +) +def test_generator_renders_optional_attach_reference_argument(method_name: str) -> None: + document = yaml.safe_load((Path(__file__).parents[2] / "api/telegram-bot-api.yaml").read_text(encoding="utf-8")) + methods = _MethodModelBuilder(document["paths"]).build() + method = next(method for method in methods if method.name == method_name) + + attachment = next(arg for arg in method.args if arg.wire_name == "attachments") + assert attachment.cpp_type == "std::vector" + assert attachment.default_value == "{ }" + assert method.args[-1] == attachment + assert method.compatibility_args == method.args + + def test_generator_rejects_method_without_result_schema(tmp_path: Path) -> None: schema = _schema() response_parts = schema["paths"]["/getMe"]["post"]["responses"]["200"]["content"]["application/json"]["schema"][ @@ -252,6 +315,15 @@ def test_generator_rejects_method_without_result_schema(tmp_path: Path) -> None: _OpenApiGenerator(schema_path, tmp_path).generate() +def test_json_argument_defaults_and_unsupported_return_type() -> None: + builder = _MethodModelBuilder({}) + + assert builder._arg_default("nlohmann::json", "method", "argument") == "nullptr" + assert builder._arg_declaration_type("nlohmann::json") == "const nlohmann::json&" + with pytest.raises(ValueError, match="Unsupported API return type: double"): + builder._return_description("double", "double") + + def test_every_method_with_args_generates_ordered_argument_object_delegation(tmp_path: Path) -> None: schema_path = Path(__file__).parents[2] / "api" / "telegram-bot-api.yaml" document = yaml.safe_load(schema_path.read_text(encoding="utf-8")) diff --git a/coverage_summary.py b/coverage_summary.py new file mode 100644 index 00000000..7bf85410 --- /dev/null +++ b/coverage_summary.py @@ -0,0 +1,19 @@ +import json +import sys + +with open(sys.argv[1]) as report: + data = json.load(report) + +lines = [line for source_file in data["source_files"] for line in source_file["coverage"] if line is not None] +branches = [ + branch + for source_file in data["source_files"] + for branch in zip(*(iter(source_file.get("branches", [])),) * 4, strict=True) +] +covered_lines = sum(line > 0 for line in lines) +covered_branches = sum(branch[3] > 0 for branch in branches) +coverage = (covered_lines + covered_branches) / (len(lines) + len(branches)) +line_coverage = covered_lines / len(lines) +branch_coverage = covered_branches / len(branches) + +print(f"Coveralls: {coverage:.1%} (lines {line_coverage:.1%}, branches {branch_coverage:.1%})") diff --git a/include/tgbot/Api.h b/include/tgbot/Api.h index e4cf8517..996ae6a0 100644 --- a/include/tgbot/Api.h +++ b/include/tgbot/Api.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -38,12 +39,12 @@ class TGBOT_API Api { /** * @brief Downloads a Telegram file and returns its contents. */ - std::string downloadFile(const std::string& filePath, const std::vector& fields = { }) const; + std::string downloadFile(std::string_view filePath, const std::vector& fields = { }) const; const HttpClient& _httpClient; protected: - nlohmann::json sendRequest(const std::string& method, const std::vector& fields) const; + nlohmann::json sendRequest(std::string_view method, const std::vector& fields) const; const std::string _token; const std::string _url; diff --git a/include/tgbot/ApiCodec.h b/include/tgbot/ApiCodec.h index 35c494e6..0b30b7be 100644 --- a/include/tgbot/ApiCodec.h +++ b/include/tgbot/ApiCodec.h @@ -3,13 +3,17 @@ #include "tgbot/HttpFormField.h" #include "tgbot/InputFile.h" #include "tgbot/Json.h" +#include "tgbot/export.h" #include #include #include +#include #include +#include #include +#include #include #include @@ -17,34 +21,36 @@ namespace TgBot::ApiRequest { -void appendField(std::vector& fields, const char* name, const std::string& value); -void appendField(std::vector& fields, const char* name, const std::shared_ptr& value); +TGBOT_API void appendField(std::vector& fields, std::string_view name, std::string_view value); +TGBOT_API void appendField(std::vector& fields, std::string_view name, + const std::shared_ptr& value); template -void appendField(std::vector& fields, const char* name, const T& value) { +requires(!std::is_convertible_v) +void appendField(std::vector& fields, std::string_view name, const T& value) { if constexpr (std::is_arithmetic_v) { - fields.push_back({ name, std::to_string(value) }); + fields.push_back({ std::string(name), std::to_string(value) }); } else { - fields.push_back({ name, Json::encode(value).dump() }); + fields.push_back({ std::string(name), Json::encode(value).dump() }); } } template -void appendField(std::vector& fields, const char* name, const std::optional& value) { +void appendField(std::vector& fields, std::string_view name, const std::optional& value) { if (value) { appendField(fields, name, *value); } } template -void appendField(std::vector& fields, const char* name, const std::shared_ptr& value) { +void appendField(std::vector& fields, std::string_view name, const std::shared_ptr& value) { if (value) { - fields.push_back({ name, Json::encode(value).dump() }); + fields.push_back({ std::string(name), Json::encode(value).dump() }); } } template -void appendField(std::vector& fields, const char* name, const std::variant& value) { +void appendField(std::vector& fields, std::string_view name, const std::variant& value) { std::visit( [&](const auto& item) { appendField(fields, name, item); @@ -54,31 +60,36 @@ void appendField(std::vector& fields, const char* name, const std template requires std::is_arithmetic_v -void appendOptionalField(std::vector& fields, const char* name, T value) { +void appendOptionalField(std::vector& fields, std::string_view name, T value) { if (value != 0) { appendField(fields, name, value); } } -void appendOptionalField(std::vector& fields, const char* name, const std::string& value); -void appendOptionalField(std::vector& fields, const char* name, const nlohmann::json& value); +TGBOT_API void appendOptionalField(std::vector& fields, std::string_view name, std::string_view value); +TGBOT_API void appendOptionalField(std::vector& fields, std::string_view name, const std::string& value); + +TGBOT_API void appendOptionalField(std::vector& fields, std::string_view name, + const nlohmann::json& value); +TGBOT_API void appendOptionalField(std::vector& fields, std::string_view name, + const std::vector& value); template -void appendOptionalField(std::vector& fields, const char* name, const std::shared_ptr& value) { +void appendOptionalField(std::vector& fields, std::string_view name, const std::shared_ptr& value) { if (value) { appendField(fields, name, value); } } template -void appendOptionalField(std::vector& fields, const char* name, const std::vector& value) { +void appendOptionalField(std::vector& fields, std::string_view name, const std::vector& value) { if (!value.empty()) { appendField(fields, name, value); } } template -void appendOptionalField(std::vector& fields, const char* name, const std::variant& value) { +void appendOptionalField(std::vector& fields, std::string_view name, const std::variant& value) { std::visit( [&](const auto& item) { appendOptionalField(fields, name, item); @@ -86,35 +97,47 @@ void appendOptionalField(std::vector& fields, const char* name, c value); } +/** + * @brief Internal non-owning parameter proxy for immediate use with makeFields(). + * + * Do not store this object. The referenced name and value must remain valid until + * makeFields() returns. + */ template struct Parameter { static constexpr bool isRequired = Required; - const char* name; + std::string_view name; const T& value; }; +/** + * @brief Internal non-owning parameter proxy with a default value. + * + * Do not store this object. The referenced name and value must remain valid until + * makeFields() returns. + */ template struct ParameterWithDefault { static constexpr bool isRequired = false; - const char* name; + std::string_view name; const T& value; T defaultValue; }; template -Parameter required(const char* name, const T& value) { +Parameter required(std::string_view name, const T& value) { return { name, value }; } template -Parameter optional(const char* name, const T& value) { +Parameter optional(std::string_view name, const T& value) { return { name, value }; } template -ParameterWithDefault optional(const char* name, const T& value, T defaultValue) { +ParameterWithDefault optional(std::string_view name, const T& value, T defaultValue) { return { name, value, std::move(defaultValue) }; } @@ -136,6 +159,13 @@ std::vector makeFields(const Parameters&... parameters) { }(), ...); + std::unordered_set names; + for (const auto& field : result) { + if (!names.emplace(field.name).second) { + throw std::invalid_argument("Duplicate multipart field name: " + field.name); + } + } + return result; } diff --git a/include/tgbot/ApiMethods.inc.h b/include/tgbot/ApiMethods.inc.h index cf57cc29..c5a6a636 100644 --- a/include/tgbot/ApiMethods.inc.h +++ b/include/tgbot/ApiMethods.inc.h @@ -10,10 +10,16 @@ * @param sticker A JSON-serialized object with information about the added sticker. If * exactly the same sticker had already been added to the set, then the set * isn't changed. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return True on success. */ - bool addStickerToSet(std::int64_t userId, const std::string& name, std::shared_ptr sticker) const; + bool addStickerToSet(std::int64_t userId, + std::string_view name, + std::shared_ptr sticker, + const std::vector& attachments = { }) const; /** * @brief Use this method to add a new sticker to a set created by the bot. Emoji sticker sets can @@ -47,10 +53,10 @@ * * @return True on success. */ - bool answerCallbackQuery(const std::string& callbackQueryId, - const std::string& text = "", + bool answerCallbackQuery(std::string_view callbackQueryId, + std::string_view text = "", bool showAlert = false, - const std::string& url = "", + std::string_view url = "", std::int32_t cacheTime = 0) const; /** @@ -74,7 +80,7 @@ * * @return True on success. */ - bool answerChatJoinRequestQuery(const std::string& chatJoinRequestQueryId, const std::string& result) const; + bool answerChatJoinRequestQuery(std::string_view chatJoinRequestQueryId, std::string_view result) const; /** * @brief Use this method to process a received chat join request query. Returns True on success. @@ -94,7 +100,7 @@ * * @return The resulting SentGuestMessage object. */ - std::shared_ptr answerGuestQuery(const std::string& guestQueryId, + std::shared_ptr answerGuestQuery(std::string_view guestQueryId, std::shared_ptr result) const; /** @@ -127,11 +133,11 @@ * * @return True on success. */ - bool answerInlineQuery(const std::string& inlineQueryId, + bool answerInlineQuery(std::string_view inlineQueryId, const std::vector>& results, std::int32_t cacheTime = 300, bool isPersonal = false, - const std::string& nextOffset = "", + std::string_view nextOffset = "", std::shared_ptr button = nullptr) const; /** @@ -163,9 +169,8 @@ * * @return True on success. */ - bool answerPreCheckoutQuery(const std::string& preCheckoutQueryId, - bool ok, - const std::string& errorMessage = "") const; + bool + answerPreCheckoutQuery(std::string_view preCheckoutQueryId, bool ok, std::string_view errorMessage = "") const; /** * @brief Once the user has confirmed their payment and shipping details, the Bot API sends the @@ -197,10 +202,10 @@ * * @return True on success. */ - bool answerShippingQuery(const std::string& shippingQueryId, + bool answerShippingQuery(std::string_view shippingQueryId, bool ok, const std::vector>& shippingOptions = { }, - const std::string& errorMessage = "") const; + std::string_view errorMessage = "") const; /** * @brief If you sent an invoice requesting a shipping address and the parameter is_flexible was @@ -223,7 +228,7 @@ * * @return The resulting SentWebAppMessage object. */ - std::shared_ptr answerWebAppQuery(const std::string& webAppQueryId, + std::shared_ptr answerWebAppQuery(std::string_view webAppQueryId, std::shared_ptr result) const; /** @@ -426,7 +431,7 @@ * * @return True on success. */ - bool convertGiftToStars(const std::string& businessConnectionId, const std::string& ownedGiftId) const; + bool convertGiftToStars(std::string_view businessConnectionId, std::string_view ownedGiftId) const; /** * @brief Converts a given regular gift to Telegram Stars. Requires the can_convert_gifts_to_stars @@ -488,8 +493,8 @@ std::shared_ptr copyMessage(std::variant chatId, std::variant fromChatId, std::int32_t messageId, - const std::string& caption = "", - const std::string& parseMode = "", + std::string_view caption = "", + std::string_view parseMode = "", const std::vector>& captionEntities = { }, bool disableNotification = false, std::shared_ptr replyParameters = nullptr, @@ -501,7 +506,7 @@ std::int32_t messageThreadId = 0, bool allowPaidBroadcast = false, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", bool showCaptionAboveMedia = false, std::shared_ptr suggestedPostParameters = nullptr, @@ -594,7 +599,7 @@ std::shared_ptr createChatInviteLink(std::variant chatId, std::int32_t expireDate = 0, std::int32_t memberLimit = 0, - const std::string& name = "", + std::string_view name = "", bool createsJoinRequest = false) const; /** @@ -628,7 +633,7 @@ std::shared_ptr createChatSubscriptionInviteLink(std::variant chatId, std::int32_t subscriptionPeriod, std::int32_t subscriptionPrice, - const std::string& name = "") const; + std::string_view name = "") const; /** * @brief Use this method to create a subscription invite link for a channel chat. The bot must @@ -661,9 +666,9 @@ * @return The resulting ForumTopic object. */ std::shared_ptr createForumTopic(std::variant chatId, - const std::string& name, + std::string_view name, std::int32_t iconColor = 0, - const std::string& iconCustomEmojiId = "") const; + std::string_view iconCustomEmojiId = "") const; /** * @brief Use this method to create a topic in a forum supergroup chat or a private chat with a @@ -736,16 +741,16 @@ * * @return The resulting string. */ - std::string createInvoiceLink(const std::string& title, - const std::string& description, - const std::string& payload, - const std::string& providerToken, - const std::string& currency, + std::string createInvoiceLink(std::string_view title, + std::string_view description, + std::string_view payload, + std::string_view providerToken, + std::string_view currency, const std::vector>& prices, std::int32_t maxTipAmount = 0, const std::vector& suggestedTipAmounts = { }, - const std::string& providerData = "", - const std::string& photoUrl = "", + std::string_view providerData = "", + std::string_view photoUrl = "", std::int32_t photoSize = 0, std::int32_t photoWidth = 0, std::int32_t photoHeight = 0, @@ -756,7 +761,7 @@ bool sendPhoneNumberToProvider = false, bool sendEmailToProvider = false, bool isFlexible = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", std::int32_t subscriptionPeriod = 0) const; /** @@ -788,15 +793,19 @@ * of text when used in messages, the accent color if used as emoji status, * white on chat photos, or another appropriate color based on context; for * custom emoji sticker sets only + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return True on success. */ bool createNewStickerSet(std::int64_t userId, - const std::string& name, - const std::string& title, + std::string_view name, + std::string_view title, const std::vector>& stickers, Sticker::Type stickerType = Sticker::Type::Regular, - bool needsRepainting = false) const; + bool needsRepainting = false, + const std::vector& attachments = { }) const; /** * @brief Use this method to create a new sticker set owned by a user. The bot will be able to @@ -845,7 +854,7 @@ */ bool declineSuggestedPost(std::variant chatId, std::int32_t messageId, - const std::string& comment = "") const; + std::string_view comment = "") const; /** * @brief Use this method to decline a suggested post in a direct messages chat. The bot must have @@ -903,7 +912,7 @@ * * @return True on success. */ - bool deleteBusinessMessages(const std::string& businessConnectionId, + bool deleteBusinessMessages(std::string_view businessConnectionId, const std::vector& messageIds) const; /** @@ -1128,7 +1137,7 @@ * @return True on success. */ bool deleteMyCommands(std::shared_ptr scope = nullptr, - const std::string& languageCode = "") const; + std::string_view languageCode = "") const; /** * @brief Use this method to delete the list of the bot's commands for the given scope and user @@ -1149,7 +1158,7 @@ * * @return True on success. */ - bool deleteStickerFromSet(const std::string& sticker) const; + bool deleteStickerFromSet(std::string_view sticker) const; /** * @brief Use this method to delete a sticker from a set created by the bot. Returns True on @@ -1170,7 +1179,7 @@ * * @return True on success. */ - bool deleteStickerSet(const std::string& name) const; + bool deleteStickerSet(std::string_view name) const; /** * @brief Use this method to delete a sticker set that was created by the bot. Returns True on @@ -1192,7 +1201,7 @@ * * @return True on success. */ - bool deleteStory(const std::string& businessConnectionId, std::int32_t storyId) const; + bool deleteStory(std::string_view businessConnectionId, std::int32_t storyId) const; /** * @brief Deletes a story previously posted by the bot on behalf of a managed business account. @@ -1242,10 +1251,10 @@ * @return The resulting ChatInviteLink object. */ std::shared_ptr editChatInviteLink(std::variant chatId, - const std::string& inviteLink, + std::string_view inviteLink, std::int32_t expireDate = 0, std::int32_t memberLimit = 0, - const std::string& name = "", + std::string_view name = "", bool createsJoinRequest = false) const; /** @@ -1272,8 +1281,8 @@ * @return The resulting ChatInviteLink object. */ std::shared_ptr editChatSubscriptionInviteLink(std::variant chatId, - const std::string& inviteLink, - const std::string& name = "") const; + std::string_view inviteLink, + std::string_view name = "") const; /** * @brief Use this method to edit a subscription invite link created by the bot. The bot must have @@ -1308,9 +1317,9 @@ bool editEphemeralMessageCaption(std::variant chatId, std::int32_t ephemeralMessageId, std::int64_t receiverUserId, - const std::string& caption = "", + std::string_view caption = "", const std::vector>& captionEntities = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", std::shared_ptr replyMarkup = nullptr) const; /** @@ -1408,10 +1417,10 @@ bool editEphemeralMessageText(std::variant chatId, std::int32_t ephemeralMessageId, std::int64_t receiverUserId, - const std::string& text, + std::string_view text, const std::vector>& entities = { }, std::shared_ptr linkPreviewOptions = nullptr, - const std::string& parseMode = "", + std::string_view parseMode = "", std::shared_ptr replyMarkup = nullptr) const; /** @@ -1445,8 +1454,8 @@ */ bool editForumTopic(std::variant chatId, std::int32_t messageThreadId, - const std::string& name = "", - const std::string& iconCustomEmojiId = "") const; + std::string_view name = "", + std::string_view iconCustomEmojiId = "") const; /** * @brief Use this method to edit name and icon of a topic in a forum supergroup chat or a private @@ -1471,7 +1480,7 @@ * * @return True on success. */ - bool editGeneralForumTopic(std::variant chatId, const std::string& name) const; + bool editGeneralForumTopic(std::variant chatId, std::string_view name) const; /** * @brief Use this method to edit the name of the 'General' topic in a forum supergroup chat. The @@ -1512,13 +1521,13 @@ */ std::shared_ptr editMessageCaption(std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& caption = "", - const std::string& inlineMessageId = "", + std::string_view caption = "", + std::string_view inlineMessageId = "", std::shared_ptr replyMarkup = nullptr, - const std::string& parseMode = "", + std::string_view parseMode = "", const std::vector>& captionEntities = { }, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool showCaptionAboveMedia = false) const; /** @@ -1548,7 +1557,7 @@ * @return The resulting Message object. */ std::shared_ptr editMessageChecklist(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr checklist, std::int32_t messageId, std::shared_ptr replyMarkup @@ -1600,12 +1609,12 @@ double longitude, std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "", + std::string_view inlineMessageId = "", std::shared_ptr replyMarkup = nullptr, double horizontalAccuracy = 0, std::int32_t heading = 0, std::int32_t proximityAlertRadius = 0, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", std::int32_t livePeriod = 0) const; /** @@ -1642,15 +1651,19 @@ * @param replyMarkup A JSON-serialized object for a new inline keyboard * @param businessConnectionId Unique identifier of the business connection on behalf of which the * message to be edited was sent + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Message object, or nullptr if Telegram returns True. */ std::shared_ptr editMessageMedia(std::shared_ptr media, std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "", + std::string_view inlineMessageId = "", std::shared_ptr replyMarkup = nullptr, - const std::string& businessConnectionId = "") const; + std::string_view businessConnectionId = "", + const std::vector& attachments = { }) const; /** * @brief Use this method to edit animation, audio, document, live photo, photo, or video @@ -1690,9 +1703,9 @@ */ std::shared_ptr editMessageReplyMarkup(std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "", + std::string_view inlineMessageId = "", std::shared_ptr replyMarkup = nullptr, - const std::string& businessConnectionId = "") const; + std::string_view businessConnectionId = "") const; /** * @brief Use this method to edit only the reply markup of messages. On success, if the edited @@ -1732,19 +1745,23 @@ * @param richMessage New rich content of the message; required if text isn't specified. * Direct upload of new files isn't supported when an inline message is * edited. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Message object, or nullptr if Telegram returns True. */ - std::shared_ptr editMessageText(const std::string& text = "", + std::shared_ptr editMessageText(std::string_view text = "", std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "", - const std::string& parseMode = "", + std::string_view inlineMessageId = "", + std::string_view parseMode = "", std::shared_ptr linkPreviewOptions = nullptr, std::shared_ptr replyMarkup = nullptr, const std::vector>& entities = { }, - const std::string& businessConnectionId = "", - std::shared_ptr richMessage = nullptr) const; + std::string_view businessConnectionId = "", + std::shared_ptr richMessage = nullptr, + const std::vector& attachments = { }) const; /** * @brief Use this method to edit text, rich and game messages. On success, if the edited message @@ -1771,16 +1788,20 @@ * which can be specified instead of parse_mode * @param parseMode Mode for parsing entities in the story caption. See formatting options * for more details. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Story object. */ - std::shared_ptr editStory(const std::string& businessConnectionId, + std::shared_ptr editStory(std::string_view businessConnectionId, std::shared_ptr content, std::int32_t storyId, const std::vector>& areas = { }, - const std::string& caption = "", + std::string_view caption = "", const std::vector>& captionEntities = { }, - const std::string& parseMode = "") const; + std::string_view parseMode = "", + const std::vector& attachments = { }) const; /** * @brief Edits a story previously posted by the bot on behalf of a managed business account. @@ -1805,9 +1826,8 @@ * * @return True on success. */ - bool editUserStarSubscription(bool isCanceled, - const std::string& telegramPaymentChargeId, - std::int64_t userId) const; + bool + editUserStarSubscription(bool isCanceled, std::string_view telegramPaymentChargeId, std::int64_t userId) const; /** * @brief Allows the bot to cancel or re-enable extension of a subscription paid in Telegram @@ -1879,7 +1899,7 @@ bool protectContent = false, std::int32_t messageThreadId = 0, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::shared_ptr suggestedPostParameters = nullptr, std::int32_t videoStartTimestamp = 0) const; @@ -1975,7 +1995,7 @@ * * @return The resulting OwnedGifts object. */ - std::shared_ptr getBusinessAccountGifts(const std::string& businessConnectionId, + std::shared_ptr getBusinessAccountGifts(std::string_view businessConnectionId, bool excludeFromBlockchain = false, bool excludeLimitedNonUpgradable = false, bool excludeLimitedUpgradable = false, @@ -1984,7 +2004,7 @@ bool excludeUnlimited = false, bool excludeUnsaved = false, std::int32_t limit = 0, - const std::string& offset = "", + std::string_view offset = "", bool sortByPrice = false) const; /** @@ -2005,7 +2025,7 @@ * * @return The resulting StarAmount object. */ - std::shared_ptr getBusinessAccountStarBalance(const std::string& businessConnectionId) const; + std::shared_ptr getBusinessAccountStarBalance(std::string_view businessConnectionId) const; /** * @brief Returns the amount of Telegram Stars owned by a managed business account. Requires the @@ -2025,7 +2045,7 @@ * * @return The resulting BusinessConnection object. */ - std::shared_ptr getBusinessConnection(const std::string& businessConnectionId) const; + std::shared_ptr getBusinessConnection(std::string_view businessConnectionId) const; /** * @brief Use this method to get information about the connection of the bot with a business @@ -2119,7 +2139,7 @@ bool excludeUnlimited = false, bool excludeUnsaved = false, std::int32_t limit = 0, - const std::string& offset = "", + std::string_view offset = "", bool sortByPrice = false) const; /** @@ -2232,7 +2252,7 @@ * * @return The resulting File object. */ - std::shared_ptr getFile(const std::string& fileId) const; + std::shared_ptr getFile(std::string_view fileId) const; /** * @brief Use this method to get basic information about a file and prepare it for downloading. @@ -2278,7 +2298,7 @@ std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "") const; + std::string_view inlineMessageId = "") const; /** * @brief Use this method to get data for high score tables. Will return the score of the @@ -2354,7 +2374,7 @@ * @return The resulting list of BotCommand objects. */ std::vector> getMyCommands(std::shared_ptr scope = nullptr, - const std::string& languageCode = "") const; + std::string_view languageCode = "") const; /** * @brief Use this method to get the current list of the bot's commands for the given scope and @@ -2398,7 +2418,7 @@ * * @return The resulting BotDescription object. */ - std::shared_ptr getMyDescription(const std::string& languageCode = "") const; + std::shared_ptr getMyDescription(std::string_view languageCode = "") const; /** * @brief Use this method to get the current bot description for the given user language. Returns @@ -2418,7 +2438,7 @@ * * @return The resulting BotName object. */ - std::shared_ptr getMyName(const std::string& languageCode = "") const; + std::shared_ptr getMyName(std::string_view languageCode = "") const; /** * @brief Use this method to get the current bot name for the given user language. Returns BotName @@ -2438,7 +2458,7 @@ * * @return The resulting BotShortDescription object. */ - std::shared_ptr getMyShortDescription(const std::string& languageCode = "") const; + std::shared_ptr getMyShortDescription(std::string_view languageCode = "") const; /** * @brief Use this method to get the current bot short description for the given user language. @@ -2488,7 +2508,7 @@ * * @return The resulting StickerSet object. */ - std::shared_ptr getStickerSet(const std::string& name) const; + std::shared_ptr getStickerSet(std::string_view name) const; /** * @brief Use this method to get a sticker set. On success, a StickerSet object is returned. @@ -2593,7 +2613,7 @@ bool excludeUnique = false, bool excludeUnlimited = false, std::int32_t limit = 0, - const std::string& offset = "", + std::string_view offset = "", bool sortByPrice = false) const; /** @@ -2714,9 +2734,9 @@ bool giftPremiumSubscription(std::int32_t monthCount, std::int32_t starCount, std::int64_t userId, - const std::string& text = "", + std::string_view text = "", const std::vector>& textEntities = { }, - const std::string& textParseMode = "") const; + std::string_view textParseMode = "") const; /** * @brief Gifts a Telegram Premium subscription to the given user. Returns True on success. @@ -2806,7 +2826,7 @@ */ bool pinChatMessage(std::variant chatId, std::int32_t messageId, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool disableNotification = false) const; /** @@ -2839,18 +2859,22 @@ * @param postToChatPage Pass True to keep the story accessible after it expires * @param protectContent Pass True if the content of the story must be protected from forwarding * and screenshotting + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Story object. */ std::shared_ptr postStory(std::int32_t activePeriod, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr content, const std::vector>& areas = { }, - const std::string& caption = "", + std::string_view caption = "", const std::vector>& captionEntities = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool postToChatPage = false, - bool protectContent = false) const; + bool protectContent = false, + const std::vector& attachments = { }) const; /** * @brief Posts a story on behalf of a managed business account. Requires the can_manage_stories @@ -2952,7 +2976,7 @@ * @return True on success. */ bool readBusinessMessage(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::int32_t messageId) const; /** @@ -2973,7 +2997,7 @@ * * @return True on success. */ - bool refundStarPayment(const std::string& telegramPaymentChargeId, std::int64_t userId) const; + bool refundStarPayment(std::string_view telegramPaymentChargeId, std::int64_t userId) const; /** * @brief Refunds a successful payment in Telegram Stars. Returns True on success. @@ -2996,7 +3020,7 @@ * * @return True on success. */ - bool removeBusinessAccountProfilePhoto(const std::string& businessConnectionId, bool isPublic = false) const; + bool removeBusinessAccountProfilePhoto(std::string_view businessConnectionId, bool isPublic = false) const; /** * @brief Removes the current profile photo of a managed business account. Requires the @@ -3137,13 +3161,17 @@ * @param sticker A JSON-serialized object with information about the added sticker. If * exactly the same sticker had already been added to the set, then the set * remains unchanged. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return True on success. */ bool replaceStickerInSet(std::int64_t userId, - const std::string& name, - const std::string& oldSticker, - std::shared_ptr sticker) const; + std::string_view name, + std::string_view oldSticker, + std::shared_ptr sticker, + const std::vector& attachments = { }) const; /** * @brief Use this method to replace an existing sticker in a sticker set with a new one. The @@ -3175,7 +3203,7 @@ * @return The resulting Story object. */ std::shared_ptr repostStory(std::int32_t activePeriod, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::variant fromChatId, std::int32_t fromStoryId, bool postToChatPage = false, @@ -3244,7 +3272,7 @@ * @return The resulting ChatInviteLink object. */ std::shared_ptr revokeChatInviteLink(std::variant chatId, - const std::string& inviteLink) const; + std::string_view inviteLink) const; /** * @brief Use this method to revoke an invite link created by the bot. If the primary link is @@ -3382,23 +3410,23 @@ std::int32_t width = 0, std::int32_t height = 0, std::variant, std::string> thumbnail = { }, - const std::string& caption = "", + std::string_view caption = "", std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, bool hasSpoiler = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, bool showCaptionAboveMedia = false, std::shared_ptr suggestedPostParameters @@ -3478,26 +3506,26 @@ */ std::shared_ptr sendAudio(std::variant chatId, std::variant, std::string> audio, - const std::string& caption = "", + std::string_view caption = "", std::int32_t duration = 0, - const std::string& performer = "", - const std::string& title = "", + std::string_view performer = "", + std::string_view title = "", std::variant, std::string> thumbnail = { }, std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -3539,9 +3567,9 @@ * @return True on success. */ bool sendChatAction(std::variant chatId, - const std::string& action, + std::string_view action, std::int32_t messageThreadId = 0, - const std::string& businessConnectionId = "") const; + std::string_view businessConnectionId = "") const; /** * @brief Use this method when you need to tell the user that something is happening on the bot's @@ -3568,7 +3596,7 @@ * * @return True on success. */ - bool sendChatJoinRequestWebApp(const std::string& chatJoinRequestQueryId, const std::string& webAppUrl) const; + bool sendChatJoinRequestWebApp(std::string_view chatJoinRequestQueryId, std::string_view webAppUrl) const; /** * @brief Use this method to process a received chat join request query by showing a Mini App to @@ -3601,10 +3629,10 @@ * @return The resulting Message object. */ std::shared_ptr sendChecklist(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr checklist, bool disableNotification = false, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", bool protectContent = false, std::shared_ptr replyMarkup = nullptr, std::shared_ptr replyParameters = nullptr) const; @@ -3661,10 +3689,10 @@ * @return The resulting Message object. */ std::shared_ptr sendContact(std::variant chatId, - const std::string& phoneNumber, - const std::string& firstName, - const std::string& lastName = "", - const std::string& vcard = "", + std::string_view phoneNumber, + std::string_view firstName, + std::string_view lastName = "", + std::string_view vcard = "", bool disableNotification = false, std::shared_ptr replyParameters = nullptr, std::variant, @@ -3673,11 +3701,11 @@ std::shared_ptr> replyMarkup = { }, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -3733,13 +3761,13 @@ std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& emoji = "", + std::string_view emoji = "", std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::shared_ptr suggestedPostParameters = nullptr) const; @@ -3815,23 +3843,23 @@ std::shared_ptr sendDocument(std::variant chatId, std::variant, std::string> document, std::variant, std::string> thumbnail = { }, - const std::string& caption = "", + std::string_view caption = "", std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, bool disableContentTypeDetection = false, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -3876,15 +3904,15 @@ * @return The resulting Message object. */ std::shared_ptr sendGame(std::variant chatId, - const std::string& gameShortName, + std::string_view gameShortName, std::shared_ptr replyParameters = nullptr, std::shared_ptr replyMarkup = nullptr, bool disableNotification = false, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& messageEffectId = "") const; + std::string_view messageEffectId = "") const; /** * @brief Use this method to send a game. On success, the sent Message is returned. @@ -3918,12 +3946,12 @@ * * @return True on success. */ - bool sendGift(const std::string& giftId, + bool sendGift(std::string_view giftId, std::variant chatId = { }, bool payForUpgrade = false, - const std::string& text = "", + std::string_view text = "", const std::vector>& textEntities = { }, - const std::string& textParseMode = "", + std::string_view textParseMode = "", std::int64_t userId = 0) const; /** @@ -4017,14 +4045,14 @@ * @return The resulting Message object. */ std::shared_ptr sendInvoice(std::variant chatId, - const std::string& title, - const std::string& description, - const std::string& payload, - const std::string& providerToken, - const std::string& currency, + std::string_view title, + std::string_view description, + std::string_view payload, + std::string_view providerToken, + std::string_view currency, const std::vector>& prices, - const std::string& providerData = "", - const std::string& photoUrl = "", + std::string_view providerData = "", + std::string_view photoUrl = "", std::int32_t photoSize = 0, std::int32_t photoWidth = 0, std::int32_t photoHeight = 0, @@ -4041,11 +4069,11 @@ std::int32_t messageThreadId = 0, std::int32_t maxTipAmount = 0, const std::vector& suggestedTipAmounts = { }, - const std::string& startParameter = "", + std::string_view startParameter = "", bool protectContent = false, bool allowPaidBroadcast = false, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::shared_ptr suggestedPostParameters = nullptr) const; @@ -4116,16 +4144,16 @@ std::variant, std::string> livePhoto, std::variant, std::string> photo, bool allowPaidBroadcast = false, - const std::string& businessConnectionId = "", - const std::string& callbackQueryId = "", - const std::string& caption = "", + std::string_view businessConnectionId = "", + std::string_view callbackQueryId = "", + std::string_view caption = "", const std::vector>& captionEntities = { }, std::int64_t directMessagesTopicId = 0, bool disableNotification = false, bool hasSpoiler = false, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int32_t messageThreadId = 0, - const std::string& parseMode = "", + std::string_view parseMode = "", bool protectContent = false, std::int64_t receiverUserId = 0, std::variant, @@ -4210,11 +4238,11 @@ std::int32_t proximityAlertRadius = 0, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -4253,6 +4281,9 @@ * sent; required if the messages are sent to a direct messages chat * @param messageEffectId Unique identifier of the message effect to be added to the message; for * private chats only + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting list of Message objects. */ @@ -4267,10 +4298,11 @@ std::shared_ptr replyParameters = nullptr, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "") const; + std::string_view messageEffectId = "", + const std::vector& attachments = { }) const; /** * @brief Use this method to send a group of photos, live photos, videos, documents or audios as @@ -4327,23 +4359,23 @@ * @return The resulting Message object. */ std::shared_ptr sendMessage(std::variant chatId, - const std::string& text, + std::string_view text, std::shared_ptr linkPreviewOptions = nullptr, std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& entities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -4380,8 +4412,8 @@ std::int32_t draftId, const std::vector>& entities = { }, std::int32_t messageThreadId = 0, - const std::string& parseMode = "", - const std::string& text = "") const; + std::string_view parseMode = "", + std::string_view text = "") const; /** * @brief Use this method to stream a partial message to a user while the message is being @@ -4434,6 +4466,9 @@ * to send; for direct messages chats only. If the message is sent as a * reply to another suggested post, then that suggested post is * automatically declined. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Message object. */ @@ -4441,14 +4476,14 @@ const std::vector>& media, std::int32_t starCount, bool allowPaidBroadcast = false, - const std::string& businessConnectionId = "", - const std::string& caption = "", + std::string_view businessConnectionId = "", + std::string_view caption = "", const std::vector>& captionEntities = { }, std::int64_t directMessagesTopicId = 0, bool disableNotification = false, std::int32_t messageThreadId = 0, - const std::string& parseMode = "", - const std::string& payload = "", + std::string_view parseMode = "", + std::string_view payload = "", bool protectContent = false, std::variant, std::shared_ptr, @@ -4457,7 +4492,8 @@ std::shared_ptr replyParameters = nullptr, bool showCaptionAboveMedia = false, std::shared_ptr suggestedPostParameters - = nullptr) const; + = nullptr, + const std::vector& attachments = { }) const; /** * @brief Use this method to send paid media. On success, the sent Message is returned. @@ -4521,23 +4557,23 @@ */ std::shared_ptr sendPhoto(std::variant chatId, std::variant, std::string> photo, - const std::string& caption = "", + std::string_view caption = "", std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, bool hasSpoiler = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, bool showCaptionAboveMedia = false, std::shared_ptr suggestedPostParameters @@ -4626,7 +4662,7 @@ * @return The resulting Message object. */ std::shared_ptr sendPoll(std::variant chatId, - const std::string& question, + std::string_view question, const std::vector>& options, bool disableNotification = false, std::shared_ptr replyParameters = nullptr, @@ -4635,32 +4671,32 @@ std::shared_ptr, std::shared_ptr> replyMarkup = { }, bool isAnonymous = true, - const std::string& type = "", + std::string_view type = "", bool allowsMultipleAnswers = false, - const std::string& explanation = "", - const std::string& explanationParseMode = "", + std::string_view explanation = "", + std::string_view explanationParseMode = "", const std::vector>& explanationEntities = { }, std::int32_t openPeriod = 0, std::int32_t closeDate = 0, bool isClosed = false, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowAddingOptions = false, bool allowPaidBroadcast = false, bool allowsRevoting = false, const std::vector& correctOptionIds = { }, const std::vector& countryCodes = { }, - const std::string& description = "", + std::string_view description = "", const std::vector>& descriptionEntities = { }, - const std::string& descriptionParseMode = "", + std::string_view descriptionParseMode = "", std::shared_ptr explanationMedia = nullptr, bool hideResultsUntilCloses = false, std::shared_ptr media = nullptr, bool membersOnly = false, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", const std::vector>& questionEntities = { }, - const std::string& questionParseMode = "", + std::string_view questionParseMode = "", bool shuffleOptions = false) const; /** @@ -4704,16 +4740,19 @@ * to send; for direct messages chats only. If the message is sent as a * reply to another suggested post, then that suggested post is * automatically declined. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return The resulting Message object. */ std::shared_ptr sendRichMessage(std::variant chatId, std::shared_ptr richMessage, bool allowPaidBroadcast = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", std::int64_t directMessagesTopicId = 0, bool disableNotification = false, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int32_t messageThreadId = 0, bool protectContent = false, std::variant, @@ -4722,7 +4761,8 @@ std::shared_ptr> replyMarkup = { }, std::shared_ptr replyParameters = nullptr, std::shared_ptr suggestedPostParameters - = nullptr) const; + = nullptr, + const std::vector& attachments = { }) const; /** * @brief Use this method to send rich messages. If the message contains a block with a media @@ -4822,12 +4862,12 @@ bool disableNotification = false, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& emoji = "", - const std::string& businessConnectionId = "", + std::string_view emoji = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -4893,25 +4933,25 @@ std::shared_ptr sendVenue(std::variant chatId, double latitude, double longitude, - const std::string& title, - const std::string& address, - const std::string& foursquareId = "", - const std::string& foursquareType = "", + std::string_view title, + std::string_view address, + std::string_view foursquareId = "", + std::string_view foursquareType = "", bool disableNotification = false, std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& googlePlaceId = "", - const std::string& googlePlaceType = "", + std::string_view googlePlaceId = "", + std::string_view googlePlaceType = "", std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -5003,24 +5043,24 @@ std::int32_t width = 0, std::int32_t height = 0, std::variant, std::string> thumbnail = { }, - const std::string& caption = "", + std::string_view caption = "", std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, bool hasSpoiler = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::variant, std::string> cover = { }, std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, bool showCaptionAboveMedia = false, std::int32_t startTimestamp = 0, @@ -5104,11 +5144,11 @@ std::shared_ptr> replyMarkup = { }, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -5176,23 +5216,23 @@ */ std::shared_ptr sendVoice(std::variant chatId, std::variant, std::string> voice, - const std::string& caption = "", + std::string_view caption = "", std::int32_t duration = 0, std::shared_ptr replyParameters = nullptr, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup = { }, - const std::string& parseMode = "", + std::string_view parseMode = "", bool disableNotification = false, const std::vector>& captionEntities = { }, std::int32_t messageThreadId = 0, bool protectContent = false, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", bool allowPaidBroadcast = false, - const std::string& callbackQueryId = "", + std::string_view callbackQueryId = "", std::int64_t directMessagesTopicId = 0, - const std::string& messageEffectId = "", + std::string_view messageEffectId = "", std::int64_t receiverUserId = 0, std::shared_ptr suggestedPostParameters = nullptr) const; @@ -5219,7 +5259,7 @@ * * @return True on success. */ - bool setBusinessAccountBio(const std::string& businessConnectionId, const std::string& bio = "") const; + bool setBusinessAccountBio(std::string_view businessConnectionId, std::string_view bio = "") const; /** * @brief Changes the bio of a managed business account. Requires the can_change_bio business bot @@ -5243,7 +5283,7 @@ * @return True on success. */ bool setBusinessAccountGiftSettings(std::shared_ptr acceptedGiftTypes, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool showGiftButton) const; /** @@ -5267,9 +5307,9 @@ * * @return True on success. */ - bool setBusinessAccountName(const std::string& businessConnectionId, - const std::string& firstName, - const std::string& lastName = "") const; + bool setBusinessAccountName(std::string_view businessConnectionId, + std::string_view firstName, + std::string_view lastName = "") const; /** * @brief Changes the first and last name of a managed business account. Requires the @@ -5290,12 +5330,16 @@ * @param isPublic Pass True to set the public photo, which will be visible even if the * main photo is hidden by the business account's privacy settings. An * account can have only one public photo. + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return True on success. */ - bool setBusinessAccountProfilePhoto(const std::string& businessConnectionId, + bool setBusinessAccountProfilePhoto(std::string_view businessConnectionId, std::shared_ptr photo, - bool isPublic = false) const; + bool isPublic = false, + const std::vector& attachments = { }) const; /** * @brief Changes the profile photo of a managed business account. Requires the @@ -5316,8 +5360,7 @@ * * @return True on success. */ - bool setBusinessAccountUsername(const std::string& businessConnectionId, - const std::string& username = "") const; + bool setBusinessAccountUsername(std::string_view businessConnectionId, std::string_view username = "") const; /** * @brief Changes the username of a managed business account. Requires the can_change_username @@ -5343,7 +5386,7 @@ */ bool setChatAdministratorCustomTitle(std::variant chatId, std::int64_t userId, - const std::string& customTitle) const; + std::string_view customTitle) const; /** * @brief Use this method to set a custom title for an administrator in a supergroup promoted by @@ -5367,7 +5410,7 @@ * @return True on success. */ bool setChatDescription(std::variant chatId, - const std::string& description = "") const; + std::string_view description = "") const; /** * @brief Use this method to change the description of a group, a supergroup or a channel. The bot @@ -5394,7 +5437,7 @@ */ bool setChatMemberTag(std::variant chatId, std::int64_t userId, - const std::string& tag = "") const; + std::string_view tag = "") const; /** * @brief Use this method to set a tag for a regular member in a group or a supergroup. The bot @@ -5500,7 +5543,7 @@ * * @return True on success. */ - bool setChatStickerSet(std::variant chatId, const std::string& stickerSetName) const; + bool setChatStickerSet(std::variant chatId, std::string_view stickerSetName) const; /** * @brief Use this method to set a new group sticker set for a supergroup. The bot must be an @@ -5525,7 +5568,7 @@ * * @return True on success. */ - bool setChatTitle(std::variant chatId, const std::string& title) const; + bool setChatTitle(std::variant chatId, std::string_view title) const; /** * @brief Use this method to change the title of a chat. Titles can't be changed for private @@ -5548,7 +5591,7 @@ * * @return True on success. */ - bool setCustomEmojiStickerSetThumbnail(const std::string& name, const std::string& customEmojiId = "") const; + bool setCustomEmojiStickerSetThumbnail(std::string_view name, std::string_view customEmojiId = "") const; /** * @brief Use this method to set the thumbnail of a custom emoji sticker set. Returns True on @@ -5587,7 +5630,7 @@ bool disableEditMessage = false, std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "") const; + std::string_view inlineMessageId = "") const; /** * @brief Use this method to set the score of the specified user in a game message. On success, if @@ -5679,7 +5722,7 @@ */ bool setMyCommands(const std::vector>& commands, std::shared_ptr scope = nullptr, - const std::string& languageCode = "") const; + std::string_view languageCode = "") const; /** * @brief Use this method to change the list of the bot's commands. See this manual for more @@ -5732,7 +5775,7 @@ * * @return True on success. */ - bool setMyDescription(const std::string& description = "", const std::string& languageCode = "") const; + bool setMyDescription(std::string_view description = "", std::string_view languageCode = "") const; /** * @brief Use this method to change the bot's description, which is shown in the chat with the bot @@ -5754,7 +5797,7 @@ * * @return True on success. */ - bool setMyName(const std::string& name = "", const std::string& languageCode = "") const; + bool setMyName(std::string_view name = "", std::string_view languageCode = "") const; /** * @brief Use this method to change the bot's name. Returns True on success. @@ -5769,10 +5812,14 @@ * @brief Changes the profile photo of the bot. Returns True on success. * * @param photo The new profile photo to set + * @param attachments Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. * * @return True on success. */ - bool setMyProfilePhoto(std::shared_ptr photo) const; + bool setMyProfilePhoto(std::shared_ptr photo, + const std::vector& attachments = { }) const; /** * @brief Changes the profile photo of the bot. Returns True on success. @@ -5796,8 +5843,7 @@ * * @return True on success. */ - bool setMyShortDescription(const std::string& shortDescription = "", - const std::string& languageCode = "") const; + bool setMyShortDescription(std::string_view shortDescription = "", std::string_view languageCode = "") const; /** * @brief Use this method to change the bot's short description, which is shown on the bot's @@ -5852,7 +5898,7 @@ * * @return True on success. */ - bool setStickerEmojiList(const std::string& sticker, const std::vector& emojiList) const; + bool setStickerEmojiList(std::string_view sticker, const std::vector& emojiList) const; /** * @brief Use this method to change the list of emoji assigned to a regular or custom emoji @@ -5875,7 +5921,7 @@ * * @return True on success. */ - bool setStickerKeywords(const std::string& sticker, const std::vector& keywords = { }) const; + bool setStickerKeywords(std::string_view sticker, const std::vector& keywords = { }) const; /** * @brief Use this method to change search keywords assigned to a regular or custom emoji sticker. @@ -5897,7 +5943,7 @@ * * @return True on success. */ - bool setStickerMaskPosition(const std::string& sticker, + bool setStickerMaskPosition(std::string_view sticker, std::shared_ptr maskPosition = nullptr) const; /** @@ -5919,7 +5965,7 @@ * * @return True on success. */ - bool setStickerPositionInSet(const std::string& sticker, std::int32_t position) const; + bool setStickerPositionInSet(std::string_view sticker, std::int32_t position) const; /** * @brief Use this method to move a sticker in a set created by the bot to a specific position. @@ -5956,9 +6002,9 @@ * * @return True on success. */ - bool setStickerSetThumbnail(const std::string& name, + bool setStickerSetThumbnail(std::string_view name, std::int64_t userId, - const std::string& format, + std::string_view format, std::variant, std::string> thumbnail = { }) const; /** @@ -5980,7 +6026,7 @@ * * @return True on success. */ - bool setStickerSetTitle(const std::string& name, const std::string& title) const; + bool setStickerSetTitle(std::string_view name, std::string_view title) const; /** * @brief Use this method to set the title of a created sticker set. Returns True on success. @@ -6004,7 +6050,7 @@ * @return True on success. */ bool setUserEmojiStatus(std::int64_t userId, - const std::string& emojiStatusCustomEmojiId = "", + std::string_view emojiStatusCustomEmojiId = "", std::int32_t emojiStatusExpirationDate = 0) const; /** @@ -6055,13 +6101,13 @@ * * @return True on success. */ - bool setWebhook(const std::string& url, + bool setWebhook(std::string_view url, std::shared_ptr certificate = nullptr, std::int32_t maxConnections = 40, const std::vector& allowedUpdates = { }, - const std::string& ipAddress = "", + std::string_view ipAddress = "", bool dropPendingUpdates = false, - const std::string& secretToken = "") const; + std::string_view secretToken = "") const; /** * @brief Use this method to specify a URL and receive incoming updates via an outgoing webhook. @@ -6099,9 +6145,9 @@ */ std::shared_ptr stopMessageLiveLocation(std::variant chatId = { }, std::int32_t messageId = 0, - const std::string& inlineMessageId = "", + std::string_view inlineMessageId = "", std::shared_ptr replyMarkup = nullptr, - const std::string& businessConnectionId = "") const; + std::string_view businessConnectionId = "") const; /** * @brief Use this method to stop updating a live location message before live_period expires. On @@ -6129,7 +6175,7 @@ */ std::shared_ptr stopPoll(std::variant chatId, std::int32_t messageId, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", std::shared_ptr replyMarkup = nullptr) const; /** @@ -6151,7 +6197,7 @@ * * @return True on success. */ - bool transferBusinessAccountStars(const std::string& businessConnectionId, std::int32_t starCount) const; + bool transferBusinessAccountStars(std::string_view businessConnectionId, std::int32_t starCount) const; /** * @brief Transfers Telegram Stars from the business account balance to the bot's balance. @@ -6178,9 +6224,9 @@ * * @return True on success. */ - bool transferGift(const std::string& businessConnectionId, + bool transferGift(std::string_view businessConnectionId, std::int64_t newOwnerChatId, - const std::string& ownedGiftId, + std::string_view ownedGiftId, std::int32_t starCount = 0) const; /** @@ -6369,7 +6415,7 @@ * @return True on success. */ bool unpinChatMessage(std::variant chatId, - const std::string& businessConnectionId = "", + std::string_view businessConnectionId = "", std::int32_t messageId = 0) const; /** @@ -6402,8 +6448,8 @@ * * @return True on success. */ - bool upgradeGift(const std::string& businessConnectionId, - const std::string& ownedGiftId, + bool upgradeGift(std::string_view businessConnectionId, + std::string_view ownedGiftId, bool keepOriginalDetails = false, std::int32_t starCount = 0) const; @@ -6433,7 +6479,7 @@ */ std::shared_ptr uploadStickerFile(std::int64_t userId, std::variant, std::string> sticker, - const std::string& stickerFormat) const; + std::string_view stickerFormat) const; /** * @brief Use this method to upload a file with a sticker for later use in the @@ -6459,8 +6505,7 @@ * * @return True on success. */ - bool verifyChat(std::variant chatId, - const std::string& customDescription = "") const; + bool verifyChat(std::variant chatId, std::string_view customDescription = "") const; /** * @brief Verifies a chat on behalf of the organization which is represented by the bot. Returns @@ -6483,7 +6528,7 @@ * * @return True on success. */ - bool verifyUser(std::int64_t userId, const std::string& customDescription = "") const; + bool verifyUser(std::int64_t userId, std::string_view customDescription = "") const; /** * @brief Verifies a user on behalf of the organization which is represented by the bot. Returns diff --git a/include/tgbot/Bot.h b/include/tgbot/Bot.h index 9d8e0c10..533b765c 100644 --- a/include/tgbot/Bot.h +++ b/include/tgbot/Bot.h @@ -20,7 +20,7 @@ class HttpClient; class TGBOT_API Bot { public: explicit Bot(std::string token, const HttpClient& httpClient = _getDefaultHttpClient(), - const std::string& url = "https://api.telegram.org"); + std::string url = "https://api.telegram.org"); /** * @return Token for accessing api. diff --git a/include/tgbot/EventBroadcaster.h b/include/tgbot/EventBroadcaster.h index 9218585d..e3c4026d 100644 --- a/include/tgbot/EventBroadcaster.h +++ b/include/tgbot/EventBroadcaster.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -47,14 +48,14 @@ class TGBOT_API EventBroadcaster { * @param commandName Command name which listener can handle. * @param listener Listener. Pass nullptr to remove listener of command */ - void onCommand(const std::string& commandName, const MessageListener& listener); + void onCommand(std::string_view commandName, const MessageListener& listener); /** * @brief Registers listener which receives all messages with commands (messages with leading '/' char). * @param commandsList Commands names which listener can handle. * @param listener Listener. Pass nullptr to remove listener of commands */ - void onCommand(const std::initializer_list& commandsList, const MessageListener& listener); + void onCommand(std::initializer_list commandsList, const MessageListener& listener); /** * @brief Registers listener which receives all messages with commands (messages with leading '/' char) which haven't been handled by other listeners. @@ -171,11 +172,19 @@ class TGBOT_API EventBroadcaster { void onSuccessfulPayment(const SuccessfulPaymentListener& listener); private: + struct TransparentStringHash { + using is_transparent = void; + + std::size_t operator()(std::string_view value) const noexcept { + return std::hash { }(value); + } + }; + template void broadcast(const std::vector& listeners, ObjectType object) const; void broadcastAnyMessage(const std::shared_ptr& message) const; - bool broadcastCommand(const std::string& command, const std::shared_ptr& message) const; + bool broadcastCommand(std::string_view command, const std::shared_ptr& message) const; void broadcastUnknownCommand(const std::shared_ptr& message) const; void broadcastNonCommandMessage(const std::shared_ptr& message) const; void broadcastEditedMessage(const std::shared_ptr& message) const; @@ -195,7 +204,7 @@ class TGBOT_API EventBroadcaster { void broadcastSuccessfulPayment(const std::shared_ptr& message) const; std::vector _onAnyMessageListeners; - std::unordered_map _onCommandListeners; + std::unordered_map> _onCommandListeners; std::vector _onUnknownCommandListeners; std::vector _onNonCommandMessageListeners; std::vector _onEditedMessageListeners; diff --git a/include/tgbot/HttpClient.h b/include/tgbot/HttpClient.h index 4f626a5d..d83d1416 100644 --- a/include/tgbot/HttpClient.h +++ b/include/tgbot/HttpClient.h @@ -8,6 +8,7 @@ #include #include #include +#include namespace TgBot { @@ -19,7 +20,7 @@ namespace TgBot { class TGBOT_API RequestCancelled : public std::runtime_error { public: RequestCancelled(); - explicit RequestCancelled(const std::string& request); + explicit RequestCancelled(std::string_view request); }; /** diff --git a/include/tgbot/HttpFormField.h b/include/tgbot/HttpFormField.h index 0b440ea0..3006dc13 100644 --- a/include/tgbot/HttpFormField.h +++ b/include/tgbot/HttpFormField.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -12,6 +13,7 @@ struct HttpFile { std::string data; std::string mimeType; std::string fileName; + std::optional filePath; }; /** diff --git a/include/tgbot/HttpServer.h b/include/tgbot/HttpServer.h index bfedb5f7..d8a62131 100644 --- a/include/tgbot/HttpServer.h +++ b/include/tgbot/HttpServer.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -171,8 +172,8 @@ class HttpServer { }); } - static void reportError(const ErrorHandler& errorHandler, const std::string& message) { - const std::runtime_error error(message); + static void reportError(const ErrorHandler& errorHandler, std::string_view message) { + const std::runtime_error error { std::string(message) }; if (errorHandler) { errorHandler(error); } diff --git a/include/tgbot/InputFile.h b/include/tgbot/InputFile.h index bb9bfb36..f7e4453b 100644 --- a/include/tgbot/InputFile.h +++ b/include/tgbot/InputFile.h @@ -3,7 +3,9 @@ #include "tgbot/export.h" #include +#include #include +#include namespace TgBot { @@ -16,12 +18,14 @@ struct TGBOT_API InputFile { using Ptr = std::shared_ptr; /** - * @brief Contents of a file. + * @brief Contents used for an in-memory upload. + * + * Empty for files created with fromFile(). */ std::string data; /** - * @brief Mime type of a file. + * @brief Mime type of the contents. */ std::string mimeType; @@ -30,10 +34,51 @@ struct TGBOT_API InputFile { */ std::string fileName; + /** + * @brief Local path used for a streaming upload. + * + * When set, the file is read by the HTTP client during the request and data is + * ignored. The file must remain accessible and unchanged until the request completes. + */ + std::optional filePath; + + /** + * @brief Creates new std::shared_ptr from binary data held in memory. + * + * @param data File contents. May contain null bytes. + * @param mimeType Mime type of the file. + * @param fileName File name reported in the multipart upload. + */ + static std::shared_ptr fromData(std::string data, std::string mimeType, std::string fileName); + /** * @brief Creates new std::shared_ptr from an existing file. + * + * Stores the path without reading the file. The HTTP client streams its contents + * during the request and uses the basename of filePath as the multipart file name. + * The file must remain accessible and unchanged until the request completes. + * + * @param filePath Path to the file to stream. + * @param mimeType Mime type of the file. + */ + static std::shared_ptr fromFile(std::string_view filePath, std::string mimeType); +}; + +/** + * @brief A named multipart file referenced from a Telegram API argument as attach://name. + * + * @ingroup api + */ +struct InputFileAttachment { + /** + * @brief Multipart form field name used in the attach:// reference. + */ + std::string name; + + /** + * @brief File contents to upload under the specified name. */ - static std::shared_ptr fromFile(const std::string& filePath, const std::string& mimeType); + std::shared_ptr file; }; } // namespace TgBot diff --git a/include/tgbot/TgException.h b/include/tgbot/TgException.h index 2be756ed..ab563c07 100644 --- a/include/tgbot/TgException.h +++ b/include/tgbot/TgException.h @@ -4,6 +4,7 @@ #include #include +#include namespace TgBot { @@ -29,7 +30,7 @@ class TGBOT_API TgException : public std::runtime_error { InvalidJson = 101 }; - TgException(const std::string& description, ErrorCode errorCode); + TgException(std::string_view description, ErrorCode errorCode); const ErrorCode errorCode; }; diff --git a/include/tgbot/TgWebhookLocalServer.h b/include/tgbot/TgWebhookLocalServer.h index 274a5070..357447dd 100644 --- a/include/tgbot/TgWebhookLocalServer.h +++ b/include/tgbot/TgWebhookLocalServer.h @@ -16,7 +16,7 @@ namespace TgBot { */ class TGBOT_API TgWebhookLocalServer : public TgWebhookServer { public: - TgWebhookLocalServer(const std::string& unixSocketPath, const std::string& path, const EventHandler& eventHandler); + TgWebhookLocalServer(const std::string& unixSocketPath, std::string path, const EventHandler& eventHandler); TgWebhookLocalServer(const std::string& unixSocketPath, const Bot& bot); }; diff --git a/include/tgbot/TgWebhookTcpServer.h b/include/tgbot/TgWebhookTcpServer.h index 4437bb93..cb21666a 100644 --- a/include/tgbot/TgWebhookTcpServer.h +++ b/include/tgbot/TgWebhookTcpServer.h @@ -13,7 +13,7 @@ namespace TgBot { */ class TGBOT_API TgWebhookTcpServer : public TgWebhookServer { public: - TgWebhookTcpServer(unsigned short port, const std::string& path, const EventHandler& eventHandler); + TgWebhookTcpServer(unsigned short port, std::string path, const EventHandler& eventHandler); TgWebhookTcpServer(unsigned short port, const Bot& bot); }; diff --git a/include/tgbot/Types.h b/include/tgbot/Types.h index 905a6b21..c19adbdc 100644 --- a/include/tgbot/Types.h +++ b/include/tgbot/Types.h @@ -2,6 +2,7 @@ #pragma once +#include "tgbot/InputFile.h" #include "tgbot/export.h" #include @@ -15,8 +16,6 @@ namespace TgBot { -struct InputFile; - struct AcceptedGiftTypes; struct AffiliateInfo; struct Animation; @@ -13983,6 +13982,13 @@ struct AddStickerToSetArgs { * isn't changed. */ std::shared_ptr sticker { }; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -14763,6 +14769,13 @@ struct CreateNewStickerSetArgs { * custom emoji sticker sets only */ bool needsRepainting = false; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -15508,6 +15521,13 @@ struct EditMessageMediaArgs { * message to be edited was sent */ std::string businessConnectionId = ""; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -15610,6 +15630,13 @@ struct EditMessageTextArgs { * edited. */ std::shared_ptr richMessage = nullptr; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -15653,6 +15680,13 @@ struct EditStoryArgs { * for more details. */ std::string parseMode = ""; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -16541,6 +16575,13 @@ struct PostStoryArgs { * and screenshotting */ bool protectContent = false; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -16808,6 +16849,13 @@ struct ReplaceStickerInSetArgs { * remains unchanged. */ std::shared_ptr sticker { }; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -18339,6 +18387,13 @@ struct SendMediaGroupArgs { * private chats only */ std::string messageEffectId = ""; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -18604,6 +18659,13 @@ struct SendPaidMediaArgs { * automatically declined. */ std::shared_ptr suggestedPostParameters = nullptr; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -19027,6 +19089,13 @@ struct SendRichMessageArgs { * automatically declined. */ std::shared_ptr suggestedPostParameters = nullptr; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -19792,6 +19861,13 @@ struct SetBusinessAccountProfilePhotoArgs { * account can have only one public photo. */ bool isPublic = false; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** @@ -20178,6 +20254,13 @@ struct SetMyProfilePhotoArgs { * @brief The new profile photo to set */ std::shared_ptr photo { }; + + /** + * @brief Files uploaded as named multipart parts. Reference each file from a + * composite Telegram API argument as attach:// and use the same name + * in InputFileAttachment. + */ + std::vector attachments = { }; }; /** diff --git a/poetry.lock b/poetry.lock index 5d47e8cb..0ad39576 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,6 +13,143 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "coverage" +version = "7.15.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "coverage-7.15.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d0be6daac4cce6b8c8dc65886bae1b082ddbca4da8e5cbb5e15166acf253e264"}, + {file = "coverage-7.15.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b24e078eabcd6a9caa8b0713f9bc1eeb310bcc960a29d45a3b4fcd4b16d5b11d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cfe20cc8cf8821d4fe54f89106cbf06aa27f37b5bbe3535568065a81539b4150"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:83cf06cdd687677742caff1a9134833b7a8b75f111519d2cb0e0ba1b9a851e15"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8fa4de68e2a752468ff14b4e15db7def689a71be759e826a31ccecbef69c5fd0"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4dff9daa47d83120c3ec38ce921214242944a832aa04e903e50b5b7ebac8972d"}, + {file = "coverage-7.15.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a093fd37229918976f602aa07aa59e0973cde82186f220c8e197f721f5be0ce4"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:317db01a2cb02552fd67e2b1cca77a4b528a2a277176c5e0bf2cecbb639d3f54"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8ee3838dcb656602c3b51e16aed9bfb0822f8d8d6d1c5966d32ec8c104be8e20"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:425920379052ff1fe465268f3361d35804a241bbdd5a1b592c8cb60df4c52325"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:69bb2400abef928e365ea7d4d9925169ada78ed2295546780002d4b65de3df88"}, + {file = "coverage-7.15.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:81661f82d302484e3119e7c80c519c02fa9bcc2a6b339baf67d67bc89c580f04"}, + {file = "coverage-7.15.4-cp310-cp310-win32.whl", hash = "sha256:cb476b2e828ecb71cb6b6a928d23fd20a7ddb501188022dae1c37499149cc338"}, + {file = "coverage-7.15.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fc2130bf37df31852a8384f12601563a45a0024bccc6624f38355cba7a8d360"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbac5abad70df71019988f83f26ac7092ff2642975def4429e98dc7585ef3490"}, + {file = "coverage-7.15.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:357a173465c7ce028d07a95cc2b63b5bf59f50ecdd5ad75c5cbb78ada984048e"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:21b803935e2efc3acebe9697197a294fccf5dc4e5382bd6369542ff7a7d2a1d7"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7a2b580774a4786c1053157c0165e04476e03ff293993d7c148eee784a94bae6"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a9464451c4efffe8d47ace5a540b10b0dc10e879066290f8600872b7f54a419d"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de602f34123c2f4af1c1869c6dbbbd60da6d5983bf01937367295d135cccbfce"}, + {file = "coverage-7.15.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6879ded16a27f3eeca19b900c147e81616e7054db451471a611b2755ee5249f7"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:986be58c3ab54aae8d3496a6225eea74f760fdbe739b38bd442c7e8d133aa53b"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6103639613fe6c1e989082948419bc77a2d26b6c825c99d7fad25f7d3d87afc"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d3af93dddb5659276c63bc16ac6466ac2033a70ca816097bbc06345b8ccdf571"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b10075e5421d04265766a6d1dac809bbeb8a946fbb23c8f82c227409b2190719"}, + {file = "coverage-7.15.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a67a9f78b2942d87ba8ce3059c642164d2aedd65337377fb52fe9803656bc5c7"}, + {file = "coverage-7.15.4-cp311-cp311-win32.whl", hash = "sha256:69484d1aca26e322e1c3ce03f09341e84524ababad2d7202161738d83cc9f82e"}, + {file = "coverage-7.15.4-cp311-cp311-win_amd64.whl", hash = "sha256:63fd6fcd1dd6e158f7eb78606e72933b3f6d01e7b747f99c6c12d764307a0fdc"}, + {file = "coverage-7.15.4-cp311-cp311-win_arm64.whl", hash = "sha256:ea82116c9893fa89e929b7f197ee5a1950a76e91cc5c85ba503fc02379d04890"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4fedd1f7f428f9fe83b1ead5e7cc87a43427be31aadafbac3ac0636dc7abb22"}, + {file = "coverage-7.15.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37e2f0cdf58e2e1fed4e4d5a8f8786ae2f7eb80b478016876667dc4a01d60a97"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fb55d0e70bb15f2e81477613627286581414693d74ac7963c93a790dd453ca9d"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:899b9da30f3c6c336566e3707495bb23e8302d39d862f01fa78c48b99b9437e2"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d15715e8c46552827e5e4f30a35575a2dbcad14454cf3284c54483946bd16931"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:002a438859f7b430bc99afeaf01a6d187dad1d0dc907b64cdeffc632a5db8fd8"}, + {file = "coverage-7.15.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e4193a04b518f7968f3099755f5509ee7cccc6dc2b92a6b14841934d22e222c9"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e98dcc55d572b38e69d117da7e8e8efb8500f1f5eaf81ecd460a63220790b839"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:af6c538498ce66c10d3fd541c2a8d5b03da5850355add34e6cba564210cb9e72"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1d10025d96ea89fc2f73714dbc4cbd433fe012c1ac9e23f895d7728b238b6e52"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d802e1947603162ded419bff83ac7489820355d2b856dfb09206574e3a37ac0c"}, + {file = "coverage-7.15.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2de40895718f91951b86712b4c5b694acaf9a0a49be13874896f599a1eed3f4"}, + {file = "coverage-7.15.4-cp312-cp312-win32.whl", hash = "sha256:5c3431b2161279b7db5c2a1aa58ae02e5cb8c3c42d93a5094be3f5537bd5b11b"}, + {file = "coverage-7.15.4-cp312-cp312-win_amd64.whl", hash = "sha256:6befeab5fb2b51c958ca4ac6c5d141a1e8240f4f76e46350f1911963deda49cd"}, + {file = "coverage-7.15.4-cp312-cp312-win_arm64.whl", hash = "sha256:67bc345491ab55b837277d76f5775d057e8c7f1ac44d890d8c2c82adde258c6f"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c705b28feb2775dc82a25f1d473a370bc37ff93f5177f4e29ce2425f560f6921"}, + {file = "coverage-7.15.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ff205ab5e3ecc670f6a4dd19d9cbf12ede53dd41cfc1e15716ec961ea6d314e"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5172326e861a38b48b48befca15e0f477a26b283337a33a739c8fed229934e36"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:12b59c90084e3234fb11184886bf4a40f4f16a8c8f867be2e087b81f8e8868d4"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349062d66f00b40fa2c1c222438bad25fabf755631b5d82937fe985c8008615c"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4256ced708e598e05209bc1a8ab4074e04a51dba4c62fb45926a229af675ace7"}, + {file = "coverage-7.15.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d80f974b20782d9612c8b4c9beeca867074c7cf4079d1419843fa25a26428b25"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e179f19bfe1d31f8eeeaa12990194d761c4f62f0759661000bca6cd8729f40b"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8bc16bb47b7679670eceff71d78bfb7d6e5b143f6c2cd117487ec7c75e0d4b78"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd685005cd2c4200adfc14cf39a603b9320efab3f18a8f7f156d20c9cc3345f"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:337399ad2c93b3acd2a937627dae8b3e86b66707cd3d3e856347999aadf1ef8d"}, + {file = "coverage-7.15.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:96e257121228ec5cd2bb919276e94ac11074471bc37d68dbae0e8308cce15fff"}, + {file = "coverage-7.15.4-cp313-cp313-win32.whl", hash = "sha256:c65a9e0dfc6143491879da4e13b5e30f8be192055de508d737fb14601edbd22c"}, + {file = "coverage-7.15.4-cp313-cp313-win_amd64.whl", hash = "sha256:2ff8f5e9b8f7a94f0c11c45631eee103dbcb7d63274edd12c56efe1be690b3b4"}, + {file = "coverage-7.15.4-cp313-cp313-win_arm64.whl", hash = "sha256:6e0a8a5083b096487d6cfced94cdd514d8f5db6f113610fb36c0620edb1028cf"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:770e9325ab5ea6d56f77e59b29ecfe0ac20b57a82a601876f90494a4dda0386f"}, + {file = "coverage-7.15.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d12b33a3a50a1676b7784dc8d00a0c6d66a9f2add4b85a041c19b6a7e53ef23c"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5669c8378ebde86f5def7a25d29586631b58acc27ffde04399f678f3dfc6e082"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff97a14362eef486483ed44042ca2027ea257df6ff768e62358ee0c9776925ac"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a325e815318638aed1655d9c06e6d7c2d3d46c09231ce988070428a8762d734"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:474223409d88eb20d2d6a0d37ea60e8647a65a90cc008dc1f0410af5f64f1e0d"}, + {file = "coverage-7.15.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7f2f62ae3cd189dd2e13aece758c57b3eecbd27be070dbd4cbd10936049e5dbf"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:39ece820e29e0a2ba34b3ecb3be83c27e997eed8926f2ba6fe7ce7a0bda5843b"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f21b56dcace11dfe013014201f577dcd592b2a9b72182d930361b47cf6f73f25"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:93a3a0b662abcc10c73a47cbc72cd60f63618d6989fb2d1286e50eacd974f303"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:141fae2cabf5569b782c10afc4c850ce10f618c13f8db54765cba99cc839da1f"}, + {file = "coverage-7.15.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:81294c7e6ab30c5f74c0353b11b2fd6320e72d9bee6ac73b357caa8b916323a5"}, + {file = "coverage-7.15.4-cp314-cp314-win32.whl", hash = "sha256:7bbd7d6418e0dab31a206af5203bd43ae36edb8e7fba1940b055d3e9249290d7"}, + {file = "coverage-7.15.4-cp314-cp314-win_amd64.whl", hash = "sha256:f0204ed122758782970526057093f448051a39db9d810d4e344bb87a3546f425"}, + {file = "coverage-7.15.4-cp314-cp314-win_arm64.whl", hash = "sha256:9e71e7bc71c686a123347ae47a0de33a175e797a85bb57b791492adf4eec8ed8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7c922735321eef3f87c280a3d39afff6b646723a2880b862cda4ac7a093b8aa8"}, + {file = "coverage-7.15.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f41c17c4668a655ce96d090d8d5ffdc24ef64b5a02f9753884d08483e8a4a41a"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46822e9b6ff1c6a72b518c162c44a8f45a61a1d609c51084bf5b16c023c5037b"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3d6f4955b73b5445271379a59e3792b0d978f42d4a01e0cf7a67d9c33a3bb0a5"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3fc9e047706fb4a9abb54f719d3aa643e80e5bb3818182c40aee01ac0f0247ba"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05e491d4f3165d62d4f5c8fd48dfeabf2ae8f42cbbd484319af33ea851b78982"}, + {file = "coverage-7.15.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:226c66e80ec0598d3b9b4874123df167ccca342aca8714f77cac6829688ee09c"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac41cc14bebda0dbfb0628036b7f75706935c95bcc07fefe9a0f93614aa60a57"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:8af623e5cd92080acddd02b38f2f406a2c3a0893c38950b211890361448fbf26"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:07545711d4f0f32852a18f18ad11f76f0109909d09e78b9008b4cfc67e829429"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a0865421cfdc53654b342d515e5a233187590882d20b95752150e53f65460017"}, + {file = "coverage-7.15.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:460115e32ee40566476db5048f9bec1e842c127ad8e6f8be745aad3ac9cbc839"}, + {file = "coverage-7.15.4-cp314-cp314t-win32.whl", hash = "sha256:cbde877ef9dd7baf272b9bfef2b8a25edd45d9170fc326951dd20eb480335e85"}, + {file = "coverage-7.15.4-cp314-cp314t-win_amd64.whl", hash = "sha256:3da9e92d1c551fd7563833e9ade686efb0c4b7363ab7681a94283958c950bf5e"}, + {file = "coverage-7.15.4-cp314-cp314t-win_arm64.whl", hash = "sha256:3a54f5a0d85050c73a38f6793090ee83974531e67fe5e57a1da9bee11398aa5e"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:2c9872e4d9dc5d3cf616bf4b382f5a00359305a5be666a3dd0b5cdb4e49597f9"}, + {file = "coverage-7.15.4-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:e101dbb4b9b72f0cddd8cdc8c9c5b47f456766f5e0ac82dbfb75e5c55409b78a"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7d1abebdb047729e852b9c77a00497dfbeb11eb3a117e037d7dbc3ac8e5f5c54"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d28a4a899354d0ea6214cc59b4fa19eefbce1b9ff1688ab579acf49e894bd3fb"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffb3c2aacea411cc7e1d27712490c11108e2de1d39019ae32915493a59a8b9ed"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9447978a92f405d301123cfd39ff49895490efb769a758fe2734c7f631bf8ce"}, + {file = "coverage-7.15.4-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:050467a7983b8e2fe7dd41a78bb30c3e7f8c0b8cafda14b1c46f8b5e3cf2dd3c"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:d003b7a5708ddad5c206c79607a6b92abb6fc13c57d99d8a4468cc03a2941ced"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c38efe30fd74e5c19e9433f11fb1f5dc9c6522770971b7c6145bbaa413dc8800"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:1f4f826d70f772ab8b0c052329580d7fe8b8abd191e4ce0c8f81aec6614665d3"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:4a4bf917c9953f57c957be31c1cd504e3bd2f34d4a352b9d391a3025336f6768"}, + {file = "coverage-7.15.4-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:1c9bf40ebef178a45192c75c4964760bb261b0e6ad725da5fc4c93f674f19753"}, + {file = "coverage-7.15.4-cp315-cp315-win32.whl", hash = "sha256:43619d04c3671792d2c4706ae8bf45e265dc87bbd4078189ef8b847ea1e74be2"}, + {file = "coverage-7.15.4-cp315-cp315-win_amd64.whl", hash = "sha256:be619439dbcd31a2eab10b32de9fff62c26ed4bab69dc32b8363fdaaa0882809"}, + {file = "coverage-7.15.4-cp315-cp315-win_arm64.whl", hash = "sha256:def597967dafc2e8d97c9097ea453c464e0bb8ed38f193a43070f10dc623bb6d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:c7dbc748ac8a1e3e59a2b28bea47675e6e778081dbbf081bde0d75def2fcbe1d"}, + {file = "coverage-7.15.4-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:2413074a5ecbb61a01a7888fc72db0ca324d13588c5b38bc0dd8564cdcdfea26"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4e6f6f632b7b2f714bf7a1346e8f97b650ee71f3c298aaad42a2ab60f0f07645"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8df457da2249d3c75ca2e5e835d59c725abfe92d27fdff6cd99eed85b51d5e9a"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:050f66a08805acb5b8a23c6d4a517b1ecf82c08e81ed0e4bd727df065e5c6624"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1587fb771d1ccceef708fdde1e5af8c7ed24b486b61d13a321acb7d8145390aa"}, + {file = "coverage-7.15.4-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8b4f1c3a69ca580f3fbd6b2046915f536d7f586874f25c1bb23add2a3c88d50f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:ffb58d7eff5b7f6ecc6fa21d6288ab7f968a212cb67d682c269c09b9eba3b66f"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:d9df165544774574ee004b953023d1bebada1894a80b1052a43d798b0f676e67"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:f9de0a24a4079b53e523b5c5e2c5945ec251ab486652659955187cf255a259bc"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:150089274bdc9f940628552cb92844e0223c987f1902ab8efe9f45a2ec758d88"}, + {file = "coverage-7.15.4-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:a58a94fed5da6997d258e8f7668c1e195fbd04a691d781b7558f1e468f9e68bc"}, + {file = "coverage-7.15.4-cp315-cp315t-win32.whl", hash = "sha256:ebd5a6d8466ff30836572f3ba2cae8a5e8f85029b1c6d5e2ed338dc472a5166a"}, + {file = "coverage-7.15.4-cp315-cp315t-win_amd64.whl", hash = "sha256:288bde2a2d7ab6b6c2d7252fcde8b524387f2d970bdba9658fc6f8bbcaef0f9b"}, + {file = "coverage-7.15.4-cp315-cp315t-win_arm64.whl", hash = "sha256:68be5e1de60ff13c9095bbec0e5a7fa45b33b101752215b91345ea1f61c4a278"}, + {file = "coverage-7.15.4-py3-none-any.whl", hash = "sha256:964730a1e9de9c0cf11be6a1a3c79ce419c34882842abd256086ba4698705e84"}, + {file = "coverage-7.15.4.tar.gz", hash = "sha256:0548198fff07ccf4faf469520bce1c2eceb1ce3e62891921138dec10907f9d00"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] + [[package]] name = "exceptiongroup" version = "1.3.1" @@ -228,6 +365,26 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""} [package.extras] dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytest-cov" +version = "7.1.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678"}, + {file = "pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2"}, +] + +[package.dependencies] +coverage = {version = ">=7.10.6", extras = ["toml"]} +pluggy = ">=1.2" +pytest = ">=7" + +[package.extras] +testing = ["process-tests", "pytest-xdist", "virtualenv"] + [[package]] name = "pyyaml" version = "6.0.3" @@ -361,7 +518,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_full_version <= \"3.11.0a6\"" files = [ {file = "tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30"}, {file = "tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a"}, @@ -428,4 +585,4 @@ files = [ [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "9893921ac7ef024eb5f585d7d500c7e5d14526634eaf2c0a1c353d738ccb41df" +content-hash = "d7feda8954fead7700aa393e9e3205af23162afd29d6582fdabf9cad59d3a1d4" diff --git a/pyproject.toml b/pyproject.toml index 09334edd..011502c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,6 +10,7 @@ python = "^3.10" pyyaml = "^6.0" jinja2 = "^3.1" pytest = "^9.0.3" +pytest-cov = "^7.0" ruff = "^0.16" tgbotspec = "^0.2.4" @@ -22,6 +23,14 @@ addopts = "-v" testpaths = ["api_codegen/tests"] pythonpath = ["."] +[tool.coverage.run] +branch = true +source = ["api_codegen"] +omit = ["api_codegen/tests/*"] + +[tool.coverage.report] +show_missing = true + [tool.ruff] target-version = "py310" line-length = 120 diff --git a/src/Api.cpp b/src/Api.cpp index 1d49444d..2a008a72 100644 --- a/src/Api.cpp +++ b/src/Api.cpp @@ -15,12 +15,17 @@ Api::Api(std::string token, const HttpClient& httpClient, std::string url) , _url(std::move(url)) { } -std::string Api::downloadFile(const std::string& filePath, const std::vector& fields) const { - return _httpClient.makeRequest(_url + "/file/bot" + _token + "/" + filePath, fields); +std::string Api::downloadFile(std::string_view filePath, const std::vector& fields) const { + std::string url; + url.reserve(_url.size() + _token.size() + filePath.size() + 10); + url.append(_url).append("/file/bot").append(_token).append("/").append(filePath); + return _httpClient.makeRequest(url, fields); } -nlohmann::json Api::sendRequest(const std::string& method, const std::vector& fields) const { - const std::string url = _url + "/bot" + _token + "/" + method; +nlohmann::json Api::sendRequest(std::string_view method, const std::vector& fields) const { + std::string url; + url.reserve(_url.size() + _token.size() + method.size() + 6); + url.append(_url).append("/bot").append(_token).append("/").append(method); const std::string body = _httpClient.makeRequest(url, fields); if (body.starts_with("")) { throw TgException("tgbot-cpp received HTML instead of a Telegram Bot API response", diff --git a/src/ApiCodec.cpp b/src/ApiCodec.cpp index 0f96f87a..9111c5fe 100644 --- a/src/ApiCodec.cpp +++ b/src/ApiCodec.cpp @@ -1,30 +1,49 @@ #include "tgbot/ApiCodec.h" +#include + namespace TgBot::ApiRequest { -void appendField(std::vector& fields, const char* name, const std::string& value) { - fields.push_back({ name, value }); +void appendField(std::vector& fields, std::string_view name, std::string_view value) { + fields.push_back({ std::string(name), std::string(value) }); } -void appendField(std::vector& fields, const char* name, const std::shared_ptr& value) { +void appendField(std::vector& fields, std::string_view name, const std::shared_ptr& value) { if (value) { fields.push_back({ - name, - HttpFile { value->data, value->mimeType, value->fileName }, + std::string(name), + HttpFile { value->data, value->mimeType, value->fileName, value->filePath }, }); } } -void appendOptionalField(std::vector& fields, const char* name, const std::string& value) { +void appendOptionalField(std::vector& fields, std::string_view name, std::string_view value) { if (!value.empty()) { appendField(fields, name, value); } } -void appendOptionalField(std::vector& fields, const char* name, const nlohmann::json& value) { +void appendOptionalField(std::vector& fields, std::string_view name, const std::string& value) { + appendOptionalField(fields, name, std::string_view(value)); +} + +void appendOptionalField(std::vector& fields, std::string_view name, const nlohmann::json& value) { if (!value.is_null()) { appendField(fields, name, value.dump()); } } +void appendOptionalField(std::vector& fields, std::string_view, + const std::vector& value) { + for (const auto& attachment : value) { + if (attachment.name.empty()) { + throw std::invalid_argument("Multipart attachment name must not be empty"); + } + if (!attachment.file) { + throw std::invalid_argument("Multipart attachment file must not be null: " + attachment.name); + } + appendField(fields, attachment.name, attachment.file); + } +} + } // namespace TgBot::ApiRequest diff --git a/src/ApiMethods.cpp b/src/ApiMethods.cpp index 18688757..a41755a6 100644 --- a/src/ApiMethods.cpp +++ b/src/ApiMethods.cpp @@ -7,26 +7,28 @@ namespace TgBot { bool Api::addStickerToSet(std::int64_t userId, - const std::string& name, - std::shared_ptr sticker) const { + std::string_view name, + std::shared_ptr sticker, + const std::vector& attachments) const { return ApiResponse::decode(sendRequest( "addStickerToSet", ApiRequest::makeFields( ApiRequest::required("user_id", userId), ApiRequest::required("name", name), - ApiRequest::required("sticker", sticker) + ApiRequest::required("sticker", sticker), + ApiRequest::optional("attachments", attachments) ) ) ); } bool Api::addStickerToSet(const AddStickerToSetArgs& args) const { - return addStickerToSet(args.userId, args.name, args.sticker); + return addStickerToSet(args.userId, args.name, args.sticker, args.attachments); } -bool Api::answerCallbackQuery(const std::string& callbackQueryId, - const std::string& text, +bool Api::answerCallbackQuery(std::string_view callbackQueryId, + std::string_view text, bool showAlert, - const std::string& url, + std::string_view url, std::int32_t cacheTime) const { return ApiResponse::decode(sendRequest( "answerCallbackQuery", @@ -44,7 +46,7 @@ bool Api::answerCallbackQuery(const AnswerCallbackQueryArgs& args) const { return answerCallbackQuery(args.callbackQueryId, args.text, args.showAlert, args.url, args.cacheTime); } -bool Api::answerChatJoinRequestQuery(const std::string& chatJoinRequestQueryId, const std::string& result) const { +bool Api::answerChatJoinRequestQuery(std::string_view chatJoinRequestQueryId, std::string_view result) const { return ApiResponse::decode(sendRequest( "answerChatJoinRequestQuery", ApiRequest::makeFields( @@ -58,7 +60,7 @@ bool Api::answerChatJoinRequestQuery(const AnswerChatJoinRequestQueryArgs& args) return answerChatJoinRequestQuery(args.chatJoinRequestQueryId, args.result); } -std::shared_ptr Api::answerGuestQuery(const std::string& guestQueryId, +std::shared_ptr Api::answerGuestQuery(std::string_view guestQueryId, std::shared_ptr result) const { return ApiResponse::decode>( sendRequest( @@ -74,11 +76,11 @@ std::shared_ptr Api::answerGuestQuery(const AnswerGuestQueryAr return answerGuestQuery(args.guestQueryId, args.result); } -bool Api::answerInlineQuery(const std::string& inlineQueryId, +bool Api::answerInlineQuery(std::string_view inlineQueryId, const std::vector>& results, std::int32_t cacheTime, bool isPersonal, - const std::string& nextOffset, + std::string_view nextOffset, std::shared_ptr button) const { return ApiResponse::decode(sendRequest( "answerInlineQuery", @@ -102,9 +104,9 @@ bool Api::answerInlineQuery(const AnswerInlineQueryArgs& args) const { args.button); } -bool Api::answerPreCheckoutQuery(const std::string& preCheckoutQueryId, +bool Api::answerPreCheckoutQuery(std::string_view preCheckoutQueryId, bool ok, - const std::string& errorMessage) const { + std::string_view errorMessage) const { return ApiResponse::decode( sendRequest( "answerPreCheckoutQuery", @@ -120,10 +122,10 @@ bool Api::answerPreCheckoutQuery(const AnswerPreCheckoutQueryArgs& args) const { return answerPreCheckoutQuery(args.preCheckoutQueryId, args.ok, args.errorMessage); } -bool Api::answerShippingQuery(const std::string& shippingQueryId, +bool Api::answerShippingQuery(std::string_view shippingQueryId, bool ok, const std::vector>& shippingOptions, - const std::string& errorMessage) const { + std::string_view errorMessage) const { return ApiResponse::decode(sendRequest( "answerShippingQuery", ApiRequest::makeFields( @@ -139,7 +141,7 @@ bool Api::answerShippingQuery(const AnswerShippingQueryArgs& args) const { return answerShippingQuery(args.shippingQueryId, args.ok, args.shippingOptions, args.errorMessage); } -std::shared_ptr Api::answerWebAppQuery(const std::string& webAppQueryId, +std::shared_ptr Api::answerWebAppQuery(std::string_view webAppQueryId, std::shared_ptr result) const { return ApiResponse::decode>( sendRequest( @@ -255,7 +257,7 @@ bool Api::closeGeneralForumTopic(const CloseGeneralForumTopicArgs& args) const { return closeGeneralForumTopic(args.chatId); } -bool Api::convertGiftToStars(const std::string& businessConnectionId, const std::string& ownedGiftId) const { +bool Api::convertGiftToStars(std::string_view businessConnectionId, std::string_view ownedGiftId) const { return ApiResponse::decode(sendRequest( "convertGiftToStars", ApiRequest::makeFields( @@ -272,8 +274,8 @@ bool Api::convertGiftToStars(const ConvertGiftToStarsArgs& args) const { std::shared_ptr Api::copyMessage(std::variant chatId, std::variant fromChatId, std::int32_t messageId, - const std::string& caption, - const std::string& parseMode, + std::string_view caption, + std::string_view parseMode, const std::vector>& captionEntities, bool disableNotification, std::shared_ptr replyParameters, @@ -285,7 +287,7 @@ std::shared_ptr Api::copyMessage(std::variant suggestedPostParameters, std::int32_t videoStartTimestamp) const { @@ -372,7 +374,7 @@ std::vector> Api::copyMessages(const CopyMessagesArgs std::shared_ptr Api::createChatInviteLink(std::variant chatId, std::int32_t expireDate, std::int32_t memberLimit, - const std::string& name, + std::string_view name, bool createsJoinRequest) const { return ApiResponse::decode>( sendRequest( @@ -395,7 +397,7 @@ std::shared_ptr Api::createChatSubscriptionInviteLink(std::variant chatId, std::int32_t subscriptionPeriod, std::int32_t subscriptionPrice, - const std::string& name) const { + std::string_view name) const { return ApiResponse::decode>(sendRequest( "createChatSubscriptionInviteLink", ApiRequest::makeFields( @@ -416,9 +418,9 @@ Api::createChatSubscriptionInviteLink(const CreateChatSubscriptionInviteLinkArgs } std::shared_ptr Api::createForumTopic(std::variant chatId, - const std::string& name, + std::string_view name, std::int32_t iconColor, - const std::string& iconCustomEmojiId) const { + std::string_view iconCustomEmojiId) const { return ApiResponse::decode>( sendRequest( "createForumTopic", @@ -435,16 +437,16 @@ std::shared_ptr Api::createForumTopic(const CreateForumTopicArgs& ar return createForumTopic(args.chatId, args.name, args.iconColor, args.iconCustomEmojiId); } -std::string Api::createInvoiceLink(const std::string& title, - const std::string& description, - const std::string& payload, - const std::string& providerToken, - const std::string& currency, +std::string Api::createInvoiceLink(std::string_view title, + std::string_view description, + std::string_view payload, + std::string_view providerToken, + std::string_view currency, const std::vector>& prices, std::int32_t maxTipAmount, const std::vector& suggestedTipAmounts, - const std::string& providerData, - const std::string& photoUrl, + std::string_view providerData, + std::string_view photoUrl, std::int32_t photoSize, std::int32_t photoWidth, std::int32_t photoHeight, @@ -455,7 +457,7 @@ std::string Api::createInvoiceLink(const std::string& title, bool sendPhoneNumberToProvider, bool sendEmailToProvider, bool isFlexible, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::int32_t subscriptionPeriod) const { return ApiResponse::decode(sendRequest( "createInvoiceLink", @@ -512,11 +514,12 @@ std::string Api::createInvoiceLink(const CreateInvoiceLinkArgs& args) const { } bool Api::createNewStickerSet(std::int64_t userId, - const std::string& name, - const std::string& title, + std::string_view name, + std::string_view title, const std::vector>& stickers, Sticker::Type stickerType, - bool needsRepainting) const { + bool needsRepainting, + const std::vector& attachments) const { return ApiResponse::decode( sendRequest( "createNewStickerSet", @@ -526,7 +529,8 @@ bool Api::createNewStickerSet(std::int64_t userId, ApiRequest::required("title", title), ApiRequest::required("stickers", stickers), ApiRequest::optional("sticker_type", stickerType, Sticker::Type::Regular), - ApiRequest::optional("needs_repainting", needsRepainting) + ApiRequest::optional("needs_repainting", needsRepainting), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -537,7 +541,8 @@ bool Api::createNewStickerSet(const CreateNewStickerSetArgs& args) const { args.title, args.stickers, args.stickerType, - args.needsRepainting); + args.needsRepainting, + args.attachments); } bool Api::declineChatJoinRequest(std::variant chatId, std::int64_t userId) const { @@ -556,7 +561,7 @@ bool Api::declineChatJoinRequest(const DeclineChatJoinRequestArgs& args) const { bool Api::declineSuggestedPost(std::variant chatId, std::int32_t messageId, - const std::string& comment) const { + std::string_view comment) const { return ApiResponse::decode( sendRequest( "declineSuggestedPost", @@ -589,7 +594,7 @@ bool Api::deleteAllMessageReactions(const DeleteAllMessageReactionsArgs& args) c return deleteAllMessageReactions(args.chatId, args.actorChatId, args.userId); } -bool Api::deleteBusinessMessages(const std::string& businessConnectionId, +bool Api::deleteBusinessMessages(std::string_view businessConnectionId, const std::vector& messageIds) const { return ApiResponse::decode( sendRequest( @@ -715,7 +720,7 @@ bool Api::deleteMessages(const DeleteMessagesArgs& args) const { return deleteMessages(args.chatId, args.messageIds); } -bool Api::deleteMyCommands(std::shared_ptr scope, const std::string& languageCode) const { +bool Api::deleteMyCommands(std::shared_ptr scope, std::string_view languageCode) const { return ApiResponse::decode( sendRequest( "deleteMyCommands", @@ -730,7 +735,7 @@ bool Api::deleteMyCommands(const DeleteMyCommandsArgs& args) const { return deleteMyCommands(args.scope, args.languageCode); } -bool Api::deleteStickerFromSet(const std::string& sticker) const { +bool Api::deleteStickerFromSet(std::string_view sticker) const { return ApiResponse::decode( sendRequest( "deleteStickerFromSet", @@ -744,7 +749,7 @@ bool Api::deleteStickerFromSet(const DeleteStickerFromSetArgs& args) const { return deleteStickerFromSet(args.sticker); } -bool Api::deleteStickerSet(const std::string& name) const { +bool Api::deleteStickerSet(std::string_view name) const { return ApiResponse::decode( sendRequest( "deleteStickerSet", @@ -758,7 +763,7 @@ bool Api::deleteStickerSet(const DeleteStickerSetArgs& args) const { return deleteStickerSet(args.name); } -bool Api::deleteStory(const std::string& businessConnectionId, std::int32_t storyId) const { +bool Api::deleteStory(std::string_view businessConnectionId, std::int32_t storyId) const { return ApiResponse::decode( sendRequest( "deleteStory", @@ -788,10 +793,10 @@ bool Api::deleteWebhook(const DeleteWebhookArgs& args) const { } std::shared_ptr Api::editChatInviteLink(std::variant chatId, - const std::string& inviteLink, + std::string_view inviteLink, std::int32_t expireDate, std::int32_t memberLimit, - const std::string& name, + std::string_view name, bool createsJoinRequest) const { return ApiResponse::decode>(sendRequest( "editChatInviteLink", @@ -816,8 +821,8 @@ std::shared_ptr Api::editChatInviteLink(const EditChatInviteLink } std::shared_ptr Api::editChatSubscriptionInviteLink(std::variant chatId, - const std::string& inviteLink, - const std::string& name) const { + std::string_view inviteLink, + std::string_view name) const { return ApiResponse::decode>(sendRequest( "editChatSubscriptionInviteLink", ApiRequest::makeFields( @@ -836,9 +841,9 @@ Api::editChatSubscriptionInviteLink(const EditChatSubscriptionInviteLinkArgs& ar bool Api::editEphemeralMessageCaption(std::variant chatId, std::int32_t ephemeralMessageId, std::int64_t receiverUserId, - const std::string& caption, + std::string_view caption, const std::vector>& captionEntities, - const std::string& parseMode, + std::string_view parseMode, std::shared_ptr replyMarkup) const { return ApiResponse::decode( sendRequest( @@ -915,10 +920,10 @@ bool Api::editEphemeralMessageReplyMarkup(const EditEphemeralMessageReplyMarkupA bool Api::editEphemeralMessageText(std::variant chatId, std::int32_t ephemeralMessageId, std::int64_t receiverUserId, - const std::string& text, + std::string_view text, const std::vector>& entities, std::shared_ptr linkPreviewOptions, - const std::string& parseMode, + std::string_view parseMode, std::shared_ptr replyMarkup) const { return ApiResponse::decode(sendRequest( "editEphemeralMessageText", @@ -948,8 +953,8 @@ bool Api::editEphemeralMessageText(const EditEphemeralMessageTextArgs& args) con bool Api::editForumTopic(std::variant chatId, std::int32_t messageThreadId, - const std::string& name, - const std::string& iconCustomEmojiId) const { + std::string_view name, + std::string_view iconCustomEmojiId) const { return ApiResponse::decode( sendRequest( "editForumTopic", @@ -966,7 +971,7 @@ bool Api::editForumTopic(const EditForumTopicArgs& args) const { return editForumTopic(args.chatId, args.messageThreadId, args.name, args.iconCustomEmojiId); } -bool Api::editGeneralForumTopic(std::variant chatId, const std::string& name) const { +bool Api::editGeneralForumTopic(std::variant chatId, std::string_view name) const { return ApiResponse::decode(sendRequest( "editGeneralForumTopic", ApiRequest::makeFields( @@ -982,12 +987,12 @@ bool Api::editGeneralForumTopic(const EditGeneralForumTopicArgs& args) const { std::shared_ptr Api::editMessageCaption(std::variant chatId, std::int32_t messageId, - const std::string& caption, - const std::string& inlineMessageId, + std::string_view caption, + std::string_view inlineMessageId, std::shared_ptr replyMarkup, - const std::string& parseMode, + std::string_view parseMode, const std::vector>& captionEntities, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool showCaptionAboveMedia) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "editMessageCaption", @@ -1018,7 +1023,7 @@ std::shared_ptr Api::editMessageCaption(const EditMessageCaptionArgs& a } std::shared_ptr Api::editMessageChecklist(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr checklist, std::int32_t messageId, std::shared_ptr replyMarkup) const { @@ -1046,12 +1051,12 @@ std::shared_ptr Api::editMessageLiveLocation(double latitude, double longitude, std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId, + std::string_view inlineMessageId, std::shared_ptr replyMarkup, double horizontalAccuracy, std::int32_t heading, std::int32_t proximityAlertRadius, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::int32_t livePeriod) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "editMessageLiveLocation", @@ -1088,9 +1093,10 @@ std::shared_ptr Api::editMessageLiveLocation(const EditMessageLiveLocat std::shared_ptr Api::editMessageMedia(std::shared_ptr media, std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId, + std::string_view inlineMessageId, std::shared_ptr replyMarkup, - const std::string& businessConnectionId) const { + std::string_view businessConnectionId, + const std::vector& attachments) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "editMessageMedia", ApiRequest::makeFields( @@ -1099,7 +1105,8 @@ std::shared_ptr Api::editMessageMedia(std::shared_ptr media ApiRequest::optional("message_id", messageId), ApiRequest::optional("inline_message_id", inlineMessageId), ApiRequest::optional("reply_markup", replyMarkup), - ApiRequest::optional("business_connection_id", businessConnectionId) + ApiRequest::optional("business_connection_id", businessConnectionId), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -1110,14 +1117,15 @@ std::shared_ptr Api::editMessageMedia(const EditMessageMediaArgs& args) args.messageId, args.inlineMessageId, args.replyMarkup, - args.businessConnectionId); + args.businessConnectionId, + args.attachments); } std::shared_ptr Api::editMessageReplyMarkup(std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId, + std::string_view inlineMessageId, std::shared_ptr replyMarkup, - const std::string& businessConnectionId) const { + std::string_view businessConnectionId) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "editMessageReplyMarkup", ApiRequest::makeFields( @@ -1138,17 +1146,19 @@ std::shared_ptr Api::editMessageReplyMarkup(const EditMessageReplyMarku args.businessConnectionId); } -std::shared_ptr Api::editMessageText(const std::string& text, +std::shared_ptr Api::editMessageText(std::string_view text, std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId, - const std::string& parseMode, + std::string_view inlineMessageId, + std::string_view parseMode, std::shared_ptr linkPreviewOptions, std::shared_ptr replyMarkup, const std::vector>& entities, - const std::string& businessConnectionId, - std::shared_ptr richMessage) const { - return ApiResponse::decodeObjectOrTrue(sendRequest( + std::string_view businessConnectionId, + std::shared_ptr richMessage, + const std::vector& attachments) const { + return ApiResponse::decodeObjectOrTrue( + sendRequest( "editMessageText", ApiRequest::makeFields( ApiRequest::optional("text", text), @@ -1160,7 +1170,8 @@ std::shared_ptr Api::editMessageText(const std::string& text, ApiRequest::optional("reply_markup", replyMarkup), ApiRequest::optional("entities", entities), ApiRequest::optional("business_connection_id", businessConnectionId), - ApiRequest::optional("rich_message", richMessage) + ApiRequest::optional("rich_message", richMessage), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -1175,18 +1186,19 @@ std::shared_ptr Api::editMessageText(const EditMessageTextArgs& args) c args.replyMarkup, args.entities, args.businessConnectionId, - args.richMessage); + args.richMessage, + args.attachments); } -std::shared_ptr Api::editStory(const std::string& businessConnectionId, +std::shared_ptr Api::editStory(std::string_view businessConnectionId, std::shared_ptr content, std::int32_t storyId, const std::vector>& areas, - const std::string& caption, + std::string_view caption, const std::vector>& captionEntities, - const std::string& parseMode) const { - return ApiResponse::decode>( - sendRequest( + std::string_view parseMode, + const std::vector& attachments) const { + return ApiResponse::decode>(sendRequest( "editStory", ApiRequest::makeFields( ApiRequest::required("business_connection_id", businessConnectionId), @@ -1195,7 +1207,8 @@ std::shared_ptr Api::editStory(const std::string& businessConnectionId, ApiRequest::optional("areas", areas), ApiRequest::optional("caption", caption), ApiRequest::optional("caption_entities", captionEntities), - ApiRequest::optional("parse_mode", parseMode) + ApiRequest::optional("parse_mode", parseMode), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -1207,11 +1220,12 @@ std::shared_ptr Api::editStory(const EditStoryArgs& args) const { args.areas, args.caption, args.captionEntities, - args.parseMode); + args.parseMode, + args.attachments); } bool Api::editUserStarSubscription(bool isCanceled, - const std::string& telegramPaymentChargeId, + std::string_view telegramPaymentChargeId, std::int64_t userId) const { return ApiResponse::decode(sendRequest( "editUserStarSubscription", @@ -1248,7 +1262,7 @@ std::shared_ptr Api::forwardMessage(std::variant suggestedPostParameters, std::int32_t videoStartTimestamp) const { return ApiResponse::decode>(sendRequest( @@ -1319,7 +1333,7 @@ std::shared_ptr Api::getAvailableGifts() const { ) ); } -std::shared_ptr Api::getBusinessAccountGifts(const std::string& businessConnectionId, +std::shared_ptr Api::getBusinessAccountGifts(std::string_view businessConnectionId, bool excludeFromBlockchain, bool excludeLimitedNonUpgradable, bool excludeLimitedUpgradable, @@ -1328,7 +1342,7 @@ std::shared_ptr Api::getBusinessAccountGifts(const std::string& busi bool excludeUnlimited, bool excludeUnsaved, std::int32_t limit, - const std::string& offset, + std::string_view offset, bool sortByPrice) const { return ApiResponse::decode>( sendRequest( @@ -1363,7 +1377,7 @@ std::shared_ptr Api::getBusinessAccountGifts(const GetBusinessAccoun args.sortByPrice); } -std::shared_ptr Api::getBusinessAccountStarBalance(const std::string& businessConnectionId) const { +std::shared_ptr Api::getBusinessAccountStarBalance(std::string_view businessConnectionId) const { return ApiResponse::decode>( sendRequest( "getBusinessAccountStarBalance", @@ -1378,7 +1392,7 @@ Api::getBusinessAccountStarBalance(const GetBusinessAccountStarBalanceArgs& args return getBusinessAccountStarBalance(args.businessConnectionId); } -std::shared_ptr Api::getBusinessConnection(const std::string& businessConnectionId) const { +std::shared_ptr Api::getBusinessConnection(std::string_view businessConnectionId) const { return ApiResponse::decode>( sendRequest( "getBusinessConnection", @@ -1431,7 +1445,7 @@ std::shared_ptr Api::getChatGifts(std::variant>( sendRequest( @@ -1524,7 +1538,7 @@ std::vector> Api::getCustomEmojiStickers(const GetCusto return getCustomEmojiStickers(args.customEmojiIds); } -std::shared_ptr Api::getFile(const std::string& fileId) const { +std::shared_ptr Api::getFile(std::string_view fileId) const { return ApiResponse::decode>( sendRequest( "getFile", @@ -1549,7 +1563,7 @@ std::vector> Api::getForumTopicIconStickers() const { std::vector> Api::getGameHighScores(std::int64_t userId, std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId) const { + std::string_view inlineMessageId) const { return ApiResponse::decode>>(sendRequest( "getGameHighScores", ApiRequest::makeFields( @@ -1602,7 +1616,7 @@ std::shared_ptr Api::getMe() const { } std::vector> Api::getMyCommands(std::shared_ptr scope, - const std::string& languageCode) const { + std::string_view languageCode) const { return ApiResponse::decode>>( sendRequest( "getMyCommands", @@ -1632,7 +1646,7 @@ Api::getMyDefaultAdministratorRights(const GetMyDefaultAdministratorRightsArgs& return getMyDefaultAdministratorRights(args.forChannels); } -std::shared_ptr Api::getMyDescription(const std::string& languageCode) const { +std::shared_ptr Api::getMyDescription(std::string_view languageCode) const { return ApiResponse::decode>( sendRequest( "getMyDescription", @@ -1646,7 +1660,7 @@ std::shared_ptr Api::getMyDescription(const GetMyDescriptionArgs return getMyDescription(args.languageCode); } -std::shared_ptr Api::getMyName(const std::string& languageCode) const { +std::shared_ptr Api::getMyName(std::string_view languageCode) const { return ApiResponse::decode>( sendRequest( "getMyName", @@ -1660,7 +1674,7 @@ std::shared_ptr Api::getMyName(const GetMyNameArgs& args) const { return getMyName(args.languageCode); } -std::shared_ptr Api::getMyShortDescription(const std::string& languageCode) const { +std::shared_ptr Api::getMyShortDescription(std::string_view languageCode) const { return ApiResponse::decode>( sendRequest( "getMyShortDescription", @@ -1696,7 +1710,7 @@ std::shared_ptr Api::getStarTransactions(const GetStarTransact return getStarTransactions(args.limit, args.offset); } -std::shared_ptr Api::getStickerSet(const std::string& name) const { +std::shared_ptr Api::getStickerSet(std::string_view name) const { return ApiResponse::decode>( sendRequest( "getStickerSet", @@ -1751,7 +1765,7 @@ std::shared_ptr Api::getUserGifts(std::int64_t userId, bool excludeUnique, bool excludeUnlimited, std::int32_t limit, - const std::string& offset, + std::string_view offset, bool sortByPrice) const { return ApiResponse::decode>(sendRequest( "getUserGifts", @@ -1840,9 +1854,9 @@ std::shared_ptr Api::getWebhookInfo() const { bool Api::giftPremiumSubscription(std::int32_t monthCount, std::int32_t starCount, std::int64_t userId, - const std::string& text, + std::string_view text, const std::vector>& textEntities, - const std::string& textParseMode) const { + std::string_view textParseMode) const { return ApiResponse::decode( sendRequest( "giftPremiumSubscription", @@ -1903,7 +1917,7 @@ bool Api::logOut() const { bool Api::pinChatMessage(std::variant chatId, std::int32_t messageId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool disableNotification) const { return ApiResponse::decode( sendRequest( @@ -1922,14 +1936,15 @@ bool Api::pinChatMessage(const PinChatMessageArgs& args) const { } std::shared_ptr Api::postStory(std::int32_t activePeriod, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr content, const std::vector>& areas, - const std::string& caption, + std::string_view caption, const std::vector>& captionEntities, - const std::string& parseMode, + std::string_view parseMode, bool postToChatPage, - bool protectContent) const { + bool protectContent, + const std::vector& attachments) const { return ApiResponse::decode>(sendRequest( "postStory", ApiRequest::makeFields( @@ -1941,7 +1956,8 @@ std::shared_ptr Api::postStory(std::int32_t activePeriod, ApiRequest::optional("caption_entities", captionEntities), ApiRequest::optional("parse_mode", parseMode), ApiRequest::optional("post_to_chat_page", postToChatPage), - ApiRequest::optional("protect_content", protectContent) + ApiRequest::optional("protect_content", protectContent), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -1955,7 +1971,8 @@ std::shared_ptr Api::postStory(const PostStoryArgs& args) const { args.captionEntities, args.parseMode, args.postToChatPage, - args.protectContent); + args.protectContent, + args.attachments); } bool Api::promoteChatMember(std::variant chatId, @@ -2026,7 +2043,7 @@ bool Api::promoteChatMember(const PromoteChatMemberArgs& args) const { } bool Api::readBusinessMessage(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::int32_t messageId) const { return ApiResponse::decode( sendRequest( @@ -2043,7 +2060,7 @@ bool Api::readBusinessMessage(const ReadBusinessMessageArgs& args) const { return readBusinessMessage(args.chatId, args.businessConnectionId, args.messageId); } -bool Api::refundStarPayment(const std::string& telegramPaymentChargeId, std::int64_t userId) const { +bool Api::refundStarPayment(std::string_view telegramPaymentChargeId, std::int64_t userId) const { return ApiResponse::decode(sendRequest( "refundStarPayment", ApiRequest::makeFields( @@ -2057,7 +2074,7 @@ bool Api::refundStarPayment(const RefundStarPaymentArgs& args) const { return refundStarPayment(args.telegramPaymentChargeId, args.userId); } -bool Api::removeBusinessAccountProfilePhoto(const std::string& businessConnectionId, bool isPublic) const { +bool Api::removeBusinessAccountProfilePhoto(std::string_view businessConnectionId, bool isPublic) const { return ApiResponse::decode( sendRequest( "removeBusinessAccountProfilePhoto", @@ -2150,27 +2167,28 @@ std::string Api::replaceManagedBotToken(const ReplaceManagedBotTokenArgs& args) } bool Api::replaceStickerInSet(std::int64_t userId, - const std::string& name, - const std::string& oldSticker, - std::shared_ptr sticker) const { - return ApiResponse::decode( - sendRequest( + std::string_view name, + std::string_view oldSticker, + std::shared_ptr sticker, + const std::vector& attachments) const { + return ApiResponse::decode(sendRequest( "replaceStickerInSet", ApiRequest::makeFields( ApiRequest::required("user_id", userId), ApiRequest::required("name", name), ApiRequest::required("old_sticker", oldSticker), - ApiRequest::required("sticker", sticker) + ApiRequest::required("sticker", sticker), + ApiRequest::optional("attachments", attachments) ) ) ); } bool Api::replaceStickerInSet(const ReplaceStickerInSetArgs& args) const { - return replaceStickerInSet(args.userId, args.name, args.oldSticker, args.sticker); + return replaceStickerInSet(args.userId, args.name, args.oldSticker, args.sticker, args.attachments); } std::shared_ptr Api::repostStory(std::int32_t activePeriod, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::variant fromChatId, std::int32_t fromStoryId, bool postToChatPage, @@ -2224,7 +2242,7 @@ bool Api::restrictChatMember(const RestrictChatMemberArgs& args) const { } std::shared_ptr Api::revokeChatInviteLink(std::variant chatId, - const std::string& inviteLink) const { + std::string_view inviteLink) const { return ApiResponse::decode>( sendRequest( "revokeChatInviteLink", @@ -2291,23 +2309,23 @@ Api::sendAnimation(std::variant chatId, std::int32_t width, std::int32_t height, std::variant, std::string> thumbnail, - const std::string& caption, + std::string_view caption, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, std::int32_t messageThreadId, bool protectContent, bool hasSpoiler, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, bool showCaptionAboveMedia, std::shared_ptr suggestedPostParameters) const { @@ -2369,26 +2387,26 @@ std::shared_ptr Api::sendAnimation(const SendAnimationArgs& args) const std::shared_ptr Api::sendAudio(std::variant chatId, std::variant, std::string> audio, - const std::string& caption, + std::string_view caption, std::int32_t duration, - const std::string& performer, - const std::string& title, + std::string_view performer, + std::string_view title, std::variant, std::string> thumbnail, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -2444,9 +2462,9 @@ std::shared_ptr Api::sendAudio(const SendAudioArgs& args) const { } bool Api::sendChatAction(std::variant chatId, - const std::string& action, + std::string_view action, std::int32_t messageThreadId, - const std::string& businessConnectionId) const { + std::string_view businessConnectionId) const { return ApiResponse::decode( sendRequest( "sendChatAction", @@ -2463,7 +2481,7 @@ bool Api::sendChatAction(const SendChatActionArgs& args) const { return sendChatAction(args.chatId, args.action, args.messageThreadId, args.businessConnectionId); } -bool Api::sendChatJoinRequestWebApp(const std::string& chatJoinRequestQueryId, const std::string& webAppUrl) const { +bool Api::sendChatJoinRequestWebApp(std::string_view chatJoinRequestQueryId, std::string_view webAppUrl) const { return ApiResponse::decode(sendRequest( "sendChatJoinRequestWebApp", ApiRequest::makeFields( @@ -2478,10 +2496,10 @@ bool Api::sendChatJoinRequestWebApp(const SendChatJoinRequestWebAppArgs& args) c } std::shared_ptr Api::sendChecklist(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr checklist, bool disableNotification, - const std::string& messageEffectId, + std::string_view messageEffectId, bool protectContent, std::shared_ptr replyMarkup, std::shared_ptr replyParameters) const { @@ -2512,10 +2530,10 @@ std::shared_ptr Api::sendChecklist(const SendChecklistArgs& args) const } std::shared_ptr Api::sendContact(std::variant chatId, - const std::string& phoneNumber, - const std::string& firstName, - const std::string& lastName, - const std::string& vcard, + std::string_view phoneNumber, + std::string_view firstName, + std::string_view lastName, + std::string_view vcard, bool disableNotification, std::shared_ptr replyParameters, std::variant, @@ -2524,11 +2542,11 @@ std::shared_ptr Api::sendContact(std::variant> replyMarkup, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -2582,13 +2600,13 @@ std::shared_ptr Api::sendDice(std::variant c std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& emoji, + std::string_view emoji, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>( sendRequest( @@ -2628,23 +2646,23 @@ std::shared_ptr Api::sendDice(const SendDiceArgs& args) const { std::shared_ptr Api::sendDocument(std::variant chatId, std::variant, std::string> document, std::variant, std::string> thumbnail, - const std::string& caption, + std::string_view caption, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, bool disableContentTypeDetection, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -2696,15 +2714,15 @@ std::shared_ptr Api::sendDocument(const SendDocumentArgs& args) const { } std::shared_ptr Api::sendGame(std::variant chatId, - const std::string& gameShortName, + std::string_view gameShortName, std::shared_ptr replyParameters, std::shared_ptr replyMarkup, bool disableNotification, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& messageEffectId) const { + std::string_view messageEffectId) const { return ApiResponse::decode>( sendRequest( "sendGame", @@ -2736,12 +2754,12 @@ std::shared_ptr Api::sendGame(const SendGameArgs& args) const { args.messageEffectId); } -bool Api::sendGift(const std::string& giftId, +bool Api::sendGift(std::string_view giftId, std::variant chatId, bool payForUpgrade, - const std::string& text, + std::string_view text, const std::vector>& textEntities, - const std::string& textParseMode, + std::string_view textParseMode, std::int64_t userId) const { return ApiResponse::decode(sendRequest( "sendGift", @@ -2768,14 +2786,14 @@ bool Api::sendGift(const SendGiftArgs& args) const { } std::shared_ptr Api::sendInvoice(std::variant chatId, - const std::string& title, - const std::string& description, - const std::string& payload, - const std::string& providerToken, - const std::string& currency, + std::string_view title, + std::string_view description, + std::string_view payload, + std::string_view providerToken, + std::string_view currency, const std::vector>& prices, - const std::string& providerData, - const std::string& photoUrl, + std::string_view providerData, + std::string_view photoUrl, std::int32_t photoSize, std::int32_t photoWidth, std::int32_t photoHeight, @@ -2792,11 +2810,11 @@ std::shared_ptr Api::sendInvoice(std::variant& suggestedTipAmounts, - const std::string& startParameter, + std::string_view startParameter, bool protectContent, bool allowPaidBroadcast, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( "sendInvoice", @@ -2875,16 +2893,16 @@ Api::sendLivePhoto(std::variant chatId, std::variant, std::string> livePhoto, std::variant, std::string> photo, bool allowPaidBroadcast, - const std::string& businessConnectionId, - const std::string& callbackQueryId, - const std::string& caption, + std::string_view businessConnectionId, + std::string_view callbackQueryId, + std::string_view caption, const std::vector>& captionEntities, std::int64_t directMessagesTopicId, bool disableNotification, bool hasSpoiler, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int32_t messageThreadId, - const std::string& parseMode, + std::string_view parseMode, bool protectContent, std::int64_t receiverUserId, std::variant, @@ -2959,11 +2977,11 @@ std::shared_ptr Api::sendLocation(std::variant suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3025,10 +3043,11 @@ Api::sendMediaGroup(std::variant chatId, std::shared_ptr replyParameters, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, std::int64_t directMessagesTopicId, - const std::string& messageEffectId) const { + std::string_view messageEffectId, + const std::vector& attachments) const { return ApiResponse::decode>>(sendRequest( "sendMediaGroup", ApiRequest::makeFields( @@ -3041,7 +3060,8 @@ Api::sendMediaGroup(std::variant chatId, ApiRequest::optional("business_connection_id", businessConnectionId), ApiRequest::optional("allow_paid_broadcast", allowPaidBroadcast), ApiRequest::optional("direct_messages_topic_id", directMessagesTopicId), - ApiRequest::optional("message_effect_id", messageEffectId) + ApiRequest::optional("message_effect_id", messageEffectId), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -3056,27 +3076,28 @@ std::vector> Api::sendMediaGroup(const SendMediaGroupAr args.businessConnectionId, args.allowPaidBroadcast, args.directMessagesTopicId, - args.messageEffectId); + args.messageEffectId, + args.attachments); } std::shared_ptr Api::sendMessage(std::variant chatId, - const std::string& text, + std::string_view text, std::shared_ptr linkPreviewOptions, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& entities, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3127,8 +3148,8 @@ bool Api::sendMessageDraft(std::variant chatId, std::int32_t draftId, const std::vector>& entities, std::int32_t messageThreadId, - const std::string& parseMode, - const std::string& text) const { + std::string_view parseMode, + std::string_view text) const { return ApiResponse::decode(sendRequest( "sendMessageDraft", ApiRequest::makeFields( @@ -3151,27 +3172,27 @@ bool Api::sendMessageDraft(const SendMessageDraftArgs& args) const { args.text); } -std::shared_ptr -Api::sendPaidMedia(std::variant chatId, - const std::vector>& media, - std::int32_t starCount, - bool allowPaidBroadcast, - const std::string& businessConnectionId, - const std::string& caption, - const std::vector>& captionEntities, - std::int64_t directMessagesTopicId, - bool disableNotification, - std::int32_t messageThreadId, - const std::string& parseMode, - const std::string& payload, - bool protectContent, - std::variant, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr> replyMarkup, - std::shared_ptr replyParameters, - bool showCaptionAboveMedia, - std::shared_ptr suggestedPostParameters) const { +std::shared_ptr Api::sendPaidMedia(std::variant chatId, + const std::vector>& media, + std::int32_t starCount, + bool allowPaidBroadcast, + std::string_view businessConnectionId, + std::string_view caption, + const std::vector>& captionEntities, + std::int64_t directMessagesTopicId, + bool disableNotification, + std::int32_t messageThreadId, + std::string_view parseMode, + std::string_view payload, + bool protectContent, + std::variant, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr> replyMarkup, + std::shared_ptr replyParameters, + bool showCaptionAboveMedia, + std::shared_ptr suggestedPostParameters, + const std::vector& attachments) const { return ApiResponse::decode>(sendRequest( "sendPaidMedia", ApiRequest::makeFields( @@ -3191,7 +3212,8 @@ Api::sendPaidMedia(std::variant chatId, ApiRequest::optional("reply_markup", replyMarkup), ApiRequest::optional("reply_parameters", replyParameters), ApiRequest::optional("show_caption_above_media", showCaptionAboveMedia), - ApiRequest::optional("suggested_post_parameters", suggestedPostParameters) + ApiRequest::optional("suggested_post_parameters", suggestedPostParameters), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -3213,28 +3235,29 @@ std::shared_ptr Api::sendPaidMedia(const SendPaidMediaArgs& args) const args.replyMarkup, args.replyParameters, args.showCaptionAboveMedia, - args.suggestedPostParameters); + args.suggestedPostParameters, + args.attachments); } std::shared_ptr Api::sendPhoto(std::variant chatId, std::variant, std::string> photo, - const std::string& caption, + std::string_view caption, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, std::int32_t messageThreadId, bool protectContent, bool hasSpoiler, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, bool showCaptionAboveMedia, std::shared_ptr suggestedPostParameters) const { @@ -3287,7 +3310,7 @@ std::shared_ptr Api::sendPhoto(const SendPhotoArgs& args) const { } std::shared_ptr Api::sendPoll(std::variant chatId, - const std::string& question, + std::string_view question, const std::vector>& options, bool disableNotification, std::shared_ptr replyParameters, @@ -3296,32 +3319,32 @@ std::shared_ptr Api::sendPoll(std::variant c std::shared_ptr, std::shared_ptr> replyMarkup, bool isAnonymous, - const std::string& type, + std::string_view type, bool allowsMultipleAnswers, - const std::string& explanation, - const std::string& explanationParseMode, + std::string_view explanation, + std::string_view explanationParseMode, const std::vector>& explanationEntities, std::int32_t openPeriod, std::int32_t closeDate, bool isClosed, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowAddingOptions, bool allowPaidBroadcast, bool allowsRevoting, const std::vector& correctOptionIds, const std::vector& countryCodes, - const std::string& description, + std::string_view description, const std::vector>& descriptionEntities, - const std::string& descriptionParseMode, + std::string_view descriptionParseMode, std::shared_ptr explanationMedia, bool hideResultsUntilCloses, std::shared_ptr media, bool membersOnly, - const std::string& messageEffectId, + std::string_view messageEffectId, const std::vector>& questionEntities, - const std::string& questionParseMode, + std::string_view questionParseMode, bool shuffleOptions) const { return ApiResponse::decode>(sendRequest( "sendPoll", @@ -3401,24 +3424,23 @@ std::shared_ptr Api::sendPoll(const SendPollArgs& args) const { args.shuffleOptions); } -std::shared_ptr -Api::sendRichMessage(std::variant chatId, - std::shared_ptr richMessage, - bool allowPaidBroadcast, - const std::string& businessConnectionId, - std::int64_t directMessagesTopicId, - bool disableNotification, - const std::string& messageEffectId, - std::int32_t messageThreadId, - bool protectContent, - std::variant, - std::shared_ptr, - std::shared_ptr, - std::shared_ptr> replyMarkup, - std::shared_ptr replyParameters, - std::shared_ptr suggestedPostParameters) const { - return ApiResponse::decode>( - sendRequest( +std::shared_ptr Api::sendRichMessage(std::variant chatId, + std::shared_ptr richMessage, + bool allowPaidBroadcast, + std::string_view businessConnectionId, + std::int64_t directMessagesTopicId, + bool disableNotification, + std::string_view messageEffectId, + std::int32_t messageThreadId, + bool protectContent, + std::variant, + std::shared_ptr, + std::shared_ptr, + std::shared_ptr> replyMarkup, + std::shared_ptr replyParameters, + std::shared_ptr suggestedPostParameters, + const std::vector& attachments) const { + return ApiResponse::decode>(sendRequest( "sendRichMessage", ApiRequest::makeFields( ApiRequest::required("chat_id", chatId), @@ -3432,7 +3454,8 @@ Api::sendRichMessage(std::variant chatId, ApiRequest::optional("protect_content", protectContent), ApiRequest::optional("reply_markup", replyMarkup), ApiRequest::optional("reply_parameters", replyParameters), - ApiRequest::optional("suggested_post_parameters", suggestedPostParameters) + ApiRequest::optional("suggested_post_parameters", suggestedPostParameters), + ApiRequest::optional("attachments", attachments) ) ) ); } @@ -3449,7 +3472,8 @@ std::shared_ptr Api::sendRichMessage(const SendRichMessageArgs& args) c args.protectContent, args.replyMarkup, args.replyParameters, - args.suggestedPostParameters); + args.suggestedPostParameters, + args.attachments); } bool Api::sendRichMessageDraft(std::variant chatId, @@ -3481,12 +3505,12 @@ std::shared_ptr Api::sendSticker(std::variant suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3532,25 +3556,25 @@ std::shared_ptr Api::sendSticker(const SendStickerArgs& args) const { std::shared_ptr Api::sendVenue(std::variant chatId, double latitude, double longitude, - const std::string& title, - const std::string& address, - const std::string& foursquareId, - const std::string& foursquareType, + std::string_view title, + std::string_view address, + std::string_view foursquareId, + std::string_view foursquareType, bool disableNotification, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& googlePlaceId, - const std::string& googlePlaceType, + std::string_view googlePlaceId, + std::string_view googlePlaceType, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3612,24 +3636,24 @@ std::shared_ptr Api::sendVideo(std::variant std::int32_t width, std::int32_t height, std::variant, std::string> thumbnail, - const std::string& caption, + std::string_view caption, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, std::int32_t messageThreadId, bool protectContent, bool hasSpoiler, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::variant, std::string> cover, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, bool showCaptionAboveMedia, std::int32_t startTimestamp, @@ -3710,11 +3734,11 @@ Api::sendVideoNote(std::variant chatId, std::shared_ptr> replyMarkup, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3763,23 +3787,23 @@ std::shared_ptr Api::sendVideoNote(const SendVideoNoteArgs& args) const std::shared_ptr Api::sendVoice(std::variant chatId, std::variant, std::string> voice, - const std::string& caption, + std::string_view caption, std::int32_t duration, std::shared_ptr replyParameters, std::variant, std::shared_ptr, std::shared_ptr, std::shared_ptr> replyMarkup, - const std::string& parseMode, + std::string_view parseMode, bool disableNotification, const std::vector>& captionEntities, std::int32_t messageThreadId, bool protectContent, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool allowPaidBroadcast, - const std::string& callbackQueryId, + std::string_view callbackQueryId, std::int64_t directMessagesTopicId, - const std::string& messageEffectId, + std::string_view messageEffectId, std::int64_t receiverUserId, std::shared_ptr suggestedPostParameters) const { return ApiResponse::decode>(sendRequest( @@ -3828,7 +3852,7 @@ std::shared_ptr Api::sendVoice(const SendVoiceArgs& args) const { args.suggestedPostParameters); } -bool Api::setBusinessAccountBio(const std::string& businessConnectionId, const std::string& bio) const { +bool Api::setBusinessAccountBio(std::string_view businessConnectionId, std::string_view bio) const { return ApiResponse::decode( sendRequest( "setBusinessAccountBio", @@ -3844,7 +3868,7 @@ bool Api::setBusinessAccountBio(const SetBusinessAccountBioArgs& args) const { } bool Api::setBusinessAccountGiftSettings(std::shared_ptr acceptedGiftTypes, - const std::string& businessConnectionId, + std::string_view businessConnectionId, bool showGiftButton) const { return ApiResponse::decode(sendRequest( "setBusinessAccountGiftSettings", @@ -3860,9 +3884,9 @@ bool Api::setBusinessAccountGiftSettings(const SetBusinessAccountGiftSettingsArg return setBusinessAccountGiftSettings(args.acceptedGiftTypes, args.businessConnectionId, args.showGiftButton); } -bool Api::setBusinessAccountName(const std::string& businessConnectionId, - const std::string& firstName, - const std::string& lastName) const { +bool Api::setBusinessAccountName(std::string_view businessConnectionId, + std::string_view firstName, + std::string_view lastName) const { return ApiResponse::decode( sendRequest( "setBusinessAccountName", @@ -3878,24 +3902,26 @@ bool Api::setBusinessAccountName(const SetBusinessAccountNameArgs& args) const { return setBusinessAccountName(args.businessConnectionId, args.firstName, args.lastName); } -bool Api::setBusinessAccountProfilePhoto(const std::string& businessConnectionId, +bool Api::setBusinessAccountProfilePhoto(std::string_view businessConnectionId, std::shared_ptr photo, - bool isPublic) const { + bool isPublic, + const std::vector& attachments) const { return ApiResponse::decode(sendRequest( "setBusinessAccountProfilePhoto", ApiRequest::makeFields( ApiRequest::required("business_connection_id", businessConnectionId), ApiRequest::required("photo", photo), - ApiRequest::optional("is_public", isPublic) + ApiRequest::optional("is_public", isPublic), + ApiRequest::optional("attachments", attachments) ) ) ); } bool Api::setBusinessAccountProfilePhoto(const SetBusinessAccountProfilePhotoArgs& args) const { - return setBusinessAccountProfilePhoto(args.businessConnectionId, args.photo, args.isPublic); + return setBusinessAccountProfilePhoto(args.businessConnectionId, args.photo, args.isPublic, args.attachments); } -bool Api::setBusinessAccountUsername(const std::string& businessConnectionId, const std::string& username) const { +bool Api::setBusinessAccountUsername(std::string_view businessConnectionId, std::string_view username) const { return ApiResponse::decode( sendRequest( "setBusinessAccountUsername", @@ -3912,7 +3938,7 @@ bool Api::setBusinessAccountUsername(const SetBusinessAccountUsernameArgs& args) bool Api::setChatAdministratorCustomTitle(std::variant chatId, std::int64_t userId, - const std::string& customTitle) const { + std::string_view customTitle) const { return ApiResponse::decode( sendRequest( "setChatAdministratorCustomTitle", @@ -3928,7 +3954,7 @@ bool Api::setChatAdministratorCustomTitle(const SetChatAdministratorCustomTitleA return setChatAdministratorCustomTitle(args.chatId, args.userId, args.customTitle); } -bool Api::setChatDescription(std::variant chatId, const std::string& description) const { +bool Api::setChatDescription(std::variant chatId, std::string_view description) const { return ApiResponse::decode( sendRequest( "setChatDescription", @@ -3945,7 +3971,7 @@ bool Api::setChatDescription(const SetChatDescriptionArgs& args) const { bool Api::setChatMemberTag(std::variant chatId, std::int64_t userId, - const std::string& tag) const { + std::string_view tag) const { return ApiResponse::decode(sendRequest( "setChatMemberTag", ApiRequest::makeFields( @@ -4008,8 +4034,7 @@ bool Api::setChatPhoto(const SetChatPhotoArgs& args) const { return setChatPhoto(args.chatId, args.photo); } -bool Api::setChatStickerSet(std::variant chatId, - const std::string& stickerSetName) const { +bool Api::setChatStickerSet(std::variant chatId, std::string_view stickerSetName) const { return ApiResponse::decode(sendRequest( "setChatStickerSet", ApiRequest::makeFields( @@ -4023,7 +4048,7 @@ bool Api::setChatStickerSet(const SetChatStickerSetArgs& args) const { return setChatStickerSet(args.chatId, args.stickerSetName); } -bool Api::setChatTitle(std::variant chatId, const std::string& title) const { +bool Api::setChatTitle(std::variant chatId, std::string_view title) const { return ApiResponse::decode(sendRequest( "setChatTitle", ApiRequest::makeFields( @@ -4037,7 +4062,7 @@ bool Api::setChatTitle(const SetChatTitleArgs& args) const { return setChatTitle(args.chatId, args.title); } -bool Api::setCustomEmojiStickerSetThumbnail(const std::string& name, const std::string& customEmojiId) const { +bool Api::setCustomEmojiStickerSetThumbnail(std::string_view name, std::string_view customEmojiId) const { return ApiResponse::decode( sendRequest( "setCustomEmojiStickerSetThumbnail", @@ -4058,7 +4083,7 @@ std::shared_ptr Api::setGameScore(std::int64_t userId, bool disableEditMessage, std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId) const { + std::string_view inlineMessageId) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "setGameScore", ApiRequest::makeFields( @@ -4121,7 +4146,7 @@ bool Api::setMessageReaction(const SetMessageReactionArgs& args) const { bool Api::setMyCommands(const std::vector>& commands, std::shared_ptr scope, - const std::string& languageCode) const { + std::string_view languageCode) const { return ApiResponse::decode( sendRequest( "setMyCommands", @@ -4152,7 +4177,7 @@ bool Api::setMyDefaultAdministratorRights(const SetMyDefaultAdministratorRightsA return setMyDefaultAdministratorRights(args.rights, args.forChannels); } -bool Api::setMyDescription(const std::string& description, const std::string& languageCode) const { +bool Api::setMyDescription(std::string_view description, std::string_view languageCode) const { return ApiResponse::decode(sendRequest( "setMyDescription", ApiRequest::makeFields( @@ -4166,7 +4191,7 @@ bool Api::setMyDescription(const SetMyDescriptionArgs& args) const { return setMyDescription(args.description, args.languageCode); } -bool Api::setMyName(const std::string& name, const std::string& languageCode) const { +bool Api::setMyName(std::string_view name, std::string_view languageCode) const { return ApiResponse::decode( sendRequest( "setMyName", @@ -4181,21 +4206,23 @@ bool Api::setMyName(const SetMyNameArgs& args) const { return setMyName(args.name, args.languageCode); } -bool Api::setMyProfilePhoto(std::shared_ptr photo) const { +bool Api::setMyProfilePhoto(std::shared_ptr photo, + const std::vector& attachments) const { return ApiResponse::decode( sendRequest( "setMyProfilePhoto", ApiRequest::makeFields( - ApiRequest::required("photo", photo) + ApiRequest::required("photo", photo), + ApiRequest::optional("attachments", attachments) ) ) ); } bool Api::setMyProfilePhoto(const SetMyProfilePhotoArgs& args) const { - return setMyProfilePhoto(args.photo); + return setMyProfilePhoto(args.photo, args.attachments); } -bool Api::setMyShortDescription(const std::string& shortDescription, const std::string& languageCode) const { +bool Api::setMyShortDescription(std::string_view shortDescription, std::string_view languageCode) const { return ApiResponse::decode(sendRequest( "setMyShortDescription", ApiRequest::makeFields( @@ -4224,7 +4251,7 @@ bool Api::setPassportDataErrors(const SetPassportDataErrorsArgs& args) const { return setPassportDataErrors(args.userId, args.errors); } -bool Api::setStickerEmojiList(const std::string& sticker, const std::vector& emojiList) const { +bool Api::setStickerEmojiList(std::string_view sticker, const std::vector& emojiList) const { return ApiResponse::decode( sendRequest( "setStickerEmojiList", @@ -4239,7 +4266,7 @@ bool Api::setStickerEmojiList(const SetStickerEmojiListArgs& args) const { return setStickerEmojiList(args.sticker, args.emojiList); } -bool Api::setStickerKeywords(const std::string& sticker, const std::vector& keywords) const { +bool Api::setStickerKeywords(std::string_view sticker, const std::vector& keywords) const { return ApiResponse::decode( sendRequest( "setStickerKeywords", @@ -4254,7 +4281,7 @@ bool Api::setStickerKeywords(const SetStickerKeywordsArgs& args) const { return setStickerKeywords(args.sticker, args.keywords); } -bool Api::setStickerMaskPosition(const std::string& sticker, std::shared_ptr maskPosition) const { +bool Api::setStickerMaskPosition(std::string_view sticker, std::shared_ptr maskPosition) const { return ApiResponse::decode(sendRequest( "setStickerMaskPosition", ApiRequest::makeFields( @@ -4268,7 +4295,7 @@ bool Api::setStickerMaskPosition(const SetStickerMaskPositionArgs& args) const { return setStickerMaskPosition(args.sticker, args.maskPosition); } -bool Api::setStickerPositionInSet(const std::string& sticker, std::int32_t position) const { +bool Api::setStickerPositionInSet(std::string_view sticker, std::int32_t position) const { return ApiResponse::decode( sendRequest( "setStickerPositionInSet", @@ -4283,9 +4310,9 @@ bool Api::setStickerPositionInSet(const SetStickerPositionInSetArgs& args) const return setStickerPositionInSet(args.sticker, args.position); } -bool Api::setStickerSetThumbnail(const std::string& name, +bool Api::setStickerSetThumbnail(std::string_view name, std::int64_t userId, - const std::string& format, + std::string_view format, std::variant, std::string> thumbnail) const { return ApiResponse::decode( sendRequest( @@ -4303,7 +4330,7 @@ bool Api::setStickerSetThumbnail(const SetStickerSetThumbnailArgs& args) const { return setStickerSetThumbnail(args.name, args.userId, args.format, args.thumbnail); } -bool Api::setStickerSetTitle(const std::string& name, const std::string& title) const { +bool Api::setStickerSetTitle(std::string_view name, std::string_view title) const { return ApiResponse::decode(sendRequest( "setStickerSetTitle", ApiRequest::makeFields( @@ -4318,7 +4345,7 @@ bool Api::setStickerSetTitle(const SetStickerSetTitleArgs& args) const { } bool Api::setUserEmojiStatus(std::int64_t userId, - const std::string& emojiStatusCustomEmojiId, + std::string_view emojiStatusCustomEmojiId, std::int32_t emojiStatusExpirationDate) const { return ApiResponse::decode(sendRequest( "setUserEmojiStatus", @@ -4334,13 +4361,13 @@ bool Api::setUserEmojiStatus(const SetUserEmojiStatusArgs& args) const { return setUserEmojiStatus(args.userId, args.emojiStatusCustomEmojiId, args.emojiStatusExpirationDate); } -bool Api::setWebhook(const std::string& url, +bool Api::setWebhook(std::string_view url, std::shared_ptr certificate, std::int32_t maxConnections, const std::vector& allowedUpdates, - const std::string& ipAddress, + std::string_view ipAddress, bool dropPendingUpdates, - const std::string& secretToken) const { + std::string_view secretToken) const { return ApiResponse::decode(sendRequest( "setWebhook", ApiRequest::makeFields( @@ -4367,9 +4394,9 @@ bool Api::setWebhook(const SetWebhookArgs& args) const { std::shared_ptr Api::stopMessageLiveLocation(std::variant chatId, std::int32_t messageId, - const std::string& inlineMessageId, + std::string_view inlineMessageId, std::shared_ptr replyMarkup, - const std::string& businessConnectionId) const { + std::string_view businessConnectionId) const { return ApiResponse::decodeObjectOrTrue(sendRequest( "stopMessageLiveLocation", ApiRequest::makeFields( @@ -4392,7 +4419,7 @@ std::shared_ptr Api::stopMessageLiveLocation(const StopMessageLiveLocat std::shared_ptr Api::stopPoll(std::variant chatId, std::int32_t messageId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::shared_ptr replyMarkup) const { return ApiResponse::decode>(sendRequest( "stopPoll", @@ -4409,7 +4436,7 @@ std::shared_ptr Api::stopPoll(const StopPollArgs& args) const { return stopPoll(args.chatId, args.messageId, args.businessConnectionId, args.replyMarkup); } -bool Api::transferBusinessAccountStars(const std::string& businessConnectionId, std::int32_t starCount) const { +bool Api::transferBusinessAccountStars(std::string_view businessConnectionId, std::int32_t starCount) const { return ApiResponse::decode( sendRequest( "transferBusinessAccountStars", @@ -4424,9 +4451,9 @@ bool Api::transferBusinessAccountStars(const TransferBusinessAccountStarsArgs& a return transferBusinessAccountStars(args.businessConnectionId, args.starCount); } -bool Api::transferGift(const std::string& businessConnectionId, +bool Api::transferGift(std::string_view businessConnectionId, std::int64_t newOwnerChatId, - const std::string& ownedGiftId, + std::string_view ownedGiftId, std::int32_t starCount) const { return ApiResponse::decode( sendRequest( @@ -4532,7 +4559,7 @@ bool Api::unpinAllGeneralForumTopicMessages(const UnpinAllGeneralForumTopicMessa } bool Api::unpinChatMessage(std::variant chatId, - const std::string& businessConnectionId, + std::string_view businessConnectionId, std::int32_t messageId) const { return ApiResponse::decode( sendRequest( @@ -4549,8 +4576,8 @@ bool Api::unpinChatMessage(const UnpinChatMessageArgs& args) const { return unpinChatMessage(args.chatId, args.businessConnectionId, args.messageId); } -bool Api::upgradeGift(const std::string& businessConnectionId, - const std::string& ownedGiftId, +bool Api::upgradeGift(std::string_view businessConnectionId, + std::string_view ownedGiftId, bool keepOriginalDetails, std::int32_t starCount) const { return ApiResponse::decode( @@ -4571,7 +4598,7 @@ bool Api::upgradeGift(const UpgradeGiftArgs& args) const { std::shared_ptr Api::uploadStickerFile(std::int64_t userId, std::variant, std::string> sticker, - const std::string& stickerFormat) const { + std::string_view stickerFormat) const { return ApiResponse::decode>(sendRequest( "uploadStickerFile", ApiRequest::makeFields( @@ -4586,7 +4613,7 @@ std::shared_ptr Api::uploadStickerFile(const UploadStickerFileArgs& args) return uploadStickerFile(args.userId, args.sticker, args.stickerFormat); } -bool Api::verifyChat(std::variant chatId, const std::string& customDescription) const { +bool Api::verifyChat(std::variant chatId, std::string_view customDescription) const { return ApiResponse::decode(sendRequest( "verifyChat", ApiRequest::makeFields( @@ -4600,7 +4627,7 @@ bool Api::verifyChat(const VerifyChatArgs& args) const { return verifyChat(args.chatId, args.customDescription); } -bool Api::verifyUser(std::int64_t userId, const std::string& customDescription) const { +bool Api::verifyUser(std::int64_t userId, std::string_view customDescription) const { return ApiResponse::decode(sendRequest( "verifyUser", ApiRequest::makeFields( diff --git a/src/Bot.cpp b/src/Bot.cpp index be7d0da9..985be4c5 100644 --- a/src/Bot.cpp +++ b/src/Bot.cpp @@ -7,9 +7,9 @@ namespace TgBot { -Bot::Bot(std::string token, const HttpClient& httpClient, const std::string& url) +Bot::Bot(std::string token, const HttpClient& httpClient, std::string url) : _token(std::move(token)) - , _api(_token, httpClient, url) + , _api(_token, httpClient, std::move(url)) , _eventBroadcaster(std::make_unique()) , _eventHandler(getEvents()) { } diff --git a/src/CurlHttpClient.cpp b/src/CurlHttpClient.cpp index dcab9b14..8f65508e 100644 --- a/src/CurlHttpClient.cpp +++ b/src/CurlHttpClient.cpp @@ -96,7 +96,11 @@ std::string CurlHttpClient::makeRequest(const std::string& url, std::span(&field.value)) { - curl_mime_data(part, file->data.c_str(), file->data.size()); + if (file->filePath) { + curl_mime_filedata(part, file->filePath->c_str()); + } else { + curl_mime_data(part, file->data.c_str(), file->data.size()); + } curl_mime_type(part, file->mimeType.c_str()); curl_mime_filename(part, file->fileName.c_str()); } else { @@ -126,7 +130,7 @@ std::string CurlHttpClient::makeRequest(const std::string& url, std::span& commandsList, +void EventBroadcaster::onCommand(std::initializer_list commandsList, const MessageListener& listener) { if (listener) { - for (const auto& command : commandsList) { - _onCommandListeners[command] = listener; + for (const std::string_view command : commandsList) { + _onCommandListeners.insert_or_assign(std::string(command), listener); } } else { - for (const auto& command : commandsList) { - _onCommandListeners.erase(command); + for (const std::string_view command : commandsList) { + const auto item = _onCommandListeners.find(command); + if (item != _onCommandListeners.end()) { + _onCommandListeners.erase(item); + } } } } @@ -105,7 +111,7 @@ void EventBroadcaster::broadcastAnyMessage(const std::shared_ptr& messa broadcast>(_onAnyMessageListeners, message); } -bool EventBroadcaster::broadcastCommand(const std::string& command, const std::shared_ptr& message) const { +bool EventBroadcaster::broadcastCommand(std::string_view command, const std::shared_ptr& message) const { const auto iter = _onCommandListeners.find(command); if (iter == _onCommandListeners.end()) { return false; diff --git a/src/EventHandler.cpp b/src/EventHandler.cpp index 436f3f0f..ff12a012 100644 --- a/src/EventHandler.cpp +++ b/src/EventHandler.cpp @@ -2,7 +2,7 @@ #include #include -#include +#include namespace TgBot { @@ -64,23 +64,23 @@ void EventHandler::handleUpdate(const std::shared_ptr& update) const { void EventHandler::handleMessage(const std::shared_ptr& message) const { _broadcaster.broadcastAnyMessage(message); - const std::string text = message->text.value_or(""); + const std::string_view text = message->text ? std::string_view(*message->text) : ""; if (text.starts_with('/')) { std::size_t splitPosition; std::size_t spacePosition = text.find(' '); std::size_t atSymbolPosition = text.find('@'); - if (spacePosition == std::string::npos) { - if (atSymbolPosition == std::string::npos) { + if (spacePosition == std::string_view::npos) { + if (atSymbolPosition == std::string_view::npos) { splitPosition = text.size(); } else { splitPosition = atSymbolPosition; } - } else if (atSymbolPosition == std::string::npos) { + } else if (atSymbolPosition == std::string_view::npos) { splitPosition = spacePosition; } else { splitPosition = std::min(spacePosition, atSymbolPosition); } - std::string command = text.substr(1, splitPosition - 1); + const std::string_view command = text.substr(1, splitPosition - 1); if (!_broadcaster.broadcastCommand(command, message)) { _broadcaster.broadcastUnknownCommand(message); } diff --git a/src/HttpClient.cpp b/src/HttpClient.cpp index 7a51005c..00aaa9f7 100644 --- a/src/HttpClient.cpp +++ b/src/HttpClient.cpp @@ -6,8 +6,8 @@ RequestCancelled::RequestCancelled() : std::runtime_error("request cancelled") { } -RequestCancelled::RequestCancelled(const std::string& request) - : std::runtime_error("request cancelled: " + request) { +RequestCancelled::RequestCancelled(std::string_view request) + : std::runtime_error(std::string("request cancelled: ").append(request)) { } } // namespace TgBot diff --git a/src/InputFile.cpp b/src/InputFile.cpp index d77d115a..0e4d88e7 100644 --- a/src/InputFile.cpp +++ b/src/InputFile.cpp @@ -1,23 +1,28 @@ #include "tgbot/InputFile.h" #include -#include #include -#include #include +#include namespace TgBot { -std::shared_ptr InputFile::fromFile(const std::string& filePath, const std::string& mimeType) { - std::ifstream input(filePath, std::ios::binary); - input.exceptions(std::ifstream::failbit | std::ifstream::badbit); - std::ostringstream contents; - contents << input.rdbuf(); +std::shared_ptr InputFile::fromData(std::string data, std::string mimeType, std::string fileName) { + auto result = std::make_shared(); + result->data = std::move(data); + result->mimeType = std::move(mimeType); + result->fileName = std::move(fileName); + + return result; +} + +std::shared_ptr InputFile::fromFile(std::string_view filePath, std::string mimeType) { + const std::filesystem::path path(filePath); auto result(std::make_shared()); - result->data = contents.str(); - result->mimeType = mimeType; - result->fileName = std::filesystem::path(filePath).filename().string(); + result->mimeType = std::move(mimeType); + result->fileName = path.filename().string(); + result->filePath = path.string(); return result; } diff --git a/src/TgException.cpp b/src/TgException.cpp index e1d81da6..88482e2b 100644 --- a/src/TgException.cpp +++ b/src/TgException.cpp @@ -2,8 +2,8 @@ namespace TgBot { -TgException::TgException(const std::string& description, ErrorCode errorCode) - : runtime_error(description) +TgException::TgException(std::string_view description, ErrorCode errorCode) + : runtime_error(std::string(description)) , errorCode(errorCode) { } diff --git a/src/TgWebhookLocalServer.cpp b/src/TgWebhookLocalServer.cpp index 8347b852..7817fe6f 100644 --- a/src/TgWebhookLocalServer.cpp +++ b/src/TgWebhookLocalServer.cpp @@ -4,9 +4,9 @@ namespace TgBot { -TgWebhookLocalServer::TgWebhookLocalServer(const std::string& unixSocketPath, const std::string& path, +TgWebhookLocalServer::TgWebhookLocalServer(const std::string& unixSocketPath, std::string path, const EventHandler& eventHandler) - : TgWebhookServer(boost::asio::local::stream_protocol::endpoint(unixSocketPath), path, eventHandler) { + : TgWebhookServer(boost::asio::local::stream_protocol::endpoint(unixSocketPath), std::move(path), eventHandler) { } TgWebhookLocalServer::TgWebhookLocalServer(const std::string& unixSocketPath, const Bot& bot) diff --git a/src/TgWebhookTcpServer.cpp b/src/TgWebhookTcpServer.cpp index df146582..6d3ee4fd 100644 --- a/src/TgWebhookTcpServer.cpp +++ b/src/TgWebhookTcpServer.cpp @@ -2,8 +2,8 @@ namespace TgBot { -TgWebhookTcpServer::TgWebhookTcpServer(unsigned short port, const std::string& path, const EventHandler& eventHandler) - : TgWebhookServer(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), path, eventHandler) { +TgWebhookTcpServer::TgWebhookTcpServer(unsigned short port, std::string path, const EventHandler& eventHandler) + : TgWebhookServer(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port), std::move(path), eventHandler) { } TgWebhookTcpServer::TgWebhookTcpServer(unsigned short port, const Bot& bot) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9dca6ae0..08b076e3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -7,9 +7,14 @@ add_executable(TgBot_test tgbot/HttpFormField.cpp tgbot/HttpServer.cpp tgbot/TgLongPoll.cpp + tgbot/TgWebhookServer.cpp tgbot/TypesJson.cpp ) target_link_libraries(TgBot_test PRIVATE TgBot::TgBot GTest::gtest_main) +if(ENABLE_COVERAGE) + target_compile_options(TgBot_test PRIVATE -O0 -g --coverage) + target_link_options(TgBot_test PRIVATE --coverage) +endif() include(GoogleTest) gtest_discover_tests(TgBot_test) diff --git a/tests/tgbot/Api.cpp b/tests/tgbot/Api.cpp index 92bd7f9d..27408734 100644 --- a/tests/tgbot/Api.cpp +++ b/tests/tgbot/Api.cpp @@ -5,7 +5,10 @@ #include "tgbot/TgException.h" #include "tgbot/Types.h" +#include + #include +#include #include #include #include @@ -67,6 +70,21 @@ TEST(Api, PassesCompleteUrlToHttpClient) { EXPECT_EQ(httpClient.requestUrl, "https://api.telegram.org/bottoken/getMe"); } +TEST(Api, DownloadFilePassesCompleteUrlAndFieldsToHttpClient) { + HttpClientMock httpClient; + httpClient.response = "file-data"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + const std::vector fields { { "range", "bytes=0-3" } }; + + const auto contents = api.downloadFile("documents/file.bin", fields); + + EXPECT_EQ(contents, "file-data"); + EXPECT_EQ(httpClient.requestUrl, "https://api.telegram.org/file/bottoken/documents/file.bin"); + ASSERT_EQ(httpClient.requestFields.size(), 1); + EXPECT_EQ(httpClient.requestFields[0].name, "range"); + EXPECT_EQ(std::get(httpClient.requestFields[0].value), "bytes=0-3"); +} + TEST(Api, GetChatAdministratorsPreservesAdministratorRights) { HttpClientMock httpClient; httpClient.response @@ -120,6 +138,25 @@ TEST(Api, SerializesStringVariantAndPresentOptionalArguments) { EXPECT_EQ(std::get(httpClient.requestFields[3].value), "business-id"); } +TEST(Api, SerializesOptionalFileIdStoredInsideVariant) { + HttpClientMock httpClient; + httpClient.response = R"({"ok":true,"result":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"}}})"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + const std::variant, std::string> animation { std::string("animation-id") }; + const std::variant, std::string> thumbnail { std::string("thumbnail-id") }; + + const auto message = api.sendAnimation(std::int64_t { 42 }, animation, 0, 0, 0, thumbnail); + + ASSERT_TRUE(message); + ASSERT_EQ(httpClient.requestFields.size(), 3); + EXPECT_EQ(httpClient.requestFields[0].name, "chat_id"); + EXPECT_EQ(std::get(httpClient.requestFields[0].value), "42"); + EXPECT_EQ(httpClient.requestFields[1].name, "animation"); + EXPECT_EQ(std::get(httpClient.requestFields[1].value), "animation-id"); + EXPECT_EQ(httpClient.requestFields[2].name, "thumbnail"); + EXPECT_EQ(std::get(httpClient.requestFields[2].value), "thumbnail-id"); +} + TEST(Api, SerializesArgumentObject) { HttpClientMock httpClient; httpClient.response = R"({"ok":true,"result":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"}}})"; @@ -230,6 +267,92 @@ TEST(Api, SerializesWebhookFileAndStructuredArguments) { EXPECT_EQ(std::get(httpClient.requestFields[6].value), "secret"); } +TEST(Api, SendMediaGroupUploadsNamedAttachments) { + HttpClientMock httpClient; + httpClient.response = R"({"ok":true,"result":[]})"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + auto media = std::make_shared(); + media->media = "attach://photo"; + auto file = std::make_shared(); + file->data = "photo-data"; + file->mimeType = "image/jpeg"; + file->fileName = "photo.jpg"; + TgBot::SendMediaGroupArgs args; + args.chatId = std::int64_t { 42 }; + args.media = { media }; + args.attachments = { { "photo", file } }; + + EXPECT_TRUE(api.sendMediaGroup(args).empty()); + + ASSERT_EQ(httpClient.requestFields.size(), 3); + EXPECT_EQ(httpClient.requestFields[0].name, "chat_id"); + EXPECT_EQ(httpClient.requestFields[1].name, "media"); + const auto mediaJson = nlohmann::json::parse(std::get(httpClient.requestFields[1].value)); + ASSERT_EQ(mediaJson.size(), 1); + EXPECT_EQ(mediaJson[0].at("type"), "photo"); + EXPECT_EQ(mediaJson[0].at("media"), "attach://photo"); + EXPECT_EQ(httpClient.requestFields[2].name, "photo"); + const auto& uploaded = std::get(httpClient.requestFields[2].value); + EXPECT_EQ(uploaded.data, "photo-data"); + EXPECT_EQ(uploaded.mimeType, "image/jpeg"); + EXPECT_EQ(uploaded.fileName, "photo.jpg"); + + EXPECT_TRUE(api.sendMediaGroup(std::int64_t { 42 }, args.media).empty()); + ASSERT_EQ(httpClient.requestFields.size(), 2); +} + +TEST(Api, EditMessageMediaUploadsNamedAttachment) { + HttpClientMock httpClient; + httpClient.response = R"({"ok":true,"result":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"}}})"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + auto photo = std::make_shared(); + photo->media = "attach://photo"; + auto media = std::make_shared(); + media->value = photo; + auto file = std::make_shared(); + file->data = "photo-data"; + file->mimeType = "image/jpeg"; + file->fileName = "photo.jpg"; + + TgBot::EditMessageMediaArgs args; + args.media = media; + args.chatId = std::int64_t { 42 }; + args.messageId = 7; + args.attachments = { { "photo", file } }; + + ASSERT_NE(api.editMessageMedia(args), nullptr); + + ASSERT_EQ(httpClient.requestFields.size(), 4); + EXPECT_EQ(httpClient.requestFields[0].name, "media"); + EXPECT_EQ(httpClient.requestFields[1].name, "chat_id"); + EXPECT_EQ(httpClient.requestFields[2].name, "message_id"); + EXPECT_EQ(httpClient.requestFields[3].name, "photo"); +} + +TEST(Api, RejectsInvalidNamedAttachments) { + HttpClientMock httpClient; + httpClient.response = R"({"ok":true,"result":[]})"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + auto media = std::make_shared(); + media->media = "attach://photo"; + auto file = std::make_shared(); + TgBot::SendMediaGroupArgs args; + args.chatId = std::int64_t { 42 }; + args.media = { media }; + + args.attachments = { { "", file } }; + EXPECT_THROW(api.sendMediaGroup(args), std::invalid_argument); + + args.attachments = { { "photo", nullptr } }; + EXPECT_THROW(api.sendMediaGroup(args), std::invalid_argument); + + args.attachments = { { "photo", file }, { "photo", file } }; + EXPECT_THROW(api.sendMediaGroup(args), std::invalid_argument); + + args.attachments = { { "chat_id", file } }; + EXPECT_THROW(api.sendMediaGroup(args), std::invalid_argument); +} + TEST(Api, SerializesFileStoredInsideVariant) { HttpClientMock httpClient; httpClient.response = R"({"ok":true,"result":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"}}})"; @@ -250,6 +373,23 @@ TEST(Api, SerializesFileStoredInsideVariant) { EXPECT_EQ(file.data, "audio-data"); EXPECT_EQ(file.mimeType, "audio/mpeg"); EXPECT_EQ(file.fileName, "audio.mp3"); + EXPECT_FALSE(file.filePath); +} + +TEST(Api, SerializesStreamingFilePathInsideVariant) { + HttpClientMock httpClient; + httpClient.response = R"({"ok":true,"result":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"}}})"; + TgBot::Api api("token", httpClient, "https://api.telegram.org"); + const auto document = TgBot::InputFile::fromFile("files/document.pdf", "application/pdf"); + + ASSERT_TRUE(api.sendDocument(std::int64_t { 42 }, document)); + + ASSERT_EQ(httpClient.requestFields.size(), 2); + const auto& file = std::get(httpClient.requestFields[1].value); + EXPECT_TRUE(file.data.empty()); + EXPECT_EQ(file.mimeType, "application/pdf"); + EXPECT_EQ(file.fileName, "document.pdf"); + EXPECT_EQ(file.filePath, "files/document.pdf"); } TEST(Api, LeavesDocumentedWebhookDefaultToTelegram) { diff --git a/tests/tgbot/CurlHttpClient.cpp b/tests/tgbot/CurlHttpClient.cpp index 0db1c3cc..0680e621 100644 --- a/tests/tgbot/CurlHttpClient.cpp +++ b/tests/tgbot/CurlHttpClient.cpp @@ -1,19 +1,50 @@ #include #include "tgbot/CurlHttpClient.h" +#include "tgbot/HttpFormField.h" #include +#include +#include +#include #include +#include #include +#include +#include #include #include +#include #include +#include +#include #include +#include namespace { using Tcp = boost::asio::ip::tcp; +namespace http = boost::beast::http; + +class TemporaryFile { +public: + explicit TemporaryFile(std::string_view contents) + : path(std::filesystem::temp_directory_path() + / ("tgbot-cpp-upload-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()) + + ".bin")) { + std::ofstream output(path, std::ios::binary); + output.exceptions(std::ofstream::failbit | std::ofstream::badbit); + output.write(contents.data(), static_cast(contents.size())); + } + + ~TemporaryFile() { + std::error_code error; + std::filesystem::remove(path, error); + } + + const std::filesystem::path path; +}; void expectCancelledRequest(const bool includePath, const std::string& expectedMessage) { boost::asio::io_context ioContext; @@ -82,6 +113,45 @@ void expectCancelledRequest(const bool includePath, const std::string& expectedM } } +http::request captureMultipartRequest(std::span fields) { + boost::asio::io_context ioContext; + Tcp::acceptor acceptor(ioContext, Tcp::endpoint(Tcp::v4(), 0)); + std::promise> requestPromise; + auto capturedRequest = requestPromise.get_future(); + std::thread serverThread([&] { + try { + Tcp::socket socket(ioContext); + acceptor.accept(socket); + boost::beast::flat_buffer buffer; + http::request request; + http::read(socket, buffer, request); + + http::response response(http::status::ok, 11); + response.body() = "ok"; + response.prepare_payload(); + http::write(socket, response); + requestPromise.set_value(std::move(request)); + } catch (...) { + requestPromise.set_exception(std::current_exception()); + } + }); + + TgBot::CurlHttpClient httpClient; + const auto port = acceptor.local_endpoint().port(); + try { + if (httpClient.makeRequest("http://127.0.0.1:" + std::to_string(port), fields) != "ok") { + throw std::runtime_error("Local HTTP server returned an unexpected response"); + } + } catch (...) { + acceptor.close(); + serverThread.join(); + throw; + } + serverThread.join(); + + return capturedRequest.get(); +} + TEST(CurlHttpClient, CancelWithoutMethodThrowsRequestCancelled) { expectCancelledRequest(false, "request cancelled"); } @@ -90,4 +160,124 @@ TEST(CurlHttpClient, CancelWithMethodThrowsRequestCancelledWithMethodName) { expectCancelledRequest(true, "request cancelled: getUpdates"); } +TEST(CurlHttpClient, EternalCancellationIsObservable) { + TgBot::CurlHttpClient httpClient; + + EXPECT_FALSE(httpClient.isEternalCancelled()); + EXPECT_EQ(httpClient.getRequestMaxRetries(), 3); + EXPECT_EQ(httpClient.getRequestBackoffSeconds(), 1); + + httpClient.cancel(true); + + EXPECT_TRUE(httpClient.isEternalCancelled()); +} + +TEST(CurlHttpClient, SendsBinaryMultipartFileWithMetadata) { + const std::string binaryData("a\0b", 3); + const std::vector fields { + { "chat_id", "42" }, + { "photo", TgBot::HttpFile { binaryData, "application/octet-stream", "photo.bin" } }, + }; + + const auto request = captureMultipartRequest(fields); + EXPECT_EQ(request.method(), http::verb::post); + EXPECT_NE(std::string(request[http::field::content_type]).find("multipart/form-data; boundary="), + std::string::npos); + EXPECT_NE(request.body().find("name=\"chat_id\""), std::string::npos); + EXPECT_NE(request.body().find("name=\"photo\"; filename=\"photo.bin\""), std::string::npos); + EXPECT_NE(request.body().find("Content-Type: application/octet-stream"), std::string::npos); + EXPECT_NE(request.body().find(binaryData), std::string::npos); +} + +TEST(CurlHttpClient, StreamsMultipartFileFromDisk) { + const std::string binaryData("streamed\0data", 13); + const TemporaryFile temporaryFile(binaryData); + const std::vector fields { + { "document", TgBot::HttpFile { "", "application/octet-stream", "document.bin", temporaryFile.path.string() } }, + }; + + const auto request = captureMultipartRequest(fields); + + EXPECT_EQ(request.method(), http::verb::post); + EXPECT_NE(request.body().find("name=\"document\"; filename=\"document.bin\""), std::string::npos); + EXPECT_NE(request.body().find("Content-Type: application/octet-stream"), std::string::npos); + EXPECT_NE(request.body().find(binaryData), std::string::npos); +} + +TEST(CurlHttpClient, SupportsConcurrentMultipartRequests) { + constexpr std::size_t requestCount = 8; + + boost::asio::io_context ioContext; + Tcp::acceptor acceptor(ioContext, Tcp::endpoint(Tcp::v4(), 0)); + std::exception_ptr serverException; + std::thread serverThread([&] { + try { + for (std::size_t index = 0; index < requestCount; ++index) { + Tcp::socket socket(ioContext); + acceptor.accept(socket); + + boost::beast::flat_buffer buffer; + http::request request; + http::read(socket, buffer, request); + + http::response response(http::status::ok, request.version()); + response.body() = request.body(); + response.prepare_payload(); + http::write(socket, response); + } + } catch (...) { + serverException = std::current_exception(); + } + }); + + TgBot::CurlHttpClient httpClient; + const std::string url = "http://127.0.0.1:" + std::to_string(acceptor.local_endpoint().port()); + std::vector responses(requestCount); + std::vector exceptions(requestCount); + std::vector requestThreads; + std::atomic readyCount { 0 }; + std::atomic start { false }; + + requestThreads.reserve(requestCount); + for (std::size_t index = 0; index < requestCount; ++index) { + requestThreads.emplace_back([&, index] { + readyCount.fetch_add(1); + readyCount.notify_one(); + start.wait(false); + + try { + const std::string payload = "payload-" + std::to_string(index); + const std::vector fields { + { "document", + TgBot::HttpFile { payload, "application/octet-stream", + "document-" + std::to_string(index) + ".bin" } }, + }; + responses[index] = httpClient.makeRequest(url + "/request/" + std::to_string(index), fields); + } catch (...) { + exceptions[index] = std::current_exception(); + } + }); + } + + auto ready = readyCount.load(); + while (ready != requestCount) { + readyCount.wait(ready); + ready = readyCount.load(); + } + start.store(true); + start.notify_all(); + + for (auto& thread : requestThreads) { + thread.join(); + } + serverThread.join(); + + ASSERT_EQ(serverException, nullptr); + for (std::size_t index = 0; index < requestCount; ++index) { + EXPECT_EQ(exceptions[index], nullptr); + EXPECT_NE(responses[index].find("filename=\"document-" + std::to_string(index) + ".bin\""), std::string::npos); + EXPECT_NE(responses[index].find("payload-" + std::to_string(index)), std::string::npos); + } +} + } // namespace diff --git a/tests/tgbot/EventHandler.cpp b/tests/tgbot/EventHandler.cpp index 41ab245a..006e93ec 100644 --- a/tests/tgbot/EventHandler.cpp +++ b/tests/tgbot/EventHandler.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace { @@ -48,6 +49,19 @@ TEST(EventHandler, KnownCommandNotifiesAnyAndMatchingCommandListeners) { EXPECT_EQ(nonCommands, 0); } +TEST(EventHandler, CommandWithoutArgumentsIgnoresBotUsername) { + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + int commands = 0; + broadcaster.onCommand("start", [&](const auto&) { + ++commands; + }); + + handler.handleUpdate(messageUpdate("/start@my_bot")); + + EXPECT_EQ(commands, 1); +} + TEST(EventHandler, UnknownCommandNotifiesUnknownCommandListener) { TgBot::EventBroadcaster broadcaster; TgBot::EventHandler handler(broadcaster); @@ -92,3 +106,132 @@ TEST(EventHandler, RemovedCommandListenerIsNotCalled) { EXPECT_EQ(commands, 0); EXPECT_EQ(unknownCommands, 1); } + +TEST(EventHandler, CommandListCanBeRegisteredAndRemoved) { + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + std::vector commands; + broadcaster.onCommand({ "start", "help" }, [&](const auto& message) { + commands.push_back(*message->text); + }); + + handler.handleUpdate(messageUpdate("/start")); + handler.handleUpdate(messageUpdate("/help")); + broadcaster.onCommand({ "start", "help", "missing" }, nullptr); + handler.handleUpdate(messageUpdate("/start")); + + EXPECT_EQ(commands, (std::vector { "/start", "/help" })); +} + +TEST(EventHandler, DispatchesEverySupportedUpdateType) { + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + auto update = std::make_shared(); + update->editedMessage = std::make_shared(); + update->channelPost = std::make_shared(); + update->editedChannelPost = std::make_shared(); + update->inlineQuery = std::make_shared(); + update->chosenInlineResult = std::make_shared(); + update->callbackQuery = std::make_shared(); + update->shippingQuery = std::make_shared(); + update->preCheckoutQuery = std::make_shared(); + update->poll = std::make_shared(); + update->pollAnswer = std::make_shared(); + update->myChatMember = std::make_shared(); + update->chatMember = std::make_shared(); + update->chatJoinRequest = std::make_shared(); + update->messageReaction = std::make_shared(); + update->messageReactionCount = std::make_shared(); + + int anyMessages = 0; + int editedMessages = 0; + int inlineQueries = 0; + int chosenInlineResults = 0; + int callbackQueries = 0; + int shippingQueries = 0; + int preCheckoutQueries = 0; + int polls = 0; + int pollAnswers = 0; + int myChatMembers = 0; + int chatMembers = 0; + int chatJoinRequests = 0; + int messageReactions = 0; + int messageReactionCounts = 0; + broadcaster.onAnyMessage([&](const auto&) { + ++anyMessages; + }); + broadcaster.onEditedMessage([&](const auto&) { + ++editedMessages; + }); + broadcaster.onInlineQuery([&](const auto&) { + ++inlineQueries; + }); + broadcaster.onChosenInlineResult([&](const auto&) { + ++chosenInlineResults; + }); + broadcaster.onCallbackQuery([&](const auto&) { + ++callbackQueries; + }); + broadcaster.onShippingQuery([&](const auto&) { + ++shippingQueries; + }); + broadcaster.onPreCheckoutQuery([&](const auto&) { + ++preCheckoutQueries; + }); + broadcaster.onPoll([&](const auto&) { + ++polls; + }); + broadcaster.onPollAnswer([&](const auto&) { + ++pollAnswers; + }); + broadcaster.onMyChatMember([&](const auto&) { + ++myChatMembers; + }); + broadcaster.onChatMember([&](const auto&) { + ++chatMembers; + }); + broadcaster.onChatJoinRequest([&](const auto&) { + ++chatJoinRequests; + }); + broadcaster.onMessageReaction([&](const auto&) { + ++messageReactions; + }); + broadcaster.onMessageReactionCount([&](const auto&) { + ++messageReactionCounts; + }); + + handler.handleUpdate(update); + + EXPECT_EQ(anyMessages, 1); + EXPECT_EQ(editedMessages, 2); + EXPECT_EQ(inlineQueries, 1); + EXPECT_EQ(chosenInlineResults, 1); + EXPECT_EQ(callbackQueries, 1); + EXPECT_EQ(shippingQueries, 1); + EXPECT_EQ(preCheckoutQueries, 1); + EXPECT_EQ(polls, 1); + EXPECT_EQ(pollAnswers, 1); + EXPECT_EQ(myChatMembers, 1); + EXPECT_EQ(chatMembers, 1); + EXPECT_EQ(chatJoinRequests, 1); + EXPECT_EQ(messageReactions, 1); + EXPECT_EQ(messageReactionCounts, 1); +} + +TEST(EventHandler, SuccessfulPaymentNotifiesListener) { + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + auto update = messageUpdate("payment"); + update->message->successfulPayment = std::make_shared(); + std::shared_ptr receivedMessage; + std::shared_ptr receivedPayment; + broadcaster.onSuccessfulPayment([&](const auto& message, const auto& payment) { + receivedMessage = message; + receivedPayment = payment; + }); + + handler.handleUpdate(update); + + EXPECT_EQ(receivedMessage, update->message); + EXPECT_EQ(receivedPayment, update->message->successfulPayment); +} diff --git a/tests/tgbot/HttpFormField.cpp b/tests/tgbot/HttpFormField.cpp index 64fc4bc6..634177f3 100644 --- a/tests/tgbot/HttpFormField.cpp +++ b/tests/tgbot/HttpFormField.cpp @@ -1,7 +1,9 @@ #include #include "tgbot/HttpFormField.h" +#include "tgbot/InputFile.h" +#include #include #include @@ -23,4 +25,27 @@ TEST(HttpFormField, StoresFileValue) { EXPECT_EQ(file.data, "contents"); EXPECT_EQ(file.mimeType, "image/jpeg"); EXPECT_EQ(file.fileName, "photo.jpg"); + EXPECT_FALSE(file.filePath); +} + +TEST(InputFile, CreatesFromBinaryDataInMemory) { + const std::string data("a\0b", 3); + + const auto file = TgBot::InputFile::fromData(data, "application/octet-stream", "data.bin"); + + EXPECT_EQ(file->data, data); + EXPECT_EQ(file->mimeType, "application/octet-stream"); + EXPECT_EQ(file->fileName, "data.bin"); + EXPECT_FALSE(file->filePath); +} + +TEST(InputFile, CreatesStreamingFileWithoutReadingContents) { + const std::filesystem::path path = std::filesystem::path("files") / "data.bin"; + + const auto file = TgBot::InputFile::fromFile(path.string(), "application/octet-stream"); + + EXPECT_TRUE(file->data.empty()); + EXPECT_EQ(file->mimeType, "application/octet-stream"); + EXPECT_EQ(file->fileName, "data.bin"); + EXPECT_EQ(file->filePath, path.string()); } diff --git a/tests/tgbot/TgLongPoll.cpp b/tests/tgbot/TgLongPoll.cpp index 1541e6e7..b16c2609 100644 --- a/tests/tgbot/TgLongPoll.cpp +++ b/tests/tgbot/TgLongPoll.cpp @@ -4,13 +4,17 @@ #include "tgbot/HttpClient.h" #include "tgbot/TgLongPoll.h" +#include #include #include #include +#include #include #include #include #include +#include +#include namespace { @@ -38,8 +42,53 @@ class FailingHttpClient final : public TgBot::HttpClient { } }; +class SequencedHttpClient final : public TgBot::HttpClient { +public: + std::string makeRequest(const std::string&, std::span fields) const override { + requests.emplace_back(fields.begin(), fields.end()); + if (nextResponse == responses.size()) { + throw std::logic_error("unexpected request"); + } + return responses[nextResponse++]; + } + + mutable std::size_t nextResponse = 0; + mutable std::vector> requests; + std::vector responses; +}; + } // namespace +TEST(TgLongPoll, StartDispatchesPreviousUpdatesAndAdvancesOffset) { + SequencedHttpClient httpClient; + httpClient.responses = { + R"({"ok":true,"result":[{"update_id":7,"message":{"message_id":1,"date":2,"chat":{"id":3,"type":"private"},"text":"hello"}}]})", + R"({"ok":true,"result":[]})", + }; + TgBot::Bot bot("token", httpClient, "url"); + std::shared_ptr receivedMessage; + bot.getEvents().onAnyMessage([&](const auto& message) { + receivedMessage = message; + }); + auto allowedUpdates = std::make_shared>(std::initializer_list { "message" }); + TgBot::TgLongPoll longPoll(bot, 25, 0, allowedUpdates); + + longPoll.start(); + EXPECT_EQ(receivedMessage, nullptr); + longPoll.start(); + + ASSERT_TRUE(receivedMessage); + ASSERT_TRUE(receivedMessage->text); + EXPECT_EQ(*receivedMessage->text, "hello"); + ASSERT_EQ(httpClient.requests.size(), 2); + const auto offset + = std::find_if(httpClient.requests[1].begin(), httpClient.requests[1].end(), [](const auto& field) { + return field.name == "offset"; + }); + ASSERT_NE(offset, httpClient.requests[1].end()); + EXPECT_EQ(std::get(offset->value), "8"); +} + TEST(TgLongPoll, StopCancelsActiveRequest) { BlockingHttpClient httpClient; TgBot::Bot bot("token", httpClient, "url"); @@ -64,6 +113,28 @@ TEST(TgLongPoll, StopCancelsActiveRequest) { EXPECT_EQ(exception, nullptr); } +TEST(TgLongPoll, StartLoopRejectsConcurrentInvocation) { + BlockingHttpClient httpClient; + TgBot::Bot bot("token", httpClient, "url"); + TgBot::TgLongPoll longPoll(bot); + std::exception_ptr exception; + std::thread thread([&] { + try { + longPoll.startLoop({ }, { }); + } catch (...) { + exception = std::current_exception(); + } + }); + + httpClient.requestStarted.wait(false); + + EXPECT_THROW(longPoll.startLoop({ }, { }), std::logic_error); + + longPoll.stop(); + thread.join(); + EXPECT_EQ(exception, nullptr); +} + TEST(TgLongPoll, StopReportsCancellationToErrorHandler) { BlockingHttpClient httpClient; TgBot::Bot bot("token", httpClient, "url"); diff --git a/tests/tgbot/TgWebhookServer.cpp b/tests/tgbot/TgWebhookServer.cpp new file mode 100644 index 00000000..adfc2f12 --- /dev/null +++ b/tests/tgbot/TgWebhookServer.cpp @@ -0,0 +1,270 @@ +#include + +#include "tgbot/Bot.h" +#include "tgbot/EventBroadcaster.h" +#include "tgbot/EventHandler.h" +#include "tgbot/HttpClient.h" +#include "tgbot/TgWebhookLocalServer.h" +#include "tgbot/TgWebhookTcpServer.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using Tcp = boost::asio::ip::tcp; + +constexpr std::string_view defaultUpdateBody + = R"({"update_id":1,"message":{"message_id":2,"date":0,"chat":{"id":3,"type":"private"},"text":"hello"}})"; + +class HttpClientStub final : public TgBot::HttpClient { +public: + std::string makeRequest(const std::string&, std::span) const override { + return { }; + } +}; + +std::uint16_t findAvailablePort() { + boost::asio::io_context ioContext; + Tcp::acceptor acceptor(ioContext, Tcp::endpoint(Tcp::v4(), 0)); + + return acceptor.local_endpoint().port(); +} + +template +void sendUpdate(boost::asio::basic_stream_socket& socket, std::string_view path, + std::string_view body = defaultUpdateBody) { + namespace http = boost::beast::http; + + http::request request(http::verb::post, path, 11); + request.set(http::field::host, "localhost"); + request.body() = body; + request.prepare_payload(); + http::write(socket, request); + + boost::beast::flat_buffer buffer; + http::response response; + http::read(socket, buffer, response); + EXPECT_EQ(response.result(), http::status::ok); +} + +void sendTcpUpdate(std::uint16_t port, std::string_view path, std::string_view body = defaultUpdateBody) { + boost::asio::io_context ioContext; + Tcp::socket socket(ioContext); + socket.connect(Tcp::endpoint(boost::asio::ip::address_v4::loopback(), port)); + sendUpdate(socket, path, body); +} + +TEST(TgWebhookTcpServer, DispatchesOnlyUpdatesSentToConfiguredPath) { + const std::uint16_t port = findAvailablePort(); + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + TgBot::TgWebhookTcpServer server(port, "/webhook", handler); + int messages = 0; + broadcaster.onAnyMessage([&](const auto&) { + ++messages; + server.stop(); + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + sendTcpUpdate(port, "/wrong"); + EXPECT_EQ(messages, 0); + sendTcpUpdate(port, "/webhook"); + serverThread.join(); + + EXPECT_EQ(messages, 1); +} + +TEST(TgWebhookTcpServer, UsesBotTokenAsDefaultPath) { + const std::uint16_t port = findAvailablePort(); + HttpClientStub httpClient; + TgBot::Bot bot("token", httpClient); + TgBot::TgWebhookTcpServer server(port, bot); + int messages = 0; + bot.getEvents().onAnyMessage([&](const auto&) { + ++messages; + server.stop(); + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + sendTcpUpdate(port, "/token"); + serverThread.join(); + + EXPECT_EQ(messages, 1); +} + +TEST(TgWebhookTcpServer, KeepsFirstMessageAliveWhileNextListenerRuns) { + const std::uint16_t port = findAvailablePort(); + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + TgBot::TgWebhookTcpServer server(port, "/webhook", handler); + std::promise firstMessagePromise; + std::promise secondMessagePromise; + auto firstMessageFuture = firstMessagePromise.get_future(); + auto secondMessageFuture = secondMessagePromise.get_future(); + std::atomic secondListenerStarted { false }; + std::atomic releaseSecondListener { false }; + broadcaster.onAnyMessage([&](const auto& message) { + if (*message->text == "first") { + firstMessagePromise.set_value(message); + return; + } + + secondMessagePromise.set_value(message); + secondListenerStarted.store(true); + secondListenerStarted.notify_one(); + releaseSecondListener.wait(false); + server.stop(); + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + sendTcpUpdate( + port, "/webhook", + R"({"update_id":1,"message":{"message_id":1,"date":0,"chat":{"id":3,"type":"private"},"text":"first"}})"); + auto firstMessage = firstMessageFuture.get(); + std::thread secondRequestThread([&] { + sendTcpUpdate( + port, "/webhook", + R"({"update_id":2,"message":{"message_id":2,"date":0,"chat":{"id":3,"type":"private"},"text":"second"}})"); + }); + + secondListenerStarted.wait(false); + auto secondMessage = secondMessageFuture.get(); + EXPECT_EQ(*firstMessage->text, "first"); + EXPECT_EQ(firstMessage->messageId, 1); + EXPECT_EQ(*secondMessage->text, "second"); + EXPECT_EQ(secondMessage->messageId, 2); + + releaseSecondListener.store(true); + releaseSecondListener.notify_one(); + secondRequestThread.join(); + serverThread.join(); +} + +TEST(TgWebhookTcpServer, ReleasesMessageAfterRequestCompletes) { + const std::uint16_t port = findAvailablePort(); + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + TgBot::TgWebhookTcpServer server(port, "/webhook", handler); + std::weak_ptr receivedMessage; + broadcaster.onAnyMessage([&](const auto& message) { + receivedMessage = message; + server.stop(); + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + sendTcpUpdate(port, "/webhook"); + serverThread.join(); + + EXPECT_TRUE(receivedMessage.expired()); +} + +TEST(TgWebhookTcpServer, KeepsConcurrentUpdatesIndependent) { + constexpr std::size_t updateCount = 8; + + const std::uint16_t port = findAvailablePort(); + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + TgBot::TgWebhookTcpServer server(port, "/webhook", handler); + std::mutex messagesMutex; + std::vector messages; + broadcaster.onAnyMessage([&](const auto& message) { + bool receivedAllUpdates; + { + const std::lock_guard lock(messagesMutex); + messages.push_back(message); + receivedAllUpdates = messages.size() == updateCount; + } + if (receivedAllUpdates) { + server.stop(); + } + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + std::vector requestThreads; + requestThreads.reserve(updateCount); + for (std::size_t index = 0; index < updateCount; ++index) { + requestThreads.emplace_back([&, index] { + const std::string body = "{\"update_id\":" + std::to_string(index + 1) + ",\"message\":{\"message_id\":" + + std::to_string(index + 1) + ",\"date\":0,\"chat\":{\"id\":3,\"type\":\"private\"},\"text\":\"message-" + + std::to_string(index + 1) + "\"}}"; + sendTcpUpdate(port, "/webhook", body); + }); + } + + for (auto& thread : requestThreads) { + thread.join(); + } + serverThread.join(); + + std::ranges::sort(messages, { }, &TgBot::Message::messageId); + ASSERT_EQ(messages.size(), updateCount); + std::vector> weakMessages; + weakMessages.reserve(updateCount); + for (std::size_t index = 0; index < updateCount; ++index) { + EXPECT_EQ(messages[index]->messageId, index + 1); + EXPECT_EQ(*messages[index]->text, "message-" + std::to_string(index + 1)); + weakMessages.push_back(messages[index]); + } + + messages.clear(); + + for (const auto& message : weakMessages) { + EXPECT_TRUE(message.expired()); + } +} + +#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS) && !defined(_WIN32) + +TEST(TgWebhookLocalServer, DispatchesUpdateFromUnixSocket) { + TgBot::EventBroadcaster broadcaster; + TgBot::EventHandler handler(broadcaster); + const std::filesystem::path socketPath = std::filesystem::temp_directory_path() + / ("tgbot-cpp-webhook-" + std::to_string(reinterpret_cast(&broadcaster)) + ".sock"); + TgBot::TgWebhookLocalServer server(socketPath.string(), "/webhook", handler); + int messages = 0; + broadcaster.onAnyMessage([&](const auto&) { + ++messages; + server.stop(); + }); + std::thread serverThread([&] { + server.start({ }, { }); + }); + + boost::asio::io_context ioContext; + boost::asio::local::stream_protocol::socket socket(ioContext); + socket.connect(boost::asio::local::stream_protocol::endpoint(socketPath.string())); + sendUpdate(socket, "/webhook"); + serverThread.join(); + std::filesystem::remove(socketPath); + + EXPECT_EQ(messages, 1); +} + +#endif + +} // namespace