Telegram Bot APi server initial commit.

This commit is contained in:
levlam 2020-11-03 19:34:10 +03:00
commit 74559bab15
26 changed files with 13931 additions and 0 deletions

122
.clang-format Normal file
View File

@ -0,0 +1,122 @@
---
Language: Cpp
# BasedOnStyle: Google
AccessModifierOffset: -1
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: false
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None # All
AllowShortIfStatementsOnASingleLine: Never # WithoutElse
AllowShortLambdasOnASingleLine: Inline # All
AllowShortLoopsOnASingleLine: false # true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
SplitEmptyNamespace: true
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: true # false
BreakInheritanceList: BeforeComma # BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: true # false
BreakConstructorInitializers: BeforeComma # BeforeColon
# BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 120 # 80
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: true
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
- Q_FOREACH_THIS_LIST_MUST_BE_NON_EMPTY
IncludeBlocks: Preserve
#IndentCaseBlocks: false
IndentCaseLabels: true
IndentGotoLabels: true
IndentPPDirectives: None
IndentWidth: 2
IndentWrappedFunctionNames: false
# InsertTrailingCommas: None
# JavaScriptQuotes: Leave
# JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
# ObjCBinPackProtocolList: Never
# ObjCBlockIndentWidth: 2
# ObjCBreakBeforeNestedBlockParam: true
# ObjCSpaceAfterProperty: false
# ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: false # true
SortIncludes: false # disabled, because we need case insensitive sort
SortUsingDeclarations: false # true
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 100 # 8
UseCRLF: false
UseTab: Never
...

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
* text=auto
*.cpp text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.h text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.md text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.txt text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent
*.html text whitespace=blank-at-eol,space-before-tab,blank-at-eof,tab-in-indent

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
**/*build*/
**/.*.swp
**/.DS_Store
bin/
vcpkg/

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "td"]
path = td
url = http://github.com/tdlib/td.git

80
CMakeLists.txt Normal file
View File

