public release
This commit is contained in:
parent
f9315603b6
commit
d95aa2840c
|
@ -0,0 +1,59 @@
|
||||||
|
cmake_minimum_required(VERSION 3.9)
|
||||||
|
project(tg2sip)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
add_subdirectory(libtgvoip)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
find_package(Td 1.2.0 REQUIRED)
|
||||||
|
find_package(spdlog 0.17 REQUIRED)
|
||||||
|
pkg_check_modules(PJSIP libpjproject>=2.8 REQUIRED)
|
||||||
|
pkg_check_modules(OPUS opus REQUIRED)
|
||||||
|
|
||||||
|
add_executable(tg2sip
|
||||||
|
tg2sip/main.cpp
|
||||||
|
tg2sip/tg.cpp
|
||||||
|
tg2sip/tg.h
|
||||||
|
tg2sip/sip.cpp
|
||||||
|
tg2sip/sip.h
|
||||||
|
tg2sip/settings.cpp
|
||||||
|
tg2sip/settings.h
|
||||||
|
tg2sip/logging.cpp
|
||||||
|
tg2sip/logging.h
|
||||||
|
tg2sip/utils.cpp
|
||||||
|
tg2sip/utils.h
|
||||||
|
tg2sip/queue.h
|
||||||
|
tg2sip/gateway.cpp
|
||||||
|
tg2sip/gateway.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET tg2sip PRE_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
${CMAKE_SOURCE_DIR}/settings.ini
|
||||||
|
$<TARGET_FILE_DIR:tg2sip>)
|
||||||
|
|
||||||
|
target_include_directories(tg2sip PRIVATE
|
||||||
|
${PJSIP_INCLUDE_DIRS}
|
||||||
|
${PROJECT_SOURCE_DIR}
|
||||||
|
${OPUS_INCLUDE_DIRS}
|
||||||
|
include)
|
||||||
|
|
||||||
|
target_link_libraries(tg2sip PRIVATE
|
||||||
|
${PJSIP_LIBRARIES}
|
||||||
|
libtgvoip
|
||||||
|
Threads::Threads
|
||||||
|
Td::TdStatic)
|
||||||
|
|
||||||
|
add_executable(gen_db
|
||||||
|
tg2sip/gen_db.cpp
|
||||||
|
tg2sip/settings.cpp
|
||||||
|
tg2sip/settings.h)
|
||||||
|
|
||||||
|
target_include_directories(gen_db PRIVATE
|
||||||
|
include)
|
||||||
|
|
||||||
|
target_link_libraries(gen_db PRIVATE
|
||||||
|
Td::TdStatic)
|
|
@ -0,0 +1,339 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Lesser General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
convey the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along
|
||||||
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program is interactive, make it output a short notice like this
|
||||||
|
when it starts in an interactive mode:
|
||||||
|
|
||||||
|
Gnomovision version 69, Copyright (C) year name of author
|
||||||
|
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, the commands you use may
|
||||||
|
be called something other than `show w' and `show c'; they could even be
|
||||||
|
mouse-clicks or menu items--whatever suits your program.
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or your
|
||||||
|
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||||
|
necessary. Here is a sample; alter the names:
|
||||||
|
|
||||||
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||||
|
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||||
|
|
||||||
|
<signature of Ty Coon>, 1 April 1989
|
||||||
|
Ty Coon, President of Vice
|
||||||
|
|
||||||
|
This General Public License does not permit incorporating your program into
|
||||||
|
proprietary programs. If your program is a subroutine library, you may
|
||||||
|
consider it more useful to permit linking proprietary applications with the
|
||||||
|
library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License.
|
|
@ -0,0 +1,69 @@
|
||||||
|
FROM ubuntu:bionic as builder
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential git \
|
||||||
|
wget ca-certificates \
|
||||||
|
pkg-config libopus-dev libssl-dev \
|
||||||
|
zlib1g-dev gperf ccache \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN wget https://cmake.org/files/v3.9/cmake-3.9.6-Linux-x86_64.sh \
|
||||||
|
&& sh cmake-3.9.6-Linux-x86_64.sh --prefix=/usr --exclude-subdir
|
||||||
|
|
||||||
|
COPY tdlib_header.patch /
|
||||||
|
|
||||||
|
RUN git clone https://github.com/tdlib/td.git \
|
||||||
|
&& cd td \
|
||||||
|
&& git checkout cfe4d9bdcee9305632eb228a46a95407d05b5c7a -b build \
|
||||||
|
&& git apply /tdlib_header.patch \
|
||||||
|
&& mkdir build \
|
||||||
|
&& cd build \
|
||||||
|
&& cmake -DCMAKE_BUILD_TYPE=Release .. \
|
||||||
|
&& cmake --build . --target install
|
||||||
|
|
||||||
|
COPY config_site.h /
|
||||||
|
COPY pj_threadname.patch /
|
||||||
|
COPY pj_cpp1z.patch /
|
||||||
|
|
||||||
|
RUN git clone https://github.com/pjsip/pjproject.git \
|
||||||
|
&& cd pjproject \
|
||||||
|
&& git reset --hard 2.8 \
|
||||||
|
&& git apply /pj_threadname.patch \
|
||||||
|
&& git apply /pj_cpp1z.patch \
|
||||||
|
&& cp /config_site.h pjlib/include/pj \
|
||||||
|
&& ./configure --disable-sound CFLAGS="-O3 -DNDEBUG" \
|
||||||
|
&& make dep \
|
||||||
|
&& make -j $(shell nproc) \
|
||||||
|
&& make install
|
||||||
|
|
||||||
|
RUN git clone -n https://github.com/gabime/spdlog.git \
|
||||||
|
&& cd spdlog \
|
||||||
|
&& git checkout tags/v0.17.0 \
|
||||||
|
&& mkdir build \
|
||||||
|
&& cd build \
|
||||||
|
&& cmake -DCMAKE_BUILD_TYPE=Release -DSPDLOG_BUILD_EXAMPLES=OFF -DSPDLOG_BUILD_TESTING=OFF .. \
|
||||||
|
&& cmake --build . --target install
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
|
||||||
|
RUN cd src \
|
||||||
|
&& mkdir build \
|
||||||
|
&& cd build \
|
||||||
|
&& cmake -DCMAKE_BUILD_TYPE=Release .. \
|
||||||
|
&& cmake --build .
|
||||||
|
|
||||||
|
FROM ubuntu:bionic
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
libopus0 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /gateway
|
||||||
|
|
||||||
|
COPY --from=builder /src/build/tg2sip .
|
||||||
|
COPY --from=builder /src/build/gen_db .
|
||||||
|
COPY --from=builder /src/build/settings.ini .
|
||||||
|
|
||||||
|
CMD ./tg2sip
|
27
README.md
27
README.md
|
@ -0,0 +1,27 @@
|
||||||
|
# TG2SIP
|
||||||
|
|
||||||
|
TG2SIP is a Telegram<->SIP voice gateway. It can be used to forward incoming telegram calls to your SIP PBX or make SIP->Telegram calls.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Your SIP PBX should be comaptible with `L16@48000` or `OPUS@48000` voice codec.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Download prebuild version from CI or compile it yourself.
|
||||||
|
2. Obtain `api_id` and `api_hash` tokens from [this](https://my.telegram.org) page and put them in `settings.ini` file.
|
||||||
|
3. Login into telegram with `gen_db` app
|
||||||
|
4. Set SIP server settings in `settings.ini`
|
||||||
|
5. Run `tg2sip`
|
||||||
|
|
||||||
|
## Donate
|
||||||
|
|
||||||
|
[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=755FZWPRC9YGL&lc=US&item_name=TG2SIP¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted)
|
||||||
|
|
||||||
|
[Yandex.Money](https://yasobe.ru/na/tg2sip)
|
||||||
|
|
||||||
|
**BTC** 39wNzvtcyRrTKmq5DjcUfGTixnGVSf8qLg
|
||||||
|
**BCH** qqgwg0g96sayht4lzxc89ky7mkdxfyj7jcl5m8qfps
|
||||||
|
**ETH** 0x72B8cb476b2c85b1170Ae2cdFB243B17680290b4
|
||||||
|
**ETC** 0x9C7d6CD9F9E0584e65f8aD20e1d2Ced947a55207
|
||||||
|
**LTC** MFyBRJTnHqXharzH7D3FYeEhAJuywMRfMd
|
|
@ -0,0 +1,47 @@
|
||||||
|
FROM ubuntu:bionic
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends \
|
||||||
|
build-essential git \
|
||||||
|
wget ca-certificates \
|
||||||
|
pkg-config libopus-dev libssl-dev \
|
||||||
|
zlib1g-dev gperf ccache \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN wget https://cmake.org/files/v3.9/cmake-3.9.6-Linux-x86_64.sh \
|
||||||
|
&& sh cmake-3.9.6-Linux-x86_64.sh --prefix=/usr --exclude-subdir
|
||||||
|
|
||||||
|
COPY tdlib_header.patch /
|
||||||
|
COPY tdlib_threadname.patch /
|
||||||
|
|
||||||
|
RUN git clone https://github.com/tdlib/td.git \
|
||||||
|
&& cd td \
|
||||||
|
&& git reset --hard v1.2.0 \
|
||||||
|
&& git apply /tdlib_header.patch \
|
||||||
|
&& git apply /tdlib_threadname.patch \
|
||||||
|
&& mkdir build \
|
||||||
|
&& cd build \
|
||||||
|
&& cmake -DCMAKE_BUILD_TYPE=Release .. \
|
||||||
|
&& cmake --build . --target install \
|
||||||
|
&& cd / \
|
||||||
|
&& rm -rf td
|
||||||
|
|
||||||
|
COPY config_site.h /
|
||||||
|
|
||||||
|
RUN git clone https://github.com/Infactum/pjproject.git \
|
||||||
|
&& cd pjproject \
|
||||||
|
&& cp /config_site.h pjlib/include/pj \
|
||||||
|
&& ./configure --disable-sound CFLAGS="-O3 -DNDEBUG" \
|
||||||
|
&& make dep && make && make install \
|
||||||
|
&& cd / \
|
||||||
|
&& rm -rf pjproject
|
||||||
|
|
||||||
|
RUN git clone -n https://github.com/gabime/spdlog.git \
|
||||||
|
&& cd spdlog \
|
||||||
|
&& git checkout tags/v0.17.0 \
|
||||||
|
&& mkdir build \
|
||||||
|
&& cd build \
|
||||||
|
&& cmake -DCMAKE_BUILD_TYPE=Release -DSPDLOG_BUILD_EXAMPLES=OFF -DSPDLOG_BUILD_TESTING=OFF .. \
|
||||||
|
&& cmake --build . --target install \
|
||||||
|
&& cd / \
|
||||||
|
&& rm -rf spdlog
|
|
@ -0,0 +1,8 @@
|
||||||
|
#define PJMEDIA_CONF_USE_SWITCH_BOARD 1
|
||||||
|
#define PJMEDIA_CONF_SWITCH_BOARD_BUF_SIZE 2000
|
||||||
|
|
||||||
|
#define PJSUA_MAX_CALLS 128
|
||||||
|
#define PJSUA_MAX_PLAYERS 256
|
||||||
|
#define PJ_IOQUEUE_MAX_HANDLES 256
|
||||||
|
|
||||||
|
#define PJMEDIA_CODEC_L16_HAS_48KHZ_MONO 1
|
|
@ -0,0 +1,19 @@
|
||||||
|
diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp
|
||||||
|
index a150e6c..cd66d91 100644
|
||||||
|
--- a/td/telegram/Td.cpp
|
||||||
|
+++ b/td/telegram/Td.cpp
|
||||||
|
@@ -4827,10 +4827,10 @@ Status Td::set_parameters(td_api::object_ptr<td_api::tdlibParameters> parameters
|
||||||
|
if (options.application_version.empty()) {
|
||||||
|
return Status::Error(400, "Application version must be non-empty");
|
||||||
|
}
|
||||||
|
- if (options.api_id != 21724) {
|
||||||
|
- options.application_version += ", TDLib ";
|
||||||
|
- options.application_version += TDLIB_VERSION;
|
||||||
|
- }
|
||||||
|
+ // if (options.api_id != 21724) {
|
||||||
|
+ // options.application_version += ", TDLib ";
|
||||||
|
+ // options.application_version += TDLIB_VERSION;
|
||||||
|
+ // }
|
||||||
|
G()->set_mtproto_header(std::make_unique<MtprotoHeader>(options));
|
||||||
|
|
||||||
|
state_ = State::Decrypt;
|
|
@ -0,0 +1,12 @@
|
||||||
|
diff --git a/tdutils/td/utils/port/detail/ThreadStl.h b/tdutils/td/utils/port/detail/ThreadStl.h
|
||||||
|
index 64bf321..68d3bf6 100644
|
||||||
|
--- a/tdutils/td/utils/port/detail/ThreadStl.h
|
||||||
|
+++ b/tdutils/td/utils/port/detail/ThreadStl.h
|
||||||
|
@@ -37,6 +37,7 @@ class ThreadStl {
|
||||||
|
invoke_tuple(std::move(args));
|
||||||
|
clear_thread_locals();
|
||||||
|
});
|
||||||
|
+ pthread_setname_np(thread_.native_handle(), "tdlib");
|
||||||
|
}
|
||||||
|
|
||||||
|
void join() {
|
|
@ -0,0 +1,441 @@
|
||||||
|
// Read an INI file into easy-to-access name/value pairs.
|
||||||
|
|
||||||
|
// inih and INIReader are released under the New BSD license (see LICENSE.txt).
|
||||||
|
// Go to the project home page for more info:
|
||||||
|
//
|
||||||
|
// https://github.com/benhoyt/inih
|
||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __INI_H__
|
||||||
|
#define __INI_H__
|
||||||
|
|
||||||
|
/* Make this header file easier to include in C++ code */
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* Typedef for prototype of handler function. */
|
||||||
|
typedef int (*ini_handler)(void* user, const char* section,
|
||||||
|
const char* name, const char* value);
|
||||||
|
|
||||||
|
/* Typedef for prototype of fgets-style reader function. */
|
||||||
|
typedef char* (*ini_reader)(char* str, int num, void* stream);
|
||||||
|
|
||||||
|
/* Parse given INI-style file. May have [section]s, name=value pairs
|
||||||
|
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
||||||
|
is "" if name=value pair parsed before any section heading. name:value
|
||||||
|
pairs are also supported as a concession to Python's configparser.
|
||||||
|
|
||||||
|
For each name=value pair parsed, call handler function with given user
|
||||||
|
pointer as well as section, name, and value (data only valid for duration
|
||||||
|
of handler call). Handler should return nonzero on success, zero on error.
|
||||||
|
|
||||||
|
Returns 0 on success, line number of first error on parse error (doesn't
|
||||||
|
stop on first error), -1 on file open error, or -2 on memory allocation
|
||||||
|
error (only when INI_USE_STACK is zero).
|
||||||
|
*/
|
||||||
|
int ini_parse(const char* filename, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
|
||||||
|
close the file when it's finished -- the caller must do that. */
|
||||||
|
int ini_parse_file(FILE* file, ini_handler handler, void* user);
|
||||||
|
|
||||||
|
/* Same as ini_parse(), but takes an ini_reader function pointer instead of
|
||||||
|
filename. Used for implementing custom or string-based I/O. */
|
||||||
|
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user);
|
||||||
|
|
||||||
|
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
||||||
|
configparser. If allowed, ini_parse() will call the handler with the same
|
||||||
|
name for each subsequent line parsed. */
|
||||||
|
#ifndef INI_ALLOW_MULTILINE
|
||||||
|
#define INI_ALLOW_MULTILINE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
||||||
|
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
||||||
|
#ifndef INI_ALLOW_BOM
|
||||||
|
#define INI_ALLOW_BOM 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to allow inline comments (with valid inline comment characters
|
||||||
|
specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
|
||||||
|
Python 3.2+ configparser behaviour. */
|
||||||
|
#ifndef INI_ALLOW_INLINE_COMMENTS
|
||||||
|
#define INI_ALLOW_INLINE_COMMENTS 1
|
||||||
|
#endif
|
||||||
|
#ifndef INI_INLINE_COMMENT_PREFIXES
|
||||||
|
#define INI_INLINE_COMMENT_PREFIXES ";"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Nonzero to use stack, zero to use heap (malloc/free). */
|
||||||
|
#ifndef INI_USE_STACK
|
||||||
|
#define INI_USE_STACK 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Stop parsing on first error (default is to keep parsing). */
|
||||||
|
#ifndef INI_STOP_ON_FIRST_ERROR
|
||||||
|
#define INI_STOP_ON_FIRST_ERROR 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Maximum line length for any line in INI file. */
|
||||||
|
#ifndef INI_MAX_LINE
|
||||||
|
#define INI_MAX_LINE 200
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* inih -- simple .INI file parser
|
||||||
|
|
||||||
|
inih is released under the New BSD license (see LICENSE.txt). Go to the project
|
||||||
|
home page for more info:
|
||||||
|
|
||||||
|
https://github.com/benhoyt/inih
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
#include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_SECTION 50
|
||||||
|
#define MAX_NAME 50
|
||||||
|
|
||||||
|
/* Strip whitespace chars off end of given string, in place. Return s. */
|
||||||
|
inline static char* rstrip(char* s)
|
||||||
|
{
|
||||||
|
char* p = s + strlen(s);
|
||||||
|
while (p > s && isspace((unsigned char)(*--p)))
|
||||||
|
*p = '\0';
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first non-whitespace char in given string. */
|
||||||
|
inline static char* lskip(const char* s)
|
||||||
|
{
|
||||||
|
while (*s && isspace((unsigned char)(*s)))
|
||||||
|
s++;
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return pointer to first char (of chars) or inline comment in given string,
|
||||||
|
or pointer to null at end of string if neither found. Inline comment must
|
||||||
|
be prefixed by a whitespace character to register as a comment. */
|
||||||
|
inline static char* find_chars_or_comment(const char* s, const char* chars)
|
||||||
|
{
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
int was_space = 0;
|
||||||
|
while (*s && (!chars || !strchr(chars, *s)) &&
|
||||||
|
!(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
|
||||||
|
was_space = isspace((unsigned char)(*s));
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
while (*s && (!chars || !strchr(chars, *s))) {
|
||||||
|
s++;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return (char*)s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
||||||
|
inline static char* strncpy0(char* dest, const char* src, size_t size)
|
||||||
|
{
|
||||||
|
strncpy(dest, src, size);
|
||||||
|
dest[size - 1] = '\0';
|
||||||
|
return dest;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
inline int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
|
||||||
|
void* user)
|
||||||
|
{
|
||||||
|
/* Uses a fair bit of stack (use heap instead if you need to) */
|
||||||
|
#if INI_USE_STACK
|
||||||
|
char line[INI_MAX_LINE];
|
||||||
|
#else
|
||||||
|
char* line;
|
||||||
|
#endif
|
||||||
|
char section[MAX_SECTION] = "";
|
||||||
|
char prev_name[MAX_NAME] = "";
|
||||||
|
|
||||||
|
char* start;
|
||||||
|
char* end;
|
||||||
|
char* name;
|
||||||
|
char* value;
|
||||||
|
int lineno = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
line = (char*)malloc(INI_MAX_LINE);
|
||||||
|
if (!line) {
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Scan through stream line by line */
|
||||||
|
while (reader(line, INI_MAX_LINE, stream) != NULL) {
|
||||||
|
lineno++;
|
||||||
|
|
||||||
|
start = line;
|
||||||
|
#if INI_ALLOW_BOM
|
||||||
|
if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
|
||||||
|
(unsigned char)start[1] == 0xBB &&
|
||||||
|
(unsigned char)start[2] == 0xBF) {
|
||||||
|
start += 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
start = lskip(rstrip(start));
|
||||||
|
|
||||||
|
if (*start == ';' || *start == '#') {
|
||||||
|
/* Per Python configparser, allow both ; and # comments at the
|
||||||
|
start of a line */
|
||||||
|
}
|
||||||
|
#if INI_ALLOW_MULTILINE
|
||||||
|
else if (*prev_name && *start && start > line) {
|
||||||
|
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
end = find_chars_or_comment(start, NULL);
|
||||||
|
if (*end)
|
||||||
|
*end = '\0';
|
||||||
|
rstrip(start);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Non-blank line with leading whitespace, treat as continuation
|
||||||
|
of previous name's value (as per Python configparser). */
|
||||||
|
if (!handler(user, section, prev_name, start) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else if (*start == '[') {
|
||||||
|
/* A "[section]" line */
|
||||||
|
end = find_chars_or_comment(start + 1, "]");
|
||||||
|
if (*end == ']') {
|
||||||
|
*end = '\0';
|
||||||
|
strncpy0(section, start + 1, sizeof(section));
|
||||||
|
*prev_name = '\0';
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No ']' found on section line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (*start) {
|
||||||
|
/* Not a comment, must be a name[=:]value pair */
|
||||||
|
end = find_chars_or_comment(start, "=:");
|
||||||
|
if (*end == '=' || *end == ':') {
|
||||||
|
*end = '\0';
|
||||||
|
name = rstrip(start);
|
||||||
|
value = lskip(end + 1);
|
||||||
|
#if INI_ALLOW_INLINE_COMMENTS
|
||||||
|
end = find_chars_or_comment(value, NULL);
|
||||||
|
if (*end)
|
||||||
|
*end = '\0';
|
||||||
|
#endif
|
||||||
|
rstrip(value);
|
||||||
|
|
||||||
|
/* Valid name[=:]value pair found, call handler */
|
||||||
|
strncpy0(prev_name, name, sizeof(prev_name));
|
||||||
|
if (!handler(user, section, name, value) && !error)
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
else if (!error) {
|
||||||
|
/* No '=' or ':' found on name[=:]value line */
|
||||||
|
error = lineno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if INI_STOP_ON_FIRST_ERROR
|
||||||
|
if (error)
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !INI_USE_STACK
|
||||||
|
free(line);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
inline int ini_parse_file(FILE* file, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
return ini_parse_stream((ini_reader)fgets, file, handler, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See documentation in header file. */
|
||||||
|
inline int ini_parse(const char* filename, ini_handler handler, void* user)
|
||||||
|
{
|
||||||
|
FILE* file;
|
||||||
|
int error;
|
||||||
|
|
||||||
|
file = fopen(filename, "r");
|
||||||
|
if (!file)
|
||||||
|
return -1;
|
||||||
|
error = ini_parse_file(file, handler, user);
|
||||||
|
fclose(file);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* __INI_H__ */
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __INIREADER_H__
|
||||||
|
#define __INIREADER_H__
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// Read an INI file into easy-to-access name/value pairs. (Note that I've gone
|
||||||
|
// for simplicity here rather than speed, but it should be pretty decent.)
|
||||||
|
class INIReader
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// Empty Constructor
|
||||||
|
INIReader() {};
|
||||||
|
|
||||||
|
// Construct INIReader and parse given filename. See ini.h for more info
|
||||||
|
// about the parsing.
|
||||||
|
INIReader(std::string filename);
|
||||||
|
|
||||||
|
// Return the result of ini_parse(), i.e., 0 on success, line number of
|
||||||
|
// first error on parse error, or -1 on file open error.
|
||||||
|
int ParseError() const;
|
||||||
|
|
||||||
|
// Return the list of sections found in ini file
|
||||||
|
std::set<std::string> Sections();
|
||||||
|
|
||||||
|
// Get a string value from INI file, returning default_value if not found.
|
||||||
|
std::string Get(std::string section, std::string name,
|
||||||
|
std::string default_value);
|
||||||
|
|
||||||
|
// Get an integer (long) value from INI file, returning default_value if
|
||||||
|
// not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2").
|
||||||
|
long GetInteger(std::string section, std::string name, long default_value);
|
||||||
|
|
||||||
|
// Get a real (floating point double) value from INI file, returning
|
||||||
|
// default_value if not found or not a valid floating point value
|
||||||
|
// according to strtod().
|
||||||
|
double GetReal(std::string section, std::string name, double default_value);
|
||||||
|
|
||||||
|
// Get a boolean value from INI file, returning default_value if not found or if
|
||||||
|
// not a valid true/false value. Valid true values are "true", "yes", "on", "1",
|
||||||
|
// and valid false values are "false", "no", "off", "0" (not case sensitive).
|
||||||
|
bool GetBoolean(std::string section, std::string name, bool default_value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int _error;
|
||||||
|
std::map<std::string, std::string> _values;
|
||||||
|
std::set<std::string> _sections;
|
||||||
|
static std::string MakeKey(std::string section, std::string name);
|
||||||
|
static int ValueHandler(void* user, const char* section, const char* name,
|
||||||
|
const char* value);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __INIREADER_H__
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __INIREADER__
|
||||||
|
#define __INIREADER__
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
|
||||||
|
inline INIReader::INIReader(string filename)
|
||||||
|
{
|
||||||
|
_error = ini_parse(filename.c_str(), ValueHandler, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int INIReader::ParseError() const
|
||||||
|
{
|
||||||
|
return _error;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::set<string> INIReader::Sections()
|
||||||
|
{
|
||||||
|
return _sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline string INIReader::Get(string section, string name, string default_value)
|
||||||
|
{
|
||||||
|
string key = MakeKey(section, name);
|
||||||
|
return _values.count(key) ? _values[key] : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline long INIReader::GetInteger(string section, string name, long default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
const char* value = valstr.c_str();
|
||||||
|
char* end;
|
||||||
|
// This parses "1234" (decimal) and also "0x4D2" (hex)
|
||||||
|
long n = strtol(value, &end, 0);
|
||||||
|
return end > value ? n : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline double INIReader::GetReal(string section, string name, double default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
const char* value = valstr.c_str();
|
||||||
|
char* end;
|
||||||
|
double n = strtod(value, &end);
|
||||||
|
return end > value ? n : default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool INIReader::GetBoolean(string section, string name, bool default_value)
|
||||||
|
{
|
||||||
|
string valstr = Get(section, name, "");
|
||||||
|
// Convert to lower case to make string comparisons case-insensitive
|
||||||
|
std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower);
|
||||||
|
if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1")
|
||||||
|
return true;
|
||||||
|
else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0")
|
||||||
|
return false;
|
||||||
|
else
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline string INIReader::MakeKey(string section, string name)
|
||||||
|
{
|
||||||
|
string key = section + "=" + name;
|
||||||
|
// Convert to lower case to make section/name lookups case-insensitive
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(), ::tolower);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int INIReader::ValueHandler(void* user, const char* section, const char* name,
|
||||||
|
const char* value)
|
||||||
|
{
|
||||||
|
INIReader* reader = (INIReader*)user;
|
||||||
|
string key = MakeKey(section, name);
|
||||||
|
if (reader->_values[key].size() > 0)
|
||||||
|
reader->_values[key] += "\n";
|
||||||
|
reader->_values[key] += value;
|
||||||
|
reader->_sections.insert(section);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __INIREADER__
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,82 @@
|
||||||
|
; sample settings file
|
||||||
|
; commented settings are optional
|
||||||
|
|
||||||
|
[logging]
|
||||||
|
;core=2 ; 0-trace 2-info 4-err 6-off
|
||||||
|
; 1-debug 3-warn 5-crit
|
||||||
|
|
||||||
|
;tgvoip=5 ; same as core
|
||||||
|
;pjsip=2 ; same as core
|
||||||
|
;sip_messages=true ; log sip messages if pjsip debug is enabled
|
||||||
|
|
||||||
|
;console_min_level=0 ; minimal log level that will be written into console
|
||||||
|
;file_min_level=0 ; same but into file
|
||||||
|
|
||||||
|
;tdlib=3 ; TDLib is written to file only and has its own log level values
|
||||||
|
; not affected by other log settings
|
||||||
|
; 0-fatal 2-warnings 4-debug
|
||||||
|
; 1-errors 3-info 5-verbose debug
|
||||||
|
|
||||||
|
[sip]
|
||||||
|
;public_address= ; Address to advertise as the address UDP transport.
|
||||||
|
|
||||||
|
;stun_server=
|
||||||
|
|
||||||
|
;port=5060
|
||||||
|
;port_range=0 ; Specify the port range for socket binding, relative to the start
|
||||||
|
; port number specified in port.
|
||||||
|
|
||||||
|
;id_uri=sip:localhost ; The Address of Record or AOR, that is full SIP URL that identifies the account.
|
||||||
|
; The value can take name address or URL format, and will look something
|
||||||
|
; like "sip:account@serviceprovider".
|
||||||
|
|
||||||
|
;callback_uri= ; SIP URI for TG->SIP incoming calls processing
|
||||||
|
|
||||||
|
;raw_pcm=true ; use L16@48k codec if true or OPUS@48k otherwise
|
||||||
|
; keep true for lower CPU consumption
|
||||||
|
|
||||||
|
;thread_count=1 ; Specify the number of worker threads to handle incoming RTP
|
||||||
|
; packets. A value of one is recommended for most applications.
|
||||||
|
|
||||||
|
[telegram]
|
||||||
|
api_id= ; Application identifier for Telegram API access
|
||||||
|
api_hash= ; Application identifier hash for Telegram API access
|
||||||
|
; which can be obtained at https://my.telegram.org.
|
||||||
|
|
||||||
|
;system_language_code=en-US ; IETF language tag of the user's operating system language
|
||||||
|
|
||||||
|
;device_model=PC ; Model of the device the application is being run on
|
||||||
|
|
||||||
|
;system_version=Linux ; Version of the operating system the application is being run on
|
||||||
|
|
||||||
|
;application_version=1.0
|
||||||
|
|
||||||
|
;database_folder= ; The path to the directory for the persistent TDLib database;
|
||||||
|
; if empty, the current working directory will be used.
|
||||||
|
|
||||||
|
;udp_p2p=false ; True, if UDP peer-to-peer connections are supported
|
||||||
|
|
||||||
|
;udp_reflector=true ; True, if connection through UDP reflectors is supported.
|
||||||
|
|
||||||
|
;enable_aec=false ; acoustic echo cancellation
|
||||||
|
;enable_ns=false ; noise suppression
|
||||||
|
;enable_agc=false ; automatic gain control
|
||||||
|
|
||||||
|
;use_proxy=false ; use SOCKS5 proxy for MTProto requests
|
||||||
|
;proxy_address=
|
||||||
|
;proxy_port=0
|
||||||
|
;proxy_username=
|
||||||
|
;proxy_password=
|
||||||
|
|
||||||
|
;use_voip_proxy=false ; use SOCKS5 proxy for VoIP
|
||||||
|
;voip_proxy_address=
|
||||||
|
;voip_proxy_port=0
|
||||||
|
;voip_proxy_username=
|
||||||
|
;voip_proxy_password=
|
||||||
|
|
||||||
|
[other]
|
||||||
|
;extra_wait_time=30 ; If gateway gets temporary blocked with "Too Many Requests" reason,
|
||||||
|
; then block all outgoing telegram requests for X more seconds than was
|
||||||
|
; requested by server
|
||||||
|
|
||||||
|
;peer_flood_time=86400 ; Seconds to wait on PEER_FLOOD
|
|
@ -0,0 +1,883 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <regex>
|
||||||
|
#include "gateway.h"
|
||||||
|
|
||||||
|
namespace sml = boost::sml;
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
|
||||||
|
#define CALL_PROTO_MIN_LAYER 65
|
||||||
|
#define CALL_PROTO_MAX_LAYER 74
|
||||||
|
|
||||||
|
namespace state_machine::guards {
|
||||||
|
bool IsIncoming::operator()(const td::td_api::object_ptr<td::td_api::updateCall> &event) const {
|
||||||
|
return !event->call_->is_outgoing_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInState::operator()(const td::td_api::object_ptr<td::td_api::updateCall> &event) const {
|
||||||
|
return event->call_->state_->get_id() == id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackUriIsSet::operator()(const Settings &settings) const {
|
||||||
|
return !settings.callback_uri().empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsMediaReady::operator()(const sip::events::CallMediaStateUpdate &event) const {
|
||||||
|
return event.has_media;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSipInState::operator()(const sip::events::CallStateUpdate &event) const {
|
||||||
|
return event.state == state_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsTextContent::operator()(const td::td_api::object_ptr<td::td_api::updateNewMessage> &event) const {
|
||||||
|
return event->message_->content_->get_id() == td::td_api::messageText::ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDtmfString::operator()(const td::td_api::object_ptr<td::td_api::updateNewMessage> &event) const {
|
||||||
|
|
||||||
|
// String must contain only characters as described on RFC 2833 section 3.10.
|
||||||
|
// if PJMEDIA_HAS_DTMF_FLASH is enabled, character 'R' is used to represent
|
||||||
|
// the event type 16 (flash) as stated in RFC 4730.
|
||||||
|
// PJSUA maximum number of characters are 32.
|
||||||
|
|
||||||
|
auto content = td_api::move_object_as<td_api::messageText>(event->message_->content_);
|
||||||
|
const std::regex dtmf_regex("^[0-9A-D*#]{1,32}$");
|
||||||
|
auto result = regex_match(content->text_->text_, dtmf_regex);
|
||||||
|
|
||||||
|
// Don't forget to put data back!
|
||||||
|
event->message_->content_ = td_api::move_object_as<td_api::MessageContent>(content);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace state_machine::actions {
|
||||||
|
void StoreSipId::operator()(Context &ctx, const sip::events::IncomingCall &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
ctx.sip_call_id = event.id;
|
||||||
|
DEBUG(logger, "[{}] associated with SIP#{}", ctx.id(), ctx.sip_call_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreTgId::operator()(Context &ctx, const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
ctx.tg_call_id = event->call_->id_;
|
||||||
|
DEBUG(logger, "[{}] associated with TG#{}", ctx.id(), ctx.tg_call_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StoreTgUserId::operator()(Context &ctx, const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
ctx.user_id = event->call_->user_id_;
|
||||||
|
DEBUG(logger, "[{}] stored user id {}", ctx.id(), ctx.user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanTgId::operator()(Context &ctx) const {
|
||||||
|
ctx.tg_call_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CleanSipId::operator()(Context &ctx) const {
|
||||||
|
ctx.sip_call_id = PJSUA_INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void CleanUp::operator()(Context &ctx, sip::Client &sip_client, tg::Client &tg_client,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
TRACE(logger, "[{}] cleanup start", ctx.id());
|
||||||
|
|
||||||
|
if (ctx.controller) {
|
||||||
|
ctx.controller->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.tg_call_id != 0) {
|
||||||
|
DEBUG(logger, "[{}] hangup TG #{}", ctx.id(), ctx.tg_call_id);
|
||||||
|
|
||||||
|
auto result = tg_client.send_query_async(td_api::make_object<td_api::discardCall>(
|
||||||
|
ctx.tg_call_id, /* call_id_ */
|
||||||
|
false, /* is_disconnected_ */
|
||||||
|
0, /* duration_ */
|
||||||
|
ctx.tg_call_id /*connection_id */
|
||||||
|
)).get();
|
||||||
|
|
||||||
|
if (result->get_id() == td_api::error::ID) {
|
||||||
|
logger->error("[{}] TG call discard failure:\n{}", ctx.id(), to_string(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.tg_call_id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.sip_call_id != PJSUA_INVALID_ID) {
|
||||||
|
try {
|
||||||
|
sip_client.Hangup(ctx.sip_call_id, ctx.hangup_prm);
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
logger->error(error.reason);
|
||||||
|
}
|
||||||
|
ctx.sip_call_id = PJSUA_INVALID_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE(logger, "[{}] cleanup end");
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialSip::operator()(Context &ctx, tg::Client &tg_client, sip::Client &sip_client,
|
||||||
|
const td_api::object_ptr<td_api::updateCall> &event,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
const Settings &settings, std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
|
||||||
|
auto tg_user_id = event->call_->user_id_;
|
||||||
|
auto response = tg_client.send_query_async(td_api::make_object<td_api::getUser>(tg_user_id)).get();
|
||||||
|
|
||||||
|
if (response->get_id() == td_api::error::ID) {
|
||||||
|
logger->error("[{}] get user info of id {} failed\n{}", ctx.id(), tg_user_id, to_string(response));
|
||||||
|
auto err = td_api::move_object_as<td_api::error>(response);
|
||||||
|
throw std::runtime_error{err->message_};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto user = td::move_tl_object_as<td_api::user>(response);
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
|
||||||
|
auto &headers = prm.txOption.headers;
|
||||||
|
auto header = pj::SipHeader();
|
||||||
|
|
||||||
|
{
|
||||||
|
// debug purpose header
|
||||||
|
header.hName = "X-GW-Context";
|
||||||
|
header.hValue = ctx.id();
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
header.hName = "X-TG-ID";
|
||||||
|
header.hValue = std::to_string(tg_user_id);
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->first_name_.empty()) {
|
||||||
|
header.hName = "X-TG-FirstName";
|
||||||
|
header.hValue = user->first_name_;
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->last_name_.empty()) {
|
||||||
|
header.hName = "X-TG-LastName";
|
||||||
|
header.hValue = user->last_name_;
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->username_.empty()) {
|
||||||
|
header.hName = "X-TG-Username";
|
||||||
|
header.hValue = user->username_;
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->phone_number_.empty()) {
|
||||||
|
header.hName = "X-TG-Phone";
|
||||||
|
header.hValue = user->phone_number_;
|
||||||
|
headers.push_back(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ctx.sip_call_id = sip_client.Dial(settings.callback_uri(), prm);
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.reason;
|
||||||
|
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG(logger, "[{}] associated with SIP#{}", ctx.id(), ctx.sip_call_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnswerTg::operator()(Context &ctx, tg::Client &tg_client, const Settings &settings,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
|
||||||
|
auto response = tg_client.send_query_async(td_api::make_object<td_api::acceptCall>(
|
||||||
|
ctx.tg_call_id,
|
||||||
|
td_api::make_object<td_api::callProtocol>(settings.udp_p2p(),
|
||||||
|
settings.udp_reflector(),
|
||||||
|
CALL_PROTO_MIN_LAYER, CALL_PROTO_MAX_LAYER)
|
||||||
|
)).get();
|
||||||
|
|
||||||
|
if (response->get_id() == td_api::error::ID) {
|
||||||
|
logger->error("[{}] TG #{} accept failure\n{}", ctx.id(), ctx.tg_call_id, to_string(response));
|
||||||
|
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(response);
|
||||||
|
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = std::to_string(error->code_) + "; " + error->message_;
|
||||||
|
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void AcceptIncomingSip::operator()(Context &ctx, sip::Client &sip_client, const Settings &settings,
|
||||||
|
const sip::events::IncomingCall &event,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
auto ext = event.extension;
|
||||||
|
|
||||||
|
bool ext_valid{true};
|
||||||
|
|
||||||
|
if (ext.length() > 3 && ext.substr(0, 3) == "tg#") {
|
||||||
|
ctx.ext_username = ext.substr(3, std::string::npos);
|
||||||
|
} else if (ext.length() > 1 && ext[0] == '+' && is_digits(ext.substr(1, std::string::npos))) {
|
||||||
|
ctx.ext_phone = ext.substr(1, std::string::npos);
|
||||||
|
} else if (is_digits(ext)) {
|
||||||
|
try {
|
||||||
|
ctx.user_id = std::stoi(ext);
|
||||||
|
} catch (const std::invalid_argument &e) {
|
||||||
|
ext_valid = false;
|
||||||
|
} catch (const std::out_of_range &e) {
|
||||||
|
ext_valid = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ext_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext_valid) {
|
||||||
|
auto a_prm = pj::CallOpParam(true);
|
||||||
|
a_prm.statusCode = PJSIP_SC_RINGING;
|
||||||
|
DEBUG(logger, "[{}] setting SIP #{} in ringing mode", ctx.id(), ctx.sip_call_id);
|
||||||
|
try {
|
||||||
|
sip_client.Answer(ctx.sip_call_id, a_prm);
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.reason;
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.what();
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
pj::CallOpParam prm;
|
||||||
|
prm.statusCode = PJSIP_SC_BAD_EXTENSION;
|
||||||
|
logger->warn("[{}] called invalid extension {}", ctx.id(), ext);
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), prm});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnswerSip::operator()(Context &ctx, sip::Client &sip_client, const Settings &settings,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_OK;
|
||||||
|
DEBUG(logger, "[{}] answering SIP #{}", ctx.id(), ctx.sip_call_id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sip_client.Answer(ctx.sip_call_id, prm);
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.reason;
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.what();
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void state_machine::actions::CreateTgVoip::operator()(Context &ctx, const Settings &settings,
|
||||||
|
const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
|
||||||
|
DEBUG(logger, "[{}] creating voip for TG #{}", ctx.id(), ctx.tg_call_id);
|
||||||
|
|
||||||
|
using namespace tgvoip;
|
||||||
|
|
||||||
|
auto state = td_api::move_object_as<td_api::callStateReady>(event->call_->state_);
|
||||||
|
auto voip_controller = std::make_shared<VoIPController>();
|
||||||
|
|
||||||
|
static auto config = VoIPController::Config(
|
||||||
|
3000, /*init_timeout*/
|
||||||
|
3000, /*recv_timeout*/
|
||||||
|
DATA_SAVING_NEVER, /*data_saving*/
|
||||||
|
settings.aec_enabled(), /*enableAEC*/
|
||||||
|
settings.ns_enabled(), /*enableNS*/
|
||||||
|
settings.agc_enabled(), /*enableAGC*/
|
||||||
|
false /*enableCallUpgrade*/
|
||||||
|
);
|
||||||
|
voip_controller->SetConfig(config);
|
||||||
|
if (settings.voip_proxy_enabled()) {
|
||||||
|
voip_controller->SetProxy(
|
||||||
|
PROXY_SOCKS5,
|
||||||
|
settings.voip_proxy_address(),
|
||||||
|
settings.voip_proxy_port(),
|
||||||
|
settings.voip_proxy_username(),
|
||||||
|
settings.voip_proxy_password()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
char encryption_key[256];
|
||||||
|
memcpy(encryption_key, state->encryption_key_.c_str(), 256);
|
||||||
|
voip_controller->SetEncryptionKey(encryption_key, event->call_->is_outgoing_);
|
||||||
|
|
||||||
|
vector<Endpoint> endpoints;
|
||||||
|
for (auto &connection : state->connections_) {
|
||||||
|
unsigned char peer_tag[16];
|
||||||
|
memcpy(peer_tag, connection->peer_tag_.c_str(), 16);
|
||||||
|
auto ipv4 = IPv4Address(connection->ip_);
|
||||||
|
auto ipv6 = IPv6Address(connection->ipv6_);
|
||||||
|
endpoints.emplace_back(Endpoint(connection->id_,
|
||||||
|
static_cast<uint16_t>(connection->port_),
|
||||||
|
ipv4,
|
||||||
|
ipv6,
|
||||||
|
Endpoint::TYPE_UDP_RELAY,
|
||||||
|
peer_tag));
|
||||||
|
}
|
||||||
|
voip_controller->SetRemoteEndpoints(endpoints, settings.udp_p2p(), CALL_PROTO_MAX_LAYER);
|
||||||
|
voip_controller->Start();
|
||||||
|
voip_controller->Connect();
|
||||||
|
|
||||||
|
ctx.controller = std::move(voip_controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
void state_machine::actions::BridgeAudio::operator()(Context &ctx, sip::Client &sip_client,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
DEBUG(logger, "[{}] bridging tgvoip audio with SIP#{}", ctx.id(), ctx.sip_call_id);
|
||||||
|
|
||||||
|
try {
|
||||||
|
sip_client.BridgeAudio(ctx.sip_call_id,
|
||||||
|
ctx.controller->AudioMediaInput(),
|
||||||
|
ctx.controller->AudioMediaOutput());
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.reason;
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
} catch (const std::runtime_error &error) {
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
hangup_prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
hangup_prm.reason = error.what();
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), hangup_prm});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetHangupPrm::operator()(Context &ctx, const state_machine::events::InternalError &event) const {
|
||||||
|
ctx.hangup_prm = event.prm;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialTg::operator()(Context &ctx, tg::Client &tg_client, const Settings &settings, Cache &cache,
|
||||||
|
std::chrono::steady_clock::time_point &block_until,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) {
|
||||||
|
|
||||||
|
DEBUG(logger, "[{}] dialing tg", ctx.id());
|
||||||
|
|
||||||
|
ctx_ = &ctx;
|
||||||
|
tg_client_ = &tg_client;
|
||||||
|
settings_ = &settings;
|
||||||
|
cache_ = &cache;
|
||||||
|
block_until_ = &block_until;
|
||||||
|
internal_events_ = &internal_events;
|
||||||
|
logger_ = logger;
|
||||||
|
|
||||||
|
if (block_until > std::chrono::steady_clock::now()) {
|
||||||
|
auto seconds_left = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
block_until - std::chrono::steady_clock::now()).count();
|
||||||
|
|
||||||
|
DEBUG(logger, "[{}] dropping call cause of temp TG block for {} seconds", ctx.id(), seconds_left);
|
||||||
|
|
||||||
|
pj::CallOpParam prm;
|
||||||
|
prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
prm.reason = "FLOOD_WAIT " + std::to_string(seconds_left);
|
||||||
|
internal_events.emplace(state_machine::events::InternalError{ctx.id(), prm});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ctx.ext_username.empty()) {
|
||||||
|
dial_by_username();
|
||||||
|
} else if (!ctx.ext_phone.empty()) {
|
||||||
|
dial_by_phone();
|
||||||
|
} else {
|
||||||
|
dial_by_id(ctx.user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialTg::dial_by_id(int32_t id) {
|
||||||
|
auto response = tg_client_->send_query_async(td_api::make_object<td_api::createCall>(
|
||||||
|
id /* id */,
|
||||||
|
td_api::make_object<td_api::callProtocol>(settings_->udp_p2p(), settings_->udp_reflector(),
|
||||||
|
CALL_PROTO_MIN_LAYER, CALL_PROTO_MAX_LAYER))
|
||||||
|
).get();
|
||||||
|
|
||||||
|
if (response->get_id() == td_api::error::ID) {
|
||||||
|
logger_->error("[{}] failed to create telegram call\n{}", ctx_->id(), to_string(response));
|
||||||
|
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(response);
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
prm.reason = std::to_string(error->code_) + "; " + error->message_;
|
||||||
|
|
||||||
|
internal_events_->emplace(state_machine::events::InternalError{ctx_->id(), prm});
|
||||||
|
|
||||||
|
parse_error(std::move(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto call_id_ = td::move_tl_object_as<td_api::callId>(response);
|
||||||
|
|
||||||
|
DEBUG(logger_, "[{}] associated with TG#{}", ctx_->id(), call_id_->id_);
|
||||||
|
ctx_->tg_call_id = call_id_->id_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialTg::dial_by_phone() {
|
||||||
|
|
||||||
|
auto it = cache_->phone_cache.find(ctx_->ext_phone);
|
||||||
|
if (it != cache_->phone_cache.end()) {
|
||||||
|
DEBUG(logger_, "[{}] found id {} for {} in phone cache", ctx_->id(), it->second, ctx_->ext_phone);
|
||||||
|
dial_by_id(it->second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto contact = td_api::make_object<td_api::contact>();
|
||||||
|
contact->phone_number_ = ctx_->ext_phone;
|
||||||
|
|
||||||
|
auto contacts = std::vector<td_api::object_ptr<td_api::contact>>();
|
||||||
|
contacts.emplace_back(std::move(contact));
|
||||||
|
|
||||||
|
auto response = tg_client_->send_query_async(
|
||||||
|
td_api::make_object<td_api::importContacts>(std::move(contacts))).get();
|
||||||
|
|
||||||
|
if (response->get_id() == td_api::error::ID) {
|
||||||
|
logger_->error("[{}] contacts import failure\n{}", ctx_->id(), to_string(response));
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(response);
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
prm.reason = std::to_string(error->code_) + "; " + error->message_;
|
||||||
|
internal_events_->emplace(state_machine::events::InternalError{ctx_->id(), prm});
|
||||||
|
|
||||||
|
parse_error(std::move(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto imported_contacts = td::move_tl_object_as<td_api::importedContacts>(response);
|
||||||
|
auto user_id_ = imported_contacts->user_ids_[0];
|
||||||
|
|
||||||
|
if (user_id_ == 0) {
|
||||||
|
logger_->error("[{}] {} is not telegram user yet", ctx_->id(), ctx_->ext_phone);
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_NOT_FOUND;
|
||||||
|
prm.reason = "not registered in telegram";
|
||||||
|
internal_events_->emplace(state_machine::events::InternalError{ctx_->id(), prm});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG(logger_, "[{}] adding id {} for {} to phone cache", ctx_->id(), user_id_, ctx_->ext_phone);
|
||||||
|
cache_->phone_cache.emplace(ctx_->ext_phone, user_id_);
|
||||||
|
dial_by_id(user_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialTg::dial_by_username() {
|
||||||
|
auto it = cache_->username_cache.find(ctx_->ext_username);
|
||||||
|
if (it != cache_->username_cache.end()) {
|
||||||
|
DEBUG(logger_, "[{}] found id {} for {} in username cache", ctx_->id(), it->second, ctx_->ext_username);
|
||||||
|
dial_by_id(it->second);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto response = tg_client_->send_query_async(
|
||||||
|
td_api::make_object<td_api::searchPublicChat>(ctx_->ext_username)).get();
|
||||||
|
|
||||||
|
if (response->get_id() == td_api::error::ID) {
|
||||||
|
logger_->error("[{}] chat request failure\n{}", ctx_->id(), to_string(response));
|
||||||
|
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(response);
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
prm.reason = std::to_string(error->code_) + "; " + error->message_;
|
||||||
|
internal_events_->emplace(state_machine::events::InternalError{ctx_->id(), prm});
|
||||||
|
|
||||||
|
parse_error(std::move(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto chat = td::move_tl_object_as<td_api::chat>(response);
|
||||||
|
|
||||||
|
if (chat->type_->get_id() != td_api::chatTypePrivate::ID) {
|
||||||
|
|
||||||
|
auto prm = pj::CallOpParam(true);
|
||||||
|
prm.statusCode = PJSIP_SC_INTERNAL_SERVER_ERROR;
|
||||||
|
prm.reason = "not a user";
|
||||||
|
internal_events_->emplace(state_machine::events::InternalError{ctx_->id(), prm});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto id = static_cast<int32_t>(chat->id_);
|
||||||
|
DEBUG(logger_, "[{}] adding id {} for {} to username cache", ctx_->id(), id, ctx_->ext_username);
|
||||||
|
cache_->username_cache.emplace(ctx_->ext_username, id);
|
||||||
|
dial_by_id(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialTg::parse_error(td_api::object_ptr<td::td_api::error> error) {
|
||||||
|
std::smatch match;
|
||||||
|
|
||||||
|
const std::regex delay_regex("Too Many Requests: retry after (\\d+)");
|
||||||
|
if (std::regex_search(error->message_, match, delay_regex)) {
|
||||||
|
auto seconds = std::stoi(match[1]);
|
||||||
|
*block_until_ = std::chrono::steady_clock::now() +
|
||||||
|
std::chrono::seconds(seconds + settings_->extra_wait_time());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::regex flood_regex("PEER_FLOOD");
|
||||||
|
if (std::regex_search(error->message_, match, flood_regex)) {
|
||||||
|
*block_until_ = std::chrono::steady_clock::now() + std::chrono::seconds(settings_->peer_flood_time());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DialDtmf::operator()(Context &ctx, sip::Client &sip_client,
|
||||||
|
const td_api::object_ptr<td::td_api::updateNewMessage> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const {
|
||||||
|
|
||||||
|
auto content = td_api::move_object_as<td_api::messageText>(event->message_->content_);
|
||||||
|
DEBUG(logger, "[{}] sending DTMF {}", ctx.id(), content->text_->text_);
|
||||||
|
try {
|
||||||
|
sip_client.DialDtmf(ctx.sip_call_id, content->text_->text_);
|
||||||
|
} catch (const pj::Error &error) {
|
||||||
|
logger->error(error.reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace state_machine {
|
||||||
|
|
||||||
|
Logger::Logger(std::string context_id, shared_ptr<spdlog::logger> logger) : logger_(std::move(logger)),
|
||||||
|
context_id_(std::move(context_id)) {
|
||||||
|
TRACE(logger_, "[{}] logger created", context_id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::~Logger() {
|
||||||
|
TRACE(logger_, "[{}] ~logger", context_id_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class SM>
|
||||||
|
void Logger::log_process_event(const td::td_api::object_ptr<td::td_api::updateCall> &event) {
|
||||||
|
TRACE(logger_, "[{}] [process_event]\n{}", context_id_, td::td_api::to_string(event));
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class SM>
|
||||||
|
void Logger::log_process_event(const sip::events::Event &event) {
|
||||||
|
TRACE(logger_, "[{}] [process_event] sip", context_id_);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class SM, class TEvent>
|
||||||
|
void Logger::log_process_event(const TEvent &) {
|
||||||
|
TRACE(logger_, "[{}] [process_event] {}", context_id_, sml::aux::get_type_name<TEvent>());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class SM, class TGuard, class TEvent>
|
||||||
|
void Logger::log_guard(const TGuard &, const TEvent &event, bool result) {
|
||||||
|
TRACE(logger_, "[{}] [guard] {} {} {}", context_id_, sml::aux::get_type_name<TGuard>(),
|
||||||
|
"event", (result ? "[OK]" : "[Reject]"));
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class SM, class TAction, class TEvent>
|
||||||
|
void Logger::log_action(const TAction &, const TEvent &event) {
|
||||||
|
TRACE(logger_, "[{}] [action] {} {}", context_id_, sml::aux::get_type_name<TAction>(), "event");
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class SmLogger, class TSrcState, class TDstState>
|
||||||
|
void Logger::log_state_change(const TSrcState &src, const TDstState &dst) {
|
||||||
|
TRACE(logger_, "[{}] [transition] {} -> {}", context_id_, src.c_str(), dst.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
struct from_tg {
|
||||||
|
auto operator()() const {
|
||||||
|
using namespace boost::sml;
|
||||||
|
using namespace td::td_api;
|
||||||
|
using namespace guards;
|
||||||
|
using namespace actions;
|
||||||
|
return make_transition_table(
|
||||||
|
*"sip_wait_media"_s + event<sip::events::CallMediaStateUpdate>[IsMediaReady{}]
|
||||||
|
/ AnswerTg{} = "wait_tg"_s,
|
||||||
|
"wait_tg"_s + event<object_ptr<updateCall>>[IsInState{callStateReady::ID}]
|
||||||
|
/ (CreateTgVoip{}, BridgeAudio{}) = "wait_dtmf"_s,
|
||||||
|
"wait_dtmf"_s + event<object_ptr<updateNewMessage>>[IsTextContent{} && IsDtmfString{}]
|
||||||
|
/ DialDtmf{} = "wait_dtmf"_s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct from_sip {
|
||||||
|
auto operator()() const {
|
||||||
|
using namespace boost::sml;
|
||||||
|
using namespace td::td_api;
|
||||||
|
using namespace guards;
|
||||||
|
using namespace actions;
|
||||||
|
return make_transition_table(
|
||||||
|
*"sip_wait_confirm"_s + event<sip::events::CallStateUpdate>
|
||||||
|
[IsSipInState{PJSIP_INV_STATE_EARLY}] / DialTg{} = "wait_tg"_s,
|
||||||
|
"wait_tg"_s + event<object_ptr<updateCall>>[IsInState{callStateReady::ID}]
|
||||||
|
/ (StoreTgUserId{}, CreateTgVoip{}, AnswerSip{}) = "sip_wait_media"_s,
|
||||||
|
"sip_wait_media"_s + event<sip::events::CallMediaStateUpdate>[IsMediaReady{}]
|
||||||
|
/ BridgeAudio{} = "wait_dtmf"_s,
|
||||||
|
"wait_dtmf"_s + event<object_ptr<updateNewMessage>>[IsTextContent{} && IsDtmfString{}]
|
||||||
|
/ DialDtmf{} = "wait_dtmf"_s
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StateMachine {
|
||||||
|
auto operator()() const {
|
||||||
|
using namespace boost::sml;
|
||||||
|
using namespace td::td_api;
|
||||||
|
using namespace guards;
|
||||||
|
using namespace actions;
|
||||||
|
using namespace events;
|
||||||
|
return make_transition_table(
|
||||||
|
*"init"_s + event<object_ptr<updateCall>>
|
||||||
|
[IsIncoming{} && IsInState{callStatePending::ID} && CallbackUriIsSet{}]
|
||||||
|
/ (StoreTgId{}, StoreTgUserId{}, DialSip{}) = state<from_tg>,
|
||||||
|
"init"_s + event<object_ptr<updateCall>>
|
||||||
|
[IsIncoming{} && IsInState{callStatePending::ID} && !CallbackUriIsSet{}]
|
||||||
|
/ StoreTgId{} = X,
|
||||||
|
"init"_s + event<object_ptr<updateCall>> = X,
|
||||||
|
"init"_s + event<sip::events::IncomingCall> / (StoreSipId{}, AcceptIncomingSip{}) = state<from_sip>,
|
||||||
|
"init"_s + event<sip::events::CallStateUpdate> = X,
|
||||||
|
"init"_s + event<sip::events::CallMediaStateUpdate> = X,
|
||||||
|
|
||||||
|
state<from_tg> + event<object_ptr<updateCall>>
|
||||||
|
[IsInState{callStateDiscarded::ID} || IsInState{callStateError::ID}] /
|
||||||
|
CleanTgId{} = X,
|
||||||
|
state<from_tg> + event<sip::events::CallStateUpdate>
|
||||||
|
[IsSipInState{PJSIP_INV_STATE_DISCONNECTED}] / CleanSipId{} = X,
|
||||||
|
state<from_tg> + event<InternalError> / SetHangupPrm{} = X,
|
||||||
|
|
||||||
|
state<from_sip> + event<object_ptr<updateCall>>
|
||||||
|
[IsInState{callStateDiscarded::ID} || IsInState{callStateError::ID}] /
|
||||||
|
CleanTgId{} = X,
|
||||||
|
state<from_sip> + event<InternalError> / SetHangupPrm{} = X,
|
||||||
|
state<from_sip> + event<sip::events::CallStateUpdate>
|
||||||
|
[IsSipInState{PJSIP_INV_STATE_DISCONNECTED}] / CleanSipId{} = X,
|
||||||
|
|
||||||
|
X + on_entry<_> / CleanUp{}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Context::Context() : id_(next_ctx_id()) {}
|
||||||
|
|
||||||
|
const std::string Context::id() const { return id_; };
|
||||||
|
|
||||||
|
std::string Context::next_ctx_id() {
|
||||||
|
static int64_t ctx_counter{0};
|
||||||
|
static std::string id_prefix = std::to_string(getpid()) + "-";
|
||||||
|
return id_prefix + std::to_string(++ctx_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gateway::Gateway(sip::Client &sip_client_, tg::Client &tg_client_,
|
||||||
|
OptionalQueue<sip::events::Event> &sip_events_,
|
||||||
|
OptionalQueue<tg::Client::Object> &tg_events_,
|
||||||
|
std::shared_ptr<spdlog::logger> logger_,
|
||||||
|
Settings &settings)
|
||||||
|
: sip_client_(sip_client_), tg_client_(tg_client_), logger_(std::move(logger_)),
|
||||||
|
sip_events_(sip_events_), tg_events_(tg_events_), settings_(settings) {}
|
||||||
|
|
||||||
|
void Gateway::start(volatile sig_atomic_t e_flag) {
|
||||||
|
|
||||||
|
load_cache();
|
||||||
|
|
||||||
|
while (!e_flag) {
|
||||||
|
auto tick_start = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
if (auto event = internal_events_.pop(); event) {
|
||||||
|
std::visit([this](auto &&casted_event) {
|
||||||
|
process_event(casted_event);
|
||||||
|
}, event.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto event = tg_events_.pop(); event) {
|
||||||
|
using namespace td::td_api;
|
||||||
|
auto &&object = event.value();
|
||||||
|
switch (object->get_id()) {
|
||||||
|
case updateCall::ID:
|
||||||
|
process_event(move_object_as<updateCall>(object));
|
||||||
|
break;
|
||||||
|
case updateNewMessage::ID:
|
||||||
|
process_event(move_object_as<updateNewMessage>(object));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto event = sip_events_.pop(); event) {
|
||||||
|
std::visit([this](auto &&casted_event) {
|
||||||
|
process_event(casted_event);
|
||||||
|
}, event.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tick_end = std::chrono::steady_clock::now();
|
||||||
|
auto duration = tick_end - tick_start;
|
||||||
|
auto sleep_time = std::chrono::milliseconds(10) - duration;
|
||||||
|
if (sleep_time.count() > 0) {
|
||||||
|
std::this_thread::sleep_for(sleep_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gateway::load_cache() {
|
||||||
|
logger_->info("Loading contacts cache");
|
||||||
|
|
||||||
|
auto search_response = tg_client_.send_query_async(
|
||||||
|
td::td_api::make_object<td::td_api::searchContacts>("", INT32_MAX)).get();
|
||||||
|
if (search_response->get_id() == td::td_api::error::ID) {
|
||||||
|
logger_->error("offline contacts search failed during cache fill\n{}", to_string(search_response));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto users = td::td_api::move_object_as<td::td_api::users>(search_response);
|
||||||
|
std::vector<std::future<tg::Client::Object>> responses;
|
||||||
|
for (auto user_id : users->user_ids_) {
|
||||||
|
responses.emplace_back(tg_client_.send_query_async(td::td_api::make_object<td::td_api::getUser>(user_id)));
|
||||||
|
}
|
||||||
|
for (auto &future : responses) {
|
||||||
|
auto response = future.get();
|
||||||
|
|
||||||
|
if (response->get_id() == td::td_api::error::ID) {
|
||||||
|
logger_->error("failed to get user info from DB\n{}", to_string(response));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto user = td::td_api::move_object_as<td::td_api::user>(response);
|
||||||
|
|
||||||
|
if (!user->have_access_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->username_.empty()) {
|
||||||
|
cache_.username_cache.emplace(user->username_, user->id_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user->phone_number_.empty()) {
|
||||||
|
cache_.phone_cache.emplace(user->phone_number_, user->id_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger_->info("Loaded {} usernames and {} phones into contacts cache",
|
||||||
|
cache_.username_cache.size(),
|
||||||
|
cache_.phone_cache.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Bridge *>::iterator Gateway::search_call(const std::function<bool(const Bridge *)> &predicate) {
|
||||||
|
auto iter = std::find_if(bridges.begin(), bridges.end(), predicate);
|
||||||
|
|
||||||
|
if (iter == bridges.end()) {
|
||||||
|
auto ctx = std::make_unique<Context>();
|
||||||
|
auto sm_logger = std::make_unique<state_machine::Logger>(ctx->id(), logger_);
|
||||||
|
auto sm = std::make_unique<state_machine::sm_t>(*sm_logger, sip_client_, tg_client_, settings_, logger_,
|
||||||
|
*ctx, cache_, internal_events_, block_until);
|
||||||
|
|
||||||
|
auto bridge = new Bridge;
|
||||||
|
bridge->ctx = std::move(ctx);
|
||||||
|
bridge->sm = std::move(sm);
|
||||||
|
bridge->logger = std::move(sm_logger);
|
||||||
|
|
||||||
|
bridges.emplace_back(bridge);
|
||||||
|
iter = bridges.end() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gateway::process_event(td::td_api::object_ptr<td::td_api::updateCall> update_call) {
|
||||||
|
|
||||||
|
auto iter = search_call([id = update_call->call_->id_](const Bridge *bridge) {
|
||||||
|
return bridge->ctx->tg_call_id == id;
|
||||||
|
});
|
||||||
|
|
||||||
|
(*iter)->sm->process_event(update_call);
|
||||||
|
|
||||||
|
if ((*iter)->sm->is(sml::X)) {
|
||||||
|
delete *iter;
|
||||||
|
bridges.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gateway::process_event(td::td_api::object_ptr<td::td_api::updateNewMessage> update_message) {
|
||||||
|
|
||||||
|
std::vector<Bridge *> matches;
|
||||||
|
for (auto bridge : bridges) {
|
||||||
|
if (bridge->ctx->user_id == update_message->message_->sender_user_id_) {
|
||||||
|
matches.emplace_back(bridge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches.size() > 1) {
|
||||||
|
logger_->error("ambiguous message from {}", update_message->message_->sender_user_id_);
|
||||||
|
return;
|
||||||
|
} else if (matches.size() == 1) {
|
||||||
|
TRACE(logger_, "routing message to ctx {}", matches[0]->ctx->id());
|
||||||
|
matches[0]->sm->process_event(update_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Gateway::process_event(state_machine::events::InternalError &event) {
|
||||||
|
auto iter = std::find_if(bridges.begin(), bridges.end(), [id = event.ctx_id](const Bridge *bridge) {
|
||||||
|
return bridge->ctx->id() == id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (iter == bridges.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*iter)->sm->process_event(event);
|
||||||
|
|
||||||
|
if ((*iter)->sm->is(sml::X)) {
|
||||||
|
delete *iter;
|
||||||
|
bridges.erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TSipEvent>
|
||||||
|
void Gateway::process_event(const TSipEvent &event) {
|
||||||
|
|
||||||
|
auto iter = search_call([id = event.id](const Bridge *bridge) {
|
||||||
|
return bridge->ctx->sip_call_id == id;
|
||||||
|
});
|
||||||
|
|
||||||
|
(*iter)->sm->process_event(event);
|
||||||
|
|
||||||
|
if ((*iter)->sm->is(sml::X)) {
|
||||||
|
delete *iter;
|
||||||
|
bridges.erase(iter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_GATEWAY_H
|
||||||
|
#define TG2SIP_GATEWAY_H
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <regex>
|
||||||
|
#include <libtgvoip/VoIPController.h>
|
||||||
|
#include <boost/sml.hpp>
|
||||||
|
#include <csignal>
|
||||||
|
#include "sip.h"
|
||||||
|
#include "tg.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "queue.h"
|
||||||
|
|
||||||
|
namespace sml = boost::sml;
|
||||||
|
|
||||||
|
class Context;
|
||||||
|
|
||||||
|
struct Cache;
|
||||||
|
|
||||||
|
namespace state_machine::events {
|
||||||
|
|
||||||
|
struct InternalError {
|
||||||
|
std::string ctx_id;
|
||||||
|
pj::CallOpParam prm;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::variant<InternalError> Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace state_machine::guards {
|
||||||
|
struct IsIncoming {
|
||||||
|
bool operator()(const td::td_api::object_ptr<td::td_api::updateCall> &event) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IsInState {
|
||||||
|
public:
|
||||||
|
explicit IsInState(int32_t id) : id_(id) {}
|
||||||
|
|
||||||
|
bool operator()(const td::td_api::object_ptr<td::td_api::updateCall> &event) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const int32_t id_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class IsSipInState {
|
||||||
|
public:
|
||||||
|
explicit IsSipInState(pjsip_inv_state state) : state_(state) {}
|
||||||
|
|
||||||
|
bool operator()(const sip::events::CallStateUpdate &event) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const pjsip_inv_state state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CallbackUriIsSet {
|
||||||
|
bool operator()(const Settings &settings) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IsMediaReady {
|
||||||
|
bool operator()(const sip::events::CallMediaStateUpdate &event) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IsTextContent {
|
||||||
|
bool operator()(const td::td_api::object_ptr<td::td_api::updateNewMessage> &event) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IsDtmfString {
|
||||||
|
bool operator()(const td::td_api::object_ptr<td::td_api::updateNewMessage> &event) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace state_machine::actions {
|
||||||
|
|
||||||
|
struct StoreTgId {
|
||||||
|
void operator()(Context &ctx, const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoreTgUserId {
|
||||||
|
void operator()(Context &ctx, const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CleanTgId {
|
||||||
|
void operator()(Context &ctx) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CleanSipId {
|
||||||
|
void operator()(Context &ctx) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StoreSipId {
|
||||||
|
void operator()(Context &ctx, const sip::events::IncomingCall &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DialSip {
|
||||||
|
void operator()(Context &ctx, tg::Client &tg_client, sip::Client &sip_client,
|
||||||
|
const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
const Settings &settings, std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnswerTg {
|
||||||
|
void operator()(Context &ctx, tg::Client &tg_client, const Settings &settings,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AcceptIncomingSip {
|
||||||
|
void operator()(Context &ctx, sip::Client &sip_client, const Settings &settings,
|
||||||
|
const sip::events::IncomingCall &event,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AnswerSip {
|
||||||
|
void operator()(Context &ctx, sip::Client &sip_client, const Settings &settings,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BridgeAudio {
|
||||||
|
void operator()(Context &ctx, sip::Client &sip_client,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CreateTgVoip {
|
||||||
|
void operator()(Context &ctx, const Settings &settings,
|
||||||
|
const td::td_api::object_ptr<td::td_api::updateCall> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CleanUp {
|
||||||
|
void operator()(Context &ctx, sip::Client &sip_client, tg::Client &tg_client,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetHangupPrm {
|
||||||
|
void operator()(Context &ctx, const state_machine::events::InternalError &event) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DialTg {
|
||||||
|
private:
|
||||||
|
Context *ctx_;
|
||||||
|
tg::Client *tg_client_;
|
||||||
|
Settings const *settings_;
|
||||||
|
Cache *cache_;
|
||||||
|
std::chrono::steady_clock::time_point *block_until_;
|
||||||
|
OptionalQueue<state_machine::events::Event> *internal_events_;
|
||||||
|
std::shared_ptr<spdlog::logger> logger_;
|
||||||
|
|
||||||
|
void parse_error(td::td_api::object_ptr<td::td_api::error> error);
|
||||||
|
|
||||||
|
void dial_by_id(int32_t id);
|
||||||
|
|
||||||
|
void dial_by_phone();
|
||||||
|
|
||||||
|
void dial_by_username();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void operator()(Context &ctx, tg::Client &tg_client, const Settings &settings, Cache &cache,
|
||||||
|
std::chrono::steady_clock::time_point &block_until,
|
||||||
|
OptionalQueue<state_machine::events::Event> &internal_events,
|
||||||
|
std::shared_ptr<spdlog::logger> logger);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DialDtmf {
|
||||||
|
void operator()(Context &ctx, sip::Client &sip_client,
|
||||||
|
const td::td_api::object_ptr<td::td_api::updateNewMessage> &event,
|
||||||
|
std::shared_ptr<spdlog::logger> logger) const;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace state_machine {
|
||||||
|
class Logger {
|
||||||
|
public:
|
||||||
|
Logger(std::string context_id, shared_ptr<spdlog::logger> logger);
|
||||||
|
|
||||||
|
virtual ~Logger();
|
||||||
|
|
||||||
|
template<class SM>
|
||||||
|
void log_process_event(const td::td_api::object_ptr<td::td_api::updateCall> &event);
|
||||||
|
|
||||||
|
template<class SM>
|
||||||
|
void log_process_event(const sip::events::Event &event);
|
||||||
|
|
||||||
|
template<class SM, class TEvent>
|
||||||
|
void log_process_event(const TEvent &);
|
||||||
|
|
||||||
|
template<class SM, class TGuard, class TEvent>
|
||||||
|
void log_guard(const TGuard &, const TEvent &event, bool result);
|
||||||
|
|
||||||
|
template<class SM, class TAction, class TEvent>
|
||||||
|
void log_action(const TAction &, const TEvent &event);
|
||||||
|
|
||||||
|
template<class SmLogger, class TSrcState, class TDstState>
|
||||||
|
void log_state_change(const TSrcState &src, const TDstState &dst);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string context_id_;
|
||||||
|
std::shared_ptr<spdlog::logger> logger_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StateMachine;
|
||||||
|
|
||||||
|
typedef sml::sm<StateMachine, sml::logger<Logger>, sml::thread_safe<std::recursive_mutex>> sm_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Cache {
|
||||||
|
std::map<std::string, int32_t> username_cache;
|
||||||
|
std::map<std::string, int32_t> phone_cache;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Context {
|
||||||
|
public:
|
||||||
|
Context();
|
||||||
|
|
||||||
|
const std::string id() const;
|
||||||
|
|
||||||
|
pjsua_call_id sip_call_id{PJSUA_INVALID_ID};
|
||||||
|
int32_t tg_call_id{0};
|
||||||
|
std::shared_ptr<tgvoip::VoIPController> controller{nullptr};
|
||||||
|
|
||||||
|
std::string ext_phone;
|
||||||
|
std::string ext_username;
|
||||||
|
int32_t user_id{0};
|
||||||
|
|
||||||
|
pj::CallOpParam hangup_prm;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::string id_;
|
||||||
|
|
||||||
|
std::string next_ctx_id();
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Bridge {
|
||||||
|
std::unique_ptr<state_machine::sm_t> sm;
|
||||||
|
std::unique_ptr<Context> ctx;
|
||||||
|
std::unique_ptr<state_machine::Logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Gateway {
|
||||||
|
public:
|
||||||
|
Gateway(sip::Client &sip_client_, tg::Client &tg_client_,
|
||||||
|
OptionalQueue<sip::events::Event> &sip_events_,
|
||||||
|
OptionalQueue<tg::Client::Object> &tg_events_,
|
||||||
|
std::shared_ptr<spdlog::logger> logger_,
|
||||||
|
Settings &settings);
|
||||||
|
|
||||||
|
Gateway(const Gateway &) = delete;
|
||||||
|
|
||||||
|
Gateway &operator=(const Gateway &) = delete;
|
||||||
|
|
||||||
|
void start(volatile sig_atomic_t e_flag);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<spdlog::logger> logger_;
|
||||||
|
|
||||||
|
sip::Client &sip_client_;
|
||||||
|
tg::Client &tg_client_;
|
||||||
|
const Settings &settings_;
|
||||||
|
|
||||||
|
OptionalQueue<sip::events::Event> &sip_events_;
|
||||||
|
OptionalQueue<tg::Client::Object> &tg_events_;
|
||||||
|
OptionalQueue<state_machine::events::Event> internal_events_;
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point block_until{std::chrono::steady_clock::now()};
|
||||||
|
Cache cache_;
|
||||||
|
// Would be better to use smart pointer here,
|
||||||
|
// but it is not allowed by sm_t forward declaration
|
||||||
|
std::vector<Bridge *> bridges;
|
||||||
|
|
||||||
|
std::vector<Bridge *>::iterator search_call(const std::function<bool(const Bridge *)> &predicate);
|
||||||
|
|
||||||
|
void process_event(td::td_api::object_ptr<td::td_api::updateCall> update_call);
|
||||||
|
|
||||||
|
void process_event(td::td_api::object_ptr<td::td_api::updateNewMessage> update_message);
|
||||||
|
|
||||||
|
void process_event(state_machine::events::InternalError &event);
|
||||||
|
|
||||||
|
template<typename TSipEvent>
|
||||||
|
void process_event(const TSipEvent &event);
|
||||||
|
|
||||||
|
void load_cache();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //TG2SIP_GATEWAY_H
|
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <INIReader.h>
|
||||||
|
#include <td/telegram/Client.h>
|
||||||
|
#include <td/telegram/Log.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
// overloaded
|
||||||
|
namespace detail {
|
||||||
|
template<class... Fs>
|
||||||
|
struct overload;
|
||||||
|
|
||||||
|
template<class F>
|
||||||
|
struct overload<F> : public F {
|
||||||
|
explicit overload(F f) : F(f) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class F, class... Fs>
|
||||||
|
struct overload<F, Fs...>
|
||||||
|
: public overload<F>, overload<Fs...> {
|
||||||
|
overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) {
|
||||||
|
}
|
||||||
|
|
||||||
|
using overload<F>::operator();
|
||||||
|
using overload<Fs...>::operator();
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template<class... F>
|
||||||
|
auto overloaded(F... f) {
|
||||||
|
return detail::overload<F...>(f...);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
|
||||||
|
class TDClient {
|
||||||
|
public:
|
||||||
|
explicit TDClient(Settings &settings_) : settings(settings_) {
|
||||||
|
td::Log::set_verbosity_level(1);
|
||||||
|
client = std::make_unique<td::Client>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void auth() {
|
||||||
|
while (!sequence_done) {
|
||||||
|
process_response(client->receive(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Object = td_api::object_ptr<td_api::Object>;
|
||||||
|
|
||||||
|
Settings &settings;
|
||||||
|
bool sequence_done{false};
|
||||||
|
|
||||||
|
std::unique_ptr<td::Client> client;
|
||||||
|
td_api::object_ptr<td_api::AuthorizationState> authorization_state;
|
||||||
|
|
||||||
|
std::uint64_t current_query_id{0};
|
||||||
|
std::uint64_t authentication_query_id{0};
|
||||||
|
std::map<std::uint64_t, std::function<void(Object)>> handlers;
|
||||||
|
|
||||||
|
std::uint64_t next_query_id() {
|
||||||
|
return ++current_query_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler) {
|
||||||
|
auto query_id = next_query_id();
|
||||||
|
if (handler) {
|
||||||
|
handlers.emplace(query_id, std::move(handler));
|
||||||
|
}
|
||||||
|
client->send({query_id, std::move(f)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_response(td::Client::Response response) {
|
||||||
|
if (!response.object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.id == 0) {
|
||||||
|
return process_update(std::move(response.object));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = handlers.find(response.id);
|
||||||
|
if (it != handlers.end()) {
|
||||||
|
it->second(std::move(response.object));
|
||||||
|
handlers.erase(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_update(td_api::object_ptr<td_api::Object> update) {
|
||||||
|
td_api::downcast_call(
|
||||||
|
*update, overloaded(
|
||||||
|
[this](td_api::updateAuthorizationState &update_authorization_state) {
|
||||||
|
authorization_state = std::move(update_authorization_state.authorization_state_);
|
||||||
|
on_authorization_state_update();
|
||||||
|
},
|
||||||
|
[](auto &update) {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto create_authentication_query_handler() {
|
||||||
|
return [this, id = authentication_query_id](Object object) {
|
||||||
|
if (id == authentication_query_id) {
|
||||||
|
check_authentication_error(std::move(object));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void check_authentication_error(Object object) {
|
||||||
|
if (object->get_id() == td_api::error::ID) {
|
||||||
|
auto error = td::move_tl_object_as<td_api::error>(object);
|
||||||
|
std::cerr << "Error: " << to_string(error);
|
||||||
|
on_authorization_state_update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_authorization_state_update() {
|
||||||
|
authentication_query_id++;
|
||||||
|
td_api::downcast_call(
|
||||||
|
*authorization_state,
|
||||||
|
overloaded(
|
||||||
|
[this](td_api::authorizationStateReady &) {
|
||||||
|
sequence_done = true;
|
||||||
|
std::cerr << "Authorization OK" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateLoggingOut &) {
|
||||||
|
sequence_done = true;
|
||||||
|
std::cerr << "Logging out" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateClosing &) {
|
||||||
|
std::cerr << "Closing" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateClosed &) {
|
||||||
|
sequence_done = true;
|
||||||
|
std::cerr << "Terminated" << std::endl;
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitCode &wait_code) {
|
||||||
|
std::string first_name;
|
||||||
|
std::string last_name;
|
||||||
|
if (!wait_code.is_registered_) {
|
||||||
|
std::cerr << "Enter your first name: ";
|
||||||
|
std::cin >> first_name;
|
||||||
|
std::cerr << "Enter your last name: ";
|
||||||
|
std::cin >> last_name;
|
||||||
|
}
|
||||||
|
std::cerr << "Enter authentication code: ";
|
||||||
|
std::string code;
|
||||||
|
std::cin >> code;
|
||||||
|
send_query(
|
||||||
|
td_api::make_object<td_api::checkAuthenticationCode>(code, first_name, last_name),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitPassword &) {
|
||||||
|
std::cerr << "Enter authentication password: ";
|
||||||
|
std::string password;
|
||||||
|
std::cin >> password;
|
||||||
|
send_query(td_api::make_object<td_api::checkAuthenticationPassword>(password),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitPhoneNumber &) {
|
||||||
|
std::cerr << "Enter phone number: ";
|
||||||
|
std::string phone_number;
|
||||||
|
std::cin >> phone_number;
|
||||||
|
send_query(td_api::make_object<td_api::setAuthenticationPhoneNumber>(
|
||||||
|
phone_number, false /*allow_flash_calls*/, false /*is_current_phone_number*/),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitEncryptionKey &) {
|
||||||
|
send_query(td_api::make_object<td_api::checkDatabaseEncryptionKey>(""),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
|
||||||
|
td_api::object_ptr<td_api::Proxy> proxy;
|
||||||
|
if (settings.proxy_enabled()) {
|
||||||
|
auto socks_proxy = td_api::make_object<td_api::proxySocks5>(
|
||||||
|
settings.proxy_address(),
|
||||||
|
settings.proxy_port(),
|
||||||
|
settings.proxy_username(),
|
||||||
|
settings.proxy_password()
|
||||||
|
);
|
||||||
|
proxy = td_api::move_object_as<td_api::Proxy>(socks_proxy);
|
||||||
|
} else {
|
||||||
|
auto empty_proxy = td_api::make_object<td_api::proxyEmpty>();
|
||||||
|
proxy = td_api::move_object_as<td_api::Proxy>(empty_proxy);
|
||||||
|
}
|
||||||
|
send_query(td_api::make_object<td_api::setProxy>(
|
||||||
|
td_api::move_object_as<td_api::Proxy>(proxy)),
|
||||||
|
[](Object) {});
|
||||||
|
},
|
||||||
|
[this](td_api::authorizationStateWaitTdlibParameters &) {
|
||||||
|
auto lib_parameters = td_api::make_object<td_api::tdlibParameters>();
|
||||||
|
lib_parameters = td_api::make_object<td_api::tdlibParameters>();
|
||||||
|
|
||||||
|
lib_parameters->api_id_ = settings.api_id();
|
||||||
|
lib_parameters->api_hash_ = settings.api_hash();
|
||||||
|
lib_parameters->database_directory_ = settings.db_folder();
|
||||||
|
|
||||||
|
lib_parameters->system_language_code_ = settings.sys_lang_code();
|
||||||
|
lib_parameters->device_model_ = settings.device_model();
|
||||||
|
lib_parameters->system_version_ = settings.system_version();
|
||||||
|
lib_parameters->application_version_ = settings.app_version();
|
||||||
|
|
||||||
|
lib_parameters->use_file_database_ = false;
|
||||||
|
lib_parameters->use_chat_info_database_ = true;
|
||||||
|
lib_parameters->use_message_database_ = false;
|
||||||
|
lib_parameters->use_secret_chats_ = false;
|
||||||
|
lib_parameters->enable_storage_optimizer_ = true;
|
||||||
|
|
||||||
|
send_query(td_api::make_object<td_api::setTdlibParameters>(std::move(lib_parameters)),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
|
||||||
|
auto reader = INIReader("settings.ini");
|
||||||
|
Settings settings(reader);
|
||||||
|
|
||||||
|
if (!settings.is_loaded()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TDClient client(settings);
|
||||||
|
client.auth();
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <td/telegram/Log.h>
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
void init_logging(Settings &settings) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::vector<spdlog::sink_ptr> sinks;
|
||||||
|
|
||||||
|
auto console_sink = std::make_shared<spdlog::sinks::ansicolor_stdout_sink_mt>();
|
||||||
|
console_sink->set_level(static_cast<spdlog::level::level_enum>(settings.console_min_level()));
|
||||||
|
sinks.push_back(std::move(console_sink));
|
||||||
|
|
||||||
|
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>("tg2sip.log", 100 * 1024 * 1024, 1);
|
||||||
|
file_sink->set_level(static_cast<spdlog::level::level_enum>(settings.file_min_level()));
|
||||||
|
sinks.push_back(std::move(file_sink));
|
||||||
|
|
||||||
|
std::vector<string> loggers;
|
||||||
|
loggers.emplace_back("core");
|
||||||
|
loggers.emplace_back("pjsip");
|
||||||
|
loggers.emplace_back("tgvoip");
|
||||||
|
|
||||||
|
for (auto log_name : loggers) {
|
||||||
|
auto logger = std::make_shared<spdlog::logger>(log_name, begin(sinks), end(sinks));
|
||||||
|
spdlog::register_logger(logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_DEBUG_
|
||||||
|
spdlog::set_pattern("%^[%T.%e][t:%t][p:%P][%n][%l] %v%$");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {
|
||||||
|
l->set_level(spdlog::level::info);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (const spdlog::spdlog_ex &ex) {
|
||||||
|
std::cerr << ex.what() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
td::Log::set_file_path("tdlib.log");
|
||||||
|
td::Log::set_verbosity_level(settings.tdlib_log_level());
|
||||||
|
|
||||||
|
spdlog::get("core")->set_level(static_cast<spdlog::level::level_enum>(settings.log_level()));
|
||||||
|
spdlog::get("pjsip")->set_level(static_cast<spdlog::level::level_enum>(settings.pjsip_log_level()));
|
||||||
|
spdlog::get("tgvoip")->set_level(static_cast<spdlog::level::level_enum>(settings.tgvoip_log_level()));
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_LOGGING_H
|
||||||
|
#define TG2SIP_LOGGING_H
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/fmt/ostr.h>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
#ifndef DISABLE_DEBUG_
|
||||||
|
#define STRINGIZE_(x) #x
|
||||||
|
#define STRINGIZE(x) STRINGIZE_(x)
|
||||||
|
#define TRACE(logger, ...) logger->trace("" __FILE__ ":" STRINGIZE(__LINE__)" " __VA_ARGS__)
|
||||||
|
#define DEBUG(logger, ...) logger->debug(__VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define TRACE(logger, ...) (void)0
|
||||||
|
#define DEBUG(logger, ...) (void)0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void init_logging(Settings &settings);
|
||||||
|
|
||||||
|
#endif //TG2SIP_LOGGING_H
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <iostream>
|
||||||
|
#include <csignal>
|
||||||
|
#include <variant>
|
||||||
|
#include "logging.h"
|
||||||
|
#include "queue.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "tg.h"
|
||||||
|
#include "sip.h"
|
||||||
|
#include "gateway.h"
|
||||||
|
|
||||||
|
volatile sig_atomic_t e_flag = 0;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
pthread_setname_np(pthread_self(), "main");
|
||||||
|
|
||||||
|
auto reader = INIReader("settings.ini");
|
||||||
|
Settings settings(reader);
|
||||||
|
|
||||||
|
if (!settings.is_loaded()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_logging(settings);
|
||||||
|
|
||||||
|
std::set_terminate([]() {
|
||||||
|
auto exc = std::current_exception();
|
||||||
|
try {
|
||||||
|
rethrow_exception(exc);
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
spdlog::get("core")->critical("Unhandled exception: {}", e.what());
|
||||||
|
}
|
||||||
|
spdlog::drop_all();
|
||||||
|
std::abort();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto logger = spdlog::get("core");
|
||||||
|
|
||||||
|
OptionalQueue<sip::events::Event> sip_events;
|
||||||
|
/* will be removed on pj endpoint destroy */
|
||||||
|
auto sip_log_writer = new sip::LogWriter(spdlog::get("pjsip"));
|
||||||
|
auto sip_account = std::make_unique<sip::Account>(logger);
|
||||||
|
auto sip_account_cfg = std::make_unique<sip::AccountConfig>(settings);
|
||||||
|
auto sip_client = std::make_unique<sip::Client>(
|
||||||
|
std::move(sip_account),
|
||||||
|
std::move(sip_account_cfg),
|
||||||
|
sip_events,
|
||||||
|
settings,
|
||||||
|
logger,
|
||||||
|
sip_log_writer
|
||||||
|
);
|
||||||
|
sip_client->start();
|
||||||
|
|
||||||
|
OptionalQueue<tg::Client::Object> tg_events;
|
||||||
|
auto tg_client = std::make_unique<tg::Client>(settings, logger, tg_events);
|
||||||
|
tg_client->start();
|
||||||
|
auto tg_is_ready_future = tg_client->is_ready();
|
||||||
|
auto tg_status = tg_is_ready_future.wait_for(std::chrono::seconds(5));
|
||||||
|
if (tg_status != std::future_status::ready || !tg_is_ready_future.get()) {
|
||||||
|
logger->critical("failed to start TG client");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto gateway = std::make_unique<Gateway>(*sip_client, *tg_client, sip_events, tg_events, logger, settings);
|
||||||
|
|
||||||
|
signal(SIGINT, [](int) { e_flag = 1; });
|
||||||
|
signal(SIGTERM, [](int) { e_flag = 1; });
|
||||||
|
|
||||||
|
gateway->start(e_flag);
|
||||||
|
|
||||||
|
logger->info("performing a graceful shutdown...");
|
||||||
|
spdlog::drop_all();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_QUEUE_H
|
||||||
|
#define TG2SIP_QUEUE_H
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class OptionalQueue {
|
||||||
|
public:
|
||||||
|
OptionalQueue() = default;
|
||||||
|
|
||||||
|
OptionalQueue(const OptionalQueue &) = delete;
|
||||||
|
|
||||||
|
OptionalQueue &operator=(const OptionalQueue &) = delete;
|
||||||
|
|
||||||
|
virtual ~OptionalQueue() = default;
|
||||||
|
|
||||||
|
void emplace(std::optional<T> &&value) {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(this->mutex);
|
||||||
|
q.emplace(std::move(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<T> pop() {
|
||||||
|
std::unique_lock<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
if (q.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<T> value = std::move(this->q.front());
|
||||||
|
this->q.pop();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::queue<std::optional<T>> q;
|
||||||
|
std::mutex mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //TG2SIP_QUEUE_H
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <thread>
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
Settings::Settings(INIReader &reader) {
|
||||||
|
|
||||||
|
if (reader.ParseError() < 0) {
|
||||||
|
std::cerr << "Can't load settings file!\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// logging
|
||||||
|
console_min_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "console_min_level", 0)), 0, 6);
|
||||||
|
file_min_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "file_min_level", 0)), 0, 6);
|
||||||
|
log_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "core", 2)), 0, 6);
|
||||||
|
tdlib_log_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "tdlib", 3)), 0, 6);
|
||||||
|
tgvoip_log_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "tgvoip", 5)), 0, 6);
|
||||||
|
pjsip_log_level_ = std::clamp(static_cast<int>(reader.GetInteger("logging", "pjsip", 2)), 0, 6);
|
||||||
|
pjsip_log_sip_messages_ = reader.GetBoolean("logging", "sip_messages", true);
|
||||||
|
|
||||||
|
// sip
|
||||||
|
sip_port_ = static_cast<unsigned int>(reader.GetInteger("sip", "port", 0));
|
||||||
|
id_uri_ = reader.Get("sip", "id_uri", "sip:localhost");
|
||||||
|
callback_uri_ = reader.Get("sip", "callback_uri", "");
|
||||||
|
public_address_ = reader.Get("sip", "public_address", "");
|
||||||
|
stun_server_ = reader.Get("sip", "stun_server", "");
|
||||||
|
raw_pcm_ = reader.GetBoolean("sip", "raw_pcm", true);
|
||||||
|
sip_thread_count_ = static_cast<unsigned int>(reader.GetInteger("sip", "thread_count", 1));
|
||||||
|
sip_port_range_ = static_cast<unsigned int>(reader.GetInteger("sip", "port_range", 0));
|
||||||
|
|
||||||
|
//telegram
|
||||||
|
api_id_ = static_cast<int>(reader.GetInteger("telegram", "api_id", 0));
|
||||||
|
api_hash_ = reader.Get("telegram", "api_hash", "");
|
||||||
|
db_folder_ = reader.Get("telegram", "database_folder", "");
|
||||||
|
system_language_code_ = reader.Get("telegram", "database_folder", "en-US");
|
||||||
|
device_model_ = reader.Get("telegram", "device_model", "PC");
|
||||||
|
system_version_ = reader.Get("telegram", "system_version", "Linux");
|
||||||
|
application_version_ = reader.Get("telegram", "application_version", "1.0");
|
||||||
|
|
||||||
|
udp_p2p_ = reader.GetBoolean("telegram", "udp_p2p", false);
|
||||||
|
udp_reflector_ = reader.GetBoolean("telegram", "udp_reflector", true);
|
||||||
|
|
||||||
|
aec_enabled_ = reader.GetBoolean("telegram", "enable_aec", false);
|
||||||
|
ns_enabled_ = reader.GetBoolean("telegram", "enable_ns", false);
|
||||||
|
agc_enabled_ = reader.GetBoolean("telegram", "enable_agc", false);
|
||||||
|
|
||||||
|
proxy_enabled_ = reader.GetBoolean("telegram", "use_proxy", false);
|
||||||
|
proxy_address_ = reader.Get("telegram", "proxy_address", "");
|
||||||
|
proxy_port_ = static_cast<int32_t>(reader.GetInteger("telegram", "proxy_port", 0));
|
||||||
|
proxy_username_ = reader.Get("telegram", "proxy_username", "");
|
||||||
|
proxy_password_ = reader.Get("telegram", "proxy_password", "");
|
||||||
|
|
||||||
|
voip_proxy_enabled_ = reader.GetBoolean("telegram", "use_voip_proxy", false);
|
||||||
|
voip_proxy_address_ = reader.Get("telegram", "voip_proxy_address", "");
|
||||||
|
voip_proxy_port_ = static_cast<uint16_t>(reader.GetInteger("telegram", "voip_proxy_port", 0));
|
||||||
|
voip_proxy_username_ = reader.Get("telegram", "voip_proxy_username", "");
|
||||||
|
voip_proxy_password_ = reader.Get("telegram", "voip_proxy_password", "");
|
||||||
|
|
||||||
|
extra_wait_time_ = static_cast<unsigned int>(reader.GetInteger("other", "extra_wait_time", 30));
|
||||||
|
peer_flood_time_ = static_cast<unsigned int>(reader.GetInteger("other", "peer_flood_time", 86400));
|
||||||
|
|
||||||
|
if (api_id_ == 0 || api_hash_.empty()) {
|
||||||
|
std::cerr << "TDLib api settings must be set!\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_loaded_ = true;
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_SETTINGS_H
|
||||||
|
#define TG2SIP_SETTINGS_H
|
||||||
|
|
||||||
|
#include <INIReader.h>
|
||||||
|
|
||||||
|
class Settings {
|
||||||
|
private:
|
||||||
|
bool is_loaded_{false};
|
||||||
|
|
||||||
|
int console_min_level_;
|
||||||
|
int file_min_level_;
|
||||||
|
int log_level_;
|
||||||
|
int pjsip_log_level_;
|
||||||
|
bool pjsip_log_sip_messages_;
|
||||||
|
int tgvoip_log_level_;
|
||||||
|
int tdlib_log_level_;
|
||||||
|
|
||||||
|
unsigned int sip_port_;
|
||||||
|
std::string id_uri_;
|
||||||
|
std::string callback_uri_;
|
||||||
|
std::string public_address_;
|
||||||
|
std::string stun_server_;
|
||||||
|
bool raw_pcm_;
|
||||||
|
unsigned int sip_thread_count_;
|
||||||
|
unsigned int sip_port_range_;
|
||||||
|
|
||||||
|
int api_id_;
|
||||||
|
std::string api_hash_;
|
||||||
|
std::string db_folder_;
|
||||||
|
std::string system_language_code_;
|
||||||
|
std::string device_model_;
|
||||||
|
std::string system_version_;
|
||||||
|
std::string application_version_;
|
||||||
|
|
||||||
|
bool udp_p2p_;
|
||||||
|
bool udp_reflector_;
|
||||||
|
|
||||||
|
bool aec_enabled_;
|
||||||
|
bool ns_enabled_;
|
||||||
|
bool agc_enabled_;
|
||||||
|
|
||||||
|
bool proxy_enabled_;
|
||||||
|
std::string proxy_address_;
|
||||||
|
std::int32_t proxy_port_;
|
||||||
|
std::string proxy_username_;
|
||||||
|
std::string proxy_password_;
|
||||||
|
|
||||||
|
bool voip_proxy_enabled_;
|
||||||
|
std::string voip_proxy_address_;
|
||||||
|
uint16_t voip_proxy_port_;
|
||||||
|
std::string voip_proxy_username_;
|
||||||
|
std::string voip_proxy_password_;
|
||||||
|
|
||||||
|
unsigned int extra_wait_time_;
|
||||||
|
unsigned int peer_flood_time_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Settings(INIReader &reader);
|
||||||
|
|
||||||
|
Settings(const Settings &) = delete;
|
||||||
|
|
||||||
|
Settings &operator=(const Settings &) = delete;
|
||||||
|
|
||||||
|
bool is_loaded() const { return is_loaded_; };
|
||||||
|
|
||||||
|
int console_min_level() const { return console_min_level_; };
|
||||||
|
|
||||||
|
int file_min_level() const { return file_min_level_; };
|
||||||
|
|
||||||
|
int log_level() const { return log_level_; };
|
||||||
|
|
||||||
|
int pjsip_log_level() const { return pjsip_log_level_; };
|
||||||
|
|
||||||
|
bool pjsip_log_sip_messages() const { return pjsip_log_sip_messages_; }
|
||||||
|
|
||||||
|
int tgvoip_log_level() const { return tgvoip_log_level_; };
|
||||||
|
|
||||||
|
int tdlib_log_level() const { return tdlib_log_level_; };
|
||||||
|
|
||||||
|
unsigned int sip_port() const { return sip_port_; };
|
||||||
|
|
||||||
|
string id_uri() const { return id_uri_; };
|
||||||
|
|
||||||
|
string callback_uri() const { return callback_uri_; };
|
||||||
|
|
||||||
|
string public_address() const { return public_address_; };
|
||||||
|
|
||||||
|
string stun_server() const { return stun_server_; };
|
||||||
|
|
||||||
|
bool raw_pcm() const { return raw_pcm_; };
|
||||||
|
|
||||||
|
unsigned int sip_thread_count() const { return sip_thread_count_; };
|
||||||
|
|
||||||
|
unsigned int sip_port_range() const { return sip_port_range_; };
|
||||||
|
|
||||||
|
int api_id() const { return api_id_; };
|
||||||
|
|
||||||
|
std::string api_hash() const { return api_hash_; };
|
||||||
|
|
||||||
|
std::string db_folder() const { return db_folder_; };
|
||||||
|
|
||||||
|
std::string sys_lang_code() const { return system_language_code_; };
|
||||||
|
|
||||||
|
std::string device_model() const { return device_model_; };
|
||||||
|
|
||||||
|
std::string system_version() const { return system_version_; };
|
||||||
|
|
||||||
|
std::string app_version() const { return application_version_; };
|
||||||
|
|
||||||
|
bool udp_p2p() const { return udp_p2p_; };
|
||||||
|
|
||||||
|
bool udp_reflector() const { return udp_reflector_; };
|
||||||
|
|
||||||
|
bool aec_enabled() const { return aec_enabled_; };
|
||||||
|
|
||||||
|
bool ns_enabled() const { return ns_enabled_; };
|
||||||
|
|
||||||
|
bool agc_enabled() const { return agc_enabled_; };
|
||||||
|
|
||||||
|
bool proxy_enabled() const { return proxy_enabled_; };
|
||||||
|
|
||||||
|
std::string proxy_address() const { return proxy_address_; };
|
||||||
|
|
||||||
|
int32_t proxy_port() const { return proxy_port_; };
|
||||||
|
|
||||||
|
std::string proxy_username() const { return proxy_username_; };
|
||||||
|
|
||||||
|
std::string proxy_password() const { return proxy_password_; };
|
||||||
|
|
||||||
|
bool voip_proxy_enabled() const { return voip_proxy_enabled_; };
|
||||||
|
|
||||||
|
std::string voip_proxy_address() const { return voip_proxy_address_; };
|
||||||
|
|
||||||
|
uint16_t voip_proxy_port() const { return voip_proxy_port_; };
|
||||||
|
|
||||||
|
std::string voip_proxy_username() const { return voip_proxy_username_; };
|
||||||
|
|
||||||
|
std::string voip_proxy_password() const { return voip_proxy_password_; };
|
||||||
|
|
||||||
|
unsigned int extra_wait_time() const { return extra_wait_time_; };
|
||||||
|
|
||||||
|
unsigned int peer_flood_time() const { return peer_flood_time_; };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //TG2SIP_SETTINGS_H
|
|
@ -0,0 +1,312 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "sip.h"
|
||||||
|
|
||||||
|
using namespace sip;
|
||||||
|
|
||||||
|
void LogWriter::write(const pj::LogEntry &entry) {
|
||||||
|
auto msg = entry.msg;
|
||||||
|
// 6 = DTRACE, 5 = TRACE, ... , 0 = FATAL
|
||||||
|
int spd_log_level = entry.level > 5 ? 0 : 5 - entry.level;
|
||||||
|
logger->log(static_cast<spdlog::level::level_enum>(spd_log_level), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountConfig::AccountConfig(Settings &settings) {
|
||||||
|
|
||||||
|
idUri = settings.id_uri();
|
||||||
|
|
||||||
|
// in case SIP timer RFC4028 is used
|
||||||
|
// required when registration is used
|
||||||
|
callConfig.timerMinSESec = 120;
|
||||||
|
callConfig.timerSessExpiresSec = 1800;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Account::addHandler(std::function<void(pj::OnIncomingCallParam &)> &&handler) {
|
||||||
|
onIncomingCallHandler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Account::onIncomingCall(pj::OnIncomingCallParam &prm) {
|
||||||
|
if (onIncomingCallHandler) {
|
||||||
|
onIncomingCallHandler(prm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Call::Call(pj::Account &acc, std::shared_ptr<spdlog::logger> logger, int call_id = PJSUA_INVALID_ID)
|
||||||
|
: pj::Call(acc, call_id),
|
||||||
|
logger(std::move(logger)) {}
|
||||||
|
|
||||||
|
Call::~Call() {
|
||||||
|
TRACE(logger, "~Call {}", getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Call::addHandler(std::function<void(pj::OnCallStateParam &)> &&handler) {
|
||||||
|
onCallStateHandler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Call::addHandler(std::function<void(pj::OnCallMediaStateParam &)> &&handler) {
|
||||||
|
onCallMediaStateHandler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
pj::AudioMedia *Call::audio_media() {
|
||||||
|
|
||||||
|
pj::AudioMedia *aud_med{nullptr};
|
||||||
|
|
||||||
|
auto ci = getInfo();
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < ci.media.size(); i++) {
|
||||||
|
if (ci.media[i].type == PJMEDIA_TYPE_AUDIO && getMedia(i)) {
|
||||||
|
aud_med = dynamic_cast<pj::AudioMedia *>(getMedia(i));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aud_med;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Call::onCallState(pj::OnCallStateParam &prm) {
|
||||||
|
if (onCallStateHandler) {
|
||||||
|
onCallStateHandler(prm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Call::onCallMediaState(pj::OnCallMediaStateParam &prm) {
|
||||||
|
if (onCallMediaStateHandler) {
|
||||||
|
onCallMediaStateHandler(prm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const string Call::localUser() {
|
||||||
|
return local_user_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::Client(std::unique_ptr<Account> account_, std::unique_ptr<AccountConfig> account_cfg_,
|
||||||
|
OptionalQueue<events::Event> &events_, Settings &settings,
|
||||||
|
std::shared_ptr<spdlog::logger> logger_, LogWriter *sip_log_writer)
|
||||||
|
: account(std::move(account_)),
|
||||||
|
account_cfg(std::move(account_cfg_)),
|
||||||
|
events(events_),
|
||||||
|
logger(std::move(logger_)) {
|
||||||
|
|
||||||
|
// magic statics yay!
|
||||||
|
static auto ep = std::make_unique<pj::Endpoint>();
|
||||||
|
|
||||||
|
if (ep->libGetState() == PJSUA_STATE_NULL) {
|
||||||
|
init_pj_endpoint(settings, sip_log_writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
account->addHandler([this](pj::OnIncomingCallParam &prm) {
|
||||||
|
auto call = std::make_shared<Call>(*account, logger, prm.callId);
|
||||||
|
auto ci = call->getInfo();
|
||||||
|
|
||||||
|
call->local_user_ = user_from_uri(ci.localUri);
|
||||||
|
calls.emplace(call->getId(), call);
|
||||||
|
|
||||||
|
set_default_handlers(call);
|
||||||
|
|
||||||
|
DEBUG(logger, "incoming SIP call #{} from {} to {} with call-id {}", ci.id, ci.remoteUri, ci.localUri,
|
||||||
|
ci.callIdString);
|
||||||
|
|
||||||
|
events.emplace(events::IncomingCall{ci.id, call->localUser()});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::~Client() {
|
||||||
|
TRACE(logger, "~Client");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::init_pj_endpoint(Settings &settings, LogWriter *sip_log_writer) {
|
||||||
|
using namespace pj;
|
||||||
|
|
||||||
|
auto log_writer = sip_log_writer;
|
||||||
|
auto &ep = pj::Endpoint::instance();
|
||||||
|
|
||||||
|
// Hide logs before pjsua2 init
|
||||||
|
pj_log_set_level(0);
|
||||||
|
|
||||||
|
ep.libCreate();
|
||||||
|
|
||||||
|
EpConfig ep_cfg;
|
||||||
|
if (!settings.pjsip_log_sip_messages()) {
|
||||||
|
// don't log SIP messages
|
||||||
|
ep_cfg.logConfig.msgLogging = 0;
|
||||||
|
}
|
||||||
|
ep_cfg.logConfig.consoleLevel = 6;
|
||||||
|
|
||||||
|
// TODO: partially remove decor. Leave filename. Could require pjsip recompile.
|
||||||
|
ep_cfg.logConfig.decor = 0;
|
||||||
|
ep_cfg.logConfig.writer = log_writer;
|
||||||
|
|
||||||
|
// https://trac.pjsip.org/repos/wiki/FAQ#high-perf
|
||||||
|
// disable echo canceller
|
||||||
|
ep_cfg.medConfig.ecTailLen = 0;
|
||||||
|
// set SIP threads number
|
||||||
|
ep_cfg.medConfig.threadCnt = settings.sip_thread_count();
|
||||||
|
|
||||||
|
// 10ms ptime required to keep L16 RTP packet below MTU
|
||||||
|
ep_cfg.medConfig.audioFramePtime = 10;
|
||||||
|
ep_cfg.medConfig.ptime = 10;
|
||||||
|
|
||||||
|
// must be the same as used in media ports
|
||||||
|
ep_cfg.medConfig.clockRate = 48000;
|
||||||
|
|
||||||
|
// in case of trouble check:
|
||||||
|
// PJSUA_MAX_CALLS
|
||||||
|
// PJMEDIA_MAX_MTU
|
||||||
|
// PJ_IOQUEUE_MAX_HANDLES = calls x2
|
||||||
|
// PJSUA_MAX_PLAYERS = max calls
|
||||||
|
|
||||||
|
ep_cfg.uaConfig.maxCalls = PJSUA_MAX_CALLS;
|
||||||
|
|
||||||
|
auto stun_server = settings.stun_server();
|
||||||
|
if (!stun_server.empty()) {
|
||||||
|
ep_cfg.uaConfig.stunServer.emplace_back(stun_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.libInit(ep_cfg);
|
||||||
|
ep.audDevManager().setNullDev();
|
||||||
|
|
||||||
|
// pjSIP with switch board require matching of SIP audio
|
||||||
|
// and TG audio port clock rate so we MUST force
|
||||||
|
// using 48kHz codecs for all SIP calls
|
||||||
|
std::string codecId = settings.raw_pcm() ? "L16/48000/1" : "opus/48000/2";
|
||||||
|
CodecInfoVector codecVector = ep.codecEnum();
|
||||||
|
|
||||||
|
for (auto const &value : codecVector) {
|
||||||
|
ep.codecSetPriority(value->codecId, (pj_uint8_t) (value->codecId == codecId ? 255 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
TransportConfig t_cfg;
|
||||||
|
|
||||||
|
// defaults to any open port
|
||||||
|
t_cfg.port = settings.sip_port();
|
||||||
|
t_cfg.portRange = settings.sip_port_range();
|
||||||
|
|
||||||
|
auto public_address = settings.public_address();
|
||||||
|
if (!public_address.empty()) {
|
||||||
|
t_cfg.publicAddress = public_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
ep.transportCreate(PJSIP_TRANSPORT_UDP, t_cfg);
|
||||||
|
|
||||||
|
ep.libStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::start() {
|
||||||
|
account->create(*account_cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Client::user_from_uri(const std::string &uri) {
|
||||||
|
std::string user{};
|
||||||
|
|
||||||
|
pj_pool_t *pj_pool = pjsua_pool_create("temp%p", 2048, 1024);
|
||||||
|
|
||||||
|
if (pj_pool != nullptr) {
|
||||||
|
|
||||||
|
std::vector<char> buffer(uri.begin(), uri.end());
|
||||||
|
buffer.push_back('\0');
|
||||||
|
|
||||||
|
pjsip_uri *pj_uri = pjsip_parse_uri(pj_pool, &buffer[0], uri.size(), PJSIP_PARSE_URI_IN_FROM_TO_HDR);
|
||||||
|
|
||||||
|
if (pj_uri != nullptr && PJSIP_URI_SCHEME_IS_SIP(pj_uri)) {
|
||||||
|
auto pj_sip_uri = static_cast<pjsip_sip_uri *>(pjsip_uri_get_uri(pj_uri));
|
||||||
|
user = std::string(pj_sip_uri->user.ptr,
|
||||||
|
static_cast<unsigned long>(pj_sip_uri->user.slen));
|
||||||
|
}
|
||||||
|
|
||||||
|
pj_pool_release(pj_pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
pjsua_call_id Client::Dial(const std::string &uri, const pj::CallOpParam &prm) {
|
||||||
|
auto call = std::make_shared<Call>(*account, logger);
|
||||||
|
set_default_handlers(call);
|
||||||
|
|
||||||
|
// sip ID is known only after making call
|
||||||
|
call->makeCall(uri, prm);
|
||||||
|
auto sip_id = call->getId();
|
||||||
|
calls.emplace(sip_id, call);
|
||||||
|
|
||||||
|
return sip_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::set_default_handlers(const std::shared_ptr<Call> &call) {
|
||||||
|
call->addHandler([this, call_wpt = std::weak_ptr<Call>(call)](pj::OnCallStateParam &prm) {
|
||||||
|
if (auto call_spt = call_wpt.lock()) {
|
||||||
|
events.emplace(events::CallStateUpdate{call_spt->getId(), call_spt->getInfo().state});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
call->addHandler([this, call_wpt = std::weak_ptr<Call>(call)](pj::OnCallMediaStateParam &prm) {
|
||||||
|
if (auto call_spt = call_wpt.lock()) {
|
||||||
|
events.emplace(events::CallMediaStateUpdate{call_spt->getId(), call_spt->hasMedia()});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::Hangup(const pjsua_call_id call_id, const pj::CallOpParam &prm) {
|
||||||
|
auto it = calls.find(call_id);
|
||||||
|
if (it != calls.end()) {
|
||||||
|
auto call = it->second;
|
||||||
|
call->hangup(prm);
|
||||||
|
calls.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::BridgeAudio(const pjsua_call_id call_id, pj::AudioMedia *input, pj::AudioMedia *output) {
|
||||||
|
|
||||||
|
auto it = calls.find(call_id);
|
||||||
|
|
||||||
|
if (it == calls.end()) {
|
||||||
|
throw std::runtime_error{"CALL_NOT_FOUND"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto sip_audio = it->second->audio_media();
|
||||||
|
|
||||||
|
if (sip_audio == nullptr) {
|
||||||
|
throw std::runtime_error{"SIP_MEDIA_NOT_READY"};
|
||||||
|
}
|
||||||
|
|
||||||
|
sip_audio->startTransmit(*input);
|
||||||
|
output->startTransmit(*sip_audio);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::Answer(pjsua_call_id call_id, const pj::CallOpParam &prm) {
|
||||||
|
auto it = calls.find(call_id);
|
||||||
|
|
||||||
|
if (it == calls.end()) {
|
||||||
|
throw std::runtime_error{"CALL_NOT_FOUND"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto call = it->second;
|
||||||
|
call->answer(prm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::DialDtmf(pjsua_call_id call_id, const string &dtmf_digits) {
|
||||||
|
auto it = calls.find(call_id);
|
||||||
|
|
||||||
|
if (it == calls.end()) {
|
||||||
|
throw std::runtime_error{"CALL_NOT_FOUND"};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto call = it->second;
|
||||||
|
call->dialDtmf(dtmf_digits);
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_SIP_H
|
||||||
|
#define TG2SIP_SIP_H
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
#include <pjsua2.hpp>
|
||||||
|
#include "logging.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "queue.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
namespace sip {
|
||||||
|
|
||||||
|
namespace events {
|
||||||
|
struct IncomingCall {
|
||||||
|
pjsua_call_id id;
|
||||||
|
std::string extension;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CallStateUpdate {
|
||||||
|
pjsua_call_id id;
|
||||||
|
pjsip_inv_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CallMediaStateUpdate {
|
||||||
|
pjsua_call_id id;
|
||||||
|
bool has_media;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::variant<IncomingCall, CallStateUpdate, CallMediaStateUpdate> Event;
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogWriter : public pj::LogWriter {
|
||||||
|
public:
|
||||||
|
explicit LogWriter(std::shared_ptr<spdlog::logger> logger) : logger(std::move(logger)) {};
|
||||||
|
|
||||||
|
~LogWriter() override = default;
|
||||||
|
|
||||||
|
void write(const pj::LogEntry &entry) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AccountConfig : public pj::AccountConfig {
|
||||||
|
explicit AccountConfig(Settings &settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
class Call : public pj::Call {
|
||||||
|
public:
|
||||||
|
Call(pj::Account &acc, std::shared_ptr<spdlog::logger> logger, int call_id);
|
||||||
|
|
||||||
|
~Call() override;
|
||||||
|
|
||||||
|
void addHandler(std::function<void(pj::OnCallStateParam &)> &&handler);
|
||||||
|
|
||||||
|
void addHandler(std::function<void(pj::OnCallMediaStateParam &)> &&handler);
|
||||||
|
|
||||||
|
const std::string localUser();
|
||||||
|
|
||||||
|
pj::AudioMedia *audio_media();
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Client;
|
||||||
|
|
||||||
|
void onCallState(pj::OnCallStateParam &prm) override;
|
||||||
|
|
||||||
|
void onCallMediaState(pj::OnCallMediaStateParam &prm) override;
|
||||||
|
|
||||||
|
std::function<void(pj::OnCallStateParam &)> onCallStateHandler;
|
||||||
|
std::function<void(pj::OnCallMediaStateParam &)> onCallMediaStateHandler;
|
||||||
|
std::string local_user_;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Account : public pj::Account {
|
||||||
|
public:
|
||||||
|
explicit Account(std::shared_ptr<spdlog::logger> logger) : logger(std::move(logger)) {};
|
||||||
|
|
||||||
|
void addHandler(std::function<void(pj::OnIncomingCallParam &)> &&handler);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onIncomingCall(pj::OnIncomingCallParam &prm) override;
|
||||||
|
|
||||||
|
std::function<void(pj::OnIncomingCallParam &)> onIncomingCallHandler;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
Client(std::unique_ptr<Account> account_, std::unique_ptr<AccountConfig> account_cfg_,
|
||||||
|
OptionalQueue<events::Event> &events, Settings &settings,
|
||||||
|
std::shared_ptr<spdlog::logger> logger_, LogWriter *sip_log_writer);
|
||||||
|
|
||||||
|
Client(const Client &) = delete;
|
||||||
|
|
||||||
|
Client &operator=(const Client &) = delete;
|
||||||
|
|
||||||
|
virtual ~Client();
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
pjsua_call_id Dial(const std::string &uri, const pj::CallOpParam &prm);
|
||||||
|
|
||||||
|
void Answer(pjsua_call_id call_id, const pj::CallOpParam &prm);
|
||||||
|
|
||||||
|
void Hangup(pjsua_call_id call_id, const pj::CallOpParam &prm);
|
||||||
|
|
||||||
|
void DialDtmf(pjsua_call_id call_id, const string &dtmf_digits);
|
||||||
|
|
||||||
|
void BridgeAudio(pjsua_call_id call_id, pj::AudioMedia *input, pj::AudioMedia *output);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
std::unique_ptr<Account> account;
|
||||||
|
std::unique_ptr<AccountConfig> account_cfg;
|
||||||
|
|
||||||
|
OptionalQueue<events::Event> &events;
|
||||||
|
std::map<int, std::shared_ptr<Call>> calls;
|
||||||
|
|
||||||
|
static void init_pj_endpoint(Settings &settings, LogWriter *sip_log_writer);
|
||||||
|
|
||||||
|
static std::string user_from_uri(const std::string &uri);
|
||||||
|
|
||||||
|
void set_default_handlers(const std::shared_ptr<Call> &call);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //TG2SIP_SIP_H
|
|
@ -0,0 +1,202 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tg.h"
|
||||||
|
#include "queue.h"
|
||||||
|
|
||||||
|
using namespace tg;
|
||||||
|
|
||||||
|
Client::Client(Settings &settings, std::shared_ptr<spdlog::logger> logger_,
|
||||||
|
OptionalQueue<Object> &events_)
|
||||||
|
: logger(std::move(logger_)), events(events_) {
|
||||||
|
|
||||||
|
client = std::make_unique<td::Client>();
|
||||||
|
init_lib_parameters(settings);
|
||||||
|
init_proxy(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
Client::~Client() {
|
||||||
|
TRACE(logger, "~Client");
|
||||||
|
is_closed = true;
|
||||||
|
if (thread_.joinable()) {
|
||||||
|
thread_.join();
|
||||||
|
}
|
||||||
|
client.release();
|
||||||
|
TRACE(logger, "~Client done");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::init_lib_parameters(Settings &settings) {
|
||||||
|
lib_parameters = td_api::make_object<td_api::tdlibParameters>();
|
||||||
|
|
||||||
|
lib_parameters->api_id_ = settings.api_id();
|
||||||
|
lib_parameters->api_hash_ = settings.api_hash();
|
||||||
|
lib_parameters->database_directory_ = settings.db_folder();
|
||||||
|
|
||||||
|
lib_parameters->system_language_code_ = settings.sys_lang_code();
|
||||||
|
lib_parameters->device_model_ = settings.device_model();
|
||||||
|
lib_parameters->system_version_ = settings.system_version();
|
||||||
|
lib_parameters->application_version_ = settings.app_version();
|
||||||
|
|
||||||
|
lib_parameters->use_file_database_ = false;
|
||||||
|
lib_parameters->use_chat_info_database_ = true;
|
||||||
|
lib_parameters->use_message_database_ = false;
|
||||||
|
lib_parameters->use_secret_chats_ = false;
|
||||||
|
lib_parameters->enable_storage_optimizer_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::init_proxy(Settings &settings) {
|
||||||
|
if (settings.proxy_enabled()) {
|
||||||
|
auto socks_proxy = td_api::make_object<td_api::proxySocks5>(
|
||||||
|
settings.proxy_address(),
|
||||||
|
settings.proxy_port(),
|
||||||
|
settings.proxy_username(),
|
||||||
|
settings.proxy_password()
|
||||||
|
);
|
||||||
|
proxy = td_api::move_object_as<td_api::Proxy>(socks_proxy);
|
||||||
|
} else {
|
||||||
|
auto empty_proxy = td_api::make_object<td_api::proxyEmpty>();
|
||||||
|
proxy = td_api::move_object_as<td_api::Proxy>(empty_proxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::loop() {
|
||||||
|
TRACE(logger, "TG client thread started");
|
||||||
|
|
||||||
|
while (!is_closed) {
|
||||||
|
auto response = client->receive(WAIT_TIMEOUT);
|
||||||
|
process_response(std::move(response));
|
||||||
|
}
|
||||||
|
TRACE(logger, "TG client thread ended");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::start() {
|
||||||
|
thread_ = std::thread(&Client::loop, this);
|
||||||
|
pthread_setname_np(thread_.native_handle(), "tg_client");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::process_response(td::Client::Response response) {
|
||||||
|
|
||||||
|
if (!response.object) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACE(logger, "TG client got response with ID {}\n{}", response.id, to_string(response.object));
|
||||||
|
|
||||||
|
if (response.id == 0) {
|
||||||
|
return process_update(std::move(response.object));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = handlers.find(response.id);
|
||||||
|
if (it != handlers.end()) {
|
||||||
|
it->second(std::move(response.object));
|
||||||
|
handlers.erase(it->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::process_update(Object update) {
|
||||||
|
switch (update->get_id()) {
|
||||||
|
case td_api::updateAuthorizationState::ID: {
|
||||||
|
auto update_authorization_state = td_api::move_object_as<td_api::updateAuthorizationState>(update);
|
||||||
|
on_authorization_state_update(std::move(update_authorization_state->authorization_state_));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case td_api::updateConnectionState::ID: {
|
||||||
|
auto update_connection_state = td_api::move_object_as<td_api::updateConnectionState>(update);
|
||||||
|
if (update_connection_state->state_->get_id() == td_api::connectionStateReady::ID) {
|
||||||
|
logger->info("TG client connected");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case td_api::updateCall::ID:
|
||||||
|
case td_api::updateNewMessage::ID: {
|
||||||
|
events.emplace(std::move(update));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler) {
|
||||||
|
auto query_id = next_query_id();
|
||||||
|
if (handler) {
|
||||||
|
handlers.emplace(query_id, std::move(handler));
|
||||||
|
}
|
||||||
|
client->send({query_id, std::move(f)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::future<Client::Object> Client::send_query_async(td_api::object_ptr<td_api::Function> f) {
|
||||||
|
|
||||||
|
if (std::this_thread::get_id() == thread_.get_id()) {
|
||||||
|
logger->critical("Call of send_query_async from TG thread will cause deadlock");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto promise = std::make_shared<std::promise<Object>>();
|
||||||
|
auto future = promise->get_future();
|
||||||
|
|
||||||
|
send_query(std::move(f), [this, promise](Object object) {
|
||||||
|
try {
|
||||||
|
promise->set_value(std::move(object));
|
||||||
|
} catch (std::future_error &error) {
|
||||||
|
logger->critical("failed to set send_query_async promise value {}", error.code());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::uint64_t Client::next_query_id() {
|
||||||
|
return ++current_query_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Client::create_authentication_query_handler() {
|
||||||
|
return [this](Object object) {
|
||||||
|
check_authentication_error(std::move(object));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::check_authentication_error(Object object) {
|
||||||
|
if (object->get_id() == td_api::error::ID) {
|
||||||
|
auto error = td_api::move_object_as<td_api::error>(object);
|
||||||
|
logger->error("TG client authorization error\n{}", to_string(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::on_authorization_state_update(td_api::object_ptr<td_api::AuthorizationState> authorization_state) {
|
||||||
|
switch (authorization_state->get_id()) {
|
||||||
|
case td_api::authorizationStateReady::ID:
|
||||||
|
is_ready_.set_value(true);
|
||||||
|
logger->info("TG client authorization ready");
|
||||||
|
break;
|
||||||
|
case td_api::authorizationStateWaitEncryptionKey::ID:
|
||||||
|
send_query(td_api::make_object<td_api::checkDatabaseEncryptionKey>(),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
send_query(td_api::make_object<td_api::setProxy>(td_api::move_object_as<td_api::Proxy>(proxy)),
|
||||||
|
create_authentication_query_handler());
|
||||||
|
break;
|
||||||
|
case td_api::authorizationStateWaitTdlibParameters::ID: {
|
||||||
|
send_query(td_api::make_object<td_api::setTdlibParameters>(
|
||||||
|
std::move(lib_parameters)), create_authentication_query_handler());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
is_ready_.set_value(false);
|
||||||
|
logger->error("TG client auto sign-on failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_TG_H
|
||||||
|
#define TG2SIP_TG_H
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
#include <functional>
|
||||||
|
#include <future>
|
||||||
|
#include <map>
|
||||||
|
#include <td/telegram/Client.h>
|
||||||
|
#include "utils.h"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "settings.h"
|
||||||
|
#include "queue.h"
|
||||||
|
|
||||||
|
namespace tg {
|
||||||
|
|
||||||
|
namespace td_api = td::td_api;
|
||||||
|
|
||||||
|
class Client {
|
||||||
|
public:
|
||||||
|
using Object = td_api::object_ptr<td_api::Object>;
|
||||||
|
|
||||||
|
virtual ~Client();
|
||||||
|
|
||||||
|
explicit Client(Settings &settings, std::shared_ptr<spdlog::logger> logger_,
|
||||||
|
OptionalQueue<Object> &events_);
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
void send_query(td_api::object_ptr<td_api::Function> f, std::function<void(Object)> handler = nullptr);
|
||||||
|
|
||||||
|
std::future<Object> send_query_async(td_api::object_ptr<td_api::Function> f);
|
||||||
|
|
||||||
|
std::future<bool> is_ready() { return is_ready_.get_future(); };
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
OptionalQueue<Object> &events;
|
||||||
|
|
||||||
|
const double WAIT_TIMEOUT = 10;
|
||||||
|
|
||||||
|
std::unique_ptr<td::Client> client;
|
||||||
|
std::unique_ptr<td_api::tdlibParameters> lib_parameters;
|
||||||
|
td_api::object_ptr<td_api::Proxy> proxy;
|
||||||
|
|
||||||
|
std::thread thread_;
|
||||||
|
|
||||||
|
std::promise<bool> is_ready_;
|
||||||
|
bool is_closed{false};
|
||||||
|
std::uint64_t current_query_id{0};
|
||||||
|
|
||||||
|
std::map<std::uint64_t, std::function<void(Object)>> handlers;
|
||||||
|
|
||||||
|
void init_lib_parameters(Settings &settings);
|
||||||
|
|
||||||
|
void init_proxy(Settings &settings);
|
||||||
|
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
void process_response(td::Client::Response response);
|
||||||
|
|
||||||
|
void process_update(Object update);
|
||||||
|
|
||||||
|
std::uint64_t next_query_id();
|
||||||
|
|
||||||
|
auto create_authentication_query_handler();
|
||||||
|
|
||||||
|
void check_authentication_error(Object object);
|
||||||
|
|
||||||
|
void on_authorization_state_update(td_api::object_ptr<td_api::AuthorizationState> authorization_state);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //TG2SIP_TG_H
|
|
@ -0,0 +1,21 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
bool is_digits(const std::string &str) { return std::all_of(str.begin(), str.end(), ::isdigit); };
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017-2018 infactum (infactum@gmail.com)
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TG2SIP_UTILS_H
|
||||||
|
#define TG2SIP_UTILS_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
bool is_digits(const std::string &str);
|
||||||
|
|
||||||
|
template<class ToT, class FromT>
|
||||||
|
std::unique_ptr<ToT> move_unique_ptr_as(std::unique_ptr<FromT> &from) {
|
||||||
|
return std::unique_ptr<ToT>(static_cast<ToT *>(from.release()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //TG2SIP_UTILS_H
|
Loading…
Reference in New Issue