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