From 242614fc68370740daac608b3913b42ce336faee Mon Sep 17 00:00:00 2001 From: eta Date: Thu, 28 May 2020 18:33:03 +0100 Subject: [PATCH] README, LICENSE, and general user friendliness --- LICENSE | 675 ++++++++++++++++++++++++++++++++++++++++ README.md | 147 +++++++++ doc/mod_http_upload.lua | 434 ++++++++++++++++++++++++++ logo.png | Bin 0 -> 23110 bytes stuff.lisp | 31 +- whatsxmpp.asd | 2 +- 6 files changed, 1278 insertions(+), 11 deletions(-) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 doc/mod_http_upload.lua create mode 100644 logo.png diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..53d1f3d --- /dev/null +++ b/LICENSE @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. 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 +them 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 prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. 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. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey 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; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + 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. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +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. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + 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 +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 3 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 . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program 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, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU 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. But first, please read +. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..05f061c --- /dev/null +++ b/README.md @@ -0,0 +1,147 @@ +# whatsxmpp + +[![GNU AGPLv3 licensed](https://www.gnu.org/graphics/agplv3-155x51.png)](https://www.gnu.org/licenses/agpl-3.0.en.html) +[![XMPP chatroom: whatsxmpp@conf.theta.eu.org](https://inverse.chat/badge.svg?room=whatsxmpp@conf.theta.eu.org)](xmpp:whatsxmpp@conf.theta.eu.org?join) +![Maintenance](https://img.shields.io/maintenance/yes/2020.svg) + +![Lisp warning](http://www.lisperati.com/lisplogo_warning_256.png) + +A WhatsApp Web transport for the [Extensible Messaging and Presence Protocol (XMPP)](https://xmpp.org/), otherwise known as Jabber. (alpha!) + +*This is the spiritual successor of [sms-irc](https://git.theta.eu.org/eta/sms-irc), a similar project that works with IRC instead of XMPP.* + +## What is this? + +This is a multi-user transport for WhatsApp, using the [whatscl](https://git.theta.eu.org/eta/whatscl** +library for Common Lisp. By scanning a QR code generated by the bridge with the WhatsApp +app on your phone, you can send and receive messages and media with your Jabber ID. + +**Note:** You currently need an XMPP server of your own to try this. It's only been tested +with [prosody 0.11](https://prosody.im/) as of the time of writing - and there are some +additional caveats: take a look at the requirements list. + +## What works? + +- Sending private messages/DMs both ways +- *Basic* support for MUCs +- Magically populating your roster using [XEP-0144: Roster Item Exchange](https://xmpp.org/extensions/xep-0144.html) +- Downloading/decrypting media from WhatsApp and uploading it to your XEP-0363 server +- Avatars +- Read receipts +- Status text +- Typing notifications / chat state + +## What doesn't yet? + +- [XEP-0313: Message Archive Management](https://xmpp.org/extensions/xep-0313.html) in MUCs (DMs should be done by your server) +- Support for users joining and leaving MUCs +- Support for the topic changing in MUCs +- Uploading media to WhatsApp (currently, it just comes through as a link) +- Probably other stuff + +## What you'll need + +- An XMPP server (we recommend [prosody](https://prosody.im/), but it might also work with ejabberd; let us know!) + - You need to set up a new *external component* for the bridge ([see prosody doc](https://prosody.im/doc/components)). + - In addition, you **must** configure an [XEP-0363 (HTTP File Upload)](https://xmpp.org/extensions/xep-0363.html) component. ([see prosody doc](https://modules.prosody.im/mod_http_upload.html)) + - **WARNING:** Prosody's `mod_http_upload` does not allow the bridge to use it, as of the time of writing (2020-05-28). You will need to replace `mod_http_upload.lua` in your community modules directory with `doc/mod_http_upload.lua` from this repository for it to work. +- An installation of [Docker](https://www.docker.com/) + - You *can* try and run the bridge without Docker. However, we really don't recommend it, especially if you aren't familiar with Common Lisp. + - Ask in the support MUC (link at the top of this file) if you want to do this. +- [SQLite](https://www.sqlite.org/) installed (specifically the `sqlite3` command). + +## Instructions + +### Step 1: configure your XMPP server + +Make sure you've followed the links above to set up XEP-0363 and an external component for the bridge. With prosody, your config might look something like: + +``` +Component "upload.capulet.lit" "http_upload" + http_upload_file_size_limit = 104857600 + +Component "whatsapp.capulet.lit" + component_secret = "juliet" +``` + + +### Step 2: set up the database and storage for the bridge + +I'm going to assume you want to store bridge data at the path `/wx`. Replace this path +with wherever you actually want to put the data in the commands below. + +Set up the database schema: + +``` +$ sqlite3 /wx/data.sqlite3 < ./schema.sql +``` + +Then, configure the bridge -- replacing the values below with the actual values: + +``` +$ sqlite3 /wx/data.sqlite3 +SQLite version 3.31.1 2020-01-27 19:55:54 +Enter ".help" for usage hints. +sqlite> INSERT INTO configuration (rev, server, port, component_name, shared_secret, upload_component_name) VALUES (1, "capulet.lit", 5347, "whatsapp.capulet.lit", "juliet", "upload.capulet.lit"); +sqlite> .quit +``` + +A few things to note here: + +- The `port` is whatever port your XMPP server accepts incoming component connections on (*not* client-to-server or server-to-server connections!). For prosody, the default is 5347. +- The `component_name` is whatever you specified in the prosody config for this component. +- The `shared_secret` is the same as the `component_secret`. +- The `upload_component_name` is the name of the XEP-0363 HTTP Upload component. + +### Step 3: run the bridge + +You can build the Docker image yourself from the `Dockerfile` in the repo, or you can just +use my hosted copy. (Don't download it a ton you guys, I pay for this stuff.) + +You'll want to pass through the directory (`/wx`, or whatever it is) where you created +the database as a Docker volume, so the container can access it. Then, supply the path +to the database as the first argument to the container. + +The hosted image is called `eu.gcr.io/etainfra/whatsxmpp`. Using the `docker` CLI, +you might run the bridge like so: + +``` +docker run \ + --name whatsxmpp \ + --restart always \ + -v /wx:/data \ + eu.gcr.io/etainfra/whatsxmpp + /data/data.sqlite3 + +``` + +Consult the [Docker reference](https://docs.docker.com/engine/reference/run/) for more details +on this step, including how to run the image in the background and more! + +If you see `Connection complete! \o/` in the logs, it's working! + +### Step 4: set up the WhatsApp Web part + +You'll interact with the bridge by talking to `admin@whatsapp.capulet.lit` (well, +the last part of that JID will be different depending on your setup). Send this user +`help` to check that the bridge is working (you should get some help text). + +To set it up, have your phone at the ready to scan the QR code (Menu -> WhatsApp Web). +Then, send `register` to the admin user, and scan the QR code you're given. + +(Did it break at this part and give you a nasty error? Check your XEP-0363 HTTP Upload +service is working correctly, and allows the bridge to use it as described above!** + +You should then receive a crapton of presence subscription requests and MUC invites +for everyone you are remotely related to on WhatsApp, and for all the WhatsApp groups +you're in, and the bridge is done! + +**Tip:** If your client supports [XEP-0144: Roster Item Exchange](https://xmpp.org/extensions/xep-0144.html) (Gajim on desktop is good for this), send `getroster` to the admin user +to pop up a window where you can insert all your WhatsApp contacts in one go! + +## Support + +[![XMPP chatroom: whatsxmpp@conf.theta.eu.org](https://inverse.chat/badge.svg?room=whatsxmpp@conf.theta.eu.org)](xmpp:whatsxmpp@conf.theta.eu.org?join) + +Come join us in [whatsxmpp@conf.theta.eu.org](xmpp:whatsxmpp@conf.theta.eu.org?join) if you +have questions or issues using the bridge. diff --git a/doc/mod_http_upload.lua b/doc/mod_http_upload.lua new file mode 100644 index 0000000..36b0c55 --- /dev/null +++ b/doc/mod_http_upload.lua @@ -0,0 +1,434 @@ +-- mod_http_upload +-- +-- Copyright (C) 2015-2018 Kim Alvefur +-- +-- This file is MIT/X11 licensed. +-- +-- Implementation of HTTP Upload file transfer mechanism used by Conversations +-- + +-- imports +local st = require"util.stanza"; +local lfs = require"lfs"; +local url = require "socket.url"; +local dataform = require "util.dataforms".new; +local datamanager = require "util.datamanager"; +local array = require "util.array"; +local t_concat = table.concat; +local t_insert = table.insert; +local s_upper = string.upper; +local httpserver = require "net.http.server"; +local have_id, id = pcall(require, "util.id"); -- Only available in 0.10+ +local uuid = require"util.uuid".generate; +if have_id then + uuid = id.medium; +end + +local function join_path(...) -- COMPAT util.path was added in 0.10 + return table.concat({ ... }, package.config:sub(1,1)); +end + +-- config +local file_size_limit = module:get_option_number(module.name .. "_file_size_limit", 1024 * 1024); -- 1 MB +local quota = module:get_option_number(module.name .. "_quota"); +local max_age = module:get_option_number(module.name .. "_expire_after"); + +--- sanity +local parser_body_limit = module:context("*"):get_option_number("http_max_content_size", 10*1024*1024); +if file_size_limit > parser_body_limit then + module:log("warn", "%s_file_size_limit exceeds HTTP parser limit on body size, capping file size to %d B", + module.name, parser_body_limit); + file_size_limit = parser_body_limit; +end + +-- depends +module:depends("http"); +module:depends("disco"); + +local http_files; + +if not pcall(function () + http_files = require "net.http.files"; +end) then + http_files = module:depends"http_files"; +end + +local mime_map = module:shared("/*/http_files/mime").types; +if not mime_map then + mime_map = { + html = "text/html", htm = "text/html", + xml = "application/xml", + txt = "text/plain", + css = "text/css", + js = "application/javascript", + png = "image/png", + gif = "image/gif", + jpeg = "image/jpeg", jpg = "image/jpeg", + svg = "image/svg+xml", + }; + module:shared("/*/http_files/mime").types = mime_map; + + local mime_types, err = io.open(module:get_option_path("mime_types_file", "/etc/mime.types", "config"), "r"); + if mime_types then + local mime_data = mime_types:read("*a"); + mime_types:close(); + setmetatable(mime_map, { + __index = function(t, ext) + local typ = mime_data:match("\n(%S+)[^\n]*%s"..(ext:lower()).."%s") or "application/octet-stream"; + t[ext] = typ; + return typ; + end + }); + end +end + +-- namespaces +local namespace = "urn:xmpp:http:upload:0"; +local legacy_namespace = "urn:xmpp:http:upload"; + +-- identity and feature advertising +module:add_identity("store", "file", module:get_option_string("name", "HTTP File Upload")); +module:add_feature(namespace); +module:add_feature(legacy_namespace); + +module:add_extension(dataform { + { name = "FORM_TYPE", type = "hidden", value = namespace }, + { name = "max-file-size", type = "text-single" }, +}:form({ ["max-file-size"] = ("%d"):format(file_size_limit) }, "result")); + +module:add_extension(dataform { + { name = "FORM_TYPE", type = "hidden", value = legacy_namespace }, + { name = "max-file-size", type = "text-single" }, +}:form({ ["max-file-size"] = ("%d"):format(file_size_limit) }, "result")); + +-- state +local pending_slots = module:shared("upload_slots"); + +local storage_path = module:get_option_string(module.name .. "_path", join_path(prosody.paths.data, module.name)); +lfs.mkdir(storage_path); + +local function expire(username, host) + if not max_age then return true; end + local uploads, err = datamanager.list_load(username, host, module.name); + if err then return false, err; end + if not uploads then return true; end + uploads = array(uploads); + local expiry = os.time() - max_age; + local upload_window = os.time() - 900; + local before = #uploads; + uploads:filter(function (item) + local filename = item.filename; + if item.dir then + filename = join_path(storage_path, item.dir, item.filename); + end + if item.time < expiry then + local deleted, whynot = os.remove(filename); + if not deleted then + module:log("warn", "Could not delete expired upload %s: %s", filename, whynot or "delete failed"); + end + os.remove(filename:match("^(.*)[/\\]")); + return false; + elseif item.time < upload_window and not lfs.attributes(filename) then + return false; -- File was not uploaded or has been deleted since + end + return true; + end); + local after = #uploads; + if before == after then return true end -- nothing changed, skip write + return datamanager.list_store(username, host, module.name, uploads); +end + +local function check_quota(username, host, does_it_fit) + if not quota then return true; end + local uploads, err = datamanager.list_load(username, host, module.name); + if err then + return false; + elseif not uploads then + if does_it_fit then + return does_it_fit < quota; + end + return true; + end + local sum = does_it_fit or 0; + for _, item in ipairs(uploads) do + sum = sum + item.size; + end + return sum < quota; +end + +local measure_slot = function () end +if module.measure then + -- COMPAT 0.9 + -- module:measure was added in 0.10 + measure_slot = module:measure("slot", "sizes"); +end + +local function handle_request(origin, stanza, xmlns, filename, filesize) + local username, host = origin.username, origin.host; + -- local clients only + if origin.type ~= "c2s" and origin.type ~= "component" then + module:log("debug", "Request for upload slot from a %s", origin.type); + return nil, st.error_reply(stanza, "cancel", "not-authorized"); + end + -- validate + if not filename or filename:find("/") then + module:log("debug", "Filename %q not allowed", filename or ""); + return nil, st.error_reply(stanza, "modify", "bad-request", "Invalid filename"); + end + expire(username, host); + if not filesize then + module:log("debug", "Missing file size"); + return nil, st.error_reply(stanza, "modify", "bad-request", "Missing or invalid file size"); + elseif filesize > file_size_limit then + module:log("debug", "File too large (%d > %d)", filesize, file_size_limit); + return nil, st.error_reply(stanza, "modify", "not-acceptable", "File too large") + :tag("file-too-large", {xmlns=xmlns}) + :tag("max-file-size"):text(("%d"):format(file_size_limit)); + elseif not check_quota(username, host, filesize) then + module:log("debug", "Upload of %dB by %s would exceed quota", filesize, origin.full_jid); + return nil, st.error_reply(stanza, "wait", "resource-constraint", "Quota reached"); + end + + local random_dir = uuid(); + local created, err = lfs.mkdir(join_path(storage_path, random_dir)); + + if not created then + module:log("error", "Could not create directory for slot: %s", err); + return nil, st.error_reply(stanza, "wait", "internal-server-error"); + end + + local ok = datamanager.list_append(username, host, module.name, { + filename = filename, dir = random_dir, size = filesize, time = os.time() }); + + if not ok then + return nil, st.error_reply(stanza, "wait", "internal-server-error"); + end + + local slot = random_dir.."/"..filename; + pending_slots[slot] = stanza.attr.from; + + module:add_timer(900, function() + pending_slots[slot] = nil; + end); + + measure_slot(filesize); + + origin.log("debug", "Given upload slot %q", slot); + + local base_url = module:http_url(); + local slot_url = url.parse(base_url); + slot_url.path = url.parse_path(slot_url.path or "/"); + t_insert(slot_url.path, random_dir); + t_insert(slot_url.path, filename); + slot_url.path.is_directory = false; + slot_url.path = url.build_path(slot_url.path); + slot_url = url.build(slot_url); + return slot_url; +end + +-- hooks +module:hook("iq/host/"..namespace..":request", function (event) + local stanza, origin = event.stanza, event.origin; + local request = stanza.tags[1]; + local filename = request.attr.filename; + local filesize = tonumber(request.attr.size); + + local slot_url, err = handle_request(origin, stanza, namespace, filename, filesize); + if not slot_url then + origin.send(err); + return true; + end + + local reply = st.reply(stanza) + :tag("slot", { xmlns = namespace }) + :tag("get", { url = slot_url }):up() + :tag("put", { url = slot_url }):up() + :up(); + origin.send(reply); + return true; +end); + +module:hook("iq/host/"..legacy_namespace..":request", function (event) + local stanza, origin = event.stanza, event.origin; + local request = stanza.tags[1]; + local filename = request:get_child_text("filename"); + local filesize = tonumber(request:get_child_text("size")); + + local slot_url, err = handle_request(origin, stanza, legacy_namespace, filename, filesize); + if not slot_url then + origin.send(err); + return true; + end + + local reply = st.reply(stanza) + :tag("slot", { xmlns = legacy_namespace }) + :tag("get"):text(slot_url):up() + :tag("put"):text(slot_url):up() + :up(); + origin.send(reply); + return true; +end); + +local measure_upload = function () end +if module.measure then + -- COMPAT 0.9 + -- module:measure was added in 0.10 + measure_upload = module:measure("upload", "sizes"); +end + +-- http service +local function set_cross_domain_headers(response) + local headers = response.headers; + headers.access_control_allow_methods = "GET, PUT, OPTIONS"; + headers.access_control_allow_headers = "Content-Type"; + headers.access_control_max_age = "7200"; + headers.access_control_allow_origin = response.request.headers.origin or "*"; + return response; +end + +local function upload_data(event, path) + set_cross_domain_headers(event.response); + + local uploader = pending_slots[path]; + if not uploader then + module:log("warn", "Attempt to upload to unknown slot %q", path); + return; -- 404 + end + local random_dir, filename = path:match("^([^/]+)/([^/]+)$"); + if not random_dir then + module:log("warn", "Invalid file path %q", path); + return 400; + end + if #event.request.body > file_size_limit then + module:log("warn", "Uploaded file too large %d bytes", #event.request.body); + return 400; + end + pending_slots[path] = nil; + local full_filename = join_path(storage_path, random_dir, filename); + if lfs.attributes(full_filename) then + module:log("warn", "File %s exists already, not replacing it", full_filename); + return 409; + end + local fh, ferr = io.open(full_filename, "w"); + if not fh then + module:log("error", "Could not open file %s for upload: %s", full_filename, ferr); + return 500; + end + local ok, err = fh:write(event.request.body); + if not ok then + module:log("error", "Could not write to file %s for upload: %s", full_filename, err); + os.remove(full_filename); + return 500; + end + ok, err = fh:close(); + if not ok then + module:log("error", "Could not write to file %s for upload: %s", full_filename, err); + os.remove(full_filename); + return 500; + end + measure_upload(#event.request.body); + module:log("info", "File uploaded by %s to slot %s", uploader, random_dir); + return 201; +end + +-- FIXME Duplicated from net.http.server + +local codes = require "net.http.codes"; +local headerfix = setmetatable({}, { + __index = function(t, k) + local v = "\r\n"..k:gsub("_", "-"):gsub("%f[%w].", s_upper)..": "; + t[k] = v; + return v; + end +}); + +local function send_response_sans_body(response, body) + if response.finished then return; end + response.finished = true; + response.conn._http_open_response = nil; + + local status_line = "HTTP/"..response.request.httpversion.." "..(response.status or codes[response.status_code]); + local headers = response.headers; + if type(body) == "string" then + headers.content_length = #body; + elseif io.type(body) == "file" then + headers.content_length = body:seek("end"); + body:close(); + end + + local output = { status_line }; + for k,v in pairs(headers) do + t_insert(output, headerfix[k]..v); + end + t_insert(output, "\r\n\r\n"); + -- Here we *don't* add the body to the output + + response.conn:write(t_concat(output)); + if response.on_destroy then + response:on_destroy(); + response.on_destroy = nil; + end + if response.persistent then + response:finish_cb(); + else + response.conn:close(); + end +end + +local serve_uploaded_files = http_files.serve({ path = storage_path, mime_map = mime_map }); + +local function serve_head(event, path) + set_cross_domain_headers(event.response); + event.response.send = send_response_sans_body; + event.response.send_file = send_response_sans_body; + return serve_uploaded_files(event, path); +end + +if httpserver.send_head_response then + -- Prosody will take care of HEAD requests since hg:3f4c25425589 + serve_head = nil +end + +local function serve_hello(event) + event.response.headers.content_type = "text/html;charset=utf-8" + return "\n