@ -0,0 +1,80 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
project(TelegramBotApi VERSION 5.0 LANGUAGES CXX)
add_subdirectory(td EXCLUDE_FROM_ALL)
if (NOT DEFINED CMAKE_MODULE_PATH)
set(CMAKE_MODULE_PATH "")
endif()
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/td/CMake" "${CMAKE_MODULE_PATH}")
if (NOT DEFINED CMAKE_INSTALL_BINDIR)
set(CMAKE_INSTALL_BINDIR "bin")
endif()
if (POLICY CMP0054)
# do not expand quoted arguments
cmake_policy(SET CMP0054 NEW)
endif()
if (POLICY CMP0060)
# link libraries by full path
cmake_policy(SET CMP0060 NEW)
endif()
include(PreventInSourceBuild)
prevent_in_source_build()
set(CMAKE_THREAD_PREFER_PTHREAD ON)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
if (THREADS_HAVE_PTHREAD_ARG)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
include(TdSetUpCompiler)
td_set_up_compiler()
if (CLANG OR GCC)
if (MEMPROF)
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag(-no-pie CXX_NO_PIE_FLAG)
if (CXX_NO_PIE_FLAG)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -no-pie")
elseif (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-no_pie")
endif()
endif()
endif()
set(TG_HTTP_CLIENT_SOURCE
telegram-bot-api/telegram-bot-api.cpp
telegram-bot-api/Client.cpp
telegram-bot-api/ClientManager.cpp
telegram-bot-api/HttpConnection.cpp
telegram-bot-api/HttpStatConnection.cpp
telegram-bot-api/Query.cpp
telegram-bot-api/Stats.cpp
telegram-bot-api/WebhookActor.cpp
telegram-bot-api/Client.h
telegram-bot-api/ClientManager.h
telegram-bot-api/ClientParameters.h
telegram-bot-api/HttpConnection.h
telegram-bot-api/HttpServer.h
telegram-bot-api/HttpStatConnection.h
telegram-bot-api/Query.h
telegram-bot-api/Stats.h
telegram-bot-api/WebhookActor.h
)
add_executable(telegram-bot-api ${TG_HTTP_CLIENT_SOURCE})
target_include_directories(telegram-bot-api PRIVATE $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_link_libraries(telegram-bot-api PRIVATE memprof tdactor tdcore tddb tdnet tdutils)
install(TARGETS telegram-bot-api RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
if (MSVC AND VCPKG_TOOLCHAIN)
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/" DESTINATION "${CMAKE_INSTALL_BINDIR}" FILES_MATCHING PATTERN "*.dll")
endif()

23
LICENSE_1_0.txt Normal file
View File

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

90
README.md Normal file
View File

@ -0,0 +1,90 @@
# Telegram Bot API
The Telegram Bot API provides an HTTP API for creating [Telegram Bots](https://core.telegram.org/bots).
If you've got any questions about bots or would like to report an issue with your bot, kindly contact us at [@BotSupport](https://t.me/BotSupport) in Telegram.
Please note that only global Bot API issues that affect all bots are suitable for this repository.
## Table of Contents
- [Installation](#installation)
- [Dependencies](#dependencies)
- [Usage](#usage)
- [Documentation](#documentation)
- [Moving a bot to a local server](#switching)
- [Moving a bot from one local server to another](#moving)
- [License](#license)
<a name="installation"></a>
## Installation
The simplest way to build and install `Telegram Bot API server` is to use our [Telegram Bot API server build instructions generator](https://tdlib.github.io/telegram-bot-api/build.html).
If you do that, you'll only need to choose the target operating system to receive the complete build instructions.
In general, you need to install all `Telegram Bot API server` [dependencies](#dependencies) and compile the source code using CMake:
```
git clone --recursive https://github.com/tdlib/telegram-bot-api.git
cd telegram-bot-api
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build . --target install
```
<a name="dependencies"></a>
## Dependencies
To build and run `Telegram Bot API server` you will need:
* OpenSSL
* zlib
* C++14 compatible compiler (e.g., Clang 3.4+, GCC 4.9+, MSVC 19.0+ (Visual Studio 2015+), Intel C++ Compiler 17+) (build only)
* gperf (build only)
* CMake (3.0.2+, build only)
<a name="usage"></a>
## Usage
Use `telegram-bot-api --help` to receive the list of all available options of the Telegram Bot API server.
The only mandatory options are `--api-id` and `--api-hash`. You must obtain your own `api_id` and `api_hash`
as described in https://core.telegram.org/api/obtaining_api_id and specify them using the `--api-id` and `--api-hash` options
or the `TELEGRAM_API_ID` and `TELEGRAM_API_HASH` environment variables.
To enable Bot API features not available at `https://api.telegram.org`, such as downloading files without a size limit, specify the option `--local`.
The Telegram Bot API server accepts only HTTP requests, so a TLS termination proxy needs to be used to handle remote HTTPS requests.
By default the Telegram Bot API server is launched on the port 8081, which can be changed using the option `--http-port`.
<a name="documentation"></a>
## Documentation
See [Bots: An introduction for developers](https://core.telegram.org/bots) for a brief description of Telegram Bots and their features.
See the [Telegram Bot API documentation](https://core.telegram.org/bots/api) for a description of the Bot API interface and a complete list of available classes, methods and updates.
See the [Telegram Bot API server build instructions generator](https://tdlib.github.io/telegram-bot-api/build.html) for detailed instructions on how to build the Telegram Bot API server.
Subscribe to [@BotNews](https://t.me/botnews) to be the first to know about the latest updates and join the discussion in [@BotTalk](https://t.me/bottalk).
<a name="switching"></a>
## Moving a bot to a local server
To guarantee that your bot will receive all updates, you must deregister it with the `https://api.telegram.org` server by calling the method [logOut](https://core.telegram.org/bots/api#logout).
After the bot is logged out, you can replace the address to which the bot sends requests with the address of your local server and use it in the usual way.
If the server is launched in `--local` mode, make sure that the bot can correctly handle absolute file paths in response to `getFile` requests.
<a name="moving"></a>
## Moving a bot from one local server to another
If the bot is logged in on more than one server simultaneously, there is no guarantee that it will receive all updates.
To move a bot from one local server to another you can use the method [logOut](https://core.telegram.org/bots/api#logout) to log out on the old server before switching to the new one.
If you want to avoid losing updates between logging out on the old server and launching on the new server, you can remove the bot's webhook using the method
[deleteWebhook](https://core.telegram.org/bots/api#deletewebhook), then use the method [close](https://core.telegram.org/bots/api#close) to close the bot instance.
After the instance is closed, locate the bot's subdirectory in the working directory of the old server by the bot's user ID, move the subdirectory to the working directory of the new server
and continue sending requests to the new server as usual.
<a name="license"></a>
## License
`Telegram Bot API server` source code is licensed under the terms of the Boost Software License. See [LICENSE_1_0.txt](http://www.boost.org/LICENSE_1_0.txt) for more information.

494
build.html Normal file
View File

@ -0,0 +1,494 @@
<!DOCTYPE html>
<html>
<head>
<title>Telegram Bot API server build instructions</title>
<style>
.hide { display: none; }
div.main { max-width:1200px; margin: auto; font-size: x-large; }
select.large { font-size: large; }
</style>
</head>
<body onload="onLoad(true)" onpopstate="onLoad(false)">
<div class="main">
<div id="osSelectDiv" class="large" style="text-align:center;">
<p>Choose an operating system, on which you want to use the Telegram Bot API server:</p>
<select id="osSelect" onchange="onOsChanged(false)" autofocus class="large">
<option>Choose an operating system:</option>
<option>Windows</option>
<option>Linux</option>
<option>macOS</option>
<option>FreeBSD</option>
<option>OpenBSD</option>
<option>NetBSD</option>
</select>
<p></p>
</div>
<div id="linuxSelectDiv" class="hide" style="text-align:center;">
<p>Choose a Linux distro, on which you want to use the Telegram Bot API server:</p>
<select id="linuxSelect" onchange="onOsChanged(false)" class="large">
<option>Choose a Linux distro:</option>
<option>Alpine</option>
<option>Debian 8</option>
<option>Debian 9</option>
<option>Debian 10</option>
<option>Ubuntu 14</option>
<option>Ubuntu 16</option>
<option>Ubuntu 18</option>
<option>Ubuntu 20</option>
<option>Other</option>
</select>
<p></p>
</div>
<div id="buildOptionsDiv" class="hide" style="text-align:center;">
<div id="buildDebugDiv" class="hide">
<label><input type="checkbox" id="buildDebugCheckbox" onchange="onOptionsChanged()"/>Build the debug binary. Debug binaries are much larger and slower than the release one.</label>
</div>
<div id="buildInstallLocalDiv" class="hide">
<label><input type="checkbox" id="buildInstallLocalCheckbox" onchange="onOptionsChanged()"/>Install the built Telegram Bot API server to /usr/local instead of placing the files to telegram-bot-api/bin.</label>
</div>
<p></p>
<div id="buildCompilerDiv" class="hide">
<span>Choose which compiler you want to use to build the Telegram Bot API server:</span><br>
<label><input type="radio" id="buildCompilerRadioGcc" name="buildCompilerRadio" onchange="onOptionsChanged()" checked/>g++</label>
<label><input type="radio" id="buildCompilerRadioClang" name="buildCompilerRadio" onchange="onOptionsChanged()"/>clang++ (recommended)</label>
<p></p>
</div>
<div id="buildShellDiv" class="hide">
<span>Choose which shell application you want to use for building:</span><br>
<label><input type="radio" id="buildShellRadioPowerShell" name="buildShellRadio" onchange="onOptionsChanged()" checked/>PowerShell</label>
<label><input type="radio" id="buildShellRadioBash" name="buildShellRadio" onchange="onOptionsChanged()"/>mintty/Bash</label>
<p></p>
</div>
<div id="buildShellBsdDiv" class="hide">
<span>Choose which shell application you want to use for building:</span><br>
<label><input type="radio" id="buildShellBsdRadioCsh" name="buildShellRadioBsd" onchange="onOptionsChanged()" checked/>tcsh/csh</label>
<label><input type="radio" id="buildShellBsdRadioBash" name="buildShellRadioBsd" onchange="onOptionsChanged()"/>Bash</label>
<p></p>
</div>
<div id="buildBitnessDiv" class="hide">
<span>Choose for which bitness you want to build the Telegram Bot API server:</span><br>
<label><input type="radio" id="buildBitnessRadio64" name="buildBitnessRadio" onchange="onOptionsChanged()" checked/>64</label>
<label><input type="radio" id="buildBitnessRadio32" name="buildBitnessRadio" onchange="onOptionsChanged()"/>32</label>
<p></p>
</div>
<div id="buildRootDiv" class="hide">
<label><input type="checkbox" id="buildRootCheckbox" onchange="onOptionsChanged()"/>Build from root user (unrecommended).</label>
</div>
<p></p>
</div>
<div id="buildTextDiv" class="hide" style="text-align:center;">
<p id="buildText">Hidden text</p>
</div>
<div id="buildCommandsDiv" class="hide" style="text-align:left;">
<p id="buildPre">Hidden text</p>
<code id="buildCommands">Empty commands</code>
</div>
</div>
<script>
function onLoad(initial) {
var url = new URL(window.location.href);
var os = url.searchParams.get('os');
if (!os) {
os = '';
}
var os_options = document.getElementById('osSelect').options;
for (var i = 0; i < os_options.length; i++) {
os_options[i].selected = os_options[i].text.toLowerCase() === os.toLowerCase();
}
onOsChanged(initial || !history.state);
}
function onOsChanged(initial) {
var os = document.getElementById('osSelect').value;
if (os.includes('Choose ')) {
if (history.state != '') {
history.pushState('', '', 'build.html');
}
document.getElementById('linuxSelectDiv').style.display = 'none';
document.getElementById('buildTextDiv').style.display = 'none';
document.getElementById('buildOptionsDiv').style.display = 'none';
document.getElementById('buildCommandsDiv').style.display = 'none';
return;
}
if (!initial && history.state !== os) {
history.pushState(os, '', 'build.html?os=' + encodeURIComponent(os));
}
var os_linux = os.includes('Linux');
if (os_linux) {
document.getElementById('linuxSelectDiv').style.display = 'block';
var linux_distro = document.getElementById('linuxSelect').value;
if (linux_distro.includes('Choose ')) {
document.getElementById('buildTextDiv').style.display = 'none';
document.getElementById('buildOptionsDiv').style.display = 'none';
document.getElementById('buildCommandsDiv').style.display = 'none';
return;
}
} else {
document.getElementById('linuxSelectDiv').style.display = 'none';
}
document.getElementById('buildTextDiv').style.display = 'block';
document.getElementById('buildText').innerHTML = 'Here is complete instruction for building Telegram Bot API Server on ' + os + ':';
document.getElementById('buildOptionsDiv').style.display = 'block';
onOptionsChanged();
}
function onOptionsChanged() {
var os = document.getElementById('osSelect').value;
var os_windows = os.includes('Windows');
var os_linux = os.includes('Linux');
var os_mac = os.includes('macOS');
var os_freebsd = os.includes('FreeBSD');
var os_openbsd = os.includes('OpenBSD');
var os_netbsd = os.includes('NetBSD');
var linux_distro = 'none';
if (os_linux) {
linux_distro = document.getElementById('linuxSelect').value;
}
document.getElementById('buildCommandsDiv').style.display = 'block';
var use_clang = os_freebsd || os_openbsd;
if (os_linux && linux_distro !== 'Alpine') {
document.getElementById('buildCompilerDiv').style.display = 'block';
use_clang = document.getElementById('buildCompilerRadioClang').checked;
} else {
document.getElementById('buildCompilerDiv').style.display = 'none';
}
var use_root = false;
if ((os_linux && linux_distro !== 'Other') || os_openbsd || os_netbsd) {
use_root = document.getElementById('buildRootCheckbox').checked;
document.getElementById('buildRootDiv').style.display = 'block';
} else {
document.getElementById('buildRootDiv').style.display = 'none';
}
var use_powershell = false;
var use_cmd = false;
var use_csh = false;
if (os_windows) {
document.getElementById('buildShellDiv').style.display = 'block';
use_powershell = document.getElementById('buildShellRadioPowerShell').checked;
} else {
document.getElementById('buildShellDiv').style.display = 'none';
}
if (os_freebsd) {
document.getElementById('buildShellBsdDiv').style.display = 'block';
use_csh = document.getElementById('buildShellBsdRadioCsh').checked;
} else {
document.getElementById('buildShellBsdDiv').style.display = 'none';
}
var use_msvc = os_windows;
var use_vcpkg = os_windows;
var is_debug_build = document.getElementById('buildDebugCheckbox').checked;
document.getElementById('buildDebugDiv').style.display = 'block';
var sudo = 'sudo ';
if (use_root || linux_distro.includes('Debian') || os_freebsd || os_openbsd || os_netbsd) {
sudo = '';
}
var build_32bit = false;
var build_64bit = false;
if (use_msvc) {
document.getElementById('buildBitnessDiv').style.display = 'block';
build_32bit = document.getElementById('buildBitnessRadio32').checked;
build_64bit = document.getElementById('buildBitnessRadio64').checked;
} else {
document.getElementById('buildBitnessDiv').style.display = 'none';
}
var local = './';
if (use_cmd) {
local = '.\\';
}
var install_dir = 'telegram-bot-api/bin';
if (!os_windows) {
document.getElementById('buildInstallLocalDiv').style.display = 'block';
if (document.getElementById('buildInstallLocalCheckbox').checked) {
install_dir = '/usr/local';
}
} else {
document.getElementById('buildInstallLocalDiv').style.display = 'none';
}
var pre_text = [];
if (os_windows) {
pre_text.push('Note that Windows Subsystem for Linux (WSL) and Cygwin are not Windows environments, so you need to use instructions for Linux for them instead.');
pre_text.push('Download and install <a href="https://visualstudio.microsoft.com/ru/vs/community/">Microsoft Visual Studio</a>. Enable C++ support while installing.');
pre_text.push('Download and install <a href="https://cmake.org/download/">CMake</a>; choose "Add CMake to the system PATH" option while installing.');
pre_text.push('Download and install <a href="https://git-scm.com/download/win">Git</a>.');
pre_text.push('Download and install <a href="https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/">gperf</a>. Add the path to gperf.exe to the PATH environment variable.');
}
if (os_linux && linux_distro === 'Other') {
var compiler = use_clang ? 'clang++ >= 3.4' : 'g++ >= 4.9.2';
pre_text.push('Install Git, ' + compiler + ', make, CMake >= 3.0.2, OpenSSL-dev, zlib-dev, gperf using your package manager.');
}
if (os_freebsd) {
pre_text.push('Note that the following instruction is for FreeBSD 11.');
pre_text.push('Note that the following calls to <code>pkg</code> needs to be run as <code>root</code>.');
}
if (os_openbsd) {
pre_text.push('Note that the following instruction is for OpenBSD 6.7 and default KSH shell.');
pre_text.push('Note that building requires a lot of memory, so you may need to increase allowed per-process memory usage in /etc/login.conf or build from root.');
}
if (os_netbsd) {
pre_text.push('Note that the following instruction is for NetBSD 8.0 and default SH shell.');
}
var terminal_name = (function () {
if (os_windows) {
return use_powershell ? 'PowerShell' : 'mintty/Bash';
}
if (os_mac) {
return 'Terminal';
}
if (os_openbsd) {
return 'ksh';
}
if (os_netbsd) {
return 'sh';
}
if (use_csh) {
return 'tcsh/csh';
}
return 'Bash';
})();
if (os_windows) {
pre_text.push('Close and re-open ' + terminal_name + ' if the PATH environment variable was changed.');
}
pre_text.push('Run these commands in ' + terminal_name + ' to build Bot API server and to install it to ' + install_dir + ':');
document.getElementById('buildPre').innerHTML = '<ul><li>' + pre_text.join('</li><li>') + '</li></ul>';
document.getElementById('buildPre').style.display = 'block';
if (install_dir !== '/usr/local') {
install_dir = '..';
}
function getClangVersionSuffix() {
switch (linux_distro) {
case 'Ubuntu 14':
return '-3.9';
case 'Ubuntu 18':
return '-6.0';
case 'Ubuntu 20':
return '-10';
default:
return ''; // use default version
}
}
var commands = [];
if (os_mac) {
commands.push('xcode-select --install');
commands.push('/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"');
commands.push('brew install gperf cmake openssl');
} else if (os_linux && linux_distro !== 'Other') {
switch (linux_distro) {
case 'Alpine':
commands.push(sudo + 'apk update');
commands.push(sudo + 'apk upgrade');
var packages = 'alpine-sdk linux-headers git zlib-dev openssl-dev gperf cmake';
commands.push(sudo + 'apk add --update ' + packages);
break;
case 'Debian 8':
case 'Debian 9':
case 'Debian 10':
case 'Ubuntu 14':
case 'Ubuntu 16':
case 'Ubuntu 18':
case 'Ubuntu 20':
if (linux_distro.includes('Debian') && !use_root) {
commands.push('su -');
}
if (linux_distro === 'Ubuntu 14' && !use_clang) {
commands.push(sudo + 'add-apt-repository ppa:ubuntu-toolchain-r/test');
}
commands.push(sudo + 'apt-get update');
commands.push(sudo + 'apt-get upgrade');
var packages = 'make git zlib1g-dev libssl-dev gperf';
if (linux_distro === 'Ubuntu 14') {
packages += ' cmake3';
} else {
packages += ' cmake';
}
if (use_clang) {
packages += ' clang' + getClangVersionSuffix() + ' libc++-dev';
if (linux_distro === 'Debian 10' || linux_distro === 'Ubuntu 18' || linux_distro === 'Ubuntu 20') {
packages += ' libc++abi-dev';
}
} else {
packages += ' g++';
if (linux_distro === 'Ubuntu 14') {
packages += '-4.9';
}
}
commands.push(sudo + 'apt-get install ' + packages);
if (linux_distro.includes('Debian') && !use_root) {
commands.push('exit');
}
break;
}
} else if (os_freebsd) {
commands.push(sudo + 'pkg upgrade');
var packages = 'git gperf cmake';
commands.push(sudo + 'pkg install ' + packages);
} else if (os_openbsd) {
if (!use_root) {
commands.push('su -');
}
var packages = 'git gperf cmake';
commands.push('pkg_add -z ' + packages);
if (!use_root) {
commands.push('exit');
}
} else if (os_netbsd) {
if (!use_root) {
commands.push('su -');
}
commands.push('export PKG_PATH=ftp://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/i386/8.0_2019Q2/All');
var packages = 'git gperf cmake openssl gcc5-libs';
commands.push('pkg_add ' + packages);
if (!use_root) {
commands.push('exit');
}
}
commands.push('git clone --recursive https://github.com/tdlib/telegram-bot-api.git');
commands.push('cd telegram-bot-api');
if (use_vcpkg) {
commands.push('git clone https://github.com/Microsoft/vcpkg.git');
commands.push('cd vcpkg');
commands.push(local + 'bootstrap-vcpkg.bat');
if (build_64bit) {
commands.push(local + 'vcpkg.exe install openssl:x64-windows zlib:x64-windows');
} else {
commands.push(local + 'vcpkg.exe install openssl:x86-windows zlib:x86-windows');
}
commands.push('cd ..');
}
function getBacicCmakeInitOptions() {
var options = [];
if (!use_msvc) {
options.push('-DCMAKE_BUILD_TYPE=' + (is_debug_build ? 'Debug' : 'Release'));
}
if (use_msvc) {
if (build_64bit) {
options.push('-A x64');
} else {
options.push('-A Win32');
}
}
return options;
}
commands.push(use_powershell ? 'Remove-Item build -Force -Recurse -ErrorAction SilentlyContinue' : 'rm -rf build');
commands.push('mkdir build');
commands.push('cd build');
if (!use_msvc) {
var c_flags = [];
var cxx_flags = [];
if (build_32bit) {
c_flags.push('-m32');
cxx_flags.push('-m32');
} else if (build_64bit) {
c_flags.push('-m64');
cxx_flags.push('-m64');
}
if (os_linux) {
if (use_clang) {
cxx_flags.push('-stdlib=libc++');
} else {
cxx_flags.push('');
}
}
if (c_flags.length) {
commands.push('export CFLAGS="' + c_flags.join(' ') + '"');
}
if (cxx_flags.length) {
commands.push('export CXXFLAGS="' + cxx_flags.join(' ') + '"');
}
}
cmake_init_options = getBacicCmakeInitOptions();
if (os_mac) {
cmake_init_options.push('-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl/');
}
cmake_init_options.push('-DCMAKE_INSTALL_PREFIX:PATH=' + install_dir);
if (use_vcpkg) {
cmake_init_options.push('-DCMAKE_TOOLCHAIN_FILE:FILEPATH=../vcpkg/scripts/buildsystems/vcpkg.cmake');
}
function getCmakeInitCommand(options) {
var prefix = '';
if (os_linux) {
if (use_clang) {
var clang_version_suffix = getClangVersionSuffix();
prefix = 'CC=/usr/bin/clang' + clang_version_suffix + ' CXX=/usr/bin/clang++' + clang_version_suffix + ' ';
} else if (linux_distro === 'Ubuntu 14') {
prefix = 'CC=/usr/bin/gcc-4.9 CXX=/usr/bin/g++-4.9 ';
}
}
return prefix + 'cmake ' + options.join(' ') + ' ..';
}
commands.push(getCmakeInitCommand(cmake_init_options));
let build_command = 'cmake --build . --target install';
if (use_msvc) {
if (!is_debug_build) {
commands.push(build_command + ' --config Release');
}
if (is_debug_build) {
commands.push(build_command + ' --config Debug');
}
} else {
commands.push(build_command);
}
commands.push('cd ../..');
if (install_dir !== '/usr/local') {
install_dir = 'telegram-bot-api';
}
commands.push((use_powershell ? 'dir ' : 'ls -l ') + install_dir + '/bin/telegram-bot-api*');
document.getElementById('buildCommands').innerHTML = '<ul><li>' + commands.join('</li><li>') + '</li></ul>';
}
</script>
</body>
</html>

1
td Submodule

@ -0,0 +1 @@
Subproject commit c9a70fcd49757084a0c3124e610e92bf0586894b

9039
telegram-bot-api/Client.cpp Normal file

File diff suppressed because it is too large Load Diff

935
telegram-bot-api/Client.h Normal file
View File

@ -0,0 +1,935 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/Query.h"
#include "telegram-bot-api/Stats.h"
#include "telegram-bot-api/WebhookActor.h"
#include "td/telegram/ClientActor.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/actor/SignalSlot.h"
#include "td/actor/Timeout.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include <functional>
#include <limits>
#include <memory>
#include <queue>
#include <unordered_map>
#include <unordered_set>
namespace telegram_bot_api {
struct ClientParameters;
namespace td_api = td::td_api;
class Client : public WebhookActor::Callback {
public:
Client(td::ActorShared<> parent, const td::string &bot_token, bool is_test_dc, td::int64 tqueue_id,
std::shared_ptr<const ClientParameters> parameters, td::ActorId<BotStatActor> stat_actor);
void start_up() override;
void send(PromisedQueryPtr query) override;
void close();
// for stats
ServerBotInfo get_bot_info() const;
private:
using int32 = td::int32;
using int64 = td::int64;
using Slice = td::Slice;
using Status = td::Status;
template <class T>
using object_ptr = td_api::object_ptr<T>;
static constexpr bool USE_MESSAGE_DATABASE = false;
static constexpr int32 MAX_CERTIFICATE_FILE_SIZE = 3 << 20;
static constexpr int32 MAX_DOWNLOAD_FILE_SIZE = 20 << 20;
static constexpr int32 MESSAGES_CACHE_TIME = 3600;
static constexpr std::size_t MIN_PENDING_UPDATES_WARNING = 200;
static constexpr int64 GREAT_MINDS_SET_ID = 1842540969984001;
static constexpr Slice GREAT_MINDS_SET_NAME = "TelegramGreatMinds";
static constexpr int32 MASK_POINTS_SIZE = 4;
static constexpr Slice MASK_POINTS[MASK_POINTS_SIZE] = {"forehead", "eyes", "mouth", "chin"};
static constexpr int32 MAX_LENGTH = 10000; // max width or height
static constexpr int32 MAX_DURATION = 24 * 60 * 60;
static constexpr int LOGGING_OUT_ERROR_CODE = 401;
static constexpr Slice LOGGING_OUT_ERROR_DESCRIPTION = "Unauthorized";
static constexpr int CLOSING_ERROR_CODE = 500;
static constexpr Slice CLOSING_ERROR_DESCRIPTION = "Internal Server Error: restart";
class JsonFile;
class JsonDatedFile;
class JsonDatedFiles;
class JsonUser;
class JsonUsers;
class JsonChatPermissions;
class JsonChatPhotoInfo;
class JsonChatLocation;
class JsonChat;
class JsonMessageSender;
class JsonAnimation;
class JsonAudio;
class JsonDocument;
class JsonPhotoSize;
class JsonPhoto;
class JsonChatPhoto;
class JsonThumbnail;
class JsonMaskPosition;
class JsonSticker;
class JsonStickers;
class JsonVideo;
class JsonVideoNote;
class JsonVoiceNote;
class JsonContact;
class JsonDice;
class JsonGame;
class JsonInvoice;
class JsonLocation;
class JsonVenue;
class JsonPollOption;
class JsonPoll;
class JsonPollAnswer;
class JsonEntity;
class JsonVectorEntities;
class JsonCallbackGame;
class JsonInlineKeyboardButton;
class JsonInlineKeyboard;
class JsonReplyMarkup;
class JsonMessage;
class JsonMessages;
class JsonDeletedMessage;
class JsonMessageId;
class JsonInlineQuery;
class JsonChosenInlineResult;
class JsonCallbackQuery;
class JsonInlineCallbackQuery;
class JsonShippingQuery;
class JsonPreCheckoutQuery;
class JsonBotCommand;
class JsonChatPhotos;
class JsonChatMember;
class JsonChatMembers;
class JsonGameHighScore;
class JsonAddress;
class JsonOrderInfo;
class JsonSuccessfulPaymentBot;
class JsonEncryptedPassportElement;
class JsonEncryptedCredentials;
class JsonPassportData;
class JsonProximityAlertTriggered;
class JsonUpdateTypes;
class JsonWebhookInfo;
class JsonStickerSet;
class JsonCustomJson;
class TdOnOkCallback;
class TdOnAuthorizationCallback;
class TdOnInitCallback;
class TdOnGetUserProfilePhotosCallback;
class TdOnSendMessageCallback;
class TdOnSendMessageAlbumCallback;
class TdOnDeleteFailedToSendMessageCallback;
class TdOnEditMessageCallback;
class TdOnEditInlineMessageCallback;
class TdOnStopPollCallback;
class TdOnOkQueryCallback;
class TdOnGetReplyMessageCallback;
class TdOnGetEditedMessageCallback;
class TdOnGetCallbackQueryMessageCallback;
class TdOnGetStickerSetCallback;
class TdOnGetMyCommandsCallback;
class TdOnGetChatFullInfoCallback;
class TdOnGetChatStickerSetCallback;
class TdOnGetChatPinnedMessageCallback;
class TdOnGetChatPinnedMessageToUnpinCallback;
class TdOnGetGroupMembersCallback;
class TdOnGetSupergroupMembersCallback;
class TdOnGetSupergroupMembersCountCallback;
class TdOnGenerateChatInviteLinkCallback;
class TdOnGetGameHighScoresCallback;
class TdOnReturnFileCallback;
class TdOnReturnStickerSetCallback;
class TdOnDownloadFileCallback;
class TdOnCancelDownloadFileCallback;
class TdOnSendCustomRequestCallback;
void on_get_reply_message(int64 chat_id, object_ptr<td_api::message> reply_to_message);
void on_get_edited_message(object_ptr<td_api::message> edited_message);
void on_get_callback_query_message(object_ptr<td_api::message> message, int32 user_id, int state);
void on_get_sticker_set(int64 set_id, int32 new_callback_query_user_id, int64 new_message_chat_id,
object_ptr<td_api::stickerSet> sticker_set);
void on_get_sticker_set_name(int64 set_id, const td::string &name);
class TdQueryCallback {
public:
virtual void on_result(object_ptr<td_api::Object> result) = 0;
TdQueryCallback() = default;
TdQueryCallback(const TdQueryCallback &) = delete;
TdQueryCallback &operator=(const TdQueryCallback &) = delete;
TdQueryCallback(TdQueryCallback &&) = delete;
TdQueryCallback &operator=(TdQueryCallback &&) = delete;
virtual ~TdQueryCallback() = default;
};
struct UserInfo;
struct ChatInfo;
enum class AccessRights { Read, Edit, Write };
template <class OnSuccess>
class TdOnCheckUserCallback;
template <class OnSuccess>
class TdOnCheckUserNoFailCallback;
template <class OnSuccess>
class TdOnCheckChatCallback;
template <class OnSuccess>
class TdOnCheckMessageCallback;
template <class OnSuccess>
class TdOnCheckRemoteFileIdCallback;
template <class OnSuccess>
class TdOnGetChatMemberCallback;
template <class OnSuccess>
class TdOnSearchStickerSetCallback;
class TdOnResolveBotUsernameCallback;
template <class OnSuccess>
void check_user(int32 user_id, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void check_user_no_fail(int32 user_id, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
static void check_user_read_access(const UserInfo *user_info, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void check_chat_access(int64 chat_id, AccessRights access_rights, const ChatInfo *chat_info, PromisedQueryPtr query,
OnSuccess on_success) const;
template <class OnSuccess>
void check_chat(Slice chat_id_str, AccessRights access_rights, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void check_remote_file_id(td::string file_id, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void check_message(Slice chat_id_str, int64 message_id, bool allow_empty, AccessRights access_rights,
Slice message_type, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void resolve_sticker_set(const td::string &sticker_set_name, PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void resolve_reply_markup_bot_usernames(object_ptr<td_api::ReplyMarkup> reply_markup, PromisedQueryPtr query,
OnSuccess on_success);
template <class OnSuccess>
void resolve_inline_query_results_bot_usernames(td::vector<object_ptr<td_api::InputInlineQueryResult>> results,
PromisedQueryPtr query, OnSuccess on_success);
template <class OnSuccess>
void get_chat_member(int64 chat_id, int32 user_id, PromisedQueryPtr query, OnSuccess on_success);
void send_request(object_ptr<td_api::Function> &&f, std::unique_ptr<TdQueryCallback> handler);
void do_send_request(object_ptr<td_api::Function> &&f, std::unique_ptr<TdQueryCallback> handler);
static object_ptr<td_api::Object> execute(object_ptr<td_api::Function> &&f);
void on_update(object_ptr<td_api::Object> result);
void on_result(td::uint64 id, object_ptr<td_api::Object> result);
void on_update_authorization_state();
void log_out();
void on_closed();
void finish_closing();
void clear_tqueue();
bool allow_update_before_authorization(const td_api::Object *update) const;
void update_shared_unix_time_difference();
void on_update_file(object_ptr<td_api::file> file);
static bool to_bool(td::MutableSlice value);
static td::Result<object_ptr<td_api::keyboardButton>> get_keyboard_button(td::JsonValue &button);
td::Result<object_ptr<td_api::inlineKeyboardButton>> get_inline_keyboard_button(td::JsonValue &button);
td::Result<object_ptr<td_api::ReplyMarkup>> get_reply_markup(const Query *query);
td::Result<object_ptr<td_api::ReplyMarkup>> get_reply_markup(td::JsonValue &&value);
static td::Result<object_ptr<td_api::labeledPricePart>> get_labeled_price_part(td::JsonValue &value);
static td::Result<td::vector<object_ptr<td_api::labeledPricePart>>> get_labeled_price_parts(td::JsonValue &value);
static td::Result<object_ptr<td_api::shippingOption>> get_shipping_option(td::JsonValue &option);
static td::Result<td::vector<object_ptr<td_api::shippingOption>>> get_shipping_options(const Query *query);
static td::Result<td::vector<object_ptr<td_api::shippingOption>>> get_shipping_options(td::JsonValue &&value);
static td::Result<object_ptr<td_api::InputMessageContent>> get_input_message_content(
td::JsonValue &input_message_content, bool is_input_message_content_required);
static object_ptr<td_api::ChatAction> get_chat_action(const Query *query);
static td::string get_local_file_path(Slice file_uri);
object_ptr<td_api::InputFile> get_input_file(const Query *query, Slice field_name, bool force_file = false) const;
object_ptr<td_api::InputFile> get_input_file(const Query *query, Slice field_name, Slice file_id,
bool force_file) const;
object_ptr<td_api::inputThumbnail> get_input_thumbnail(const Query *query, Slice field_name) const;
td::Result<object_ptr<td_api::InputInlineQueryResult>> get_inline_query_result(td::JsonValue &&value);
td::Result<td::vector<object_ptr<td_api::InputInlineQueryResult>>> get_inline_query_results(const Query *query);
td::Result<td::vector<object_ptr<td_api::InputInlineQueryResult>>> get_inline_query_results(td::JsonValue &&value);
static td::Result<object_ptr<td_api::botCommand>> get_bot_command(td::JsonValue &&value);
static td::Result<td::vector<object_ptr<td_api::botCommand>>> get_bot_commands(const Query *query);
static td::Result<object_ptr<td_api::maskPosition>> get_mask_position(const Query *query, Slice field_name);
static td::Result<object_ptr<td_api::maskPosition>> get_mask_position(td::JsonValue &&value);
static int32 mask_point_to_index(const object_ptr<td_api::MaskPoint> &mask_point);
static object_ptr<td_api::MaskPoint> mask_index_to_point(int32 index);
td::Result<td::vector<object_ptr<td_api::InputSticker>>> get_input_stickers(const Query *query) const;
static td::Result<td::string> get_passport_element_hash(Slice encoded_hash);
static td::Result<object_ptr<td_api::InputPassportElementErrorSource>> get_passport_element_error_source(
td::JsonObject &object);
static td::Result<object_ptr<td_api::inputPassportElementError>> get_passport_element_error(td::JsonValue &&value);
static td::Result<td::vector<object_ptr<td_api::inputPassportElementError>>> get_passport_element_errors(
const Query *query);
static td::Result<object_ptr<td_api::formattedText>> get_caption(const Query *query);
static td::Result<object_ptr<td_api::TextEntityType>> get_text_entity_type(td::JsonObject &object);
static td::Result<object_ptr<td_api::textEntity>> get_text_entity(td::JsonValue &&value);
static td::Result<object_ptr<td_api::formattedText>> get_formatted_text(td::string text, td::string parse_mode,
td::JsonValue &&input_entities);
static td::Result<object_ptr<td_api::inputMessageText>> get_input_message_text(const Query *query);
static td::Result<object_ptr<td_api::inputMessageText>> get_input_message_text(td::string text,
bool disable_web_page_preview,
td::string parse_mode,
td::JsonValue &&input_entities);
static td::Result<object_ptr<td_api::location>> get_location(const Query *query);
static td::Result<object_ptr<td_api::chatPermissions>> get_chat_permissions(const Query *query, bool &allow_legacy);
td::Result<object_ptr<td_api::InputMessageContent>> get_input_media(const Query *query, td::JsonValue &&input_media,
bool for_album) const;
td::Result<object_ptr<td_api::InputMessageContent>> get_input_media(const Query *query, Slice field_name,
bool for_album) const;
td::Result<td::vector<object_ptr<td_api::InputMessageContent>>> get_input_message_contents(const Query *query,
Slice field_name) const;
td::Result<td::vector<object_ptr<td_api::InputMessageContent>>> get_input_message_contents(
const Query *query, td::JsonValue &&value) const;
static object_ptr<td_api::messageSendOptions> get_message_send_options(bool disable_notification);
static td::Result<td::vector<td::string>> get_poll_options(const Query *query);
static int32 get_integer_arg(const Query *query, Slice field_name, int32 default_value,
int32 min_value = std::numeric_limits<int32>::min(),
int32 max_value = std::numeric_limits<int32>::max());
static td::Result<td::MutableSlice> get_required_string_arg(const Query *query, Slice field_name);
static int64 get_message_id(const Query *query, Slice field_name = "message_id");
static td::Result<Slice> get_inline_message_id(const Query *query, Slice field_name = "inline_message_id");
static td::Result<int32> get_user_id(const Query *query, Slice field_name = "user_id");
int64 extract_yet_unsent_message_query_id(int64 chat_id, int64 message_id, bool *is_reply_to_message_deleted);
void on_message_send_succeeded(object_ptr<td_api::message> &&message, int64 old_message_id);
void on_message_send_failed(int64 chat_id, int64 old_message_id, int64 new_message_id, Status result);
static bool init_methods();
void on_cmd(PromisedQueryPtr query);
Status process_get_me_query(PromisedQueryPtr &query);
Status process_get_my_commands_query(PromisedQueryPtr &query);
Status process_set_my_commands_query(PromisedQueryPtr &query);
Status process_get_user_profile_photos_query(PromisedQueryPtr &query);
Status process_send_message_query(PromisedQueryPtr &query);
Status process_send_animation_query(PromisedQueryPtr &query);
Status process_send_audio_query(PromisedQueryPtr &query);
Status process_send_dice_query(PromisedQueryPtr &query);
Status process_send_document_query(PromisedQueryPtr &query);
Status process_send_photo_query(PromisedQueryPtr &query);
Status process_send_sticker_query(PromisedQueryPtr &query);
Status process_send_video_query(PromisedQueryPtr &query);
Status process_send_video_note_query(PromisedQueryPtr &query);
Status process_send_voice_query(PromisedQueryPtr &query);
Status process_send_game_query(PromisedQueryPtr &query);
Status process_send_invoice_query(PromisedQueryPtr &query);
Status process_send_location_query(PromisedQueryPtr &query);
Status process_send_venue_query(PromisedQueryPtr &query);
Status process_send_contact_query(PromisedQueryPtr &query);
Status process_send_poll_query(PromisedQueryPtr &query);
Status process_stop_poll_query(PromisedQueryPtr &query);
Status process_copy_message_query(PromisedQueryPtr &query);
Status process_forward_message_query(PromisedQueryPtr &query);
Status process_send_media_group_query(PromisedQueryPtr &query);
Status process_send_chat_action_query(PromisedQueryPtr &query);
Status process_edit_message_text_query(PromisedQueryPtr &query);
Status process_edit_message_live_location_query(PromisedQueryPtr &query);
Status process_edit_message_media_query(PromisedQueryPtr &query);
Status process_edit_message_caption_query(PromisedQueryPtr &query);
Status process_edit_message_reply_markup_query(PromisedQueryPtr &query);
Status process_delete_message_query(PromisedQueryPtr &query);
Status process_set_game_score_query(PromisedQueryPtr &query);
Status process_get_game_high_scores_query(PromisedQueryPtr &query);
Status process_answer_inline_query_query(PromisedQueryPtr &query);
Status process_answer_callback_query_query(PromisedQueryPtr &query);
Status process_answer_shipping_query_query(PromisedQueryPtr &query);
Status process_answer_pre_checkout_query_query(PromisedQueryPtr &query);
Status process_export_chat_invite_link_query(PromisedQueryPtr &query);
Status process_get_chat_query(PromisedQueryPtr &query);
Status process_set_chat_photo_query(PromisedQueryPtr &query);
Status process_delete_chat_photo_query(PromisedQueryPtr &query);
Status process_set_chat_title_query(PromisedQueryPtr &query);
Status process_set_chat_permissions_query(PromisedQueryPtr &query);
Status process_set_chat_description_query(PromisedQueryPtr &query);
Status process_pin_chat_message_query(PromisedQueryPtr &query);
Status process_unpin_chat_message_query(PromisedQueryPtr &query);
Status process_unpin_all_chat_messages_query(PromisedQueryPtr &query);
Status process_set_chat_sticker_set_query(PromisedQueryPtr &query);
Status process_delete_chat_sticker_set_query(PromisedQueryPtr &query);
Status process_get_chat_member_query(PromisedQueryPtr &query);
Status process_get_chat_administrators_query(PromisedQueryPtr &query);
Status process_get_chat_member_count_query(PromisedQueryPtr &query);
Status process_leave_chat_query(PromisedQueryPtr &query);
Status process_promote_chat_member_query(PromisedQueryPtr &query);
Status process_set_chat_administrator_custom_title_query(PromisedQueryPtr &query);
Status process_ban_chat_member_query(PromisedQueryPtr &query);
Status process_restrict_chat_member_query(PromisedQueryPtr &query);
Status process_unban_chat_member_query(PromisedQueryPtr &query);
Status process_get_sticker_set_query(PromisedQueryPtr &query);
Status process_upload_sticker_file_query(PromisedQueryPtr &query);
Status process_create_new_sticker_set_query(PromisedQueryPtr &query);
Status process_add_sticker_to_set_query(PromisedQueryPtr &query);
Status process_set_sticker_set_thumb_query(PromisedQueryPtr &query);
Status process_set_sticker_position_in_set_query(PromisedQueryPtr &query);
Status process_delete_sticker_from_set_query(PromisedQueryPtr &query);
Status process_set_passport_data_errors_query(PromisedQueryPtr &query);
Status process_send_custom_request_query(PromisedQueryPtr &query);
Status process_answer_custom_query_query(PromisedQueryPtr &query);
Status process_get_updates_query(PromisedQueryPtr &query);
Status process_set_webhook_query(PromisedQueryPtr &query);
Status process_get_webhook_info_query(PromisedQueryPtr &query);
Status process_get_file_query(PromisedQueryPtr &query);
void webhook_verified(td::string cached_ip_address) override;
void webhook_success() override;
void webhook_error(Status status) override;
void webhook_closed(Status status) override;
void hangup_shared() override;
int32 get_webhook_max_connections(const Query *query) const;
static bool get_webhook_fix_ip_address(const Query *query);
void do_set_webhook(PromisedQueryPtr query, bool was_deleted);
void save_webhook() const;
td::string get_webhook_certificate_path() const;
void do_send_message(object_ptr<td_api::InputMessageContent> input_message_content, PromisedQueryPtr query);
int64 get_send_message_query_id(PromisedQueryPtr query, bool is_multisend);
void on_sent_message(object_ptr<td_api::message> &&message, int64 query_id);
void do_get_file(object_ptr<td_api::file> file, PromisedQueryPtr query);
bool is_file_being_downloaded(int32 file_id) const;
void on_file_download(int32 file_id, td::Result<object_ptr<td_api::file>> r_file);
void fix_reply_markup_bot_user_ids(object_ptr<td_api::ReplyMarkup> &reply_markup) const;
void fix_inline_query_results_bot_user_ids(td::vector<object_ptr<td_api::InputInlineQueryResult>> &results) const;
void resolve_bot_usernames(PromisedQueryPtr query, td::Promise<PromisedQueryPtr> on_success);
void on_resolve_bot_username(const td::string &username, int32 user_id);
void abort_long_poll(bool from_set_webhook);
void fail_query_conflict(Slice message, PromisedQueryPtr &&query);
static void fail_query_with_error(PromisedQueryPtr query, int32 error_code, Slice error_message,
Slice default_message = Slice());
static void fail_query_with_error(PromisedQueryPtr &&query, object_ptr<td_api::error> error,
Slice default_message = Slice());
class JsonUpdates;
void do_get_updates(int32 offset, int32 limit, int32 timeout, PromisedQueryPtr query);
void long_poll_wakeup(bool force_flag);
void raw_event(const td::Event::Raw &event) override;
void loop() override;
void timeout_expired() override;
struct UserInfo {
enum class Type { Regular, Deleted, Bot, Unknown };
Type type = Type::Unknown;
td::string first_name;
td::string last_name;
td::string username;
td::string language_code;
td::string bio;
bool have_access = false;
bool can_join_groups = false;
bool can_read_all_group_messages = false;
bool is_inline_bot = false;
};
static void add_user(std::unordered_map<int32, UserInfo> &users, object_ptr<td_api::user> &&user);
void set_user_bio(int32 user_id, td::string &&bio);
const UserInfo *get_user_info(int32 user_id) const;
struct GroupInfo {
td::string description;
td::string invite_link;
int32 member_count = 0;
bool left = false;
bool kicked = false;
bool is_active = false;
int32 upgraded_to_supergroup_id = 0;
};
static void add_group(std::unordered_map<int32, GroupInfo> &groups, object_ptr<td_api::basicGroup> &&group);
void set_group_description(int32 group_id, td::string &&descripton);
void set_group_invite_link(int32 group_id, td::string &&invite_link);
const GroupInfo *get_group_info(int32 group_id) const;
struct SupergroupInfo {
td::string username;
td::string description;
td::string invite_link;
int64 sticker_set_id = 0;
int32 date = 0;
int32 slow_mode_delay = 0;
int64 linked_chat_id = 0;
object_ptr<td_api::chatLocation> location;
object_ptr<td_api::ChatMemberStatus> status;
bool is_supergroup = false;
bool can_set_sticker_set = false;
bool has_location = false;
};
static void add_supergroup(std::unordered_map<int32, SupergroupInfo> &supergroups,
object_ptr<td_api::supergroup> &&supergroup);
void set_supergroup_description(int32 supergroup_id, td::string &&descripton);
void set_supergroup_invite_link(int32 supergroup_id, td::string &&invite_link);
void set_supergroup_sticker_set_id(int32 supergroup_id, int64 sticker_set_id);
void set_supergroup_can_set_sticker_set(int32 supergroup_id, bool can_set_sticker_set);
void set_supergroup_slow_mode_delay(int32 supergroup_id, int32 slow_mode_delay);
void set_supergroup_linked_chat_id(int32 supergroup_id, int64 linked_chat_id);
void set_supergroup_location(int32 supergroup_id, object_ptr<td_api::chatLocation> location);
const SupergroupInfo *get_supergroup_info(int32 supergroup_id) const;
struct ChatInfo {
enum class Type { Private, Group, Supergroup, Unknown };
Type type = Type::Unknown;
td::string title;
object_ptr<td_api::chatPhotoInfo> photo;
object_ptr<td_api::chatPermissions> permissions;
union {
int32 user_id;
int32 group_id;
int32 supergroup_id;
};
};
ChatInfo *add_chat(int64 chat_id);
const ChatInfo *get_chat(int64 chat_id) const;
enum class ChatType { Private, Group, Supergroup, Channel, Unknown };
ChatType get_chat_type(int64 chat_id) const;
td::string get_chat_description(int64 chat_id) const;
struct MessageInfo {
mutable double access_time = 1e20;
mutable const MessageInfo *lru_next = nullptr;
mutable const MessageInfo *lru_prev = nullptr;
int64 id = 0;
int32 sender_user_id = 0;
int64 sender_chat_id = 0;
int64 chat_id = 0;
int32 date = 0;
int32 edit_date = 0;
int64 initial_chat_id = 0;
int32 initial_sender_user_id = 0;
int64 initial_sender_chat_id = 0;
int32 initial_send_date = 0;
int64 initial_message_id = 0;
td::string initial_author_signature;
td::string initial_sender_name;
td::string author_signature;
int64 reply_to_message_id = 0;
int64 media_album_id = 0;
int32 via_bot_user_id = 0;
object_ptr<td_api::MessageContent> content;
object_ptr<td_api::ReplyMarkup> reply_markup;
mutable bool is_reply_to_message_deleted = false;
mutable bool is_content_changed = false;
};
static int64 &get_reply_to_message_id(object_ptr<td_api::message> &message);
void set_message_reply_to_message_id(MessageInfo *message_info, int64 reply_to_message_id);
static td::CSlice get_callback_data(const object_ptr<td_api::InlineKeyboardButtonType> &type);
static bool are_equal_inline_keyboard_buttons(const td_api::inlineKeyboardButton *lhs,
const td_api::inlineKeyboardButton *rhs);
static bool are_equal_inline_keyboards(const td_api::replyMarkupInlineKeyboard *lhs,
const td_api::replyMarkupInlineKeyboard *rhs);
void set_message_reply_markup(MessageInfo *message_info, object_ptr<td_api::ReplyMarkup> &&reply_markup);
static int64 get_sticker_set_id(const object_ptr<td_api::MessageContent> &content);
bool have_sticker_set_name(int64 sticker_set_id) const;
Slice get_sticker_set_name(int64 sticker_set_id) const;
int32 choose_added_member_id(const td_api::messageChatAddMembers *message_add_members) const;
bool need_skip_update_message(int64 chat_id, const object_ptr<td_api::message> &message, bool is_edited) const;
void json_store_file(td::JsonObjectScope &object, const td_api::file *file, bool with_path = false) const;
void json_store_thumbnail(td::JsonObjectScope &object, const td_api::thumbnail *thumbnail) const;
static void json_store_callback_query_payload(td::JsonObjectScope &object,
const td_api::CallbackQueryPayload *payload);
static void json_store_permissions(td::JsonObjectScope &object, const td_api::chatPermissions *permissions);
void remove_replies_to_message(int64 chat_id, int64 reply_to_message_id, bool only_from_cache);
void delete_message(int64 chat_id, int64 message_id, bool only_from_cache);
static void delete_messages_lru(void *client_void);
void schedule_next_delete_messages_lru();
void update_message_lru(const MessageInfo *message_info) const;
void add_new_message(object_ptr<td_api::message> &&message, bool is_edited);
void process_new_message_queue(int64 chat_id);
struct FullMessageId {
int64 chat_id;
int64 message_id;
FullMessageId() : chat_id(0), message_id(0) {
}
FullMessageId(int64 chat_id, int64 message_id) : chat_id(chat_id), message_id(message_id) {
}
bool operator==(const FullMessageId &other) const {
return chat_id == other.chat_id && message_id == other.message_id;
}
};
struct FullMessageIdHash {
std::size_t operator()(FullMessageId full_message_id) const {
return std::hash<td::int64>()(full_message_id.chat_id) * 2023654985u +
std::hash<td::int64>()(full_message_id.message_id);
}
};
FullMessageId add_message(object_ptr<td_api::message> &&message, bool force_update_content = false);
const MessageInfo *get_message(int64 chat_id, int64 message_id) const;
MessageInfo *get_message_editable(int64 chat_id, int64 message_id);
void update_message_content(int64 chat_id, int64 message_id, object_ptr<td_api::MessageContent> &&content);
void on_update_message_edited(int64 chat_id, int64 message_id, int32 edit_date,
object_ptr<td_api::ReplyMarkup> &&reply_markup);
int32 get_unix_time() const;
static int64 as_tdlib_message_id(int32 message_id);
static int32 as_client_message_id(int64 message_id);
static int64 get_supergroup_chat_id(int32 supergroup_id);
static int64 get_basic_group_chat_id(int32 basic_group_id);
void add_update_poll(object_ptr<td_api::updatePoll> &&update);
void add_update_poll_answer(object_ptr<td_api::updatePollAnswer> &&update);
void add_new_inline_query(int64 inline_query_id, int32 sender_user_id, object_ptr<td_api::location> location,
const td::string &query, const td::string &offset);
void add_new_chosen_inline_result(int32 sender_user_id, object_ptr<td_api::location> location,
const td::string &query, const td::string &result_id,
const td::string &inline_message_id);
void add_new_callback_query(object_ptr<td_api::updateNewCallbackQuery> &&query);
void process_new_callback_query_queue(int32 user_id, int state);
void add_new_inline_callback_query(object_ptr<td_api::updateNewInlineCallbackQuery> &&query);
void add_new_shipping_query(object_ptr<td_api::updateNewShippingQuery> &&query);
void add_new_pre_checkout_query(object_ptr<td_api::updateNewPreCheckoutQuery> &&query);
void add_new_custom_event(object_ptr<td_api::updateNewCustomEvent> &&event);
void add_new_custom_query(object_ptr<td_api::updateNewCustomQuery> &&query);
// append only before Size
enum class UpdateType : int32 {
Message,
EditedMessage,
ChannelPost,
EditedChannelPost,
InlineQuery,
ChosenInlineResult,
CallbackQuery,
CustomEvent,
CustomQuery,
ShippingQuery,
PreCheckoutQuery,
Poll,
PollAnswer,
Size
};
static Slice get_update_type_name(UpdateType update_type);
static td::uint32 get_allowed_update_types(td::MutableSlice allowed_updates, bool is_internal);
bool update_allowed_update_types(const Query *query);
template <class T>
void add_update(UpdateType update_type, const T &update, int32 timeout, int64 webhook_queue_id);
void add_update_impl(UpdateType update_type, const td::VirtuallyJsonable &update, int32 timeout,
int64 webhook_queue_id);
std::size_t get_pending_update_count() const;
static bool is_chat_member(const object_ptr<td_api::ChatMemberStatus> &status);
static td::string get_chat_member_status(const object_ptr<td_api::ChatMemberStatus> &status);
static td::string get_passport_element_type(int32 id);
static object_ptr<td_api::PassportElementType> get_passport_element_type(Slice type);
bool have_message_access(int64 chat_id) const;
// by default all 13 update types up to PollAnswer are allowed
static constexpr td::uint32 DEFAULT_ALLOWED_UPDATE_TYPES = ((1 << 13) - 1);
object_ptr<td_api::AuthorizationState> authorization_state_;
bool was_authorized_ = false;
bool closing_ = false;
bool logging_out_ = false;
bool need_close_ = false;
bool clear_tqueue_ = false;
td::ActorShared<> parent_;
td::string bot_token_;
td::string bot_token_with_dc_;
td::string bot_token_id_;
bool is_test_dc_;
int64 tqueue_id_;
double start_timestamp_ = 0;
int32 my_id_ = -1;
int32 authorization_date_ = -1;
int32 group_anonymous_bot_user_id_ = 0;
int32 service_notifications_user_id_ = 0;
static std::unordered_map<td::string, Status (Client::*)(PromisedQueryPtr &query)> methods_;
MessageInfo messages_lru_root_;
std::unordered_map<FullMessageId, std::unique_ptr<MessageInfo>, FullMessageIdHash> messages_; // message cache
std::unordered_map<int32, UserInfo> users_; // user info cache
std::unordered_map<int32, GroupInfo> groups_; // group info cache
std::unordered_map<int32, SupergroupInfo> supergroups_; // supergroup info cache
std::unordered_map<int64, ChatInfo> chats_; // chat info cache
std::unordered_map<FullMessageId, std::unordered_set<int64>, FullMessageIdHash>
reply_message_ids_; // message -> replies to it
std::unordered_map<FullMessageId, std::unordered_set<int64>, FullMessageIdHash>
yet_unsent_reply_message_ids_; // message -> replies to it
td::Timeout next_delete_messages_lru_timeout_;
std::unordered_map<int32, td::vector<PromisedQueryPtr>> file_download_listeners_;
std::unordered_set<int32> download_started_file_ids_;
struct YetUnsentMessage {
int64 reply_to_message_id = 0;
bool is_reply_to_message_deleted = false;
int64 send_message_query_id = 0;
};
std::unordered_map<FullMessageId, YetUnsentMessage, FullMessageIdHash> yet_unsent_messages_;
struct PendingSendMessageQuery {
PromisedQueryPtr query;
bool is_multisend = false;
int32 awaited_messages = 0;
td::vector<td::string> messages;
object_ptr<td_api::error> error;
};
std::unordered_map<int64, PendingSendMessageQuery>
pending_send_message_queries_; // query_id -> PendingSendMessageQuery
int64 current_send_message_query_id_ = 1;
struct NewMessage {
object_ptr<td_api::message> message;
bool is_edited = false;
NewMessage(object_ptr<td_api::message> &&message, bool is_edited)
: message(std::move(message)), is_edited(is_edited) {
}
};
struct NewMessageQueue {
std::queue<NewMessage> queue_;
bool has_active_request_ = false;
};
std::unordered_map<int64, NewMessageQueue> new_message_queues_; // chat_id -> queue
struct NewCallbackQueryQueue {
std::queue<object_ptr<td_api::updateNewCallbackQuery>> queue_;
bool has_active_request_ = false;
};
std::unordered_map<int32, NewCallbackQueryQueue> new_callback_query_queues_; // sender_user_id -> queue
std::unordered_map<int64, td::string> sticker_set_names_;
int32 cur_temp_bot_user_id_ = 1;
std::unordered_map<td::string, int32> bot_user_ids_;
std::unordered_set<td::string> unresolved_bot_usernames_;
std::unordered_map<int32, int32> temp_to_real_bot_user_id_;
std::unordered_map<td::string, td::vector<int64>> awaiting_bot_resolve_queries_;
struct PendingBotResolveQuery {
std::size_t pending_resolve_count = 0;
PromisedQueryPtr query;
td::Promise<PromisedQueryPtr> on_success;
};
std::unordered_map<int64, PendingBotResolveQuery> pending_bot_resolve_queries_;
int64 current_bot_resolve_query_id_ = 1;
td::string dir_;
td::string absolute_dir_;
td::ActorOwn<td::ClientActor> td_client_;
td::ActorContext context_;
std::queue<PromisedQueryPtr> cmd_queue_;
td::vector<object_ptr<td_api::Object>> pending_updates_;
td::Container<std::unique_ptr<TdQueryCallback>> handlers_;
static constexpr int32 LONG_POLL_MAX_TIMEOUT = 50;
static constexpr double LONG_POLL_MAX_DELAY = 0.01;
static constexpr double LONG_POLL_WAIT_AFTER = 0.002;
int32 long_poll_limit_ = 0;
int32 long_poll_offset_ = 0;
bool long_poll_was_wakeup_ = false;
double long_poll_hard_timeout_ = 0;
td::Slot long_poll_slot_;
PromisedQueryPtr long_poll_query_;
static constexpr int32 BOT_UPDATES_WARNING_DELAY = 30;
double next_bot_updates_warning_date_ = 0;
bool was_bot_updates_warning_ = false;
td::uint32 allowed_update_types_ = DEFAULT_ALLOWED_UPDATE_TYPES;
bool has_webhook_certificate_ = false;
enum class WebhookQueryType { Cancel, Verify };
WebhookQueryType webhook_query_type_ = WebhookQueryType::Cancel;
td::ActorOwn<WebhookActor> webhook_id_;
PromisedQueryPtr webhook_set_query_;
td::string webhook_url_;
double webhook_set_date_ = 0;
int32 webhook_max_connections_ = 0;
td::string webhook_ip_address_;
bool webhook_fix_ip_address_ = false;
int32 last_webhook_error_date_ = 0;
Status last_webhook_error_;
double next_allowed_set_webhook_date_ = 0;
double next_set_webhook_logging_date_ = 0;
double next_webhook_is_not_modified_warning_date_ = 0;
std::size_t last_pending_update_count_ = MIN_PENDING_UPDATES_WARNING;
double local_unix_time_difference_ = 0; // Unix time - now()
int32 previous_get_updates_offset_ = -1;
double previous_get_updates_start_date_ = 0;
double previous_get_updates_finish_date_ = 0;
double next_conflict_response_date_ = 0;
td::uint64 webhook_generation_ = 1;
std::shared_ptr<const ClientParameters> parameters_;
td::ActorId<BotStatActor> stat_actor_;
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,406 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/ClientManager.h"
#include "telegram-bot-api/Client.h"
#include "telegram-bot-api/ClientParameters.h"
#include "telegram-bot-api/WebhookActor.h"
#include "td/telegram/ClientActor.h"
#include "td/db/binlog/Binlog.h"
#include "td/db/binlog/ConcurrentBinlog.h"
#include "td/db/BinlogKeyValue.h"
#include "td/db/DbKey.h"
#include "td/db/TQueue.h"
#include "td/net/HttpFile.h"
#include "td/actor/MultiPromise.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/Parser.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Slice.h"
#include "td/utils/StackAllocator.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Time.h"
#include <map>
#include <tuple>
namespace telegram_bot_api {
void ClientManager::close(td::Promise<td::Unit> &&promise) {
close_promises_.push_back(std::move(promise));
if (close_flag_) {
return;
}
close_flag_ = true;
auto ids = clients_.ids();
for (auto id : ids) {
auto *client_info = clients_.get(id);
CHECK(client_info);
send_closure(client_info->client_, &Client::close);
}
if (ids.empty()) {
close_db();
}
}
void ClientManager::send(PromisedQueryPtr query) {
if (close_flag_) {
// automatically send 429
return;
}
td::string token = query->token().str();
if (token[0] == '0' || token.size() > 80u || token.find('/') != td::string::npos ||
token.find(':') == td::string::npos) {
return fail_query(401, "Unauthorized: invalid token specified", std::move(query));
}
auto r_user_id = td::to_integer_safe<td::int64>(query->token().substr(0, token.find(':')));
if (r_user_id.is_error() || r_user_id.ok() < 0 || !token_range_(r_user_id.ok())) {
return fail_query(401, "Unauthorized: unallowed token specified", std::move(query));
}
if (query->is_test_dc()) {
token += "/test";
}
auto id_it = token_to_id_.find(token);
if (id_it == token_to_id_.end()) {
std::string ip_address;
if (query->peer_address().is_valid() && !query->peer_address().is_reserved()) { // external connection
ip_address = query->peer_address().get_ip_str().str();
} else {
// invalid peer address or connection from the local network
ip_address = query->get_header("x-real-ip").str();
}
if (!ip_address.empty()) {
td::IPAddress tmp;
tmp.init_host_port(ip_address, 0).ignore();
tmp.clear_ipv6_interface();
if (tmp.is_valid()) {
ip_address = tmp.get_ip_str().str();
}
}
LOG(DEBUG) << "Receive incoming query for new bot " << token << " from " << query->peer_address();
if (!ip_address.empty()) {
LOG(DEBUG) << "Check Client creation flood control for IP address " << ip_address;
auto res = flood_controls_.emplace(std::move(ip_address), td::FloodControlFast());
auto &flood_control = res.first->second;
if (res.second) {
flood_control.add_limit(60, 20); // 20 in a minute
flood_control.add_limit(60 * 60, 600); // 600 in an hour
}
td::uint32 now = static_cast<td::uint32>(td::Time::now());
td::uint32 wakeup_at = flood_control.get_wakeup_at();
if (wakeup_at > now) {
LOG(INFO) << "Failed to create Client from IP address " << ip_address;
return query->set_retry_after_error(static_cast<int>(wakeup_at - now) + 1);
}
flood_control.add_event(static_cast<td::int32>(now));
}
auto id = clients_.create(ClientInfo{BotStatActor(stat_.actor_id(&stat_)), token, td::ActorOwn<Client>()});
auto *client_info = clients_.get(id);
auto stat_actor = client_info->stat_.actor_id(&client_info->stat_);
auto client_id = td::create_actor<Client>(
PSLICE() << "Client/" << token, actor_shared(this, id), query->token().str(), query->is_test_dc(),
get_tqueue_id(r_user_id.ok(), query->is_test_dc()), parameters_, std::move(stat_actor));
auto method = query->method();
if (method != "deletewebhook" && method != "setwebhook") {
auto bot_token_with_dc = PSTRING() << query->token() << (query->is_test_dc() ? ":T" : "");
auto webhook_info = parameters_->shared_data_->webhook_db_->get(bot_token_with_dc);
if (!webhook_info.empty()) {
send_closure(client_id, &Client::send,
get_webhook_restore_query(bot_token_with_dc, webhook_info, parameters_->shared_data_));
}
}
clients_.get(id)->client_ = std::move(client_id);
std::tie(id_it, std::ignore) = token_to_id_.emplace(token, id);
}
auto *client_info = clients_.get(id_it->second);
if (!query->is_internal()) {
query->set_stat_actor(client_info->stat_.actor_id(&client_info->stat_));
}
send_closure(client_info->client_, &Client::send, std::move(query)); // will send 429 if the client is already closed
}
void ClientManager::get_stats(td::PromiseActor<td::BufferSlice> promise,
td::vector<std::pair<td::string, td::string>> args) {
if (close_flag_) {
promise.set_value(td::BufferSlice("Closing"));
return;
}
size_t buf_size = 1 << 14;
auto buf = td::StackAllocator::alloc(buf_size);
td::StringBuilder sb(buf.as_slice());
td::Slice id_filter;
for (auto &arg : args) {
if (arg.first == "id") {
id_filter = arg.second;
}
if (arg.first == "v") {
auto r_verbosity = td::to_integer_safe<int>(arg.second);
if (r_verbosity.is_ok()) {
parameters_->shared_data_->next_verbosity_level_ = r_verbosity.ok();
}
}
}
auto now = td::Time::now();
td::int32 active_bot_count = 0;
std::multimap<td::int64, td::uint64> top_bot_ids;
for (auto id : clients_.ids()) {
auto *client_info = clients_.get(id);
CHECK(client_info);
if (client_info->stat_.is_active(now)) {
active_bot_count++;
}
if (!td::begins_with(client_info->token_, id_filter)) {
continue;
}
auto stats = client_info->stat_.as_vector(now);
double score = 0.0;
for (auto &stat : stats) {
if (stat.key_ == "update_count" || stat.key_ == "request_count") {
score -= td::to_double(stat.value_);
}
}
top_bot_ids.emplace(static_cast<td::int64>(score * 1e9), id);
}
sb << stat_.get_description() << "\n";
if (id_filter.empty()) {
sb << "uptime\t" << now - parameters_->start_timestamp_ << "\n";
sb << "bot_count\t" << clients_.size() << "\n";
sb << "active_bot_count\t" << active_bot_count << "\n";
auto r_mem_stat = td::mem_stat();
if (r_mem_stat.is_ok()) {
auto mem_stat = r_mem_stat.move_as_ok();
sb << "rss\t" << td::format::as_size(mem_stat.resident_size_) << "\n";
sb << "vm\t" << td::format::as_size(mem_stat.virtual_size_) << "\n";
sb << "rss_peak\t" << td::format::as_size(mem_stat.resident_size_peak_) << "\n";
sb << "vm_peak\t" << td::format::as_size(mem_stat.virtual_size_peak_) << "\n";
} else {
LOG(INFO) << "Failed to get memory statistics: " << r_mem_stat.error();
}
ServerCpuStat::update(td::Time::now());
auto cpu_stats = ServerCpuStat::instance().as_vector(td::Time::now());
for (auto &stat : cpu_stats) {
sb << stat.key_ << "\t" << stat.value_ << "\n";
}
sb << "buffer_memory\t" << td::format::as_size(td::BufferAllocator::get_buffer_mem()) << "\n";
sb << "active_webhook_connections\t" << WebhookActor::get_total_connections_count() << "\n";
sb << "active_requests\t" << parameters_->shared_data_->query_count_.load() << "\n";
sb << "active_network_queries\t" << td::get_pending_network_query_count(*parameters_->net_query_stats_) << "\n";
auto stats = stat_.as_vector(now);
for (auto &stat : stats) {
sb << stat.key_ << "\t" << stat.value_ << "\n";
}
}
for (auto top_bot_id : top_bot_ids) {
auto *client_info = clients_.get(top_bot_id.second);
CHECK(client_info);
auto bot_info = client_info->client_->get_actor_unsafe()->get_bot_info();
sb << "\n";
sb << "id\t" << bot_info.id_ << "\n";
sb << "uptime\t" << now - bot_info.start_timestamp_ << "\n";
sb << "token\t" << bot_info.token_ << "\n";
sb << "username\t" << bot_info.username_ << "\n";
sb << "webhook\t" << bot_info.webhook_ << "\n";
sb << "has_custom_certificate\t" << bot_info.has_webhook_certificate_ << "\n";
sb << "head_update_id\t" << bot_info.head_update_id_ << "\n";
sb << "tail_update_id\t" << bot_info.tail_update_id_ << "\n";
sb << "pending_update_count\t" << bot_info.pending_update_count_ << "\n";
sb << "webhook_max_connections\t" << bot_info.webhook_max_connections_ << "\n";
auto stats = client_info->stat_.as_vector(now);
for (auto &stat : stats) {
if (stat.key_ == "update_count" || stat.key_ == "request_count") {
sb << stat.key_ << "/sec\t" << stat.value_ << "\n";
}
}
if (sb.is_error()) {
break;
}
}
// ignore sb overflow
promise.set_value(td::BufferSlice(sb.as_cslice()));
}
td::int64 ClientManager::get_tqueue_id(td::int64 user_id, bool is_test_dc) {
return user_id + (static_cast<td::int64>(is_test_dc) << 54);
}
void ClientManager::start_up() {
//NB: the same scheduler as for database in Td
auto current_scheduler_id = td::Scheduler::instance()->sched_id();
auto scheduler_count = td::Scheduler::instance()->sched_count();
auto scheduler_id = td::min(current_scheduler_id + 1, scheduler_count - 1);
// init tqueue
{
auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
auto binlog = td::make_unique<td::Binlog>();
auto tqueue = td::TQueue::create();
td::vector<td::uint64> failed_to_replay_log_event_ids;
td::int64 loaded_event_count = 0;
binlog
->init("tqueue.binlog",
[&](const td::BinlogEvent &event) {
if (tqueue_binlog->replay(event, *tqueue).is_error()) {
failed_to_replay_log_event_ids.push_back(event.id_);
} else {
loaded_event_count++;
}
})
.ensure();
tqueue_binlog.reset();
LOG(WARNING) << "Loaded " << loaded_event_count << " TQueue events";
if (!failed_to_replay_log_event_ids.empty()) {
LOG(ERROR) << "Failed to replay " << failed_to_replay_log_event_ids.size() << " TQueue events";
for (auto &log_event_id : failed_to_replay_log_event_ids) {
binlog->erase(log_event_id);
}
}
auto concurrent_binlog = std::make_shared<td::ConcurrentBinlog>(std::move(binlog), scheduler_id);
auto concurrent_tqueue_binlog = td::make_unique<td::TQueueBinlog<td::BinlogInterface>>();
concurrent_tqueue_binlog->set_binlog(std::move(concurrent_binlog));
tqueue->set_callback(std::move(concurrent_tqueue_binlog));
parameters_->shared_data_->tqueue_ = std::move(tqueue);
}
// init webhook_db
auto concurrent_webhook_db = td::make_unique<td::BinlogKeyValue<td::ConcurrentBinlog>>();
auto status = concurrent_webhook_db->init("webhooks_db.binlog", td::DbKey::empty(), scheduler_id);
LOG_IF(FATAL, status.is_error()) << "Can't open webhooks_db.binlog " << status.error();
parameters_->shared_data_->webhook_db_ = std::move(concurrent_webhook_db);
auto &webhook_db = *parameters_->shared_data_->webhook_db_;
for (auto key_value : webhook_db.get_all()) {
if (!token_range_(td::to_integer<td::uint64>(key_value.first))) {
LOG(WARNING) << "DROP WEBHOOK: " << key_value.first << " ---> " << key_value.second;
webhook_db.erase(key_value.first);
continue;
}
auto query = get_webhook_restore_query(key_value.first, key_value.second, parameters_->shared_data_);
send_closure_later(actor_id(this), &ClientManager::send, std::move(query));
}
}
PromisedQueryPtr ClientManager::get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
std::shared_ptr<SharedData> shared_data) {
// create Query with empty promise
td::vector<td::BufferSlice> containers;
auto add_string = [&containers](td::Slice str) {
containers.emplace_back(str);
return containers.back().as_slice();
};
token = add_string(token);
LOG(WARNING) << "WEBHOOK: " << token << " ---> " << webhook_info;
bool is_test_dc = false;
if (td::ends_with(token, ":T")) {
token.remove_suffix(2);
is_test_dc = true;
}
td::ConstParser parser{webhook_info};
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> args;
if (parser.try_skip("cert/")) {
args.emplace_back(add_string("certificate"), add_string("previous"));
}
if (parser.try_skip("#maxc")) {
args.emplace_back(add_string("max_connections"), add_string(parser.read_till('/')));
parser.skip('/');
}
if (parser.try_skip("#ip")) {
args.emplace_back(add_string("ip_address"), add_string(parser.read_till('/')));
parser.skip('/');
}
if (parser.try_skip("#fix_ip")) {
args.emplace_back(add_string("fix_ip_address"), add_string("1"));
parser.skip('/');
}
if (parser.try_skip("#allow")) {
args.emplace_back(add_string("allowed_updates"), add_string(parser.read_till('/')));
parser.skip('/');
}
args.emplace_back(add_string("url"), add_string(parser.read_all()));
const auto method = add_string("setwebhook");
auto query = std::make_unique<Query>(std::move(containers), token, is_test_dc, method, std::move(args),
td::vector<std::pair<td::MutableSlice, td::MutableSlice>>(),
td::vector<td::HttpFile>(), std::move(shared_data), td::IPAddress());
query->set_internal(true);
return PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
}
void ClientManager::hangup_shared() {
auto id = get_link_token();
auto *info = clients_.get(id);
CHECK(info != nullptr);
info->client_.release();
token_to_id_.erase(info->token_);
clients_.erase(id);
if (close_flag_ && clients_.empty()) {
close_db();
}
}
void ClientManager::close_db() {
LOG(WARNING) << "Closing databases";
td::MultiPromiseActorSafe mpromise("close binlogs");
mpromise.add_promise(td::PromiseCreator::lambda(
[actor_id = actor_id(this)](td::Unit) { send_closure(actor_id, &ClientManager::finish_close); }));
parameters_->shared_data_->tqueue_->close(mpromise.get_promise());
parameters_->shared_data_->webhook_db_->close(mpromise.get_promise());
}
void ClientManager::finish_close() {
LOG(WARNING) << "Stop ClientManager";
auto promises = std::move(close_promises_);
for (auto &promise : promises) {
promise.set_value(td::Unit());
}
stop();
}
} // namespace telegram_bot_api

View File

@ -0,0 +1,80 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/Client.h"
#include "telegram-bot-api/Query.h"
#include "telegram-bot-api/Stats.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/FloodControlFast.h"
#include "td/utils/Slice.h"
#include <memory>
#include <unordered_map>
#include <utility>
namespace telegram_bot_api {
struct ClientParameters;
struct SharedData;
class ClientManager final : public td::Actor {
public:
struct TokenRange {
td::uint64 rem;
td::uint64 mod;
bool operator()(td::uint64 x) {
return x % mod == rem;
}
};
ClientManager(std::shared_ptr<const ClientParameters> parameters, TokenRange token_range)
: parameters_(std::move(parameters)), token_range_(token_range) {
}
void send(PromisedQueryPtr query);
void get_stats(td::PromiseActor<td::BufferSlice> promise, td::vector<std::pair<td::string, td::string>> args);
void close(td::Promise<td::Unit> &&promise);
private:
class ClientInfo {
public:
BotStatActor stat_;
td::string token_;
td::ActorOwn<Client> client_;
};
td::Container<ClientInfo> clients_;
BotStatActor stat_{td::ActorId<BotStatActor>()};
std::shared_ptr<const ClientParameters> parameters_;
TokenRange token_range_;
std::unordered_map<td::string, td::uint64> token_to_id_;
std::unordered_map<td::string, td::FloodControlFast> flood_controls_;
bool close_flag_ = false;
td::vector<td::Promise<td::Unit>> close_promises_;
static td::int64 get_tqueue_id(td::int64 user_id, bool is_test_dc);
static PromisedQueryPtr get_webhook_restore_query(td::Slice token, td::Slice webhook_info,
std::shared_ptr<SharedData> shared_data);
void start_up() override;
void hangup_shared() override;
void close_db();
void finish_close();
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,74 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/actor/actor.h"
#include "td/db/KeyValueSyncInterface.h"
#include "td/db/TQueue.h"
#include "td/net/GetHostByNameActor.h"
#include "td/utils/common.h"
#include "td/utils/List.h"
#include "td/utils/port/IPAddress.h"
#include <atomic>
#include <limits>
#include <memory>
namespace td {
class NetQueryStats;
}
namespace telegram_bot_api {
struct SharedData {
std::atomic<td::uint64> query_count_{0};
std::atomic<int> next_verbosity_level_{-1};
// not thread-safe
td::ListNode query_list_;
td::unique_ptr<td::KeyValueSyncInterface> webhook_db_;
td::unique_ptr<td::TQueue> tqueue_;
double unix_time_difference_{-1e100};
static constexpr size_t TQUEUE_EVENT_BUFFER_SIZE = 1000;
td::TQueue::Event event_buffer_[TQUEUE_EVENT_BUFFER_SIZE];
td::int32 get_unix_time(double now) const {
auto result = unix_time_difference_ + now;
if (result <= 0) {
return 0;
}
if (result >= std::numeric_limits<td::int32>::max()) {
return std::numeric_limits<td::int32>::max();
}
return static_cast<td::int32>(result);
}
};
struct ClientParameters {
bool local_mode_ = false;
td::int32 api_id_ = 0;
td::string api_hash_;
td::int32 default_max_webhook_connections_ = 0;
td::IPAddress webhook_proxy_ip_address_;
double start_timestamp_ = 0;
td::ActorId<td::GetHostByNameActor> get_host_by_name_actor_id_;
std::shared_ptr<SharedData> shared_data_;
std::shared_ptr<td::NetQueryStats> net_query_stats_;
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,97 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/HttpConnection.h"
#include "telegram-bot-api/Query.h"
#include "td/net/HttpHeaderCreator.h"
#include "td/utils/common.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/Parser.h"
namespace telegram_bot_api {
void HttpConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
td::ActorOwn<td::HttpInboundConnection> connection) {
CHECK(connection_->empty());
connection_ = std::move(connection);
LOG(DEBUG) << "Handle " << *http_query;
td::Parser url_path_parser(http_query->url_path_);
if (url_path_parser.peek_char() != '/') {
return send_http_error(404, "Not Found: absolute URI is specified in the Request-Line");
}
if (!url_path_parser.try_skip("/bot")) {
return send_http_error(404, "Not Found");
}
auto token = url_path_parser.read_till('/');
bool is_test_dc = false;
if (url_path_parser.try_skip("/test")) {
is_test_dc = true;
}
url_path_parser.skip('/');
if (url_path_parser.status().is_error()) {
return send_http_error(404, "Not Found");
}
auto method = url_path_parser.data();
auto query = std::make_unique<Query>(std::move(http_query->container_), token, is_test_dc, method,
std::move(http_query->args_), std::move(http_query->headers_),
std::move(http_query->files_), shared_data_, http_query->peer_address_);
td::PromiseActor<td::unique_ptr<Query>> promise;
td::FutureActor<td::unique_ptr<Query>> future;
td::init_promise_future(&promise, &future);
future.set_event(td::EventCreator::yield(actor_id()));
auto promised_query = PromisedQueryPtr(query.release(), PromiseDeleter(std::move(promise)));
send_closure(client_manager_, &ClientManager::send, std::move(promised_query));
result_ = std::move(future);
}
void HttpConnection::wakeup() {
if (result_.empty()) {
return;
}
LOG_CHECK(result_.is_ok()) << result_.move_as_error();
auto query = result_.move_as_ok();
send_response(query->http_status_code(), std::move(query->answer()), query->retry_after());
}
void HttpConnection::send_response(int http_status_code, td::BufferSlice &&content, int retry_after) {
td::HttpHeaderCreator hc;
hc.init_status_line(http_status_code);
hc.set_keep_alive();
hc.set_content_type("application/json");
if (retry_after > 0) {
hc.add_header("Retry-After", PSLICE() << retry_after);
}
hc.set_content_size(content.size());
auto r_header = hc.finish();
LOG(DEBUG) << "Response headers: " << r_header.ok();
if (r_header.is_error()) {
LOG(ERROR) << "Bad response headers";
send_closure(std::move(connection_), &td::HttpInboundConnection::write_error, r_header.move_as_error());
return;
}
LOG(DEBUG) << "Send result: " << content;
send_closure(connection_, &td::HttpInboundConnection::write_next_noflush, td::BufferSlice(r_header.ok()));
send_closure(connection_, &td::HttpInboundConnection::write_next_noflush, std::move(content));
send_closure(std::move(connection_), &td::HttpInboundConnection::write_ok);
}
void HttpConnection::send_http_error(int http_status_code, td::Slice description) {
send_response(http_status_code, td::json_encode<td::BufferSlice>(JsonQueryError(http_status_code, description)), 0);
}
} // namespace telegram_bot_api

View File

@ -0,0 +1,53 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/ClientManager.h"
#include "telegram-bot-api/Query.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/net/HttpInboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/utils/buffer.h"
#include "td/utils/Slice.h"
#include <memory>
namespace telegram_bot_api {
struct SharedData;
class HttpConnection : public td::HttpInboundConnection::Callback {
public:
explicit HttpConnection(td::ActorId<ClientManager> client_manager, std::shared_ptr<SharedData> shared_data)
: client_manager_(client_manager), shared_data_(std::move(shared_data)) {
}
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) override;
void wakeup() override;
private:
td::FutureActor<td::unique_ptr<Query>> result_;
td::ActorId<ClientManager> client_manager_;
td::ActorOwn<td::HttpInboundConnection> connection_;
std::shared_ptr<SharedData> shared_data_;
void hangup() override {
connection_.release();
stop();
}
void send_response(int http_status_code, td::BufferSlice &&content, int retry_after);
void send_http_error(int http_status_code, td::Slice description);
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,72 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/actor/actor.h"
#include "td/net/HttpInboundConnection.h"
#include "td/net/TcpListener.h"
#include "td/utils/FloodControlFast.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Time.h"
#include <functional>
namespace telegram_bot_api {
class HttpServer : public td::TcpListener::Callback {
public:
HttpServer(int port, std::function<td::ActorOwn<td::HttpInboundConnection::Callback>()> creator)
: port_(port), creator_(std::move(creator)) {
flood_control_.add_limit(1, 1); // 1 in a second
flood_control_.add_limit(60, 10); // 10 in a minute
}
private:
td::int32 port_;
std::function<td::ActorOwn<td::HttpInboundConnection::Callback>()> creator_;
td::ActorOwn<td::TcpListener> listener_;
td::FloodControlFast flood_control_;
void start_up() override {
auto now = td::Time::now();
auto wakeup_at = flood_control_.get_wakeup_at();
if (wakeup_at > now) {
set_timeout_at(wakeup_at);
return;
}
flood_control_.add_event(static_cast<td::int32>(now));
LOG(INFO) << "Create tcp listener " << td::tag("port", port_);
listener_ = td::create_actor<td::TcpListener>(PSLICE() << "TcpListener" << td::tag("port", port_), port_,
actor_shared(this, 1));
}
void hangup_shared() override {
LOG(ERROR) << "TCP listener was closed";
listener_.release();
yield();
}
void accept(td::SocketFd fd) override {
auto scheduler_count = td::Scheduler::instance()->sched_count();
auto scheduler_id = scheduler_count - 1;
if (scheduler_id > 0) {
scheduler_id--;
}
td::create_actor<td::HttpInboundConnection>("HttpInboundConnection", std::move(fd), 0, 20, 500, creator_(),
scheduler_id)
.release();
}
void loop() override {
if (listener_.empty()) {
start_up();
}
}
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,53 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/HttpStatConnection.h"
#include "td/net/HttpHeaderCreator.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
namespace telegram_bot_api {
void HttpStatConnection::handle(td::unique_ptr<td::HttpQuery> http_query,
td::ActorOwn<td::HttpInboundConnection> connection) {
CHECK(connection_->empty());
connection_ = std::move(connection);
td::PromiseActor<td::BufferSlice> promise;
td::FutureActor<td::BufferSlice> future;
init_promise_future(&promise, &future);
future.set_event(td::EventCreator::yield(actor_id()));
LOG(DEBUG) << "SEND";
send_closure(client_manager_, &ClientManager::get_stats, std::move(promise), http_query->get_args());
result_ = std::move(future);
}
void HttpStatConnection::wakeup() {
if (result_.empty()) {
return;
}
LOG_CHECK(result_.is_ok()) << result_.move_as_error();
auto content = result_.move_as_ok();
td::HttpHeaderCreator hc;
hc.init_status_line(200);
hc.set_keep_alive();
hc.set_content_type("text/plain");
hc.set_content_size(content.size());
auto r_header = hc.finish();
if (r_header.is_error()) {
send_closure(connection_.release(), &td::HttpInboundConnection::write_error, r_header.move_as_error());
return;
}
send_closure(connection_, &td::HttpInboundConnection::write_next_noflush, td::BufferSlice(r_header.ok()));
send_closure(connection_, &td::HttpInboundConnection::write_next_noflush, std::move(content));
send_closure(connection_.release(), &td::HttpInboundConnection::write_ok);
}
} // namespace telegram_bot_api

View File

@ -0,0 +1,40 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/ClientManager.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/net/HttpInboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/utils/buffer.h"
namespace telegram_bot_api {
class HttpStatConnection : public td::HttpInboundConnection::Callback {
public:
explicit HttpStatConnection(td::ActorId<ClientManager> client_manager) : client_manager_(client_manager) {
}
void handle(td::unique_ptr<td::HttpQuery> http_query, td::ActorOwn<td::HttpInboundConnection> connection) override;
void wakeup() override;
private:
td::FutureActor<td::BufferSlice> result_;
td::ActorId<ClientManager> client_manager_;
td::ActorOwn<td::HttpInboundConnection> connection_;
void hangup() override {
connection_.release();
stop();
}
};
} // namespace telegram_bot_api

119
telegram-bot-api/Query.cpp Normal file
View File

@ -0,0 +1,119 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/Query.h"
#include "telegram-bot-api/Stats.h"
#include "td/actor/actor.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/Time.h"
#include <numeric>
namespace telegram_bot_api {
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
Query::Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address)
: state_(State::Query)
, shared_data_(shared_data)
, peer_address_(peer_address)
, container_(std::move(container))
, token_(token)
, is_test_dc_(is_test_dc)
, method_(method)
, args_(std::move(args))
, headers_(std::move(headers))
, files_(std::move(files)) {
if (method_.empty()) {
method_ = arg("method");
}
td::to_lower_inplace(method_);
start_timestamp_ = td::Time::now();
LOG(INFO) << "QUERY: create " << td::tag("ptr", this) << *this;
if (shared_data_) {
shared_data_->query_count_++;
if (method_ != "getupdates") {
shared_data_->query_list_.put(this);
}
}
}
td::int64 Query::query_size() const {
return std::accumulate(
container_.begin(), container_.end(), td::int64{0},
[](td::int64 acc, const td::BufferSlice &slice) { return static_cast<td::int64>(acc + slice.size()); });
}
td::int64 Query::files_size() const {
return std::accumulate(files_.begin(), files_.end(), td::int64{0},
[](td::int64 acc, const td::HttpFile &file) { return acc + file.size; });
}
td::int64 Query::files_max_size() const {
return std::accumulate(files_.begin(), files_.end(), td::int64{0},
[](td::int64 acc, const td::HttpFile &file) { return td::max(acc, file.size); });
}
void Query::set_ok(td::BufferSlice result) {
CHECK(state_ == State::Query);
LOG(INFO) << "QUERY: got ok " << td::tag("ptr", this) << td::tag("text", result.as_slice());
answer_ = std::move(result);
state_ = State::OK;
http_status_code_ = 200;
send_response_stat();
}
void Query::set_error(int http_status_code, td::BufferSlice result) {
LOG(INFO) << "QUERY: got error " << td::tag("ptr", this) << td::tag("code", http_status_code)
<< td::tag("text", result.as_slice());
CHECK(state_ == State::Query);
answer_ = std::move(result);
state_ = State::Error;
http_status_code_ = http_status_code;
send_response_stat();
}
void Query::set_retry_after_error(int retry_after) {
retry_after_ = retry_after;
std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> parameters;
parameters.emplace("retry_after", std::make_unique<td::VirtuallyJsonableLong>(retry_after));
set_error(429, td::json_encode<td::BufferSlice>(
JsonQueryError(429, PSLICE() << "Too Many Requests: retry after " << retry_after, parameters)));
}
td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query) {
auto padded_time =
td::lpad(PSTRING() << td::format::as_time(td::Time::now_cached() - query.start_timestamp()), 10, ' ');
sb << "[bot" << td::rpad(query.token().str(), 46, ' ') << "][time:" << padded_time << ']'
<< td::tag("method", td::lpad(query.method().str(), 20, ' '));
if (!query.args().empty()) {
sb << td::oneline(PSLICE() << query.args());
}
if (!query.files().empty()) {
sb << query.files();
}
return sb;
}
void Query::send_response_stat() {
if (stat_actor_.empty()) {
return;
}
send_closure(stat_actor_, &BotStatActor::add_event<ServerBotStat::Response>,
ServerBotStat::Response{is_ok(), answer().size()}, td::Time::now());
}
} // namespace telegram_bot_api

278
telegram-bot-api/Query.h Normal file
View File

@ -0,0 +1,278 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/ClientParameters.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/net/HttpFile.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/List.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/Slice.h"
#include "td/utils/StringBuilder.h"
#include <algorithm>
#include <memory>
#include <unordered_map>
#include <utility>
namespace telegram_bot_api {
class BotStatActor;
class Query : public td::ListNode {
public:
enum class State : td::int8 { Query, OK, Error };
td::Slice token() const {
return token_;
}
bool is_test_dc() const {
return is_test_dc_;
}
td::Slice method() const {
return method_;
}
bool has_arg(td::Slice key) const {
auto it = std::find_if(args_.begin(), args_.end(),
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
return it != args_.end();
}
td::MutableSlice arg(td::Slice key) const {
auto it = std::find_if(args_.begin(), args_.end(),
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
return it == args_.end() ? td::MutableSlice() : it->second;
}
const td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &args() const {
return args_;
}
td::Slice get_header(td::Slice key) const {
auto it = std::find_if(headers_.begin(), headers_.end(),
[&key](const std::pair<td::MutableSlice, td::MutableSlice> &s) { return s.first == key; });
return it == headers_.end() ? td::Slice() : it->second;
}
const td::HttpFile *file(td::Slice key) const {
auto it = std::find_if(files_.begin(), files_.end(), [&key](const td::HttpFile &f) { return f.field_name == key; });
return it == files_.end() ? nullptr : &*it;
}
const td::vector<td::HttpFile> &files() const {
return files_;
}
const td::IPAddress &peer_address() const {
return peer_address_;
}
// for stats
td::int32 file_count() const {
return static_cast<td::int32>(files_.size());
}
td::int64 query_size() const;
td::int64 files_size() const;
td::int64 files_max_size() const;
td::BufferSlice &answer() {
return answer_;
}
int http_status_code() const {
return http_status_code_;
}
int retry_after() const {
return retry_after_;
}
void set_ok(td::BufferSlice result);
void set_error(int http_status_code, td::BufferSlice result);
void set_retry_after_error(int retry_after);
bool is_ready() const {
return state_ != State::Query;
}
bool is_error() const {
return state_ == State::Error;
}
bool is_ok() const {
return state_ == State::OK;
}
bool is_internal() const {
return is_internal_;
}
void set_internal(bool is_internal) {
is_internal_ = is_internal;
}
Query(td::vector<td::BufferSlice> &&container, td::Slice token, bool is_test_dc, td::MutableSlice method,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&args,
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> &&headers, td::vector<td::HttpFile> &&files,
std::shared_ptr<SharedData> shared_data, const td::IPAddress &peer_address);
Query(const Query &) = delete;
Query &operator=(const Query &) = delete;
Query(Query &&) = delete;
Query &operator=(Query &&) = delete;
~Query() {
if (shared_data_) {
shared_data_->query_count_--;
}
}
double start_timestamp() const {
return start_timestamp_;
}
void set_stat_actor(td::ActorId<BotStatActor> stat_actor) {
stat_actor_ = stat_actor;
}
void send_response_stat();
private:
State state_;
std::shared_ptr<SharedData> shared_data_;
double start_timestamp_;
td::IPAddress peer_address_;
td::ActorId<BotStatActor> stat_actor_;
// request
td::vector<td::BufferSlice> container_;
td::Slice token_;
bool is_test_dc_;
td::MutableSlice method_;
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> args_;
td::vector<std::pair<td::MutableSlice, td::MutableSlice>> headers_;
td::vector<td::HttpFile> files_;
bool is_internal_ = false;
// response
td::BufferSlice answer_;
int http_status_code_ = 0;
int retry_after_ = 0;
};
td::StringBuilder &operator<<(td::StringBuilder &sb, const Query &query);
// fix for outdated C++14 libraries
// https://stackoverflow.com/questions/26947704/implicit-conversion-failure-from-initializer-list
extern std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> empty_parameters;
class JsonParameters : public td::Jsonable {
public:
explicit JsonParameters(const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> &parameters)
: parameters_(parameters) {
}
void store(td::JsonValueScope *scope) const {
auto object = scope->enter_object();
for (auto &parameter : parameters_) {
CHECK(parameter.second != nullptr);
object(parameter.first, *parameter.second);
}
}
private:
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> &parameters_;
};
template <class T>
class JsonQueryOk : public td::Jsonable {
public:
JsonQueryOk(const T &result, td::Slice description) : result_(result), description_(description) {
}
void store(td::JsonValueScope *scope) const {
auto object = scope->enter_object();
object("ok", td::JsonTrue());
object("result", result_);
if (!description_.empty()) {
object("description", description_);
}
}
private:
const T &result_;
td::Slice description_;
};
class JsonQueryError : public td::Jsonable {
public:
JsonQueryError(
int error_code, td::Slice description,
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> &parameters = empty_parameters)
: error_code_(error_code), description_(description), parameters_(parameters) {
}
void store(td::JsonValueScope *scope) const {
auto object = scope->enter_object();
object("ok", td::JsonFalse());
object("error_code", error_code_);
object("description", description_);
if (!parameters_.empty()) {
object("parameters", JsonParameters(parameters_));
}
}
private:
int error_code_;
td::Slice description_;
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> &parameters_;
};
class PromiseDeleter {
public:
explicit PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>> &&promise) : promise_(std::move(promise)) {
}
PromiseDeleter() = default;
PromiseDeleter(const PromiseDeleter &) = delete;
PromiseDeleter &operator=(const PromiseDeleter &) = delete;
PromiseDeleter(PromiseDeleter &&) = default;
PromiseDeleter &operator=(PromiseDeleter &&) = default;
void operator()(Query *raw_ptr) {
td::unique_ptr<Query> query(raw_ptr); // now I cannot forget to delete this pointer
if (!promise_.empty_promise()) {
if (!query->is_ready()) {
query->set_retry_after_error(5);
}
promise_.set_value(std::move(query));
}
}
~PromiseDeleter() {
CHECK(promise_.empty());
}
private:
td::PromiseActor<td::unique_ptr<Query>> promise_;
};
using PromisedQueryPtr = std::unique_ptr<Query, PromiseDeleter>;
template <class Jsonable>
void answer_query(const Jsonable &result, PromisedQueryPtr query, td::Slice description = td::Slice()) {
query->set_ok(td::json_encode<td::BufferSlice>(JsonQueryOk<Jsonable>(result, description)));
query.reset(); // send query into promise explicitly
}
inline void fail_query(
int http_status_code, td::Slice description, PromisedQueryPtr query,
const std::unordered_map<td::string, std::unique_ptr<td::VirtuallyJsonable>> &parameters = empty_parameters) {
query->set_error(http_status_code,
td::json_encode<td::BufferSlice>(JsonQueryError(http_status_code, description, parameters)));
query.reset(); // send query into promise explicitly
}
} // namespace telegram_bot_api

159
telegram-bot-api/Stats.cpp Normal file
View File

@ -0,0 +1,159 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/Stats.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/thread.h"
#include "td/utils/StringBuilder.h"
namespace telegram_bot_api {
ServerCpuStat::ServerCpuStat() {
for (std::size_t i = 1; i < SIZE; i++) {
stat_[i] = td::TimedStat<CpuStat>(DURATIONS[i], td::Time::now());
}
}
void ServerCpuStat::add_event(const td::CpuStat &cpu_stat, double now) {
std::lock_guard<std::mutex> guard(mutex_);
for (auto &stat : stat_) {
stat.add_event(cpu_stat, now);
}
}
td::string ServerCpuStat::get_description() const {
td::string res = "DURATION";
for (auto &descr : DESCR) {
res += "\t";
res += descr;
}
return res;
}
static td::string to_percentage(td::uint64 ticks, td::uint64 total_ticks) {
static double multiplier = 100.0 * (td::thread::hardware_concurrency() ? td::thread::hardware_concurrency() : 1);
return PSTRING() << (static_cast<double>(ticks) / static_cast<double>(total_ticks) * multiplier) << "%";
}
td::vector<StatItem> CpuStat::as_vector() const {
td::vector<StatItem> res;
if (cnt_ < 2 || first_.total_ticks_ >= last_.total_ticks_) {
res.push_back({"total_cpu", "UNKNOWN"});
res.push_back({"user_cpu", "UNKNOWN"});
res.push_back({"system_cpu", "UNKNOWN"});
} else {
auto total_ticks = last_.total_ticks_ - first_.total_ticks_;
auto user_ticks = last_.process_user_ticks_ - first_.process_user_ticks_;
auto system_ticks = last_.process_system_ticks_ - first_.process_system_ticks_;
res.push_back({"total_cpu", to_percentage(user_ticks + system_ticks, total_ticks)});
res.push_back({"user_cpu", to_percentage(user_ticks, total_ticks)});
res.push_back({"system_cpu", to_percentage(system_ticks, total_ticks)});
}
return res;
}
td::vector<StatItem> ServerCpuStat::as_vector(double now) {
std::lock_guard<std::mutex> guard(mutex_);
td::vector<StatItem> res = stat_[0].get_stat(now).as_vector();
for (std::size_t i = 1; i < SIZE; i++) {
auto other = stat_[i].get_stat(now).as_vector();
CHECK(other.size() == res.size());
for (size_t j = 0; j < res.size(); j++) {
res[j].value_ += "\t";
res[j].value_ += other[j].value_;
}
}
return res;
}
constexpr int ServerCpuStat::DURATIONS[SIZE];
constexpr const char *ServerCpuStat::DESCR[SIZE];
void ServerBotStat::normalize(double duration) {
if (duration == 0) {
return;
}
request_count_ /= duration;
request_bytes_ /= duration;
request_file_count_ /= duration;
request_files_bytes_ /= duration;
response_count_ /= duration;
response_count_ok_ /= duration;
response_count_error_ /= duration;
response_bytes_ /= duration;
update_count_ /= duration;
}
void ServerBotStat::add(const ServerBotStat &stat) {
request_count_ += stat.request_count_;
request_bytes_ += stat.request_bytes_;
request_file_count_ += stat.request_file_count_;
request_files_bytes_ += stat.request_files_bytes_;
request_files_max_bytes_ = td::max(request_files_max_bytes_, stat.request_files_max_bytes_);
response_count_ += stat.response_count_;
response_count_ok_ += stat.response_count_ok_;
response_count_error_ += stat.response_count_error_;
response_bytes_ += stat.response_bytes_;
update_count_ += stat.update_count_;
}
td::vector<StatItem> ServerBotStat::as_vector() const {
td::vector<StatItem> res;
auto add_item = [&res](td::string name, auto value) {
res.push_back({std::move(name), td::to_string(value)});
};
add_item("request_count", request_count_);
add_item("request_bytes", request_bytes_);
add_item("request_file_count", request_file_count_);
add_item("request_files_bytes", request_files_bytes_);
add_item("request_max_bytes", request_files_max_bytes_);
add_item("response_count", response_count_);
add_item("response_count_ok", response_count_ok_);
add_item("response_count_error", response_count_error_);
add_item("response_bytes", response_bytes_);
add_item("update_count", update_count_);
return res;
}
td::vector<StatItem> BotStatActor::as_vector(double now) {
auto first_sd = stat_[0].stat_duration(now);
first_sd.first.normalize(first_sd.second);
td::vector<StatItem> res = first_sd.first.as_vector();
for (std::size_t i = 1; i < SIZE; i++) {
auto next_sd = stat_[i].stat_duration(now);
next_sd.first.normalize(next_sd.second);
auto other = next_sd.first.as_vector();
CHECK(other.size() == res.size());
for (size_t j = 0; j < res.size(); j++) {
res[j].value_ += "\t";
res[j].value_ += other[j].value_;
}
}
return res;
}
td::string BotStatActor::get_description() const {
td::string res = "DURATION";
for (auto &descr : DESCR) {
res += "\t";
res += descr;
}
return res;
}
bool BotStatActor::is_active(double now) const {
return last_activity_timestamp_ > now - 86400;
}
constexpr int BotStatActor::DURATIONS[SIZE];
constexpr const char *BotStatActor::DESCR[SIZE];
} // namespace telegram_bot_api

197
telegram-bot-api/Stats.h Normal file
View File

@ -0,0 +1,197 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/actor/actor.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/port/Stat.h"
#include "td/utils/Time.h"
#include "td/utils/TimedStat.h"
#include <mutex>
namespace telegram_bot_api {
struct StatItem {
td::string key_;
td::string value_;
};
class CpuStat {
public:
void on_event(const td::CpuStat &event) {
if (cnt_ == 0) {
first_ = event;
cnt_ = 1;
} else {
cnt_ = 2;
last_ = event;
}
}
td::vector<StatItem> as_vector() const;
private:
int cnt_ = 0;
td::CpuStat first_;
td::CpuStat last_;
};
class ServerCpuStat {
public:
static ServerCpuStat &instance() {
static ServerCpuStat stat;
return stat;
}
static void update(double now) {
auto r_event = td::cpu_stat();
if (r_event.is_error()) {
return;
}
instance().add_event(r_event.ok(), now);
LOG(WARNING) << "CPU usage: " << instance().stat_[1].get_stat(now).as_vector()[0].value_;
}
td::string get_description() const;
td::vector<StatItem> as_vector(double now);
private:
static constexpr std::size_t SIZE = 4;
static constexpr const char *DESCR[SIZE] = {"inf", "5sec", "1min", "1hour"};
static constexpr int DURATIONS[SIZE] = {0, 5, 60, 60 * 60};
std::mutex mutex_;
td::TimedStat<CpuStat> stat_[SIZE];
ServerCpuStat();
void add_event(const td::CpuStat &stat, double now);
};
class ServerBotInfo {
public:
td::string id_;
td::string token_;
td::string username_;
td::string webhook_;
bool has_webhook_certificate_ = false;
td::int32 head_update_id_ = 0;
td::int32 tail_update_id_ = 0;
td::int32 webhook_max_connections_ = 0;
std::size_t pending_update_count_ = 0;
double start_timestamp_ = 0;
double last_query_timestamp_ = 0;
};
struct ServerBotStat {
double request_count_ = 0;
double request_bytes_ = 0;
double request_file_count_ = 0;
double request_files_bytes_ = 0;
td::int64 request_files_max_bytes_ = 0;
double response_count_ = 0;
double response_count_ok_ = 0;
double response_count_error_ = 0;
double response_bytes_ = 0;
double update_count_ = 0;
void normalize(double duration);
void add(const ServerBotStat &stat);
struct Update {};
void on_event(const Update &update) {
update_count_ += 1;
}
struct Response {
bool ok_;
size_t size_;
};
void on_event(const Response &answer) {
response_count_++;
if (answer.ok_) {
response_count_ok_++;
} else {
response_count_error_++;
}
response_bytes_ += static_cast<double>(answer.size_);
}
struct Request {
td::int64 size_;
td::int64 file_count_;
td::int64 files_size_;
td::int64 files_max_size_;
};
void on_event(const Request &request) {
request_count_++;
request_bytes_ += static_cast<double>(request.size_);
request_file_count_ += static_cast<double>(request.file_count_);
request_files_bytes_ += static_cast<double>(request.files_size_);
request_files_max_bytes_ = td::max(request_files_max_bytes_, request.files_max_size_);
}
td::vector<StatItem> as_vector() const;
};
class BotStatActor final : public td::Actor {
public:
BotStatActor() = default;
explicit BotStatActor(td::ActorId<BotStatActor> parent) : parent_(parent) {
for (std::size_t i = 0; i < SIZE; i++) {
stat_[i] = td::TimedStat<ServerBotStat>(DURATIONS[i], td::Time::now());
}
register_actor("ServerBotStat", this).release();
}
BotStatActor(const BotStatActor &) = delete;
BotStatActor &operator=(const BotStatActor &other) = delete;
BotStatActor(BotStatActor &&) = default;
BotStatActor &operator=(BotStatActor &&other) {
if (!empty()) {
do_stop();
}
this->Actor::operator=(std::move(other));
std::move(other.stat_, other.stat_ + SIZE, stat_);
parent_ = other.parent_;
return *this;
}
~BotStatActor() override = default;
template <class EventT>
void add_event(const EventT &event, double now) {
last_activity_timestamp_ = now;
for (auto &stat : stat_) {
stat.add_event(event, now);
}
if (!parent_.empty()) {
send_closure(parent_, &BotStatActor::add_event<EventT>, event, now);
}
}
td::vector<StatItem> as_vector(double now);
td::string get_description() const;
bool is_active(double now) const;
private:
static constexpr std::size_t SIZE = 4;
static constexpr const char *DESCR[SIZE] = {"inf", "5sec", "1min", "1hour"};
static constexpr int DURATIONS[SIZE] = {0, 5, 60, 60 * 60};
td::TimedStat<ServerBotStat> stat_[SIZE];
td::ActorId<BotStatActor> parent_;
double last_activity_timestamp_ = -1e9;
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,751 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/WebhookActor.h"
#include "telegram-bot-api/ClientParameters.h"
#include "td/db/TQueue.h"
#include "td/net/GetHostByNameActor.h"
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpProxy.h"
#include "td/net/SslStream.h"
#include "td/net/TransparentProxy.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/logging.h"
#include "td/utils/misc.h"
#include "td/utils/port/Clocks.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Random.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/Span.h"
#include "td/utils/Time.h"
#include <limits>
namespace telegram_bot_api {
static int VERBOSITY_NAME(webhook) = VERBOSITY_NAME(DEBUG);
std::atomic<td::uint64> WebhookActor::total_connections_count_{0};
WebhookActor::WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_id, td::HttpUrl url,
td::string cert_path, td::int32 max_connections, bool from_db_flag,
td::string cached_ip_address, bool fix_ip_address,
std::shared_ptr<const ClientParameters> parameters)
: callback_(std::move(callback))
, tqueue_id_(tqueue_id)
, url_(std::move(url))
, cert_path_(std::move(cert_path))
, parameters_(std::move(parameters))
, fix_ip_address_(fix_ip_address)
, from_db_flag_(from_db_flag)
, max_connections_(max_connections) {
CHECK(max_connections_ > 0);
if (!cached_ip_address.empty()) {
auto r_ip_address = td::IPAddress::get_ip_address(cached_ip_address);
if (r_ip_address.is_ok()) {
ip_address_ = r_ip_address.move_as_ok();
ip_address_.set_port(url_.port_);
}
}
auto r_ascii_host = td::idn_to_ascii(url_.host_);
if (r_ascii_host.is_ok()) {
url_.host_ = r_ascii_host.move_as_ok();
}
LOG(INFO) << "Set webhook for " << tqueue_id << " with certificate = \"" << cert_path_
<< "\", protocol = " << (url_.protocol_ == td::HttpUrl::Protocol::Http ? "http" : "https")
<< ", host = " << url_.host_ << ", port = " << url_.port_ << ", query = " << url_.query_
<< ", max_connections = " << max_connections_;
}
void WebhookActor::relax_wakeup_at(double wakeup_at, const char *source) {
if (wakeup_at_ == 0 || wakeup_at < wakeup_at_) {
VLOG(webhook) << "Wake up in " << wakeup_at - td::Time::now() << " from " << source;
wakeup_at_ = wakeup_at;
}
}
void WebhookActor::resolve_ip_address() {
if (fix_ip_address_) {
return;
}
if (td::Time::now() < next_ip_address_resolve_timestamp_) {
relax_wakeup_at(next_ip_address_resolve_timestamp_, "resolve_ip_address");
return;
}
bool future_created = false;
if (future_ip_address_.empty()) {
td::PromiseActor<td::IPAddress> promise;
init_promise_future(&promise, &future_ip_address_);
future_created = true;
send_closure(parameters_->get_host_by_name_actor_id_, &td::GetHostByNameActor::run, url_.host_, url_.port_, false,
td::PromiseCreator::from_promise_actor(std::move(promise)));
}
if (future_ip_address_.is_ready()) {
next_ip_address_resolve_timestamp_ =
td::Time::now() + IP_ADDRESS_CACHE_TIME + td::Random::fast(0, IP_ADDRESS_CACHE_TIME / 10);
relax_wakeup_at(next_ip_address_resolve_timestamp_, "resolve_ip_address");
auto r_ip_address = future_ip_address_.move_as_result();
if (r_ip_address.is_error()) {
CHECK(!(r_ip_address.error() == td::Status::Error<td::FutureActor<td::IPAddress>::HANGUP_ERROR_CODE>()));
return on_error(r_ip_address.move_as_error());
}
auto new_ip_address = r_ip_address.move_as_ok();
if (!check_ip_address(new_ip_address)) {
return on_error(td::Status::Error(PSLICE() << "IP address " << new_ip_address.get_ip_str() << " is reserved"));
}
if (!(ip_address_ == new_ip_address)) {
VLOG(webhook) << "IP address has changed: " << ip_address_ << " --> " << new_ip_address;
ip_address_ = new_ip_address;
ip_generation_++;
if (was_checked_) {
on_webhook_verified();
}
}
VLOG(webhook) << "IP address was verified";
} else {
if (future_created) {
future_ip_address_.set_event(td::EventCreator::yield(actor_id()));
}
}
}
td::Status WebhookActor::create_connection() {
if (!ip_address_.is_valid()) {
VLOG(webhook) << "Can't create connection: IP address is not ready";
return td::Status::Error("IP address is not ready");
}
if (parameters_->webhook_proxy_ip_address_.is_valid()) {
auto r_proxy_socket_fd = td::SocketFd::open(parameters_->webhook_proxy_ip_address_);
if (r_proxy_socket_fd.is_error()) {
td::Slice error_message = "Can't connect to the webhook proxy";
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_proxy_socket_fd.error());
VLOG(webhook) << error;
on_webhook_error(error_message);
on_error(td::Status::Error(error_message));
return error;
}
if (!was_checked_) {
TRY_STATUS(create_ssl_stream()); // check certificate
// verify webhook even we can't establish connection to the webhook
was_checked_ = true;
on_webhook_verified();
}
VLOG(webhook) << "Create connection through proxy " << parameters_->webhook_proxy_ip_address_;
class Callback : public td::TransparentProxy::Callback {
public:
Callback(td::ActorId<WebhookActor> actor, td::int64 id) : actor_(actor), id_(id) {
}
void set_result(td::Result<td::SocketFd> result) override {
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, std::move(result), id_);
CHECK(actor_.empty());
}
Callback(const Callback &) = delete;
Callback &operator=(const Callback &) = delete;
Callback(Callback &&) = delete;
Callback &operator=(Callback &&) = delete;
~Callback() {
if (!actor_.empty()) {
send_closure(std::move(actor_), &WebhookActor::on_socket_ready_async, td::Status::Error("Cancelled"), id_);
}
}
void on_connected() override {
// nothing to do
}
private:
td::ActorId<WebhookActor> actor_;
td::int64 id_;
};
auto id = pending_sockets_.create(td::ActorOwn<>());
VLOG(webhook) << "Creating socket " << id;
*pending_sockets_.get(id) = td::create_actor<td::HttpProxy>(
"HttpProxy", r_proxy_socket_fd.move_as_ok(), ip_address_, td::string(), td::string(),
td::make_unique<Callback>(actor_id(this), id), td::ActorShared<>());
return td::Status::Error("Proxy connection is not ready");
}
auto r_fd = td::SocketFd::open(ip_address_);
if (r_fd.is_error()) {
td::Slice error_message = "Can't connect to the webhook";
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_fd.error());
VLOG(webhook) << error;
on_webhook_error(error_message);
on_error(r_fd.move_as_error());
return error;
}
return create_connection(r_fd.move_as_ok());
}
td::Result<td::SslStream> WebhookActor::create_ssl_stream() {
if (url_.protocol_ == td::HttpUrl::Protocol::Http) {
return td::SslStream();
}
auto r_ssl_stream = td::SslStream::create(url_.host_, cert_path_, td::SslStream::VerifyPeer::On, !cert_path_.empty());
if (r_ssl_stream.is_error()) {
td::Slice error_message = "Can't create an SSL connection";
auto error = td::Status::Error(PSLICE() << error_message << ": " << r_ssl_stream.error());
VLOG(webhook) << error;
on_webhook_error(PSLICE() << error_message << ": " << r_ssl_stream.error().public_message());
on_error(r_ssl_stream.move_as_error());
return std::move(error);
}
return r_ssl_stream.move_as_ok();
}
td::Status WebhookActor::create_connection(td::SocketFd fd) {
TRY_RESULT(ssl_stream, create_ssl_stream());
auto id = connections_.create(Connection());
auto *conn = connections_.get(id);
conn->actor_id_ = td::create_actor<td::HttpOutboundConnection>(
PSLICE() << "Connect:" << id, std::move(fd), std::move(ssl_stream), std::numeric_limits<size_t>::max(), 20, 60,
td::ActorShared<td::HttpOutboundConnection::Callback>(actor_id(this), id));
conn->ip_generation_ = ip_generation_;
conn->event_id_ = {};
conn->id_ = id;
ready_connections_.put(conn->to_list_node());
total_connections_count_.fetch_add(1, std::memory_order_relaxed);
if (!was_checked_) {
was_checked_ = true;
on_webhook_verified();
}
VLOG(webhook) << "Create connection " << id;
return td::Status::OK();
}
void WebhookActor::on_socket_ready_async(td::Result<td::SocketFd> r_fd, td::int64 id) {
pending_sockets_.erase(id);
if (r_fd.is_ok()) {
VLOG(webhook) << "Socket " << id << " is ready";
ready_sockets_.push_back(r_fd.move_as_ok());
} else {
VLOG(webhook) << "Failed to open socket " << id;
on_webhook_error(r_fd.error().message());
on_error(r_fd.move_as_error());
}
loop();
}
void WebhookActor::create_new_connections() {
size_t need_connections = queues_.size();
if (need_connections > static_cast<size_t>(max_connections_)) {
need_connections = max_connections_;
}
if (!was_checked_) {
need_connections = 1;
}
auto now = td::Time::now();
td::FloodControlFast *flood;
bool active;
if (last_success_timestamp_ + 10 < now) {
flood = &pending_new_connection_flood_;
if (need_connections > 1) {
need_connections = 1;
}
active = false;
} else {
flood = &active_new_connection_flood_;
if (need_connections == 0) {
need_connections = 1;
}
active = true;
}
VLOG_IF(webhook, connections_.size() < need_connections)
<< "Create new connections " << td::tag("have", connections_.size()) << td::tag("need", need_connections)
<< td::tag("pending sockets", pending_sockets_.size()) << td::tag("ready sockets", ready_sockets_.size())
<< td::tag("active", active);
while (connections_.size() + pending_sockets_.size() + ready_sockets_.size() < need_connections) {
auto wakeup_at = flood->get_wakeup_at();
if (now < wakeup_at) {
relax_wakeup_at(wakeup_at, "create_new_connections");
VLOG(webhook) << "Create new connection: flood control "
<< td::tag("after", td::format::as_time(wakeup_at - now));
break;
}
flood->add_event(static_cast<td::int32>(now));
if (create_connection().is_error()) {
relax_wakeup_at(now + 1.0, "create_new_connections error");
return;
}
}
SCOPE_EXIT {
ready_sockets_.clear();
};
while (!ready_sockets_.empty() && connections_.size() + pending_sockets_.size() < need_connections) {
auto socket_fd = std::move(ready_sockets_.back());
ready_sockets_.pop_back();
if (create_connection(std::move(socket_fd)).is_error()) {
relax_wakeup_at(now + 1.0, "create_new_connections error 2");
return;
}
}
}
void WebhookActor::loop() {
VLOG(webhook) << "Enter loop";
wakeup_at_ = 0;
if (!stop_flag_) {
load_updates();
}
if (!stop_flag_) {
resolve_ip_address();
}
if (!stop_flag_) {
create_new_connections();
}
if (!stop_flag_) {
send_updates();
}
if (!stop_flag_) {
if (wakeup_at_ != 0) {
set_timeout_at(wakeup_at_);
}
}
if (stop_flag_) {
VLOG(webhook) << "Stop";
stop();
}
}
void WebhookActor::update() {
VLOG(webhook) << "New updates in tqueue";
tqueue_empty_ = false;
loop();
}
void WebhookActor::load_updates() {
if (tqueue_empty_) {
VLOG(webhook) << "Load updates: tqueue is empty";
return;
}
if (queue_updates_.size() >= max_loaded_updates_) {
CHECK(queue_updates_.size() == max_loaded_updates_);
VLOG(webhook) << "Load updates: maximum allowed number of updates is already loaded";
return;
}
auto &tqueue = parameters_->shared_data_->tqueue_;
if (tqueue_offset_.empty()) {
tqueue_offset_ = tqueue->get_head(tqueue_id_);
}
VLOG(webhook) << "Trying to load new updates from offset " << tqueue_offset_;
auto limit = td::min(SharedData::TQUEUE_EVENT_BUFFER_SIZE, max_loaded_updates_ - queue_updates_.size());
auto updates = mutable_span(parameters_->shared_data_->event_buffer_, limit);
auto now = td::Time::now();
auto unix_time_now = parameters_->shared_data_->get_unix_time(now);
size_t total_size = 0;
if (tqueue_offset_.empty()) {
updates.truncate(0);
} else {
auto r_size = tqueue->get(tqueue_id_, tqueue_offset_, false, unix_time_now, updates);
if (r_size.is_error()) {
VLOG(webhook) << "Failed to get new updates: " << r_size.error();
tqueue_offset_ = tqueue->get_head(tqueue_id_);
r_size = tqueue->get(tqueue_id_, tqueue_offset_, false, unix_time_now, updates);
r_size.ensure();
}
total_size = r_size.ok();
}
if (updates.empty()) {
tqueue_empty_ = true;
}
for (auto &update : updates) {
VLOG(webhook) << "Load update " << update.id;
if (update_map_.find(update.id) != update_map_.end()) {
LOG(ERROR) << "Receive duplicated event from tqueue " << update.id;
continue;
}
auto &dest = update_map_[update.id];
dest.id_ = update.id;
dest.json_ = update.data.str();
dest.state_ = Update::State::Begin;
dest.delay_ = 1;
dest.wakeup_at_ = now;
CHECK(update.expires_at >= unix_time_now);
dest.expires_at_ = update.expires_at;
dest.queue_id_ = update.extra;
tqueue_offset_ = update.id.next().move_as_ok();
begin_updates_n_++;
if (dest.queue_id_ == 0) {
dest.queue_id_ = unique_queue_id_++;
}
auto &queue_updates = queue_updates_[dest.queue_id_];
if (queue_updates.event_ids.empty()) {
queues_.emplace(dest.wakeup_at_, dest.queue_id_);
}
queue_updates.event_ids.push(dest.id_);
}
bool need_warning = false;
if (total_size <= MIN_PENDING_UPDATES_WARNING / 2) {
if (last_pending_update_count_ > MIN_PENDING_UPDATES_WARNING) {
need_warning = true;
last_pending_update_count_ = MIN_PENDING_UPDATES_WARNING;
}
} else if (total_size >= last_pending_update_count_) {
need_warning = true;
while (total_size >= last_pending_update_count_) {
last_pending_update_count_ *= 2;
}
}
if (need_warning) {
LOG(WARNING) << "Found " << updates.size() << " updates out of " << total_size - update_map_.size() << " + "
<< update_map_.size() << " in " << queues_.size() << " queues after last error \""
<< last_error_message_ << "\" at " << last_error_date_;
}
if (updates.size() == total_size && last_update_was_successful_) {
send_closure(callback_, &Callback::webhook_success);
}
if (!updates.empty()) {
VLOG(webhook) << "Loaded " << updates.size() << " new updates from offset " << tqueue_offset_
<< " out of requested " << limit << ". Have total of " << update_map_.size() << " updates loaded in "
<< queue_updates_.size() << " queues";
}
}
void WebhookActor::drop_event(td::TQueue::EventId event_id) {
auto it = update_map_.find(event_id);
auto queue_id = it->second.queue_id_;
update_map_.erase(it);
auto queue_updates_it = queue_updates_.find(queue_id);
CHECK(!queue_updates_it->second.event_ids.empty());
CHECK(event_id == queue_updates_it->second.event_ids.front());
queue_updates_it->second.event_ids.pop();
if (queue_updates_it->second.event_ids.empty()) {
queue_updates_.erase(queue_updates_it);
} else {
auto &update = update_map_[queue_updates_it->second.event_ids.front()];
queues_.emplace(update.wakeup_at_, update.queue_id_);
}
parameters_->shared_data_->tqueue_->forget(tqueue_id_, event_id);
}
void WebhookActor::on_update_ok(td::TQueue::EventId event_id) {
last_update_was_successful_ = true;
last_success_timestamp_ = td::Time::now();
VLOG(webhook) << "Receive ok for update " << event_id;
drop_event(event_id);
}
void WebhookActor::on_update_error(td::TQueue::EventId event_id, td::Slice error, int retry_after) {
last_update_was_successful_ = false;
double now = td::Time::now();
auto it = update_map_.find(event_id);
if (parameters_->shared_data_->get_unix_time(now) > it->second.expires_at_) {
LOG(WARNING) << "Drop update " << event_id << ": " << error;
drop_event(event_id);
return;
}
auto &update = it->second;
update.state_ = Update::State::Begin;
const int MAX_RETRY_AFTER = 3600;
retry_after = td::clamp(retry_after, 0, MAX_RETRY_AFTER);
update.delay_ = retry_after != 0
? retry_after
: td::min(WEBHOOK_MAX_RESEND_TIMEOUT, update.delay_ * (update.fail_count_ > 0 ? 2 : 1));
update.wakeup_at_ = update.fail_count_ > 0 ? now + update.delay_ : now;
update.fail_count_++;
queues_.emplace(update.wakeup_at_, update.queue_id_);
VLOG(webhook) << "Delay update " << event_id << " for " << (update.wakeup_at_ - now) << " seconds because of "
<< error << " after " << update.fail_count_ << " fails";
begin_updates_n_++;
}
td::Status WebhookActor::send_update() {
if (ready_connections_.empty()) {
return td::Status::Error("No connection");
}
if (queues_.empty()) {
return td::Status::Error("No updates");
}
auto it = queues_.begin();
if (it->wakeup_at > td::Time::now()) {
relax_wakeup_at(it->wakeup_at, "send_update");
return td::Status::Error("No ready updates");
}
auto queue_id = it->id;
queues_.erase(it);
auto event_id = queue_updates_[queue_id].event_ids.front();
auto &update = update_map_[event_id];
auto body = td::json_encode<td::BufferSlice>(JsonUpdate(update.id_.value(), update.json_));
td::HttpHeaderCreator hc;
hc.init_post(url_.query_);
hc.add_header("Host", url_.host_);
if (!url_.userinfo_.empty()) {
hc.add_header("Authorization", PSLICE() << "Basic " << td::base64_encode(url_.userinfo_));
}
hc.set_content_type("application/json");
hc.set_content_size(body.size());
hc.set_keep_alive();
hc.add_header("Accept-Encoding", "gzip, deflate");
auto r_header = hc.finish();
if (r_header.is_error()) {
return td::Status::Error(400, "URL is too long");
}
auto &connection = *Connection::from_list_node(ready_connections_.get());
begin_updates_n_--;
update.state_ = Update::State::Send;
connection.event_id_ = update.id_;
VLOG(webhook) << "Send update " << update.id_ << " from queue " << queue_id << " into connection " << connection.id_
<< ": " << update.json_;
VLOG(webhook) << "Request headers: " << r_header.ok();
send_closure(connection.actor_id_, &td::HttpOutboundConnection::write_next_noflush, td::BufferSlice(r_header.ok()));
send_closure(connection.actor_id_, &td::HttpOutboundConnection::write_next_noflush, std::move(body));
send_closure(connection.actor_id_, &td::HttpOutboundConnection::write_ok);
return td::Status::OK();
}
void WebhookActor::send_updates() {
VLOG(webhook) << "Have " << begin_updates_n_ << " pending updates to send";
while (begin_updates_n_ > 0) {
if (send_update().is_error()) {
return;
}
}
}
void WebhookActor::handle(td::unique_ptr<td::HttpQuery> response) {
auto connection_id = get_link_token();
if (response) {
VLOG(webhook) << "Got response from connection " << connection_id;
} else {
VLOG(webhook) << "Got hangup from connection " << connection_id;
}
auto *connection_ptr = connections_.get(connection_id);
if (connection_ptr == nullptr) {
return;
}
bool close_connection = false;
td::string query_error;
td::int32 retry_after = 0;
bool need_close = false;
if (response) {
if (response->type_ != td::HttpQuery::Type::Response || !response->keep_alive_ ||
ip_generation_ != connection_ptr->ip_generation_) {
close_connection = true;
}
if (response->type_ == td::HttpQuery::Type::Response) {
if (200 <= response->code_ && response->code_ <= 299) {
auto method = response->get_arg("method");
td::to_lower_inplace(method);
if (!method.empty() && method != "deletewebhook" && method != "setwebhook" && method != "close" &&
method != "logout" && !td::begins_with(method, "get")) {
VLOG(webhook) << "Receive request " << method << " in response to webhook";
auto query =
std::make_unique<Query>(std::move(response->container_), td::MutableSlice(), false, td::MutableSlice(),
std::move(response->args_), std::move(response->headers_),
std::move(response->files_), parameters_->shared_data_, response->peer_address_);
auto promised_query =
PromisedQueryPtr(query.release(), PromiseDeleter(td::PromiseActor<td::unique_ptr<Query>>()));
send_closure(callback_, &Callback::send, std::move(promised_query));
}
first_error_410_time_ = 0;
} else {
query_error = PSTRING() << "Wrong response from the webhook: " << response->code_ << " " << response->reason_;
if (response->code_ == 410) {
if (first_error_410_time_ == 0) {
first_error_410_time_ = td::Time::now();
} else {
if (td::Time::now() > first_error_410_time_ + WEBHOOK_DROP_TIMEOUT) {
LOG(WARNING) << "Close webhook because of HTTP 410 errors";
need_close = true;
}
}
} else {
first_error_410_time_ = 0;
}
retry_after = response->get_retry_after();
// LOG(WARNING) << query_error;
on_webhook_error(query_error);
}
} else {
query_error = PSTRING() << "Wrong response from the webhook: " << *response;
on_webhook_error(query_error);
}
VLOG(webhook) << *response;
} else {
query_error = "Webhook connection closed";
connection_ptr->actor_id_.release();
close_connection = true;
}
auto event_id = connection_ptr->event_id_;
if (!event_id.empty()) {
if (query_error.empty()) {
on_update_ok(event_id);
} else {
on_update_error(event_id, query_error, retry_after);
}
} else {
CHECK(!query_error.empty());
}
connection_ptr->event_id_ = {};
if (need_close || close_connection) {
VLOG(webhook) << "Close connection " << connection_id;
connections_.erase(connection_ptr->id_);
total_connections_count_.fetch_sub(1, std::memory_order_relaxed);
} else {
ready_connections_.put(connection_ptr->to_list_node());
}
if (need_close) {
send_closure_later(actor_id(this), &WebhookActor::close);
} else {
loop();
}
}
void WebhookActor::start_up() {
max_loaded_updates_ = max_connections_ * 2;
last_success_timestamp_ = td::Time::now();
active_new_connection_flood_.add_limit(1, 10 * max_connections_);
active_new_connection_flood_.add_limit(5, 20 * max_connections_);
pending_new_connection_flood_.add_limit(1, 1);
if (!parameters_->local_mode_) {
if (url_.protocol_ == td::HttpUrl::Protocol::Https) {
if (url_.port_ != 443 && url_.port_ != 88 && url_.port_ != 80 && url_.port_ != 8443) {
VLOG(webhook) << "Can't create webhook: port " << url_.port_ << " is forbidden";
on_error(td::Status::Error("Webhook can be set up only on ports 80, 88, 443 or 8443"));
}
} else {
CHECK(url_.protocol_ == td::HttpUrl::Protocol::Http);
VLOG(webhook) << "Can't create connection: HTTP is forbidden";
on_error(td::Status::Error("HTTPS url must be provided for webhook"));
}
}
if (fix_ip_address_ && !stop_flag_) {
if (!ip_address_.is_valid()) {
on_error(td::Status::Error("Invalid IP address specified"));
} else if (!check_ip_address(ip_address_)) {
on_error(td::Status::Error(PSLICE() << "IP address " << ip_address_.get_ip_str() << " is reserved"));
}
}
if (from_db_flag_ && !stop_flag_) {
was_checked_ = true;
on_webhook_verified();
}
yield();
}
void WebhookActor::hangup_shared() {
handle(nullptr);
loop();
}
void WebhookActor::hangup() {
VLOG(webhook) << "Stop";
callback_.release();
stop();
}
void WebhookActor::close() {
VLOG(webhook) << "Close";
send_closure(std::move(callback_), &Callback::webhook_closed, td::Status::OK());
stop();
}
void WebhookActor::tear_down() {
total_connections_count_.fetch_sub(connections_.size(), std::memory_order_relaxed);
}
void WebhookActor::on_webhook_verified() {
td::string ip_address_str;
if (ip_address_.is_valid()) {
ip_address_str = ip_address_.get_ip_str().str();
}
send_closure(callback_, &Callback::webhook_verified, std::move(ip_address_str));
}
bool WebhookActor::check_ip_address(const td::IPAddress &addr) const {
if (!addr.is_valid()) {
return false;
}
if (parameters_->local_mode_) {
// allow any valid IP address
return true;
}
if (!addr.is_ipv4()) {
VLOG(webhook) << "Bad IP address (not IPv4): " << addr;
return false;
}
return !addr.is_reserved();
}
void WebhookActor::on_error(td::Status status) {
VLOG(webhook) << "Receive webhook error " << status;
if (!was_checked_) {
CHECK(!callback_.empty());
send_closure(std::move(callback_), &Callback::webhook_closed, std::move(status));
stop_flag_ = true;
}
}
void WebhookActor::on_connection_error(td::Status error) {
CHECK(error.is_error());
on_webhook_error(error.message());
}
void WebhookActor::on_webhook_error(td::Slice error) {
if (was_checked_) {
send_closure(callback_, &Callback::webhook_error, td::Status::Error(error));
last_error_date_ = td::Clocks::system(); // local time to output it to the log
last_error_message_ = error.str();
}
}
} // namespace telegram_bot_api

View File

@ -0,0 +1,230 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "telegram-bot-api/Query.h"
#include "td/db/TQueue.h"
#include "td/net/HttpOutboundConnection.h"
#include "td/net/HttpQuery.h"
#include "td/net/SslStream.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/common.h"
#include "td/utils/Container.h"
#include "td/utils/FloodControlFast.h"
#include "td/utils/HttpUrl.h"
#include "td/utils/JsonBuilder.h"
#include "td/utils/List.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/VectorQueue.h"
#include <atomic>
#include <functional>
#include <memory>
#include <set>
#include <tuple>
#include <unordered_map>
namespace telegram_bot_api {
struct ClientParameters;
class WebhookActor : public td::HttpOutboundConnection::Callback {
public:
class Callback : public td::Actor {
public:
virtual void webhook_verified(td::string cached_ip) = 0;
virtual void webhook_success() = 0;
virtual void webhook_error(td::Status status) = 0;
virtual void webhook_closed(td::Status status) = 0;
virtual void send(PromisedQueryPtr query) = 0;
};
WebhookActor(td::ActorShared<Callback> callback, td::int64 tqueue_id, td::HttpUrl url, td::string cert_path,
td::int32 max_connections, bool from_db_flag, td::string cached_ip_address, bool fix_ip_address,
std::shared_ptr<const ClientParameters> parameters);
void update();
void close();
static td::int64 get_total_connections_count() {
return total_connections_count_;
}
private:
static constexpr std::size_t MIN_PENDING_UPDATES_WARNING = 50;
static constexpr int IP_ADDRESS_CACHE_TIME = 30 * 60; // 30 minutes
static constexpr int WEBHOOK_MAX_RESEND_TIMEOUT = 60;
static constexpr int WEBHOOK_DROP_TIMEOUT = 60 * 60 * 23;
static std::atomic<td::uint64> total_connections_count_;
td::ActorShared<Callback> callback_;
td::int64 tqueue_id_;
bool tqueue_empty_ = false;
std::size_t last_pending_update_count_ = MIN_PENDING_UPDATES_WARNING;
td::HttpUrl url_;
td::string cert_path_;
std::shared_ptr<const ClientParameters> parameters_;
double last_error_date_ = 0;
td::string last_error_message_ = "<none>";
bool fix_ip_address_ = false;
bool stop_flag_ = false;
bool was_checked_ = false;
bool from_db_flag_ = false;
class Update {
public:
td::TQueue::EventId id_;
td::string json_;
td::int32 expires_at_ = 0;
double wakeup_at_ = 0;
int delay_ = 0;
int fail_count_ = 0;
enum State { Begin, Send } state_ = State::Begin;
td::int64 queue_id_{0};
};
struct QueueUpdates {
td::VectorQueue<td::TQueue::EventId> event_ids;
};
struct Queue {
Queue() = default;
Queue(double wakeup_at, td::int64 id)
: wakeup_at(wakeup_at), integer_wakeup_at(static_cast<td::int64>(wakeup_at * 1e9)), id(id) {
}
double wakeup_at{0};
td::int64 integer_wakeup_at{0};
td::int64 id{0};
bool operator<(const Queue &other) const {
return std::tie(integer_wakeup_at, id) < std::tie(other.integer_wakeup_at, other.id);
}
};
td::int32 begin_updates_n_ = 0;
td::TQueue::EventId tqueue_offset_;
std::size_t max_loaded_updates_ = 0;
struct EventIdHash {
std::size_t operator()(td::TQueue::EventId event_id) const {
return std::hash<td::int32>()(event_id.value());
}
};
std::unordered_map<td::TQueue::EventId, Update, EventIdHash> update_map_;
std::unordered_map<td::int64, QueueUpdates> queue_updates_;
std::set<Queue> queues_;
td::int64 unique_queue_id_ = static_cast<td::int64>(1) << 60;
double first_error_410_time_ = 0;
td::IPAddress ip_address_;
td::int32 ip_generation_ = 0;
double next_ip_address_resolve_timestamp_ = 0;
td::FutureActor<td::IPAddress> future_ip_address_;
class Connection : public td::ListNode {
public:
Connection() = default;
Connection(const Connection &) = delete;
Connection &operator=(const Connection &) = delete;
Connection(Connection &&) = default;
Connection &operator=(Connection &&) = default;
~Connection() = default;
td::ActorOwn<td::HttpOutboundConnection> actor_id_;
td::uint64 id_ = 0;
td::TQueue::EventId event_id_;
td::int32 ip_generation_ = -1;
static Connection *from_list_node(ListNode *node) {
return static_cast<Connection *>(node);
}
ListNode *to_list_node() {
return this;
}
};
td::Container<td::ActorOwn<>> pending_sockets_;
td::vector<td::SocketFd> ready_sockets_;
td::int32 max_connections_ = 0;
td::Container<Connection> connections_;
td::ListNode ready_connections_;
td::FloodControlFast active_new_connection_flood_;
td::FloodControlFast pending_new_connection_flood_;
double last_success_timestamp_ = 0;
double wakeup_at_ = 0;
bool last_update_was_successful_ = true;
void relax_wakeup_at(double wakeup_at, const char *source);
void resolve_ip_address();
td::Result<td::SslStream> create_ssl_stream();
td::Status create_connection() TD_WARN_UNUSED_RESULT;
td::Status create_connection(td::SocketFd fd) TD_WARN_UNUSED_RESULT;
void on_socket_ready_async(td::Result<td::SocketFd> r_fd, td::int64 id);
void create_new_connections();
void drop_event(td::TQueue::EventId event_id);
void load_updates();
void on_update_ok(td::TQueue::EventId event_id);
void on_update_error(td::TQueue::EventId event_id, td::Slice error, int retry_after);
td::Status send_update() TD_WARN_UNUSED_RESULT;
void send_updates();
void loop() override;
void handle(td::unique_ptr<td::HttpQuery> response) override;
void hangup_shared() override;
void hangup() override;
void tear_down() override;
void start_up() override;
bool check_ip_address(const td::IPAddress &addr) const;
void on_error(td::Status status);
void on_connection_error(td::Status error) override;
void on_webhook_error(td::Slice error);
void on_webhook_verified();
};
class JsonUpdate : public td::Jsonable {
public:
JsonUpdate(td::int32 id, td::Slice update) : id_(id), update_(update) {
}
void store(td::JsonValueScope *scope) const {
auto object = scope->enter_object();
object("update_id", id_);
object << td::JsonRaw(",\n");
CHECK(!update_.empty());
object << td::JsonRaw(update_);
}
private:
td::int32 id_;
td::Slice update_;
};
} // namespace telegram_bot_api

View File

@ -0,0 +1,523 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2020
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "telegram-bot-api/ClientManager.h"
#include "telegram-bot-api/ClientParameters.h"
#include "telegram-bot-api/HttpConnection.h"
#include "telegram-bot-api/HttpServer.h"
#include "telegram-bot-api/HttpStatConnection.h"
#include "telegram-bot-api/Query.h"
#include "telegram-bot-api/Stats.h"
#include "td/telegram/ClientActor.h"
#include "td/db/binlog/Binlog.h"
#include "td/db/TQueue.h"
#include "td/net/GetHostByNameActor.h"
#include "td/net/HttpInboundConnection.h"
#include "td/actor/actor.h"
#include "td/actor/PromiseFuture.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/crypto.h"
#include "td/utils/ExitGuard.h"
#include "td/utils/FileLog.h"
#include "td/utils/format.h"
//#include "td/utils/GitInfo.h"
#include "td/utils/logging.h"
#include "td/utils/MemoryLog.h"
#include "td/utils/misc.h"
#include "td/utils/OptionParser.h"
#include "td/utils/port/path.h"
#include "td/utils/port/rlimit.h"
#include "td/utils/port/signals.h"
#include "td/utils/port/stacktrace.h"
#include "td/utils/port/user.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/Time.h"
#include "memprof/memprof.h"
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <memory>
#include <tuple>
namespace telegram_bot_api {
static std::atomic_flag need_rotate_log;
static void rotate_log_signal_handler(int sig) {
need_rotate_log.clear();
}
static std::atomic_flag need_quit;
static void quit_signal_handler(int sig) {
need_quit.clear();
}
static td::MemoryLog<1 << 20> memory_log;
void print_log() {
auto buf = memory_log.get_buffer();
auto pos = memory_log.get_pos();
td::signal_safe_write("------- Log dump -------\n");
td::signal_safe_write(buf.substr(pos), false);
td::signal_safe_write(buf.substr(0, pos), false);
td::signal_safe_write("\n", false);
td::signal_safe_write("------------------------\n");
}
static void fail_signal_handler(int sig) {
td::signal_safe_write_signal_number(sig);
td::Stacktrace::PrintOptions options;
options.use_gdb = true;
td::Stacktrace::print_to_stderr(options);
print_log();
_Exit(EXIT_FAILURE);
}
static std::atomic_flag need_change_verbosity_level;
static void change_verbosity_level_signal_handler(int sig) {
need_change_verbosity_level.clear();
}
static std::atomic_flag need_dump_log;
static void dump_log_signal_handler(int sig) {
need_dump_log.clear();
}
static void sigsegv_signal_handler(int signum, void *addr) {
td::signal_safe_write_pointer(addr);
fail_signal_handler(signum);
}
int main(int argc, char *argv[]) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
td::ExitGuard exit_guard;
need_rotate_log.test_and_set();
need_quit.test_and_set();
need_change_verbosity_level.test_and_set();
need_dump_log.test_and_set();
td::Stacktrace::init();
td::setup_signals_alt_stack().ensure();
td::set_signal_handler(td::SignalType::User, rotate_log_signal_handler).ensure();
td::ignore_signal(td::SignalType::HangUp).ensure();
td::ignore_signal(td::SignalType::Pipe).ensure();
td::set_signal_handler(td::SignalType::Quit, quit_signal_handler).ensure();
td::set_signal_handler(td::SignalType::Abort, fail_signal_handler).ensure();
td::set_signal_handler(td::SignalType::Other, fail_signal_handler).ensure();
td::set_extended_signal_handler(td::SignalType::Error, sigsegv_signal_handler).ensure();
td::set_runtime_signal_handler(0, change_verbosity_level_signal_handler).ensure();
td::set_runtime_signal_handler(1, dump_log_signal_handler).ensure();
td::init_openssl_threads();
auto start_time = td::Time::now();
auto shared_data = std::make_shared<SharedData>();
auto parameters = std::make_unique<ClientParameters>();
parameters->shared_data_ = shared_data;
parameters->start_timestamp_ = start_time;
auto net_query_stats = td::create_net_query_stats();
parameters->net_query_stats_ = net_query_stats;
td::OptionParser options;
bool need_print_usage = false;
int http_port = 8081;
int http_stat_port = 0;
td::string log_file_path;
int verbosity_level = 0;
td::int64 log_max_file_size = 2000000000;
td::string working_directory;
td::string temporary_directory;
td::string username;
td::string groupname;
td::uint64 max_connections = 0;
ClientManager::TokenRange token_range{0, 1};
parameters->api_id_ = [](auto x) -> td::int32 {
if (x) {
return td::to_integer<td::int32>(td::Slice(x));
}
return 0;
}(std::getenv("TELEGRAM_API_ID"));
parameters->api_hash_ = [](auto x) -> std::string {
if (x) {
return x;
}
return std::string();
}(std::getenv("TELEGRAM_API_HASH"));
options.set_usage(td::Slice(argv[0]), "--api_id=<arg> --api-hash=<arg> [--local] [OPTION]...");
options.set_description("Telegram Bot API server");
options.add_option('h', "help", "display this help text and exit", [&] { need_print_usage = true; });
options.add_option('\0', "local", "allow the Bot API server to serve local requests",
[&] { parameters->local_mode_ = true; });
options.add_checked_option(
'\0', "api-id",
"application identifier for Telegram API access, which can be obtained at https://my.telegram.org (defaults to "
"the value of the TELEGRAM_API_ID environment variable)",
td::OptionParser::parse_integer(parameters->api_id_));
options.add_option('\0', "api-hash",
"application identifier hash for Telegram API access, which can be obtained at "
"https://my.telegram.org (defaults to the value of the TELEGRAM_API_HASH environment variable)",
td::OptionParser::parse_string(parameters->api_hash_));
options.add_checked_option('p', "http-port", PSLICE() << "HTTP listening port (default is " << http_port << ")",
td::OptionParser::parse_integer(http_port));
options.add_checked_option('s', "http-stat-port", "HTTP statistics port",
td::OptionParser::parse_integer(http_stat_port));
options.add_option('d', "dir", "server working directory", td::OptionParser::parse_string(working_directory));
options.add_option('t', "temp-dir", "directory for storing HTTP server temporary files",
td::OptionParser::parse_string(temporary_directory));
options.add_checked_option('\0', "filter",
"\"<remainder>/<modulo>\". Allow only bots with 'bot_user_id % modulo == remainder'",
[&](td::Slice rem_mod) {
td::Slice rem;
td::Slice mod;
std::tie(rem, mod) = td::split(rem_mod, '/');
TRY_RESULT(rem_i, td::to_integer_safe<td::uint64>(rem));
TRY_RESULT(mod_i, td::to_integer_safe<td::uint64>(mod));
token_range = {rem_i, mod_i};
return td::Status::OK();
});
options.add_checked_option('\0', "max-webhook-connections",
"default value of the maximum webhook connections per bot",
td::OptionParser::parse_integer(parameters->default_max_webhook_connections_));
options.add_option('l', "log", "path to the file where the log will be written",
td::OptionParser::parse_string(log_file_path));
options.add_checked_option('v', "verbosity", "log verbosity level", td::OptionParser::parse_integer(verbosity_level));
options.add_checked_option(
'\0', "log-max-file-size",
PSLICE() << "maximum size of the log file in bytes before it will be auto-rotated (default is "
<< log_max_file_size << ")",
td::OptionParser::parse_integer(log_max_file_size));
options.add_option('u', "username", "effective user name to switch to", td::OptionParser::parse_string(username));
options.add_option('g', "groupname", "effective group name to switch to", td::OptionParser::parse_string(groupname));
options.add_checked_option('c', "max-connections", "maximum number of open file descriptors",
td::OptionParser::parse_integer(max_connections));
options.add_checked_option(
'\0', "proxy", PSLICE() << "HTTP proxy server for outgoing webhook requests in the format http://host:port",
[&](td::Slice address) {
if (td::begins_with(address, "http://")) {
address.remove_prefix(7);
} else if (td::begins_with(address, "https://")) {
address.remove_prefix(8);
}
return parameters->webhook_proxy_ip_address_.init_host_port(address.str());
});
options.add_check([&] {
if (parameters->api_id_ <= 0 || parameters->api_hash_.empty()) {
return td::Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
}
return td::Status::OK();
});
options.add_check([&] {
if (verbosity_level < 0) {
return td::Status::Error("Wrong verbosity level specified");
}
return td::Status::OK();
});
auto r_non_options = options.run(argc, argv, 0);
if (need_print_usage) {
LOG(PLAIN) << options;
return 0;
}
if (r_non_options.is_error()) {
LOG(PLAIN) << argv[0] << ": " << r_non_options.error();
LOG(PLAIN) << options;
return 1;
}
class CombineLog : public td::LogInterface {
public:
void append(td::CSlice slice, int log_level) override {
if (first_ && log_level <= first_verbosity_level_) {
first_->append(slice, log_level);
}
if (second_ && log_level <= second_verbosity_level_) {
second_->append(slice, log_level);
}
}
void set_first(LogInterface *first) {
first_ = first;
}
void set_second(LogInterface *second) {
second_ = second;
}
void set_first_verbosity_level(int verbosity_level) {
first_verbosity_level_ = verbosity_level;
}
void set_second_verbosity_level(int verbosity_level) {
second_verbosity_level_ = verbosity_level;
}
void rotate() override {
if (first_) {
first_->rotate();
}
if (second_) {
second_->rotate();
}
}
td::vector<td::string> get_file_paths() override {
td::vector<td::string> result;
if (first_) {
td::append(result, first_->get_file_paths());
}
if (second_) {
td::append(result, second_->get_file_paths());
}
return result;
}
private:
LogInterface *first_ = nullptr;
int first_verbosity_level_ = VERBOSITY_NAME(FATAL);
LogInterface *second_ = nullptr;
int second_verbosity_level_ = VERBOSITY_NAME(FATAL);
};
CombineLog log;
log.set_first(td::default_log_interface);
log.set_second(&memory_log);
td::log_interface = &log;
td::FileLog file_log;
td::TsLog ts_log(&file_log);
auto init_status = [&] {
if (max_connections != 0) {
TRY_STATUS_PREFIX(td::set_resource_limit(td::ResourceLimitType::NoFile, max_connections),
"Can't set file descriptor limit: ");
}
if (!username.empty()) {
TRY_STATUS_PREFIX(td::change_user(username, groupname), "Can't change effective user: ");
}
if (!working_directory.empty()) {
TRY_STATUS_PREFIX(td::chdir(working_directory), "Can't set working directory: ");
}
if (!temporary_directory.empty()) {
TRY_STATUS_PREFIX(td::set_temporary_dir(temporary_directory), "Can't set temporary directory: ");
}
if (!log_file_path.empty()) {
TRY_STATUS_PREFIX(file_log.init(log_file_path, log_max_file_size), "Can't open log file: ");
log.set_first(&ts_log);
}
return td::Status::OK();
}();
if (init_status.is_error()) {
LOG(PLAIN) << init_status.error();
LOG(PLAIN) << options;
return 1;
}
if (parameters->default_max_webhook_connections_ <= 0) {
parameters->default_max_webhook_connections_ = parameters->local_mode_ ? 100 : 40;
}
::td::VERBOSITY_NAME(dns_resolver) = VERBOSITY_NAME(WARNING);
int memory_verbosity_level = td::max(VERBOSITY_NAME(INFO), verbosity_level);
SET_VERBOSITY_LEVEL(memory_verbosity_level);
log.set_first_verbosity_level(verbosity_level);
log.set_second_verbosity_level(memory_verbosity_level);
// LOG(WARNING) << "Bot API server with commit " << td::GitInfo::commit() << ' '
// << (td::GitInfo::is_dirty() ? "(dirty)" : "") << " started";
LOG(WARNING) << "Bot API server started";
const int threads_n = 5; // +3 for Td, one for slow HTTP connections and one for DNS resolving
td::ConcurrentScheduler sched;
sched.init(threads_n);
td::GetHostByNameActor::Options get_host_by_name_options;
get_host_by_name_options.scheduler_id = threads_n;
parameters->get_host_by_name_actor_id_ =
sched.create_actor_unsafe<td::GetHostByNameActor>(0, "GetHostByName", std::move(get_host_by_name_options))
.release();
auto client_manager =
sched.create_actor_unsafe<ClientManager>(0, "ClientManager", std::move(parameters), token_range).release();
sched
.create_actor_unsafe<HttpServer>(
0, "HttpServer", http_port,
[client_manager, shared_data] {
return td::ActorOwn<td::HttpInboundConnection::Callback>(
td::create_actor<HttpConnection>("HttpConnection", client_manager, shared_data));
})
.release();
if (http_stat_port != 0) {
sched
.create_actor_unsafe<HttpServer>(
0, "HttpStatsServer", http_stat_port,
[client_manager] {
return td::ActorOwn<td::HttpInboundConnection::Callback>(
td::create_actor<HttpStatConnection>("HttpStatConnection", client_manager));
})
.release();
}
sched.start();
double last_cron_time = start_time;
double last_dump_time = start_time - 1000.0;
double last_tqueue_gc_time = start_time - 1000.0;
td::int64 tqueue_deleted_events = 0;
td::int64 last_tqueue_deleted_events = 0;
bool close_flag = false;
std::atomic_bool can_quit{false};
ServerCpuStat::instance(); // create ServerCpuStat instance
while (true) {
sched.run_main(1);
if (!need_rotate_log.test_and_set()) {
td::log_interface->rotate();
}
if (!need_quit.test_and_set()) {
if (close_flag) {
LOG(WARNING) << "Receive stop signal again. Exit immediately...";
std::_Exit(0);
}
LOG(WARNING) << "Stopping engine by a signal";
close_flag = true;
auto guard = sched.get_main_guard();
send_closure(client_manager, &ClientManager::close, td::PromiseCreator::lambda([&can_quit](td::Unit) {
can_quit.store(true);
td::Scheduler::instance()->yield();
}));
}
if (can_quit.exchange(false)) {
break;
}
if (!need_change_verbosity_level.test_and_set()) {
auto current_global_verbosity_level = GET_VERBOSITY_LEVEL();
SET_VERBOSITY_LEVEL(256 - current_global_verbosity_level);
verbosity_level = 256 - verbosity_level;
log.set_first_verbosity_level(verbosity_level);
}
auto next_verbosity_level = shared_data->next_verbosity_level_.exchange(-1);
if (next_verbosity_level != -1) {
verbosity_level = next_verbosity_level;
memory_verbosity_level = td::max(VERBOSITY_NAME(INFO), verbosity_level);
SET_VERBOSITY_LEVEL(memory_verbosity_level);
log.set_first_verbosity_level(verbosity_level);
log.set_second_verbosity_level(memory_verbosity_level);
}
if (!need_dump_log.test_and_set()) {
print_log();
}
double now = td::Time::now();
if (now > last_cron_time + 1.0) {
last_cron_time = now;
ServerCpuStat::update(now);
}
if (now > last_tqueue_gc_time + 60.0) {
auto unix_time = shared_data->get_unix_time(now);
LOG(INFO) << "Run TQueue GC at " << unix_time;
last_tqueue_gc_time = now;
auto guard = sched.get_main_guard();
auto deleted_events = shared_data->tqueue_->run_gc(unix_time);
LOG(INFO) << "TQueue GC deleted " << deleted_events << " events";
tqueue_deleted_events += deleted_events;
if (tqueue_deleted_events > last_tqueue_deleted_events + 10000) {
LOG(WARNING) << "TQueue GC already deleted " << tqueue_deleted_events << " events since the start";
last_tqueue_deleted_events = tqueue_deleted_events;
}
}
if (now > last_dump_time + 300.0) {
last_dump_time = now;
if (is_memprof_on()) {
LOG(WARNING) << "Memory dump:";
td::vector<AllocInfo> v;
dump_alloc([&](const AllocInfo &info) { v.push_back(info); });
std::sort(v.begin(), v.end(), [](const AllocInfo &a, const AllocInfo &b) { return a.size > b.size; });
size_t total_size = 0;
size_t other_size = 0;
int count = 0;
for (auto &info : v) {
if (count++ < 50) {
LOG(WARNING) << td::format::as_size(info.size) << td::format::as_array(info.backtrace);
} else {
other_size += info.size;
}
total_size += info.size;
}
LOG(WARNING) << td::tag("other", td::format::as_size(other_size));
LOG(WARNING) << td::tag("total", td::format::as_size(total_size));
LOG(WARNING) << td::tag("total traces", get_ht_size());
LOG(WARNING) << td::tag("fast_backtrace_success_rate", get_fast_backtrace_success_rate());
}
LOG(WARNING) << td::tag("buffer_mem", td::format::as_size(td::BufferAllocator::get_buffer_mem()));
LOG(WARNING) << td::tag("buffer_slice_size", td::format::as_size(td::BufferAllocator::get_buffer_slice_size()));
auto query_count = shared_data->query_count_.load();
LOG(WARNING) << td::tag("pending queries", query_count);
td::uint64 i = 0;
bool was_gap = false;
for (auto end = &shared_data->query_list_, cur = end->prev; cur != end; cur = cur->prev, i++) {
if (i < 20 || i > query_count - 20 || i % (query_count / 50 + 1) == 0) {
if (was_gap) {
LOG(WARNING) << "...";
was_gap = false;
}
LOG(WARNING) << static_cast<Query &>(*cur);
} else {
was_gap = true;
}
}
td::dump_pending_network_queries(*net_query_stats);
}
}
LOG(WARNING) << "--------------------FINISH ENGINE--------------------";
CHECK(net_query_stats.use_count() == 1);
net_query_stats = nullptr;
sched.finish();
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
td::log_interface = td::default_log_interface;
return 0;
}
} // namespace telegram_bot_api
int main(int argc, char *argv[]) {
return telegram_bot_api::main(argc, argv);
}