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,