diff options
author | Trumeet <yuuta@yuuta.moe> | 2021-02-26 13:36:37 -0800 |
---|---|---|
committer | Trumeet <yuuta@yuuta.moe> | 2021-02-26 13:36:37 -0800 |
commit | 3c80cd2b7744a366d39de684c6451285b94a4d1b (patch) | |
tree | 9b267dc4afb3cd2e7278167678c4badf946e0245 | |
download | minebridge-3c80cd2b7744a366d39de684c6451285b94a4d1b.tar minebridge-3c80cd2b7744a366d39de684c6451285b94a4d1b.tar.gz minebridge-3c80cd2b7744a366d39de684c6451285b94a4d1b.tar.bz2 minebridge-3c80cd2b7744a366d39de684c6451285b94a4d1b.zip |
First Commit
-rw-r--r-- | .gitignore | 55 | ||||
-rw-r--r-- | AUTHORS | 0 | ||||
-rw-r--r-- | COPYING | 339 | ||||
-rw-r--r-- | ChangeLog | 0 | ||||
-rw-r--r-- | INSTALL | 368 | ||||
-rw-r--r-- | Makefile.am | 8 | ||||
-rw-r--r-- | NEWS | 0 | ||||
-rw-r--r-- | README | 49 | ||||
-rw-r--r-- | common.h | 7 | ||||
-rw-r--r-- | configure.ac | 32 | ||||
-rw-r--r-- | environ.c | 56 | ||||
-rw-r--r-- | environ.h | 20 | ||||
-rw-r--r-- | main.c | 354 | ||||
-rw-r--r-- | mcin/mc_regex.h | 55 | ||||
-rw-r--r-- | mcin/mcin.c | 94 | ||||
-rw-r--r-- | mcin/mcin.h | 17 | ||||
-rw-r--r-- | net/curlutils.c | 28 | ||||
-rw-r--r-- | net/curlutils.h | 14 | ||||
-rw-r--r-- | net/net.c | 60 | ||||
-rw-r--r-- | net/net.h | 12 | ||||
-rw-r--r-- | rcon/rcon.c | 133 | ||||
-rw-r--r-- | rcon/rcon.h | 29 | ||||
-rw-r--r-- | tg/tg.c | 412 | ||||
-rw-r--r-- | tg/tg.h | 95 |
24 files changed, 2237 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c09bce --- /dev/null +++ b/.gitignore @@ -0,0 +1,55 @@ +*.o +minebridge + +# http://www.gnu.org/software/automake + +Makefile.in +/ar-lib +/mdate-sh +/py-compile +/test-driver +/ylwrap +.deps/ +.dirstamp + +# http://www.gnu.org/software/autoconf + +autom4te.cache +/autoscan.log +/autoscan-*.log +/aclocal.m4 +/compile +/config.guess +/config.h* +/config.log +/config.status +/config.sub +/configure +/configure~ +/configure.scan +/depcomp +/install-sh +/missing +/stamp-h1 + +# https://www.gnu.org/software/libtool/ + +/ltmain.sh + +# http://www.gnu.org/software/texinfo + +/texinfo.tex + +# http://www.gnu.org/software/m4/ + +m4/libtool.m4 +m4/ltoptions.m4 +m4/ltsugar.m4 +m4/ltversion.m4 +m4/lt~obsolete.m4 + +# Generated Makefile +# (meta build system like autotools, +# can automatically generate from config.status script +# (which is called by configure script)) +Makefile @@ -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. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,368 @@ +Installation Instructions +************************* + + Copyright (C) 1994-1996, 1999-2002, 2004-2016 Free Software +Foundation, Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell command './configure && make && make install' +should configure, build, and install this package. The following +more-detailed instructions are generic; see the 'README' file for +instructions specific to this package. Some packages provide this +'INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The 'configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a 'Makefile' in each directory of the package. +It may also create one or more '.h' files containing system-dependent +definitions. Finally, it creates a shell script 'config.status' that +you can run in the future to recreate the current configuration, and a +file 'config.log' containing compiler output (useful mainly for +debugging 'configure'). + + It can also use an optional file (typically called 'config.cache' and +enabled with '--cache-file=config.cache' or simply '-C') that saves the +results of its tests to speed up reconfiguring. Caching is disabled by +default to prevent problems with accidental use of stale cache files. + + If you need to do unusual things to compile the package, please try +to figure out how 'configure' could check whether to do them, and mail +diffs or instructions to the address given in the 'README' so they can +be considered for the next release. If you are using the cache, and at +some point 'config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file 'configure.ac' (or 'configure.in') is used to create +'configure' by a program called 'autoconf'. You need 'configure.ac' if +you want to change it or regenerate 'configure' using a newer version of +'autoconf'. + + The simplest way to compile this package is: + + 1. 'cd' to the directory containing the package's source code and type + './configure' to configure the package for your system. + + Running 'configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type 'make' to compile the package. + + 3. Optionally, type 'make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type 'make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the 'make install' phase executed with root + privileges. + + 5. Optionally, type 'make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior 'make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing 'make clean'. To also remove the + files that 'configure' created (so you can compile the package for + a different kind of computer), type 'make distclean'. There is + also a 'make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type 'make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide 'make + distcheck', which can by used by developers to test that all other + targets like 'make install' and 'make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the 'configure' script does not know about. Run './configure --help' +for details on some of the pertinent environment variables. + + You can give 'configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here is +an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU 'make'. 'cd' to the +directory where you want the object files and executables to go and run +the 'configure' script. 'configure' automatically checks for the source +code in the directory that 'configure' is in and in '..'. This is known +as a "VPATH" build. + + With a non-GNU 'make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use 'make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple '-arch' options to the +compiler but only a single '-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the 'lipo' tool if you have problems. + +Installation Names +================== + + By default, 'make install' installs the package's commands under +'/usr/local/bin', include files under '/usr/local/include', etc. You +can specify an installation prefix other than '/usr/local' by giving +'configure' the option '--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option '--exec-prefix=PREFIX' to 'configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like '--bindir=DIR' to specify different values for particular +kinds of files. Run 'configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the default +for these options is expressed in terms of '${prefix}', so that +specifying just '--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to 'configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +'make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, 'make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +'${prefix}'. Any directories that were specified during 'configure', +but not in terms of '${prefix}', must each be overridden at install time +for the entire installation to be relocated. The approach of makefile +variable overrides for each directory variable is required by the GNU +Coding Standards, and ideally causes no recompilation. However, some +platforms have known limitations with the semantics of shared libraries +that end up requiring recompilation when using this method, particularly +noticeable in packages that use GNU Libtool. + + The second method involves providing the 'DESTDIR' variable. For +example, 'make install DESTDIR=/alternate/directory' will prepend +'/alternate/directory' before all installation names. The approach of +'DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of '${prefix}' +at 'configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving 'configure' the +option '--program-prefix=PREFIX' or '--program-suffix=SUFFIX'. + + Some packages pay attention to '--enable-FEATURE' options to +'configure', where FEATURE indicates an optional part of the package. +They may also pay attention to '--with-PACKAGE' options, where PACKAGE +is something like 'gnu-as' or 'x' (for the X Window System). The +'README' should mention any '--enable-' and '--with-' options that the +package recognizes. + + For packages that use the X Window System, 'configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the 'configure' options '--x-includes=DIR' and +'--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of 'make' will be. For these packages, running './configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with 'make V=1'; while running './configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with 'make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU CC +is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX 'make' updates targets which have the same time stamps as their +prerequisites, which makes it generally unusable when shipped generated +files such as 'configure' are involved. Use GNU 'make' instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its '<wchar.h>' header file. The option '-nodtk' can be used as a +workaround. If GNU CC is not installed, it is therefore recommended to +try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put '/usr/ucb' early in your 'PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in '/usr/bin'. So, if you need '/usr/ucb' +in your 'PATH', put it _after_ '/usr/bin'. + + On Haiku, software installed for all users goes in '/boot/common', +not '/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features 'configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, 'configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +'--build=TYPE' option. TYPE can either be a short name for the system +type, such as 'sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file 'config.sub' for the possible values of each field. If +'config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option '--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with '--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for 'configure' scripts to share, +you can create a site shell script called 'config.site' that gives +default values for variables like 'CC', 'cache_file', and 'prefix'. +'configure' looks for 'PREFIX/share/config.site' if it exists, then +'PREFIX/etc/config.site' if it exists. Or, you can set the +'CONFIG_SITE' environment variable to the location of the site script. +A warning: not all 'configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to 'configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the 'configure' command line, using 'VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified 'gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for 'CONFIG_SHELL' due to an +Autoconf limitation. Until the limitation is lifted, you can use this +workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +'configure' Invocation +====================== + + 'configure' recognizes the following options to control how it +operates. + +'--help' +'-h' + Print a summary of all of the options to 'configure', and exit. + +'--help=short' +'--help=recursive' + Print a summary of the options unique to this package's + 'configure', and exit. The 'short' variant lists options used only + in the top level, while the 'recursive' variant lists options also + present in any nested packages. + +'--version' +'-V' + Print the version of Autoconf used to generate the 'configure' + script, and exit. + +'--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally 'config.cache'. FILE defaults to '/dev/null' to + disable caching. + +'--config-cache' +'-C' + Alias for '--cache-file=config.cache'. + +'--quiet' +'--silent' +'-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to '/dev/null' (any error + messages will still be shown). + +'--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + 'configure' can determine that directory automatically. + +'--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: for + more details, including other options available for fine-tuning the + installation locations. + +'--no-create' +'-n' + Run the configure checks, but stop before creating any output + files. + +'configure' also accepts some other, not widely useful, options. Run +'configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..569290c --- /dev/null +++ b/Makefile.am @@ -0,0 +1,8 @@ +bin_PROGRAMS = minebridge +minebridge_SOURCES = main.c \ + environ.c \ + tg/tg.c \ + net/net.c \ + rcon/rcon.c \ + net/curlutils.c \ + mcin/mcin.c @@ -0,0 +1,49 @@ +minebridge + +IPMI for Minecraft over Telegram. + +REQUIREMENTS + +json-c 0.15 (libjson-c.so.5) +curl 7.75 (libcurl.so.4) +POSIX compliant system +gnu99 C compiler + +INSTALLATION + +autoreconf --install +./configure +make +make install + +CONFIGURATION + +TG_ADMIN=114514 # UID allowed to execute rcon commands other than /list. Equivalent to OP4. +TG_CHAT=-114514 # Chat that is allowed to send message to and from. +TG_LINK_FMT=https://t.me/c/xxx/%d # Format of Telegram links. %d: Message ID. +TG_API=114514:1919810 # Telegram bot API Key. +RCON_HOST=localhost # rcon host. +RCON_PORT=25575 # rcon port. +RCON_PASSWD=p@ssw0rd! # rcon password. + +stdin: read from Minecraft server stdout. Use either pipe or log. For example: + +tail -f --lines=0 /path/to/logs/latest.log | minebridge + +LIMITATIONS + +Cannot start the server if it is not running. + +ACKNOWLEDGEMENTS + +mcrcon: https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 +json-c +libcurl + +BUG REPORT + +yuuta@yuuta.moe + +LICENSE + +GPL v2 only. diff --git a/common.h b/common.h new file mode 100644 index 0000000..615aeae --- /dev/null +++ b/common.h @@ -0,0 +1,7 @@ +#ifndef _COMMON_H +#define _COMMON_H + +#include <libintl.h> +#define _(X) gettext(X) + +#endif // _COMMON_H diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..31ea4a6 --- /dev/null +++ b/configure.ac @@ -0,0 +1,32 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ([2.71]) +AC_INIT(minebridge, 1.0, yuuta@yuuta.moe) +AM_INIT_AUTOMAKE([-Wall -Werror subdir-objects]) +AC_CONFIG_SRCDIR([config.h.in]) +AC_CONFIG_HEADERS([config.h]) + +# Checks for programs. +AC_PROG_CC + +# Checks for libraries. +AC_CHECK_LIB([curl], [curl_easy_init]) +AC_CHECK_LIB([json-c], [json_object_put]) +AC_CHECK_LIB([pthread], [pthread_create]) + +# Checks for header files. +AC_CHECK_HEADERS([libintl.h netdb.h sys/socket.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_INT32_T +AC_TYPE_INT64_T +AC_TYPE_UINT64_T +AC_TYPE_SIZE_T +AC_FUNC_MALLOC +AC_FUNC_REALLOC +AC_CHECK_FUNCS([memset regcomp socket strerror]) + +# Checks for library functions. +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT diff --git a/environ.c b/environ.c new file mode 100644 index 0000000..821514f --- /dev/null +++ b/environ.c @@ -0,0 +1,56 @@ +#include "environ.h" +#include "common.h" + +#include <sysexits.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <inttypes.h> +#include <string.h> + +int environ_read(Environ *out) +{ + out->tg_api = getenv("TG_API"); + out->tg_chat_raw = getenv("TG_CHAT"); + char *tg_admin_raw = getenv("TG_ADMIN"); + out->tg_link_fmt = getenv("TG_LINK_FMT"); + out->rcon_host = getenv("RCON_HOST"); + out->rcon_port = getenv("RCON_PORT"); + out->rcon_passwd = getenv("RCON_PASSWD"); + if(out->tg_api == NULL || + tg_admin_raw == NULL || + out->tg_chat_raw == NULL || + out->tg_link_fmt == NULL || + out->rcon_host == NULL || + out->rcon_port == NULL) + { + fprintf(stderr, _("Required environment variables are missing.\n")); + return EX_USAGE; + } + char *endptr; + intmax_t num = strtoimax(out->tg_chat_raw, &endptr, 10); + if(strcmp(endptr, "") || (num == INTMAX_MAX && errno == ERANGE)) + { + fprintf(stderr, _("TG_CHAT is invalid\n")); + return EX_USAGE; + } + if(num > INT64_MAX || num < INT64_MIN) + { + fprintf(stderr, _("TG_CHAT is invalid\n")); + return EX_USAGE; + } + out->tg_chat = (int64_t)num; + uintmax_t unum = strtoumax(tg_admin_raw, &endptr, 10); + if(strcmp(endptr, "") || (unum == UINTMAX_MAX && errno == ERANGE)) + { + fprintf(stderr, _("TG_ADMIN is invalid\n")); + return EX_USAGE; + } + if(unum > UINT32_MAX || unum < 0) + { + fprintf(stderr, _("TG_ADMIN is invalid\n")); + return EX_USAGE; + } + out->tg_admin = (uint32_t)unum; + return 0; +} diff --git a/environ.h b/environ.h new file mode 100644 index 0000000..2aaaebf --- /dev/null +++ b/environ.h @@ -0,0 +1,20 @@ +#ifndef _ENVIRON_H +#define _ENVIRON_H + +#include <stdint.h> + +typedef struct environ { + char *tg_api; + char *tg_chat_raw; + int64_t tg_chat; + uint32_t tg_admin; + char *tg_link_fmt; + char *rcon_host; + char *rcon_port; + char *rcon_passwd; + int *rcon_sd; +} Environ; + +int environ_read(Environ *out); + +#endif // _ENVIRON_H @@ -0,0 +1,354 @@ +#include "tg/tg.h" +#include "net/net.h" +#include "rcon/rcon.h" +#include "mcin/mcin.h" +#include "common.h" +#include "net/curlutils.h" + +#include <stdlib.h> +#include <stdio.h> +#include <sysexits.h> +#include <curl/curl.h> +#include <pthread.h> +#include <string.h> +#include <unistd.h> + +void *main_mc2tg(void *e) +{ + const Environ *env = (Environ*)e; + int r = 0; + MCINMatcher matcher; + r = mcin_matcher_init(&matcher); + if(r) goto cleanup; + CURL *curl = NULL; + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); + + char buffer[501]; +read: + if(fgets(buffer, 500, stdin) == NULL) + { + r = 0; + goto cleanup; + } + char *data = mcin_matcher_match(&matcher, buffer); + if(data == NULL) goto read; + TGResp *resp; + r = tg_send_message(curl, + env->tg_api, + &resp, + env->tg_chat_raw, + data, + TG_SEND_MESSAGE_PARSE_MODE_NONE, + 0); // Ignore errors + free(data); + if(!r) + { + tg_message_free((TGMessage*)resp->result); + tg_resp_free(resp); + } + goto read; +cleanup: + if(curl != NULL) curl_easy_cleanup(curl); + mcin_matcher_free(&matcher); + pthread_exit(NULL); + return NULL; +} + +void *main_tg2mc(void *e) +{ + const Environ *env = (Environ*)e; + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); + int r = 0; + + RconPacket pkt; + TGResp *resp; + const char *allowed_updates[1] = { "message" }; + int updates = 0; + char msg[4097]; + char name[1024]; + char username[512]; + char url[512]; + char uid[32]; + int32_t max_upd_id = 0; +run: + if(*(env->rcon_sd) == 0) + { + sleep(5); + goto run; + } + r = tg_get_updates(curl, + env->tg_api, + &resp, + &updates, + max_upd_id + 1, + 100, + 60, + 1, + allowed_updates); // Ignore errors + if(r) + { + sleep(5); + goto run; + } + TGUpdate *updarr = (TGUpdate*)resp->result; + if(resp->result == NULL) + { + tg_resp_free(resp); + goto run; + } + for(int i = 0; i < updates; i ++) + { + char is_command = 0; + TGUpdate upd = updarr[i]; + if(max_upd_id < upd.update_id) max_upd_id = upd.update_id; + if(upd.message == NULL) continue; + if(upd.message->chat->id != env->tg_chat) continue; + TGMessage *tgmsg = upd.message; + if(tgmsg->text != NULL) + { + if(tgmsg->entities_length == 1) + { + const TGEntity entity = tgmsg->entities[0]; + if(entity.offset == 0 && + !strcmp(entity.type, "bot_command")) + { + if(tgmsg->from->id == env->tg_admin || + !strcmp(tgmsg->text, "/list")) + { + is_command = 1; + strcpy(msg, tgmsg->text + 1); + } + } + } + if(!is_command) + { + int len = strlen(tgmsg->text); + if(len > 100) + { + strncpy(msg, tgmsg->text, 100); + sprintf(msg + 100, "... (Remaining %d characters)", len - 100); + } + else + { + strcpy(msg, tgmsg->text); + } + } + } + else if(tgmsg->document != NULL) + { + sprintf(msg, "%s (Document)", tgmsg->document->file_name); + } + else if(tgmsg->sticker != NULL) + { + sprintf(msg, "%s (Sticker)", tgmsg->sticker->emoji); + } + else if(tgmsg->photo_length > 0) + { + if(tgmsg->caption == NULL) + sprintf(msg, "(%d photo%s)", tgmsg->photo_length, tgmsg->photo_length > 1 ? "s" : ""); + else + sprintf(msg, "%s (%d photo%s)", tgmsg->caption, tgmsg->photo_length, tgmsg->photo_length > 1 ? "s" : ""); + } + + char *cmd = NULL; + if(is_command) + { + cmd = msg; + } + else + { + json_object *req_root = json_object_new_array(); + json_object *obj1 = json_object_new_object(); + json_object_array_add(req_root, obj1); + sprintf(name, "<%s> ", tgmsg->from->first_name); + json_object_object_add(obj1, "text", json_object_new_string(name)); + json_object *hv_event_1 = json_object_new_object(); + json_object_object_add(hv_event_1, "action", json_object_new_string("show_text")); + json_object_object_add(obj1, "hoverEvent", hv_event_1); + json_object *contents_1 = json_object_new_array(); + json_object_object_add(hv_event_1, "contents", contents_1); + json_object *contents_1_text_1 = json_object_new_object(); + if(tgmsg->from->username == NULL) strcpy(username, "(No Username)\n"); + else sprintf(username, "@%s\n", tgmsg->from->username); + json_object_object_add(contents_1_text_1, "text", json_object_new_string(username)); + json_object *contents_1_text_2 = json_object_new_object(); + sprintf(uid, "UID: %d", tgmsg->from->id); + json_object_object_add(contents_1_text_2, "text", json_object_new_string(uid)); + json_object_array_add(contents_1, contents_1_text_1); + json_object_array_add(contents_1, contents_1_text_2); + json_object *obj2 = json_object_new_object(); + json_object_array_add(req_root, obj2); + json_object_object_add(obj2, "text", json_object_new_string(msg)); + json_object *obj3 = json_object_new_object(); + json_object_array_add(req_root, obj3); + json_object_object_add(obj3, "text", json_object_new_string("[t.me]")); + json_object_object_add(obj3, "underlined", json_object_new_boolean(1)); + json_object_object_add(obj3, "color", json_object_new_string("dark_aqua")); + json_object *obj3_click_event = json_object_new_object(); + json_object_object_add(obj3, "clickEvent", obj3_click_event); + json_object_object_add(obj3_click_event, "action", json_object_new_string("open_url")); + sprintf(url, env->tg_link_fmt, tgmsg->message_id); + json_object_object_add(obj3_click_event, "value", json_object_new_string(url)); + json_object *obj3_hover_event = json_object_new_object(); + json_object_object_add(obj3, "hoverEvent", obj3_hover_event); + json_object_object_add(obj3_hover_event, "action", json_object_new_string("show_text")); + json_object *obj3_hv_contents = json_object_new_object(); + json_object_object_add(obj3_hover_event, "contents", obj3_hv_contents); + json_object_object_add(obj3_hv_contents, "text", json_object_new_string("View in Telegram")); + const char *json = json_object_to_json_string(req_root); + cmd = calloc(strlen(json) + 11 /* "tellraw @a " */ + 1, sizeof(char)); + sprintf(cmd, "tellraw @a %s", json); + json_object_put(req_root); + } + r = rcon_build_packet(&pkt, is_command ? tgmsg->message_id : RCON_PID, RCON_EXEC_COMMAND, cmd); // Ignore errors + if(r) + { + fprintf(stderr, "Fail %s\n", cmd); + } + else + { + r = rcon_send_packet(*(env->rcon_sd), &pkt); // Ignore errors + } + if(!is_command) free(cmd); + tg_message_free(upd.message); + upd.message = NULL; + sleep(1); + } + if(!r) + { + tg_update_free(updarr); + tg_resp_free(resp); + } + goto run; +} + +int start_threads(Environ *env) +{ + + int r = 0; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setstacksize(&attr, sizeof(double) * 1000 * 1000 + 1000000); + pthread_t thread_tg2mc; + pthread_t thread_mc2tg; + r = pthread_create(&thread_tg2mc, &attr, main_tg2mc, env); + if(r) + { + fprintf(stderr, _("Cannot start tg2mc thread: %d"), r); + goto cleanup; + } + r = pthread_create(&thread_mc2tg, &attr, main_mc2tg, env); + if(r) + { + fprintf(stderr, _("Cannot start mc2tg thread: %d"), r); + goto cleanup; + } +cleanup: + pthread_attr_destroy(&attr); + return r; +} + +int truncate_updates(Environ *env, CURL *curl) +{ + int r = 0; + TGResp *resp; + int updates = 0; + const char *allowed_updates[1] = { "message" }; + r = tg_get_updates(curl, + env->tg_api, + &resp, + &updates, + -1, + 100, + 0, + 1, + allowed_updates); + if(r) return r; + for(int i = 0; i < updates; i ++) + { + if(((TGUpdate*)resp->result)[i].message != NULL) + tg_message_free(((TGUpdate*)resp->result)[i].message); + } + tg_update_free((TGUpdate*)resp->result); + tg_resp_free(resp); + return r; +} + +int main(int argc, char **argv) +{ + Environ environ; + int r = 0; + r = environ_read(&environ); + if(r) return r; + + curl_global_init(CURL_GLOBAL_ALL); + CURL *curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_callback); + + r = truncate_updates(&environ, curl); + if(r) goto cleanup; + + RconPacket pkt = {0, 0, 0, { 0x00 }}; + r = rcon_build_packet(&pkt, RCON_PID, RCON_AUTHENTICATE, environ.rcon_passwd); + if(r) goto cleanup; + int sd = 0; + char start_thread = 1; +conn: + r = net_connect(environ.rcon_host, environ.rcon_port, &sd); + if(r) goto cleanup; + r = rcon_send_packet(sd, &pkt); + if(r) goto cleanup; + r = rcon_recv_packet(&pkt, sd); + if(r) goto cleanup; + if(pkt.id == -1) + { + fprintf(stderr, _("Incorrect rcon password.\n")); + r = EX_NOPERM; + goto cleanup; + } + environ.rcon_sd = &sd; + + if(start_thread) + { + r = start_threads(&environ); + start_thread = 0; + if(r) goto cleanup; + } + + char id[12]; +read: + r = rcon_recv_packet(&pkt, sd); + if(r) + { + close(sd); + sd = 0; + goto conn; + } + if(pkt.id == -1 || pkt.id == RCON_PID) + goto read; + + TGResp *resp; + sprintf(id, "%d", pkt.id); + r = tg_send_message(curl, + environ.tg_api, + &resp, + environ.tg_chat_raw, + pkt.data, + TG_SEND_MESSAGE_PARSE_MODE_NONE, + id); // Ignore errors + if(!r) + { + tg_message_free((TGMessage*)resp->result); + tg_resp_free(resp); + } + goto read; + + goto cleanup; +cleanup: + if(curl != NULL) curl_easy_cleanup(curl); + curl_global_cleanup(); + if(sd != 0) net_close(sd); + return r; +} diff --git a/mcin/mc_regex.h b/mcin/mc_regex.h new file mode 100644 index 0000000..31f5c0e --- /dev/null +++ b/mcin/mc_regex.h @@ -0,0 +1,55 @@ +#ifndef _MC_REGEX_H +#define _MC_REGEX_H +#define MC_REGEX \ +".* joined the game," \ +".* lost connection: .*," \ +"Stopping server," \ +"Starting minecraft server version .*," \ +"Done \\(.*s\\)! For help type \"help\"," \ +"There are .* of a max of .* players online:.*," \ +".* has made the advancement \\[.*\\]," \ +".* has completed the challenge \\[.*\\]," \ +".* has reached the goal \\[.*\\]," \ +".* was shot by .*," \ +".* was pummeled by .*," \ +".* was pricked to death," \ +".* walked into a cactus whilst trying to escape .*," \ +".* drowned.*," \ +".* experienced kinetic energy.*," \ +".* blew up," \ +".* was blown up by .*," \ +".* was killed by \\[Intentional Game Design\\]," \ +".* hit the ground too hard.*," \ +".* fell from a high place," \ +".* fell off.*," \ +".* fell while.*," \ +".* was squashed by a falling .*," \ +".* went up in flames," \ +".* walked into fire whilst fighting .*," \ +".* burned to death," \ +".* was burnt to a crisp whilst fighting .*," \ +".* went off with a bang.*," \ +".* tried to swim in lava.*," \ +".* was struck by lightning.*," \ +".* discovered the floor was lava," \ +".* walked into danger zone due to .*," \ +".* was killed by magic.*," \ +".* was killed by .* using .*," \ +".* froze to death.*," \ +".* was slain by .*," \ +".* was fireballed by .*," \ +".* was stung to death," \ +".* was shot by a skull from .*," \ +".* starved to death.*," \ +".* suffocated in a wall.*," \ +".* was squished too much," \ +".* was squashed by .*," \ +".* was poked to death by a sweet berry bush.*," \ +".* was killed trying to hurt .*," \ +".* was killed by .* trying to hurt .*," \ +".* was impaled by .*," \ +".* fell out of the world," \ +".* didn't want to live in the same world as.*," \ +".* withered away.*," \ +"<.*> .*" +#endif // _MC_REGEX_H diff --git a/mcin/mcin.c b/mcin/mcin.c new file mode 100644 index 0000000..1430415 --- /dev/null +++ b/mcin/mcin.c @@ -0,0 +1,94 @@ +#include "mc_regex.h" +#include "mcin.h" +#include "../common.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +int mcin_matcher_init(MCINMatcher *out) +{ + int r = 0; + r = regcomp(&out->regex_master, ".*\\[([0-9][0-9]:[0-9][0-9]:[0-9][0-9])\\] \\[(.*)\\/(.*)\\]: (.*)", REG_EXTENDED); + if(r) + { + fprintf(stderr, _("Cannot compile master regex: %d.\n"), r); + return r; + } + int line = 0; + char str[] = MC_REGEX; + char *token; + char *rest = str; + while ((token = strtok_r(rest, ",", &rest))) + { + int l = line++; + regex_t *reg = &out->regex[l]; + r = regcomp(reg, token, REG_EXTENDED); + if(r) + { + fprintf(stderr, _("Cannot compile regex %s: %d.\n"), token, r); + return r; + } + } + return r; +} + +void mcin_matcher_free(MCINMatcher *matcher) +{ + regfree(&matcher->regex_master); + for(int i = 0; i < MCIN_REGEX_COUNT; i ++) + { + regfree(&matcher->regex[i]); + } +} + +char *mcin_matcher_match(MCINMatcher *matcher, const char *str) +{ + regmatch_t pmatch[5]; + if(regexec(&matcher->regex_master, str, 5, pmatch, 0)) return 0; + char *temp_str = calloc(strlen(str) + 1, sizeof(char)); + for(int i = 1 /* Ignore the string itself */; i < 5; i ++) + { + const regmatch_t match = pmatch[i]; + if(match.rm_so == -1) + { + // Shouldn't happen if the string is valid. + free(temp_str); + return NULL; + } + if(i != 2 && i != 3 && i != 4) continue; // We don't care. + int length = match.rm_eo - match.rm_so; + for(int j = 0; j < length; j ++) + { + temp_str[j] = str[match.rm_so + j]; + } + temp_str[length] = '\0'; + if(i == 2) /* Tag */ + { + if(strcmp(temp_str, "Server thread")) + { + free(temp_str); + return NULL; + } + } + if(i == 3) /* Level */ + { + if(strcmp(temp_str, "INFO")) + { + free(temp_str); + return NULL; + } + } + if(i == 4) // Data + break; + } + for(int i = 0; i < MCIN_REGEX_COUNT; i ++) + { + if(!regexec(&matcher->regex[i], str, 0, NULL, 0)) + { + return temp_str; + } + } + free(temp_str); + return NULL; +} diff --git a/mcin/mcin.h b/mcin/mcin.h new file mode 100644 index 0000000..5b6f78c --- /dev/null +++ b/mcin/mcin.h @@ -0,0 +1,17 @@ +#ifndef _MCIN_H +#define _MCIN_H + +#include <regex.h> + +#define MCIN_REGEX_COUNT 51 + +typedef struct mcin_matcher { + regex_t regex_master; + regex_t regex[MCIN_REGEX_COUNT]; +} MCINMatcher; + +int mcin_matcher_init(MCINMatcher *out); +void mcin_matcher_free(MCINMatcher *matcher); +char *mcin_matcher_match(MCINMatcher *matcher, const char *str); + +#endif // _MCIN_H diff --git a/net/curlutils.c b/net/curlutils.c new file mode 100644 index 0000000..69187cc --- /dev/null +++ b/net/curlutils.c @@ -0,0 +1,28 @@ +#include "../common.h" +#include "curlutils.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stddef.h> + +size_t curl_callback(void *ptr, size_t size, size_t nmemb, CURLBody *userp) +{ + size_t newLen = userp->len + size * nmemb; + userp->ptr = realloc(userp->ptr, newLen + 1); + if(userp->ptr == NULL) { + fprintf(stderr, _("Cannot allocate memory.\n")); + return 0; + } + memcpy(userp->ptr + userp->len, ptr, size * nmemb); + userp->ptr[newLen] = '\0'; + userp->len = newLen; + return size * nmemb; +} + +void curlbody_setup(CURLBody *body) +{ + body->len = 0; + body->ptr = calloc(1, sizeof(char)); + body->ptr[0] = '\0'; +} diff --git a/net/curlutils.h b/net/curlutils.h new file mode 100644 index 0000000..1d5a5a8 --- /dev/null +++ b/net/curlutils.h @@ -0,0 +1,14 @@ +#ifndef _CURLUTILS_H +#define _CURLUTILS_H + +#include <stddef.h> + +typedef struct curlbody { + char *ptr; + size_t len; +} CURLBody; + +size_t curl_callback(void *ptr, size_t size, size_t nmemb, CURLBody *userp); +void curlbody_setup(CURLBody *body); + +#endif // _CURLUTILS_H diff --git a/net/net.c b/net/net.c new file mode 100644 index 0000000..d999f8e --- /dev/null +++ b/net/net.c @@ -0,0 +1,60 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi <tiiffi at gmail>. + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#include "net.h" +#include "../common.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sysexits.h> +#include <unistd.h> + +int net_connect(const char *host, const char *port, int *out) +{ + int sd; + struct addrinfo hints; + struct addrinfo *server_info, *p; + memset(&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + int ret = getaddrinfo(host, port, &hints, &server_info); + if(ret) + { + fprintf(stderr, _("Cannot resolve host %s: %s.\n"), host, strerror(ret)); + return EX_IOERR; + } + for (p = server_info; p != NULL; p = p->ai_next) + { + sd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); + if (sd == -1) continue; + ret = connect(sd, p->ai_addr, p->ai_addrlen); + if(ret == -1) + { + net_close(sd); + continue; + } + break; + } + if(p == NULL) + { + fprintf(stderr, _("Cannot connect to %s:%s : %s.\n"), host, port, strerror(errno)); + freeaddrinfo(server_info); + return EX_IOERR; + } + freeaddrinfo(server_info); + *out = sd; + return 0; +} + +void net_close(int sd) +{ + close(sd); +} diff --git a/net/net.h b/net/net.h new file mode 100644 index 0000000..0bf5b62 --- /dev/null +++ b/net/net.h @@ -0,0 +1,12 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi <tiiffi at gmail>. + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#ifndef _NET_H +#define _NET_H + +int net_connect(const char *host, const char *port, int *out); +void net_close(int sd); + +#endif // _NET_H diff --git a/rcon/rcon.c b/rcon/rcon.c new file mode 100644 index 0000000..ea29ee9 --- /dev/null +++ b/rcon/rcon.c @@ -0,0 +1,133 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi <tiiffi at gmail>. + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#include "rcon.h" +#include "../common.h" + +#include <sys/socket.h> +#include <sysexits.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> + +int rcon_send_packet(int sd, RconPacket *packet) +{ + int len; + int total = 0; // bytes we've sent + int bytesleft; // bytes left to send + int ret = -1; + + bytesleft = len = packet->size + sizeof(int); + + while (total < len) + { + ret = send(sd, (char *) packet + total, bytesleft, 0); + if(ret == -1) + { + fprintf(stderr, _("send(): %s.\n"), strerror(errno)); + return EX_IOERR; + } + total += ret; + bytesleft -= ret; + } + + return EX_OK; +} + +int rcon_build_packet(RconPacket *out, int id, int cmd, char *s1) +{ + // size + id + cmd + s1 + s2 NULL terminator + int s1_len = strlen(s1); + if (s1_len > RCON_DATA_BUFFSIZE) + { + fprintf(stderr, _("Warning: Command string too long (%d). Maximum allowed: %d.\n"), s1_len, RCON_DATA_BUFFSIZE); + return EX_DATAERR; + } + + out->size = sizeof(int) * 2 + s1_len + 2; + out->id = id; + out->cmd = cmd; + strncpy(out->data, s1, RCON_DATA_BUFFSIZE); + + return EX_OK; +} + + +void rcon_print_packet(RconPacket *packet) +{ + int i; + + for (i = 0; (unsigned char) packet->data[i] != 0; ++i) + { + if ((unsigned char) packet->data[i] == 0xc2 && (unsigned char) packet->data[i+1] == 0xa7) + { + i+=2; + continue; + } + putchar(packet->data[i]); + } + + // print newline if string has no newline + if (packet->data[i-1] != 10 && packet->data[i-1] != 13) putchar('\n'); +} + +int rcon_recv_packet(RconPacket *out, int sd) +{ + int psize; + + int ret = recv(sd, (char *) &psize, sizeof(int), 0); + + if (ret == 0) + { + fprintf(stderr, _("Connection lost.\n")); + return EX_IOERR; + } + if(ret == -1) + { + fprintf(stderr, _("recv(): %d\n"), errno); + return EX_IOERR; + } + + if (ret != sizeof(int)) + { + fprintf(stderr, _("Error: recv() failed. Invalid packet size (%d).\n"), ret); + return EX_IOERR; + } + + if (psize < 10 || psize > RCON_DATA_BUFFSIZE) + { + fprintf(stderr, _("Warning: invalid packet size (%d). Must over 10 and less than %d.\n"), psize, RCON_DATA_BUFFSIZE); + + if(psize > RCON_DATA_BUFFSIZE || psize < 0) psize = RCON_DATA_BUFFSIZE; + // Former net_clean_incoming. + char tmp[psize]; + ret = recv(sd, tmp, psize, 0); + + if(ret == 0) + { + fprintf(stderr, _("Connection lost.\n")); + } + + return EX_DATAERR; + } + + out->size = psize; + + int received = 0; + while (received < psize) + { + ret = recv(sd, (char *) out + sizeof(int) + received, psize - received, 0); + if (ret == 0) // connection closed before completing receving + { + fprintf(stderr, _("Connection lost.\n")); + return EX_IOERR; + } + + received += ret; + } + + return EX_OK; +} + diff --git a/rcon/rcon.h b/rcon/rcon.h new file mode 100644 index 0000000..fc7fd45 --- /dev/null +++ b/rcon/rcon.h @@ -0,0 +1,29 @@ +/* + * Adopted from mcrcon, Copyright (c) 2012-2020, Tiiffi <tiiffi at gmail>. + * https://github.com/Tiiffi/mcrcon/tree/b02201d689b3032bc681b28f175fd3d83d167293 + */ + +#ifndef _RCON_H +#define _RCON_H + +#define RCON_DATA_BUFFSIZE 4096 +#define RCON_EXEC_COMMAND 2 +#define RCON_AUTHENTICATE 3 +#define RCON_RESPONSEVALUE 0 +#define RCON_AUTH_RESPONSE 2 +#define RCON_PID 0xBADC0DE + +typedef struct rc_packet { + int size; + int id; + int cmd; + char data[RCON_DATA_BUFFSIZE]; + // ignoring string2 for now +} RconPacket; + +int rcon_send_packet(int sd, RconPacket *packet); +int rcon_build_packet(RconPacket *out, int id, int cmd, char *s1); +void rcon_print_packet(RconPacket *packet); +int rcon_recv_packet(RconPacket *out, int sd); + +#endif // _RCON_H @@ -0,0 +1,412 @@ +#include "tg.h" +#include "../net/curlutils.h" +#include "../common.h" + +#include <string.h> +#include <stdlib.h> +#include <sysexits.h> +#include <json-c/json_object.h> +#include <json-c/json_tokener.h> +#include <curl/curl.h> + +void _tg_entity_parse(TGEntity *entity, json_object *json) +{ + entity->type = NULL; + entity->offset = 0; + entity->length = 0; + + json_object *obj; + if(json_object_object_get_ex(json, "type", &obj)) + entity->type = json_object_get_string(obj); + if(json_object_object_get_ex(json, "offset", &obj)) + entity->offset = json_object_get_int(obj); + if(json_object_object_get_ex(json, "length", &obj)) + entity->length = json_object_get_int(obj); +} + +void _tg_photo_parse(TGPhoto *photo, json_object *json) +{ +} + +TGSticker *_tg_sticker_parse(json_object *json) +{ + TGSticker *sticker = malloc(sizeof(TGSticker)); + sticker->emoji = NULL; + + json_object *obj; + if(json_object_object_get_ex(json, "emoji", &obj)) + sticker->emoji = json_object_get_string(obj); + + return sticker; +} + +TGDocument *_tg_document_parse(json_object *json) +{ + TGDocument *document = malloc(sizeof(TGDocument)); + document->file_name = NULL; + + json_object *obj; + if(json_object_object_get_ex(json, "file_name", &obj)) + document->file_name = json_object_get_string(obj); + + return document; +} +TGChat *_tg_chat_parse(json_object *json) +{ + TGChat *chat = malloc(sizeof(TGChat)); + chat->id = 0; + + json_object *obj; + if(json_object_object_get_ex(json, "id", &obj)) + chat->id = json_object_get_int64(obj); + + return chat; +} + +TGUser *_tg_user_parse(json_object *json) +{ + TGUser *user = malloc(sizeof(TGUser)); + user->id = 0; + user->first_name = NULL; + user->last_name = NULL; + user->username = NULL; + + json_object *obj; + if(json_object_object_get_ex(json, "id", &obj)) + user->id = json_object_get_int(obj); + if(json_object_object_get_ex(json, "first_name", &obj)) + user->first_name = json_object_get_string(obj); + if(json_object_object_get_ex(json, "last_name", &obj)) + user->last_name = json_object_get_string(obj); + if(json_object_object_get_ex(json, "username", &obj)) + user->username = json_object_get_string(obj); + + return user; +} + +TGMessage *_tg_message_parse(json_object *json) +{ + TGMessage *message = malloc(sizeof(TGMessage)); + message->message_id = 0; + message->from = NULL; + message->chat = NULL; + message->text = NULL; + message->sticker = NULL; + message->document = NULL; + message->photo_length = 0; + message->photo = NULL; + message->caption = NULL; + message->entities_length = 0; + message->entities = NULL; + + json_object *obj; + if(json_object_object_get_ex(json, "message_id", &obj)) + message->message_id = json_object_get_int(obj); + if(json_object_object_get_ex(json, "from", &obj)) + message->from = _tg_user_parse(obj); + if(json_object_object_get_ex(json, "chat", &obj)) + message->chat = _tg_chat_parse(obj); + if(json_object_object_get_ex(json, "text", &obj)) + message->text = json_object_get_string(obj); + if(json_object_object_get_ex(json, "sticker", &obj)) + message->sticker = _tg_sticker_parse(obj); + if(json_object_object_get_ex(json, "document", &obj)) + message->document = _tg_document_parse(obj); + if(json_object_object_get_ex(json, "photo", &obj)) + { + int length = json_object_array_length(obj); + message->photo_length = length; + message->photo = calloc(length, sizeof(TGPhoto)); + for(int i = 0; i < length; i ++) + { + json_object *childobj = json_object_array_get_idx(obj, i); + if(childobj == NULL) continue; + _tg_photo_parse(&message->photo[i], childobj); + } + } + if(json_object_object_get_ex(json, "caption", &obj)) + message->caption = json_object_get_string(obj); + if(json_object_object_get_ex(json, "entities", &obj)) + { + int length = json_object_array_length(obj); + message->entities_length = length; + message->entities = calloc(length, sizeof(TGEntity)); + for(int i = 0; i < length; i ++) + { + json_object *childobj = json_object_array_get_idx(obj, i); + if(childobj == NULL) continue; + _tg_entity_parse(&message->entities[i], childobj); + } + } + + return message; +} + +void _tg_update_parse(TGUpdate *update, json_object *json) +{ + update->update_id = 0; + update->message = NULL; + + json_object *obj; + if(json_object_object_get_ex(json, "update_id", &obj)) + update->update_id = json_object_get_int(obj); + if(json_object_object_get_ex(json, "message", &obj)) + update->message = _tg_message_parse(obj); +} + +#define _TG_BASE_URL "https://api.telegram.org/bot" + +int _tg_api_call( + CURL *curl, + const char *tg_api, + const char parse_body, + TGResp **out, + const char *method, + const int query_arg_count, + const char **query_arg_keys, + const char **query_arg_values) +{ + curl_mime *form = curl_mime_init(curl); + curl_mimepart *field = NULL; + for(int i = 0; i < query_arg_count; i ++) + { + if(query_arg_keys[i] == NULL || + query_arg_values[i] == NULL) continue; + field = curl_mime_addpart(form); + curl_mime_name(field, query_arg_keys[i]); + curl_mime_data(field, query_arg_values[i], CURL_ZERO_TERMINATED); + } + int url_size = strlen(_TG_BASE_URL) + strlen(tg_api) + 1 /* / */ + + strlen(method) + 1 /* \0 */; + char *url = calloc(url_size, sizeof(char)); + strcpy(url, _TG_BASE_URL); + strcat(url, tg_api); + strcat(url, "/"); + strcat(url, method); + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_POST, 1L); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, form); + CURLBody body; + curlbody_setup(&body); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body); + int r = 0; + r = curl_easy_perform(curl); + if(r != CURLE_OK) goto cleanup; + long http_code = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); + if(http_code != 200) + { + fprintf(stderr, _("Error while %s(): %lu (%s)\n"), method, http_code, body.ptr); + r = EX_IOERR; + goto cleanup; + } + if(parse_body) + { + json_object *json = json_tokener_parse(body.ptr); + json_object *obj; + TGResp *resp = malloc(sizeof(TGResp)); + resp->json = json; + resp->result = NULL; + if(json_object_object_get_ex(json, "result", &obj)) + resp->result_json = obj; + *out = resp; + } +cleanup: + free(url); + free(body.ptr); + curl_easy_setopt(curl, CURLOPT_MIMEPOST, NULL); + curl_mime_free(form); + return r; +} + +int tg_send_message( + const CURL *curl, + const char *tg_key, + TGResp **out, + const char *chat_id, + const char *text, + const int parse_mode, + const char *reply_to_message_id) +{ + TGResp *resp; + int r; + const char *q_arg[5] = { + "chat_id", + "text", + "parse_mode", + "allow_sending_without_reply", + "reply_to_message_id" + }; + char *parse_mode_raw; + switch(parse_mode) + { + case TG_SEND_MESSAGE_PARSE_MODE_NONE: + q_arg[2] = NULL; + parse_mode_raw = NULL; + break; + case TG_SEND_MESSAGE_PARSE_MODE_HTML: + parse_mode_raw = "HTML"; + break; + case TG_SEND_MESSAGE_PARSE_MODE_MARKDOWN: + parse_mode_raw = "Markdown"; + break; + case TG_SEND_MESSAGE_PARSE_MODE_MARKDOWN_V2: + parse_mode_raw = "MarkdownV2"; + break; + default: + parse_mode_raw = "HTML"; + break; + } + const char *q_val[5] = { + chat_id, + text, + parse_mode_raw, + "True", + reply_to_message_id + }; + r = _tg_api_call((CURL*)curl, + tg_key, + 1, + &resp, + "sendMessage", + 5, + q_arg, + q_val); + if(r) return r; + resp->result = _tg_message_parse(resp->result_json); + *out = resp; + return r; +} + +int tg_get_updates( + const CURL *curl, + const char *tg_key, + TGResp **out, + int *out_updates, + const int offset, + const int limit, + const int timeout, + const int allowed_updates_count, + const char **allowed_updates) +{ + TGResp *resp; + char parse_body = 1; + int r; + const char *q_arg[4] = { + "offset", + "limit", + "timeout", + "allowed_updates" + }; + char offset_str[12]; + sprintf(offset_str, "%d", offset); + char limit_str[12]; + sprintf(limit_str, "%d", limit); + char timeout_str[12]; + sprintf(timeout_str, "%d", timeout); + json_object *allowed_updates_arr = json_object_new_array(); + for(int i = 0; i < allowed_updates_count; i ++) + { + json_object *obj = json_object_new_string(allowed_updates[i]); + json_object_array_add(allowed_updates_arr, obj); + } + const char *q_val[4] = { + offset_str, + limit_str, + timeout_str, + json_object_to_json_string(allowed_updates_arr) + }; + r = _tg_api_call((CURL*)curl, + tg_key, + parse_body, + &resp, + "getUpdates", + 4, + q_arg, + q_val); + json_object_put(allowed_updates_arr); + if(r) return r; + if(parse_body) + { + json_object *arr = resp->result_json; + int length = json_object_array_length(arr); + TGUpdate *updarr = calloc(length, sizeof(TGUpdate)); + for(int i = 0; i < length; i ++) + { + json_object *obj = json_object_array_get_idx(arr, i); + if(obj == NULL) continue; + _tg_update_parse(&updarr[i], obj); + } + resp->result = updarr; + *out = resp; + *out_updates = length; + } + return r; +} + +void tg_message_free(TGMessage *obj) +{ + if(obj->from != NULL) + { + tg_user_free(obj->from); + obj->from = NULL; + } + if(obj->chat != NULL) + { + tg_chat_free(obj->chat); + obj->chat = NULL; + } + if(obj->document != NULL) + { + tg_document_free(obj->document); + obj->document = NULL; + } + if(obj->sticker != NULL) + { + tg_sticker_free(obj->sticker); + obj->sticker = NULL; + } + if(obj->photo != NULL) + { + free(obj->photo); + obj->photo = NULL; + } + if(obj->entities != NULL) + { + free(obj->entities); + obj->entities = NULL; + } + + free(obj); +} + +void tg_user_free(TGUser *obj) +{ + free(obj); +} + +void tg_chat_free(TGChat *obj) +{ + free(obj); +} + +void tg_resp_free(TGResp *obj) +{ + json_object_put(obj->json); + free(obj); +} + +void tg_update_free(TGUpdate *arr) +{ + free(arr); +} + +void tg_sticker_free(TGSticker *obj) +{ + free(obj); +} + +void tg_document_free(TGDocument *obj) +{ + free(obj); +} @@ -0,0 +1,95 @@ +#ifndef _TG_H +#define _TG_H + +#include "../environ.h" +#include <json-c/json_object.h> +#include <curl/curl.h> + +#define TG_SEND_MESSAGE_PARSE_MODE_NONE 0 +#define TG_SEND_MESSAGE_PARSE_MODE_HTML 1 +#define TG_SEND_MESSAGE_PARSE_MODE_MARKDOWN 2 +#define TG_SEND_MESSAGE_PARSE_MODE_MARKDOWN_V2 3 + +typedef struct tg_entity { + const char *type; + int32_t offset; + int32_t length; +} TGEntity; + +typedef struct tg_resp { + json_object *json; + void *result; + json_object *result_json; // Ownership: json +} TGResp; + +typedef struct tg_user { + int32_t id; + const char *first_name; // Ownership: json + const char *last_name; // Ownership: json + const char *username; // Ownership: json +} TGUser; + +typedef struct tg_chat { + int64_t id; +} TGChat; + +typedef struct tg_sticker { + const char *emoji; // Ownership: json +} TGSticker; + +typedef struct tg_document { + const char *file_name; // Ownership: json +} TGDocument; + +typedef struct tg_photo { +} TGPhoto; + +typedef struct tg_message { + int32_t message_id; + TGUser *from; + TGChat *chat; + const char *text; + TGDocument *document; + TGSticker *sticker; + int photo_length; + TGPhoto *photo; // Array. + const char *caption; + int entities_length; + TGEntity *entities; // Array. +} TGMessage; + +typedef struct tg_update { + int32_t update_id; + TGMessage *message; +} TGUpdate; + +int tg_send_message( + const CURL *curl, + const char *tg_key, + TGResp **out, + const char *chat_id, + const char *text, + const int parse_mode, + const char *reply_to_message_id); + +int tg_get_updates( + const CURL *curl, + const char *tg_key, + TGResp **out, // Array of structs. + int *out_updates, + const int offset, + const int limit, + const int timeout, + const int allowed_updates_count, + const char **allowed_updates); + +void tg_update_free(TGUpdate *arr); +void tg_message_free(TGMessage *obj); +void tg_user_free(TGUser *obj); +void tg_chat_free(TGChat *obj); +void tg_resp_free(TGResp *obj); +void tg_sticker_free(TGSticker *obj); +void tg_document_free(TGDocument *obj); +void tg_photo_free(TGPhoto *obj); + +#endif // _TG_H |