Hello from mod_"..module.name.." on "..module.host.."!

\n"; +end + +module:provides("http", { + route = { + ["GET"] = serve_hello; + ["GET /"] = serve_hello; + ["GET /*"] = serve_uploaded_files; + ["HEAD /*"] = serve_head; + ["PUT /*"] = upload_data; + + ["OPTIONS /*"] = function (event) + set_cross_domain_headers(event.response); + return ""; + end; + }; +}); + +module:log("info", "URL: <%s> - Ensure this can be reached by users", module:http_url()); +module:log("info", "Storage path: '%s'", storage_path); + +function module.command(args) + datamanager = require "core.storagemanager".olddm; + -- luacheck: ignore 421/user + if args[1] == "expire" then + local split = require "util.jid".prepped_split; + for i = 2, #args do + local user, host = split(args[i]); + if user then + assert(expire(user, host)); + else + for user in assert(datamanager.users(host, module.name, "list")) do + expire(user, host); + end + end + end + end +end + + + diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..43d6927ed45f29d28640dbedf699df492d60986a GIT binary patch literal 23110 zcmeFY`9IX(7dU=rVaC4iLt`t-PE5AORuV!bsWh@gk+CEqJ;z=oB1=h@NQ+X*64h8j z2!%`~#;yoiLYBUFyxyPBKk)ta^Ft4F=H7Ge+3z{$PFL(~_wsN`asmK&_F0-c0Dz^k z{^8l-H<7^wUjZNlUOeO!j zDu11{Qn=dj_ICvP-GPp!rz=Yt8ymkum*!1J9yGZ9{YzgnjD0^pdYvzPK>qQ--3#lJ zHyLl$78cQB5&v#XKOR_m8a1-A9BMrBl?KwY4DGGaz6L% zcfSKV`DI*sR+>4&H!km9J&pY_Z?w|BmZ&`cCi&;8k-m(>-#6l@72k^k*ZcoT<_<)z zt>0b^T(}&udT@4l?V|Cza{A(qJ!^9n>d|xe$GbXwho0)$?v}s$_HCzX;#=$S$<_Yv zGe&p6d^mo^YyDxd*wpMLiT+A_=*2CM!gs#mdRwuybNOe)MfuThw@-%qf8&4Oa$Yu0 z+Ou-7W-nJGf1u&B-{J-D7IG#Zyc1AWK5+EpU}O58uMbkS6Pi+9WDj*X)m6`gEWPNg zn&++A$*v}vHgWgL%Zq&}z^<^;^CIhb=uG{kRrb~{{%nri{SK8UtNpy64m>9c|pv3IXdxB?XRU1Xo z{!)WDf^`{ZPl8kM!~b)T@;}!Nl ziMtAJ_jv`BykFhc@woC+%cIAyzWzM>*gNgI#}3DmqZO`!O?PxPo=u*so_RLTF^vds zyBqM+`~F@3vBJ;iUah1b^Q&G@GPwNw&&{5<;p<#SxlSHUP(OI_LRXyZyO7-WGA?wrvdo^#`Umn)`O&)I0Kb z_sxpsw;t`v=l>0w99A9p%HqfIQwKCdjAGx*FBJ!y-8ufGmLqQNmagqR9o`F8pKZC_=bU#R zk8=*z{cPmh8#JST>Hf#ZiB8`h>=3(}d$c-hhrsKA*37d~*P8;WA4oq8j`=*^cj(WR zs^07?9@kUCqP?rdUMV+V4iyu`X}YgHK@W@iM;7KO0sfog6-C>Q{NroFTM0(@9}8K5q0i z->*9%t<4@={MtUGJaw$uCo(Q|X}6fn^B|ysnX~lX=VnfRb~@9;G?jZTt1cw-Wr+T0waRH-z9ozkK7M3-xULn=?u*{tORa>2CY{OlmnXicQ^w0p za+OmKOW(9Tk=LmBUiq?>Vs_Pjlh<3e>Sn!P#GG)T=-Ta6NDe>wO0_$Q$9CJNeZ6^6 z#kD#y4!)nBE~izQv5RRtS(n(4MSR6=kpEpq0+n==RqC^E^5u1%wHII)T(G%9TK`tT-GCXl6cRWb1Fq!&QTl!Q4mUw4A8oyHT_;Jjj2z(r`sMiVdC0Che zBD;$pp05>ToI|}logSie*-GF?8XjvYm*UQb63^hg1#aFGEfG3D=4QbqeTM7S%YCa> z&tzMqEJ+oADcuEUzH>zn=NzW@@Azu`?_-ADy_N{+8<9ZIbp0OEO+Pie!rFiAB+&iF6=NO}jmIddhMNZs2 z+vKgl{+1}u**%$Y&#`~3j7VgB}@CzPAllTe!XNpg9m4!J2Rf{Tj>_ktgB8&R_ zb#xCR*F~;ic2^k4Ja@q!m|SRAk|AKP(ofN=u?ynsX>I1OR8vQ2C%-Y-dg|il^5ycL zy(l#=ZoWk?d?|44idf1KVS>zHxQU0&0@DD_LJuD zB(h9H>ytijz(dDcF7oa8d{G}8?_)|Q#Esju^q*BV-qSrfYQL8zS>!4!aZfA8y~yF? zr^qxFxrE@14qe8FaTzh|NeKNMfEQ*BFUD#*=q7e1E)@@He$>Gos#w%DH<9cB`|u zx&>Sk`=_+d4Dajws?erIIPWblAe#OMXf1DK9F6KH-foi?eI8cR_R!0&E8cr#u3Cee z_Q&^`>I2Ou?A>|CoHN=Rm6zhpwo7W~zgSfWy=rhT+B5Fb^I!MQM?T2E!5wR?(H2LL zD7_Un@xd4CQ>ha)V6k|u)vCLbdp-Nw`%~1EtCn2WmA*2ad?}HrN2-H1HksJ?tw(^b zM~(dlv4dsrMh3C5_-}va`v^T+AtJuoKI*5ewj3n%6qHPS&(UaN92>||`?;i3@RD;q zW;E`HXp2~6wa%9)o~wtshy%?u;X3(zg-4NlyG~*FPH0_7oxrr{mzx+4)XB*16~0Oj z(6qZ3>Z5!!TI*p6^*7ztvl@MP-A?YR!C-92fVoHw)|mIZabo0aP4 zDw+#_+98RGvU09_FFb-S)?7(ysmJ-h&yV6y*tn-jl)3wM%BvF}cF`evrt@~oMwoU8#j|IG6 zdOagv4x~3& ziO$tmOR8rEok+6xyPhva%N&zFCD{N4GX zL-n=&cL7&Yf=Ir@^wUDkGCS0>L(g9KUF<<~SH4{=@?LagqggdI3prvq$$o z%S>7*GXIh{q&sWZgqW&DM_LSF@loZG-}s-5YFi|pTrq1AIhOHh zw{l+nlHl&sYJ0a<$p`85;3oK<_qKm_mi|7wpk(>%W2@7{<{6v!MStU>H>~$;9q%pE zFdp+F3F}DPx?E3W)B7Rtg4eE`JyN)?H`TOR0Y8W*zr=Qw2&O(-e$OZv-XlHv_=J=@ zn;M&eYxRir?QJr%7Zk}VVM*&_kFG}6$fQjxw3_e{87W7uZsYu;;M~)C*tOf+$`i#c z^@i_>$Jd(=#v07})OT8{{q{a2FTZC{yP#kFp!HI`2Uhr?#)qk>=(8DiraX5`L?%wW zb#O&LxqFBrTY8~9x!NMJ*5dgYnHk#3w%3-;nvSMLxd{pv>IE1VV(xtGu|0ZF*URz~ zef-sD4}oCmj*?vkWpScgL29h|*G_hq9IM1Wu3P3&Dhsqv3~sKP6J3IXy_C`7%Z8E3 z1azK@!A~FgXVazv2?hvm&yMhgXkVhh9_n^_cC=22GE*gYpLlmnwX(qh%flywx;t;N z>5EG9Tp2>?-VFJXp>oRHjJ+;(XoaeLMuxlS&E$te6S=?VM(eP*4ejIJF?qPtMcdCE zS@OD&^uEfC2NyQ7#UM79@^H;2vOelawU+Lo`o2H4GuKot^Y6T5lfsHbAy%?%7j9RsE~y%IUq)UgLF09FHXAM8V%seL522k` zkIXw*m|P4lpKX&}z;M4xblGr`I7M;ZV)8lV-Bs!ODw&>0bJ0R8sgE|&55SE1SWMV~ z)C+2yH^QX^rEFb7v%W_+_-Ijt#Sibo*oykF-^Ki1+?aDz;Q#A%mQz?bDwg>iGk2hn z(RgfFNK>vCuaao>aoPGt4`s00^T+xI*Jm5oJLU=djk_LRY849?<$l`tJDbiX9E-c< zRNyR7hUd$EtCOL};jXI8@ke9A+u(r6bylt>o`Dx|v`i``7t!Np6z@#C{WTnQ<;G{@XUB3sXFeY-5cTcT%+FhNQH}E1+CeE*m(lFv=ZICOKmBtI7>?~9 zWPtRP3mEt43ft6=SKX?{Y5a!wM5Y?{ek3`Xq<%AY5`Q$^=ZcoBc>AkF;A5|9bdstq zRmAt22v7IUqYk@u4V^E(HkCZM$CJr(jQEO_q$(lLq5M1gt(H~rsC87!#dC2tu6|{* z*D6+o7+g)*P+t&EC#c*IPg44@;8=4yT3}ysO`qG_k|@sCB=gdxV*3_}1pK2>i^=>w zDtS1;OR6{4V_!xzR`8nD+b5emLY&fuo_BBU7gmei+LYC(y*DhkfTN|+xYRRl;I8En zb+un%Tf;Z#_xWvoYpt77yUyM$_uj?~YMV@}F};;a^v!BK2SVGNwi|A}_N{Q=PSHEb zvhG?+IS)RnHcE4iwVR8j8(Wia51iS3_A_0%_iE8Q^XrtzV6dw@!s6akWYi%uO=Y=& z>rd@>w8=FJmT|0q`GtFYDr#Bavsaw`*rfGG-@Z$^)9kfHcS{E&ejTI9cEzBA8^q+X zXXCT&hwty43f~q{iLqYH8WlyH9J5*!qLQ;d*AKr0NZ?Z)WK9^qYK=J8N$0owN4zx}5o5 zKx08oUs`8Kh%|=ud_U9}a#rHLmRuPr2&q#&oA6xj)vufT=_|+mpXrACN0_U19T%xL zKYL{BVFjsz@}?_qL}xQZXJ4YPMVtNJeLmJELVfwQ9P0 z=o8v0clcxBjdycbMGVHg`0iTQ?5s7Yi@g(nFKN5U$PJ?B2tTZG)E zwO&pu$i`;axneR%&z1ir4}9v_BX?u{W@;ZdxUw57Cm6Z@=~l<^SmYo1)~}N$(I-l- z>!_yIPaMSf#Lg$$_&h2x`6@WkE_Q-X#%L=$$G_)q5<4blnVnzx{NH_g&+q+l!cMkR z`~8r*rMjm=WY-r%tX)(%y4xZkdMKF95R>@Pg#U|BK*C(l^ODayj4KQFKiQLl=q0I@ z>?%>rJ>YIZz&FJa#P?s?FAJ=u1Agq{TemNJzHfBwLbmJ~kJ7>3^uG7&41(#4h_T|s zp{i7qard&bT`LTl+l;bDu0F?v^MYdNxo?`Hoxm|&3U{>3mF1{gkW1prp(rxum_Z25Y__Um0TxYp#CoHN6{SY+$)J^{IKIbus4eQq23vdOLlI zOw))iO%ywq^x#x#EXV7{#~;S8o+?VQyXO3!ahs5=l`QY@Z7cm;v-Nwe5|U7uWk=NB zuv7t38{f3WekMgun|z?RB+8KFCTl0cl(WmXy}CE%5*8&T zmtPko8~q^f3jt$?RAqoA@tbx{9s&`XIEaDNHOOvkq`ba#hvYJ_J;lIJ4Mb3-X}A^ zBuL0q6niG}BcqV}U&S%KJ%P=pru=%xV#Ke1X0IK$yQ|P* zO*AJvv{sq5-ovk(U}Gz<*BJ@M8eaeH-+Wo!rnu)>?xa~&8N1()3|)f|&eH2?m=O_m zP!;5x=HvTha6+u-LMVT?5VQ9u?pp)6Foi-B6s>KP>#) z(T?+Y>)Fa(@zXUCB=X{+!pwkrl^75$3A!S>K3N?qh{MlQgkuQ0t zC{5}Ew?F1K8}QqV_ZG@c8kE>X(^9yvDinvOv4y{JvzS%-4KBA>*3xdxHp?ydRUhr# z6GpJ)a=LpYexG{h?r=N-D=By>L@BDk0e7u;fS-O7j z7Fw_0`N($ZFismMu&=Ix z@#4tYch+GEdOQZpErw@l+r2WZm-n7O=euuakYDCzxICxqfj()U)D8pmgVg#14`SHq zZ@Oqa*K>I$j$PkVDB|-RQ!p_2-1Kms=dTwN%{zpz&6|u|L_ZhQaJ^9=<)dU%l|Eb4 z(LKE5VX}I^@-82ap!q}Tmrt)*9=N=YbIthsvLVW7W$*pWhr0e3&kxlof^K$>Lyrr` z3VKYxs(5}>`xz;IhQ}aqhl0WP2VD>Le*67Y(T>nhtIgF{y7={*z09YpPFvT6&d`Gr zt()TAqf0oJo(9u+?zGw+UWun^j0GPxJvVdv!hw&c|E^m($qv0{q};Hb5L;BXdu-fO z9wi#2@ml)U)clvhp^507`tO3#e=77296$t+{@(XwIoZ8;t~g~Xn96s$dPyftF7SMh zJ?6vv{V@;o>{}Vf$h1eql&ekxs5Mc1=xv0@;PZRE)x{`#WoZx^tYAXd-bHtdvrnfe1a{dF5|0JRl=0k&TFyTWFtzfe=-T% zu~rk}AMVu);&0~J7}Ix;KHlXe7Wt+JjhDr}y?64Nm~zeQ9lu694}Wz|P<&rNOjUmN z_@x1%H`r#3v(hA9UjFlGKk2pAwHd9Ribd>-X%V)^$Sy-ZQR#}x%EpJC(*xATRill! zCoHR5>(})jb@_=zNHLWGKt+3-nc3|#Gy6Z!kMPkk`%;RLWj&ej?&R)!ayL1Hl|IRu zoqtZ;K2&^I$u!1pN7+wGzz;DUO$BAOq{y?iwb$Q0j7oU%g!f4#aTzrCWX6+#PTtc;#B)7uVe# ziIP=Q1phQAr3=sc-aS55+>qM9VbP-mpE8dQ*1S7f(sOh*XDwzhzr3d5{4M``&k&1v zequq=_A{l)_YTXyGf)3>P-a86N&UwDtw%%>3G)nu^ZoCrf3-8`K74AZye({tGEy&J z*W`{4ra9J^zrF5TeIAX?q3tLc9`HhU-My!$?>vN$_rZwuO55~f)Gi>5R5ghk@@!sz z_bW2j6W=@l-2LcFkIwo1x|D4UcKzHQh#?Tr@*XWN^C1`fF8KY+@m^ZOm=y)2RZYJR zgRjEur}pnP2M0hqI8nkiGy=cjpjsXc1b|D3^$!JZWsAcvaY6fREN~-OaXwL*w((IO z0El3pxyhlBuT$S`()%0(*eGedVzPnfC2#G>-FE3>r%)i5-~WGq{+|l||9ZhvE%d29 zD+`PB9^cBZKWSKuSYJs<0KkVYqjmJ72LONmE-b!C;5Yq(0h10)0Q?*}ADh);z?5H{0jCS90M9rXdF_gyy6k>Rlfb4`Ss>~W z_d}EfVLfQ*5zq~JVM>hhkoh3KK0MOrTTvNm!a4<$e1L}O%pPJ#{2*ghaK%RX90<84 zha+A`<8lQGd7ywnn{J?!TZv-gA$};_#dWX`P}wbb=^>m1gbkvIo1|bglF&Gc>{Th? zX*UA0A>mT+jt9I@_6O}yVkK*T3|SJQ?1K0#6wCX~C{j8+MI7eVn@Ry7DG%;4g}V-0 z5s+0VWh9iv>6#XfvW1P8OoX%^9UK9DduSMbbGS)#&s>6(;YO9X$uKALybE^AsIjJo_xDD zxB92a4|<@x{Sepz<7|rg2yTJmIzl(7q`~vfz!f+xR)ieQv4bf;6*PBMv zcF0JL31nmvG9t1fv>iF3-EkHD|y)uM+e~QZwLH z^^~K?pa+FXNE9d2?vx99Op)IfF2y7D?3pE$^35d-JIM~o;u=%iTrx+@Fqush84L@n z9!(-557Cn}UT8cJ7Zj9;NNMy+fDkw*9h%2-quA8W6--{{UTlFQpeb zf!W-qga1TXLcVVTXdQezMT~X=yNRP+_q>#wVZ+!@Cb97niSh&t;~?YkCLk=&jbR*P z9NPr2@@r^@E5mIQSeBbbGrSqzo4|-%ADR)!2;2m^<=W7UFvg`#fG$^qX2dX}Hi1&P zQZyrhab**@Czp$6q%cx9flRq%G$VtNwFxB3MWY#a7D1F|JmW_x z9x81_b_B)v&6wFlV8}91j77$x+J9D%t|K6?`TiI~T_gPCa>N=`Gm3~~?Zo>M%TUc& zHS`?ItVk|JoQuF;&R+(Y4=#*BRnHKtlQa@(}WYa^SSe}02JpS2N>aZ7r>8i(PYo#pmBgHMRz+E$x@QQ4HQ|| zM3+W|r=d7daA~`{!{(BbKrD*vV&by743aM;Um|9!Q^|-4f&!}oE25ADR3?c3mKr?zDveZneRm!@;aV+C z7$jdwPGCAhu1(lv*}&8Rcb84BoB54VWLs0)NR~i((W#g#c4)x+7c4n2$@tN0$sB<3 zG(clk^0FzLs92;TXg<(v$BOAlTqF)OJ2X4+x8Jp+(P^;G{i8mo89mR&>tu^h0>qQD zCrwBvnE)>aDC$BM61Wa!D&5r75(`!(h}mcy`@w#E96*e&^>J6gT#AnYr6X_3UrZhx zqF>-7lp>Xy+-MN2r=^n!Q&a*D#Sf;a?%w$E-SWJmyrx?~vwO1#|8&%T5Q#>kLD1J< za}stbyYnK2IB<&?89?cfKWT=B%&6hd1ImE>X*1|?PTTQ;fHEWRw+Us`hJbG+R!-z)DvH{}W<_Q*>XjCTz4mAJV z1R_fza0I}#NtK5hX9LY*&A&8mK^vn6IRUvnc|aEGi~k{j?#j~KlY6hliXgzdOUBcl ziR7u9qd@bPIYU<5xT+gMKsLY#0>D%V7GR;~TT$)z=rldAA7`Us9r*>jk^`$TI#nzg z0C(YCTUahriw9&!Z;LBt7Mui)PBu9JvMk2ps1RsolZD$Zv)s7Ft{;FouFjiiTuvvQ zYc~J^iP}KHfr-?rF2I&&Gdg`*q+D@;{KQ`d@!=$pp))7@A&}75u5vCth{4;=cJCmQ zVkzGZF$_pspa!A_t#2jl2Ouc7!QU1+rbI=`i1Qy010dC4b9&dM)tW4r>nNIJO2>DR|hU&j~!870eWd_%6|~ z3EahTnu8^79u|;Y(D0L&iJMv?WW)|-GACtebO=OR#|`hc=QG(=B~3B*0JXHw$j zoB%-PanQu{!8E9bIZ>(#8*gi81gkX-5NnrxYz5H;;DXo%yCIE8G!7#)hyr9@%qF|= zlgzNK%l1H63b8{QA}=^tq)ycQg6eHW=9Q^RHub6s01)r)D(Hk2brd8en&05>=xPIb z*MPkDu#8~!$%_jM+tNEw$G2ha0Y-HD#|yRhLWA(50X-ik3|=A)fUQZcgd#7iY*h&YK)hl9nLiFpO6hMsShs;HzGk=piCSIJj?3to z%A?nk>tT10jG{r_>9D*)YHP~I9O2Pga94Y);pr`N(3)Hj7sf@LlH|m1mU5e8+Q}o( z{Q)TTy)B5lp-hd>3oXxultZA6Z#h6OCT{bb)w+EavQ(-9`=?|v7-4ZgHdYo})t{gN z=*-IaTCRV|KeDue%*n<67{S zAvOTAQ!8P)3{9jLDuIWF$uxcBGYCnx?>`QG68cSbjc|08Fp2xVO}Q7eo~r-3Azhg7 zD3Co?kO9Cv<06vjj$!l@b_=pNJ2 zi%L{+Q#YS0FfuL=S-*q#4BWypy6t^snfh;h(AaiP87$TS=WIZY9`vL(wBW$SW#~hH zM>J0k(pQ8@cE{(QufbkY4hM4gvBOkcWn78Pt|BoD!)gPdC0>8|cdWI`2;M)L&x4|& z9u@;`o%{4oM3NvQgBMH=)_M@527nC(Y1((sd1g@p0 z(2C8kRRXTJyqKJ0wRTrWzjsD3`Uf1##zw z4-_+AFCZCrIb5+|!4mQK*%|PJo&HnzK|3?${SgO`ch`4t)aX8)-2fuH$$`lJ3LP>?mJ2PXUq-dI1WZ*;bo< zw*E1hiyU_;?l^Z%n;6Ndi2G>cbY5VNg$WKw2vv?9O_iF2m4mf=d_aSiXauVCZSr;3 zOVEW)#KF`Kr++Kem7Fi3D?M>8BXoRYYmadG?gpAu4t;n(edl0!5XTu9iaPiYEd=2C ziwKf_|L2b?1JCqjioWlk-VLTMPF-9|Ta8+W+;!?uj`ahQyFoVQJiMl{=V*M$_f<^$ z&_EsS?fl8Sx`3S0c;aH#QXhtL011@If;ilRJXYTep9En``9LOg6OH63jl-B4em=^y z{Mo<%%036$8o>gJvXn3O5D%b0P=L(kXyR=USTE(g5JJA&QTmQyQ5+tqLZ8Z##qsP|L60&iVkx zBgUiY&?TOwS-7doERzYCAi<2p>XDs__(R2^No$PdQ4>v<_YhF1_&a+&1u49@qGr>? z4BL@A(9Ys-f>b%CIYc6*n1GGEmyV_uHy8n&43WfPHXsr1z|AjdihfvCV;jlANl4N#T}yunM5D(Yb+Z+44YLW3lt1yt;p zWI~EiVVq$tfj=YUGSsPn*+Nn3M(T;64R+Va9h^Z7@8hN8W{Q9K7lsZLM~jNp3ZDN* zJH2IUOX+39uF9vZsyOo%LFKq3SCT0oX#_==hzQ_diu3x?D=_(Hav^G{U4*0}y0?rc z%z^Y>nsBU=*&+omUvJJ5dZCA?7aZ|YzoE{g7j$^meOX2+WQl#>5%URR>Gn)Q0?{NuG1 zWlO^G!#$7`?T*gs6IiIwm00;2p}GWBxx853kwkzO-g9`Juul8mPU&z(c#miqB;KQY zHw>tu0omv(K3nk%G(GVi+|vLrgKKGA@RrD-d;Op6pS^YMgIrI@H!MJ`e^|d~Rn~Cp z|H$G;MrCKyHsyu)=Y7=WN0x{C^-&;z|B2$-J{pD{mU#5)aDoQBHsXs&@bno(P*Yqyq0J zfEvgb9$tm?o|@uYX3#)TCz}TcgvVg$J8L>k%WD@s6=CJd_`jOZY}jwcbcTfx>#K7^ z9fz6@GDDIf9_xC{RXI7BYzwC^0SW{QD6>A)@itan4uD4H9D617e*TF6&JZ0QU>V-W zg}fr2z55Fs7dpLxE3gy;K&9!JD{$PbU&I?S>gg^}(}&hsZvw1ks@&y96kX4Sv|_`Q z;UvUiOA*M*cqcp`{y(;@tnvkV9n<}dF#9wdH$}_;#~XEd$)N82+AbX86FGomtw4JJ zxdiFTQ-cY5Z=;(M)lh?|0vBL00O;UqFcMK?hfZSo>t{bsx>i~tU=^1Dkk7iT*|Vmt zfJxy*F`x}*t{BNBG(x~4Uuuk5wCN%>vnWqio9SUa#3KSg zFZ>MblF(@%YbH>hP;;zKjwKwh9@&9L!d+$o2Ncf~R`jgnXVHqVjuis?SR$tl9pFPv zutwEj7JYJ{?7TmRDf<#Wb>$W;+KE3a{!MI8hCAT2BQ0+ho4aF^2_Y9IYkD0M zieWP)ctS@rf}8`eG4zfn8JdXLh5~fg@T1|d2B+UlBLQA4V-dW7d+3A0xkjTr$&eFb zAFAOX70rq-<{m5PJro^C8~ghviXP(94n$ZNnh_}NLa`bL5I@$c)1F@gXWdZ3OaGHF z`u9r|eWfNI^^o=LW$;Y+7GnThW@(2;DnoAy3_#i7JFaP=hp-4GMuoxhm*sY(csO)* z+DPNCmSl}v0jcJ{-BhCAAJ&++A&Gq%evcsWW-7>c?*F3bKYD{OJWKCB^+OIr4x=p) zC9C~YZpb)G$I#hXJE?Mtyma62OHtC?tSK)bb)!`*LNSk7YBYyY&Wa<9l0PZ(DV2IX z>JHb!W;mqh)}S2Wgn)lLs%U%Z_OyG5F3aXdHP0o7pJl!6*Q|EZy8CE&NX%bm6O(f9B9wX!a;N z-c-p`0(s5B;+mL<2D8x_3a|}^fkidva0vl-pz5O?2$86%eXQVuoKuwE$%8()oqx}j zsCo_LJAPIk@{r33g5@^74%9E7;s$@$Mk%CI!SMU{+^0|c1G?4Z8lJn6QW2@6_OJrAJtYQ@a4GcUqZ^2Fj!!xB&d z=BHc@MP3jrPUeGOqcUSc6XL`8s<|jU5M)(pDx16j-qA@Z7dVY(z;1^qJgmX98C7)C zG*S%|J&ebIGZ$W12*M45h5nN~Zg!DJ-M*j2iJL6vEs8E>jJC?Pil6O^gCaZSklZog zj{?j3TyP3b!J!_*kqnWH>3b)2c1niT`~s{up&T_u*yO;=5*~9!8+NztFdOqfMA36l zEZ^mSf(rf(9V8Kc>&3Ey9;JfyfKrQUM6sJl(Ki}wH}`=!Dsm!Ck8vfFV;JUE1>_5g znH*(=-ZXaju*S69f2mgIego=U-t2Ji-M0*BAOv;K-{7x=+l&*+H(f>ES!0VW!EAIx zP6EHBiAIx3!#YhG#iD{BNoM*Kz0HITz*E?ddRnkUV~J{9hHYP;5J-kTNl!>4(aV+n zrqI=Z2;T%it#n*4otNGp1wBfm%misV-e81$;oBrg_%EX=Fe~C81F~C-R5mIbRSY$XlwPMM416`jZKmzK zEt|J0VAx#gN>JBcYka)(?FTC2EKK5c0G@m>=*XhnP^tg)ti*?y zWMmhI=g!S=C5eN~kDGEb5&DPlnBj^BA9f0yd;ng~&a}BkYxZ1Twj?$lip=0FpQk3n zEmMROnpyoHNe$FBrM|Xfbd`HsSrkR^ zb(D6NncPA#blEggg#5^Edfy<_KZsSk*eXMpV|8&iFfaV1vm>f z5u>jyq#cQJ13;>h^;>}3cFA+>ZRQtY0BS7G4gz$kUj(`{0o~+)L+V4Te^ij$F>SI* zFar_guy{1p5RN<^!$^u$MUIS9Ps3k?L5n+RQ&V@j?PKG7kJsDyehQg%=d4X{cyWZ5h+s!cMD z>xp`AhY?s#LGJ}*C3cMiCc&6h94pr=1s404qHw7_;{;Fx-_j5aubHyc@dItT;2g_P zCUJvlyp$r;mg zKv<1}(_AU`5sYuLA147PiFlmOsXB7~M%{VMIFPLvngv}uKL0V@6<2zI^`7U%Ub-gR zV$za*URB)a0$`OXfKVKNuae5sCJCPap)f&)nfJ0>X`P;5DWQh5Kn3jGNE1m^R@ zYW~7@D;>KL2}?m3jDrZj8~f@&;%S*F^k#*J)35#h&(UylQz|8pd7C@G=650{GiC~U zT^^xQvm38Frjc`&z83cp#tgzggM3UMVn?a{ZL7ki+ddpR1=~dst@OlD zoMZ^Vi*PCjAT~YSec4_xdak3 ziO7$a9pyo466e4Fg*qlC*Uql?7;%9LI3duua+wQ98n#>WeMA1a&Ox=YWzT`!qs5Ra zp6PwyMJ7~vX7e{TW?S;s;z48Vh?`R!I_?oh9E|BzE`c`pF*_#RyK(CHB%dX*NGdv?5w-^qL17i4qmYMGyBxpJW1Z7xBJfV=Vwb z;atMh8IMz@TQ?bDo9i;WY8v4rgvj7%C-FlMf(yu`HOBISZ99$(`;@iAZ5R73*#D%v;RNa#Ubl?B^ zY~EximNMqH1pZtNwN-=sfFp4xG&=^46gkI!4!+xQpLCU^PgR@+Q~ZHTROS@iN z1^}d%ib%3$$Epp`n0fGlH8zamx6?G|yisR}7;-g1xroje!To8RLESladfPZ_97`@e zKIL59PZm^{8x)5T5ZKB`VdRwVm^ua8B3Pv#h#W)rcAsYx3`KFUNTeN$1PA}b+(7}V z(LV`)faIr9$Xe<$zfeh|@GX)XRn(^WqRkk9Crkegys#~|bfyt$+8vO&IA(0bazy@# z6USg)Qm67OBy`na;LkkKl(E~3kKqgpAjG$f#o!Mp3QS?P1tRPdHYpna{$etX+K;j> zvvgSdj9dU3fv_ToJLnyU!iJ3flZg-l#jv*H-4EhE$jvuD0d3;#!)&22xpPbG7ou~Y zSuj{B0S4d+M|qk>s4$w(n5eCS_C@wfEqg~PIK7Dac>IsLYJKn6Tl zqS9-*=qQ;mtcDai6xDukLwiX4Z>%x0l6VQt5E@e(gh?!21qrP^yc8FP1!KS2czHQ( zyk=4JA^&QEu1$&1Rdn34pdA4*ao(1)u{f??R~CjdoTndpL$8&iYM|OqzWBJa>?F|c zuvUcH1i43nSlxAw_vrE{>5xWRN;oYlhFOZvd^whT)~6SE-d@=UogGo+4w?QIJ)|A= z?DDoIBOEvl$HI{=U1r>r@>>_4yuE|TbQ@EIQ^-<+3Z8Dgb9hZAn3ocqbrT9q+2>bAlgf| z{PX#r!FR(~|4kaB!kYREwBh(TkCKNRJ^i`za^G%BEG1R|QUydEt`e@}9Z4rgD>LXa z&v8E*;2>DVNrh6Br97>@B%Udhd6*@36{Qvl^-QW9?+b*zz#ai8j$#xWW-Oy>&?RVh zKcXlkY!i!86wd(zQP5=Atjmg8{F{DW19wf7S-jo=OI6n}1TINOEX0+3s z@aJ>A0=KMg$5nF{x4?r9Nx8HvcQxi4_Kuxn+_36&B0K3&HZn1lGvDV!k~ELt$6-z< z&x+`VE=5et5|-z;PiYXd;KVzgibRbCHqIxVZqSZAE%G8NoRgp;uOdYyvUOY=XYbhN z$v(KoAp#;x_&MVGk3BLzroR4f&d0UMbw=wd#Lq0HY8V)HFpthZ9{f^o%__IKIJt1z zbuYMb_I1eTXd3>Dp5Jx|iHQP}Sux5>Q!O1oy8ij)9Go&{;-NP_uXE+srq6TaobJ%d z*Vp(QE6!9VIuBCYzkr0D2u67+k>RtN=>@xK}==avbD)h<*BG7p+&x*nZEzQ z_t*W&J$F9$JbcbMpU>yKKkq{aW;U>&+5r2mW)7U*Wru)BwFH7UU%(^39zzLKU48e} zEe8N>0wbWM?mkMp>1IozWE_V0xd7{>j_dd*r1T%bSq!1J;1jiv138NxX z(!iG4NXy8YAEAZMR{EF>S$oU!8~+p>*M;kdQL4UMrd9#TprEJ>`V?XU%NO1kEs^El z+qd$xSYPm#Q2TW!eQ=XQ@2p4M)s7F9w$?c#G;FA#s28f)b5LC|I562jUo-CF5 zr}m`-3RFSGkVv|=pf4Q&7xE~Mw2K?qx{%h&4%B+qx6d#CAVgT;JC3V=0Ud7v-2{Q7 zF=#jC=fjGG1oFnmWeKXg5G#XJ6y*MmphMPCkZjF9rq0c)oz$agyr@73@xVblYYz6MJA)mznt()!4Qw0hhp-}fkOE!$6{{jH zDFm^f|4ElZPh=QSJk_}=I%QdX10XXmj2G3_kg^AWs0gp^lmPI5y!&iA0;XWp#`UL` zgDo?Htn5-ah&_ih2O`QGqu%(p61tuM@#lnlyXnZ?g&X!y+foQAx}y4POr1$Wq@O~t zRoqHKhz8$48^mhplz3m?VjPK@6|pLFurvS^9)7uLW&=9`wm*nf!Z+WwlHJhH3m8&B zKewwaU=ISG*rykRWP-01BoJA;y`A|X4o6)u*?)U}|7Au%;IE+m{N*aOyj$O9VCIon z$0+SQNCG*zAYw@ayUJ<^`WC1Lxg5qZwR{)otat3~^A&lVMJxktWHyO!-7&u)8b22( ziDXVVP*CKY6>Mp;Jl#9~%$YNNomInw=fXAVXX|?>056;I16~q_cbgDG!0Zf?2m8k~bj1|S=qAreqYkfI+8`hjl=wQcK zaT8VaIB4;%AL*RBu`D4?P&9Hs@(-BV@ zUWfMtr?3qP=|xl7crnl~yc*+cw*fKCePGIZJx?`@a_*TB?I@1ntSToy!ag{h@nqIm zyzyFiC+qReCSu&qcJKUP&3|ONc+!daH)(^IC4WA+RIinPj9v-3h9lNPThCzn7?b8-?J2j%{)>dC8jm>(zkuPzcc0|=u z=a`9az#d}LlPVkEZ{9^wmOyeT4$gcgfj&L|uub-%O}x&zIVymY;1*2%duJi8JXTc4 zNVf$&956n@xoVZR1YNW&v`7G47^gNHojhwUXOXU93pd74UO&%4zG2PT&jImSkeTCFUEgr^16O=*pL4V$5Q2N z`7@Y6RUT`rAKyR|n9z`v2D3ot4DE9KZKc6#vS`n(!Hck0#A=kV1r$MhF15NPsIX8$005G`L)(`q{X_}C{ zLVt41F`5}NV#fdSoRx>oJ!Id(>g51Iz$_GF-Cyvl(LH{mIkBZ`oD#zB+Nr&@*LdEk zRVF@j;)y6gV0!85zHH-1l6SSOnu%KAJP8x5O>)G;O%4*T=YtbYw=E(%A!{4wx=MR{ zFi1;_neZ;G0R6c$@Xt>#XLE-TUCX^jRAFsDQVh{NcUR-~=#fOjgGvi>W(%wlocYjW zB{mnMcY>+oBs{TfeNi1xpI$jg2?h@)B0~eN+_|PYrE(ZvP%*wlZ0Cy6N4PP>b&mc6 zOJU1=YSZ?$Uf~1VBwM9gZU829RxD5qJ(mkASK&S& zjdRfV*Fv02+((Bx9~7s#nEN|Q2}qGJdr)-K>}%Mt4fDQ303xMQ$B8E<MeNpfP+*rCrxEnYIpaj}-+KmhZ}Ka-Z(5{Ea)+#JCcD&3_d8rJ5QwJf4dyYpL;$Xm@;S*E5qm8KB7GjM z^xIpeP4XKsF7#`e8oL@z3AZj;1tAEK#k+3nDlP?nRZ}i#3W-dh+PV5_`=Hu*eYIgQ zJjWK&=H_HR4V7AebKa|$ba{1h}2PY9awQM^@9|w2>Y5dDE(uRBs z;iNGW&kD!>( zX{Q|xU6mH|ufIpi5;O{vD|X$HoHSB;6C)W05unU;PMbuk?f*iNWaIr{Z#RG;wIWEH5GYK_>nQSpL%=N;o}F+*3eHc#x*nKvL=hVY zV~=Vuek!H|e3{lRk)v=~LL))aR~DyhIcZGG3ptpw4r#PdvlHvWJB14|%GA$a=(v~7 z$L1LF_hH(t?yBhxFK8a)5+@Ns43Z<&5*74m&FA3P3=9?qrFM!U_(21QbfQTnmWao< zX?K^>2fTGfE8rB1)2zDJ=Teln(2fq;%GqW-dZy$&RkHzgfUl!2iWn{D#h{DUgc%q# zUhS#)B`Te4K%2#BPtJU*H~Ga_*F%^)`aZ4O4qrp{m>%P-ozj~XA5ci?;3NB9qt)Zg zn6*WUDE_{2EXy zJ%_}GQ#}neA?Qj5Tlj*SjIk>2q{>Xt75LPiS$Ubaqt_WBz>)~AQQCXA%t@L+1h`LJr)Qfyc}KTI%b8&x(gMC|pvJq5jKsvQLU}2k-W0g0F=`c*YT) z{zIWLryMQ-L31&@Xxu)}Kar^%&@R(h9vN_PJjY2gU$ literal 0 HcmV?d00001 diff --git a/stuff.lisp b/stuff.lisp index 8a0127c..46c29d3 100644 --- a/stuff.lisp +++ b/stuff.lisp @@ -1,5 +1,7 @@ (in-package :whatsxmpp) +(defparameter +version+ "0.0.1") + (defparameter +streams-ns+ "urn:ietf:params:xml:ns:xmpp-streams") (defparameter +stanzas-ns+ "urn:ietf:params:xml:ns:xmpp-stanzas") (defparameter +component-ns+ "jabber:component:accept") @@ -2109,21 +2111,30 @@ Returns three values: avatar data (as two values), and a generalized boolean spe #+sbcl (defun report-error-and-die (err) - (format t "ERROR: ~A~%Backtrace: ~A~%" err - (trivial-backtrace:print-backtrace err)) + (format *error-output* "[!] Fatal error, bridge aborting!~%") + (trivial-backtrace:print-backtrace err + :output *error-output*) (sb-ext:exit :code 1 :abort t)) #+sbcl (defun main () "Hacky main() function for running this in 'the real world' (outside emacs)" + (setf *debugger-hook* (lambda (condition hook) + (declare (ignore hook)) + (report-error-and-die condition))) + (when (< (length sb-ext:*posix-argv*) 2) + (format *error-output* "fatal: a path to the database must be provided~%") + (format *error-output* "usage: ~A DATABASE_PATH~%" (elt sb-ext:*posix-argv* 0)) + (sb-ext:exit :code 2 :abort t)) (let ((*default-database-path* (elt sb-ext:*posix-argv* 1))) - (format t "Using database at ~A~%" *default-database-path*) - (setf swank:*configure-emacs-indentation* nil) - (swank:create-server :dont-close t) - (setf *debugger-hook* (lambda (condition hook) - (declare (ignore hook)) - (report-error-and-die condition))) - (format t "*mario voice* Here we go!~%") + (format t "[*] whatsxmpp version ~A / an eta project ~%" +version+) + (format t "[+] Using database at ~A~%" *default-database-path*) + #+use-swank + (block nil + (format t "[+] Starting SWANK server~%") + (setf swank:*configure-emacs-indentation* nil) + (swank:create-server :dont-close t)) + (format t "[+] Initializing bridge~%") (setf *comp* (whatsxmpp-init)) (on :error *comp* (lambda (e) (report-error-and-die e))) @@ -2137,7 +2148,7 @@ Returns three values: avatar data (as two values), and a generalized boolean spe (bt:with-lock-held (lock) (bt:condition-wait condvar lock)))))) -#+sbcl +#+use-swank (uiop:register-image-dump-hook (lambda () (swank-loader:init diff --git a/whatsxmpp.asd b/whatsxmpp.asd index 3c935b4..db9fb79 100644 --- a/whatsxmpp.asd +++ b/whatsxmpp.asd @@ -1,5 +1,5 @@ (defsystem "whatsxmpp" - :depends-on ("usocket" "bordeaux-threads" "event-emitter" "blackbird" "cxml" "ironclad" "uuid" "sqlite" "whatscl" "drakma" "local-time" "trivial-timers" "swank" "trivial-backtrace" "trivial-mimes") + :depends-on ("usocket" "bordeaux-threads" "event-emitter" "blackbird" "cxml" "ironclad" "uuid" "sqlite" "whatscl" "drakma" "local-time" "trivial-timers" "trivial-backtrace" "trivial-mimes") :serial t :build-operation "program-op" :build-pathname "whatsxmpp"