diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml new file mode 100644 index 0000000000..ac8460a96f --- /dev/null +++ b/.github/workflows/linux.yml @@ -0,0 +1,42 @@ +name: Compile ZeroBot-Plugin-linux +on: + push: + branches: + - master + +env: + GITHUB_TOKEN: ${{ github.token }} + +jobs: + my-job: + name: Build ZeroBot-Plugin-linux 🚀 + runs-on: ubuntu-latest + steps: + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache Go + id: cache + uses: actions/cache@v2 + with: + # A list of files, directories, and wildcard patterns to cache and restore + path: ~/go/pkg/mod + key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} + + - name: Tidy Go modules + run: go mod tidy + + - name: Build + run: go build -ldflags="-s -w" -o artifacts/ZeroBot-Plugin-linux + + - name: Upload Build Artifact + uses: actions/upload-artifact@v2 + with: + name: ZeroBot-Plugin-linux + path: ./artifacts \ No newline at end of file diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml new file mode 100644 index 0000000000..1e0293b265 --- /dev/null +++ b/.github/workflows/windows.yml @@ -0,0 +1,41 @@ +name: Compile ZeroBot-Plugin-windows +on: + push: + branches: + - master + +env: + GITHUB_TOKEN: ${{ github.token }} + +jobs: + my-job: + name: Build ZeroBot-Plugin-windows 🚀 + runs-on: windows-latest + steps: + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.15 + + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Cache Go + id: cache + uses: actions/cache@v2 + with: + path: ~/go/pkg/mod + key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} + + - name: Tidy Go modules + run: go mod tidy + + - name: Build + run: go build -ldflags="-s -w" -o artifacts/ZeroBot-Plugin-windows.exe + + - name: Upload Build Artifact + uses: actions/upload-artifact@v2 + with: + name: ZeroBot-Plugin-windows.exe + path: ./artifacts diff --git a/.github/yaya.jpg b/.github/yaya.jpg new file mode 100644 index 0000000000..d411f43575 Binary files /dev/null and b/.github/yaya.jpg differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..cab6962658 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +data/* +main.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..0ad25db4bd --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..46aa996fd6 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +
+ OneBot-YaYa
+

ZeroBot-Plugin

+ ZeroBot-Plugin 是 ZeroBot 的 实用插件合集

