diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 72364f9..0000000 --- a/.gitignore +++ /dev/null @@ -1,89 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# Spyder project settings -.spyderproject - -# Rope project settings -.ropeproject diff --git a/.pydevproject b/.pydevproject index 96a3f0e..5efd75b 100644 --- a/.pydevproject +++ b/.pydevproject @@ -2,7 +2,7 @@ DJANGO_MANAGE_LOCATION -weixin/manage.py +manage.py /${PROJECT_DIR_NAME} diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 599f2cb..0000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding//library/cron/checklogin.py=utf-8 diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 9cecc1d..0000000 --- a/LICENSE +++ /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. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 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: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md index 722a38b..bb870d3 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,78 @@ # lykchat信息发送系统 -lykchat信息发送系统是Python3开发的,通过模拟微信网页端,基于个人微信号,为系统管理人员提供信息发送工具。 +lykchat信息发送系统是Python3开发的,通过模拟实施聊天工具(暂时只实现微信),为系统管理人员提供信息发送工具。 -实现的功能有用户登录管理、微信登陆管理和微信信息发送功能。 +# v3.0更新说明 +## 新增功能 -# 通知 -在2017年7月20日晚上,疑似微信web端做了调整,web上不在显示好友微信号,所以即日使用微信号发送信息可能提示无法找到好友等错误提示。 +### 引用NoSQL -解决办法: + mongo存储账号信息等持久性数据 + redis存储查询缓存、微信登陆信息等缓存信息 - 1、使用好友昵称来发送信息 - 2、使用备注名来发送信息 - 但必须只能是数字、字母、符号等,不能为图片等 +### 优化前端 + + 重写用户界面 + 优化接口参数 -## 特点 +### 微信 - 1、简单高效 - 基于个人微信号,模拟微信web端,部署和维护简单 - web管理页面实现可视化管理微信登陆 - 接口采用URL,简化调用复杂度,返回结果均为json格式 - 2、信息共享 - 通过共享用户session和微信登陆信息,保证系统长期稳定运行 - 3、7*24不间断服务 - 计划任务定时检查微信登陆状态,微信保持登陆超过20天 - 4、支持发送多媒体信息 - 除了支持发送纯文字信息外,还支持发送图片、视频、文件等信息 - 5、用户管理 - 通过用户隔离微信个人号,不同用户管理不同微信号 - 用户密码分为管理密码和接口密码,保证用户信息安全性 - 6、微信信息安全 - 不会监控和存储微信聊天信息 - 不会增加和删除好友 + 同用户管理多个微信号,可以实现冗余和备份功能 + 接口发送信息,试图向该用户下在线所有微信号发送信息,只要发送文字成功后停止发送 +### 日志管理 -## 截图 + 用户操作记录日志,可以通过页面查看 + 代码断点日志,便于管理员查看 -管理页面--功能展示 +## 修复bug -![等待扫码 截图](https://raw.githubusercontent.com/lykops/lykchat/master/doc/web页面--功能说明.jpg) + 1、解决不能发送超过512K的文件,目前可以发送大于0(即空文件),小于5M的文件 + 2、解决不能发送文件名含有中文的文件 + 注意:使用固化文件名,对方显示的文件名为lykchat[.原来文件名后缀名] + 3、优化代码 + 4、不再使用Nginx作为web服务器 +## 升级说明 -管理页面--微信登陆时长 +这次版本变化太大,从部署到使用均发生很大变化。 -![微信登陆时长 截图](https://raw.githubusercontent.com/lykops/lykchat/V2.1.0/doc/微信登陆时间超过1天.jpg) - -接口-发送信息成功 +接口参数、返回值变化大,无法兼容之前版本。 -![发送信息成功 截图](https://raw.githubusercontent.com/lykops/lykchat/master/doc/接口-发送信息成功.jpg) - - -## 发送信息接口使用说明 -[https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E](https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E "发送信息接口") - -## 模块和工作流程 -[https://github.com/lykops/lykchat/wiki/%E6%A8%A1%E5%9D%97%E5%92%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B](https://github.com/lykops/lykchat/wiki/%E6%A8%A1%E5%9D%97%E5%92%8C%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B "模块和工作流程") - -## 安装手册 -[https://github.com/lykops/lykchat/wiki/%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C](https://github.com/lykops/lykchat/wiki/%E5%AE%89%E8%A3%85%E6%89%8B%E5%86%8C "安装手册") - -## ChangeLog -[https://github.com/lykops/lykchat/wiki/ChangeLog](https://github.com/lykops/lykchat/wiki/ChangeLog "ChangeLog") - -## 说明 - - 1、作者尽可能通过严谨测试来验证系统功能,但由于专业水平有限,无法避免出现bug。 - 2、该项目是基于微信web端进行开发的 - 由于微信web端参数经常变动,可能会导致系统异常。 - 如作者发现该问题,将会更新和修复。 - 3、该项目开发的目的:为监控系统提供一个通过微信发送告警信息。 - 所以该项目只实现了微信的登陆、接受和发送信息这三个功能,其他功能暂不考虑。 - 建议使用一个独立的微信号,避免在登陆过程中在微信web端、PC客户端登陆,也不要在手机端退出web登陆。 - 4:该项目为个人开源项目,免费开源。 +# 说明 + + 1、开发者尽可能通过严谨测试来验证系统功能,但由于专业水平有限,无法避免出现bug。 + 2、该项目是基于微信web端进行开发的。由于微信web端参数变动,可能会导致系统异常。 + 开发者将持续更新和修复。 + 3、该项目开发初衷:实时接收监控系统的告警信息。 + 故该项目只实现了微信的登陆、发送信息这两个功能,其他功能暂不考虑。 + 4、建议使用一个独立的微信号 + 避免在登陆过程中在微信web端、PC客户端登陆,也不要在手机端退出web登陆。 + 5、由于微信端限制,登陆超过30天后,会出现自动掉线,甚至小部分用户反馈被微信端封杀。 + 建议每隔两个星期重新登陆一次 + 6、该项目为个人开源项目,免费开源。 请勿使用该系统发送非法、不良信息。 - 在使用过程中,如有任何问题,作者不承担任何责任。 - 5、联系方式: - 微信:lyk-ops - 邮箱:liyingke112@126.com + 开发者不承担任何责任。 + +# 使用说明 + +## 部署手册 + +[部署](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E9%83%A8%E7%BD%B2%E6%89%8B%E5%86%8C.md "部署") + +[配置](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E9%85%8D%E7%BD%AE%E6%89%8B%E5%86%8C.md "配置") + +## 使用手册 + +[登录](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E7%99%BB%E9%99%86.md "登录") + + +## 微信使用 + +[微信管理](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E5%BE%AE%E4%BF%A1%E7%AE%A1%E7%90%86.md "微信管理") + +[微信接口](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E5%BE%AE%E4%BF%A1%E6%8E%A5%E5%8F%A3.md "接口使用") +## 日志管理 +[日志管理](https://github.com/lykops/lykchat/blob/v3.0/doc/help/%E6%97%A5%E5%BF%97%E7%AE%A1%E7%90%86.md "日志管理") diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md deleted file mode 100644 index a267461..0000000 --- a/doc/ChangeLog.md +++ /dev/null @@ -1,32 +0,0 @@ -# V2.1.0 -## 升级内容 - 新增发送图片、视频、文件等多媒体信息 -## 从v2.0.0更新步骤 - 1、下载最新版本 - 2、安装依赖包 - /usr/local/python36/bin/pip3 install -r /opt/lykchat/install/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple - 3、修改配置文件 - 配置文件library/config/wechat.py - 新增上传文件最大数max_upload_size(默认为5M,建议不要上传文件太大,导致访问接口超时) - 4、修改nginx的上传文件最大值 - client_max_body_size 10m; -## 说明事项 - django默认启用防CSRF(Cross-site request forgery跨站请求伪造),导致无法使用post方法调用该接口,所以作者强制关闭了防csrf功能。 - 如果你觉得有安全隐患,又不需要发送多媒体文件,请下载2.0版本:https://codeload.github.com/lykops/lykchat/zip/master - -# V2.0.0 - - 1、修复bug: - 微信登陆时间超过12小时自动退出,测试过程中测得最大登陆时长20天 - 2、完善功能: - 1)、微信会话保持机制: - 保存位置:之前保存在数据库中,修改为数据库只记录用户名,所有信息保持到文件中,减少数据库的查询、写入、加解密压力 - 动态更新微信登陆信息 - 调整会话信息内容 - 2)、优化微信检测登陆流程,大大缩短各个页面执行时间 - 3)、完善获取好友流程 - 3、新增功能: - 1)、增加用户管理机制 - 2)、好友信息缓存机制 - 4、取消功能: - 接受和处理新信息 \ No newline at end of file diff --git a/doc/FQA.md b/doc/FQA.md deleted file mode 100644 index 3bf83c7..0000000 --- a/doc/FQA.md +++ /dev/null @@ -1,11 +0,0 @@ -# Q:管理页面和接口执行慢;微信号经常退出 - 该系统严重依赖微信web端工作的。 - 管理页面和接口执行效率,很大程度上依赖于程序和微信服务器通讯质量 - 如果通讯质量差,直接导致执行慢,甚至造成微信退出等情况 - 如果微信web端接口和参数变更,可能会导致该项目无法使用 - -# Q:管理页面无法显示自己微信号? - 微信web端不会展示自己的微信号 - 该系统中的显示的自己微信号是通过群信息获取的 - 测试表明:很有可能无法获取到自己的微信号 - 解决办法是没有群的创建一个群,在任意群中发送信息 diff --git a/doc/example/test_sendfile.py b/doc/example/test_sendfile.py new file mode 100644 index 0000000..14436a0 --- /dev/null +++ b/doc/example/test_sendfile.py @@ -0,0 +1,67 @@ +import json +import os, random, sys, requests + +from requests_toolbelt.multipart.encoder import MultipartEncoder + + +url = 'http://127.0.0.1/wechat/sendmsg' +argvstr = sys.argv[1:] +argv_dict = {} +for argv in argvstr : + try : + argv = argv.split("=") + argv_dict[argv[0]] = argv[1] + except : + pass + +parameter_dict = { + 'username' : '用户' , + 'pwd' : '接口密码,注意不等于登陆密码' , + 'friend':'接受者的昵称、微信号、备注名的其中一个,不能为空', + 'content':'发送内容,不能为空', + 'file':'文件绝对路径,可以为空', + 'wxid':'发送者的昵称,能为空', + + '例子': + ''' + /usr/local/python36/bin/python3 /opt/lykchat/test_sendfile.py username=zabbix pwd=123456 friend=lykops content=恭喜发财 file=/root/b.jpg + ''' + } + +headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:50.0) Gecko/20100101 Firefox/50.0', + 'Referer': url + } + +for key in ['username', 'pwd', 'friend', 'content'] : + if key not in argv_dict : + print(parameter_dict) + exit(0) + +if 'file' not in argv_dict : + data = argv_dict + r = requests.post(url, data=data, headers=headers) + result = r.text +else : + fields = argv_dict + fields['file'] = (os.path.basename(argv_dict['file']) , open(argv_dict['file'], 'rb'), 'application/octet-stream') + + multipart_encoder = MultipartEncoder( + fields=fields, + boundary='-----------------------------' + str(random.randint(1e28, 1e29 - 1)) + ) + + headers['Content-Type'] = multipart_encoder.content_type + # multipart/form-data + + r = requests.post(url, data=multipart_encoder, headers=headers, timeout=300) + result = r.text + +result = json.loads(result) +if isinstance(result[1], str) : + content = result[1].encode() + content = content.decode() + print(content) +else : + print(result[1]) + diff --git "a/doc/help/\345\276\256\344\277\241\346\216\245\345\217\243.md" "b/doc/help/\345\276\256\344\277\241\346\216\245\345\217\243.md" new file mode 100644 index 0000000..0a8a206 --- /dev/null +++ "b/doc/help/\345\276\256\344\277\241\346\216\245\345\217\243.md" @@ -0,0 +1,55 @@ +# 发送信息 + +## URL地址 + +http://项目服务器IP/wechat/sendmsg + +## 参数说明 + + username:用户,不能为空;支持post和get方法传参 + pwd:该用户的接口密码;注意该密码不是为登陆密码;支持post和get方法传参 + friend:接受者的昵称,不能为空;支持post和get方法传参 + content:发送内容,不能为空;支持post和get方法传参 + wxid:发送者的昵称,能为空;支持post和get方法传参 + file:发送文件,能为空;只支持post方式传参 + +注意: + + 提供wxid,将指定发送者,如果该发送者不存在将直接返回失败。 + 如果不提供,会试图向所有在线微信号发送信息,只要文字发送成功即返回成功。 + +## 执行后返回结果 + +执行后返回一个json格式 + + (执行结果(布尔型),{'ok':单用户发送结果(字典),'ng:{nickname:单用户发送结果,.....}}) + ok表示文字发送成功的微信号,只能是一个。图片发送是否成功不会进行检测。 + ng表示文字发送失败的微信号 + +单用户发送结果: + + {'nickname': 发送者的微信昵称, 'uuid': 发送者的uuid, 'status': 发送者的当前状态, 'login_stamptime': 发送者的登陆时间戳, 'ok': 执行结果, 'text': {'ok': 执行结果, 'msg': 执行情况说明, 'content': 文字发送内容}, 'file': {'msg': 执行情况说明, 'ok': 执行结果, 'filename': 文件上传到服务器的位置}, 'friend': 接受者的微信昵称} + +## 例子 + +### curl + + curl 'http://127.0.0.1/wechat/sendmsg?username=lykchat&pwd=123456&friend=MySQL&content=恭喜发财' -F "file=@/root/给定的文件.txt" + +**发送文件时,必须在文件路径前加@** + +### 系统提供的python脚本 + +该脚本位于项目的doc/example/test_sendfile.py + + /usr/local/python36/bin/python3 /opt/lykchat/doc/example/test_sendfile.py wxid=MySQL username=lykchat pwd=123456 friend=lykops content=恭喜发财 file=/root/a.py + + +# 计划任务 + +为了让微信保持登陆状态,需要每隔1~3分钟访问登陆接口。 + +本项目有一个接口(http://127.0.0.1/wechat/check)专用于巡检当前状态下所有处于登陆状态的微信号。 + +只允许127.0.0.1访问该该接口。 + diff --git "a/doc/help/\345\276\256\344\277\241\347\256\241\347\220\206.md" "b/doc/help/\345\276\256\344\277\241\347\256\241\347\220\206.md" new file mode 100644 index 0000000..66ee71b --- /dev/null +++ "b/doc/help/\345\276\256\344\277\241\347\256\241\347\220\206.md" @@ -0,0 +1,30 @@ +# web页面--微信列表 +![微信列表](https://github.com/lykops/lykchat/blob/v3.0/doc/screenshot/%E5%BE%AE%E4%BF%A1%E5%88%97%E8%A1%A8.jpg?raw=true) + +该页面只会显示保持登陆和2分钟内登陆信息变化过的微信号。 + +按钮说明 + + 新增登陆:发起一个全新的微信登陆页面 + 登出微信:手动退出该微信号 + +# web页面--微信详情 + +该页面检查微信号登陆情况,展示状态信息、操作建议等咨询。 + +登陆成功后,展示如下图 + +![微信详情](https://github.com/lykops/lykchat/blob/v3.0/doc/screenshot/%E5%BE%AE%E4%BF%A1%E8%AF%A6%E6%83%85.jpg?raw=true) + +按钮说明 + + 刷新页面:重新检查微信号登陆情况,并重新获取好友信息 + 登出微信:手动退出该微信号 + +发送信息 + + 必选/填项:发送给、发送内容 + 选择发送文件:可选 + 只能小于5M的非空文件,支持任意文件类型和任何文件名的文件。 + 对方显示的文件名为lykchat[.原来文件名后缀名] + diff --git "a/doc/help/\346\227\245\345\277\227\347\256\241\347\220\206.md" "b/doc/help/\346\227\245\345\277\227\347\256\241\347\220\206.md" new file mode 100644 index 0000000..e275cd1 --- /dev/null +++ "b/doc/help/\346\227\245\345\277\227\347\256\241\347\220\206.md" @@ -0,0 +1,22 @@ +# 代码断点日志 + +代码断点保存在项目根目录下的logs,默认为/opt/lykchat/logs,文件名格式:日志类型-年-月-日.log,**年月日是记录日志的时间**。 + +日志类型分为: + + 通用型日志:用于记录项目通用日志,代号为lykchat + 微信接口日志:用于记录访问微信web端接口的日志,代码为wechat + +配置文件位于项目根目录下的lykchat/settings.py(默认为/opt/lykchat/lykchat/settings.py),变量为LOGGING。 + +日志通用格式为(每个字段使用空格隔开): + + 年-月-日 小时:分钟:秒,毫秒 记录日志的文件名:行数 模块:函数 级别英文名 自定义日志内容 + 2017-11-18 21:58:28,227 /opt/lykchat/lykchat/views.py:82 views:login INFO 超级管理员lykchat不存在,正在创建中... + + +# 用户操作日志 + +存储在mongodb中。 + +记录用户执行操作,以及执行结果。 \ No newline at end of file diff --git "a/doc/help/\347\231\273\351\231\206.md" "b/doc/help/\347\231\273\351\231\206.md" new file mode 100644 index 0000000..ba349b2 --- /dev/null +++ "b/doc/help/\347\231\273\351\231\206.md" @@ -0,0 +1,20 @@ +# 登陆 + +![登陆页面](https://github.com/lykops/lykchat/blob/v3.0/doc/screenshot/%E7%99%BB%E9%99%86.jpg?raw=true) + +如果部署完毕后,该项目没有任何用户时,系统要求你创建一个超级管理员,请看下面“创建超级管理员”部分。 + + +# 创建超级管理员 + +![创建超级管理员](https://github.com/lykops/lykchat/blob/v3.0/doc/screenshot/%E5%88%9B%E5%BB%BA%E8%B6%85%E7%BA%A7%E7%AE%A1%E7%90%86%E5%91%98.jpg?raw=true) + +**默认超级管理员用户为lykchat** + +如下修改,请修改配置文件,位于项目根目录下的library/config/frontend.py(默认为/opt/lykchat/library/config/frontend.py),变量名adminuser。 + +# 接口密码 + +接口密码用于接口调用时认证密码。 + +强制要求不同于登录密码。 \ No newline at end of file diff --git "a/doc/help/\351\203\250\347\275\262\346\211\213\345\206\214.md" "b/doc/help/\351\203\250\347\275\262\346\211\213\345\206\214.md" new file mode 100644 index 0000000..5694fb1 --- /dev/null +++ "b/doc/help/\351\203\250\347\275\262\346\211\213\345\206\214.md" @@ -0,0 +1,130 @@ +# 配置运行环境 + 关闭selinux + sed -i '/SELINUX/s/enforcing/disabled/' /etc/selinux/config + 防火墙允许外网访问80端口或者直接关闭防火墙 + systemctl firewalld disabled + +# 安装依赖包 + yum install -y epel-release + yum install telnet ntpdate glibc openssl openssl-devel openssl-static openssl098e openssl-libs vim automake autoconf gcc xz ncurses-devel patch git gcc-c++ net-tools wget unzip zip sshpass -y + yum upgrade -y + reboot + +# mongodb +## 安装 + yum源文件 + cat << EOF > /etc/yum.repos.d/mongodb-org-3.6.repo + [mongodb-org-3.6] + name=MongoDB Repository + baseurl=https://repo.mongodb.org/yum/redhat/\$releasever/mongodb-org/3.6/x86_64/ + gpgcheck=0 + enabled=1 + gpgkey=https://www.mongodb.org/static/pgp/server-3.6.asc + EOF + + yum install -y mongodb-org + 如果yum很慢的话,直接到https://repo.mongodb.org/yum/redhat/7/mongodb-org/3.6/x86_64/RPMS/下载后安装 + + +## 部署 + mkdir -p /opt/mongodb/{data,log} + mongod --dbpath /opt/mongodb/data/ --logpath /opt/mongodb/log/mongodp.log --fork + +## 建表和权限管理 + +下面的操作需要进入mongo进行操作 + +### 建表 + + db.createCollection("lykchat") + +### 授权 + + use lykchat; + db.createUser( + { + user: "lykchat", + pwd: "1qaz2wsx", + roles: + [ + { + role: "readWrite", + db: "lykchat" + } + ] + } + ); + + +### 在lykops库中验证 + + use lykchat; + db.auth('lykchat', '1qaz2wsx') + + +### 重启 + kill -9 $(ps aux | grep 'mongod --dbpath /opt' | grep -v grep | gawk {'print $2'}) + mongod --dbpath /opt/mongodb/data/ --logpath /opt/mongodb/log/mongodp.log --fork --auth + +### 验证权限 + mongo lykchat -ulykchat -p'1qaz2wsx' + 能正常登陆说明权限设置OK + +# redis +## 安装 + wget http://download.redis.io/releases/redis-3.2.11.tar.gz + tar xzf redis-3.2.11.tar.gz + cd redis-3.2.11 + make + +注:可升级到redis-4.0.6 + +## 配置 + +注:redis不是用持久化,即redis在运行时数据放在内存中,不会保存到硬盘中,关闭后将消失。 + + mv /opt/redis-3.2.11 /opt/redis + cd /opt/redis/ + + sed -i 's/daemonize no/daemonize yes/g' redis.conf + sed -i 's/^save/#save/g' redis.conf + sed -i 's/^append/#append/g' redis.conf + echo 'requirepass 1qaz2wsx' >> redis.conf + #设置密码 + +## 启动 + /opt/redis/src/redis-server /opt/redis/redis.conf + +# 部署代码 +把代码下载到/opt/lykchat + +# python +## 部署运行环境 + wget https://www.python.org/ftp/python/3.6.3/Python-3.6.3.tgz -c + tar zxvf Python-3.6.3.tgz + cd Python-3.6.3 + ./configure --prefix=/usr/local/python36/ --enable-optimizations && make && make install + ln -s /usr/local/python36/bin/{python3,pip3} /bin + mkdir /opt/lykchat/logs/ + + pip3 install -r /opt/lykchat/doc/install/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple + +## 启动django + /usr/local/python36/bin/python3 /opt/lykchat/manage.py runserver 0.0.0.0:80 + +## 配置计划任务 + + * * * * * curl http://127.0.0.1/wechat/check + + +# 自启动 + +在/etc/rc.local中添加下面几行 + + mongod --dbpath /opt/mongodb/data/ --logpath /opt/mongodb/log/mongodp.log --fork --auth + /opt/redis/src/redis-server /opt/redis/redis.conf & + + /usr/local/python36/bin/python3 /opt/lykchat/manage.py runserver 0.0.0.0:80 >> /dev/shm/lykchat.log >2& + + +chmod +x /etc/rc.d/rc.local diff --git "a/doc/help/\351\205\215\347\275\256\346\211\213\345\206\214.md" "b/doc/help/\351\205\215\347\275\256\346\211\213\345\206\214.md" new file mode 100644 index 0000000..66065c6 --- /dev/null +++ "b/doc/help/\351\205\215\347\275\256\346\211\213\345\206\214.md" @@ -0,0 +1,55 @@ +主要配置文件放在:项目根目录下的library/config,默认为/opt/lykchat/library/config/ + +# 数据库 + +数据库参数的配置文件位于:项目根目录下的library/config/database.py,默认为/opt/lykchat/library/config/database.py + +## mongodb + +本系统使用pymongo模块(mongodb官方推荐)操作管理mongodb服务器。官方文档地址为https://pypi.python.org/pypi/pymongo/3.4.0 + +连接参数是由mongo_config来定义的,具体配置请直接查看文件。 + +本系统重新构建mongodb接口,位于项目根目录下的library/connecter/database/mongo.py,默认为/opt/lykchat/library/connecter/database/mongo.py。 + +**注:本系统目前只支持单机,任何集群方式未测试过。** + +## redis + +本系统使用redis模块操作管理redis服务器。官方文档地址为https://pypi.python.org/pypi/redis/2.10.5 + +连接参数是由redis_config来定义的,具体配置请直接查看文件。 + +本系统重新构建redis接口,位于项目根目录下的library/connecter/database/redis_api.py,默认为/opt/lykchat/library/connecter/database/redis_api.py。 + +**注:本系统只支持单机,任何集群方式未测试过。** + +# 缓存 + +使用redis存储缓存。 + +## 查询缓存 + +主要是用于前端查询做缓存,配置请见本文的“数据库”--“redis”部分 + +## session缓存 + +即用户登录后会话保持,保持时长为4小时。注意:保持时长不是从登录时间开始计算,而是用户在页面操作的最后一次操作开始计算的。 + +配置文件位于项目根目录下的lykchat/settings.py(默认为/opt/lykchat/lykchat/settings.py),以SESSION_开头的参数。 + + +# 代码断点日志 + +代码断点日志保存在项目根目录下的logs,默认为/opt/lykchat/logs,文件名格式:日志类型-年-月-日.log,**年月日是记录日志的时间**。 + +配置文件位于项目根目录下的lykchat/settings.py(默认为/opt/lykchat/lykchat/settings.py),变量为LOGGING。 + +日志通用格式为(每个字段使用空格隔开): + + 年-月-日 小时:分钟:秒,毫秒 记录日志的文件名:行数 模块:函数 级别英文名 自定义日志内容 + 2017-11-18 21:58:28,227 /opt/lykchat/lykchat/views.py:82 views:login INFO 超级管理员lykchat不存在,正在创建中... + +# 超级管理员 + +默认为lykchat,配置文件位于项目根目录下的library/config/frontend.py(默认为/opt/lykchat/library/config/frontend.py),变量名adminuser diff --git a/doc/install/requirements.txt b/doc/install/requirements.txt new file mode 100644 index 0000000..295e4eb --- /dev/null +++ b/doc/install/requirements.txt @@ -0,0 +1,19 @@ +django==1.11 +#django-bootstrap +#django-bootstrap-form +#django-bootstrap-toolkit +django-crontab +#djangorestframework +#django-template-utils +django-redis-sessions +celery +celery-with-redis +filetype +filetypes +hiredis +pymongo +Pillow +rarfile +redis +#requests +requests-toolbelt \ No newline at end of file diff --git "a/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.jpg" "b/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.jpg" deleted file mode 100644 index 5e19ad2..0000000 Binary files "a/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.jpg" and /dev/null differ diff --git "a/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.vsdx" "b/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.vsdx" deleted file mode 100644 index b34881f..0000000 Binary files "a/doc/lykchat\345\267\245\344\275\234\346\265\201\347\250\213.vsdx" and /dev/null differ diff --git "a/doc/screenshot/\345\210\233\345\273\272\350\266\205\347\272\247\347\256\241\347\220\206\345\221\230.jpg" "b/doc/screenshot/\345\210\233\345\273\272\350\266\205\347\272\247\347\256\241\347\220\206\345\221\230.jpg" new file mode 100644 index 0000000..42f42ef Binary files /dev/null and "b/doc/screenshot/\345\210\233\345\273\272\350\266\205\347\272\247\347\256\241\347\220\206\345\221\230.jpg" differ diff --git "a/doc/screenshot/\345\276\256\344\277\241\345\210\227\350\241\250.jpg" "b/doc/screenshot/\345\276\256\344\277\241\345\210\227\350\241\250.jpg" new file mode 100644 index 0000000..b8f8d34 Binary files /dev/null and "b/doc/screenshot/\345\276\256\344\277\241\345\210\227\350\241\250.jpg" differ diff --git "a/doc/screenshot/\345\276\256\344\277\241\350\257\246\346\203\205.jpg" "b/doc/screenshot/\345\276\256\344\277\241\350\257\246\346\203\205.jpg" new file mode 100644 index 0000000..f38155c Binary files /dev/null and "b/doc/screenshot/\345\276\256\344\277\241\350\257\246\346\203\205.jpg" differ diff --git "a/doc/screenshot/\347\231\273\351\231\206.jpg" "b/doc/screenshot/\347\231\273\351\231\206.jpg" new file mode 100644 index 0000000..cacd2d2 Binary files /dev/null and "b/doc/screenshot/\347\231\273\351\231\206.jpg" differ diff --git "a/doc/web\347\256\241\347\220\206--\345\217\221\351\200\201\346\210\220\345\212\237.jpg" "b/doc/web\347\256\241\347\220\206--\345\217\221\351\200\201\346\210\220\345\212\237.jpg" deleted file mode 100644 index 14858da..0000000 Binary files "a/doc/web\347\256\241\347\220\206--\345\217\221\351\200\201\346\210\220\345\212\237.jpg" and /dev/null differ diff --git "a/doc/web\347\256\241\347\220\206--\345\245\275\345\217\213\345\210\227\350\241\250\344\270\213\346\213\211\346\241\206.jpg" "b/doc/web\347\256\241\347\220\206--\345\245\275\345\217\213\345\210\227\350\241\250\344\270\213\346\213\211\346\241\206.jpg" deleted file mode 100644 index df97f03..0000000 Binary files "a/doc/web\347\256\241\347\220\206--\345\245\275\345\217\213\345\210\227\350\241\250\344\270\213\346\213\211\346\241\206.jpg" and /dev/null differ diff --git "a/doc/web\347\256\241\347\220\206--\347\231\273\351\231\206.jpg" "b/doc/web\347\256\241\347\220\206--\347\231\273\351\231\206.jpg" deleted file mode 100644 index ad0f772..0000000 Binary files "a/doc/web\347\256\241\347\220\206--\347\231\273\351\231\206.jpg" and /dev/null differ diff --git "a/doc/web\351\241\265\351\235\242--\345\212\237\350\203\275\350\257\264\346\230\216.jpg" "b/doc/web\351\241\265\351\235\242--\345\212\237\350\203\275\350\257\264\346\230\216.jpg" deleted file mode 100644 index 557aef2..0000000 Binary files "a/doc/web\351\241\265\351\235\242--\345\212\237\350\203\275\350\257\264\346\230\216.jpg" and /dev/null differ diff --git "a/doc/web\351\241\265\351\235\242--\347\231\273\351\231\206\346\210\220\345\212\237.jpg" "b/doc/web\351\241\265\351\235\242--\347\231\273\351\231\206\346\210\220\345\212\237.jpg" deleted file mode 100644 index 5de1f9c..0000000 Binary files "a/doc/web\351\241\265\351\235\242--\347\231\273\351\231\206\346\210\220\345\212\237.jpg" and /dev/null differ diff --git "a/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.jpg" "b/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.jpg" deleted file mode 100644 index c3b4ed2..0000000 Binary files "a/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.jpg" and /dev/null differ diff --git "a/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.vsdx" "b/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.vsdx" deleted file mode 100644 index 19ec816..0000000 Binary files "a/doc/web\351\241\265\351\235\242\347\231\273\351\231\206\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213.vsdx" and /dev/null differ diff --git "a/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md" "b/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md" deleted file mode 100644 index daa0090..0000000 --- "a/doc/\344\275\277\347\224\250\346\211\213\345\206\214.md" +++ /dev/null @@ -1,41 +0,0 @@ -# 模块说明 - -## 管理web页面 - 可视化管理微信个人号 - 包括: - 用户登录和认证 - 微信号登陆管理:负责微信登陆、登陆信息展示等功能 - 发送信息给好友:用于测试发送功能是否可用 - 通过选择好友列表显示获取需要发送信息的好友 - 好友信息列表只展示文件传输助手、除了自己外的好友(疑似好友表示没有设置该好友没有设置性别)、部分群(是根据第一页好友信息获取的),自动屏蔽掉公众号、微信系统用户、好友为自己。 - 上传需要发送的文件 - -## 发送信息接口 ## -[https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E](https://github.com/lykops/lykchat/wiki/%E5%8F%91%E9%80%81%E4%BF%A1%E6%81%AF%E6%8E%A5%E5%8F%A3%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E "发送信息接口") - -## 计划任务 - 检测微信登陆状态: - 获取所有登录微信成功的用户,通过调用检测微信登陆接口 - -## 会话保持模块 - 存储微信登陆信息和会话信息,同用户在任何地方登陆,保证微信登陆状态一致 - 访问管理页面和微信登陆检测接口,根据session或者参数获取用户名,然后读取会话文件,页面操作后,再一次更新数据库和会话文件 - - 这些信息分别保持在数据库和文件中。 - 数据库 - 保存用户会话信息,只记录用户名 - 有效期1小时 - 每次访问更新会话有效期 - 会自动删除过期的会话信息 - 会话文件 - 保存用户所有的信息(包括微信登陆信息) - json格式 - 每次访问更新 - 默认存放在/dev/shm/lykchat下,根据用户名命名 - -## 模拟微信web端模块 - 它是该系统的核心和底层模块。 - 通过微信登陆信息,访问微信web端接口,实现管理登陆、发送信息等功能。 - -# lykchat工作流程 -![lykchat工作流程](https://raw.githubusercontent.com/lykops/lykchat/master/doc/lykchat工作流程.jpg) \ No newline at end of file diff --git "a/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md" "b/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md" deleted file mode 100644 index b4935c4..0000000 --- "a/doc/\345\217\221\351\200\201\344\277\241\346\201\257\346\216\245\345\217\243\350\257\264\346\230\216.md" +++ /dev/null @@ -1,52 +0,0 @@ -发送信息接口为其他业务系统发送信息给指定好友 - -# 特点 - 支持post和get方法 - 除了支持纯文字外,还支持图片、文字等诸多多媒体文件。 - -# 注意 - django默认启用防CSRF(Cross-site request forgery跨站请求伪造),导致无法使用post方法调用该接口,所以作者强制关闭了防csrf功能。 - 如果你觉得有安全隐患,又不需要发送多媒体文件,请下载2.0版本:https://codeload.github.com/lykops/lykchat/zip/master - -# 参数说明 - 下面参数支持post和get方法 - 'username' : - 管理用户,同管理web页面,通过用户确认微信发送者 - 'pwd' : - 接口密码,注意不等于登陆密码 - 'type' : - '发送信息类型 - 可选{"txt":"纯文字" ,"img":"图片","file":"发送文件","video":"视频"},可以为空 - 默认:不发送文件为txt,发送文件为file - 'fromalias': - 保留字段 - 发送者的微信号,目前没有使用该参数 - 'friendfield': - 接受者的字段代号,可选{0:"NickName" , 1:"Alias" , 2:"RemarkName"},可以为空 - 默认为0' - 'friend': - 接受者的昵称、微信号、备注名的其中一个,不能为空 - 'content': - 发送内容,不能为空 - 必须是post方法: - 'file': - 需要发送的文件, - 注意: - friend一定是该用户下的登陆微信好友列表中的 - friendfield最好是微信号(Alias),也可以使用昵称(NickName)或者备注名(RemarkName),但不能重复 - -# 返回信息 - json格式,{'Msg': 执行结果, 'Code':返回代码, 'ErrMsg':如果-1005返回参数列表,其他发送微信返回信息} - 常见code:0成功,-1101参数错误,-1102无法找到好友,1101微信号退出登录,其他为微信返回错误 - -# 使用实例 - 接口URL地址:http://IP(或者域名)/sendmsg - 发送纯文字: - http://192.168.100.104/sendmsg?username=zabbix&pwd=123456&friendfield=1&friend=lyk-ops&content=test - 发送多媒体: - 方法1:Linux的curl命令 - curl -F "file=@/root/a" 'http://127.0.0.1/sendmsg?username=zabbix&pwd=123456&type=img&friendfield=1&friend=lyk-ops&content=test' - 方法2:python脚本test_sendfile.py - 请参照该项目的根目录python脚本test_sendfile.py,执行 - /usr/local/python36/bin/python3 /opt/lykchat/test_upload.py "{'username':'zabbix','pwd':'123456','type':'img','friendfield':'1','friend':'lyk-ops','content':'恭喜发财','file':'/root/b.jpg'}" - \ No newline at end of file diff --git "a/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md" "b/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md" deleted file mode 100644 index 073be93..0000000 --- "a/doc/\345\256\211\350\243\205\346\211\213\345\206\214.md" +++ /dev/null @@ -1,96 +0,0 @@ -# 运行环境 -## 操作系统 - Linux - 测试环境为CentOS6、CentOS7两个版本测试 -## 语言环境 - Python3+django1.10 - 测试环境使用Python3.5.2、3.6.0两个版本测试 -## web服务器 - Nginx - 主要解决静态文件展示。 - 测试环境为nginx 1.10.2 -## 数据库 - MySQL - 保存用户会话信息,只记录用户名,有效期1小时,每次访问更新会话有效期 - 测试环境为5.7.17 - - -# 配置运行环境 - 关闭selinux - 防火墙允许外网访问80端口或者直接关闭防火墙 - -## 安装依赖包 - yum install -y epel-release - yum install telnet ntpdate lrzsz bash glibc openssl vim automake autoconf gcc xz ncurses-devel patch python-devel git python-pip gcc-c++ redhat-rpm-config openssl-devel openssl-static openssl098e openssl-libs -y - yum upgrade -y - -## 配置nginx - 先按照nginx - 配置nginx服务器,conf/nginx.conf,添加 - location / { - proxy_redirect off; - proxy_pass_header Server; - proxy_set_header Host $http_host; - proxy_set_header X-Scheme $scheme; - proxy_set_header X-Real-IP $remote_addr; - proxy_pass http://localhost:8000; - } - location /static/ { - alias /opt/lykchat/static/; - } - -## 配置mysql - - 在本地安装mysql - rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el7-9.noarch.rpm - yum install mysql-community-client mysql-community-common mysql-community-devel mysql-community-libs mysql-community-libs-compat mysql-community-server --skip-broke - - 新增一个数据库lykchat - 设置用户lykchat,密码为!QAZ2wsx,把数据库lykchat的权限分配给用户lykchat - -## 编译安装python - - wget https://www.python.org/ftp/python/3.6.0/Python-3.6.0.tgz -c - tar zxvf Python-3.6.0.tgz - cd Python-3.6.0 - ./configure --prefix=/usr/local/python36/ --enable-optimizations && make && make install - -## 安装python模块 - - 下载程序,解压到/opt/ - /usr/local/python36/bin/pip3 install -r /opt/lykchat/install/requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple - -# 配置应用程序 -## 修改配置文件 - 配置文件library/config/wechat.py - 如果未按默认方式运行的话,需要修改以下参数: - 数据库配置:DATABASES - session过期时间:SESSION_COOKIE_AGE,默认1小时 - nginx修改端口或者使用域名访问,修改url_frond - 该项目部署URL基础地址:url_frond,默认为http://127.0.0.1/ - 如果修改了nginx端口或者基础地址的话,需要修改此处,否则微信登陆检测失败 - 强烈建议使用根目录部署,不要部署在二级目录下(例如http://127.0.0.1/lykchat),导致内部页面跳转异常 - web登陆账号和密码:user_mess_dict - -## 初始化数据库和配置计划任务 - - /usr/local/python36/bin/python3 /opt/lykchat/manage.py makemigrations - /usr/local/python36/bin/python3 /opt/lykchat/manage.py migrate - - /usr/local/python36/bin/python3 /opt/lykchat/manage.py crontab add - crontab -l - 如果有类似这条 - * * * * * /usr/bin/python3 /opt/lykchat/manage.py crontab run 6d8f0feaeaa440358a85dfc8d5efa2af >>/dev/shm/lykchat.txt 2>&1 # django-cronjobs for lykchat - 说明OK - - 运行该计划任务,没有报错即可 - -# 启动服务 - - service mysqld restart - nginx - /usr/local/python36/bin/python3 /opt/lykchat/manage.py runserver - - 以上命令记得做开机启动哦 - -# 访问ip地址即可 \ No newline at end of file diff --git "a/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" "b/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" deleted file mode 100644 index 9b12ede..0000000 Binary files "a/doc/\345\276\256\344\277\241\347\231\273\351\231\206\346\227\266\351\227\264\350\266\205\350\277\2071\345\244\251.jpg" and /dev/null differ diff --git "a/doc/\346\216\245\345\217\243-\345\217\221\351\200\201\344\277\241\346\201\257\346\210\220\345\212\237.jpg" "b/doc/\346\216\245\345\217\243-\345\217\221\351\200\201\344\277\241\346\201\257\346\210\220\345\212\237.jpg" deleted file mode 100644 index ef7477d..0000000 Binary files "a/doc/\346\216\245\345\217\243-\345\217\221\351\200\201\344\277\241\346\201\257\346\210\220\345\212\237.jpg" and /dev/null differ diff --git "a/doc/\346\250\241\346\213\237\347\231\273\351\231\206web\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213\345\222\214\345\217\202\346\225\260\347\273\206\350\212\202.md" "b/doc/\346\250\241\346\213\237\347\231\273\351\231\206web\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213\345\222\214\345\217\202\346\225\260\347\273\206\350\212\202.md" deleted file mode 100644 index 7f8fc3c..0000000 --- "a/doc/\346\250\241\346\213\237\347\231\273\351\231\206web\345\276\256\344\277\241\347\232\204\346\265\201\347\250\213\345\222\214\345\217\202\346\225\260\347\273\206\350\212\202.md" +++ /dev/null @@ -1,118 +0,0 @@ -# 登陆准备阶段 - -## 微信首页 - get - https://wx.qq.com - 提供headers - 用途:获取cookie - 后续访问必须带headers、cookie参数 - -## 获取二维码uuid - get - https://wx.qq.com/jslogin - get参数分别是 - appid:值为自定义,格式为wx782c26e4c19acffb - fun:值为new - lang:值为en_us - redirect_uri:值为https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage - _:值为当前时间戳 - 完整的URL例子https://wx2.qq.com/jslogin?redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&appid=wx782c26e4c19acffb&lang=en_US&_=1485065568&fun=new - 用途:获取二维码uuid - -## 下载和展示二维码 - get - https://wx.qq.com/qrcode/{{uuid}} - 例子:https://wx2.qq.com/qrcode/AfdK5U5qyw== - -## 扫码和确认 - 访问https://wx.qq.com/cgi-bin/mmwebwx-bin/login - get参数 - loginicon:值必须为true - uuid:值为{{uuid}} - r:值为当前时间戳/1524 - _:值为当前时间戳 - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=AacJ62nlRg==&tip=0&r=-974452&_=1485065779 - 用途:返回登陆状态,登陆成功之后的redirect_uri - 返回状态码说明如下: - 200,扫码和确认成功 - 201,扫码,未确认 - 其他,未扫码或者其他原因 - -# 登陆初始化和获取登陆信息 - -## 登陆初始化 - get - {{redirect_uri}} - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A0k1PKCiAX8_8nyisAG0R9d5@qrticket_0&uuid=AacJ62nlRg==&lang=en_US&scan=1485065793 - 用途:返回登陆认证等信息,一个字典类型的json格式,下文用login_info表示 - -## 获取登陆信息 - post - https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=【当前时间戳】 - post参数为 - BaseRequest:通过1返回参数获取值 - 例子:{"BaseRequest": {"DeviceID": "uKUD8e%2Bp7iXqNpbOuPTntL7OdbsfxEv5JdQjKtb7Mc%2FVQK2leE%2BRrNVkI5fQZZjB", "Sid": "xkQE8IoFPjwXEf2W", "Uin": "575635712", "Skey": "@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324", "isgrayscale": "1"}} - 用途:返回微信用户信息、第一页好友信息、和BaseRequest、最新聊天信息等等 - - -# 获取好友信息 - - get - https://wx.qq.com/webwxgetcontact - get参数 - r:值为当前时间戳 - seq:值为0 - skey:值为login_info[Skey] - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1485065800&seq=0&skey=@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324 - 用途:返回所有的好友信息,字典json格式 - 有用的好友信息字段说明: - Sex:1表示男,2表示女,0为其他【没有设置性别的好友、公众号、群、系统账号等等】 - UserName,微信系统为每个微信号分配一个唯一号码,开头@@表示群、字母或者数字开头表示系统账号,其他【公众号、好友等】以单@开头 - NickName,个人设置的昵称,重复可能性很大 - Alias,微信号,如果没有设置为空,不会出现重复 - -# 接受信息 - -## 定时检查是否有新信息 - get - https://{{sync_url}}/synccheck - get参数是: - 'r' : 当前时间戳*1000 - 'skey' : login_info[skey] - 'sid' : login_info[sid] - 'uin' : login_info[uin] - 'deviceid' : login_info[deviceid] - 'synckey' : login_info[synckey] - '_' : 当前时间戳*1000 - 完整的URL例子:https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?deviceid=e565817597249768&synckey=List_%5B%7B%27Key%27%3A+1%2C+%27Val%27%3A+645531166%7D%2C+%7B%27Key%27%3A+2%2C+%27Val%27%3A+645531276%7D%2C+%7B%27Key%27%3A+3%2C+%27Val%27%3A+645531125%7D%2C+%7B%27Key%27%3A+1000%2C+%27Val%27%3A+1485058018%7D%5D%7CCount_4&skey=%40crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324&sid=xkQE8IoFPjwXEf2W&r=1485065802608&_=1485065802608&uin=575635712 - 用途:返回最新信息数,0表示没有新消息 -## 获取新信息内容 - post - https://wx.qq.com/webwxsync?sid=login_info[sid]&skey=login_info[skey]&lang=en_US&pass_ticket=login_info[pass_ticket] - post参数为 - 'BaseRequest' : login_info[BaseRequest] - 'SyncKey' : login_info[SyncKey] - 'rr' :~当前时间戳*1000 - 例子:{"rr": -1485065809, "BaseRequest": {"Ret": 0, "ErrMsg": ""}, "SyncKey": {"List": [{"Key": 1, "Val": 645531166}, {"Key": 2, "Val": 645531278}, {"Key": 3, "Val": 645531125}, {"Key": 11, "Val": 645531260}, {"Key": 13, "Val": 645524153}, {"Key": 201, "Val": 1485065810}, {"Key": 203, "Val": 1485064747}, {"Key": 1000, "Val": 1485058018}, {"Key": 1001, "Val": 1485057992}, {"Key": 1002, "Val": 1485058221}, {"Key": 1004, "Val": 1484911834}], "Count": 11}} - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xkQE8IoFPjwXEf2W&skey=@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324&lang=en_US&pass_ticket=uKUD8e%2Bp7iXqNpbOuPTntL7OdbsfxEv5JdQjKtb7Mc%2FVQK2leE%2BRrNVkI5fQZZjB - 用途:返回最新信息列表 - 注意:群信息的发送者放在Content开头部分 - -# 发送信息 - - post - https://wx.qq.com/webwxsendmsg - post参数 - 'BaseRequest': self.base_request, - 'Msg': { - 'Type': login_info[BaseRequest], - 'Content': content, - 'FromUserName': 自己的username, - 'ToUserName': 发送的username, - 'LocalID': int(time.time() * 1000 * 1000 * 10), - 'ClientMsgId': int(time.time() * 1000 * 1000 * 10), - }, - 'Scene' : 0 - 返回发送结果json字典 - -![web页面登陆微信的流程](https://raw.githubusercontent.com/lykops/lykchat/master/doc/web页面登陆微信的流程.jpg) \ No newline at end of file diff --git "a/doc/\347\231\273\351\231\206web\345\276\256\344\277\241\346\265\201\347\250\213\350\257\264\346\230\216.txt" "b/doc/\347\231\273\351\231\206web\345\276\256\344\277\241\346\265\201\347\250\213\350\257\264\346\230\216.txt" deleted file mode 100644 index 0e9cb44..0000000 --- "a/doc/\347\231\273\351\231\206web\345\276\256\344\277\241\346\265\201\347\250\213\350\257\264\346\230\216.txt" +++ /dev/null @@ -1,93 +0,0 @@ -第一步:登陆 -1、get访问微信首页https://wx.qq.com - 提供session、headers - 用途:获取cookie - 后续访问必须带session、headers、cookie这三个参数,并保持不变 -2、get访问https://wx.qq.com/jslogin - get参数分别是 - appid:值为自定义,格式为wx782c26e4c19acffb - fun:值为new - lang:值为en_us - redirect_uri:值为https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage - _:值为当前时间戳 - 完整的URL例子https://wx2.qq.com/jslogin?redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&appid=wx782c26e4c19acffb&lang=en_US&_=1485065568&fun=new - 用途:获取二维码uuid -3、get访问https://wx.qq.com/qrcode/{{uuid}} - 例子:https://wx2.qq.com/qrcode/AfdK5U5qyw== - 用途:下载和展示二维码 -4、扫码和确认,访问https://wx.qq.com/cgi-bin/mmwebwx-bin/login, - get参数 - loginicon:值必须为true - uuid:值为{{uuid}} - r:值为当前时间戳/1524 - _:值为当前时间戳 - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=AacJ62nlRg==&tip=0&r=-974452&_=1485065779 - 用途:返回登陆状态,登陆成功之后的redirect_uri - 返回状态码说明如下: - 200,扫码和确认成功 - 201,扫码,未确认 - 其他,未扫码或者其他原因 - -第二步、初始化页面和获取登陆信息 -1、get访问{{redirect_uri}} - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A0k1PKCiAX8_8nyisAG0R9d5@qrticket_0&uuid=AacJ62nlRg==&lang=en_US&scan=1485065793 - 用途:返回登陆认证等信息,一个字典类型的json格式,下文用login_info表示 -2、post访问https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=【当前时间戳】 - post参数为 - BaseRequest:通过1返回参数获取值 - 例子:{"BaseRequest": {"DeviceID": "uKUD8e%2Bp7iXqNpbOuPTntL7OdbsfxEv5JdQjKtb7Mc%2FVQK2leE%2BRrNVkI5fQZZjB", "Sid": "xkQE8IoFPjwXEf2W", "Uin": "575635712", "Skey": "@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324", "isgrayscale": "1"}} - 用途:返回微信用户信息、第一页好友信息、和BaseRequest、最新聊天信息等等 - - -第三步、获取好友信息 - get访问https://wx.qq.com/webwxgetcontact - get参数 - r:值为当前时间戳 - seq:值为0 - skey:值为login_info[Skey] - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1485065800&seq=0&skey=@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324 - 用途:返回所有的好友信息,字典json格式 - 有用的好友信息字段说明: - Sex:1表示男,2表示女,0为其他【公众号、群、系统账号等等】 - UserName,微信系统为每个微信号分配一个唯一号码,开头@@表示群、字母或者数字开头表示系统账号,其他【公众号、好友等】以单@开头 - NickName,个人设置的昵称,重复可能性很大 - Alias,微信号,如果没有设置为空,不会出现重复 - -第四步、接受和发送新信息 -1、接受信息 - 1)、定时检查是否有新信息 - get访问https://{{sync_url}}/synccheck - get参数是: - 'r' : 当前时间戳*1000 - 'skey' : login_info[skey] - 'sid' : login_info[sid] - 'uin' : login_info[uin] - 'deviceid' : login_info[deviceid] - 'synckey' : login_info[synckey] - '_' : 当前时间戳*1000 - 完整的URL例子:https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?deviceid=e565817597249768&synckey=List_%5B%7B%27Key%27%3A+1%2C+%27Val%27%3A+645531166%7D%2C+%7B%27Key%27%3A+2%2C+%27Val%27%3A+645531276%7D%2C+%7B%27Key%27%3A+3%2C+%27Val%27%3A+645531125%7D%2C+%7B%27Key%27%3A+1000%2C+%27Val%27%3A+1485058018%7D%5D%7CCount_4&skey=%40crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324&sid=xkQE8IoFPjwXEf2W&r=1485065802608&_=1485065802608&uin=575635712 - 用途:返回最新信息数,0表示没有新消息 - 2)、获取新信息内容,post访问https://wx.qq.com/webwxsync?sid=login_info[sid]&skey=login_info[skey]&lang=en_US&pass_ticket=login_info[pass_ticket] - post参数为 - 'BaseRequest' : login_info[BaseRequest] - 'SyncKey' : login_info[SyncKey] - 'rr' :~当前时间戳*1000 - 例子:{"rr": -1485065809, "BaseRequest": {"Ret": 0, "ErrMsg": ""}, "SyncKey": {"List": [{"Key": 1, "Val": 645531166}, {"Key": 2, "Val": 645531278}, {"Key": 3, "Val": 645531125}, {"Key": 11, "Val": 645531260}, {"Key": 13, "Val": 645524153}, {"Key": 201, "Val": 1485065810}, {"Key": 203, "Val": 1485064747}, {"Key": 1000, "Val": 1485058018}, {"Key": 1001, "Val": 1485057992}, {"Key": 1002, "Val": 1485058221}, {"Key": 1004, "Val": 1484911834}], "Count": 11}} - 完整的URL例子:https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xkQE8IoFPjwXEf2W&skey=@crypt_2b05caf0_2290c785d1bc5646d2ff0ff771ec3324&lang=en_US&pass_ticket=uKUD8e%2Bp7iXqNpbOuPTntL7OdbsfxEv5JdQjKtb7Mc%2FVQK2leE%2BRrNVkI5fQZZjB - 用途:返回最新信息列表 - 注意:群信息的发送者放在Content开头部分 -2、发送信息 - post访问https://wx.qq.com/webwxsendmsg - post参数 - 'BaseRequest': self.base_request, - 'Msg': { - 'Type': login_info[BaseRequest], - 'Content': content, - 'FromUserName': 自己的username, - 'ToUserName': 发送的username, - 'LocalID': login_info[msgid], - 'ClientMsgId': login_info[msgid], - }, - 例子:{"Msg": {"Content": "啊啊啊啊", "ToUserName": "filehelper", "FromUserName": "@974141db55d51041ee1e0b8b6af5589776a5282910b9ac1e154693430a23f79f", "Type": "Test Message", "LocalID": 1485065800472, "ClientMsgId": 1485065800472}, "BaseRequest": {"Ret": 0, "ErrMsg": ""}} - 返回发送结果json字典 - \ No newline at end of file diff --git a/install/requirements.txt b/install/requirements.txt deleted file mode 100644 index e1d2b04..0000000 --- a/install/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -django == 1.10 -django-bootstrap -django-bootstrap-form -django-bootstrap-toolkit -django-crontab -django-pagination -djangorestframework -django-template-utils -mysqlclient -Pillow -requests -requests-toolbelt \ No newline at end of file diff --git a/library/config/__pycache__/__init__.cpython-36.pyc b/library/config/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..ff525c1 Binary files /dev/null and b/library/config/__pycache__/__init__.cpython-36.pyc differ diff --git a/library/config/__pycache__/ansible.cpython-36.pyc b/library/config/__pycache__/ansible.cpython-36.pyc new file mode 100644 index 0000000..83a584e Binary files /dev/null and b/library/config/__pycache__/ansible.cpython-36.pyc differ diff --git a/library/config/__pycache__/database.cpython-36.pyc b/library/config/__pycache__/database.cpython-36.pyc new file mode 100644 index 0000000..11c6f87 Binary files /dev/null and b/library/config/__pycache__/database.cpython-36.pyc differ diff --git a/library/config/__pycache__/logging.cpython-36.pyc b/library/config/__pycache__/logging.cpython-36.pyc new file mode 100644 index 0000000..442b350 Binary files /dev/null and b/library/config/__pycache__/logging.cpython-36.pyc differ diff --git a/library/config/database.py b/library/config/database.py new file mode 100644 index 0000000..3baf69f --- /dev/null +++ b/library/config/database.py @@ -0,0 +1,35 @@ +redis_config = { + 'default' : { + 'host' : '127.0.0.1', # IP地址 + 'port' : 6379, # 端口 + 'db' : 0, # 数据库名 + 'max_connections' : 100, # 最大连接数 + 'pwd' : '1qaz2wsx', # 如果密码为空,None + 'type' : 'default', # 读写模式 + }, + 'socket_timeout' : None, + 'socket_connect_timeout' : None, + 'socket_keepalive' : 90, +} + +mongo_config = { + 'default' : { + 'host' : 'localhost', # IP地址 + 'port' : 27017, # 端口 + 'db' : 'lykchat', # 数据库名 + 'user' : 'lykchat', # 连接用户名 + 'pwd' : '1qaz2wsx', # 连接密码 + 'type' : 'default', # 读写模式 + }, + # 默认连接方式。 + 'log' : { + 'host' : 'localhost', + 'port' : 27017, + 'db' : 'lykchat', + 'user' : 'lykchat', + 'pwd' : '1qaz2wsx', + 'type' : 'default', + }, + 'mechanism':"SCRAM-SHA-1" +} + diff --git a/library/config/frontend.py b/library/config/frontend.py new file mode 100644 index 0000000..9f30340 --- /dev/null +++ b/library/config/frontend.py @@ -0,0 +1 @@ +adminuser = 'lykchat' \ No newline at end of file diff --git a/library/config/wechat.py b/library/config/wechat.py index a979929..6aeceb9 100644 --- a/library/config/wechat.py +++ b/library/config/wechat.py @@ -1,41 +1,33 @@ -import os, platform +import os from lykchat.settings import BASE_DIR -version = '2.1.0' base_url = 'https://wx2.qq.com' -os_type = platform.system() # Windows, Linux, Darwin curr_dir = os.getcwd() qr_dir = BASE_DIR + '/static/qr' appid = 'wx782c26e4c19acffb' -# 微信appid - -user_mess_dict = { - 'lykchat' : {'login_pwd' : 'lykchat', 'interface_pwd' : '123456'}, - 'zabbix' : {'login_pwd' : 'zabbix', 'interface_pwd' : '123456'}, - 'test' : {'login_pwd' : 'test', 'interface_pwd' : '123456'}, - } -# web账号信息,login_pwd用于web端登陆密码,interface_pwd用于接口发送信息密码 - + # 微信appid,是微信端指定的,不要随意修改 +curr_dir = os.getcwd() + login_status_code_dict = { - 100 : {'descript':'空空如也', 'suggest':'正在初始化,请稍后!如果你等待很久,请刷新页面', 'status': 100}, + 100 : {'descript':'空空如也', 'suggest':'正在初始化请稍后,如你等待很久请刷新', 'status': 100}, 101 : {'descript':'初始化完毕', 'suggest':'请扫二维码,如已扫码请刷新页面', 'status': 101}, 102 : {'descript':'等待扫码', 'suggest':'请扫二维码,如已扫码请刷新页面', 'status': 102}, - 200 : {'descript':'确认登陆', 'suggest':'请刷新页面', 'status': 200}, - 201 : {'descript':'扫码成功', 'suggest':'请刷新页面', 'status': 201}, + 200 : {'descript':'确认登陆', 'suggest':'请在手机上确认,如已确认请刷新页面', 'status': 200}, + 201 : {'descript':'扫码成功', 'suggest':'请在手机上确认,如已确认请刷新页面', 'status': 201}, 202 : {'descript':'等待扫码', 'suggest':'请扫二维码,如已扫码请刷新页面', 'status': 202}, 221 : {'descript':'登陆初始化完成', 'suggest':'请刷新页面', 'status': 221}, - 222 : {'descript':'保持登陆', 'suggest':'可以发送信息', 'status': 222}, - 400 : {'descript':'未知原因退出登陆', 'suggest':'未知原因退出登陆,请重新登陆', 'status': 400}, - 401 : {'descript':'被微信强制退出', 'suggest':'请重新登陆,请重新登录!如果多次如此,可能是微信修改了接口,请联系开发者', 'status': 401}, + 222 : {'descript':'登陆中', 'suggest':'可以发送信息', 'status': 222}, + 400 : {'descript':'未知原因退出', 'suggest':'请重新登陆', 'status': 400}, + 401 : {'descript':'被微信强制退出', 'suggest':'请重新登陆', 'status': 401}, 402 : {'descript':'手机端退出登陆', 'suggest':'请重新登陆', 'status': 402}, 403 : {'descript':'该微信号人为禁止登陆', 'suggest':'该微信号人为禁止登陆', 'status': 403}, - 404 : {'descript':'可能被拉黑', 'suggest':'如果多次出现这个情况,被服务器拉黑,等几个小时在登陆吧,或者换个微信', 'status': 404}, + 404 : {'descript':'可能被微信端拉黑', 'suggest':'如果多次出现,被微信端拉黑,等一段时间再登陆或者换微信号', 'status': 404}, 408 : {'descript':'二维码已失效', 'suggest':'请刷新页面', 'status': 408}, 444 : {'descript':'web页面退出登陆', 'suggest':'请重新登陆', 'status': 444}, - 500 : {'descript':'被拉黑了', 'suggest':'无人道,又被拉黑!等几个小时在登陆把,或者换个微信吧', 'status': 500}, - } + 500 : {'descript':'可能被微信端拉黑', 'suggest':'出现在确认登陆前出现说明二维码过期;负责被拉黑,等一段时间再登陆或者换微信号', 'status': 500}, +} # 200、201、408属于微信自定义状态,其他自定义的 - + language = 'Chinese' sendresult_translation_dict = { 'Chinese': { @@ -46,40 +38,18 @@ - 1004: u'服务器返回异常值', - 1005: u'参数错误', - 1006: u'未知错误', + - 9999: u'执行时代码出现异常', 1201: u'该微信不存在', 1101: u'微信退出登录', 1204: u'疑似原因:可能是自己的微信号', + 3 : u'疑似原因:手机端退出超过1天,手机登陆该微信', 0: u'发送成功', }, } - -DATABASES = { - 'default': { - 'ENGINE' : 'django.db.backends.mysql', - 'NAME' : 'lykchat', - 'USER' : 'lykchat', - 'PASSWORD' :'!QAZ2wsx', - 'HOST' : '127.0.0.1', - 'PORT' : '3306', - 'OPTIONS': { - 'charset': 'utf8', - 'use_unicode': True, - }, - }, -} - -SESSION_COOKIE_AGE = 60 * 60 * 1 - + max_upload_size = 1024 * 1024 * 5 # 上传文件最大值,单位bytes,默认5M - -CRONJOBS = ( - ('*/2 * * * *', 'library.cron.checklogin.check_login', '>>/dev/shm/lykchat.txt 2>&1'), -) -# 检测登陆状态的计划任务 - -url_frond = 'http://127.0.0.1/' - + friendlist_field_list = ['UserName', 'NickName', 'Alias', 'Sex', 'RemarkName'] ''' UserName,每个好友的唯一标示 diff --git a/library/cron/__init__.py b/library/connecter/__init__.py similarity index 100% rename from library/cron/__init__.py rename to library/connecter/__init__.py diff --git a/library/connecter/__pycache__/__init__.cpython-36.pyc b/library/connecter/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..c53c14e Binary files /dev/null and b/library/connecter/__pycache__/__init__.cpython-36.pyc differ diff --git a/library/file/__init__.py b/library/connecter/visit_url/__init__.py similarity index 100% rename from library/file/__init__.py rename to library/connecter/visit_url/__init__.py diff --git a/library/connecter/visit_url/__pycache__/__init__.cpython-36.pyc b/library/connecter/visit_url/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..82a36d8 Binary files /dev/null and b/library/connecter/visit_url/__pycache__/__init__.cpython-36.pyc differ diff --git a/library/visit_url/request/cookie.py b/library/connecter/visit_url/cookie.py similarity index 55% rename from library/visit_url/request/cookie.py rename to library/connecter/visit_url/cookie.py index 950d069..cb966e5 100644 --- a/library/visit_url/request/cookie.py +++ b/library/connecter/visit_url/cookie.py @@ -1,26 +1,35 @@ -import requests, re +import requests, re, logging + class Request_Url(): + ''' 使用requests模块访问web页面,必须提供url ''' + def __init__(self, url, headers={}, cookies={}, data={}, files={}, params={}, allow_redirects=True): + self.logger = logging.getLogger("default") default_headers = { 'Accept' : 'application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding' : 'gzip, deflate, br', 'charset': 'UTF-8', + # 'Upgrade-Insecure-Requests':'1',用于让浏览器自动升级请求从http到https,一定不要添加,上传文件后无法发送文件 'Accept-Language': 'en-US,en;q=0.8,zh-CN;q=0.5,zh;q=0.3', + # 'Accept-Language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3', 'Connection': 'keep-alive', - 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0', + 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0', } - if headers == {} : + try : + if not headers : + headers = default_headers + else : + for header in ['Accept' , 'Accept-Encoding' , 'Accept-Language', 'Connection', 'User-Agent']: + if header not in headers or not headers[header]: + headers[header] = default_headers[header] + except : headers = default_headers - else : - for header in ['Accept' , 'Accept-Encoding' , 'Accept-Language', 'Connection', 'User-Agent']: - if header not in headers or headers[header] == '': - headers[header] = default_headers[header] - # 新增headers + # 新增headers url_protocol_list = ['http' , 'https'] url_protocol = re.split('://' , url)[0] @@ -28,42 +37,57 @@ def __init__(self, url, headers={}, cookies={}, data={}, files={}, params={}, al self.url = 'http://' + url else : self.url = url + + ''' + if 'Host' not in headers : + url = re.split('://' , self.url)[1] + headers['Host'] = re.split('/' , url)[0] + 一定不要添加,上传文件后无法发送文件 + ''' try : if data != {} : url_req = requests.post(self.url, headers=headers , data=data, cookies=cookies , allow_redirects=allow_redirects) # post方法 elif files != {} : - url_req = requests.post(self.url, data=files, headers=headers, timeout=300) + # url_req = requests.post(self.url, data=files, headers=headers, timeout=600) + url_req = requests.post(self.url, data=files, headers=headers, cookies=cookies, timeout=600) + # 上传文件 else : url_req = requests.get(self.url, headers=headers, cookies=cookies , allow_redirects=allow_redirects, params=params) # get方法 except Exception as e : - print(e) + url_req = None + self.logger.error('访问' + self.url + '时出错,原因:' + str(e)) + self.url_req = url_req # 访问后设置headers,用于后续访问,如果没有的,不设置 try : for header in ['Content-Type' , 'Connection' , 'Domain', 'Referer']: if header in self.url_req.headers: headers[header] = self.url_req.headers[header] + ''' if 'Content-Length' in self.url_req.headers : - site_size = self.url_req.headers['Content-Length'] - self.site_size = int(site_size) + size_size = self.url_req.headers['Content-Length'] + self.size_size = int(size_size) + ''' except : pass self.headers = headers - self.url_req = url_req - new_cookies = dict(self.url_req.cookies) + #self.url_req = url_req + try : + new_cookies = dict(self.url_req.cookies) + except : + new_cookies = {} - if new_cookies != cookies or new_cookies != {}: - for k, v in new_cookies.items() : - cookies[k] = v + for k, v in new_cookies.items() : + cookies[k] = v # 更新cookies self.cookies = cookies - # 指定cookie,用于后续访问,微信中需要保持session即可 + # 指定cookie,用于后续访问 def return_web_request_base_dict(self): @@ -72,3 +96,4 @@ def return_web_request_base_dict(self): def return_context(self): return self.url_req + diff --git a/library/connecter/wechat/__init__.py b/library/connecter/wechat/__init__.py new file mode 100644 index 0000000..7aba9f3 --- /dev/null +++ b/library/connecter/wechat/__init__.py @@ -0,0 +1,47 @@ +import logging +import os, platform + +import library.config.wechat as wechat_conf +from library.storage.cache import Manager_Cache +from library.storage.database.mongo import Op_Mongo +from library.storage.logging.mongo import Logging_Mongo + +class Base(): + def __init__(self, session_info_dict={}): + self.logger = logging.getLogger("wechat") + self.curr_dir = wechat_conf.curr_dir + self.qr_dir = wechat_conf.qr_dir + self.appid = wechat_conf.appid + self.login_status_code_dict = wechat_conf.login_status_code_dict + self.language = wechat_conf.language + self.sendresult_translation_dict = wechat_conf.sendresult_translation_dict + self.max_upload_size = wechat_conf.max_upload_size + self.base_dir = wechat_conf.BASE_DIR + self.friendlist_field_list = wechat_conf.friendlist_field_list + # 加载配置 + + self.session_info_dict = session_info_dict + self.web_request_base_dict = self.session_info_dict.get('web_request_base_dict', {}) + self.uuid = self.session_info_dict.get('uuid', None) + self.status = self.session_info_dict.get('status', 100) + self.redirect_uri = self.session_info_dict.get('redirect_uri', '') + self.login_info = self.session_info_dict.get('login_info', {}) + self.friend_list = self.session_info_dict.get('friend_list', {}) + self.base_url = self.session_info_dict.get('url', wechat_conf.base_url) + self.sid = self.session_info_dict.get('sid', '') + self.uin = self.session_info_dict.get('uin', '') + self.deviceid = self.session_info_dict.get('deviceid', '') + self.synckey = self.session_info_dict.get('synckey', '') + self.SyncKey = self.session_info_dict.get('SyncKey', '') + self.sync_url = self.session_info_dict.get('sync_url', '') + self.mmwebwx_url = self.login_info.get('url', self.base_dir) + self.myself = self.session_info_dict.get('myself', {}) + self.base_request = self.login_info.get('BaseRequest', {}) + self.skey = self.login_info.get('skey', '') + self.pass_ticket = self.login_info.get('pass_ticket', '') + self.firstpage_contactlist = self.session_info_dict.get('firstpage_contactlist', []) + self.groupuser_list = self.session_info_dict.get('groupuser_list', []) + + if not self.friend_list : + for field in self.friendlist_field_list: + self.friend_list[field] = [] diff --git a/library/connecter/wechat/__pycache__/__init__.cpython-36.pyc b/library/connecter/wechat/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000..024859a Binary files /dev/null and b/library/connecter/wechat/__pycache__/__init__.cpython-36.pyc differ diff --git a/library/connecter/wechat/__pycache__/friend.cpython-36.pyc b/library/connecter/wechat/__pycache__/friend.cpython-36.pyc new file mode 100644 index 0000000..199cb8b Binary files /dev/null and b/library/connecter/wechat/__pycache__/friend.cpython-36.pyc differ diff --git a/library/connecter/wechat/__pycache__/login.cpython-36.pyc b/library/connecter/wechat/__pycache__/login.cpython-36.pyc new file mode 100644 index 0000000..8a73ccc Binary files /dev/null and b/library/connecter/wechat/__pycache__/login.cpython-36.pyc differ diff --git a/library/connecter/wechat/__pycache__/logout.cpython-36.pyc b/library/connecter/wechat/__pycache__/logout.cpython-36.pyc new file mode 100644 index 0000000..9abfd29 Binary files /dev/null and b/library/connecter/wechat/__pycache__/logout.cpython-36.pyc differ diff --git a/library/connecter/wechat/__pycache__/ready.cpython-36.pyc b/library/connecter/wechat/__pycache__/ready.cpython-36.pyc new file mode 100644 index 0000000..a48dd75 Binary files /dev/null and b/library/connecter/wechat/__pycache__/ready.cpython-36.pyc differ diff --git a/library/connecter/wechat/__pycache__/send.cpython-36.pyc b/library/connecter/wechat/__pycache__/send.cpython-36.pyc new file mode 100644 index 0000000..fd994e6 Binary files /dev/null and b/library/connecter/wechat/__pycache__/send.cpython-36.pyc differ diff --git a/library/connecter/wechat/friend.py b/library/connecter/wechat/friend.py new file mode 100644 index 0000000..5908a02 --- /dev/null +++ b/library/connecter/wechat/friend.py @@ -0,0 +1,213 @@ +import json, time, re + +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base + + +class Get_Friend(Base): + def get_friend_list(self): + + ''' + 获得所有好友信息,返回好友清单 + 格式如下: + { + 'UserName':['@aafadfd','@bbbbbb',....], + 'NickName':['lykops','ops',....] + 'Alias':['lykops','ops',....] + 'Sex':[1,2,...] + } + ''' + + url = '%s/webwxgetcontact?r=%s&seq=0&skey=%s' % (self.mmwebwx_url, int(time.time() * 1000), self.skey) + # url = '%s/webwxgetcontact?lang=zh_CN&pass_ticket=%s&r=%s&seq=0&skey=%s' % (self.mmwebwx_url, self.pass_ticket, int(time.time() * 1000), self.skey) + open_url = Request_Url(url, **self.web_request_base_dict) + self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] = open_url.return_web_request_base_dict() + url_req = open_url.return_context() + + member_list = json.loads(url_req.content.decode('utf-8', 'replace')) + member_list = member_list['MemberList'] + ''' + [{'RemarkPYQuanPin': '', 'HeadImgUrl': '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=645115088&username=@4f9f900c9207f0d6d1e104ed99561de1&skey=@crypt_2b05caf0_8cef13f418418c2333f2cb1a3b3172e1', 'AppAccountFlag': 0, 'Statues': 0, 'SnsFlag': 0, 'AttrStatus': 0, 'PYQuanPin': 'gaoxiaokaifayunwei', 'City': 'Chaoyang', 'HideInputBarFlag': 0, 'ContactFlag': 3, 'Uin': 0, 'Sex': 0, 'PYInitial': 'GXKFYW', 'OwnerUin': 0, 'RemarkName': '', 'Alias': 'DevOpsGeek', 'Signature': 'InfoQ。~', 'KeyWord': 'gh_', 'RemarkPYInitial': '', 'UniFriend': 0, 'UserName': '@4f9f900c9207f0d6d1e104ed99561de1', 'ChatRoomId': 0, 'MemberCount': 0, 'Province': 'Beijing', 'DisplayName': '', 'NickName': '高效开发运维', 'VerifyFlag': 24, 'EncryChatRoomId': '', 'StarFriend': 0, 'MemberList': []}] + sex: + 1为男 + 2为女 + 0为其他 + ''' + + for field in self.friendlist_field_list: + self.friend_list[field] = [] + # 需要清空,否则在web页面上登陆后,不更新程序时,出现旧数据不会并删除 + + for friend_message in member_list : + self._update_friend(friend_message) + + for contact in self.firstpage_contactlist : + self._update_friend(contact) + + self._update_myself() + # 更新个人信息 + + self.session_info_dict['friend_list'] = self.friend_list + return (True, self.session_info_dict) + + + def get_singlefriend_dict(self, friend, post_field='UserName'): + + ''' + 好友信息进行转化,把现有字段post_field转化从字典 + ''' + + if not self.friend_list : + self.friend_list = self.get_friend_list() + + if post_field not in self.friendlist_field_list : + self.logger.error('查找好友失败,字段' + post_field + '不在field_list中') + return (False, '查找好友失败,字段' + post_field + '不在field_list中') + + if not friend: + self.logger.error('查找好友失败,参数friend为空') + return (False, '查找好友失败,参数friend为空') + + try : + # self.update_friend_list() + self.friend_list = self.session_info_dict['friend_list'] + friend_index = self.friend_list[post_field].index(friend) + except Exception as e: + self.logger.error('查找好友失败,未知错误,' + str(e)) + return (False, '查找好友失败,未知错误,' + str(e)) + + friend_dict = {} + for field in self.friendlist_field_list: + friend_dict[field] = self.friend_list[field][friend_index] + + return (True, friend_dict) + + ''' + def update_friend_list(self): + + 通过微信第一页好友列表获取好友,追加好友 + + for contact in self.firstpage_contactlist : + self._update_friend(contact) + + self._update_myself() + # 更新个人信息 + + self.session_info_dict['friend_list'] = self.friend_list + return (True,self.friend_list) + ''' + + def get_group_contact(self, group_username , username='' , username_list=[]): + + ''' + 根据群的username和成员的username,获得成员信息 + ''' + + url = '%s/webwxbatchgetcontact?type=ex&r=%s&lang=zh_CN&pass_ticket=%s' % (self.mmwebwx_url, int(time.time() * 1000), self.pass_ticket) + post_list = [] + + if not group_username or not re.search('^@@', group_username): + self.logger.error('查找群组失败,参数group_username不是一个合法的群组') + return (False, '查找群组失败,参数group_username不是一个合法的群组') + + if username_list : + for username in username_list : + temp = {"UserName":username, "EncryChatRoomId":group_username} + post_list.append(temp) + list_count = len(username_list) + else : + if username and re.search('@' , username): + temp = {"UserName":username, "EncryChatRoomId":group_username} + post_list.append(temp) + list_count = 1 + else : + self.logger.error('没有查到该群组的成员') + return (False, '没有查到该群组的成员') + + data = { + 'BaseRequest' : self.base_request, + 'List' : post_list, + 'Count' : list_count, + } + + open_url = Request_Url(url, data=json.dumps(data) , **self.web_request_base_dict) + self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] = open_url.return_web_request_base_dict() + url_req = open_url.return_context() + web_dict = json.loads(url_req.content.decode('utf-8', 'replace')) + contact_list = web_dict['ContactList'] + + group_contact_dict = {} + for contact in contact_list : + group_contact_dict[username] = {'UserName':username , 'Alias' : contact['Alias'], 'NickName' : contact['NickName'], 'Sex' : contact['Sex']} + + # 更新好友列表 + self._update_friend(group_contact_dict[username]) + + return (True, group_contact_dict) + + + def _update_myself(self): + + ''' + 通过群获取登陆微信号的信息 + ''' + + myself = self.session_info_dict['myself'] + my_user = myself['UserName'] + my_alias = myself['Alias'] + my_nick = myself['NickName'] + + if not my_alias or not my_nick : + try : + group_username = self.groupuser_list[0] + except : + for contact in self.firstpage_contactlist : + if re.search('@@' , contact['UserName']) : + group_username = contact.get('UserName', '') + break + else : + group_username = '' + + if not isinstance(group_username, str) : + group_username = '' + + if group_username and re.search('@@' , group_username) : + result = self.get_group_contact(group_username, username=my_user) + if result[0] : + group_contact_dict = result[1].get(my_user, {}) + else : + group_contact_dict = {} + + if group_contact_dict: + self.logger.info(group_contact_dict) + self.session_info_dict['myself']['NickName'] = group_contact_dict['NickName'] + self.session_info_dict['myself']['Alias'] = group_contact_dict['Alias'] + self.session_info_dict['myself']['UserName'] = my_user + # return (True,self.session_info_dict['myself']) + + + def _update_friend(self, friend_dict): + + ''' + 向好友列表中更新单个好友信息 + ''' + + user = friend_dict['UserName'] + if user in self.friend_list['UserName']: + user_index = self.friend_list['UserName'].index(user) + self.friend_list['Alias'][user_index] = friend_dict['Alias'] + self.friend_list['NickName'][user_index] = friend_dict['NickName'] + self.friend_list['Sex'][user_index] = friend_dict['Sex'] + try : + self.friend_list['RemarkName'][user_index] = friend_dict['RemarkName'] + except : + self.friend_list['RemarkName'][user_index] = '' + else : + for field in self.friendlist_field_list: + try : + value = friend_dict[field] + except : + value = '' + self.friend_list[field].append(value) + + self.session_info_dict['friend_list'] = self.friend_list diff --git a/library/wechat/login.py b/library/connecter/wechat/login.py similarity index 82% rename from library/wechat/login.py rename to library/connecter/wechat/login.py index c3f1008..584db24 100644 --- a/library/wechat/login.py +++ b/library/connecter/wechat/login.py @@ -1,29 +1,16 @@ import json, time, re , random , xml.dom.minidom +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base -from library.config import wechat -from library.visit_url.request.cookie import Request_Url - - -class Login(): - def __init__(self , session_info_dict): - self.field_list = wechat.friendlist_field_list - - self.session_info_dict = session_info_dict - self.login_info = self.session_info_dict['login_info'] - self.redirect_uri = self.session_info_dict['redirect_uri'] - self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] - - if not self.login_info : - self.login_info = {} - - +class Login(Base): def init_login(self): + ''' 扫码、点击确认之后的第一步 ''' - if (self.redirect_uri == '' or not self.redirect_uri) and (self.login_info == {} or not self.login_info): - self.session_info_dict['status'] = 100 - return self.session_info_dict + + if not self.redirect_uri and not self.login_info: + return (False, '请先执行扫码和确认登陆等环节') open_url = Request_Url(self.redirect_uri, allow_redirects=False, **self.web_request_base_dict) self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] = open_url.return_web_request_base_dict() @@ -42,7 +29,12 @@ def init_login(self): elif node.nodeName == 'wxsid': self.login_info['wxsid'] = self.login_info['BaseRequest']['Sid'] = node.childNodes[0].data elif node.nodeName == 'wxuin': - self.login_info['wxuin'] = self.login_info['BaseRequest']['Uin'] = node.childNodes[0].data + uin = node.childNodes[0].data + try : + uin = int(uin) + except : + pass + self.login_info['wxuin'] = self.login_info['BaseRequest']['Uin'] = uin elif node.nodeName == 'pass_ticket': self.login_info['pass_ticket'] = self.login_info['BaseRequest']['DeviceID'] = node.childNodes[0].data elif node.nodeName == 'ret': @@ -56,30 +48,33 @@ def init_login(self): self.login_info['isgrayscale'] = node.childNodes[0].data if self.login_info['BaseRequest'] == {} : - self.status = 500 - self.session_info_dict['status'] = self.status + self.session_info_dict['status'] = self.status = 500 self.session_info_dict['login_info'] = self.login_info ''' if self.login_info['ret'] == '1203' : - print(" 你的微信可能被封,请更换微信登陆或者过几小时再试") + print("你的微信可能被封,请更换微信登陆或者过几小时再试") ''' - return self.session_info_dict + return (True, self.session_info_dict) else : - self.session_info_dict['status'] = 221 + self.session_info_dict['status'] = self.status = 221 self.session_info_dict['login_info'] = self.login_info - return self.session_info_dict + return (True, self.session_info_dict) def check_login(self): + ''' 获得微信号登陆信息,可检测是否保持登陆成功 + ''' + ''' self.base_url = self.login_info['url'] self.base_request = self.login_info['BaseRequest'] self.pass_ticket = self.login_info['pass_ticket'] self.skey = self.login_info['skey'] + ''' - url = '%s/webwxinit?r=%s&pass_ticket=%s&skey=%s' % (self.base_url , ~int(time.time()), self.pass_ticket, self.skey) + url = '%s/webwxinit?r=%s&pass_ticket=%s&skey=%s' % (self.mmwebwx_url , ~int(time.time()), self.pass_ticket, self.skey) data = { 'BaseRequest': self.base_request } data = json.dumps(data) @@ -99,14 +94,14 @@ def check_login(self): status = 401 if status >= 300 : - self.session_info_dict['status'] = status - return self.session_info_dict + self.session_info_dict['status'] = self.status = status + return (True, self.session_info_dict) self.myself = {} for key , value in web_dict.items() : if key == 'User' : # 微信号信息 - for field in self.field_list: + for field in self.friendlist_field_list: try : self.myself[field] = value[field] except : @@ -134,7 +129,7 @@ def check_login(self): firstpage_contactlist = [] for contact in web_dict['ContactList'] : contact_list = {} - for field in self.field_list: + for field in self.friendlist_field_list: contact_list[field] = contact[field] firstpage_contactlist.append(contact_list) self.session_info_dict['firstpage_contactlist'] = firstpage_contactlist @@ -143,11 +138,20 @@ def check_login(self): self.session_info_dict['login_info'] = self.login_info self.session_info_dict['status'] = status + ''' groupuser_list = self.session_info_dict['groupuser_list'] frienduser_list = self.session_info_dict['frienduser_list'] - if (frienduser_list == [] or not frienduser_list) and (groupuser_list == [] or not groupuser_list) : + + for frienduser in firstpage_contactlist : + username = frienduser.get('UserName', '') + if re.search('@@', username) : + groupuser_list.append(frienduser) + + if not frienduser_list and not groupuser_list : self.webwxsync() - return self.session_info_dict + ''' + + return (True, self.session_info_dict) def status_notify(self): @@ -155,7 +159,7 @@ def status_notify(self): 用于告知微信手机端登录状态 ''' self.myself = self.session_info_dict['myself'] - url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.base_url, self.pass_ticket) + url = '%s/webwxstatusnotify?lang=zh_CN&pass_ticket=%s' % (self.mmwebwx_url, self.pass_ticket) data = { 'BaseRequest': self.base_request, "Code":3, @@ -180,7 +184,6 @@ def webwxsync(self): self.sid = self.login_info['wxsid'] url = '%s/webwxsync?sid=%s&skey=%s' % (self.base_url , self.sid, self.skey) - data = { 'BaseRequest': self.base_request, 'SyncKey' : self.login_info['SyncKey'] , @@ -193,6 +196,8 @@ def webwxsync(self): web_dict = url_req.content.decode('utf-8', 'replace') web_dict = json.loads(web_dict) + self.logger.info(web_dict) + try : frienduser = web_dict['AddMsgList'][0]['StatusNotifyUserName'] # 注意:不是每次执行会出现这个结果的,大概1%的概率 @@ -207,4 +212,4 @@ def webwxsync(self): self.session_info_dict['groupuser_list'] = groupuser_list self.session_info_dict['frienduser_list'] = frienduser_list - return self.session_info_dict + return (True, self.session_info_dict) diff --git a/library/connecter/wechat/logout.py b/library/connecter/wechat/logout.py new file mode 100644 index 0000000..3ff16f7 --- /dev/null +++ b/library/connecter/wechat/logout.py @@ -0,0 +1,19 @@ +import json + +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base + + +class Logout(Base): + def logout(self): + ''' + 退出登录 + ''' + url = '%s/webwxlogout?redirect=1&type=0&skey=%s&sid=%s&uin=%s' % (self.mmwebwx_url, self.skey, self.sid , self.uin) + data = { + 'sid' : self.sid, + 'uin' : self.uin, + } + data = json.dumps(data) + open_url = Request_Url(url, data=data , **self.web_request_base_dict) + open_url.return_context() diff --git a/library/wechat/ready.py b/library/connecter/wechat/ready.py similarity index 56% rename from library/wechat/ready.py rename to library/connecter/wechat/ready.py index 62ce26d..645923d 100644 --- a/library/wechat/ready.py +++ b/library/connecter/wechat/ready.py @@ -1,76 +1,40 @@ -import os -import time, sys, re +import time, re -from library.config import wechat -from library.visit_url.request.cookie import Request_Url -from lykchat.settings import BASE_DIR +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base +from library.utils.path import make_dir - -class Ready(): +class Ready(Base): + ''' 登陆准备工作 通过二维码,扫描、确认登陆等检测 ''' - def __init__(self , session_info_dict={}): - self.os_type = wechat.os_type - self.base_url = wechat.base_url - self.qr_dir = wechat.qr_dir - self.appid = wechat.appid - - self.session_info_dict = session_info_dict - self.uuid = self.session_info_dict['uuid'] - self.status = self.session_info_dict['status'] - self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] - - if (self.uuid == '' or not self.uuid) or (not self.web_request_base_dict or self.web_request_base_dict == {}): - web_request_base_dict = {} - open_url = Request_Url(self.base_url, **web_request_base_dict) - self.web_request_base_dict = open_url.return_web_request_base_dict() - # 初始化self.web_request_base_dict,如果uuid和web_request_base_dict不为空说明已经初始化 - - if self.uuid == '' or not self.uuid : - self.uuid = self._get_qruuid() - self.status = 100 - else : - self.status = 100 - - self.redirect_uri = False - self.enablecmdqr = 1 - - def _scan_qr(self): - ''' - 扫描二维码 - ''' - for count in range(10) : - qrstorage = self.get_qr() - if qrstorage: - break - elif count == 9: - sys.exit() - - def check_status(self): + ''' 检测当前状态 - 关于最初status值为: - 文本界面-1 - web界面100 ''' - status = self.status - self.redirect_uri = '' + + self.uuid = self.session_info_dict.get('uuid', '') + status = self.session_info_dict.get('status', 100) + if not self.web_request_base_dict or not self.uuid: + return (False, '请先获取二维码') + count = 0 while 1 : - print(status) - if status < 0 : - self._scan_qr() - elif status < 200 or status == 201 : + if status < 200 : pass - elif status == 200 or status >= 400 : - break - else : - status = 500 + if status == 201 : + pass + elif status == 200 : + pass + elif status >= 400 : break + # else : + # status = 500 + # break if count == 10 : break @@ -82,16 +46,18 @@ def check_status(self): self.session_info_dict['status'] = status self.session_info_dict['redirect_uri'] = self.redirect_uri self.session_info_dict['web_request_base_dict'] = self.web_request_base_dict - return self.session_info_dict + return (True, self.session_info_dict) def _check_confirm(self): + ''' 判断是否微信用户在手机上是否点击确认,并返回一个状态吗,类似与http响应码 ''' + url = self.base_url + '/cgi-bin/mmwebwx-bin/login' - local_timestamp = int(time.time() * 1000) - params = 'loginicon=true&uuid=%s&tip=0&r=%s&_=%s' % (self.uuid, -int(local_timestamp / 966), local_timestamp) + local_timestamp = time.time() + params = 'loginicon=true&uuid=%s&tip=0&r=%s&_=%s' % (self.uuid, ~int(local_timestamp), local_timestamp * 1000) open_url = Request_Url(url, params=params, **self.web_request_base_dict) self.web_request_base_dict = open_url.return_web_request_base_dict() @@ -116,10 +82,12 @@ def _check_confirm(self): return 500 - def _get_qruuid(self): + def get_qruuid(self): + ''' 获取登陆时二维码uuid ''' + url = self.base_url + '/jslogin' local_timestamp = int(time.time()*1000) params = { @@ -143,31 +111,36 @@ def _get_qruuid(self): def get_qr(self): + ''' 根据uuid获取二维码 ''' - url = '%s/qrcode/%s' % (self.base_url, self.uuid) + + if not self.web_request_base_dict: + open_url = Request_Url(self.base_url, {}) + self.web_request_base_dict = open_url.return_web_request_base_dict() + # 初始化self.web_request_base_dict + + if self.uuid is None : + uuid = self.get_qruuid() + + url = '%s/qrcode/%s' % (self.base_url, uuid) try: open_url = Request_Url(url, **self.web_request_base_dict) self.web_request_base_dict = open_url.return_web_request_base_dict() url_req = open_url.return_context() except: - return False - - picdir = self.qr_dir + '/' + self.uuid + '.jpg' - - if os.path.exists(self.qr_dir) : - if not os.path.isdir(self.qr_dir) : - os.rename(self.qr_dir, self.qr_dir + '-' + str(int(time.time)) + '-lykchat-bk') - os.mkdir(self.qr_dir) - else : - os.mkdir(self.qr_dir) + return (False, '获取二维码失败') + picdir = self.qr_dir + '/' + uuid + '.jpg' + make_dir(self.qr_dir, chmods=755 , force=True , backup=True) with open(picdir, 'wb') as f: f.write(url_req.content) - self.session_info_dict['uuid'] = self.uuid + self.session_info_dict['uuid'] = uuid self.session_info_dict['status'] = 202 self.session_info_dict['web_request_base_dict'] = self.web_request_base_dict self.session_info_dict['qr_stamptime'] = int(time.time()) - self.session_info_dict['qr_url'] = picdir.replace(BASE_DIR, '') - return self.session_info_dict + self.session_info_dict['start_timestamp'] = int(time.time()) + self.session_info_dict['qr_url'] = picdir.replace(self.base_dir, '') + + return (True, self.session_info_dict) diff --git a/library/wechat/receive.py b/library/connecter/wechat/receive.py similarity index 74% rename from library/wechat/receive.py rename to library/connecter/wechat/receive.py index a364e7b..e7b1962 100644 --- a/library/wechat/receive.py +++ b/library/connecter/wechat/receive.py @@ -1,31 +1,20 @@ import json, time , re -from library.visit_url.request.cookie import Request_Url +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base -class Receive_Msg(): +class Receive_Msg(Base): + ''' 接受和发送信息 ''' - def __init__(self, session_info_dict): - self.session_info_dict = session_info_dict - self.web_request_base_dict = self.session_info_dict['web_request_base_dict'] - self.login_info = self.session_info_dict['login_info'] - self.skey = self.login_info['skey'] - self.sid = self.login_info['wxsid'] - self.uin = self.login_info['wxuin'] - self.deviceid = self.login_info['deviceid'] - self.synckey = self.login_info['synckey'] - self.SyncKey = self.login_info['SyncKey'] - self.sync_url = self.login_info['sync_url'] - self.pass_ticket = self.login_info['pass_ticket'] - self.base_url = self.login_info['url'] - self.base_request = self.login_info['BaseRequest'] - def _check_newmsg(self): + ''' 用于获取最新接受信息数 ''' + url = '%s/synccheck' % self.sync_url local_ts = int(time.time() * 1000) @@ -67,8 +56,7 @@ def receive(self): msg_no = self._check_newmsg() msg_no = str(msg_no) if msg_no == '0' : - print('没有新的消息') - return [] + return (True, []) url = '%s/webwxsync?sid=%s&skey=%s&pass_ticket=%s' % (self.base_url, self.sid, self.skey, self.pass_ticket) data = { @@ -94,4 +82,4 @@ def receive(self): self.login_info['synckey'] = self.login_info['synckey'] + '|' + temp self.message_list = newmessage_dict['AddMsgList'] - return self.message_list + return (True, self.message_list) diff --git a/library/connecter/wechat/send.py b/library/connecter/wechat/send.py new file mode 100644 index 0000000..b40ee5b --- /dev/null +++ b/library/connecter/wechat/send.py @@ -0,0 +1,309 @@ +import json, re, time, os +import mimetypes +from requests_toolbelt.multipart.encoder import MultipartEncoder + +from library.connecter.visit_url.cookie import Request_Url +from library.connecter.wechat import Base +from library.utils.file import get_filetype +from library.utils.get_md5 import get_file_md5 +from library.utils.type_conv import random_str + +from .friend import Get_Friend + + +class Send_Msg(Base): + + ''' + 接受和发送信息 + ''' + + def send(self, content, filename='', tousername='filehelper' , post_field='UserName'): + + ''' + 发送信息,返回类型为字典 + ''' + + get_friend = Get_Friend(self.session_info_dict) + result = get_friend.get_singlefriend_dict(tousername, post_field=post_field) + if result[0] : + friend_dict = result[1] + else : + self.logger.error('找到好友时出错,' + result[1]) + return {'Msg': '找到好友时出错,' + result[1], 'Code':-1102, 'ErrMsg':'找到好友时出错', 'friend':''} + + tousername = friend_dict.get('UserName', '') + self.nickname = friend_dict.get('NickName', '') + + if not re.search('@', tousername) and tousername != 'filehelper': + self.logger.warn('找到好友时出错,没有找到指定好友') + return {'Msg': '找到好友时出错,没有找到指定好友', 'Code':-1102, 'ErrMsg':'没有找到指定好友', 'friend':''} + + if filename : + result = self._send_media(filename, tousername, content) + else : + result = self._send_text(tousername, content) + + return result + + + def _send_media(self, filename, tousername, content): + file_url = self.login_info['file_url'] + url = file_url + '/webwxuploadmedia?f=json' + + result = get_filetype(filename) + self.logger.info(result) + if result[0] : + mime_type = result[2] + media_type = re.split(r'/' , mime_type)[0] or 'application' + name = 'lykchat.' + filename.split('.')[-1] + else : + mime_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream' # MIME格式,注意是根据文件后缀名来确认的 + media_type = re.split(r'/' , mime_type)[0] or 'application' + name = 'lykchat' + # 修改name是用来解决中文文件名的情况 + + # 微信识别的文档格式 + if media_type == 'image' : + media_type = 'pic' + elif media_type == 'audio' or media_type == 'video' : + media_type = 'video' + else : + media_type = 'doc' + + # orig_name = os.path.basename(filename) # 文件名 + modts = os.path.getmtime(filename) # 文件修改日期 + modstr = time.localtime(modts) + lastModifieDate = time.strftime('%a %b %d %Y %H:%M:%S GMT+0800 (CST)', modstr) + file_content = open(filename, 'rb') + file_size = os.path.getsize(filename) # 文件大小 + chunksize = 524288 # 每个分块大小 + chunks = int((file_size - 1) / chunksize) + 1 + webwx_data_ticket = self.web_request_base_dict['cookies']['webwx_data_ticket'] + + if file_size == 0 : + msg = '文件为0字节,无法上传到微信失败' + return {'Msg': msg, 'Code':-1009, 'ErrMsg': msg, 'friend':self.nickname} + + uploadmediarequest_dict = { + 'UploadType':2, + "BaseRequest": self.base_request, + "ClientMediaId": int(time.time() * 1000), + "TotalLen": file_size, + "StartPos": 0, + "DataLen": file_size, + "MediaType": 4, + 'FromUserName':self.myself['UserName'], + 'ToUserName':tousername, + 'FileMd5':get_file_md5(filename), + } + + uploadmediarequest = json.dumps(uploadmediarequest_dict, ensure_ascii=False).encode('utf8') + + for chunk in range(chunks): + ff = file_content.read(chunksize) + + field_dict = { + 'id': 'WU_FILE_0', + 'name': name, + 'type': mime_type, + 'lastModifiedDate': lastModifieDate, + 'size':str(file_size), + 'chunks':str(chunks), + 'chunk':str(chunk), + 'mediatype': media_type, + 'uploadmediarequest': uploadmediarequest, + 'webwx_data_ticket': webwx_data_ticket, + 'pass_ticket':self.pass_ticket, + 'filename': (name , ff, mime_type) + } + boundarys = '------WebKitFormBoundary' + random_str(16) + # boundarys='---------------------------' + str(random.randint(1e28, 1e29 - 1)) + multipart_encoder = MultipartEncoder( + fields=field_dict, + boundary=boundarys + ) + self.web_request_base_dict['headers']['Origin-Type'] = self.base_url + self.web_request_base_dict['headers']['Content-Type'] = multipart_encoder.content_type + + open_url = Request_Url(url, files=multipart_encoder, **self.web_request_base_dict) + self.web_request_base_dict = open_url.return_web_request_base_dict() + try : + url_req = open_url.return_context() + web_reselt_dict = json.loads(url_req.text) + if web_reselt_dict['BaseResponse']['Ret'] != 0 : + msg = '文件上传到微信失败,被微信拒绝' + self.logger.warn(msg) + return {'Msg': msg, 'Code':2101, 'ErrMsg': web_reselt_dict, 'friend':self.nickname} + except Exception as e : + msg = '文件上传到微信失败,微信返回异常,' + str(e) + self.logger.warn(msg) + return {'Msg': msg, 'Code':2100, 'ErrMsg': e, 'friend':self.nickname} + + mediaid = web_reselt_dict['MediaId'] + if mediaid : + self.msgid = int(time.time() * 1000 * 1000 * 10) + try : + if media_type == 'pic' : + result = self._send_img(mediaid, tousername, content) + elif media_type == 'video': + result = self._send_video(mediaid, tousername, content) + else : + result = self._send_file(mediaid, name, file_size, tousername, content) + + return result + except Exception as e: + msg = '文件发送失败,执行时代码出现异常,' + str(e) + self.logger.error(msg) + return {'Msg': msg, 'Code':-1009, 'ErrMsg': msg, 'friend':self.nickname} + else : + msg = '文件上传到微信失败,被微信拒绝' + self.logger.warn(msg) + return {'Msg':msg, 'Code':-1008, 'ErrMsg': web_reselt_dict, 'friend':self.nickname} + + + def _send_img(self, mediaid, tousername, content): + + ''' + 发送图片 + ''' + + # url = self.mmwebwx_url + '/webwxsendmsgimg?fun=async&f=json&lang=zh_CN' + url = self.mmwebwx_url + '/webwxsendmsgimg?fun=async&f=json&lang=en_US' + data = { + 'BaseRequest':self.base_request, + 'Msg':{ + 'Type':3, + 'MediaId' :mediaid, + 'Content':content, + "FromUserName":self.myself['UserName'], + "ToUserName":tousername, + "LocalID":self.msgid, + "ClientMsgId":self.msgid + }, + "Scene":0 + } + data = json.dumps(data, ensure_ascii=False).encode('utf8') + open_url = Request_Url(url, data=data , **self.web_request_base_dict) + result_dict = self._handle_result(open_url) + return result_dict + + + def _send_file(self, mediaid, name, file_size, tousername, content): + + ''' + 发送普通文件 + ''' + + url = self.mmwebwx_url + '/webwxsendappmsg?fun=async&f=json&pass_ticket=' + self.pass_ticket + fileend = re.split(r'.', name)[-1] # 后缀名 + data = { + 'BaseRequest': self.base_request, + 'Msg': { + 'Type': 6, + 'Content': "" + name + "6" + content + "" + str(file_size) + "" + mediaid + "" + fileend + "", + 'FromUserName': self.myself['UserName'], + 'ToUserName': tousername, + 'LocalID': self.msgid, + 'ClientMsgId': self.msgid, + }, + 'Scene': 0, + } + data = json.dumps(data, ensure_ascii=False).encode('utf8') + open_url = Request_Url(url, data=data , **self.web_request_base_dict) + result_dict = self._handle_result(open_url) + return result_dict + + + def _send_video(self, mediaid, tousername, content): + + ''' + 发送视频文件 + ''' + + # url = self.mmwebwx_url + '/webwxsendvideomsg?fun=async&f=json&lang=zh_CN' + url = self.mmwebwx_url + '/webwxsendvideomsg?fun=async&f=json&lang=en_US' + data = { + 'BaseRequest':self.base_request, + 'Msg':{ + 'Type':43, + 'MediaId' :mediaid, + 'Content':content, + "FromUserName":self.myself['UserName'], + "ToUserName":tousername, + "LocalID":self.msgid, + "ClientMsgId":self.msgid + }, + "Scene":0 + } + self.login_info['msgid'] += 1 + data = json.dumps(data, ensure_ascii=False).encode('utf8') + open_url = Request_Url(url, data=data , **self.web_request_base_dict) + result_dict = self._handle_result(open_url) + return result_dict + + + def _send_text(self, tousername, content): + + ''' + 发送文字 + ''' + + url = '%s/webwxsendmsg' % self.mmwebwx_url + self.msgid = int(time.time() * 1000 * 1000 * 10) + data = { + 'BaseRequest': self.base_request, + 'Msg': { + 'Type': 'Test Message', + 'Content': content, + 'FromUserName': self.myself['UserName'], + 'ToUserName': tousername, + 'LocalID': self.msgid, + 'ClientMsgId': self.msgid, + }, + 'Scene' : 0 + } + data = json.dumps(data, ensure_ascii=False).encode('utf8') + open_url = Request_Url(url, data=data , **self.web_request_base_dict) + result_dict = self._handle_result(open_url) + return result_dict + + + def _handle_result(self, open_url): + + ''' + 返回发送信息结果,返回类型为字典 + ''' + + try : + self.web_request_base_dict = open_url.return_web_request_base_dict() + send_result = open_url.return_context() + except Exception as e : + msg = '微信端返回异常' + self.logger.error(msg) + return {'Msg': msg + ',' + str(e), 'Code':2000, 'ErrMsg': msg + ',' + str(e), 'friend':self.nickname} + + value_dict = {} + if send_result: + try: + value_dict = send_result.json() + base_response = value_dict['BaseResponse'] + except Exception as e: + msg = '微信端返回异常' + self.logger.error(msg) + return {'Msg': msg + ',' + str(e), 'Code':2000, 'ErrMsg': msg + ',' + str(e), 'friend':self.nickname} + else : + msg = '微信端返回异常' + self.logger.error(msg) + return {'Msg': msg + ',' + str(e), 'Code':2000, 'ErrMsg': msg + ',' + str(e), 'friend':self.nickname} + + result_code = base_response.get('Ret' , -1006) + try : + err_msg = self.sendresult_translation_dict[self.language][result_code] + except : + err_msg = '未知错误' + translation_value_dict = {'Msg' : err_msg , 'Code' : result_code, 'ErrMsg' :value_dict, 'friend':self.nickname} + + if result_code == 1101 : + translation_value_dict['ResCode'] = -1 + + return translation_value_dict diff --git a/library/cron/checklogin.py b/library/cron/checklogin.py deleted file mode 100644 index 68dea12..0000000 --- a/library/cron/checklogin.py +++ /dev/null @@ -1,28 +0,0 @@ -import json , os, django - -from library.config.wechat import url_frond -from library.config.wechat import user_mess_dict -from library.keepalive.wechat.logininfo import Manage_Logininfo -from library.visit_url.request.cookie import Request_Url - - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lykchat.settings") -django.setup() - -def check_login() : - op_info = Manage_Logininfo() - all_session_dict = op_info.get_history_all() - - for username in all_session_dict : - password = user_mess_dict[username]['interface_pwd'] - data = { - 'username':username, - 'password':password - } - - data = json.dumps(data) - url = url_frond + 'check_login?username=' + username + '&pwd=' + password - - open_url = Request_Url(url) - url_req = open_url.return_context() - print(url_req.text) diff --git a/library/file/upload.py b/library/file/upload.py deleted file mode 100644 index 8358353..0000000 --- a/library/file/upload.py +++ /dev/null @@ -1,49 +0,0 @@ -import os, time -from lykchat.settings import BASE_DIR - -def upload_file(file, filename='', username=''): - timestr = time.strftime('%Y%m%d' , time.localtime()) - timestr = str(timestr) - logfile = os.path.join(BASE_DIR, 'file/upload/index.txt') - - upload_dir = os.path.join(BASE_DIR, 'file/upload/' + timestr + '/') - if not os.path.exists(upload_dir): - try : - os.mkdir(upload_dir) - except : - os.makedirs(upload_dir) - - ''' - if filename == '' or not filename : - secstr = time.strftime('%H%M%S' , time.localtime()) - secstr = str(secstr) - randomstr = random.randint(1000, 9999) - randomstr = str(randomstr) - filename = timestr + '-' + secstr + randomstr - else : - filename = upload_dir + str(filename) - ''' - - datetimestr = time.strftime('%Y-%m-%d %H:%M:%S' , time.localtime()) - datetimestr = str(datetimestr) - log = str(username) + ' ' + datetimestr + ' ' + filename + '\n' - - filename = upload_dir + str(filename) - secstr = time.strftime('%H%M%S' , time.localtime()) - secstr = str(secstr) - if os.path.exists(filename): - os.rename(filename , filename + '-' + timestr + '-' + secstr) - - - with open(filename, 'wb+') as dest: - for chunk in file.chunks(): - dest.write(chunk) - - # os.system('chmod 444 ' + filename) - open(logfile, 'a').write(log) - return filename - - return False - - - diff --git a/library/frontend/__init__.py b/library/frontend/__init__.py new file mode 100644 index 0000000..41929c4 --- /dev/null +++ b/library/frontend/__init__.py @@ -0,0 +1,142 @@ +import logging + +from library.config.frontend import adminuser +from library.security.password import Manager_Password +from library.storage.database.mongo import Op_Mongo +from library.storage.database.redis_api import Op_Redis +from library.utils.time_conv import timestamp2datetime +from library.utils.type_conv import str2dict + + +class Base(): + def __init__(self, mongoclient=None, redisclient=None): + + ''' + 这是用户管理部分的MVC中的C + ''' + + self.logger = logging.getLogger("default") + self.userinfo_mongocollect = 'user.info' + self.userinfo_rediskey = 'lykchat:userinfo' + if mongoclient is None : + self.mongoclient = Op_Mongo() + self.logger.warn('无法继承,需要初始化mongodb连接') + else : + self.mongoclient = mongoclient + + if redisclient is None : + self.redisclient = Op_Redis() + self.logger.warn('无法继承,需要初始化redis连接') + else : + self.redisclient = redisclient + + self.password_api = Manager_Password() + self.expiretime = 60 * 60 * 24 + self.rediskey_prefix = 'lykchat:' + + + def get_userinfo(self, force=False, username=None): + + ''' + 获取userinfo数据 + ''' + + if force : + self.logger.warn('强制删除用户信息缓存') + self.redisclient.delete(self.userinfo_rediskey) + + result = self.redisclient.get(self.userinfo_rediskey, fmt='obj') + if result[0] and (result[1] is not None or result[1]) : + userinfo = result[1] + else : + result = self.mongoclient.find(self.userinfo_mongocollect) + if result[0] : + userinfo = result[1] + + set_dict = { + 'name' : self.userinfo_rediskey, + 'value' : userinfo, + 'ex':self.expiretime + } + self.redisclient.set(set_dict, fmt='obj') + else : + userinfo = {} + + if username is None : + return userinfo + else : + try : + for u_dict in userinfo : + if username == u_dict['username'] : + us = u_dict + else : + continue + except : + us = {} + + try : + return us + except : + return {} + + + def get_data(self, username, redis_key, mongo_collect, force=False, mongoshare=True): + + ''' + 获取用户数据 + :parm + username:用户名 + redis_key:redis缓存key名 + mongo_collect:mongo的集合名 + force:强制刷新 + ''' + + if force: + self.logger.warn('强制删除指定缓存') + self.redisclient.delete(redis_key) + + result = self.redisclient.get(redis_key, fmt='obj') + if not result[0] or (result[0] and not result[1]) : + if mongoshare : + result = self.mongoclient.find(mongo_collect, condition_dict={'username' : username}) + else : + result = self.mongoclient.find(mongo_collect) + + if result[0] : + data_dict = result[1] + self.write_cache(redis_key, data_dict) + else : + self.logger.error('从数据库中查询数据失败,原因:' + result[1]) + return result + else : + data_dict = result[1] + + try : + del data_dict['username'] + except : + pass + + return (True, data_dict) + + + def write_cache(self, redis_key, data, expire=60 * 60, ftm='obj'): + try : + self.logger.warn('强制删除缓存') + self.redisclient.delete(redis_key) + except : + pass + + set_dict = { + 'name' : redis_key, + 'value' : data, + 'ex':expire + } + + result = self.redisclient.set(set_dict, fmt=ftm) + if result[0] : + content = '写缓存成功' + # self.logger.info(content) + return (True, content) + else : + self.logger.info('写缓存失败,原因:' + result[1]) + return (False, '写缓存失败,' + result[1]) diff --git a/library/keepalive/__init__.py b/library/frontend/sysadmin/__init__.py similarity index 100% rename from library/keepalive/__init__.py rename to library/frontend/sysadmin/__init__.py diff --git a/library/frontend/sysadmin/uri.py b/library/frontend/sysadmin/uri.py new file mode 100644 index 0000000..1b7101b --- /dev/null +++ b/library/frontend/sysadmin/uri.py @@ -0,0 +1,240 @@ +import re + +from library.frontend import Base +from library.utils.type_conv import str2list + + +class Manager_Uri(Base): + def __init__(self, mongoclient=None, redisclient=None): + self.uridict = { + '/':{'name':'首页', 'nav':False, 'referer' : True, 'level':0, 'parent':'/'}, + '/login.html':{'name':'登陆', 'nav':False, 'referer' : False, 'level':1, 'parent':'/'}, + '/logout.html':{'name':'退出', 'nav':False, 'referer' : False, 'level':1, 'parent':'/'}, + + '/user/':{'name':'用户管理', 'nav':True, 'referer' : True, 'level':1, 'parent':'/'}, + '/user/create_admin':{'name':'创建管理员', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/detail':{'name':'用户信息', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/list':{'name':'用户列表', 'nav':False, 'referer' : True, 'level':2, 'parent':'user/'}, + '/user/add':{'name':'新增用户', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/edit':{'name':'编辑用户', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/chgpwd':{'name':'修改用户登陆密码', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/chgpvltwd':{'name':'修改用户机密密码 ', 'nav':False, 'referer' : True, 'level':2, 'parent':'/user/'}, + '/user/del':{'name':'删除用户', 'nav':False, 'referer' : False, 'level':2, 'parent':'/user/'}, + '/user/disable':{'name':'禁用用户', 'nav':False, 'referer' : False, 'level':2, 'parent':'/user/'}, + '/user/enable':{'name':'启用用户', 'nav':False, 'referer' : False, 'level':2, 'parent':'/user/'}, + + '/wechat/':{'name':'微信管理', 'nav':True, 'referer' : True, 'level':1, 'parent':'/'}, + '/wechat/list':{'name':'微信列表', 'nav':False, 'referer' : True, 'level':2, 'parent':'wechat/'}, + '/wechat/detail':{'name':'微信登陆信息详情', 'nav':False, 'referer' : True, 'level':2, 'parent':'wechat/'}, + '/wechat/logout':{'name':'微信登出', 'nav':False, 'referer' : False, 'level':2, 'parent':'wechat/'}, + '/wechat/check':{'name':'微信登陆状态检查', 'nav':False, 'referer' : False, 'level':2, 'parent':'wechat/'}, + '/wechat/sendmsg':{'name':'微信发送信息', 'nav':False, 'referer' : False, 'level':2, 'parent':'wechat/'}, + + + '/logging/':{'name':'查看日志', 'nav':True, 'referer' : True, 'level':1, 'parent':'/'}, + } + + super(Manager_Uri, self).__init__(mongoclient=mongoclient, redisclient=redisclient) + + + def get_nav(self, username, force=False): + + ''' + 获取左侧栏信息 + ''' + + redis_key = 'lykops:' + username + ':uri:nav' + if force: + self.logger.warn('强制删除缓存') + self.redisclient.delete(redis_key) + else : + result = self.redisclient.get(redis_key) + if not result[0] or (result[1] is None or not result[1]) : + nav_str = result[1] + return nav_str + + nav_str = self.gen_nav(username) + set_dict = { + 'name' : redis_key, + 'value' : nav_str, + 'ex':self.expiretime + } + self.redisclient.set(set_dict, fmt='obj') + return nav_str + + + def gen_nav(self, username): + + ''' + 生成左侧栏信息,注意:目前只支持两级 + ''' + + nav_dict = {} + for uri, value in self.uridict.items() : + if value['nav'] : + nav_dict[uri] = value + + uri_dict = {} + for key, value in nav_dict.items() : + level = value['level'] + if level == 1 : + if key not in uri_dict : + uri_dict[key] = {} + + for childkey, childvalue in nav_dict.items() : + if childvalue['parent'] == key: + uri_dict[key][childkey] = {} + + for key, value in nav_dict.items() : + level = value['level'] + if level == 2 : + parent = value['parent'] + if key not in uri_dict[parent] : + uri_dict[parent][key] = {} + + for childkey, childvalue in nav_dict.items() : + if childvalue['parent'] == key: + uri_dict[parent][key][childkey] = {} + + nav_str = '' + + for key,value in uri_dict.items() : + if not value : + name = self.uridict[key]['name'] + nav_str = nav_str + "