diff --git a/COPYING.gpl3 b/COPYING.gpl3 deleted file mode 100644 index 94a9ed0..0000000 --- a/COPYING.gpl3 +++ /dev/null @@ -1,674 +0,0 @@ - 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/COPYING.md b/COPYING.md new file mode 100644 index 0000000..78f62ad --- /dev/null +++ b/COPYING.md @@ -0,0 +1,596 @@ +GNU GENERAL PUBLIC LICENSE +========================== + +Version 3, 29 June 2007 + +Copyright © 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/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000..29cb6ca --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,80 @@ +# Installation and configuration guide + +I assume that you have a basic understanding of XMPP and the the concept of a XMPP component / transport. If not, please get a book about Jabber or read the standards. + +transWhat is a XMPP transport. By this means it extends the functionallity of an existing XMPP server. It acts as a gateway between the XMPP and WhatsApp networks. It receives WhatsApp messages and forwards them to your XMPP client (and vice-versa). + +The implementation of transWhat is based on the [Spectrum 2](http://www.spectrum.im) framework and the [Yowsup 2](https://github.com/tgalal/yowsup) library to interface with WhatsApp. + +The following chart summarizes the involved components and the protocols they use to communicate. + +## Prosody + +### Installation + +I will not cover the installation of Prosody in this guide. Please look for some other tutorials on how to do that. + +### Configuration + +The only important thing for us is the configuration of a XMPP component (Spectrum 2 in our case). +See http://prosody.im/doc/components. + +Append the following at the end of `/etc/prosody/prosody.cfg.lua` + + Component "whatsapp.0l.de" + component_secret = "whatsappsucks" + component_ports = { 5221 } + component_interface = "127.0.0.1" + +## Spectrum 2 + +#### Installation + +Manual compile latest version from [Github](https://github.com/hanzz/libtransport). +You can use the following guide: http://spectrum.im/documentation/installation/from_source_code.html. + +#### Configuration + +Create a new file `/etc/spectrum2/transports/whatsapp.cfg` with the following content: + + [service] + user = spectrum + group = spectrum + + jid = whatsapp.0l.de + + server = localhost + password = whatsappsucks + port = 5221 + + backend_host = localhost + backend = /location/to/transwhat/transwhat.py + + users_per_backend = 10 + more_resources = 1 + + admin_jid = your@jid.example + + [identity] + name = transWhat + type = xmpp + category = gateway + + [logging] + config = /etc/spectrum2/logging.cfg + backend_config = /etc/spectrum2/backend-logging.cfg + +## transWhat + +### Installation + +Checkout the latest version of transWhat from GitHub: + + $ git clone git@github.com:stv0g/transwhat.git + +Install required dependencies: + + $ pip install --pre e4u protobuf python-dateutil yowsup + + - **e4u**: is a simple emoji4unicode python bindings + - [**yowsup**](https://github.com/tgalal/yowsup): is a python library that enables you build application which use WhatsApp service. diff --git a/README.md b/README.md index ac537d9..e025ca7 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,16 @@ transWhat is a WhatsApp XMPP Gateway based on [Spectrum 2](http://www.spectrum.im) and [Yowsup 2](https://github.com/tgalal/yowsup). -## Dependencies +## [Installation](INSTALL.md) +## [Usage](USAGE.md) -#### Python packages +## Features - pip install e4u protobuf mysql dateutil - - - **e4u**: is a simple emoji4unicode python bindings - - [**yowsup**](https://github.com/tgalal/yowsup): is a python library that enables you build application which use WhatsApp service. - - **mysqldb**: MySQL client python bindings - -#### Spectrum 2 -is a XMPP transport - -Manual compile latest version from https://github.com/hanzz/libtransport. + * Typing notifications + * Receive images, audio & video + * Set/get online status + * Set status message + * Groupchats ## Contributors @@ -23,11 +19,18 @@ Pull requests, bug reports etc. are welcome. Help us to provide a open implement The following persons have contributed major parts of this code: - - **Steffen Vogel** (@stv0g): Idea and initial implementation based on Yowsup 1 - - **Mohammed Yaseen Mowzer** (@moyamo): Port to Yowsup 2 + - @stv0g (Steffen Vogel): Idea and initial implementation based on Yowsup 1 + - @moyamo (Mohammed Yaseen Mowzer): Port to Yowsup 2 + - @DaZZZl: Improvements to group chats, media & message receipts -## Documentation +## [License](COPYING.md) -A project wiki is available [here](https://dev.0l.de/wiki/projects/transwhat/). +transWhat 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. + +Foobar 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. + +## Links + +An *outdated* project wiki is available [here](https://dev.0l.de/wiki/projects/transwhat/). An *outdated* writeup of this project is also availabe at my [blog](http://www.steffenvogel.de/2013/06/29/transwhat/). diff --git a/Spectrum2/backend.py b/Spectrum2/backend.py index 05b498f..feb2e94 100644 --- a/Spectrum2/backend.py +++ b/Spectrum2/backend.py @@ -19,6 +19,7 @@ class SpectrumBackend: @param host: Host where Spectrum2 NetworkPluginServer runs. @param port: Port. """ + def __init__(self): self.m_pingReceived = False self.m_data = "" @@ -38,14 +39,14 @@ class SpectrumBackend: self.send(message) def handleMessageAck(self, user, legacyName, ID): - m = protocol_pb2.ConversationMessage() - m.userName = user - m.buddyName = legacyName - m.message = "" - m.id = ID + m = protocol_pb2.ConversationMessage() + m.userName = user + m.buddyName = legacyName + m.message = "" + m.id = ID - message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK) - self.send(message) + message = WRAP(m.SerializeToString(), protocol_pb2.WrapperMessage.TYPE_CONV_MESSAGE_ACK) + self.send(message) def handleAttention(self, user, buddyName, msg): @@ -344,6 +345,14 @@ class SpectrumBackend: groups = [g for g in payload.group] self.handleBuddyRemovedRequest(payload.userName, payload.buddyName, groups); + def handleBuddiesPayload(self, data): + payload = protocol_pb2.Buddies() + if (payload.ParseFromString(data) == False): + #TODO: ERROR + return + + self.handleBuddies(payload); + def handleChatStatePayload(self, data, msgType): payload = protocol_pb2.Buddy() if (payload.ParseFromString(data) == False): @@ -364,10 +373,10 @@ class SpectrumBackend: if (len(self.m_data) >= 4): expected_size = struct.unpack('!I', self.m_data[0:4])[0] if (len(self.m_data) - 4 < expected_size): - self.logger.error("Expected Data Size Error") + self.logger.debug("Data packet incomplete") return else: - self.logger.error("Data too small") + self.logger.debug("Data packet incomplete") return packet = self.m_data[4:4+expected_size] @@ -376,17 +385,15 @@ class SpectrumBackend: parseFromString = wrapper.ParseFromString(packet) except: self.m_data = self.m_data[expected_size+4:] - self.logger.error("Parse from String exception") + self.logger.error("Parse from String exception. Skipping packet.") return if parseFromString == False: self.m_data = self.m_data[expected_size+4:] - self.logger.error("Parse from String failed") + self.logger.error("Parse from String failed. Skipping packet.") return self.m_data = self.m_data[4+expected_size:] - #self.logger.error("Data Type: %s",wrapper.type) - if wrapper.type == protocol_pb2.WrapperMessage.TYPE_LOGIN: self.handleLoginPayload(wrapper.payload) @@ -430,6 +437,8 @@ class SpectrumBackend: self.handleConvMessageAckPayload(wrapper.payload) elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_RAW_XML: self.handleRawXmlRequest(wrapper.payload) + elif wrapper.type == protocol_pb2.WrapperMessage.TYPE_BUDDIES: + self.handleBuddiesPayload(wrapper.payload) def send(self, data): header = struct.pack('!I',len(data)) @@ -490,6 +499,9 @@ class SpectrumBackend: raise NotImplementedError, "Implement me" + def handleBuddies(self, buddies): + pass + def handleLogoutRequest(self, user, legacyName): """ Called when XMPP user wants to disconnect legacy network. @@ -507,7 +519,7 @@ class SpectrumBackend: @param legacyName: Legacy network name of buddy or room. @param message: Plain text message. @param xhtml: XHTML message. - @param ID: message ID + @param ID: message ID """ raise NotImplementedError, "Implement me" diff --git a/Spectrum2/protocol.proto b/Spectrum2/protocol.proto index 8d48386..78a3b9a 100644 --- a/Spectrum2/protocol.proto +++ b/Spectrum2/protocol.proto @@ -63,6 +63,10 @@ message Buddy { optional bool blocked = 8; } +message Buddies { + repeated Buddy buddy = 1; +} + message ConversationMessage { required string userName = 1; required string buddyName = 2; @@ -182,6 +186,7 @@ message WrapperMessage { TYPE_ROOM_LIST = 32; TYPE_CONV_MESSAGE_ACK = 33; TYPE_RAW_XML = 34; + TYPE_BUDDIES = 35; } required Type type = 1; optional bytes payload = 2; diff --git a/Spectrum2/protocol_pb2.py b/Spectrum2/protocol_pb2.py index 638435c..13f0c5f 100644 --- a/Spectrum2/protocol_pb2.py +++ b/Spectrum2/protocol_pb2.py @@ -19,7 +19,7 @@ _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( name='protocol.proto', package='pbnetwork', - serialized_pb=_b('\n\x0eprotocol.proto\x12\tpbnetwork\"\x19\n\tConnected\x12\x0c\n\x04user\x18\x01 \x02(\t\"<\n\x0c\x44isconnected\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\r\n\x05\x65rror\x18\x02 \x02(\x05\x12\x0f\n\x07message\x18\x03 \x01(\t\"P\n\x05Login\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\x12\x10\n\x08password\x18\x03 \x02(\t\x12\x13\n\x0b\x65xtraFields\x18\x04 \x03(\t\"*\n\x06Logout\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\"\xab\x01\n\x05\x42uddy\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\r\n\x05group\x18\x04 \x03(\t\x12%\n\x06status\x18\x05 \x01(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x10\n\x08iconHash\x18\x07 \x01(\t\x12\x0f\n\x07\x62locked\x18\x08 \x01(\x08\"\xa9\x01\n\x13\x43onversationMessage\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x0f\n\x07message\x18\x03 \x02(\t\x12\x10\n\x08nickname\x18\x04 \x01(\t\x12\r\n\x05xhtml\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x10\n\x08headline\x18\x07 \x01(\x08\x12\n\n\x02id\x18\x08 \x01(\t\x12\n\n\x02pm\x18\t \x01(\x08\"J\n\x04Room\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x10\n\x08nickname\x18\x02 \x02(\t\x12\x0c\n\x04room\x18\x03 \x02(\t\x12\x10\n\x08password\x18\x04 \x01(\t\"&\n\x08RoomList\x12\x0c\n\x04room\x18\x01 \x03(\t\x12\x0c\n\x04name\x18\x02 \x03(\t\"\x9c\x01\n\x0bParticipant\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x0c\n\x04room\x18\x02 \x02(\t\x12\x10\n\x08nickname\x18\x03 \x02(\t\x12\x0c\n\x04\x66lag\x18\x04 \x02(\x05\x12%\n\x06status\x18\x05 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x0f\n\x07newname\x18\x07 \x01(\t\"k\n\x05VCard\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\n\n\x02id\x18\x03 \x02(\x05\x12\x10\n\x08\x66ullname\x18\x04 \x01(\t\x12\x10\n\x08nickname\x18\x05 \x01(\t\x12\r\n\x05photo\x18\x06 \x01(\x0c\"X\n\x06Status\x12\x10\n\x08userName\x18\x01 \x02(\t\x12%\n\x06status\x18\x03 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x04 \x01(\t\"B\n\x05Stats\x12\x0b\n\x03res\x18\x01 \x02(\x05\x12\x10\n\x08init_res\x18\x02 \x02(\x05\x12\x0e\n\x06shared\x18\x03 \x02(\x05\x12\n\n\x02id\x18\x04 \x02(\t\"Y\n\x04\x46ile\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x10\n\x08\x66ileName\x18\x03 \x02(\t\x12\x0c\n\x04size\x18\x04 \x02(\x05\x12\x0c\n\x04\x66tID\x18\x05 \x01(\x05\".\n\x10\x46ileTransferData\x12\x0c\n\x04\x66tID\x18\x01 \x02(\x05\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"\x1f\n\rBackendConfig\x12\x0e\n\x06\x63onfig\x18\x01 \x02(\t\"\x9a\x06\n\x0eWrapperMessage\x12,\n\x04type\x18\x01 \x02(\x0e\x32\x1e.pbnetwork.WrapperMessage.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xc8\x05\n\x04Type\x12\x12\n\x0eTYPE_CONNECTED\x10\x01\x12\x15\n\x11TYPE_DISCONNECTED\x10\x02\x12\x0e\n\nTYPE_LOGIN\x10\x03\x12\x0f\n\x0bTYPE_LOGOUT\x10\x04\x12\x16\n\x12TYPE_BUDDY_CHANGED\x10\x06\x12\x16\n\x12TYPE_BUDDY_REMOVED\x10\x07\x12\x15\n\x11TYPE_CONV_MESSAGE\x10\x08\x12\r\n\tTYPE_PING\x10\t\x12\r\n\tTYPE_PONG\x10\n\x12\x12\n\x0eTYPE_JOIN_ROOM\x10\x0b\x12\x13\n\x0fTYPE_LEAVE_ROOM\x10\x0c\x12\x1c\n\x18TYPE_PARTICIPANT_CHANGED\x10\r\x12\x1e\n\x1aTYPE_ROOM_NICKNAME_CHANGED\x10\x0e\x12\x1d\n\x19TYPE_ROOM_SUBJECT_CHANGED\x10\x0f\x12\x0e\n\nTYPE_VCARD\x10\x10\x12\x17\n\x13TYPE_STATUS_CHANGED\x10\x11\x12\x15\n\x11TYPE_BUDDY_TYPING\x10\x12\x12\x1d\n\x19TYPE_BUDDY_STOPPED_TYPING\x10\x13\x12\x14\n\x10TYPE_BUDDY_TYPED\x10\x14\x12\x15\n\x11TYPE_AUTH_REQUEST\x10\x15\x12\x12\n\x0eTYPE_ATTENTION\x10\x16\x12\x0e\n\nTYPE_STATS\x10\x17\x12\x11\n\rTYPE_FT_START\x10\x18\x12\x12\n\x0eTYPE_FT_FINISH\x10\x19\x12\x10\n\x0cTYPE_FT_DATA\x10\x1a\x12\x11\n\rTYPE_FT_PAUSE\x10\x1b\x12\x14\n\x10TYPE_FT_CONTINUE\x10\x1c\x12\r\n\tTYPE_EXIT\x10\x1d\x12\x17\n\x13TYPE_BACKEND_CONFIG\x10\x1e\x12\x0e\n\nTYPE_QUERY\x10\x1f\x12\x12\n\x0eTYPE_ROOM_LIST\x10 \x12\x19\n\x15TYPE_CONV_MESSAGE_ACK\x10!\x12\x10\n\x0cTYPE_RAW_XML\x10\"*\xb3\x05\n\x0f\x43onnectionError\x12\"\n\x1e\x43ONNECTION_ERROR_NETWORK_ERROR\x10\x00\x12%\n!CONNECTION_ERROR_INVALID_USERNAME\x10\x01\x12*\n&CONNECTION_ERROR_AUTHENTICATION_FAILED\x10\x02\x12.\n*CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE\x10\x03\x12#\n\x1f\x43ONNECTION_ERROR_NO_SSL_SUPPORT\x10\x04\x12%\n!CONNECTION_ERROR_ENCRYPTION_ERROR\x10\x05\x12 \n\x1c\x43ONNECTION_ERROR_NAME_IN_USE\x10\x06\x12%\n!CONNECTION_ERROR_INVALID_SETTINGS\x10\x07\x12&\n\"CONNECTION_ERROR_CERT_NOT_PROVIDED\x10\x08\x12#\n\x1f\x43ONNECTION_ERROR_CERT_UNTRUSTED\x10\t\x12!\n\x1d\x43ONNECTION_ERROR_CERT_EXPIRED\x10\n\x12\'\n#CONNECTION_ERROR_CERT_NOT_ACTIVATED\x10\x0b\x12+\n\'CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH\x10\x0c\x12.\n*CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH\x10\r\x12%\n!CONNECTION_ERROR_CERT_SELF_SIGNED\x10\x0e\x12%\n!CONNECTION_ERROR_CERT_OTHER_ERROR\x10\x0f\x12 \n\x1c\x43ONNECTION_ERROR_OTHER_ERROR\x10\x10*\x86\x01\n\nStatusType\x12\x11\n\rSTATUS_ONLINE\x10\x00\x12\x0f\n\x0bSTATUS_AWAY\x10\x01\x12\x0e\n\nSTATUS_FFC\x10\x02\x12\r\n\tSTATUS_XA\x10\x03\x12\x0e\n\nSTATUS_DND\x10\x04\x12\x0f\n\x0bSTATUS_NONE\x10\x05\x12\x14\n\x10STATUS_INVISIBLE\x10\x06*\x88\x02\n\x0fParticipantFlag\x12\x19\n\x15PARTICIPANT_FLAG_NONE\x10\x00\x12\x1e\n\x1aPARTICIPANT_FLAG_MODERATOR\x10\x01\x12\x1d\n\x19PARTICIPANT_FLAG_CONFLICT\x10\x02\x12\x1b\n\x17PARTICIPANT_FLAG_BANNED\x10\x04\x12#\n\x1fPARTICIPANT_FLAG_NOT_AUTHORIZED\x10\x08\x12\x17\n\x13PARTICIPANT_FLAG_ME\x10\x10\x12\x1b\n\x17PARTICIPANT_FLAG_KICKED\x10 \x12#\n\x1fPARTICIPANT_FLAG_ROOM_NOT_FOUND\x10@') + serialized_pb=_b('\n\x0eprotocol.proto\x12\tpbnetwork\"\x19\n\tConnected\x12\x0c\n\x04user\x18\x01 \x02(\t\"<\n\x0c\x44isconnected\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\r\n\x05\x65rror\x18\x02 \x02(\x05\x12\x0f\n\x07message\x18\x03 \x01(\t\"P\n\x05Login\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\x12\x10\n\x08password\x18\x03 \x02(\t\x12\x13\n\x0b\x65xtraFields\x18\x04 \x03(\t\"*\n\x06Logout\x12\x0c\n\x04user\x18\x01 \x02(\t\x12\x12\n\nlegacyName\x18\x02 \x02(\t\"\xab\x01\n\x05\x42uddy\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\r\n\x05\x61lias\x18\x03 \x01(\t\x12\r\n\x05group\x18\x04 \x03(\t\x12%\n\x06status\x18\x05 \x01(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x10\n\x08iconHash\x18\x07 \x01(\t\x12\x0f\n\x07\x62locked\x18\x08 \x01(\x08\"*\n\x07\x42uddies\x12\x1f\n\x05\x62uddy\x18\x01 \x03(\x0b\x32\x10.pbnetwork.Buddy\"\xa9\x01\n\x13\x43onversationMessage\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x0f\n\x07message\x18\x03 \x02(\t\x12\x10\n\x08nickname\x18\x04 \x01(\t\x12\r\n\x05xhtml\x18\x05 \x01(\t\x12\x11\n\ttimestamp\x18\x06 \x01(\t\x12\x10\n\x08headline\x18\x07 \x01(\x08\x12\n\n\x02id\x18\x08 \x01(\t\x12\n\n\x02pm\x18\t \x01(\x08\"J\n\x04Room\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x10\n\x08nickname\x18\x02 \x02(\t\x12\x0c\n\x04room\x18\x03 \x02(\t\x12\x10\n\x08password\x18\x04 \x01(\t\"&\n\x08RoomList\x12\x0c\n\x04room\x18\x01 \x03(\t\x12\x0c\n\x04name\x18\x02 \x03(\t\"\x9c\x01\n\x0bParticipant\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x0c\n\x04room\x18\x02 \x02(\t\x12\x10\n\x08nickname\x18\x03 \x02(\t\x12\x0c\n\x04\x66lag\x18\x04 \x02(\x05\x12%\n\x06status\x18\x05 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x06 \x01(\t\x12\x0f\n\x07newname\x18\x07 \x01(\t\"k\n\x05VCard\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\n\n\x02id\x18\x03 \x02(\x05\x12\x10\n\x08\x66ullname\x18\x04 \x01(\t\x12\x10\n\x08nickname\x18\x05 \x01(\t\x12\r\n\x05photo\x18\x06 \x01(\x0c\"X\n\x06Status\x12\x10\n\x08userName\x18\x01 \x02(\t\x12%\n\x06status\x18\x03 \x02(\x0e\x32\x15.pbnetwork.StatusType\x12\x15\n\rstatusMessage\x18\x04 \x01(\t\"B\n\x05Stats\x12\x0b\n\x03res\x18\x01 \x02(\x05\x12\x10\n\x08init_res\x18\x02 \x02(\x05\x12\x0e\n\x06shared\x18\x03 \x02(\x05\x12\n\n\x02id\x18\x04 \x02(\t\"Y\n\x04\x46ile\x12\x10\n\x08userName\x18\x01 \x02(\t\x12\x11\n\tbuddyName\x18\x02 \x02(\t\x12\x10\n\x08\x66ileName\x18\x03 \x02(\t\x12\x0c\n\x04size\x18\x04 \x02(\x05\x12\x0c\n\x04\x66tID\x18\x05 \x01(\x05\".\n\x10\x46ileTransferData\x12\x0c\n\x04\x66tID\x18\x01 \x02(\x05\x12\x0c\n\x04\x64\x61ta\x18\x02 \x02(\x0c\"\x1f\n\rBackendConfig\x12\x0e\n\x06\x63onfig\x18\x01 \x02(\t\"\xac\x06\n\x0eWrapperMessage\x12,\n\x04type\x18\x01 \x02(\x0e\x32\x1e.pbnetwork.WrapperMessage.Type\x12\x0f\n\x07payload\x18\x02 \x01(\x0c\"\xda\x05\n\x04Type\x12\x12\n\x0eTYPE_CONNECTED\x10\x01\x12\x15\n\x11TYPE_DISCONNECTED\x10\x02\x12\x0e\n\nTYPE_LOGIN\x10\x03\x12\x0f\n\x0bTYPE_LOGOUT\x10\x04\x12\x16\n\x12TYPE_BUDDY_CHANGED\x10\x06\x12\x16\n\x12TYPE_BUDDY_REMOVED\x10\x07\x12\x15\n\x11TYPE_CONV_MESSAGE\x10\x08\x12\r\n\tTYPE_PING\x10\t\x12\r\n\tTYPE_PONG\x10\n\x12\x12\n\x0eTYPE_JOIN_ROOM\x10\x0b\x12\x13\n\x0fTYPE_LEAVE_ROOM\x10\x0c\x12\x1c\n\x18TYPE_PARTICIPANT_CHANGED\x10\r\x12\x1e\n\x1aTYPE_ROOM_NICKNAME_CHANGED\x10\x0e\x12\x1d\n\x19TYPE_ROOM_SUBJECT_CHANGED\x10\x0f\x12\x0e\n\nTYPE_VCARD\x10\x10\x12\x17\n\x13TYPE_STATUS_CHANGED\x10\x11\x12\x15\n\x11TYPE_BUDDY_TYPING\x10\x12\x12\x1d\n\x19TYPE_BUDDY_STOPPED_TYPING\x10\x13\x12\x14\n\x10TYPE_BUDDY_TYPED\x10\x14\x12\x15\n\x11TYPE_AUTH_REQUEST\x10\x15\x12\x12\n\x0eTYPE_ATTENTION\x10\x16\x12\x0e\n\nTYPE_STATS\x10\x17\x12\x11\n\rTYPE_FT_START\x10\x18\x12\x12\n\x0eTYPE_FT_FINISH\x10\x19\x12\x10\n\x0cTYPE_FT_DATA\x10\x1a\x12\x11\n\rTYPE_FT_PAUSE\x10\x1b\x12\x14\n\x10TYPE_FT_CONTINUE\x10\x1c\x12\r\n\tTYPE_EXIT\x10\x1d\x12\x17\n\x13TYPE_BACKEND_CONFIG\x10\x1e\x12\x0e\n\nTYPE_QUERY\x10\x1f\x12\x12\n\x0eTYPE_ROOM_LIST\x10 \x12\x19\n\x15TYPE_CONV_MESSAGE_ACK\x10!\x12\x10\n\x0cTYPE_RAW_XML\x10\"\x12\x10\n\x0cTYPE_BUDDIES\x10#*\xb3\x05\n\x0f\x43onnectionError\x12\"\n\x1e\x43ONNECTION_ERROR_NETWORK_ERROR\x10\x00\x12%\n!CONNECTION_ERROR_INVALID_USERNAME\x10\x01\x12*\n&CONNECTION_ERROR_AUTHENTICATION_FAILED\x10\x02\x12.\n*CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE\x10\x03\x12#\n\x1f\x43ONNECTION_ERROR_NO_SSL_SUPPORT\x10\x04\x12%\n!CONNECTION_ERROR_ENCRYPTION_ERROR\x10\x05\x12 \n\x1c\x43ONNECTION_ERROR_NAME_IN_USE\x10\x06\x12%\n!CONNECTION_ERROR_INVALID_SETTINGS\x10\x07\x12&\n\"CONNECTION_ERROR_CERT_NOT_PROVIDED\x10\x08\x12#\n\x1f\x43ONNECTION_ERROR_CERT_UNTRUSTED\x10\t\x12!\n\x1d\x43ONNECTION_ERROR_CERT_EXPIRED\x10\n\x12\'\n#CONNECTION_ERROR_CERT_NOT_ACTIVATED\x10\x0b\x12+\n\'CONNECTION_ERROR_CERT_HOSTNAME_MISMATCH\x10\x0c\x12.\n*CONNECTION_ERROR_CERT_FINGERPRINT_MISMATCH\x10\r\x12%\n!CONNECTION_ERROR_CERT_SELF_SIGNED\x10\x0e\x12%\n!CONNECTION_ERROR_CERT_OTHER_ERROR\x10\x0f\x12 \n\x1c\x43ONNECTION_ERROR_OTHER_ERROR\x10\x10*\x86\x01\n\nStatusType\x12\x11\n\rSTATUS_ONLINE\x10\x00\x12\x0f\n\x0bSTATUS_AWAY\x10\x01\x12\x0e\n\nSTATUS_FFC\x10\x02\x12\r\n\tSTATUS_XA\x10\x03\x12\x0e\n\nSTATUS_DND\x10\x04\x12\x0f\n\x0bSTATUS_NONE\x10\x05\x12\x14\n\x10STATUS_INVISIBLE\x10\x06*\x88\x02\n\x0fParticipantFlag\x12\x19\n\x15PARTICIPANT_FLAG_NONE\x10\x00\x12\x1e\n\x1aPARTICIPANT_FLAG_MODERATOR\x10\x01\x12\x1d\n\x19PARTICIPANT_FLAG_CONFLICT\x10\x02\x12\x1b\n\x17PARTICIPANT_FLAG_BANNED\x10\x04\x12#\n\x1fPARTICIPANT_FLAG_NOT_AUTHORIZED\x10\x08\x12\x17\n\x13PARTICIPANT_FLAG_ME\x10\x10\x12\x1b\n\x17PARTICIPANT_FLAG_KICKED\x10 \x12#\n\x1fPARTICIPANT_FLAG_ROOM_NOT_FOUND\x10@') ) _sym_db.RegisterFileDescriptor(DESCRIPTOR) @@ -100,8 +100,8 @@ _CONNECTIONERROR = _descriptor.EnumDescriptor( ], containing_type=None, options=None, - serialized_start=2102, - serialized_end=2793, + serialized_start=2164, + serialized_end=2855, ) _sym_db.RegisterEnumDescriptor(_CONNECTIONERROR) @@ -143,8 +143,8 @@ _STATUSTYPE = _descriptor.EnumDescriptor( ], containing_type=None, options=None, - serialized_start=2796, - serialized_end=2930, + serialized_start=2858, + serialized_end=2992, ) _sym_db.RegisterEnumDescriptor(_STATUSTYPE) @@ -190,8 +190,8 @@ _PARTICIPANTFLAG = _descriptor.EnumDescriptor( ], containing_type=None, options=None, - serialized_start=2933, - serialized_end=3197, + serialized_start=2995, + serialized_end=3259, ) _sym_db.RegisterEnumDescriptor(_PARTICIPANTFLAG) @@ -368,11 +368,15 @@ _WRAPPERMESSAGE_TYPE = _descriptor.EnumDescriptor( name='TYPE_RAW_XML', index=32, number=34, options=None, type=None), + _descriptor.EnumValueDescriptor( + name='TYPE_BUDDIES', index=33, number=35, + options=None, + type=None), ], containing_type=None, options=None, - serialized_start=1387, - serialized_end=2099, + serialized_start=1431, + serialized_end=2161, ) _sym_db.RegisterEnumDescriptor(_WRAPPERMESSAGE_TYPE) @@ -618,6 +622,36 @@ _BUDDY = _descriptor.Descriptor( ) +_BUDDIES = _descriptor.Descriptor( + name='Buddies', + full_name='pbnetwork.Buddies', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='buddy', full_name='pbnetwork.Buddies.buddy', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + oneofs=[ + ], + serialized_start=418, + serialized_end=460, +) + + _CONVERSATIONMESSAGE = _descriptor.Descriptor( name='ConversationMessage', full_name='pbnetwork.ConversationMessage', @@ -699,8 +733,8 @@ _CONVERSATIONMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=419, - serialized_end=588, + serialized_start=463, + serialized_end=632, ) @@ -750,8 +784,8 @@ _ROOM = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=590, - serialized_end=664, + serialized_start=634, + serialized_end=708, ) @@ -787,8 +821,8 @@ _ROOMLIST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=666, - serialized_end=704, + serialized_start=710, + serialized_end=748, ) @@ -859,8 +893,8 @@ _PARTICIPANT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=707, - serialized_end=863, + serialized_start=751, + serialized_end=907, ) @@ -924,8 +958,8 @@ _VCARD = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=865, - serialized_end=972, + serialized_start=909, + serialized_end=1016, ) @@ -968,8 +1002,8 @@ _STATUS = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=974, - serialized_end=1062, + serialized_start=1018, + serialized_end=1106, ) @@ -1019,8 +1053,8 @@ _STATS = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1064, - serialized_end=1130, + serialized_start=1108, + serialized_end=1174, ) @@ -1077,8 +1111,8 @@ _FILE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1132, - serialized_end=1221, + serialized_start=1176, + serialized_end=1265, ) @@ -1114,8 +1148,8 @@ _FILETRANSFERDATA = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1223, - serialized_end=1269, + serialized_start=1267, + serialized_end=1313, ) @@ -1144,8 +1178,8 @@ _BACKENDCONFIG = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1271, - serialized_end=1302, + serialized_start=1315, + serialized_end=1346, ) @@ -1182,11 +1216,12 @@ _WRAPPERMESSAGE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1305, - serialized_end=2099, + serialized_start=1349, + serialized_end=2161, ) _BUDDY.fields_by_name['status'].enum_type = _STATUSTYPE +_BUDDIES.fields_by_name['buddy'].message_type = _BUDDY _PARTICIPANT.fields_by_name['status'].enum_type = _STATUSTYPE _STATUS.fields_by_name['status'].enum_type = _STATUSTYPE _WRAPPERMESSAGE.fields_by_name['type'].enum_type = _WRAPPERMESSAGE_TYPE @@ -1196,6 +1231,7 @@ DESCRIPTOR.message_types_by_name['Disconnected'] = _DISCONNECTED DESCRIPTOR.message_types_by_name['Login'] = _LOGIN DESCRIPTOR.message_types_by_name['Logout'] = _LOGOUT DESCRIPTOR.message_types_by_name['Buddy'] = _BUDDY +DESCRIPTOR.message_types_by_name['Buddies'] = _BUDDIES DESCRIPTOR.message_types_by_name['ConversationMessage'] = _CONVERSATIONMESSAGE DESCRIPTOR.message_types_by_name['Room'] = _ROOM DESCRIPTOR.message_types_by_name['RoomList'] = _ROOMLIST @@ -1246,6 +1282,13 @@ Buddy = _reflection.GeneratedProtocolMessageType('Buddy', (_message.Message,), d )) _sym_db.RegisterMessage(Buddy) +Buddies = _reflection.GeneratedProtocolMessageType('Buddies', (_message.Message,), dict( + DESCRIPTOR = _BUDDIES, + __module__ = 'protocol_pb2' + # @@protoc_insertion_point(class_scope:pbnetwork.Buddies) + )) +_sym_db.RegisterMessage(Buddies) + ConversationMessage = _reflection.GeneratedProtocolMessageType('ConversationMessage', (_message.Message,), dict( DESCRIPTOR = _CONVERSATIONMESSAGE, __module__ = 'protocol_pb2' diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..dcea058 --- /dev/null +++ b/USAGE.md @@ -0,0 +1,73 @@ +# Usage + +## Starting/stopping service + +The `transwhat.py` script gets started as a backend by Spectrum. You should not try to run it manually. +To simplify the management of multiple transports (WhatsApp, IRC, Facebook, libpurple, ...), Spectrum provides a little helper tool called `spectrum_manager`: + +To start transwhat run + +``` + spectrum_manager start whatsapp + +``` + +To stop transwhat run + +``` + spectrum_manager stop whatsapp +``` + +## Bot + +You might want to talk to bot if you're feeling lonely ;-P + +The bot is one of the contacts every user has in its contact list. It offers you a simple way to interact with the server: + +| **Command** | **Description** | +| ------------ | --------------- | +| `\help` | show this message | +| `\prune` | clear your buddylist | +| `\sync` | sync your imported contacts with WhatsApp | +| `\lastseen` | request last online timestamp from buddy | +| `\leave` | permanently leave group chat | +| `\groups` | print all attended groups | +| `\getgroups` | get current groups from WA | + +All commands start with a **back**slash! + +## Login + +To login to the transWhat, you should use the service discovery option in your XMPP client. + +When asked about the login credentials, enter your data as described below: + +| **Setting** | **Value** | **Example** | +| ----------- | ------------------------- | --------------- | +| User | CountryCode + PhoneNumber | 4917634911387 | +| Password | WhatsApp password | [*Base64 string*](https://github.com/davidgfnet/whatsapp-purple#how-do-i-get-my-user-name-and-password)| + + +### Buddies + +WhatsApp does not store your contacts on their servers. Thus you need to import your contacts manually with your XMPP Client or use [[.:bot|our bot]] to Import your contacts from Google (preferred). + +(In Pidgin: Menu => Buddys => Add Buddy) + +Just use the same JID format as for your login: + + CountryCode + PhoneNumber + "@whatsapp.example.org" + +### Groups + +To chat with groups you need to add them manually to your XMPP client. + +To get a list of your WhatsApp groups, you can use the Auto Discovery functionality of your XMPP client. + +(In Pidgin: Menu => Buddys => Join Chat => RoomList) + +### Smileys / Emojis + +To be able to see smileys, you will need an [Unicode Emoji font](https://github.com/stv0g/unicode-emoji/raw/master/symbola/Symbola.ttf). + +When using Pidgin, you might want to check out my [stv0g's Unicode emoji theme](https://github.com/stv0g/unicode-emoji)]. diff --git a/bot.py b/bot.py index 16d0e1d..dcd3708 100644 --- a/bot.py +++ b/bot.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -30,26 +29,16 @@ import time import os import utils -from constants import * -#from googleclient import GoogleClient - -#from Yowsup.Contacts.contacts import WAContactsSyncRequest - class Bot(): def __init__(self, session, name = "Bot"): self.session = session self.name = name - #self.google = GoogleClient() - self.commands = { - "import": self._import, "help": self._help, "prune": self._prune, - "welcome": self._welcome, - "fortune": self._fortune, "sync": self._sync, - "groups": self._groups, + "groups": self._groups, "getgroups": self._getgroups } @@ -81,81 +70,7 @@ class Bot(): def send(self, message): self.session.backend.handleMessage(self.session.user, self.name, message) - def __do_import(self, token): - # Google - google = self.google.getContacts(token) - self.send("%d buddies imported from google" % len(google)) - - result = { } - for number, name in google.iteritems(): - number = re.sub("[^0-9]", "", number) - number = number if number[0] == "0" else "+" + number - - result[number] = { 'nick': name, 'state': 0 } - - # WhatsApp - user = self.session.legacyName - password = self.session.password - sync = WAContactsSyncRequest(user, password, result.keys()) - whatsapp = sync.send()['c'] - - for w in whatsapp: - result[w['p']]['state'] = w['w'] - result[w['p']]['number'] = w['n'] - - self.send("%d buddies are using whatsapp" % len(filter(lambda w: w['w'], whatsapp))) - - for r in result.values(): - if r['nick']: - self.session.buddies.add( - number = r['number'], - nick = r['nick'], - groups = [u'Google'], - state = r['state'] - ) - - self.send("%d buddies imported" % len(whatsapp)) - - def __get_token(self, filename, timeout = 30): - file = open(filename, 'r') - file.seek(-1, 2) # look at the end - - count = 0 - while count < timeout: - line = file.readline() - - if line in ["", "\n"]: - time.sleep(1) - count += 1 - continue - else: - timestamp, number, token = line[:-1].split("\t") - if (number == self.session.legacyName): - file.close() - return token - - file.close() - # commands - def _import(self, token = None): - if not token: - token_url = self.google.getTokenUrl("http://whatsapp.0l.de/auth.py") - auth_url = "http://whatsapp.0l.de/auth.py?number=%s&auth_url=%s" % (self.session.legacyName, urllib.quote(token_url)) - short_url = utils.shorten(auth_url) - self.send("please visit this url to auth: %s" % short_url) - - self.send("waiting for authorization...") - token = self.__get_token(TOKEN_FILE) - if token: - self.send("got token: %s" % token) - self.__do_import(token) - self.session.updateRoster() - else: - self.send("timeout! please use \"\\import [token]\"") - else: - self.__do_import(token) - self.session.updateRoster() - def _sync(self): user = self.session.legacyName password = self.session.password @@ -170,33 +85,23 @@ class Bot(): def _help(self): self.send("""following bot commands are available: -\\help show this message +\\help show this message \\prune clear your buddylist -\\import [token] import buddies from Google \\sync sync your imported contacts with WhatsApp -\\fortune [database] give me a quote -\\groups print all attended groups -\\getgroups get current groups from WA following user commands are available: -\\lastseen request last online timestamp from buddy""") +\\lastseen request last online timestamp from buddy - def _fortune(self, database = '', prefix=''): - if os.path.exists("/usr/share/fortune/%s" % database): - fortune = os.popen('/usr/bin/fortune %s' % database).read() - self.send(prefix + fortune[:-1]) - else: - self.send("invalid database") - - def _welcome(self): - motd = open(MOTD_FILE, "r").read() - self.send(motd[:-1]) - self.call("fortune", ("disclaimer", "Disclaimer: ")) +following group commands are available +\\leave permanently leave group chat +\\groups print all attended groups +\\getgroups get current groups from WA""") def _prune(self): self.session.buddies.prune() self.session.updateRoster() self.send("buddy list cleared") + def _groups(self): for group in self.session.groups: buddy = self.session.groups[group].owner @@ -206,6 +111,7 @@ following user commands are available: nick = buddy self.send(self.session.groups[group].id + "@" + self.session.backend.spectrum_jid + " " + self.session.groups[group].subject + " Owner: " + nick ) + def _getgroups(self): #self.session.call("group_getGroups", ("participating",)) self.session.requestGroupsList(self.session._updateGroups) diff --git a/buddy.py b/buddy.py index 83c4739..0823163 100644 --- a/buddy.py +++ b/buddy.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -25,39 +24,15 @@ __status__ = "Prototype" from Spectrum2 import protocol_pb2 import logging -import threading - - -class Number(): - - def __init__(self, number, state, db): - self.number = number - self.db = db - self.state = state - - cur = self.db.cursor() - cur.execute("SELECT id FROM numbers WHERE number = %s AND state = %s", (self.number, self.state)) - if (cur.rowcount): - self.id = cur.fetchone()[0] - else: - cur.execute("REPLACE numbers (number, state) VALUES (%s, %s)", (self.number, self.state)) - self.db.commit() - self.id = cur.lastrowid - - def __str__(self): - return "%s (id=%s)" % (self.number, self.id) class Buddy(): - def __init__(self, owner, number, nick, groups, image_hash, id, db): - self.id = id - self.db = db - + def __init__(self, owner, number, nick, statusMsg, groups, image_hash): self.nick = nick self.owner = owner self.number = number self.groups = groups - self.image_hash = image_hash + self.image_hash = image_hash if image_hash is not None else "" self.statusMsg = "" self.lastseen = 0 self.presence = 0 @@ -69,121 +44,100 @@ class Buddy(): if image_hash is not None: self.image_hash = image_hash - groups = u",".join(groups).encode("latin-1") - cur = self.db.cursor() - cur.execute("UPDATE buddies SET nick = %s, groups = %s, image_hash = %s WHERE owner_id = %s AND buddy_id = %s", (self.nick, groups, self.image_hash, self.owner.id, self.number.id)) - self.db.commit() - - def delete(self): - cur = self.db.cursor() - cur.execute("DELETE FROM buddies WHERE owner_id = %s AND buddy_id = %s", (self.owner.id, self.number.id)) - self.db.commit() - self.id = None - - @staticmethod - def create(owner, number, nick, groups, image_hash, db): - groups = u",".join(groups).encode("latin-1") - cur = db.cursor() - cur.execute("REPLACE buddies (owner_id, buddy_id, nick, groups, image_hash) VALUES (%s, %s, %s, %s, %s)", (owner.id, number.id, nick, groups, image_hash)) - db.commit() - - return Buddy(owner, number, nick, groups, image_hash, cur.lastrowid, db) - def __str__(self): - return "%s (nick=%s, id=%s)" % (self.number, self.nick, self.id) + return "%s (nick=%s)" % (self.number, self.nick) class BuddyList(dict): - def __init__(self, owner, db): - self.db = db - self.owner = Number(owner, 1, db) - self.lock = threading.Lock() + def __init__(self, owner, backend, user, session): + self.owner = owner + self.backend = backend + self.session = session + self.user = user + self.logger = logging.getLogger(self.__class__.__name__) + self.synced = False + + def _load(self, buddies): + for buddy in buddies: + number = buddy.buddyName + nick = buddy.alias + statusMsg = buddy.statusMessage + groups = [g for g in buddy.group] + image_hash = buddy.iconHash + self[number] = Buddy(self.owner, number, nick, statusMsg, + groups, image_hash) + + self.logger.debug("Update roster") + +# old = self.buddies.keys() +# self.buddies.load() +# new = self.buddies.keys() +# contacts = new + contacts = self.keys() + + if self.synced == False: + self.session.sendSync(contacts, delta = False, interactive = True) + self.synced = True + +# add = set(new) - set(old) +# remove = set(old) - set(new) + +# self.logger.debug("Roster remove: %s", str(list(remove))) + self.logger.debug("Roster add: %s", str(list(contacts))) + +# for number in remove: +# self.backend.handleBuddyChanged(self.user, number, "", [], +# protocol_pb2.STATUS_NONE) +# self.backend.handleBuddyRemoved(self.user, number) +# self.unsubscribePresence(number) +# + for number in contacts: + buddy = self[number] + if number != 'bot': + self.backend.handleBuddyChanged(self.user, number, buddy.nick, + buddy.groups, protocol_pb2.STATUS_NONE, + iconHash = buddy.image_hash if buddy.image_hash is not None else "") + self.session.subscribePresence(number) - def load(self): - self.clear() - self.lock.acquire() - - cur = self.db.cursor() - cur.execute("""SELECT - b.id AS id, - n.number AS number, - b.nick AS nick, - b.groups AS groups, - n.state AS state, - b.image_hash AS image_hash - FROM buddies AS b - LEFT JOIN numbers AS n - ON b.buddy_id = n.id - WHERE - b.owner_id IN (%s, 0) - AND n.state >= 1 - ORDER BY b.owner_id DESC""", self.owner.id) - - for i in range(cur.rowcount): - id, number, nick, groups, state, image_hash = cur.fetchone() - self[number] = Buddy(self.owner, Number(number, state, self.db), nick.decode('latin1'), groups.split(","), image_hash, id, self.db) - self.lock.release() - + def load(self, buddies): + if self.session.loggedIn: + self._load(buddies) + else: + self.session.loginQueue.append(lambda: self._load(buddies)) def update(self, number, nick, groups, image_hash): - self.lock.acquire() if number in self: buddy = self[number] buddy.update(nick, groups, image_hash) else: - buddy = self.add(number, nick, groups, 1, image_hash) - self.lock.release() + self.session.sendSync([number], delta = True, interactive = True) + self.session.subscribePresence(number) + buddy = Buddy(self.owner, number, nick, "", groups, image_hash) + self[number] = buddy + self.logger.debug("Roster add: %s", buddy) + + if buddy.presence == 0: + status = protocol_pb2.STATUS_NONE + elif buddy.presence == 'unavailable': + status = protocol_pb2.STATUS_AWAY + else: + status = protocol_pb2.STATUS_ONLINE + self.backend.handleBuddyChanged(self.user, number, buddy.nick, + buddy.groups, status, + iconHash = buddy.image_hash if buddy.image_hash is not None else "") return buddy - def add(self, number, nick, groups = [], state = 0, image_hash = ""): - return Buddy.create(self.owner, Number(number, state, self.db), nick, groups, image_hash, self.db) - def remove(self, number): try: buddy = self[number] - self.lock.acquire() - buddy.delete() - self.lock.release() + del self[number] + self.backend.handleBuddyChanged(self.user, number, "", [], + protocol_pb2.STATUS_NONE) + self.backend.handleBuddyRemoved(self.user, number) + self.session.unsubscribePresence(number) +# TODO Sync remove return buddy except KeyError: return None - - def prune(self): - self.lock.acquire() - - cur = self.db.cursor() - cur.execute("DELETE FROM buddies WHERE owner_id = %s", self.owner.id) - self.db.commit() - self.lock.release() - - - def sync(self, user, password): - self.lock.acquire() - cur = self.db.cursor() - cur.execute("""SELECT - n.number AS number, - n.state AS state - FROM buddies AS r - LEFT JOIN numbers AS n - ON r.buddy_id = n.id - WHERE - r.owner_id = %s""", self.owner.id) - - # prefix every number with leading 0 to force internation format - numbers = dict([("+" + number, state) for number, state in cur.fetchall()]) - - if len(numbers) == 0: - return 0 - - result = WAContactsSyncRequest(user, password, numbers.keys()).send() - - using = 0 - for number in result['c']: - cur = self.db.cursor() - cur.execute("UPDATE numbers SET state = %s WHERE number = %s", (number['w'], number['n'])) - self.db.commit() - using += number['w'] - self.lock.release() - return using diff --git a/conf/motd b/conf/motd deleted file mode 100644 index e274226..0000000 --- a/conf/motd +++ /dev/null @@ -1,11 +0,0 @@ -Welcome to transWhat! - -===== NEWS ==== -- 03.06.13 transWhat service is born -- 18.06.13 major deployment of development version -- 07.09.15 transWhat is alive again. Now running with new Yowsup 2 library - -Type "\help" for a list of available commands. - -Visit http://github.com/stv0g/transwhat/ for more details. -Join xmpp://transwhat@conference.jabber.ccc.de to hangout and discuss. diff --git a/conf/schema.sql b/conf/schema.sql deleted file mode 100644 index f6fb0e7..0000000 --- a/conf/schema.sql +++ /dev/null @@ -1,31 +0,0 @@ -SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"; -SET time_zone = "+00:00"; - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; -/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; - - -CREATE TABLE IF NOT EXISTS `buddies` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `owner_id` int(11) NOT NULL, - `buddy_id` int(11) NOT NULL, - `nick` varchar(255) NOT NULL, - `groups` varchar(255) NOT NULL, - `image_hash` varchar(40), - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -CREATE TABLE IF NOT EXISTS `numbers` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `number` varchar(32) NOT NULL, - `picture` blob NOT NULL, - `state` int(11) NOT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `number` (`number`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/conf/spectrum_dev.cfg b/conf/spectrum_dev.cfg deleted file mode 100644 index bdbd833..0000000 --- a/conf/spectrum_dev.cfg +++ /dev/null @@ -1,29 +0,0 @@ -[service] -server_mode = 1 - -user = spectrum -group = spectrum - -jid = whatsapp-dev.0l.de - -server = 0.0.0.0 -port = 5221 - -cert = /home/stv0g/files/whatsapp/transwhat/conf/spectrum_cert.p12 - -backend_host = localhost -backend = /home/stv0g/files/whatsapp/transwhat/transwhat.py -users_per_backend = 10 -more_resources = 1 - -admin_jid = 4917696978528@whatsapp.0l.de - -[identity] -name = transWhat - -type = xmpp -category = gateway - -[logging] -config = /etc/spectrum2/logging.cfg -backend_config = /etc/spectrum2/backend-logging.cfg diff --git a/conf/spectrum_prod.cfg b/conf/spectrum_prod.cfg deleted file mode 100644 index da18a6b..0000000 --- a/conf/spectrum_prod.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[service] -server_mode = 1 - -user = spectrum -group = spectrum - -jid = whatsapp.0l.de - -server = 0.0.0.0 -port = 5222 - -cert = /opt/transwhat/conf/spectrum_cert.p12 - -backend_host = localhost -backend = /opt/transwhat/transwhat.py -users_per_backend = 10 - -admin_jid = 4917696978528@whatsapp.0l.de - -[identity] -name = transWhat - -type = xmpp -category = gateway - -[logging] -config = /etc/spectrum2/logging.cfg -backend_config = /etc/spectrum2/backend-logging.cfg diff --git a/constants.py.sample b/constants.py.sample deleted file mode 100644 index 958c2e9..0000000 --- a/constants.py.sample +++ /dev/null @@ -1,34 +0,0 @@ -__author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" -__license__ = "GPLv3" -__maintainer__ = "Steffen Vogel" -__email__ = "post@steffenvogel.de" -__status__ = "Prototype" - -""" - This file is part of transWhat - - transWhat 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 - any later version. - - transwhat 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 transWhat. If not, see . -""" - -DB_HOST = "localhost" -DB_USER = "" -DB_PASS = "" -DB_TABLE = "transwhat" - -BASE_PATH = "/opt/transwhat" - -TOKEN_FILE = BASE_PATH + "/logs/tokens" -MOTD_FILE = BASE_PATH + "/conf/motd" -REQUESTS_FILE = BASE_PATH + "/logs/requests" \ No newline at end of file diff --git a/group.py b/group.py index f5c703e..8708fe7 100644 --- a/group.py +++ b/group.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -29,6 +28,7 @@ class Group(): self.subject = subject self.subjectOwner = subjectOwner self.owner = owner + self.joined = False self.nick = "me" - self.participants = { } + self.participants = [] diff --git a/reader.py b/reader.py index 9d17c9a..1137973 100644 --- a/reader.py +++ b/reader.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat diff --git a/session.py b/session.py index 7f14441..6dfed66 100644 --- a/session.py +++ b/session.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -28,7 +27,6 @@ import urllib import time from PIL import Image -import MySQLdb import sys import os @@ -42,7 +40,6 @@ from buddy import BuddyList from threading import Timer from group import Group from bot import Bot -from constants import * from yowsupwrapper import YowsupApp @@ -57,14 +54,11 @@ class MsgIDs: class Session(YowsupApp): - def __init__(self, backend, user, legacyName, extra, db): + def __init__(self, backend, user, legacyName, extra): super(Session, self).__init__() self.logger = logging.getLogger(self.__class__.__name__) self.logger.info("Created: %s", legacyName) - #self.db = db - self.db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE) - self.backend = backend self.user = user self.legacyName = legacyName @@ -74,12 +68,14 @@ class Session(YowsupApp): self.groups = {} self.gotGroupList = False + # Functions to exectute when logged in via yowsup + self.loginQueue = [] self.joinRoomQueue = [] self.presenceRequested = [] self.offlineQueue = [] self.msgIDs = { } self.groupOfflineQueue = { } - self.shouldBeConnected = False + self.loggedIn = False self.timer = None self.password = None @@ -87,13 +83,13 @@ class Session(YowsupApp): self.lastMsgId = None self.synced = False - self.buddies = BuddyList(self.legacyName, self.db) + self.buddies = BuddyList(self.legacyName, self.backend, self.user, self) self.bot = Bot(self) self.imgMsgId = None - self.imgPath = "" - self.imgBuddy = None - self.imgType = "" + self.imgPath = "" + self.imgBuddy = None + self.imgType = "" def __del__(self): # handleLogoutRequest @@ -102,6 +98,7 @@ class Session(YowsupApp): def logout(self): self.logger.info("%s logged out", self.user) super(Session, self).logout() + self.loggedIn = False def login(self, password): self.logger.info("%s attempting login", self.user) @@ -132,39 +129,6 @@ class Session(YowsupApp): '\n'.join(text) + '\nIf you do not join them you will lose messages' #self.bot.send(message) - def updateRoster(self): - self.logger.debug("Update roster") - - old = self.buddies.keys() - self.buddies.load() - new = self.buddies.keys() - contacts = new - - if self.synced == False: - self.sendSync(contacts, delta = False, interactive = True) - self.synced = True - - add = set(new) - set(old) - remove = set(old) - set(new) - - self.logger.debug("Roster remove: %s", str(list(remove))) - self.logger.debug("Roster add: %s", str(list(add))) - - for number in remove: - self.backend.handleBuddyChanged(self.user, number, "", [], - protocol_pb2.STATUS_NONE) - self.backend.handleBuddyRemoved(self.user, number) - self.unsubscribePresence(number) - - for number in add: - buddy = self.buddies[number] - self.subscribePresence(number) - self.backend.handleBuddyChanged(self.user, number, buddy.nick, - buddy.groups, protocol_pb2.STATUS_NONE, - iconHash = buddy.image_hash if buddy.image_hash is not None else "") - - #self.requestLastSeen(number, self._lastSeen) - def _updateGroups(self, response, request): self.logger.debug('Received groups list %s', response) groups = response.getGroups() @@ -223,9 +187,18 @@ class Session(YowsupApp): self.backend.handleRoomNicknameChanged( self.user, self._shortenGroupId(room), group.subject ) + group.joined = True else: self.logger.warn("Room doesn't exist: %s", room) + def leaveRoom(self, room): + if room in self.groups: + self.logger.info("Leaving room: %s room=%s", self.legacyName, room) + group = self.groups[room] + group.joined = False + else: + self.logger.warn("Room doesn't exist: %s. Unable to leave.", room) + def _refreshParticipants(self, room): group = self.groups[room] for jid in group.participants: @@ -269,17 +242,19 @@ class Session(YowsupApp): #self.bot.call("welcome") self.initialized = True self.sendPresence(True) - self.updateRoster() + for func in self.loginQueue: + func() self.logger.debug('Requesting groups list') self.requestGroupsList(self._updateGroups) + self.loggedIn = True # Called by superclass def onAuthFailed(self, reason): self.logger.info("Auth failed: %s (%s)", self.user, reason) self.backend.handleDisconnected(self.user, 0, reason) self.password = None - self.shouldBeConnected = False + self.loggedIn = False # Called by superclass def onDisconnect(self): @@ -296,7 +271,7 @@ class Session(YowsupApp): buddy = self.buddies[_from.split('@')[0]] #self.backend.handleBuddyChanged(self.user, buddy.number.number, # buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE) - self.backend.handleMessageAck(self.user, buddy.number.number, self.msgIDs[_id].xmppId) + self.backend.handleMessageAck(self.user, buddy.number, self.msgIDs[_id].xmppId) self.msgIDs[_id].cnt = self.msgIDs[_id].cnt +1 if self.msgIDs[_id].cnt == 2: del self.msgIDs[_id] @@ -475,12 +450,34 @@ class Session(YowsupApp): self.bot.send("You have been added to group: %s@%s (%s)" % (self._shortenGroupId(room), subject, self.backend.spectrum_jid)) + # Called by superclass def onParticipantsAddedToGroup(self, group): self.logger.debug("Participants added to group: %s", group) room = group.getGroupId().split('@')[0] self.groups[room].participants.extend(group.getParticipants()) self._refreshParticipants(room) + # Called by superclass + def onParticipantsRemovedFromGroup(self, room, participants): + self.logger.debug("Participants removed from group: %s, %s", + room, participants) + group = self.groups[room] + for jid in participants: + group.participants.remove(jid) + buddy = jid.split("@")[0] + try: + nick = self.buddies[buddy].nick + except KeyError: + nick = buddy + if nick == "": + nick = buddy + if buddy == self.legacyName: + nick = group.nick + flags = protocol_pb2.PARTICIPANT_FLAG_NONE + self.backend.handleParticipantChanged( + self.user, nick, self._shortenGroupId(room), flags, + protocol_pb2.STATUS_NONE, buddy) + def onPresenceReceived(self, _type, name, jid, lastseen): self.logger.info("Presence received: %s %s %s %s", _type, name, jid, lastseen) buddy = jid.split("@")[0] @@ -492,8 +489,8 @@ class Session(YowsupApp): if (lastseen == str(buddy.lastseen)) and (_type == buddy.presence): return - - if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")): + + if ((lastseen != "deny") and (lastseen != None) and (lastseen != "none")): buddy.lastseen = int(lastseen) if (_type == None): buddy.lastseen = time.time() @@ -502,22 +499,22 @@ class Session(YowsupApp): timestamp = time.localtime(buddy.lastseen) statusmsg = buddy.statusMsg + time.strftime("\n Last seen: %a, %d %b %Y %H:%M:%S", timestamp) - + if _type == "unavailable": self.onPresenceUnavailable(buddy, statusmsg) else: self.onPresenceAvailable(buddy, statusmsg) - - def onPresenceAvailable(self, buddy, statusmsg): + + def onPresenceAvailable(self, buddy, statusmsg): self.logger.info("Is available: %s", buddy) - self.backend.handleBuddyChanged(self.user, buddy.number.number, + self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick, buddy.groups, protocol_pb2.STATUS_ONLINE, statusmsg, buddy.image_hash) def onPresenceUnavailable(self, buddy, statusmsg): self.logger.info("Is unavailable: %s", buddy) - self.backend.handleBuddyChanged(self.user, buddy.number.number, + self.backend.handleBuddyChanged(self.user, buddy.number, buddy.nick, buddy.groups, protocol_pb2.STATUS_AWAY, statusmsg, buddy.image_hash) # spectrum RequestMethods @@ -540,6 +537,11 @@ class Session(YowsupApp): self.legacyName, sender, message) message = message.encode("utf-8") + # FIXME: Fragile, should pass this in to onDlerror + self.dlerror_message = message + self.dlerror_sender = sender + self.dlerror_ID = ID + # End Fragile if sender == "bot": self.bot.parse(message) @@ -555,15 +557,19 @@ class Session(YowsupApp): self.msgIDs[waId] = MsgIDs( ID, waId) else: room = sender - try: - group = self.groups[self._lengthenGroupId(room)] - self.logger.info("Group Message from %s to %s Groups: %s", - group.nick , group , self.groups) - self.backend.handleMessage( - self.user, room, message.decode('utf-8'), group.nick - ) - except KeyError: - self.logger.error('Group not found: %s', room) + if message[0] == '\\' and message[:1] != '\\\\': + self.logger.debug("Executing command %s in %s", message, room) + self.executeCommand(message, room) + else: + try: + group = self.groups[self._lengthenGroupId(room)] + self.logger.debug("Group Message from %s to %s Groups: %s", + group.nick , group , self.groups) + self.backend.handleMessage( + self.user, room, message.decode('utf-8'), group.nick + ) + except KeyError: + self.logger.error('Group not found: %s', room) if (".jpg" in message.lower()) or (".webp" in message.lower()): if (".jpg" in message.lower()): @@ -573,6 +579,7 @@ class Session(YowsupApp): self.imgMsgId = ID self.imgBuddy = room + "@g.us" + downloader = MediaDownloader(self.onDlsuccess, self.onDlerror) downloader.download(message) #self.imgMsgId = ID @@ -620,6 +627,29 @@ class Session(YowsupApp): self.logger.info("WA Message send to %s with ID %s", buddy, waId) #self.sendTextMessage(sender + '@s.whatsapp.net', message) + + def executeCommand(self, command, room): + if command == '\\leave': + self.logger.debug("Leaving room %s", room) + # Leave group on whatsapp side + self.leaveGroup(room) + # Delete Room on spectrum side + group = self.groups[room] + for jid in group.participants: + buddy = jid.split("@")[0] + try: + nick = self.buddies[buddy].nick + except KeyError: + nick = buddy + if nick == "": + nick = buddy + if buddy == self.legacyName: + nick = group.nick + flags = protocol_pb2.PARTICIPANT_FLAG_ROOM_NOT_FOUND + self.backend.handleParticipantChanged( + self.user, nick, self._shortenGroupId(room), flags, + protocol_pb2.STATUS_NONE, buddy) + del self.groups[room] def _requestLastSeen(self, buddy): @@ -681,8 +711,21 @@ class Session(YowsupApp): else: self.logger.debug("Group message sent from %s (%s) to %s: %s", buddy, nick, room, messageContent) - self.backend.handleMessage(self.user, self._shortenGroupId(room), - messageContent, nick, "", timestamp) + try: + group = self.groups[room] + if group.joined: + self.backend.handleMessage(self.user,room, messageContent, + nick, "", timestamp) + else: + self.bot.send("You have received a message in group: %s@%s" + % (room, self.backend.spectrum_jid)) + self.bot.send("Join the group in order to reply") + self.bot.send("%s: %s" % (nick, messageContent)) + except KeyError: + self.logger.warn("Group is not in group list") + self.backend.handleMessage(self.user, self._shortenGroupId(room), + messageContent, nick, "", timestamp) + def changeStatus(self, status): if status != self.status: @@ -712,19 +755,19 @@ class Session(YowsupApp): msg = self.offlineQueue.pop(0) self.backend.handleMessage(self.user, msg[0], msg[1], "", "", msg[2]) + # Called when user logs in to initialize the roster + def loadBuddies(self, buddies): + self.buddies.load(buddies) + # also for adding a new buddy def updateBuddy(self, buddy, nick, groups, image_hash = None): if buddy != "bot": self.buddies.update(buddy, nick, groups, image_hash) - if self.initialized == True: - self.updateRoster() def removeBuddy(self, buddy): if buddy != "bot": self.logger.info("Buddy removed: %s", buddy) self.buddies.remove(buddy) - self.updateRoster() - def requestVCard(self, buddy, ID=None): def onSuccess(response, request): @@ -740,6 +783,68 @@ class Session(YowsupApp): self.logger.debug('Requesting profile picture of %s', buddy) self.requestProfilePicture(buddy, onSuccess = onSuccess) + def onDlsuccess(self, path): + self.logger.info("Success: Image downloaded to %s", path) + os.rename(path, path+"."+self.imgType) + if self.imgType != "jpg": + im = Image.open(path+"."+self.imgType) + im.save(path+".jpg") + self.imgPath = path+".jpg" + statinfo = os.stat(self.imgPath) + name=os.path.basename(self.imgPath) + self.logger.info("Buddy %s",self.imgBuddy) + self.image_send(self.imgBuddy, self.imgPath) + + #self.logger.info("Sending picture %s of size %s with name %s",self.imgPath, statinfo.st_size, name) + #mtype = "image" + + #sha1 = hashlib.sha256() + #fp = open(self.imgPath, 'rb') + #try: + # sha1.update(fp.read()) + # hsh = base64.b64encode(sha1.digest()) + # self.call("media_requestUpload", (hsh, mtype, os.path.getsize(self.imgPath))) + #finally: + # fp.close() + + + def onDlerror(self): + self.logger.info("Download Error. Sending message as is.") + waId = self.sendTextMessage(self.dlerror_sender + '@s.whatsapp.net', self.dlerror_message) + self.msgIDs[waId] = MsgIDs(self.dlerror_ID, waId) + + + def _doSendImage(self, filePath, url, to, ip = None, caption = None): + waId = self.doSendImage(filePath, url, to, ip, caption) + self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) + + def _doSendAudio(self, filePath, url, to, ip = None, caption = None): + waId = self.doSendAudio(filePath, url, to, ip, caption) + self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) + + + + + def createThumb(self, size=100, raw=False): + img = Image.open(self.imgPath) + width, height = img.size + img_thumbnail = self.imgPath + '_thumbnail' + + if width > height: + nheight = float(height) / width * size + nwidth = size + else: + nwidth = float(width) / height * size + nheight = size + + img.thumbnail((nwidth, nheight), Image.ANTIALIAS) + img.save(img_thumbnail, 'JPEG') + + with open(img_thumbnail, 'rb') as imageFile: + raw = base64.b64encode(imageFile.read()) + + return raw + # Not used def onLocationReceived(self, messageId, jid, name, preview, latitude, longitude, receiptRequested, isBroadcast): buddy = jid.split("@")[0] @@ -777,63 +882,3 @@ class Session(YowsupApp): - def onDlsuccess(self, path): - self.logger.info("Success: Image downloaded to %s", path) - os.rename(path, path+"."+self.imgType) - if self.imgType != "jpg": - im = Image.open(path+"."+self.imgType) - im.save(path+".jpg") - self.imgPath = path+".jpg" - statinfo = os.stat(self.imgPath) - name=os.path.basename(self.imgPath) - self.logger.info("Buddy %s",self.imgBuddy) - self.image_send(self.imgBuddy, self.imgPath) - - #self.logger.info("Sending picture %s of size %s with name %s",self.imgPath, statinfo.st_size, name) - #mtype = "image" - - #sha1 = hashlib.sha256() - #fp = open(self.imgPath, 'rb') - #try: - # sha1.update(fp.read()) - # hsh = base64.b64encode(sha1.digest()) - # self.call("media_requestUpload", (hsh, mtype, os.path.getsize(self.imgPath))) - #finally: - # fp.close() - - - def onDlerror(self): - self.logger.info("Download Error") - - - def _doSendImage(self, filePath, url, to, ip = None, caption = None): - waId = self.doSendImage(filePath, url, to, ip, caption) - self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) - - def _doSendAudio(self, filePath, url, to, ip = None, caption = None): - waId = self.doSendAudio(filePath, url, to, ip, caption) - self.msgIDs[waId] = MsgIDs(self.imgMsgId, waId) - - - - - def createThumb(self, size=100, raw=False): - img = Image.open(self.imgPath) - width, height = img.size - img_thumbnail = self.imgPath + '_thumbnail' - - if width > height: - nheight = float(height) / width * size - nwidth = size - else: - nwidth = float(width) / height * size - nheight = size - - img.thumbnail((nwidth, nheight), Image.ANTIALIAS) - img.save(img_thumbnail, 'JPEG') - - with open(img_thumbnail, 'rb') as imageFile: - raw = base64.b64encode(imageFile.read()) - - return raw - diff --git a/transwhat.py b/transwhat.py index 5875dc4..aa58ad9 100755 --- a/transwhat.py +++ b/transwhat.py @@ -1,11 +1,10 @@ #!/usr/bin/python __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -29,7 +28,6 @@ import traceback import logging import asyncore import sys, os -import MySQLdb import e4u import threading import Queue @@ -39,7 +37,6 @@ sys.path.insert(0, os.getcwd()) from Spectrum2.iochannel import IOChannel from whatsappbackend import WhatsAppBackend -from constants import * from yowsup.common import YowConstants from yowsup.stacks import YowStack @@ -75,10 +72,11 @@ def connectionClosed(): closed = True # Main -db = MySQLdb.connect(DB_HOST, DB_USER, DB_PASS, DB_TABLE) io = IOChannel(args.host, args.port, handleTransportData, connectionClosed) -plugin = WhatsAppBackend(io, db, args.j) +plugin = WhatsAppBackend(io, args.j) + +plugin.handleBackendConfig('features', 'send_buddies_on_login', 1) while True: try: diff --git a/utils.py b/utils.py index 69a0c44..f6d120c 100644 --- a/utils.py +++ b/utils.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat diff --git a/whatsappbackend.py b/whatsappbackend.py index 0de395d..a2ddc9e 100644 --- a/whatsappbackend.py +++ b/whatsappbackend.py @@ -1,9 +1,8 @@ __author__ = "Steffen Vogel" -__copyright__ = "Copyright 2013, Steffen Vogel" +__copyright__ = "Copyright 2015, Steffen Vogel" __license__ = "GPLv3" __maintainer__ = "Steffen Vogel" __email__ = "post@steffenvogel.de" -__status__ = "Prototype" """ This file is part of transWhat @@ -30,11 +29,10 @@ from session import Session import logging class WhatsAppBackend(SpectrumBackend): - def __init__(self, io, db, spectrum_jid): + def __init__(self, io, spectrum_jid): SpectrumBackend.__init__(self) self.logger = logging.getLogger(self.__class__.__name__) self.io = io - self.db = db self.sessions = { } self.spectrum_jid = spectrum_jid # Used to prevent duplicate messages @@ -46,7 +44,7 @@ class WhatsAppBackend(SpectrumBackend): def handleLoginRequest(self, user, legacyName, password, extra): self.logger.debug("handleLoginRequest(user=%s, legacyName=%s)", user, legacyName) if user not in self.sessions: - self.sessions[user] = Session(self, user, legacyName, extra, self.db) + self.sessions[user] = Session(self, user, legacyName, extra) if user not in self.lastMessage: self.lastMessage[user] = {} @@ -78,11 +76,23 @@ class WhatsAppBackend(SpectrumBackend): self.logger.debug("handleJoinRoomRequest(user=%s, room=%s, nickname=%s)", user, room, nickname) self.sessions[user].joinRoom(room, nickname) + def handleLeaveRoomRequest(self, user, room): + self.logger.debug("handleLeaveRoomRequest(user=%s, room=%s)", user, room) + self.sessions[user].leaveRoom(room) + def handleStatusChangeRequest(self, user, status, statusMessage): self.logger.debug("handleStatusChangeRequest(user=%s, status=%d, statusMessage=%s)", user, status, statusMessage) self.sessions[user].changeStatusMessage(statusMessage) self.sessions[user].changeStatus(status) + def handleBuddies(self, buddies): + """Called when user logs in. Used to initialize roster.""" + self.logger.debug("handleBuddies(buddies=%s)", buddies) + buddies = [b for b in buddies.buddy] + if len(buddies) > 0: + user = buddies[0].userName + self.sessions[user].loadBuddies(buddies) + def handleBuddyUpdatedRequest(self, user, buddy, nick, groups): self.logger.debug("handleBuddyUpdatedRequest(user=%s, buddy=%s, nick=%s, groups=%s)", user, buddy, nick, str(groups)) self.sessions[user].updateBuddy(buddy, nick, groups) @@ -112,9 +122,6 @@ class WhatsAppBackend(SpectrumBackend): def handleBuddyBlockToggled(self, user, buddy, blocked): pass - def handleLeaveRoomRequest(self, user, room): - pass - def handleVCardUpdatedRequest(self, user, photo, nickname): pass @@ -138,7 +145,7 @@ class WhatsAppBackend(SpectrumBackend): pass def handleMessageAckRequest(self, user, legacyName, ID = 0): - self.logger.info("Meassage ACK request for %s !!",leagcyName) + self.logger.info("Meassage ACK request for %s !!",legacyName) def sendData(self, data): self.io.sendData(data) diff --git a/yowsupwrapper.py b/yowsupwrapper.py index 978da3f..d7c3a79 100644 --- a/yowsupwrapper.py +++ b/yowsupwrapper.py @@ -61,7 +61,6 @@ class YowsupApp(object): YowContactsIqProtocolLayer, YowChatstateProtocolLayer, YowCallsProtocolLayer, - YowMediaProtocolLayer, YowPrivacyProtocolLayer, YowProfilesProtocolLayer, YowGroupsProtocolLayer, @@ -217,6 +216,7 @@ class YowsupApp(object): - phone_number: (str) The cellphone number of the person to subscribe to """ + self.logger.debug("Subscribing to Presence updates from %s", (phone_number)) jid = phone_number + '@s.whatsapp.net' entity = SubscribePresenceProtocolEntity(jid) self.sendEntity(entity) @@ -233,6 +233,16 @@ class YowsupApp(object): entity = UnsubscribePresenceProtocolEntity(jid) self.sendEntity(entity) + def leaveGroup(self, group): + """ + Permanently leave a WhatsApp group + + Args: + - group: (str) the group id (e.g. 27831788123-144024456) + """ + entity = LeaveGroupsIqProtocolEntity([group + '@g.us']) + self.sendEntity(entity) + def setStatus(self, statusText): """ Send status to whatsapp @@ -492,6 +502,15 @@ class YowsupApp(object): """Called when participants have been added to a group""" pass + def onParticipantsRemovedFromGroup(self, group, participants): + """Called when participants have been removed from a group + + Args: + - group: (str) id of the group (e.g. 27831788123-144024456) + - participants: (list) jids of participants that are removed + """ + pass + def sendEntity(self, entity): """Sends an entity down the stack (as if YowsupAppLayer called toLower)""" self.stack.broadcastEvent(YowLayerEvent(YowsupAppLayer.TO_LOWER_EVENT, @@ -587,15 +606,21 @@ class YowsupAppLayer(YowInterfaceLayer): """ Sends ack automatically """ - self.logger.debug("Received notification: %s", entity) + self.logger.debug("Received notification (%s): %s", type(entity), entity) self.toLower(entity.ack()) if isinstance(entity, CreateGroupsNotificationProtocolEntity): self.caller.onAddedToGroup(entity) elif isinstance(entity, AddGroupsNotificationProtocolEntity): self.caller.onParticipantsAddedToGroup(entity) + elif isinstance(entity, RemoveGroupsNotificationProtocolEntity): + self.caller.onParticipantsRemovedFromGroup( + entity.getGroupId().split('@')[0], + entity.getParticipants().keys() + ) @ProtocolEntityCallback('message') def onMessageReceived(self, entity): + self.logger.debug("Received Message: %s", entity) if entity.getType() == MessageProtocolEntity.MESSAGE_TYPE_TEXT: self.caller.onTextMessage( entity._id,