+ +[![YAYA](https://img.shields.io/badge/OneBot-YaYa-green.svg?style=social&logo=appveyor)](https://github.com/Yiwen-Chan/OneBot-YaYa) +[![GOCQ](https://img.shields.io/badge/OneBot-MiraiGo-green.svg?style=social&logo=appveyor)](https://github.com/Mrs4s/go-cqhttp) +[![OICQ](https://img.shields.io/badge/OneBot-OICQ-green.svg?style=social&logo=appveyor)](https://github.com/takayama-lily/node-onebot) +[![MIRAI](https://img.shields.io/badge/OneBot-Mirai-green.svg?style=social&logo=appveyor)](https://github.com/yyuueexxiinngg/onebot-kotlin) + +[![Go Report Card](https://goreportcard.com/badge/github.com/Yiwen-Chan/ZeroBot-Plugin?style=flat-square&logo=go)](https://goreportcard.com/report/github.com/github.com/Yiwen-Chan/ZeroBot-Plugin) +[![Badge](https://img.shields.io/badge/onebot-v11-black?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHAAAABwCAMAAADxPgR5AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAAxQTFRF////29vbr6+vAAAAk1hCcwAAAAR0Uk5T////AEAqqfQAAAKcSURBVHja7NrbctswDATQXfD//zlpO7FlmwAWIOnOtNaTM5JwDMa8E+PNFz7g3waJ24fviyDPgfhz8fHP39cBcBL9KoJbQUxjA2iYqHL3FAnvzhL4GtVNUcoSZe6eSHizBcK5LL7dBr2AUZlev1ARRHCljzRALIEog6H3U6bCIyqIZdAT0eBuJYaGiJaHSjmkYIZd+qSGWAQnIaz2OArVnX6vrItQvbhZJtVGB5qX9wKqCMkb9W7aexfCO/rwQRBzsDIsYx4AOz0nhAtWu7bqkEQBO0Pr+Ftjt5fFCUEbm0Sbgdu8WSgJ5NgH2iu46R/o1UcBXJsFusWF/QUaz3RwJMEgngfaGGdSxJkE/Yg4lOBryBiMwvAhZrVMUUvwqU7F05b5WLaUIN4M4hRocQQRnEedgsn7TZB3UCpRrIJwQfqvGwsg18EnI2uSVNC8t+0QmMXogvbPg/xk+Mnw/6kW/rraUlvqgmFreAA09xW5t0AFlHrQZ3CsgvZm0FbHNKyBmheBKIF2cCA8A600aHPmFtRB1XvMsJAiza7LpPog0UJwccKdzw8rdf8MyN2ePYF896LC5hTzdZqxb6VNXInaupARLDNBWgI8spq4T0Qb5H4vWfPmHo8OyB1ito+AysNNz0oglj1U955sjUN9d41LnrX2D/u7eRwxyOaOpfyevCWbTgDEoilsOnu7zsKhjRCsnD/QzhdkYLBLXjiK4f3UWmcx2M7PO21CKVTH84638NTplt6JIQH0ZwCNuiWAfvuLhdrcOYPVO9eW3A67l7hZtgaY9GZo9AFc6cryjoeFBIWeU+npnk/nLE0OxCHL1eQsc1IciehjpJv5mqCsjeopaH6r15/MrxNnVhu7tmcslay2gO2Z1QfcfX0JMACG41/u0RrI9QAAAABJRU5ErkJggg==)](https://github.com/howmanybots/onebot) +[![Badge](https://img.shields.io/badge/zerobot-v1.0.1-black?style=flat-square&logo=go)](https://github.com/wdvxdr1123/ZeroBot) +[![License](https://img.shields.io/github/license/Yiwen-Chan/OneBot-YaYa.svg?style=flat-square&logo=gnu)](https://raw.githubusercontent.com/Yiwen-Chan/ZeroBot-Plugin/master/LICENSE) +[![qq group](https://img.shields.io/badge/group-1048452984-red?style=flat-square&logo=tencent-qq)](https://jq.qq.com/?_wv=1027&k=QMb7x1mM) + +
+ + +### 功能 +- 群管 + - [x] 禁言[@xxx][分钟] + - [x] 解除禁言[@xxx] + - [x] 我要自闭 [分钟] + - [x] 开启全员禁言 + - [x] 解除全员禁言 + - [x] 升为管理[@xxx] + - [x] 取消管理[@xxx] + - [x] 修改名片[@xxx][xxx] + - [x] 修改头衔[@xxx][xxx] + - [x] 申请头衔[xxx] + - [x] 踢出群聊[@xxx] + - [x] 退出群聊[群号] + - [x] *入群欢迎 + - [x] *退群通知 + - [ ] 同意入群请求 + - [ ] 同意好友请求 + - [ ] 撤回[@xxx] [xxx] + - [ ] 警告[@xxx] + - [x] run[xxx] +- 涩图 + - [x] 来份[涩图/二次元/风景] + - [x] 添加[涩图/二次元/风景][P站图片ID] + - [x] 删除[涩图/二次元/风景][P站图片ID] + - [x] setu -s + - [x] setu -x + - [x] setu -p +- 点歌 + - [x] 点歌[xxx] +- TODO... + +### 使用方法 + +本项目符合 [OneBot](https://github.com/howmanybots/onebot) 标准,可基于以下项目与机器人框架/平台进行交互 +| 项目地址 | 平台 | 核心作者 | 备注 | +| --- | --- | --- | --- | +| [Yiwen-Chan/OneBot-YaYa](https://github.com/Yiwen-Chan/OneBot-YaYa) | [先驱](https://www.xianqubot.com/) | kanri | | +| [richardchien/coolq-http-api](https://github.com/richardchien/coolq-http-api) | CKYU | richardchien | 可在 Mirai 平台使用 [mirai-native](https://github.com/iTXTech/mirai-native) 加载 | +| [Mrs4s/go-cqhttp](https://github.com/Mrs4s/go-cqhttp) | [MiraiGo](https://github.com/Mrs4s/MiraiGo) | Mrs4s | | +| [yyuueexxiinngg/cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai) | [Mirai](https://github.com/mamoe/mirai) | yyuueexxiinngg | | +| [takayama-lily/onebot](https://github.com/takayama-lily/onebot) | [OICQ](https://github.com/takayama-lily/oicq) | takayama | | + +#### 本地编译 +1. 下载安装 [Go](https://studygolang.com/dl/golang/go1.16.2.windows-amd64.msi) 环境 +2. 下载安装 [TDM-GCC](https://github.com/jmeubank/tdm-gcc/releases),并添加到环境变量 +3. [clone](https://github.com/Yiwen-Chan/ZeroBot-Plugin/archive/master.zip) 本项目,本地解压 +4. 编辑 main.go 文件,内容按需修改 +5. 双击点击 build.bat 文件 +6. 运行框架,并同时运行本插件 + +#### 利用 Actions 编译 (推荐) +1. 点击右上角 Fork 本项目,并转跳到自己 Fork 的仓库 +2. 点击仓库上方的 Actions 按钮,确认使用 Actions +3. 编辑 main.go 文件,内容按需修改,返回仓库 +4. 点击 Actions 按钮,等待编译完成,在 Actions 里下载编译好的文件 +5. 运行框架,并同时运行本插件 + diff --git a/app.go b/app.go deleted file mode 100644 index 4e4f8109ae..0000000000 --- a/app.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "fmt" - - m "gm/modules" - "gm/utils" - - zero "github.com/wdvxdr1123/ZeroBot" -) - -var Conf = &utils.YamlConfig{} - -func main() { - zero.Run(zero.Option{ - Host: Conf.Host, - Port: Conf.Port, - AccessToken: Conf.AccessToken, - NickName: []string{"GroupManager"}, - CommandPrefix: "/", - SuperUsers: Conf.Master, - }) - select {} -} - -func init() { - fmt.Println(` -====================[GroupManager]==================== -* Mirai + ZeroBot + Golang -* Copyright © 2018-2020 Kanri, All Rights Reserved -* Project: https://github.com/Yiwen-Chan/GroupManager -======================================================= -`) - Conf = utils.Load("./data/config.yml") - m.Conf = Conf - fmt.Println("[GroupManager] 有需要请按 GitHub 项目上描述的方法修改配置文件") -} diff --git a/build.bat b/build.bat new file mode 100644 index 0000000000..4ea29e5924 --- /dev/null +++ b/build.bat @@ -0,0 +1,6 @@ +go version +gcc -v +go env -w GOPROXY=https://goproxy.cn,direct +go env -w GO111MODULE=auto +go build -ldflags="-s -w" -o ZeroBot-Plugin-windows.exe +pause diff --git a/chat/chat.go b/chat/chat.go new file mode 100644 index 0000000000..b9820c6ee1 --- /dev/null +++ b/chat/chat.go @@ -0,0 +1,17 @@ +package chat + +import ( + "time" + + zero "github.com/wdvxdr1123/ZeroBot" +) + +func init() { // 插件主体 + // 戳一戳 + zero.On("notice/notify/poke", zero.OnlyToMe).SetBlock(false).SetPriority(0). + Handle(func(ctx *zero.Ctx) { + time.Sleep(time.Second * 1) + ctx.Send("请不要戳我 >_<") + return + }) +} diff --git a/chat/learn.go b/chat/learn.go new file mode 100644 index 0000000000..ce7919f682 --- /dev/null +++ b/chat/learn.go @@ -0,0 +1,56 @@ +package chat + +import ( + zero "github.com/wdvxdr1123/ZeroBot" + yaml "gopkg.in/yaml.v2" + "io/ioutil" + "math/rand" + "os" + "strings" + "time" +) + +func init() { + botName := "小沫" //填写机器人的名称 + myData := map[string][]string{} + yaml.Unmarshal(FileRead("chat\\myData.yaml"),&myData) + + zero.OnRegex("^(.+?)$").Handle(func(ctx *zero.Ctx) { + + text := ctx.State["regex_matched"].([]string)[1] + if strings.Index(text,botName+"跟我学")==0{ + kv := strings.Split(text[len(botName+"跟我学 "):len(text)]," ") + if len(kv)>1 && kv[1]!=""{ + myData[kv[0]] = kv[1:len(kv)] + content,_ := yaml.Marshal(myData) + FileWrite("chat\\myData.yaml",content) + ctx.Send(botName + "学会了奇怪的新知识["+kv[0]+"]:\n- "+strings.Join(kv[1:len(kv)],"\n- ")) + }else { + ctx.Send("你想让"+botName+"学些什么呀?") + } + return + } + + for k,vs := range myData{ + if strings.Index(text,k) != -1{ + rand.Seed(time.Now().Unix()) + ctx.Send(vs[rand.Intn(len(vs))]) + return + } + } + }) +} + +func FileRead(path string) []byte { + //读取文件数据 + file,_ := os.Open(path) + defer file.Close() + data,_ := ioutil.ReadAll(file) + return data +} + +func FileWrite(path string,content []byte) int { + //写入文件数据 + ioutil.WriteFile(path,content,0644) + return len(content) +} diff --git a/github/repo_searcher.go b/github/repo_searcher.go new file mode 100644 index 0000000000..d67f5d7fe6 --- /dev/null +++ b/github/repo_searcher.go @@ -0,0 +1,78 @@ +package github + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" +) + +func init() { // 插件主体 + zero.OnRegex(`>G\s(.*)`).SetBlock(true).SetPriority(0). + Handle(func(ctx *zero.Ctx) { + api, _ := url.Parse("https://api.github.com/search/repositories") + params := url.Values{} + params.Set("q", ctx.State["regex_matched"].([]string)[1]) + api.RawQuery = params.Encode() + link := api.String() + + client := &http.Client{} + + req, err := http.NewRequest("GET", link, nil) + if err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36") + resp, err := client.Do(req) + if err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + + if code := resp.StatusCode; code != 200 { + // 如果返回不是200则立刻抛出错误 + ctx.Send(fmt.Sprintf("ERROR: code %d", code)) + return + } + count := gjson.ParseBytes(body).Get("total_count").Int() + if count == 0 { + ctx.Send("没有找到这样的仓库") + return + } + repo := gjson.ParseBytes(body).Get("items.0") + language := repo.Get("language").Str + if language == "" { + language = "None" + } + license := strings.ToUpper(repo.Get("license.key").Str) + if license == "" { + license = "None" + } + id := ctx.Send(fmt.Sprintf( + "%s: \nDescription: %s\nStar/Fork/Issue: %d/%d/%d\nLanguage: %s\nLicense: %s\nLast pushed: %s\nJump: %s", + repo.Get("full_name").Str, + repo.Get("description").Str, + repo.Get("watchers").Int(), + repo.Get("forks").Int(), + repo.Get("open_issues").Int(), + language, + license, + repo.Get("updated_at").Str, + repo.Get("html_url").Str, + )) + if id == 0 { + ctx.Send("ERROR: 可能被风控,发送失败") + } + }) +} diff --git a/go.mod b/go.mod index 8131e6c931..6b9fd7be9c 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,12 @@ -module gm +module github.com/Yiwen-Chan/ZeroBot-Plugin -go 1.14 +go 1.15 require ( - github.com/wdvxdr1123/ZeroBot v0.0.0-20201129064504-07d9e1b5400f - gopkg.in/yaml.v2 v2.4.0 - gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 + github.com/antchfx/htmlquery v1.2.3 + github.com/mattn/go-sqlite3 v1.14.6 + github.com/sirupsen/logrus v1.8.0 + github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 + github.com/tidwall/gjson v1.7.4 + github.com/wdvxdr1123/ZeroBot v1.0.3-0.20210407015205-7cb1b23c5417 ) diff --git a/go.sum b/go.sum index f7cef80532..5e3cf825f5 100644 --- a/go.sum +++ b/go.sum @@ -1,35 +1,76 @@ +github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M= +github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= +github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0= +github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls= +github.com/magefile/mage v1.11.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2-0.20210109003243-333559e1834b h1:6Xjqolv/0DDdUqlpnsTomXQvjvvkz7Ux7TcMALvozEw= +github.com/modern-go/reflect2 v1.0.2-0.20210109003243-333559e1834b/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU= +github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816 h1:J6v8awz+me+xeb/cUTotKgceAYouhIB3pjzgRd6IlGk= github.com/t-tomalak/logrus-easy-formatter v0.0.0-20190827215021-c074f06c5816/go.mod h1:tzym/CEb5jnFI+Q0k4Qq3+LvRF4gO3E2pxS8fHP8jcA= -github.com/tidwall/gjson v1.6.1 h1:LRbvNuNuvAiISWg6gxLEFuCe72UKy5hDqhxW/8183ws= -github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= -github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= -github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= -github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI= +github.com/tidwall/gjson v1.7.4 h1:19cchw8FOxkG5mdLRkGf9jqIqEyqdZhPqW60XfyFxk8= +github.com/tidwall/gjson v1.7.4/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= +github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= +github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/wdvxdr1123/ZeroBot v0.0.0-20201129064504-07d9e1b5400f h1:06qEGANaKnXdJShXGEbD70F77C7AQAXA0nkQVdyORHc= -github.com/wdvxdr1123/ZeroBot v0.0.0-20201129064504-07d9e1b5400f/go.mod h1:e92qGONnOry8XSfaUUlG7OhT9gyD839P8oZ2wfPe60w= +github.com/tidwall/pretty v1.0.5/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/wdvxdr1123/ZeroBot v1.0.3-0.20210407015205-7cb1b23c5417 h1:L0yqzJ6c6Uh8ZStkMjO5G30SY5ZjD5t2d6KvXWDT2h4= +github.com/wdvxdr1123/ZeroBot v1.0.3-0.20210407015205-7cb1b23c5417/go.mod h1:BZQGqjw0PX8Aojj3QnVHTXIH9YOEc8ZvLLUosM39Gho= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM= -golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= \ No newline at end of file +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43 h1:SgQ6LNaYJU0JIuEHv9+s6EbhSCwYeAf5Yvj6lpYlqAE= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000000..3fca8b5148 --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "fmt" + + log "github.com/sirupsen/logrus" + easy "github.com/t-tomalak/logrus-easy-formatter" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/driver" + + _ "github.com/Yiwen-Chan/ZeroBot-Plugin/chat" + _ "github.com/Yiwen-Chan/ZeroBot-Plugin/github" + _ "github.com/Yiwen-Chan/ZeroBot-Plugin/manager" + _ "github.com/Yiwen-Chan/ZeroBot-Plugin/music" + setutime "github.com/Yiwen-Chan/ZeroBot-Plugin/setutime" +) + +func init() { + log.SetFormatter(&easy.Formatter{ + TimestampFormat: "2006-01-02 15:04:05", + LogFormat: "[zero][%time%][%lvl%]: %msg% \n", + }) + log.SetLevel(log.DebugLevel) + + setutime.CACHEGROUP = 868047498 // 涩图缓冲群,必须修改 + setutime.PoolList = []string{"涩图", "二次元", "风景", "车万"} // 涩图类型,可以不修改 +} + +func main() { + fmt.Printf(` +====================[ZeroBot-Plugin]==================== +* OneBot + ZeroBot + Golang +* Copyright © 2018-2020 Kanri, All Rights Reserved +* Project: https://github.com/Yiwen-Chan/ZeroBot-Plugin +======================================================== +`) + zero.Run(zero.Config{ + NickName: []string{"bot"}, + CommandPrefix: "/", + SuperUsers: []string{"825111790", "213864964"}, // 必须修改,否则无权限 + Driver: []zero.Driver{ + driver.NewWebSocketClient("127.0.0.1", "6700", ""), + }, + }) + select {} +} diff --git a/manager/manager.go b/manager/manager.go new file mode 100644 index 0000000000..7c6bda4641 --- /dev/null +++ b/manager/manager.go @@ -0,0 +1,254 @@ +package manager + +import ( + "strings" + + "github.com/Yiwen-Chan/ZeroBot-Plugin/manager/utils" + zero "github.com/wdvxdr1123/ZeroBot" +) + +func init() { // 插件主体 + // 菜单 + zero.OnFullMatch("群管系统").SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.Send(`====群管==== +- 禁言@QQ 1 +- 解除禁言 @QQ +- 我要自闭 1 +- 开启全员禁言 +- 解除全员禁言 +- 升为管理@QQ +- 取消管理@QQ +- 修改名片@QQ XXX +- 修改头衔@QQ XXX +- 申请头衔 XXX +- 踢出群聊@QQ +- 退出群聊 1234 +- 群聊转发 1234 XXX +- 私聊转发 0000 XXX`) + return + }) + // 升为管理 + zero.OnRegex(`^升为管理.*?(\d+)`, zero.OnlyGroup, zero.SuperUserPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupAdmin( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被升为管理的人的qq + true, + ) + nickname := ctx.GetGroupMemberInfo( // 被升为管理的人的昵称 + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被升为管理的人的qq + false, + ).Get("nickname").Str + ctx.Send(nickname + " 升为了管理~") + return + }) + // 取消管理 + zero.OnRegex(`^取消管理.*?(\d+)`, zero.OnlyGroup, zero.SuperUserPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupAdmin( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被取消管理的人的qq + false, + ) + nickname := ctx.GetGroupMemberInfo( // 被取消管理的人的昵称 + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被取消管理的人的qq + false, + ).Get("nickname").Str + ctx.Send("残念~ " + nickname + " 暂时失去了管理员的资格") + return + }) + // 踢出群聊 + zero.OnRegex(`^踢出群聊.*?(\d+)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupKick( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被踢出群聊的人的qq + false, + ) + nickname := ctx.GetGroupMemberInfo( // 被踢出群聊的人的昵称 + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被踢出群聊的人的qq + false, + ).Get("nickname").Str + ctx.Send("残念~ " + nickname + " 被放逐") + return + }) + // 退出群聊 + zero.OnRegex(`^退出群聊.*?(\d+)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupLeave( + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 要退出的群的群号 + true, + ) + return + }) + // 开启全体禁言 + zero.OnRegex(`^开启全员禁言$`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupWholeBan( + ctx.Event.GroupID, + true, + ) + ctx.Send("全员自闭开始~") + return + }) + // 解除全员禁言 + zero.OnRegex(`^解除全员禁言$`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupWholeBan( + ctx.Event.GroupID, + false, + ) + ctx.Send("全员自闭结束~") + return + }) + // 禁言 + zero.OnRegex(`^禁言.*?(\d+).*?\s(\d+)(.*)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + duration := utils.Str2Int(ctx.State["regex_matched"].([]string)[2]) + switch ctx.State["regex_matched"].([]string)[3] { + case "分钟": + // + case "小时": + duration = duration * 60 + case "天": + duration = duration * 60 * 24 + default: + // + } + if duration >= 43200 { + duration = 43199 // qq禁言最大时长为一个月 + } + ctx.SetGroupBan( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 要禁言的人的qq + duration*60, // 要禁言的时间(分钟) + ) + ctx.Send("小黑屋收留成功~") + return + }) + // 解除禁言 + zero.OnRegex(`^解除禁言.*?(\d+)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupBan( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 要解除禁言的人的qq + 0, + ) + ctx.Send("小黑屋释放成功~") + return + }) + // 自闭禁言 + zero.OnRegex(`^我要自闭.*?(\d+)(.*)`, zero.OnlyGroup).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + duration := utils.Str2Int(ctx.State["regex_matched"].([]string)[1]) + switch ctx.State["regex_matched"].([]string)[2] { + case "分钟": + // + case "小时": + duration = duration * 60 + case "天": + duration = duration * 60 * 24 + default: + // + } + if duration >= 43200 { + duration = 43199 // qq禁言最大时长为一个月 + } + ctx.SetGroupBan( + ctx.Event.GroupID, + ctx.Event.UserID, + duration*60, // 要自闭的时间(分钟) + ) + ctx.Send("那我就不手下留情了~") + return + }) + // 修改名片 + zero.OnRegex(`^修改名片.*?(\d+).*?\s(.*)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupCard( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被修改群名片的人 + ctx.State["regex_matched"].([]string)[2], // 修改成的群名片 + ) + ctx.Send("嗯!已经修改了") + return + }) + // 修改头衔 + zero.OnRegex(`^修改头衔.*?(\d+).*?\s(.*)`, zero.OnlyGroup, zero.AdminPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupSpecialTitle( + ctx.Event.GroupID, + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 被修改群头衔的人 + ctx.State["regex_matched"].([]string)[2], // 修改成的群头衔 + ) + ctx.Send("嗯!已经修改了") + return + }) + // 申请头衔 + zero.OnRegex(`^申请头衔(.*)`, zero.OnlyGroup).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + ctx.SetGroupSpecialTitle( + ctx.Event.GroupID, + ctx.Event.UserID, // 被修改群头衔的人 + ctx.State["regex_matched"].([]string)[1], // 修改成的群头衔 + ) + ctx.Send("嗯!不错的头衔呢~") + return + }) + // 群聊转发 + zero.OnRegex(`^群聊转发.*?(\d+)\s(.*)`, zero.SuperUserPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + // 对CQ码进行反转义 + content := ctx.State["regex_matched"].([]string)[2] + content = strings.ReplaceAll(content, "[", "[") + content = strings.ReplaceAll(content, "]", "]") + ctx.SendGroupMessage( + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 需要发送的群 + content, // 需要发送的信息 + ) + ctx.Send("📧 --> " + ctx.State["regex_matched"].([]string)[1]) + return + }) + // 私聊转发 + zero.OnRegex(`^私聊转发.*?(\d+)\s(.*)`, zero.SuperUserPermission).SetBlock(true).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + // 对CQ码进行反转义 + content := ctx.State["regex_matched"].([]string)[2] + content = strings.ReplaceAll(content, "[", "[") + content = strings.ReplaceAll(content, "]", "]") + ctx.SendPrivateMessage( + utils.Str2Int(ctx.State["regex_matched"].([]string)[1]), // 需要发送的人的qq + content, // 需要发送的信息 + ) + ctx.Send("📧 --> " + ctx.State["regex_matched"].([]string)[1]) + return + }) + // 入群欢迎 + zero.OnNotice().SetBlock(false).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + if ctx.Event.NoticeType == "group_increase" { + ctx.Send("欢迎~") + } + return + }) + // 退群提醒 + zero.OnNotice().SetBlock(false).SetPriority(40). + Handle(func(ctx *zero.Ctx) { + if ctx.Event.NoticeType == "group_decrease" { + ctx.Send("有人跑路了~") + } + return + }) + // 运行 CQ 码 + zero.OnRegex(`^run(.*)$`, zero.SuperUserPermission).SetBlock(true).SetPriority(0). + Handle(func(ctx *zero.Ctx) { + var cmd = ctx.State["regex_matched"].([]string)[1] + cmd = strings.ReplaceAll(cmd, "[", "[") + cmd = strings.ReplaceAll(cmd, "]", "]") + ctx.Send(cmd) + }) +} diff --git a/manager/utils/utils.go b/manager/utils/utils.go new file mode 100644 index 0000000000..b55ffa7bc7 --- /dev/null +++ b/manager/utils/utils.go @@ -0,0 +1,15 @@ +package utils + +import ( + "strconv" +) + +func Int2Str(val int64) string { + str := strconv.FormatInt(val, 10) + return str +} + +func Str2Int(str string) int64 { + val, _ := strconv.ParseInt(str, 10, 64) + return val +} diff --git a/modules/main.go b/modules/main.go deleted file mode 100644 index 6491dc03eb..0000000000 --- a/modules/main.go +++ /dev/null @@ -1,5 +0,0 @@ -package modules - -import "gm/utils" - -var Conf = &utils.YamlConfig{} diff --git a/modules/manage.go b/modules/manage.go deleted file mode 100644 index 6532368e5e..0000000000 --- a/modules/manage.go +++ /dev/null @@ -1,67 +0,0 @@ -package modules - -import ( - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(manage{}) -} - -type manage struct{} - -func (manage) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "manage", - Version: "0.0.1", - Details: "设置群名片群头衔", - } -} - -func (manage) Start() { // 插件主体 - promoteManager := zero.OnRegex("^升为管理.*?(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupAdmin(event.GroupID, GetInt(state, 1), true) - zero.Send(event, "恭喜~") - return zero.SuccessResponse - }, - ) - promoteManager.Priority = 20 - promoteManager.Block = true - - cancleManager := zero.OnRegex("^取消管理.*?(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupAdmin(event.GroupID, GetInt(state, 1), false) - zero.Send(event, "残念~") - return zero.SuccessResponse - }, - ) - cancleManager.Priority = 21 - cancleManager.Block = true - - kick := zero.OnRegex("^踢出群聊.*?(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupKick(event.GroupID, GetInt(state, 1), false) - zero.Send(event, "残念~") - return zero.SuccessResponse - }, - ) - kick.Priority = 22 - kick.Block = true - - leave := zero.OnRegex("^退出群聊.*?(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupLeave(event.GroupID, true) - zero.Send(event, "明白了!") - return zero.SuccessResponse - }, - ) - leave.Priority = 23 - leave.Block = true - -} diff --git a/modules/mute.go b/modules/mute.go deleted file mode 100644 index f265636eb3..0000000000 --- a/modules/mute.go +++ /dev/null @@ -1,77 +0,0 @@ -package modules - -import ( - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(mute{}) -} - -type mute struct{} - -func (mute) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "mute", - Version: "0.0.1", - Details: "禁言", - } -} - -func (mute) Start() { // 插件主体 - unWholeBan := zero.OnRegex("^解除全体禁言$", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupWholeBan(event.GroupID, false) - zero.Send(event, "小黑屋收留成功~") - return zero.SuccessResponse - }, - ) - unWholeBan.Priority = 1 - unWholeBan.Block = true - - wholeBan := zero.OnRegex("^开启全体禁言$", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupWholeBan(event.GroupID, true) - zero.Send(event, "小黑屋收留成功~") - return zero.SuccessResponse - }, - ) - wholeBan.Priority = 2 - wholeBan.Block = true - - unban := zero.OnRegex("^解除禁言.*?(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupBan(event.GroupID, GetInt(state, 1), 0) - zero.Send(event, "小黑屋释放成功~") - return zero.SuccessResponse - }, - ) - unban.Priority = 3 - unban.Block = true - - ban := zero.OnRegex("^禁言.*?(\\d+).*?\\s(\\d+)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupBan(event.GroupID, GetInt(state, 1), GetInt(state, 2)*1000) - zero.Send(event, "小黑屋收留成功~") - return zero.SuccessResponse - }, - ) - ban.Priority = 4 - ban.Block = true - - selfBan := zero.OnRegex("^我要自闭.*?(\\d+)分钟", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupBan(event.GroupID, event.UserID, GetInt(state, 1)*1000) - zero.Send(event, "那我就不手下留情了") - return zero.SuccessResponse - }, - ) - selfBan.Priority = 5 - selfBan.Block = true -} diff --git a/modules/notice.go b/modules/notice.go deleted file mode 100644 index 52d48e20c7..0000000000 --- a/modules/notice.go +++ /dev/null @@ -1,61 +0,0 @@ -package modules - -import ( - "time" - - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(notice{}) -} - -type notice struct{} - -func (notice) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "notice", - Version: "0.0.1", - Details: "设置群名片群头衔", - } -} - -func (notice) Start() { // 插件主体 - notify := zero.OnNotice(). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - if event.NoticeType == "notify" { - time.Sleep(time.Second * 1) - zero.Send(event, "请不要戳我 >_<") - } - return zero.SuccessResponse - }, - ) - notify.Priority = 40 - notify.Block = true - - increase := zero.OnNotice(). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - if event.NoticeType == "group_increase" { - zero.Send(event, "欢迎~") - } - return zero.SuccessResponse - }, - ) - increase.Priority = 41 - increase.Block = true - - decrease := zero.OnNotice(). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - if event.NoticeType == "group_decrease" { - zero.Send(event, "有人跑路了") - } - return zero.SuccessResponse - }, - ) - decrease.Priority = 42 - decrease.Block = true -} diff --git a/modules/request.go b/modules/request.go deleted file mode 100644 index a4b17af9c3..0000000000 --- a/modules/request.go +++ /dev/null @@ -1,52 +0,0 @@ -package modules - -import ( - "gm/utils" - - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(request{}) -} - -type request struct{} - -func (request) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "request", - Version: "0.0.1", - Details: "设置群名片群头衔", - } -} - -func (request) Start() { // 插件主体 - friendAdd := zero.OnNotice(). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - if event.RequestType == "friend" { - zero.SendPrivateMessage(utils.Str2Int(Conf.Master[0]), "有人想加我") - } - return zero.SuccessResponse - }, - ) - friendAdd.Priority = 50 - friendAdd.Block = true - - groupAdd := zero.OnNotice(). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - if event.RequestType == "friend" { - if event.SubType == "add" { - zero.Send(event, "有人申请加群") - } else { - zero.SendPrivateMessage(utils.Str2Int(Conf.Master[0]), "有人想拉我入群") - } - } - return zero.SuccessResponse - }, - ) - groupAdd.Priority = 51 - groupAdd.Block = true -} diff --git a/modules/sender.go b/modules/sender.go deleted file mode 100644 index 9268e7fb95..0000000000 --- a/modules/sender.go +++ /dev/null @@ -1,44 +0,0 @@ -package modules - -import ( - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(sender{}) -} - -type sender struct{} - -func (sender) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "sender", - Version: "0.0.1", - Details: "设置群名片群头衔", - } -} - -func (sender) Start() { // 插件主体 - promoteManager := zero.OnRegex("^群聊转发.*?(\\d+)\\s(.*)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SendGroupMessage(GetInt(state, 1), GetStr(state, 2)) - zero.Send(event, "complete!") - return zero.SuccessResponse - }, - ) - promoteManager.Priority = 30 - promoteManager.Block = true - - cancleManager := zero.OnRegex("^私聊转发.*?(\\d+)\\s(.*)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SendPrivateMessage(GetInt(state, 1), GetStr(state, 2)) - zero.Send(event, "complete!") - return zero.SuccessResponse - }, - ) - cancleManager.Priority = 31 - cancleManager.Block = true -} diff --git a/modules/title.go b/modules/title.go deleted file mode 100644 index 1c7eed709f..0000000000 --- a/modules/title.go +++ /dev/null @@ -1,55 +0,0 @@ -package modules - -import ( - zero "github.com/wdvxdr1123/ZeroBot" -) - -func init() { - zero.RegisterPlugin(title{}) -} - -type title struct{} - -func (title) GetPluginInfo() zero.PluginInfo { // 返回插件信息 - return zero.PluginInfo{ - Author: "kanri", - PluginName: "title", - Version: "0.0.1", - Details: "设置群名片群头衔", - } -} - -func (title) Start() { // 插件主体 - setCard := zero.OnRegex("^修改名片.*?(\\d+).*?\\s(.*)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupCard(event.GroupID, GetInt(state, 1), GetStr(state, 2)) - zero.Send(event, "嗯!已经修改了") - return zero.SuccessResponse - }, - ) - setCard.Priority = 10 - setCard.Block = true - - setTitle := zero.OnRegex("^修改头衔.*?(\\d+).*?\\s(.*)", zero.OnlyGroup, zero.AdminPermission). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupSpecialTitle(event.GroupID, GetInt(state, 1), GetStr(state, 2)) - zero.Send(event, "嗯!已经修改了") - return zero.SuccessResponse - }, - ) - setTitle.Priority = 11 - setTitle.Block = true - - setSelfTitle := zero.OnRegex("^申请头衔(.*)"). - Handle( - func(matcher *zero.Matcher, event zero.Event, state zero.State) zero.Response { - zero.SetGroupSpecialTitle(event.GroupID, event.UserID, GetStr(state, 2)) - zero.Send(event, "嗯!不错的头衔呢~") - return zero.SuccessResponse - }, - ) - setSelfTitle.Priority = 12 - setSelfTitle.Block = true -} diff --git a/modules/utils.go b/modules/utils.go deleted file mode 100644 index 57530c3cf8..0000000000 --- a/modules/utils.go +++ /dev/null @@ -1,17 +0,0 @@ -package modules - -import ( - "fmt" - "gm/utils" - - zero "github.com/wdvxdr1123/ZeroBot" -) - -func GetInt(state zero.State, index int64) int64 { - fmt.Println(state["regex_matched"].([]string)) - return utils.Str2Int(state["regex_matched"].([]string)[index]) -} - -func GetStr(state zero.State, index int64) string { - return state["regex_matched"].([]string)[index] -} diff --git a/music/music_selecter.go b/music/music_selecter.go new file mode 100644 index 0000000000..d6d95ba246 --- /dev/null +++ b/music/music_selecter.go @@ -0,0 +1,170 @@ +package music + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "github.com/tidwall/gjson" + zero "github.com/wdvxdr1123/ZeroBot" + "io/ioutil" + "net/http" + "net/url" + "strings" + "time" +) + +func init() { + zero.OnRegex("^酷我点歌(.+?)$").SetBlock(true).SetPriority(50).Handle(func(ctx *zero.Ctx) { + ctx.Send(KuWo(ctx.State["regex_matched"].([]string)[1])) + return + }) + + zero.OnRegex("^酷狗点歌(.+?)$").SetBlock(true).SetPriority(50).Handle(func(ctx *zero.Ctx) { + ctx.Send(KuGou(ctx.State["regex_matched"].([]string)[1])) + return + }) + + zero.OnRegex("^网易点歌(.+?)$").SetBlock(true).SetPriority(50).Handle(func(ctx *zero.Ctx) { + ctx.Send(WyCloud(ctx.State["regex_matched"].([]string)[1])) + return + }) + + zero.OnRegex("^点歌(.+?)$").SetBlock(true).SetPriority(50).Handle(func(ctx *zero.Ctx) { + ctx.Send(QQMusic(ctx.State["regex_matched"].([]string)[1])) + return + }) +} + +//----------------------------------------------------------------------- + +func KuWo(KeyWord string) string { + headers := map[string]string{ + "Cookie": "Hm_lvt_cdb524f42f0ce19b169a8071123a4797=1610284708,1610699237; _ga=GA1.2.1289529848.1591618534; kw_token=LWKACV45JSQ; Hm_lpvt_cdb524f42f0ce19b169a8071123a4797=1610699468; _gid=GA1.2.1868980507.1610699238; _gat=1", + "csrf": "LWKACV45JSQ", + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + "Referer": "https://www.kuwo.cn/search/list?key=", + } + api := "https://www.kuwo.cn/api/www/search/searchMusicBykeyWord?key="+url.QueryEscape(KeyWord)+"&pn=1&rn=1&httpsStatus=1" + info := gjson.ParseBytes(NetGet(api,headers)).Get("data.list.0") + return fmt.Sprintf("[CQ:music,type=custom,url=%s,audio=%s,title=%s,content=%s,image=%s]", + fmt.Sprintf("https://www.kuwo.cn/play_detail/%d",info.Get("rid").Int()), + gjson.ParseBytes( + NetGet(fmt.Sprintf( + "http://www.kuwo.cn/url?format=mp3&rid=%d&response=url&type=convert_url3&br=128kmp3&from=web&httpsStatus=1", info.Get("rid").Int()),headers)).Get("url").Str, + info.Get("name").Str, + info.Get("artist").Str, + info.Get("pic").Str, + ) +} + +func KuGou(KeyWord string) string { + stamp := time.Now().UnixNano()/1e6 + api := fmt.Sprintf( + "https://complexsearch.kugou.com/v2/search/song?callback=callback123&keyword=%s&page=1&pagesize=30&bitrate=0&isfuzzy=0&tag=em&inputtype=0&platform=WebFilter&userid=-1&clientver=2000&iscorrection=1&privilege_filter=0&srcappid=2919&clienttime=%d&mid=%d&uuid=%d&dfid=-&signature=%s", + KeyWord,stamp,stamp,stamp,GetMd5(fmt.Sprintf( + "NVPh5oo715z5DIWAeQlhMDsWXXQV4hwtbitrate=0callback=callback123clienttime=%dclientver=2000dfid=-inputtype=0iscorrection=1isfuzzy=0keyword=%smid=%dpage=1pagesize=30platform=WebFilterprivilege_filter=0srcappid=2919tag=emuserid=-1uuid=%dNVPh5oo715z5DIWAeQlhMDsWXXQV4hwt", + stamp,KeyWord,stamp,stamp)), + ) + res := NetGet(api, map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"}) + info := gjson.ParseBytes(res[12:len(res)-2]).Get("data.lists.0") + res = NetGet( + fmt.Sprintf("https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=%s&album_id=%s",info.Get("FileHash").Str,info.Get("AlbumID").Str), + map[string]string{ + "Cookie": "kg_mid=d8e70a262c93d47599c6196c612d6f4f; Hm_lvt_aedee6983d4cfc62f509129360d6bb3d=1610278505,1611631363,1611722252; kg_dfid=33ZWee1kircl0jcJ1h0WF1fX; Hm_lpvt_aedee6983d4cfc62f509129360d6bb3d=1611727348; kg_dfid_collect=d41d8cd98f00b204e9800998ecf8427e", + "Host": "wwwapi.kugou.com", + "TE": "Trailers", + "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0", + }) + jump_url := fmt.Sprintf("https://www.kugou.com/song/#hash=%s&album_id=%s",info.Get("FileHash").Str,info.Get("AlbumID").Str) + info = gjson.ParseBytes(res).Get("data") + return fmt.Sprintf("[CQ:music,type=custom,url=%s,audio=%s,title=%s,content=%s,image=%s]", + jump_url, + strings.Replace(info.Get("play_backup_url").Str,"\\/","/",-1), + info.Get("song_name").Str, + info.Get("author_name").Str, + info.Get("img").Str, + ) +} + +func WyCloud(KeyWord string) string { + res := NetPost( + "http://music.163.com/api/search/pc", + map[string]string{"offset": "0", "total": "true", "limit": "9", "type": "1", "s": KeyWord}, + map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36", + }, + ) + info := gjson.ParseBytes(res).Get("result.songs.0") + return fmt.Sprintf( + "[CQ:music,type=custom,url=%s,audio=%s,title=%s,content=%s,image=%s]", + fmt.Sprintf("http://y.music.163.com/m/song?id=%d", info.Get("id").Int()), + fmt.Sprintf("http://music.163.com/song/media/outer/url?id=%d.mp3", info.Get("id").Int()), + info.Get("name").Str, + info.Get("artists.0.name").Str, + info.Get("album.blurPicUrl").Str, + ) +} + +func QQMusic(KeyWord string) string { + params := `https://u.y.qq.com/cgi-bin/musicu.fcg?data=%7B%22req%22%3A+%7B%22module%22%3A+%22CDN.SrfCdnDispatchServer%22%2C+%22method%22%3A+%22GetCdnDispatch%22%2C+%22param%22%3A+%7B%22guid%22%3A+%223982823384%22%2C+%22calltype%22%3A+0%2C+%22userip%22%3A+%22%22%7D%7D%2C+%22req_0%22%3A+%7B%22module%22%3A+%22vkey.GetVkeyServer%22%2C+%22method%22%3A+%22CgiGetVkey%22%2C+%22param%22%3A+%7B%22guid%22%3A+%223982823384%22%2C+%22songmid%22%3A+%5B%22{}%22%5D%2C+%22songtype%22%3A+%5B0%5D%2C+%22uin%22%3A+%220%22%2C+%22loginflag%22%3A+1%2C+%22platform%22%3A+%2220%22%7D%7D%2C+%22comm%22%3A+%7B%22uin%22%3A+0%2C+%22format%22%3A+%22json%22%2C+%22ct%22%3A+24%2C+%22cv%22%3A+0%7D%7D` + res := NetGet( + "https://c.y.qq.com/soso/fcgi-bin/client_search_cp?w="+KeyWord, + map[string]string{"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0"}, + ) + info := gjson.ParseBytes(res[9:len(res)-1]).Get("data.song.list.0") + return fmt.Sprintf( + "[CQ:music,type=custom,url=%s,audio=%s,title=%s,content=%s,image=%s]", + "https://y.qq.com/n/yqq/song/"+info.Get("songmid").Str+".html", + "https://isure.stream.qqmusic.qq.com/" + gjson.ParseBytes( + NetGet( + strings.Replace(params,"{}",info.Get("songmid").Str,-1), + map[string]string{ + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1", + "referer": "http://y.qq.com", + })).Get("req_0.data.midurlinfo.0.purl").Str, + info.Get("songname").Str, + info.Get("singer.0.name").Str, + "https://y.gtimg.cn/music/photo_new"+StrMidGet("//y.gtimg.cn/music/photo_new","?max_age",string(NetGet("https://y.qq.com/n/yqq/song/"+info.Get("songmid").Str+".html", map[string]string{}))), + ) +} + +//----------------------------------------------------------------------- + +func StrMidGet(pre string,suf string,str string) string { + n := strings.Index(str, pre) + if n == -1 {n = 0} else {n = n + len(pre)} + str = string([]byte(str)[n:]) + m := strings.Index(str, suf) + if m == -1 {m = len(str)} + return string([]byte(str)[:m]) +} + +func GetMd5(s string) string { + h := md5.New() + h.Write([]byte(s)) + result := strings.ToUpper(hex.EncodeToString(h.Sum(nil))) + return result +} + +func NetGet(get_url string,headers map[string]string) []byte { + client := &http.Client{} + request,_ := http.NewRequest("GET",get_url,nil) + for key,value := range headers{request.Header.Add(key,value)} + res,_ := client.Do(request) + defer res.Body.Close() + result,_ := ioutil.ReadAll(res.Body) + return result +} + +func NetPost(post_url string,data map[string]string,headers map[string]string) []byte { + client := &http.Client{} + param := url.Values{} + for key,value := range data{param.Set(key,value)} + request,_ := http.NewRequest("POST",post_url,strings.NewReader(param.Encode())) + for key,value := range headers{request.Header.Add(key,value)} + res,_ := client.Do(request) + defer res.Body.Close() + result,_ := ioutil.ReadAll(res.Body) + return result +} diff --git a/setutime/pic_searcher.go b/setutime/pic_searcher.go new file mode 100644 index 0000000000..259aca44c1 --- /dev/null +++ b/setutime/pic_searcher.go @@ -0,0 +1,138 @@ +package setutime + +import ( + "fmt" + + utils "github.com/Yiwen-Chan/ZeroBot-Plugin/setutime/utils" + zero "github.com/wdvxdr1123/ZeroBot" +) + +func init() { // 插件主体 + // 根据PID搜图 + zero.OnRegex(`^搜图(\d+)$`).SetBlock(true).SetPriority(30). + Handle(func(ctx *zero.Ctx) { + id := utils.Str2Int(ctx.State["regex_matched"].([]string)[1]) + ctx.Send("少女祈祷中......") + // 获取P站插图信息 + illust := &utils.Illust{} + if err := illust.IllustInfo(id); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + // 下载P站插图 + if _, err := illust.PixivPicDown(CACHEPATH); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + // 发送搜索结果 + ctx.Send(illust.DetailPic) + return + }) + // 通过回复以图搜图 + zero.OnRegex(`\[CQ:reply,id=(.*?)\](.*)搜索图片`).SetBlock(true).SetPriority(32). + Handle(func(ctx *zero.Ctx) { + var pics []string // 图片搜索池子 + // 获取回复的上文图片链接 + id := utils.Str2Int(ctx.State["regex_matched"].([]string)[1]) + for _, elem := range ctx.GetMessage(id).Elements { + if elem.Type == "image" { + pics = append(pics, elem.Data["url"]) + } + } + // 没有收到图片则向用户索取 + if len(pics) == 0 { + ctx.Send("请发送多张图片!") + next := ctx.FutureEvent("message", ctx.CheckSession()) + recv, cancel := next.Repeat() + for e := range recv { // 循环获取channel发来的信息 + if len(e.Message) == 1 && e.Message[0].Type == "text" { + cancel() // 如果是纯文本则退出索取 + break + } + for _, elem := range e.Message { + if elem.Type == "image" { // 将信息中的图片添加到搜索池子 + pics = append(pics, elem.Data["url"]) + } + } + if len(pics) >= 5 { + cancel() // 如果是图片数量大于等于5则退出索取 + break + } + } + } + if len(pics) == 0 { + ctx.Send("没有收到图片,搜图结束......") + return + } + // 开始搜索图片 + ctx.Send("少女祈祷中......") + for _, pic := range pics { + if text, err := utils.SauceNaoSearch(pic); err == nil { + ctx.Send(text) // 返回SauceNAO的结果 + continue + } else { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + } + if text, err := utils.Ascii2dSearch(pic); err == nil { + ctx.Send(text) // 返回Ascii2d的结果 + continue + } else { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + } + } + return + }) + // 通过命令以图搜图 + zero.OnKeywordGroup([]string{"以图识图", "以图搜图", "搜索图片"}).SetBlock(true).SetPriority(33). + Handle(func(ctx *zero.Ctx) { + var pics []string // 图片搜索池子 + // 获取信息中图片链接 + for _, elem := range ctx.Event.Message { + if elem.Type == "image" { + pics = append(pics, elem.Data["url"]) + } + } + // 没有收到图片则向用户索取 + if len(pics) == 0 { + ctx.Send("请发送多张图片!") + next := ctx.FutureEvent("message", zero.CheckUser(ctx.Event.UserID)) + recv, cancel := next.Repeat() + for e := range recv { // 循环获取channel发来的信息 + if len(e.Message) == 1 && e.Message[0].Type == "text" { + cancel() // 如果是纯文本则退出索取 + break + } + for _, elem := range e.Message { + if elem.Type == "image" { // 将信息中的图片添加到搜索池子 + pics = append(pics, elem.Data["url"]) + } + } + if len(pics) >= 5 { + cancel() // 如果是图片数量大于等于5则退出索取 + break + } + } + } + if len(pics) == 0 { + ctx.Send("没有收到图片,搜图结束......") + return + } + // 开始搜索图片 + ctx.Send("少女祈祷中......") + for _, pic := range pics { + if text, err := utils.SauceNaoSearch(pic); err == nil { + ctx.Send(text) // 返回SauceNAO的结果 + continue + } else { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + } + if text, err := utils.Ascii2dSearch(pic); err == nil { + ctx.Send(text) // 返回Ascii2d的结果 + continue + } else { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + } + } + return + }) +} diff --git a/setutime/setu_geter.go b/setutime/setu_geter.go new file mode 100644 index 0000000000..fb94e254fa --- /dev/null +++ b/setutime/setu_geter.go @@ -0,0 +1,189 @@ +package setutime + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/Yiwen-Chan/ZeroBot-Plugin/setutime/utils" + zero "github.com/wdvxdr1123/ZeroBot" + "github.com/wdvxdr1123/ZeroBot/extension/rate" +) + +var limit = rate.NewManager(time.Minute*1, 5) + +var ( + BOTPATH = utils.PathExecute() // 当前bot运行目录 + DATAPATH = BOTPATH + "data/SetuTime/" // 数据目录 + DBPATH = DATAPATH + "SetuTime.db" // 数据库路径 + + CACHEPATH = DATAPATH + "cache/" // 缓冲图片路径 + CACHEGROUP int64 = 868047498 // 缓冲图片群,不修改无法缓存 + PoolList = []string{"涩图", "二次元", "风景", "车万"} // 可自定义 + + DB = utils.Sqlite{DBPath: DBPATH} // 新建涩图数据库对象 + PoolsCache = utils.NewPoolsCache() // 新建一个缓冲池对象 + + FORM = "PIC" // 默认 PIC 格式 +) + +func init() { + PoolsCache.Group = CACHEGROUP // 图片缓冲群 + PoolsCache.Path = CACHEPATH // 缓冲图片路径 + + utils.CreatePath(DBPATH) + utils.CreatePath(CACHEPATH) + + for i := range PoolList { + if err := DB.Create(PoolList[i], &utils.Illust{}); err != nil { + panic(err) + } + } +} + +func init() { // 插件主体 + zero.OnRegex(`^来份(.*)$`, FirstValueInList(PoolList)).SetBlock(true).SetPriority(20). + Handle(func(ctx *zero.Ctx) { + if limit.Load(ctx.Event.UserID).Acquire() == false { + ctx.Send("请稍后重试0x0...") + return + } + var type_ = ctx.State["regex_matched"].([]string)[1] + // 补充池子 + go func() { + times := utils.Min(PoolsCache.Max-PoolsCache.Size(type_), 2) + for i := 0; i < times; i++ { + illust := &utils.Illust{} + // 查询出一张图片 + if err := DB.Select(type_, illust, "ORDER BY RANDOM() limit 1"); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + continue + } + ctx.SendGroupMessage(PoolsCache.Group, "正在下载"+illust.ImageUrls) + file, err := illust.PixivPicDown(PoolsCache.Path) + if err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + continue + } + if id := ctx.SendGroupMessage(PoolsCache.Group, illust.NormalPic(file)); id == 0 { + ctx.Send(fmt.Sprintf("ERROR: %v", "发送到缓存群失败,请确认缓存群群号")) + continue + } + // 向缓冲池添加一张图片 + if err := PoolsCache.Push(type_, illust); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + continue + } + time.Sleep(time.Second * 1) + } + }() + // 如果没有缓存,阻塞5秒 + if PoolsCache.Size(type_) == 0 { + ctx.Send("[SetuTime] 正在填充弹药......") + <-time.After(time.Second * 5) + if PoolsCache.Size(type_) == 0 { + ctx.Send("[SetuTime] 等待填充,请稍后再试......") + return + } + } + // 从缓冲池里抽一张 + if id := ctx.Send(PoolsCache.GetOnePic(type_, FORM)); id == 0 { + ctx.Send(fmt.Sprintf("ERROR: %v", errors.New("可能被风控了"))) + } + return + }) + + zero.OnRegex(`^添加(.*?)(\d+)$`, FirstValueInList(PoolList), zero.SuperUserPermission).SetBlock(true).SetPriority(21). + Handle(func(ctx *zero.Ctx) { + var ( + type_ = ctx.State["regex_matched"].([]string)[1] + id = utils.Str2Int(ctx.State["regex_matched"].([]string)[2]) + illust = &utils.Illust{} + ) + ctx.Send("少女祈祷中......") + // 查询P站插图信息 + + if err := illust.IllustInfo(id); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + // 下载插画 + if _, err := illust.PixivPicDown(PoolsCache.Path); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + file := fmt.Sprintf("%s%d.jpg", PoolsCache.Path, illust.Pid) + if id := ctx.Send(illust.DetailPic(file)); id == 0 { + ctx.Send(fmt.Sprintf("ERROR: %v", "可能被风控,发送失败")) + return + } + // 添加插画到对应的数据库table + if err := DB.Insert(type_, illust); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + ctx.Send("添加成功") + return + }) + + zero.OnRegex(`^删除(.*?)(\d+)$`, FirstValueInList(PoolList), zero.SuperUserPermission).SetBlock(true).SetPriority(22). + Handle(func(ctx *zero.Ctx) { + var ( + type_ = ctx.State["regex_matched"].([]string)[1] + id = utils.Str2Int(ctx.State["regex_matched"].([]string)[2]) + ) + // 查询数据库 + if err := DB.Delete(type_, fmt.Sprintf("WHERE pid=%d", id)); err != nil { + ctx.Send(fmt.Sprintf("ERROR: %v", err)) + return + } + ctx.Send("删除成功") + return + }) + + // 查询数据库涩图数量 + zero.OnFullMatchGroup([]string{"setu -s", "setu --status"}).SetBlock(true).SetPriority(23). + Handle(func(ctx *zero.Ctx) { + state := []string{"[SetuTime]"} + for i := range PoolList { + num, err := DB.Num(PoolList[i]) + if err != nil { + num = 0 + } + state = append(state, "\n") + state = append(state, PoolList[i]) + state = append(state, ": ") + state = append(state, fmt.Sprintf("%d", num)) + } + ctx.Send(strings.Join(state, "")) + return + }) + // 开xml模式 + zero.OnFullMatchGroup([]string{"setu -x", "setu --xml"}).SetBlock(true).SetPriority(24). + Handle(func(ctx *zero.Ctx) { + FORM = "XML" + ctx.Send("[SetuTime] XML->ON") + return + }) + // 关xml模式 + zero.OnFullMatchGroup([]string{"setu -p", "setu --pic"}).SetBlock(true).SetPriority(24). + Handle(func(ctx *zero.Ctx) { + FORM = "PIC" + ctx.Send("[SetuTime] XML->OFF") + return + }) +} + +// FirstValueInList 判断正则匹配的第一个参数是否在列表中 +func FirstValueInList(list []string) zero.Rule { + return func(ctx *zero.Ctx) bool { + first := ctx.State["regex_matched"].([]string)[1] + for i := range list { + if first == list[i] { + return true + } + } + return false + } +} diff --git a/setutime/utils/ascii2d.go b/setutime/utils/ascii2d.go new file mode 100644 index 0000000000..986adae6ab --- /dev/null +++ b/setutime/utils/ascii2d.go @@ -0,0 +1,100 @@ +package utils + +import ( + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + xpath "github.com/antchfx/htmlquery" +) + +// Ascii2dSearch Ascii2d 以图搜图 +// 第一个参数 返回错误 +// 第二个参数 返回的信息 +func Ascii2dSearch(pic string) (text string, err error) { + var ( + api = "https://ascii2d.net/search/uri" + ) + transport := http.Transport{ + DisableKeepAlives: true, + } + client := &http.Client{ + Transport: &transport, + } + + // 包装请求参数 + data := url.Values{} + data.Set("uri", pic) // 图片链接 + fromData := strings.NewReader(data.Encode()) + + // 网络请求 + req, _ := http.NewRequest("POST", api, fromData) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0") + resp, err := client.Do(req) + if err != nil { + return "", err + } + // 色合检索改变到特征检索 + var bovwUrl = strings.ReplaceAll(resp.Request.URL.String(), "color", "bovw") + bovwReq, _ := http.NewRequest("POST", bovwUrl, nil) + bovwReq.Header.Set("Content-Type", "application/x-www-form-urlencoded") + bovwReq.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36") + bovwResp, err := client.Do(bovwReq) + if err != nil { + return "", err + } + defer bovwResp.Body.Close() + // 解析XPATH + doc, err := xpath.Parse(resp.Body) + if err != nil { + return "", err + } + // 取出每个返回的结果 + list := xpath.Find(doc, `//div[@class="row item-box"]`) + var link string + // 遍历取出第一个返回的PIXIV结果 + for _, n := range list { + linkPath := xpath.Find(n, `//div[2]/div[3]/h6/a[1]`) + picPath := xpath.Find(n, `//div[1]/img`) + if len(linkPath) != 0 && len(picPath) != 0 { + link = xpath.SelectAttr(linkPath[0], "href") + if strings.Contains(link, "www.pixiv.net") { + break + } + } + } + // 链接取出PIXIV id + var index = strings.LastIndex(link, "/") + if link == "" || index == -1 { + return "", errors.New("Ascii2d not found") + } + var id = Str2Int(link[index+1:]) + if id == 0 { + return "", errors.New("convert to pid error") + } + // 根据PID查询插图信息 + var illust = &Illust{} + if err := illust.IllustInfo(id); err != nil { + return "", err + } + if illust.AgeLimit != "all-age" { + return "", errors.New("Ascii2d not found") + } + // 返回插图信息文本 + return fmt.Sprintf( + `[SetuTime] emmm大概是这个? +标题:%s +插画ID:%d +画师:%s +画师ID:%d +直链:https://pixivel.moe/detail?id=%d`, + illust.Title, + illust.Pid, + illust.UserName, + illust.UserId, + illust.Pid, + ), nil +} diff --git a/setutime/utils/download.go b/setutime/utils/download.go new file mode 100644 index 0000000000..3093484999 --- /dev/null +++ b/setutime/utils/download.go @@ -0,0 +1,74 @@ +package utils + +import ( + "crypto/md5" + "crypto/tls" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "os" + "strings" +) + +// urlCache 缓存并返回缓存路径 +func (this *Illust) PixivPicDown(path string) (savePath string, err error) { + url := this.ImageUrls + pid := this.Pid + url = strings.ReplaceAll(url, "img-original", "img-master") + url = strings.ReplaceAll(url, "_p0", "_p0_master1200") + url = strings.ReplaceAll(url, ".png", ".jpg") + // 文件名为url的hash值 + savePath = path + Int2Str(pid) + ".jpg" + // 文件存在或文件大小大于10kb + if PathExists(savePath) && FileSize(savePath) > 10240 { + return savePath, nil + } + + // 模拟QQ客户端请求 + client := &http.Client{ + Transport: &http.Transport{ + DisableKeepAlives: true, + // 绕过sni审查 + TLSClientConfig: &tls.Config{ + ServerName: "-", + InsecureSkipVerify: true, + }, + // 更改dns + Dial: func(network, addr string) (net.Conn, error) { + return net.Dial("tcp", "210.140.92.140:443") + }, + }, + } + reqest, _ := http.NewRequest("GET", url, nil) + reqest.Header.Set("Referer", "https://www.pixiv.net/") + reqest.Header.Set("Host", "i.pximg.net") + reqest.Header.Set("User-Agent", "QQ/8.2.0.1296 CFNetwork/1126") + + resp, err := client.Do(reqest) + if err != nil { + return "", err + } + fmt.Println(resp.StatusCode) + if code := resp.StatusCode; code != 200 { + return "", errors.New(fmt.Sprintf("Download failed, code %d", code)) + } + defer resp.Body.Close() + // 写入文件 + data, _ := ioutil.ReadAll(resp.Body) + f, _ := os.OpenFile(savePath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) + defer f.Close() + f.Write(data) + + return savePath, err +} + +// PicHash 返回图片的 md5 值 +func PicHash(path string) string { + data, err := ioutil.ReadFile(path) + if err != nil { + return "" + } + return strings.ToUpper(fmt.Sprintf("%x", md5.Sum(data))) +} diff --git a/setutime/utils/pic_pool.go b/setutime/utils/pic_pool.go new file mode 100644 index 0000000000..dbd71b150f --- /dev/null +++ b/setutime/utils/pic_pool.go @@ -0,0 +1,113 @@ +package utils + +import ( + "fmt" + "sync" +) + +// PoolsCache 图片缓冲池 +type PoolsCache struct { + Lock sync.Mutex + Max int + Path string + Group int64 + Pool map[string][]*Illust +} + +// NewPoolsCache 返回一个缓冲池对象 +func NewPoolsCache() *PoolsCache { + return &PoolsCache{ + Max: 10, + Path: "./data/SetuTime/cache/", + Group: 1048452984, + Pool: map[string][]*Illust{}, + } +} + +// Size 返回缓冲池指定类型的现有大小 +func (p *PoolsCache) Size(type_ string) int { + return len(p.Pool[type_]) +} + +// IsFull 返回缓冲池指定类型是否已满 +func (p *PoolsCache) IsFull(type_ string) bool { + return len(p.Pool[type_]) >= p.Max +} + +// Push 向缓冲池插入一张图片,返回错误 +func (p *PoolsCache) Push(type_ string, illust *Illust) (err error) { + p.Lock.Lock() + defer p.Lock.Unlock() + p.Pool[type_] = append(p.Pool[type_], illust) + return nil +} + +// Push 在缓冲池拿出一张图片,返回错误 +func (p *PoolsCache) Pop(type_ string) (illust *Illust) { + p.Lock.Lock() + defer p.Lock.Unlock() + if p.Size(type_) == 0 { + return + } + illust = p.Pool[type_][0] + p.Pool[type_] = p.Pool[type_][1:] + return +} + +// Push 在缓冲池拿出一张图片,返回指定格式CQ码 +func (p *PoolsCache) GetOnePic(type_ string, form string) string { + var ( + illust = p.Pop(type_) + file = fmt.Sprintf("%s%d.jpg", p.Path, illust.Pid) + ) + switch form { + case "XML": + return illust.BigPic(file) + case "DETAIL": + return illust.DetailPic(file) + default: + return illust.NormalPic(file) + } +} + +// BigPic 返回一张XML大图CQ码 +func (i *Illust) BigPic(file string) string { + var hash = PicHash(file) + return fmt.Sprintf(`[CQ:xml,data= + +]`, + hash, + hash, + hash, + i.Title, + i.Pid, + i.UserName, + ) +} + +// NormalPic 返回一张普通图CQ码 +func (i *Illust) NormalPic(file string) string { + return fmt.Sprintf(`[CQ:image,file=file:///%s]`, file) +} + +// DetailPic 返回一张带详细信息的图片CQ码 +func (i *Illust) DetailPic(file string) string { + return fmt.Sprintf(`[SetuTime] %s 标题:%s +插画ID:%d +画师:%s +画师ID:%d +直链:https://pixivel.moe/detail?id=%d`, + i.NormalPic(file), + i.Title, + i.Pid, + i.UserName, + i.UserId, + i.Pid, + ) +} diff --git a/setutime/utils/pixiv_api.go b/setutime/utils/pixiv_api.go new file mode 100644 index 0000000000..09a20307e0 --- /dev/null +++ b/setutime/utils/pixiv_api.go @@ -0,0 +1,94 @@ +package utils + +import ( + "crypto/tls" + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "strings" + + "github.com/tidwall/gjson" +) + +// Illust 插画信息 +type Illust struct { + Pid int64 `db:"pid"` + Title string `db:"title"` + Caption string `db:"caption"` + Tags string `db:"tags"` + ImageUrls string `db:"image_urls"` + AgeLimit string `db:"age_limit"` + CreatedTime string `db:"created_time"` + UserId int64 `db:"user_id"` + UserName string `db:"user_name"` +} + +// IllustInfo 根据p站插画id返回插画信息Illust +func (this *Illust) IllustInfo(id int64) (err error) { + api := fmt.Sprintf("https://pixiv.net/ajax/illust/%d", id) + transport := http.Transport{ + DisableKeepAlives: true, + // 绕过sni审查 + TLSClientConfig: &tls.Config{ + ServerName: "-", + InsecureSkipVerify: true, + }, + // 更改dns + Dial: func(network, addr string) (net.Conn, error) { + return net.Dial("tcp", "210.140.131.223:443") + }, + } + client := &http.Client{ + Transport: &transport, + } + + // 网络请求 + req, err := http.NewRequest("GET", api, nil) + if err != nil { + return err + } + req.Header.Set("Host", "pixiv.net") + req.Header.Set("Referer", "pixiv.net") + req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0") + + resp, err := client.Do(req) + if err != nil { + return err + } + if code := resp.StatusCode; code != 200 { + return errors.New(fmt.Sprintf("Search illust's info failed, status %d", code)) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + json := gjson.ParseBytes(body).Get("body") + + // 如果有"R-18"tag则判断为R-18(暂时) + var ageLimit = "all-age" + for _, tag := range json.Get("tags.tags.#.tag").Array() { + if tag.Str == "R-18" { + ageLimit = "r18" + break + } + } + // 解决json返回带html格式 + var caption = strings.ReplaceAll(json.Get("illustComment").Str, "
", "\n") + if index := strings.Index(caption, "<"); index != -1 { + caption = caption[:index] + } + // 解析返回插画信息 + this.Pid = json.Get("illustId").Int() + this.Title = json.Get("illustTitle").Str + this.Caption = caption + this.Tags = fmt.Sprintln(json.Get("tags.tags.#.tag").Array()) + this.ImageUrls = json.Get("urls.original").Str + this.AgeLimit = ageLimit + this.CreatedTime = json.Get("createDate").Str + this.UserId = json.Get("userId").Int() + this.UserName = json.Get("userName").Str + return nil +} diff --git a/setutime/utils/saucenao.go b/setutime/utils/saucenao.go new file mode 100644 index 0000000000..5584e0a73b --- /dev/null +++ b/setutime/utils/saucenao.go @@ -0,0 +1,84 @@ +package utils + +import ( + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/tidwall/gjson" +) + +// SauceNaoSearch SauceNao 以图搜图 需要链接 返回错误和信息 +func SauceNaoSearch(pic string) (text string, err error) { + var ( + api = "https://saucenao.com/search.php" + apiKey = "2cc2772ca550dbacb4c35731a79d341d1a143cb5" + + minSimilarity = 70.0 // 返回图片结果的最小相似度 + ) + + transport := http.Transport{ + DisableKeepAlives: true, + } + client := &http.Client{ + Transport: &transport, + } + + // 包装请求参数 + data := url.Values{} + data.Set("url", pic) // 图片链接 + data.Set("api_key", apiKey) // api_key + data.Set("db", "5") // 只搜索Pixiv + data.Set("numres", "1") // 返回一个结果 + data.Set("output_type", "2") // 返回JSON格式数据 + fromData := strings.NewReader(data.Encode()) + + // 网络请求 + req, err := http.NewRequest("POST", api, fromData) + if err != nil { + return "", err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:6.0) Gecko/20100101 Firefox/6.0") + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + if code := resp.StatusCode; code != 200 { + // 如果返回不是200则立刻抛出错误 + return "", errors.New(fmt.Sprintf("SauceNAO not found, code %d", code)) + } + content := gjson.ParseBytes(body) + if status := content.Get("header.status").Int(); status != 0 { + // 如果json信息返回status不为0则立刻抛出错误 + return "", errors.New(fmt.Sprintf("SauceNAO not found, status %d", status)) + } + if content.Get("results.0.header.similarity").Float() < minSimilarity { + return "", errors.New("SauceNAO not found") + } + // 正常发送 + return fmt.Sprintf( + `[SetuTime] 我有把握是这个![CQ:image,file=%s]相似度:%s%% +标题:%s +插画ID:%d +画师:%s +画师ID:%d +直链:https://pixivel.moe/detail?id=%d`, + content.Get("results.0.header.thumbnail").Str, + content.Get("results.0.header.similarity").Str, + content.Get("results.0.data.title").Str, + content.Get("results.0.data.pixiv_id").Int(), + content.Get("results.0.data.member_name").Str, + content.Get("results.0.data.member_id").Int(), + content.Get("results.0.data.pixiv_id").Int(), + ), nil +} diff --git a/setutime/utils/sqlite.go b/setutime/utils/sqlite.go new file mode 100644 index 0000000000..d47d0ac56e --- /dev/null +++ b/setutime/utils/sqlite.go @@ -0,0 +1,245 @@ +package utils + +import ( + "database/sql" + "errors" + "reflect" + "strings" + + _ "github.com/mattn/go-sqlite3" +) + +// Sqlite 数据库对象 +type Sqlite struct { + DB *sql.DB + DBPath string +} + +// Create 生成数据库 +// 默认结构体的第一个元素为主键 +// 返回错误 +func (db *Sqlite) Create(table string, objptr interface{}) (err error) { + if db.DB == nil { + database, err := sql.Open("sqlite3", db.DBPath) + if err != nil { + return err + } + db.DB = database + } + var ( + tags = tags(objptr) + kinds = kinds(objptr) + top = len(tags) - 1 + cmd = []string{} + ) + cmd = append(cmd, "CREATE TABLE IF NOT EXISTS") + cmd = append(cmd, table) + cmd = append(cmd, "(") + for i := range tags { + cmd = append(cmd, tags[i]) + cmd = append(cmd, kinds[i]) + switch i { + default: + cmd = append(cmd, "NULL,") + case 0: + cmd = append(cmd, "PRIMARY KEY") + cmd = append(cmd, "NOT NULL,") + case top: + cmd = append(cmd, "NULL);") + } + } + if _, err := db.DB.Exec(strings.Join(cmd, " ")); err != nil { + return err + } + return nil +} + +// Insert 插入数据集 +// 默认结构体的第一个元素为主键 +// 返回错误 +func (db *Sqlite) Insert(table string, objptr interface{}) (err error) { + rows, err := db.DB.Query("SELECT * FROM " + table) + if err != nil { + return err + } + defer rows.Close() + tags, _ := rows.Columns() + var ( + values = values(objptr) + top = len(tags) - 1 + cmd = []string{} + ) + cmd = append(cmd, "INSERT INTO") + cmd = append(cmd, table) + for i := range tags { + switch i { + default: + cmd = append(cmd, tags[i]) + cmd = append(cmd, ",") + case 0: + cmd = append(cmd, "(") + cmd = append(cmd, tags[i]) + cmd = append(cmd, ",") + case top: + cmd = append(cmd, tags[i]) + cmd = append(cmd, ")") + } + } + for i := range tags { + switch i { + default: + cmd = append(cmd, "?") + cmd = append(cmd, ",") + case 0: + cmd = append(cmd, "VALUES (") + cmd = append(cmd, "?") + cmd = append(cmd, ",") + case top: + cmd = append(cmd, "?") + cmd = append(cmd, ")") + } + } + stmt, err := db.DB.Prepare(strings.Join(cmd, " ")) + if err != nil { + return err + } + _, err = stmt.Exec(values...) + if err != nil { + return err + } + return nil +} + +// Select 查询数据库 +// condition 可为"WHERE id = 0" +// 默认字段与结构体元素顺序一致 +// 返回错误 +func (db *Sqlite) Select(table string, objptr interface{}, condition string) (err error) { + var cmd = []string{} + cmd = append(cmd, "SELECT * FROM ") + cmd = append(cmd, table) + cmd = append(cmd, condition) + rows, err := db.DB.Query(strings.Join(cmd, " ")) + if err != nil { + return err + } + defer rows.Close() + + for rows.Next() { + if err != nil { + return err + } + err = rows.Scan(addrs(objptr)...) + if err != nil { + return err + } + return nil + } + return errors.New("数据库无此条件项目") +} + +// Delete 删除数据库 +// condition 可为"WHERE id = 0" +// 返回错误 +func (db *Sqlite) Delete(table string, condition string) (err error) { + var cmd = []string{} + cmd = append(cmd, "DELETE FROM") + cmd = append(cmd, table) + cmd = append(cmd, condition) + stmt, err := db.DB.Prepare(strings.Join(cmd, " ")) + if err != nil { + return err + } + _, err = stmt.Exec() + if err != nil { + return err + } + return nil +} + +// Num 查询数据库行数 +// 返回行数以及错误 +func (db *Sqlite) Num(table string) (num int, err error) { + var cmd = []string{} + cmd = append(cmd, "SELECT * FROM") + cmd = append(cmd, table) + rows, err := db.DB.Query(strings.Join(cmd, " ")) + if err != nil { + return num, err + } + defer rows.Close() + for rows.Next() { + num++ + } + return num, nil +} + +// tags 反射 返回结构体对象的 tag 数组 +func tags(objptr interface{}) []string { + var tags []string + elem := reflect.ValueOf(objptr).Elem() + // 判断第一个元素是否为匿名字段 + if elem.Type().Field(0).Anonymous { + elem = elem.Field(0) + } + for i, flen := 0, elem.Type().NumField(); i < flen; i++ { + tags = append(tags, elem.Type().Field(i).Tag.Get("db")) + } + return tags +} + +// kinds 反射 返回结构体对象的 kinds 数组 +func kinds(objptr interface{}) []string { + var kinds []string + elem := reflect.ValueOf(objptr).Elem() + // 判断第一个元素是否为匿名字段 + if elem.Type().Field(0).Anonymous { + elem = elem.Field(0) + } + for i, flen := 0, elem.Type().NumField(); i < flen; i++ { + switch elem.Field(i).Type().String() { + case "int64": + kinds = append(kinds, "INT") + case "string": + kinds = append(kinds, "TEXT") + default: + kinds = append(kinds, "TEXT") + } + } + return kinds +} + +// values 反射 返回结构体对象的 values 数组 +func values(objptr interface{}) []interface{} { + var values []interface{} + elem := reflect.ValueOf(objptr).Elem() + // 判断第一个元素是否为匿名字段 + if elem.Type().Field(0).Anonymous { + elem = elem.Field(0) + } + for i, flen := 0, elem.Type().NumField(); i < flen; i++ { + switch elem.Field(i).Type().String() { + case "int64": + values = append(values, elem.Field(i).Int()) + case "string": + values = append(values, elem.Field(i).String()) + default: + values = append(values, elem.Field(i).String()) + } + } + return values +} + +// addrs 反射 返回结构体对象的 addrs 数组 +func addrs(objptr interface{}) []interface{} { + var addrs []interface{} + elem := reflect.ValueOf(objptr).Elem() + // 判断第一个元素是否为匿名字段 + if elem.Type().Field(0).Anonymous { + elem = elem.Field(0) + } + for i, flen := 0, elem.Type().NumField(); i < flen; i++ { + addrs = append(addrs, elem.Field(i).Addr().Interface()) + } + return addrs +} diff --git a/setutime/utils/switch.go b/setutime/utils/switch.go new file mode 100644 index 0000000000..c57748776f --- /dev/null +++ b/setutime/utils/switch.go @@ -0,0 +1,5 @@ +package utils + +var ( + XML = true // 图片XML开关,默认开启 +) diff --git a/setutime/utils/utils.go b/setutime/utils/utils.go new file mode 100644 index 0000000000..24d75f74c7 --- /dev/null +++ b/setutime/utils/utils.go @@ -0,0 +1,73 @@ +package utils + +import ( + "os" + "strconv" + "strings" +) + +// Str2Int string --> int64 +func Str2Int(str string) int64 { + val, _ := strconv.Atoi(str) + return int64(val) +} + +// Int2Str int64 --> string +func Int2Str(val int64) string { + str := strconv.FormatInt(val, 10) + return str +} + +// PathExecute 返回当前运行目录 +func PathExecute() string { + dir, err := os.Getwd() + if err != nil { + panic(err) + } + return dir + "/" +} + +// CreatePath 生成路径或文件所对应的目录 +func CreatePath(path string) { + length := len(path) + switch { + case path[length:] != "/": + path = path[:strings.LastIndex(path, "/")] + case path[length:] != "\\": + path = path[:strings.LastIndex(path, "\\")] + default: + // + } + if !PathExists(path) { + err := os.MkdirAll(path, 0644) + if err != nil { + panic(err) + } + } +} + +// PathExists 判断路径或文件是否存在 +func PathExists(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} + +// FileSize 获取文件大小 +func FileSize(file string) int64 { + if fi, err := os.Stat(file); err == nil { + return fi.Size() + } + return 0 +} + +// Min 返回两数最小值 +func Min(a, b int) int { + switch { + default: + return a + case a > b: + return b + case a < b: + return a + } +} diff --git a/utils/utils.go b/utils/utils.go deleted file mode 100644 index b6f2e8128d..0000000000 --- a/utils/utils.go +++ /dev/null @@ -1,47 +0,0 @@ -package utils - -import ( - "io/ioutil" - "os" - "strconv" -) - -func PathExecute() string { - dir, _ := os.Getwd() - return dir + "\\" -} - -func PathExists(path string) bool { - _, err := os.Stat(path) - return err == nil || os.IsExist(err) -} - -func ReadAllText(path string) string { - b, err := ioutil.ReadFile(path) - if err != nil { - return "" - } - return string(b) -} - -func WriteAllText(path, text string) { - _ = ioutil.WriteFile(path, []byte(text), 0644) -} - -func CreatePath(path string) error { - err := os.MkdirAll(path, 0644) - if err != nil { - return err - } - return nil -} - -func Int2Str(val int64) string { - str := strconv.FormatInt(val, 10) - return str -} - -func Str2Int(str string) int64 { - val, _ := strconv.ParseInt(str, 10, 64) - return val -} diff --git a/utils/yaml.go b/utils/yaml.go deleted file mode 100644 index e1fd471286..0000000000 --- a/utils/yaml.go +++ /dev/null @@ -1,59 +0,0 @@ -package utils - -import ( - "fmt" - "os" - "strconv" - "time" - - "gopkg.in/yaml.v3" -) - -type YamlConfig struct { - Version string `yaml:"插件版本"` - Host string `yaml:"监听地址"` - Port string `yaml:"监听端口"` - AccessToken string `yaml:"Token"` - Master []string `yaml:"主人QQ"` -} - -func DefaultConfig() *YamlConfig { - return &YamlConfig{ - Version: "1", - Host: "127.0.0.1", - Port: "8080", - AccessToken: "", - Master: []string{ - "66666666", - "88888888", - }, - } -} - -func Load(p string) *YamlConfig { - if !PathExists(p) { - CreatePath(p) - DefaultConfig().Save(p) - } - c := YamlConfig{} - err := yaml.Unmarshal([]byte(ReadAllText(p)), &c) - if err != nil { - fmt.Println("[GroupManager] 尝试加载配置文件失败: 读取文件失败") - fmt.Println("[GroupManager] 原配置文件已备份") - os.Rename(p, p+".backup"+strconv.FormatInt(time.Now().Unix(), 10)) - DefaultConfig().Save(p) - } - c = YamlConfig{} - yaml.Unmarshal([]byte(ReadAllText(p)), &c) - return &c -} - -func (c *YamlConfig) Save(p string) error { - data, err := yaml.Marshal(c) - if err != nil { - fmt.Println("[GroupManager] 写入新的配置文件失败") - return err - } - WriteAllText(p, string(data)) - return nil -}