diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..e72bfddab --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..4131253a5 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +# Surge + +Netflix ratings(IMDb、douban) +```properties +[Script] +nf_rating.js = type=http-request,pattern=^https?:\/\/ios(-.*)?\.prod\.ftl\.netflix\.com\/iosui\/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +nf_rating.js = type=http-response,requires-body=1,pattern=^https?:\/\/ios(-.*)?\.prod\.ftl\.netflix\.com\/iosui\/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +[MITM] +hostname = ios-*.prod.ftl.netflix.com,ios.prod.ftl.netflix.com +``` + +Wb +```properties +[Script] +http-response ^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_launch.js +http-response ^https?://m?api\.weibo\.c(n|om)/2/(messageflow/notice|search/(container_timeline|finder)|statuses/(container_timeline_hot|container_timeline_unread|container_timeline|unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/(photos/pic_recommend_status|live/media_homelist)|video/tiny_stream_video_list|photo/info|remind/unread_count) requires-body=1,max-size=-1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_ad.js +[MITM] +hostname = api.weibo.cn, mapi.weibo.com, *.uve.weibo.com +``` + +~~Display jd historical price~~ +```properties +[Script] +http-response ^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig|basicConfig) requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js +[MITM] +hostname = api.m.jd.com +``` + +~~Display taobao historical price~~ +```properties +[Script] +http-response ^http://.+/amdc/mobileDispatch requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +http-response ^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +[MITM] +hostname = trade-acs.m.taobao.com +``` + +# Quan-X + +Netflix ratings(IMDb、douban) +```properties +[rewrite_local] +^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-request-header https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +[mitm] +hostname = ios-*.prod.ftl.netflix.com,ios.prod.ftl.netflix.com +``` + +Wb +```properties +[rewrite_local] +^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/wb_launch.js +^https?://m?api\.weibo\.c(n|om)/2/(messageflow/notice|search/(container_timeline|finder)|statuses/(container_timeline_hot|container_timeline_unread|container_timeline|unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/(photos/pic_recommend_status|live/media_homelist)|video/tiny_stream_video_list|photo/info|remind/unread_count) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/wb_ad.js +[mitm] +hostname = api.weibo.cn, mapi.weibo.com, *.uve.weibo.com +``` + +~~Display jd historical price~~ +```properties +[rewrite_local] +^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig|basicConfig) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js +[mitm] +hostname = api.m.jd.com +``` + +~~Display taobao historical price~~ +```properties +[rewrite_local] +^http://.+/amdc/mobileDispatch url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +[mitm] +hostname = trade-acs.m.taobao.com +``` diff --git a/clock_in.js b/clock_in.js new file mode 100644 index 000000000..ef94d9f5a --- /dev/null +++ b/clock_in.js @@ -0,0 +1,84 @@ +/* +README:https://github.com/yichahucha/surge/tree/master +每日打卡提醒(corn "0 9,18 * * 1-5" 周一到周五,早九晚六)+ 每日壹句(有道词典)+ 跳转钉钉打卡页面 +*/ + +const $tool = new Tool() +$tool.get('https://dict.youdao.com/infoline/style/cardList?mode=publish&client=mobile&style=daily&size=2', function (error, response, data) { + let obj = JSON.parse(data); + let date = new Date(); + let isAM = date.getHours() < 12 ? true : false; + let title = 'Clock' + (isAM ? ' in' : ' out') + (isAM ? ' ☀️' : ' 🌙'); + let subtitle = ''; + let scheme = 'dingtalk://dingtalkclient/page/link?url=https://attend.dingtalk.com/attend/index.html'; + let content = ""; + let option = {"open-url" : scheme}; + if (!error) { + if (obj && obj.length > 1) { + let yi = obj[1]; + content = yi.title + '\n' + yi.summary; + option["media-url"] = yi.image[0]; + } + } + $tool.notify(title, subtitle, content, option); + $done(); +}) + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isLoon = typeof $loon !== "undefined"; + _isSurge = typeof $httpClient != "undefined" && !_isLoon; + _isQuanX = typeof $task != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isResponse = typeof $response != "undefined" + this.notify = (title, subtitle, message, option) => { + if (_isQuanX) $notify(title, subtitle, message, option) + if (_isSurge) $notification.post(title, subtitle, message, {"url":option["open-url"]}) + if (_isLoon) $notification.post(title, subtitle, message, option["open-url"]) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} diff --git a/eval_script.js b/eval_script.js new file mode 100644 index 000000000..b4be4fe9b --- /dev/null +++ b/eval_script.js @@ -0,0 +1,841 @@ +const __conf = String.raw` + +[Remote] +// custom remote... +# https://raw.githubusercontent.com/yichahucha/surge/master/qx_sub.txt + + +[Local] +// custom local... +# jd +# ^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js + + +[Hostname] +// custom hostname... +# api.m.jd.com + + +` + +const __isUpdateGithub = true +// GitHub Token(如果使用账号密码 token 请设置为空 "") +const __token = "" +// GitHub 账号 +const __username = "xxx" +// GitHub 密码 +const __password = "xxx" +// GitHub 用户名 +const __owner = "xxx" +// GitHub 仓库名 +const __repo = "xxx" +// GitHub 分支(不指定就使用默认分支) +const __branch = "master" +// 指定 eval_script.js 文件路径(路径为空 "" 使用脚本原始路径) +const __evalPath = "eval_script.js" +// GitHub 文件路径(没有文件新创建,已有文件覆盖更新,路径为空 "" 不更新) +const __quanxPath = "eval_script/qx_script.txt" +const __surgePath = "eval_script/sg_script.sgmodule" +// GitHub 更新日志 +const __quanxCommit = "update" +const __surgeCommit = "update" + +const __emojiDone = "" +const __emojiFail = "🙃" +const __emojiSuccess = "😀" +const __emojiTasks = "🕐" +const __emojiUpdateSuccess = "🟢" +const __emojiUpdateFail = "🟠 " +const __emojiGitHub = "🔵" +const __showLine = 15 + +const __log = false +const __debug = false +const __developmentMode = false +const __concurrencyLimit = 15 +const __cacheKey = "ScriptCacheKey" + +const __tool = new ____Tool() +const __base64 = new ____Base64() + +if (__tool.isTask) { + const begin = new Date() + const storeObj = {} + //get conf info (local remote) + ____getConf() + //check conf + .then((conf) => { + return new Promise((resolve, reject) => { + if (conf.contents.length > 0) { + storeObj["confResults"] = conf.results + storeObj["confHostnames"] = conf.hostnames + if (__log) console.log(conf.contents) + resolve(conf.contents) + } else { + let message = "" + conf.results.forEach(data => { + message += message.length > 0 ? "\n" + data.message : data.message + }); + reject(message.length > 0 ? message : `Unavailable configuration! Please check!`) + } + }) + }) + //parse conf + .then((contents) => { + return new Promise((resolve, reject) => { + const parseResult = ____parseConf(contents) + if (parseResult.confMap) { + storeObj["confMap"] = parseResult.confMap + storeObj["quanxConfContents"] = parseResult.quanxConfContents + storeObj["surgeConfContents"] = parseResult.surgeConfContents + if (__log) console.log(parseResult.confMap) + if (__log) console.log("quanx\n" + parseResult.quanxConfContents.join("\n")); + if (__log) console.log("surge\n" + parseResult.surgeConfContents.join("\n")); + resolve(parseResult.confMap) + } else { + reject(`Configuration information error: ${parseResult.error}`) + } + }) + }) + //download script + .then((confMap) => { + const scriptUrls = Object.keys(confMap) + __tool.notify("", "", `Start updating ${scriptUrls.length} scripts...`) + console.log("Start updating script...") + return ____concurrentQueueLimit(scriptUrls, __concurrencyLimit, (url) => { + const urlRegex = /^(https?:\/\/(([a-zA-Z0-9]+-?)+[a-zA-Z0-9]+\.)+[a-zA-Z]+)(:\d+)?(\/.*)?(\?.*)?(#.*)?$/ + return new Promise((resolve) => { + if (urlRegex.test(url)) { + ____downloadFile(url).then((data) => { + if (data.code == 200) { + __tool.write(data.body, data.url) + } + resolve(data) + }) + } else { + __tool.write(url, url) + resolve({ body: url, url, message: `${__emojiUpdateSuccess}${url} function set successfully` }) + } + }) + }) + }) + //write cache + .then((scriptResults) => { + console.log("Stop updating script.") + storeObj["scriptResults"] = scriptResults + return __tool.write(JSON.stringify(storeObj.confMap), __cacheKey) + }) + //update github + .then(() => { + if (__isUpdateGithub) { + const quanxHostname = `${storeObj.confHostnames.length > 0 ? `hostname = ${Array.from(new Set(storeObj.confHostnames)).join(",")}` : ""}` + const surgeHostname = `${storeObj.confHostnames.length > 0 ? `hostname = %INSERT% ${Array.from(new Set(storeObj.confHostnames)).join(",")}` : ""}` + const quanxUpdateContent = `${quanxHostname}\n\n${Array.from(new Set(storeObj.quanxConfContents)).join("\n\n")}` + const surgeUpdateContent = `#!name=eval_script.js module\n\n[MITM]\n${surgeHostname}\n\n[Script]\n${Array.from(new Set(storeObj.surgeConfContents)).join("\n\n")}` + const args = [{ path: __quanxPath, content: quanxUpdateContent, commit: __quanxCommit }, { path: __surgePath, content: surgeUpdateContent, commit: __surgeCommit }] + console.log("Start updating github...") + const update = async () => { + let results = [] + for (let i = 0, len = args.length; i < len; i++) { + const arg = args[i] + if (arg.path.length > 0) { + const result = await ____updateGitHub(arg.path, arg.content, arg.commit) + results.push(result) + } + } + console.log("Stop updating github.") + return results + } + return update() + } else { + return null + } + }) + //notify + .then((githubResults) => { + const github = (() => { + let message = "" + if (githubResults && githubResults.length > 0) { + githubResults.forEach((result, index) => { + if (index == 0) message = result.message + message += message.length > 0 ? "\n" + __emojiGitHub + result.url : __emojiGitHub + result.url + }); + } + return message + })() + const confResults = storeObj.confResults + const scriptResults = storeObj.scriptResults + const resultInfo = (() => { + let message = "" + let success = 0 + let fail = 0 + confResults.concat(scriptResults).forEach(data => { + if (data.message.match("success")) success++ + if (data.message.match("fail")) fail++ + message += message.length > 0 ? "\n" + data.message : data.message + }); + return { message, count: { success, fail } } + })() + const messages = resultInfo.message.split("\n") + const detail = `${messages.slice(0, __showLine).join("\n")}${messages.length > 20 ? `\n${__emojiUpdateSuccess}......` : ""}` + const summary = `${__emojiSuccess}success: ${resultInfo.count.success} ${__emojiFail}fail: ${resultInfo.count.fail} ${__emojiTasks}tasks: ${____timeDiff(begin, new Date())}s` + const nowDate = `${new Date().Format("yyyy-MM-dd HH:mm:ss")} last updated` + const lastDate = __tool.read("ScriptLastUpdateDateKey") + const date = `${__emojiTasks}${lastDate ? lastDate : nowDate}` + console.log(`\n${summary}\n${resultInfo.message}${github.length > 0 ? `\n${github}` : ""}\n${date}`) + __tool.notify(`${__emojiDone}Update Done`, summary, `${detail}${github.length > 0 ? `\n${github}` : ""}\n${date}`) + __tool.write(nowDate, "ScriptLastUpdateDateKey") + __tool.done({}) + }) + .catch((error) => { + console.log(error) + __tool.notify("[eval_script.js]", "", error) + __tool.done({}) + }) +} + +if (!__tool.isTask) { + const __url = $request.url + const __confObj = (() => { + if (__developmentMode) { + return ____parseDevelopmentModeConf(__conf) + } else { + return JSON.parse(__tool.read(__cacheKey)) + } + })() + const __script = (() => { + let script = null + const keys = Object.keys(__confObj) + for (let i = 0, len = keys.length; i < len; i++) { + if (script) break + const key = keys[i] + const value = __confObj[key] + for (let j = 0, len = value.length; j < len; j++) { + const match = value[j] + try { + const regular = new RegExp(match.regular) + if (regular.test(__url)) { + const type = match.type + if (type && type.length > 0) { + if (__tool.scriptType == type) { + script = { url: key, match, content: __developmentMode ? key : __tool.read(key) } + break + } + } else { + script = { url: key, match, content: __developmentMode ? key : __tool.read(key) } + break + } + } + } catch (error) { + if (__debug) __tool.notify("[eval_script.js]", "", `Error regular : ${match.regular}\nRequest: ${__url}`) + //throw error + } + } + } + return script + })() + if (__script) { + if (__script.content) { + const type = __script.match.type + if (type && type.length > 0) { + if (__tool.scriptType == type) { + if (__debug) { + try { + eval(__script.content) + if (__debug) __tool.notify("[eval_script.js]", `${__tool.method} ${__tool.scriptType}==${type}`, `Execute script: ${__script.url}\nRegular: ${__script.match.regular}\nRequest: ${__url}`) + } catch (error) { + if (__debug) __tool.notify("[eval_script.js]", `${__tool.method} ${__tool.scriptType}`, `Script execute error: ${error}\nScript: ${__script.url}\nRegular: ${__script.match}\nRequest: ${__url}\nContent: ${__script.content}`) + throw error + } + } else { + eval(__script.content) + } + } else { + __tool.done({}) + if (__debug) __tool.notify("[eval_script.js]", `${__tool.method} ${__tool.scriptType}!=${type}`, `Script types do not match! Don't execute script.\nScript: ${__script.url}\nRegular: ${__script.match.regular}\nRequest: ${__url}`) + } + } else { + if (__debug) { + try { + eval(__script.content) + if (__debug) __tool.notify("[eval_script.js]", `${__tool.method} ${__tool.scriptType} ${"request&&response"}`, `Execute script: ${__script.url}\nRegular: ${__script.match.regular}\nRequest: ${__url}`) + } catch (error) { + if (__debug) __tool.notify("[eval_script.js]", `${__tool.method} ${__tool.scriptType}`, `Script execute error: ${error}\nScript: ${__script.url}\nRegular: ${__script.match.regular}\nRequest: ${__url}\nContent: ${__script.content}`) + throw error + } + } else { + eval(__script.content) + } + } + } else { + __tool.done({}) + if (__log) console.log(`script not found: ${__script.url}\nregular: ${__script.match.regular}\nrequest: ${__url}`) + } + } else { + __tool.done({}) + if (__log) console.log(`script not matched: ${__url}`) + } +} + +async function ____updateGitHub(path, content, message) { + const url = `https://api.github.com/repos/${__owner}/${__repo}/contents/${path}` + const options = { + url: url, + headers: { "Content-Type": "application/json; charset=utf-8", "User-Agent": "eval_script.js" } + } + if (__token.length > 0) { + options.headers["Authorization"] = `Token ${__token}` + } else { + options.headers["Authorization"] = `Basic ${__base64.encode(`${__username}:${__password}`)}` + } + const getContent = () => { + return new Promise(function (resolve, reject) { + if (__branch.length > 0) options.url += `?ref=${__branch}` + options["method"] = "GET" + __tool.get(options, (error, response, body) => { + if (!error) { + if (__log) console.log(`getContent: ${response.status}\n${body}`) + body = JSON.parse(body) + if (response.status == 200) { + resolve(body.sha) + } else if (response.status == 404) { + resolve(null) + } else { + reject("GitHub update failed: " + body.message) + } + } else { + reject("GitHub update failed: " + error) + } + }) + }) + } + const updateContent = (sha) => { + return new Promise(function (resolve, reject) { + const body = { + message: message, + content: __base64.encode(content) + } + if (__branch) body["branch"] = __branch + if (sha) body["sha"] = sha + options.url = url + options["body"] = JSON.stringify(body) + options["method"] = "PUT" + __tool.put(options, (error, response, body) => { + if (!error) { + if (__log) console.log(`updateContent: ${response.status}\n${body}`) + body = JSON.parse(body) + if (response.status == 200) { + resolve({ url: body.content.download_url, message: `${__emojiGitHub}GitHub updated successfully${sha != body.content.sha ? " (file changes)" : ""}` }) + } else if (response.status == 201) { + resolve({ url: body.content.download_url, message: `${__emojiGitHub}GitHub file created successfully` }) + } else { + reject("GitHub update failed: " + body.message) + } + } else { + reject("GitHub update failed: " + error) + } + }) + }) + } + const sha = await getContent() + const result = await updateContent(sha) + return result +} + +function ____getConf() { + return new Promise((resolve) => { + const remoteConf = ____removeAnnotation(____extractConf(__conf, "Remote")) + const localConf = ____removeAnnotation(____extractConf(__conf, "Local")) + const hostnameConf = ____parseHostname(____removeAnnotation(____extractConf(__conf, "Hostname"))) + if (remoteConf.length > 0) { + console.log("Start updating conf...") + if (__debug) __tool.notify("", "", `Start updating ${remoteConf.length} confs...`) + ____concurrentQueueLimit(remoteConf, __concurrencyLimit, (url) => { + return ____downloadFile(url) + }) + .then(results => { + console.log("Stop updating conf.") + let contents = [] + let hostnames = [] + results.forEach(data => { + const parseRemoteResult = ____parseRemoteConf(data.body) + if (data.body) { + contents = contents.concat(parseRemoteResult.contents) + hostnames = hostnames.concat(parseRemoteResult.hostnames) + } + }); + contents = localConf.concat(contents) + hostnames = hostnameConf.concat(hostnames) + resolve({ contents, hostnames, results }) + }) + } else { + resolve({ contents: localConf, hostnames: hostnameConf, results: [] }) + } + }) +} + +function ____parseDevelopmentModeConf(conf) { + const localConf = ____removeAnnotation(____extractConf(__conf, "eval_local")) + const result = ____parseConf(localConf) + return result.obj +} + +function ____timeDiff(begin, end) { + return Math.ceil((end.getTime() - begin.getTime()) / 1000) +} + +function ____concurrentQueueLimit(list, limit, asyncHandle) { + let results = [] + const recursion = (arr) => { + return asyncHandle(arr.shift()) + .then((data) => { + results.push(data) + if (arr.length !== 0) return recursion(arr) + else return 'finish' + }) + }; + const listCopy = [].concat(list) + let asyncList = [] + if (list.length < limit) + limit = list.length + while (limit--) { + asyncList.push(recursion(listCopy)) + } + return new Promise((resolve) => { + Promise.all(asyncList).then(() => resolve(results)) + }); +} + +function ____downloadFile(url) { + return new Promise((resolve) => { + __tool.get(url, (error, response, body) => { + const filename = url.match(/[^\/]+$/)[0] + if (!error) { + const code = response.statusCode + if (code == 200) { + console.log(`updated successfully: ${url}`) + resolve({ url, code, body, message: `${__emojiUpdateSuccess}${filename} updated successfully` }) + } else { + console.log(`update failed ${response.statusCode}: ${url}`) + resolve({ url, code, body, message: `${__emojiUpdateFail}${filename} update failed` }) + } + } else { + console.log(`updated faile ${error}`) + resolve({ url, code: null, body: null, message: `${__emojiUpdateFail}${filename} update failed` }) + } + }) + }) +} + +function ____parseHostname(hostnames) { + if (hostnames.length > 0 && hostnames[0].length > 0) { + hostnames = hostnames[0].replace(/\s/g, "").split(",") + } + return hostnames +} + +function ____extractConf(conf, type) { + const rex = new RegExp("\\[" + type + "\\](.|\\n)*?(?=\\n($|\\[))", "g") + let result = rex.exec(conf) + if (result) { + result = result[0].split("\n") + result.shift() + } else { + result = [] + } + return result +} + +function ____parseRemoteConf(conf) { + const lines = conf.split("\n") + let newLines = [] + let hostnames = [] + for (let i = 0, len = lines.length; i < len; i++) { + const eval = /^(.+)\s+eval\s+(.+)$/ + const surge = /^http\s*-\s*(request|response)\s+(\S+)\s+(.+)$/ + const newSurge = /^\S+.js\s+=\s(.+)$/ + const quanx = /^(\S+)\s+url\s+script\s*-\s*(\S+)\s*-\s*(?:header|body)\s+(\S+)$/ + let line = lines[i].trim() + if (line.length > 0) { + if (/^#{4}/.test(line)) { + line = line.replace(/^#*/, "") + newLines.push(line) + } else if (/^(?!;|#|\/\/).*/.test(line)) { + if (eval.test(line) || surge.test(line) || newSurge.test(line)) { + newLines.push(line) + } + if (quanx.test(line)) { + const path = line.match(quanx)[3].trim() + if (/^https?:\/\/.+/.test(path)) { + newLines.push(line) + } + } + if (/^hostname\s*=\s*.+/.test(line)) { + hostnames = line.replace(/\s/g, "").replace(/hostname=/, "").split(",") + } + } + } + } + return { contents: newLines, hostnames } +} + +function ____parseConf(lines) { + let confMap = {} + let quanxConfContents = [] + let surgeConfContents = [] + for (let i = 0, len = lines.length; i < len; i++) { + let line = lines[i].trim() + if (line.length > 0 && line.substring(0, 2) != "//" && line.substring(0, 1) != "#") { + const eval = /^(.+)\s+eval\s+(.+)$/ + const surge = /^http\s*-\s*(request|response)\s+(\S+)\s+(.+)$/ + const newSurge = /^\S+.js\s+=\s(.+)$/ + const quanx = /^(\S+)\s+url\s+script\s*-\s*(\S+)\s+(\S+\.js)$/ + if (surge.test(line)) { + const result = line.match(surge) + // content + const requiresBody = ____surgeArg(result[3].trim()).requiresBody + // surgeConfContents.splice(i, 0, `${line.replace(____surgeArg(result[3].trim()).scriptPath, "eval_script.js")}`); + if (__evalPath.length > 0) { + const path = __evalPath + const fileName = path.match(/[^\/]+$/)[0] + surgeConfContents.push(`${fileName} = type=http-${result[1].trim()},${requiresBody ? `requires-body=${requiresBody},` : ""}pattern=${result[2].trim()},script-path=${path}`) + quanxConfContents.push(`${result[2].trim()} url script-${result[1].trim()}-${requiresBody == "1" ? "body" : "header"} ${path}`) + } else { + const path = ____surgeArg(result[3].trim()).scriptPath + const fileName = path.match(/[^\/]+$/)[0] + surgeConfContents.push(`${fileName} = type=http-${result[1].trim()},${requiresBody ? `requires-body=${requiresBody},` : ""}pattern=${result[2].trim()},script-path=${path}`) + quanxConfContents.push(`${result[2].trim()} url script-${result[1].trim()}-${requiresBody == "1" ? "body" : "header"} ${path}`) + } + // eval + line = `${result[1].trim()} ${result[2].trim()} eval ${____surgeArg(result[3].trim()).scriptPath}` + } + else if (quanx.test(line)) { + const result = line.match(quanx) + const type = result[2].split("-") + // content + let requires = 0 + if (type[0].trim() == "response") { + requires = 1 + } else { + if (type[1].trim() == "body") { + requires = 1 + } + } + // surgeConfContents.splice(i, 0, `http-${type[0].trim()} ${result[1].trim()} ${requires == 0 ? "" : `requires-body=${requires},`}script-path=eval_script.js`) + if (__evalPath.length > 0) { + const path = __evalPath + const fileName = path.match(/[^\/]+$/)[0] + surgeConfContents.push(`${fileName} = type=http-${type[0].trim()},${requires == 0 ? "" : `requires-body=${requires},`}pattern=${result[1].trim()},script-path=${path}`) + quanxConfContents.push(`${line.replace(result[3].trim(), path)}`) + } else { + const path = result[3].trim() + const fileName = path.match(/[^\/]+$/)[0] + surgeConfContents.push(`${fileName} = type=http-${type[0].trim()},${requires == 0 ? "" : `requires-body=${requires},`}pattern=${result[1].trim()},script-path=${path}`) + quanxConfContents.push(line) + } + // eval + line = `${type[0].trim()} ${result[1].trim()} eval ${result[3].trim()}` + } + else if (newSurge.test(line)) { + //content + const result = line.match(newSurge) + const surgeArg = ____surgeArg(result[1].trim()) + // surgeConfContents.splice(i, 0, `${surgeArg.type} ${surgeArg.pattern} ${surgeArg.requiresBody ? `requires-body=${surgeArg.requiresBody},` : ""}script-path=eval_script.js`) + if (__evalPath.length > 0) { + const path = __evalPath + const fileName = path.match(/[^\/]+$/)[0] + surgeConfContents.push(`${fileName} = ${result[1].replace(surgeArg.scriptPath, path)}`) + quanxConfContents.push(`${surgeArg.pattern} url script-${surgeArg.type.replace("http-", "")}-${(surgeArg.requiresBody && surgeArg.requiresBody == "1") ? "body" : "header"} ${path}`) + } else { + const path = surgeArg.scriptPath + surgeConfContents.push(line) + quanxConfContents.push(`${surgeArg.pattern} url script-${surgeArg.type.replace("http-", "")}-${(surgeArg.requiresBody && surgeArg.requiresBody == "1") ? "body" : "header"} ${path}`) + } + // eval + line = `${surgeArg.type.replace("http-", "")} ${surgeArg.pattern} eval ${surgeArg.scriptPath}` + } + if (eval.test(line)) { + const value = line.match(eval) + const remote = value[2].trim() + const match = ____parseMatch(value[1].trim()) + if (remote.length > 0 && match.length > 0) { + if (confMap.hasOwnProperty(remote)) { + confMap[remote] = confMap[remote].concat(match) + } else { + confMap[remote] = match + } + } else { + return { confMap: null, quanxConfContents, surgeConfContents, error: line } + } + } else { + return { confMap: null, quanxConfContents, surgeConfContents, error: line } + } + } + } + return { confMap, quanxConfContents, surgeConfContents, error: null } +} + +function ____parseMatch(match) { + let matchs = [] + const typeRegex = /(request|response)\s+\S+/g + const typeItems = match.match(typeRegex) + if (typeItems && typeItems.length > 0) { + match = match.replace(typeRegex, "") + } + const normalItems = match.match(/\S+/g) + const items = (typeItems ? typeItems : []).concat(normalItems ? normalItems : []) + for (let i = 0, len = items.length; i < len; i++) { + let item = items[i] + item = item.match(/\S+/g) + if (item.length > 1) { + matchs.push({ type: item[0], regular: item[1] }) + } else { + matchs.push({ type: "", regular: item[0] }) + } + } + return matchs +} + +function ____surgeArg(arg) { + let surgeArg = {} + const args = arg.split(",") + for (let i = 0, len = args.length; i < len; i++) { + const item = args[i].trim() + const path = /^script-path\s*=\s*(\S+)$/ + const requires = /^requires-body\s*=\s*(\S+)$/ + const pattern = /^pattern\s*=\s*(\S+)$/ + const type = /^type\s*=\s*(\S+)$/ + if (path.test(item)) { + surgeArg["scriptPath"] = item.match(path)[1] + } + if (requires.test(item)) { + surgeArg["requiresBody"] = item.match(requires)[1] + } + if (pattern.test(item)) { + surgeArg["pattern"] = item.match(pattern)[1] + } + if (type.test(item)) { + surgeArg["type"] = item.match(type)[1] + } + } + return surgeArg +} + +function ____removeAnnotation(lines) { + if (lines.length > 0) { + let i = lines.length; + while (i--) { + const line = lines[i].replace(/^\s*/, "") + if (line.length == 0 || line.substring(0, 2) == "//" || line.substring(0, 1) == "#") { + lines.splice(i, 1) + } + } + } + return lines +} + +function ____Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isJsBox = typeof $jsbox != "undefined" + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + _isTask = typeof $request == "undefined" + _isResponse = typeof $response != "undefined" + _isRequestBody = typeof $request != "undefined" && typeof $request.body != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isTask = _isTask + this.isResponse = _isResponse + this.isRequestBody = _isRequestBody + this.method = (() => { + if (!_isTask && (_isSurge || _isQuanX)) { + return $request.method + } + })() + this.scriptType = (() => { + if (_isResponse) { + return "response" + } else { + return "request" + } + })() + this.done = (obj) => { + if (_isQuanX) $done(obj) + if (_isSurge) $done(obj) + if (_node) console.log("script done."); + } + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + if (_isJsBox) { + const push = require("push"); + push.schedule({ title: title, body: `${subtitle}${subtitle.length > 0 ? "\n" : ""}${message}` }); + } + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + if (_node) console.log(`write success: ${key}`); + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + if (_node) console.log(`read success: ${key}`); + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.put = (options, callback) => { + if (_isQuanX) $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + if (_isSurge) $httpClient.put(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} + +function ____Base64() { + // private property + _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + // public method for encoding + this.encode = function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + input = _utf8_encode(input); + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + } + return output; + } + // public method for decoding + this.decode = function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + while (i < input.length) { + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + output = _utf8_decode(output); + return output; + } + // private method for UTF-8 encoding + _utf8_encode = function (string) { + string = string.replace(/\r\n/g, "\n"); + var utftext = ""; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + return utftext; + } + // private method for UTF-8 decoding + _utf8_decode = function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + while (i < utftext.length) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if ((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} + +Date.prototype.Format = function (fmt) { + var o = { + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "H+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + "S": this.getMilliseconds() + }; + if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); + for (var k in o) + if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + return fmt; +} diff --git a/eval_script/qx.txt b/eval_script/qx.txt new file mode 100644 index 000000000..cd8fe015f --- /dev/null +++ b/eval_script/qx.txt @@ -0,0 +1,327 @@ +hostname = api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios.prod.ftl.netflix.com,bea.sportq.com,api.meiyan.com,api.gao1gps.cn,avoscloud.com,app.flashdown365.com,m.samh.xndm.tech,mob2015.kekenet.com,vipapi.jxedt.com,api.interpreter.caiyunai.com,pocketlists.com,book.haitunwallet.com,mubu.com,app.xunjiepdf.com,miaow.yiyongcad.com,api.lennou.com,api.gkocr.com,vira.llsapp.com,commerce-.*api.faceu.mobi,commerce-api.faceu.mobi,pan.baidu.com,api.revenuecat.com,api.rr.tv,editorapi.115.com,api.lakecoloring.com,ctrl.playcvn.com,dict.eudic.net,m.client.10010.com,api.wakamoment.ga,*.bh3.com,api.diyidan.net,api.flexibits.com,api.jiaonizuocai.com,api.sololearn.com,tncj.hortorgames.com,bkcd.b-cdn.net,souhu.mett.me,ayk.tmdidi.com,m.pearkin.com,www.baidu.com2.club,claritywallpaper.com,bookapi.ihuman.com,rest.zhibo.tv,note.youdao.com,billing.peakcloud.org,api.ithome.com,www.xmind.cn,*.arten.cn,api.weiqire.com,api.shimo.im,pay.wecut.com,*.videostarapp.com,app.api.versa-ai.com,*.bjxkhc.com,api.591master.com,jdytv.cn,user.shywck.com,*.xunjie*.com,api.psy-1.com,snailsleep.net,mp.weixin.qq.com,api.bilibili.com,app.bilibili.com,*.zhihu.com,aweme*.snssdk.com,*.kuwo.cn,vip1.kuwo.cn,*.xiaoxiao*.com,api*.tiktokv.com,*.musical.ly,*.amemv.com,p.du.163.com,getuserinfo.321mh.com,getuserinfo-globalapi.zymk.cn,ios.fuliapps.com,vsco.co,api.vnision.com,*.my10api.com,sp.kaola.com,r.inews.qq.com,apple.fuliapps.com,newdrugs.dxy.cn,app101.avictown.cc,api.hlo.xyz,api.ijo.xyz,www.luqijianggushi.com,account.wps.*,u.kanghuayun.com,api.gyrosco.pe,api1.dobenge.cn,api.mvmtv.com,mitaoapp.yeduapp.com,origin-prod-phoenix.jibjab.com,www.3ivf.com,pay.guoing.com,api.termius.com,api.bjxkhc.com,viva.v21xy.com,biz.caiyunapp.com,api.gotokeep.com,ap*.intsig.net,mp.bybutter.com,api.vuevideo.net,api.picsart.c*,api.meiease.c*,splice.oracle.*.com,api.gamer.com.tw,ios.xiangjiaoapps.com,apple.xiangjiaoapps.com,*.lagoapps.com,*.xiangxiangapps.com,avatar-nct.nixcdn.com,spclient.wg.spotify.com,oa.zalo.me,api.unfold.app,viva-asia1.vvbrd.com,graph.nhaccuatui.com,api.memrise.com,api.sync.me,pool.elsanow.io,lambda.us-east-1.amazonaws.com,api.mondlylanguages.com,api.busuu.com,owa.videoshowiosglobalserver.com:0,accounts.elevateapp.net,purchases.ws.pho.to,api-intl.mr.meitu.com,bmall.camera360.com,api.tv.zing.vn,api.calm.com,www.calm.com,api.global.mp3.zing.vn,apimboom2.globaldelight.net,photos.adobe.io,license.pdfexpert.com,subs.platforms.team,apic.musixmatch.com,api.getmimo.com,engbright.com,api.lingokids.com,www.peacefulsoundsapp.com,duolingo-leaderboards-prod.duolingo.com,mobile-api.adguard.com,api.blinkist.com,api-kinemaster-assetstore.*,api.pushover.net,api.overhq.com,receipt-validator.herewetest.com,lcs-mobile-cops.adobe.io,education.github.com,backend.getdrafts.com,ssl-api.itranslateapp.com,sk.ulysses.app,dayone.me,license.enpass.io,*.grammarly.com,api.keepkeep.com,planner5d.com,secure.istreamer.com,www.api.monkeyuni.net,api.textnow.me + +^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) url script-response-body eval_script.js + +^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/photos/pic_recommend_status|video/tiny_stream_video_list) url script-response-body eval_script.js + +^https?:\/\/mp\.weixin\.qq\.com\/mp\/getappmsgad url script-response-body eval_script.js + +^https://api.zhihu.com/moments/recommend url script-response-body eval_script.js + +^https://api.zhihu.com/topstory/recommend url script-response-body eval_script.js + +https://api.zhihu.com/v4/questions url script-response-body eval_script.js + +^https://api.zhihu.com/people/ url script-response-body eval_script.js + +^https://api.zhihu.com/market/header url script-response-body eval_script.js + +https://app.bilibili.com/x/v2/space\?access_key url script-response-body eval_script.js + +https://app.bilibili.com/x/resource/show/tab\?access_key url script-response-body eval_script.js + +https://app.bilibili.com/x/v2/feed/index\?access_key url script-response-body eval_script.js + +https://app.bilibili.com/x/v2/view\?access_key url script-response-body eval_script.js + +https://api.bilibili.com/x/v2/reply/main\?access_key url script-response-body eval_script.js + +https://api.live.bilibili.com/xlive/app-room/v1/index/getInfoByRoom\?access_key url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/aweme\/post\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/feed\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/follow\/feed\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/nearby\/feed\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/search\/item\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/general\/search\/single\/ url script-response-body eval_script.js + +^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/hot\/search\/video\/list\/ url script-response-body eval_script.js + +^https?:\/\/vip1\.kuwo\.cn\/(vip\/v2\/user\/vip|vip\/spi/mservice) url script-response-body eval_script.js + +https:\/\/.*\/getGlobalData url script-response-body eval_script.js + +^http(s)://api.bjxkhc.com/index.php/app/ios/(vod/show|(user|vod|topic|type)/index) url script-response-body eval_script.js + +^https?:\/\/p\.du\.163\.com\/gain\/readtime\/info\.json url script-response-body eval_script.js + +^https?:\/\/getuserinfo\.321mh\.com\/app_api\/v5\/getuserinfo\/ url script-response-body eval_script.js + +^https://getuserinfo-globalapi.zymk.cn/app_api/v5/(getuserinfo|coin_account|getuserinfo_ticket|getcomicinfo)/ url script-response-body eval_script.js + +https:\/\/ap(p|i)\.bilibili\.com\/((pgc\/player\/api\/playurl)|(x\/v2\/account\/myinfo\?)|(x\/v2\/account/mine\?)) url script-response-body eval_script.js + +^https?:\/\/vsco\.co\/api\/subscriptions\/2.1\/user-subscriptions\/ url script-response-body eval_script.js + +^https?:\/\/api\.vnision\.com\/v1\/(users\/|banners) url script-response-body eval_script.js + +^https?:\/\/.+\.(my10api|(.*91.*))\.(com|tips|app|xyz)(:\d{2,5})?\/api.php$ url script-response-body eval_script.js + +^https://sp\.kaola\.com/api/openad$ url script-response-body eval_script.js + +^https://r\.inews\.qq.com\/get(QQNewsUnreadList|RecommendList) url script-response-body eval_script.js + +^https?:\/\/.*\.(lago|fuli|xiang(jiao|xiang))apps\.com\/(ucp\/index|getGlobalData|.+\/reqplay\/) url script-response-body eval_script.js + +^https?:\/\/(i|newdrugs)\.dxy\.cn\/(snsapi\/username\/|app\/user\/(pro\/stat\?|init\?timestamp=)) url script-response-body eval_script.js + +^https?:\/\/(.+)\.(\w{2,3})(:?\d*)\/(api\/public\/\?service=Live\.checkLive$|public\/\/\?service=Live\.roomCharge$|lg\/video\/loadVideoFees\.do$) url script-response-body eval_script.js + +^https:\/\/www\.luqijianggushi\.com\/api\/v2\/user\/get url script-response-body eval_script.js + +^https://account.wps.*/api/users/ url script-response-body eval_script.js + +^https:\/\/api\.gyrosco\.pe\/v1\/account\/$ url script-response-body eval_script.js + +^https:\/\/api1\.dobenge\.cn\/api\/user\/getuserinfo url script-response-body eval_script.js + +^https:\/\/api\.mvmtv\.com\/index\.php.*(c=user.*a=info|a=addr.*vid=.*) url script-response-body eval_script.js + +^https:\/\/origin-prod-phoenix\.jibjab\.com\/v1\/user url script-response-body eval_script.js + +https:\/\/api\.termius\.com\/api\/v3\/bulk\/account\/ url script-response-body eval_script.js + +^https:\/\/viva\.v21xy\.com\/api\/rest\/u\/vip url script-response-body eval_script.js + +^https:\/\/biz\.caiyunapp\.com\/v2\/user\?app_name\=weather url script-response-body eval_script.js + +^https:\/\/api\.gotokeep\.com\/(.+\/subject|.+\/dynamic) url script-response-body eval_script.js + +^https:\/\/(api|api-cs)\.intsig\.net\/purchase\/cs\/query_property\? url script-response-body eval_script.js + +^https:\/\/api\.vuevideo\.net\/api\/v1\/(users\/.+\/profile|subtitle\/prepare) url script-response-body eval_script.js + +^https?:\/\/mp\.bybutter\.com\/mood\/(official-templates|privileges) url script-response-body eval_script.js + +^https:\/\/api\.(picsart|meiease)\.c(n|om)\/users\/show\/me\.json url script-response-body eval_script.js + +^https:\/\/splice\.oracle\.\w+\.com\/devices\/me url script-response-body eval_script.js + +https:\/\/pan\.baidu\.com\/rest\/2\.0\/membership\/user url script-response-body eval_script.js + +^https?://.*\.snssdk\.com/bds/(feed/stream|comment/cell_reply|cell/cell_comment|cell/detail|ward/list|user/favorite|user/cell_coment|user/cell_userfeed|user/publish_list) url script-response-body eval_script.js + +^https:\/\/viva-asia1\.vvbrd\.com\/api\/rest\/u\/vip* url script-response-body eval_script.js + +^https:\/\/api\.unfold\.app\/v1\/ios\/receipts$ url script-response-body eval_script.js + +^https:\/\/graph\.nhaccuatui\.com\/.*\/users\/info* url script-response-body eval_script.js + +^https:\/\/api\.memrise\.com\/.+\/(me\/$|dashboard\/$|leaderboards\/following\/) url script-response-body eval_script.js + +^https:\/\/origin-prod-phoenix\.jibjab\.com\/v1\/user$ url script-response-body eval_script.js + +^https:\/\/buy\.itunes\.apple\.com\/verifyReceipt$ url script-response-body eval_script.js + +^https:\/\/api\.sync\.me\/api\/purchases\/(report_purchases|get_purchases) url script-response-body eval_script.js + +^https:\/\/pool\.elsanow\.io\/user\/api\/v1\/purchase$ url script-response-body eval_script.js + +^https:\/\/lambda\.us-east-1\.amazonaws\.com/.*/functions\/prod-4-syncPurchases\/invocations$ url script-response-body eval_script.js + +^https:\/\/api\.mondlylanguages\.com\/v1\/ios\/user\/sync$ url script-response-body eval_script.js + +^https:\/\/api\.busuu\.com\/users\/me* url script-response-body eval_script.js + +^https:\/\/owa\.videoshowiosglobalserver\.com\/.*\/iosPayClient url script-response-body eval_script.js + +^https:\/\/accounts\.elevateapp\.net\/api\/users\?user%5Bauthentication_token* url script-response-body eval_script.js + +^https:\/\/api-intl\.mr\.meitu\.com/.*/subs_offer_elg$ url script-response-body eval_script.js + +^https:\/\/bmall\.camera360\.com\/api\/(iap\/check-receipt$|mix\/getinfo$) url script-response-body eval_script.js + +^https?:\/\/api\.tv\.zing\.vn\/.*/user* url script-response-body eval_script.js + +^https:\/\/api\.calm\.com\/me$ url script-response-body eval_script.js + +^https:\/\/photos\.adobe\.io\/v2\/accounts* url script-response-body eval_script.js + +^https:\/\/license\.pdfexpert\.com\/api\/1\.0\/pdfexpert6\/subscription\/(refresh$|check$) url script-response-body eval_script.js + +^https:\/\/subs\.platforms\.team\/.+\/apple\/verify$ url script-response-body eval_script.js + +^https:\/\/apic\.musixmatch\.com\/ws\/.*\/config\.get url script-response-body eval_script.js + +^https:\/\/api\.getmimo\.com\/v1\/subscriptions$ url script-response-body eval_script.js + +^https:\/\/api\.revenuecat\.com\/.+\/(receipts$|subscribers\/[a-zA-Z0-9_-]*$) url script-response-body eval_script.js + +^https:\/\/api\.lingokids\.com\/v1\/renovate_session$ url script-response-body eval_script.js + +^https:\/\/www\.peacefulsoundsapp\.com\/api\/v1\/init$ url script-response-body eval_script.js + +^https:\/\/duolingo-leaderboards-prod\.duolingo\.com\/leaderboards* url script-response-body eval_script.js + +^https:\/\/commerce-i18n-api\.faceu\.mobi\/commerce\/v1\/subscription\/user_info$ url script-response-body eval_script.js + +^https:\/\/api\.global\.mp3\.zing\.vn\/1\.0\/getUserInfo\?data=* url script-response-body eval_script.js + +^https:\/\/api\.blinkist\.com\/v4\/(me$|me.json$|me\/access$) url script-response-body eval_script.js + +^https:\/\/api\.sololearn\.com\/(Profile\/GetProfile$|authenticateDevice$) url script-response-body eval_script.js + +^https:\/\/api-kinemaster-assetstore\.(nexstreaming|kinemasters)\.com\/.*\/product\/verifyReceipt$ url script-response-body eval_script.js + +^https:\/\/api\.pushover\.net\/1\/messages\.json* url script-response-body eval_script.js + +^https:\/\/api\.overhq\.com\/(user\/token\/refresh$|subscription\/verifyReceipt$) url script-response-body eval_script.js + +^https:\/\/receipt-validator\.herewetest\.com\/apple\/verifyTransaction$ url script-response-body eval_script.js + +^https:\/\/license\.pdfexpert\.com\/api\/.*\/documents\/subscription\/(refresh$|check$) url script-response-body eval_script.js + +^https:\/\/education\.github\.com\/api\/user$ url script-response-body eval_script.js + +^https:\/\/backend\.getdrafts\.com\/api\/.*\/verification* url script-response-body eval_script.js + +^https:\/\/lcs-mobile-cops\.adobe\.io\/mobile_profile url script-response-body eval_script.js + +^https:\/\/ssl-api\.itranslateapp\.com\/.*\/subscriptions\/.*\/ios$ url script-response-body eval_script.js + +^https:\/\/sk\.ulysses\.app\/api\/v1\/itunes_receipt_verify$ url script-response-body eval_script.js + +^https:\/\/dayone\.me\/api\/(users|v2\/users\/account-status)$ url script-response-body eval_script.js + +^https:\/\/api-production\.endel\.io\/.*\/user$ url script-response-body eval_script.js + +^https:\/\/api\.shred\.app\/verifyReceipt$ url script-response-body eval_script.js + +^https:\/\/subscription\.grammarly\.com\/api\/v1$ url script-response-body eval_script.js + +^https:\/\/planner5d\.com\/api\/sets url script-response-body eval_script.js + +^https:\/\/secure\.istreamer\.com\/backend$ url script-response-body eval_script.js + +^https:\/\/www\.api\.monkeyuni\.net\/api\/.+\/mobile\/account\/load-update url script-response-body eval_script.js + +^https:\/\/api\.textnow\.me\/api2.0\/users\/.* url script-response-body eval_script.js + +^https:\/\/vipapi\.jxedt\.com\/vip\/check url script-response-body eval_script.js + +^https:\/\/api\.interpreter\.caiyunai\.com\/v1\/user url script-response-body eval_script.js + +^https:\/\/buy\.itunes\.apple\.com\/verifyReceipt url script-response-body eval_script.js + +^https:\/\/pocketlists\.com\/api\/v1\/pocketlists.me.get url script-response-body eval_script.js + +https:\/\/book\.haitunwallet\.com\/app\/vip\/status url script-response-body eval_script.js + +https:\/\/mubu\.com\/api\/app\/user\/info url script-response-body eval_script.js + +^https:\/\/app\.xunjiepdf\.com\/api\/v4\/virtualactregister url script-response-body eval_script.js + +http:\/\/miaow\.yiyongcad\.com\/api\/v4\/memprofile url script-response-body eval_script.js + +https:\/\/api\.lennou\.com\/user\/info url script-response-body eval_script.js + +^https:\/\/api\.gkocr\.com\/api\/userlogin1.php url script-response-body eval_script.js + +^https?:\/\/vira\.llsapp\.com\/api\/v2\/readings\/(accessible|limitation) url script-response-body eval_script.js + +^https:\/\/api\.rr\.tv(\/user\/privilege\/list|\/ad\/getAll|\/rrtv-video\/v4plus\/season\/detail) url script-response-body eval_script.js + +^https?:\/\/pan\.baidu\.com\/s\/ url script-response-body eval_script.js + +^https:\/\/api\.revenuecat\.com\/v1\/(receipts|\d{1,})$ url script-response-body eval_script.js + +https://(commerce-.*api|pay).(faceu|wecut).(com|mobi)/(commerce|apple)/(iosAppVerifyReceipt.php|v1/subscription/user_info) url script-response-body eval_script.js + +^https?:\/\/vip1\.kuwo\.cn\/(vip\/v2\/theme) url script-response-body eval_script.js + +^http:\/\/115\.com\/lx.*$ url script-response-body eval_script.js + +^https:\/\/api\.lakecoloring\.com\/v1\/receipt url script-response-body eval_script.js + +^http://ctrl.playcvn.com/app/(init|ads) url script-response-body eval_script.js + +^https:\/\/dict\.eudic\.net\/jingting\/GetThisChapterTaskStatus? url script-response-body eval_script.js + +^https?://m.client.10010.com/uniAdmsInterface/getHomePageAd url script-response-body eval_script.js + +^https:\/\/api\.wakamoment\.ga\/init\?platform\=ios url script-response-body eval_script.js + +^https:\/\/api\.diyidan\.net\/v0\.3\/(user\/personal_homepage|vip_user\/info|tv_series\/index\?appChanne) url script-response-body eval_script.js + +^https:\/\/api\.flexibits\.com\/v1\/(auth|account)\/(device|details|appstore-receipt)\/$ url script-response-body eval_script.js + +https?:\/\/api\.jiaonizuocai\.com url script-response-body eval_script.js + +https:\/\/api\.sololearn\.com\/(authenticateDevice|challenge\/GetContestFeed|Profile\/GetProfile)$ url script-response-body eval_script.js + +^https://tncj.hortorgames.com/chicken/fight/(answer|findQuiz) url script-response-body eval_script.js + +^https:\/\/(www\.baidu.com2\.club|ayk\.tmdidi\.com|m\.pearkin\.com|souhu\.mett\.me|bkcd\.b-cdn\.net)\/(api\/movie\/WatchMovie|api\/Account\/CheckVip|api\/account\/IndexDetail) url script-response-body eval_script.js + +^https:\/\/claritywallpaper\.com\/clarity\/api\/(userInfo|special\/queryByCatalogAll) url script-response-body eval_script.js + +https:\/\/bookapi\.ihuman\.com\/(v1\/get\_user\_info|v1\/get\_purchase\_list) url script-response-body eval_script.js + +http:\/\/rest\.zhibo\.tv\/room\/get\-room\-info\-v430 url script-response-body eval_script.js + +https://note.youdao.com/yws/(mapi/payment|api/self) url script-response-body eval_script.js + +^https:\/\/billing\.peakcloud\.org\/billing\/2\/user\/me? url script-response-body eval_script.js + +^https?:\/\/api\.ithome\.com\/json\/slide\/index url script-response-body eval_script.js + +^https?:\/\/api\.ithome\.com\/json\/(newslist|listpage)\/news url script-response-body eval_script.js + +https:\/\/www\.xmind\.cn\/\_res\/devices url script-response-body eval_script.js + +^http?:\/\/.*\.arten.cn/login/login url script-response-body eval_script.js + +^https://api.weiqire.com/api3/(visitor/|user/unlockCharpter) url script-response-body eval_script.js + +https://api.shimo.im/users/ url script-response-body eval_script.js + +^https?:\/\/.*\.videostarapp\.com\/scripts\/subsNew\.php url script-response-body eval_script.js + +https:\/\/api\.revenuecat\.com\/v1\/(subscribers|receipts) url script-response-body eval_script.js + +https://app.api.versa-ai.com/pay/order/iap/check url script-response-body eval_script.js + +^https\:\/\/hjapi\.bjxkhc\.com\/v2d2\/users\/.*\/member url script-response-body eval_script.js + +http:\/\/api\.591master\.com\:8081\/(1.0|3.6.8)\/ui(forum|common)\/(downloadwallpaper|getuser) url script-response-body eval_script.js + +^http\:\/\/jdytv\.cn\/login\/login\/veifys url script-response-body eval_script.js + +http://user.shywck.com/user/userinfo url script-response-body eval_script.js + +^https?:\/\/.*\.xunjie.*\.com\/api\/v\d\/* url script-response-body eval_script.js + +^https:\/\/api\.psy-1\.com\/cosleep\/user\/info url script-response-body eval_script.js + +^https:\/\/snailsleep\.net\/snail\/v1\/profile\/get url script-response-body eval_script.js + +^https:\/\/mob2015\.kekenet\.com\/keke\/mobile\/index\.php url script-response-body eval_script.js + +^https:\/\/m\.samh\.xndm\.tech\/userapi\/info\/v1\/getuserinfo url script-response-body eval_script.js + +^http\:\/\/app\.flashdown365\.com\/ios\/login url script-response-body eval_script.js + +^https:\/\/avoscloud\.com\/1\.1\/users\/ url script-response-body eval_script.js + +^https\:\/\/api\.gao1gps\.cn\/v1\/user\/info url script-response-body eval_script.js + +^https:\/\/api\.meiyan\.com\/iap\/verify\.json url script-response-body eval_script.js + +^https:\/\/bea\.sportq\.com\/SFitWeb\/sfit\/getUserBaseInfo url script-response-body eval_script.js + +^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig) url script-response-body eval_script.js + +^http://.+/amdc/mobileDispatch url script-request-body eval_script.js + +^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail url script-response-body eval_script.js + +^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-request-header eval_script.js + +^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-response-body eval_script.js + +^https?://ios\.prod\.ftl\.netflix\.com/iosui/warmer/.+type=show-ath url script-response-body eval_script.js \ No newline at end of file diff --git a/eval_script/qx_script.txt b/eval_script/qx_script.txt new file mode 100644 index 000000000..a7bec5f8e --- /dev/null +++ b/eval_script/qx_script.txt @@ -0,0 +1,15 @@ +hostname = api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios.prod.ftl.netflix.com + +^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-request-header eval_script.js + +^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-response-body eval_script.js + +^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig) url script-response-body eval_script.js + +^http://.+/amdc/mobileDispatch url script-request-body eval_script.js + +^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail url script-response-body eval_script.js + +^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) url script-response-body eval_script.js + +^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/photos/pic_recommend_status|video/tiny_stream_video_list) url script-response-body eval_script.js \ No newline at end of file diff --git a/eval_script/sg.sgmodule b/eval_script/sg.sgmodule new file mode 100644 index 000000000..ba1094557 --- /dev/null +++ b/eval_script/sg.sgmodule @@ -0,0 +1,331 @@ +#!name=eval_script.js module + +[MITM] +hostname = %INSERT% api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios.prod.ftl.netflix.com,bea.sportq.com,api.meiyan.com,api.gao1gps.cn,avoscloud.com,app.flashdown365.com,m.samh.xndm.tech,mob2015.kekenet.com,vipapi.jxedt.com,api.interpreter.caiyunai.com,pocketlists.com,book.haitunwallet.com,mubu.com,app.xunjiepdf.com,miaow.yiyongcad.com,api.lennou.com,api.gkocr.com,vira.llsapp.com,commerce-.*api.faceu.mobi,commerce-api.faceu.mobi,pan.baidu.com,api.revenuecat.com,api.rr.tv,editorapi.115.com,api.lakecoloring.com,ctrl.playcvn.com,dict.eudic.net,m.client.10010.com,api.wakamoment.ga,*.bh3.com,api.diyidan.net,api.flexibits.com,api.jiaonizuocai.com,api.sololearn.com,tncj.hortorgames.com,bkcd.b-cdn.net,souhu.mett.me,ayk.tmdidi.com,m.pearkin.com,www.baidu.com2.club,claritywallpaper.com,bookapi.ihuman.com,rest.zhibo.tv,note.youdao.com,billing.peakcloud.org,api.ithome.com,www.xmind.cn,*.arten.cn,api.weiqire.com,api.shimo.im,pay.wecut.com,*.videostarapp.com,app.api.versa-ai.com,*.bjxkhc.com,api.591master.com,jdytv.cn,user.shywck.com,*.xunjie*.com,api.psy-1.com,snailsleep.net,mp.weixin.qq.com,api.bilibili.com,app.bilibili.com,*.zhihu.com,aweme*.snssdk.com,*.kuwo.cn,vip1.kuwo.cn,*.xiaoxiao*.com,api*.tiktokv.com,*.musical.ly,*.amemv.com,p.du.163.com,getuserinfo.321mh.com,getuserinfo-globalapi.zymk.cn,ios.fuliapps.com,vsco.co,api.vnision.com,*.my10api.com,sp.kaola.com,r.inews.qq.com,apple.fuliapps.com,newdrugs.dxy.cn,app101.avictown.cc,api.hlo.xyz,api.ijo.xyz,www.luqijianggushi.com,account.wps.*,u.kanghuayun.com,api.gyrosco.pe,api1.dobenge.cn,api.mvmtv.com,mitaoapp.yeduapp.com,origin-prod-phoenix.jibjab.com,www.3ivf.com,pay.guoing.com,api.termius.com,api.bjxkhc.com,viva.v21xy.com,biz.caiyunapp.com,api.gotokeep.com,ap*.intsig.net,mp.bybutter.com,api.vuevideo.net,api.picsart.c*,api.meiease.c*,splice.oracle.*.com,api.gamer.com.tw,ios.xiangjiaoapps.com,apple.xiangjiaoapps.com,*.lagoapps.com,*.xiangxiangapps.com,avatar-nct.nixcdn.com,spclient.wg.spotify.com,oa.zalo.me,api.unfold.app,viva-asia1.vvbrd.com,graph.nhaccuatui.com,api.memrise.com,api.sync.me,pool.elsanow.io,lambda.us-east-1.amazonaws.com,api.mondlylanguages.com,api.busuu.com,owa.videoshowiosglobalserver.com:0,accounts.elevateapp.net,purchases.ws.pho.to,api-intl.mr.meitu.com,bmall.camera360.com,api.tv.zing.vn,api.calm.com,www.calm.com,api.global.mp3.zing.vn,apimboom2.globaldelight.net,photos.adobe.io,license.pdfexpert.com,subs.platforms.team,apic.musixmatch.com,api.getmimo.com,engbright.com,api.lingokids.com,www.peacefulsoundsapp.com,duolingo-leaderboards-prod.duolingo.com,mobile-api.adguard.com,api.blinkist.com,api-kinemaster-assetstore.*,api.pushover.net,api.overhq.com,receipt-validator.herewetest.com,lcs-mobile-cops.adobe.io,education.github.com,backend.getdrafts.com,ssl-api.itranslateapp.com,sk.ulysses.app,dayone.me,license.enpass.io,*.grammarly.com,api.keepkeep.com,planner5d.com,secure.istreamer.com,www.api.monkeyuni.net,api.textnow.me + +[Script] +eval_script.js = type=http-response,requires-body=1,pattern=^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/photos/pic_recommend_status|video/tiny_stream_video_list),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/mp\.weixin\.qq\.com\/mp\/getappmsgad,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://api.zhihu.com/moments/recommend,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://api.zhihu.com/topstory/recommend,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://api.zhihu.com/v4/questions,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://api.zhihu.com/people/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://api.zhihu.com/market/header,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://app.bilibili.com/x/v2/space\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://app.bilibili.com/x/resource/show/tab\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://app.bilibili.com/x/v2/feed/index\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://app.bilibili.com/x/v2/view\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://api.bilibili.com/x/v2/reply/main\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://api.live.bilibili.com/xlive/app-room/v1/index/getInfoByRoom\?access_key,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/aweme\/post\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/feed\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/follow\/feed\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/nearby\/feed\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/search\/item\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/general\/search\/single\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?+:\/\/[\w-]++\.amemv\.com\/aweme\/v\d\/hot\/search\/video\/list\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/vip1\.kuwo\.cn\/(vip\/v2\/user\/vip|vip\/spi/mservice),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/.*\/getGlobalData,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http(s)://api.bjxkhc.com/index.php/app/ios/(vod/show|(user|vod|topic|type)/index),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/p\.du\.163\.com\/gain\/readtime\/info\.json,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/getuserinfo\.321mh\.com\/app_api\/v5\/getuserinfo\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://getuserinfo-globalapi.zymk.cn/app_api/v5/(getuserinfo|coin_account|getuserinfo_ticket|getcomicinfo)/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/ap(p|i)\.bilibili\.com\/((pgc\/player\/api\/playurl)|(x\/v2\/account\/myinfo\?)|(x\/v2\/account/mine\?)),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/vsco\.co\/api\/subscriptions\/2.1\/user-subscriptions\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/api\.vnision\.com\/v1\/(users\/|banners),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/.+\.(my10api|(.*91.*))\.(com|tips|app|xyz)(:\d{2,5})?\/api.php$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://sp\.kaola\.com/api/openad$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://r\.inews\.qq.com\/get(QQNewsUnreadList|RecommendList),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/.*\.(lago|fuli|xiang(jiao|xiang))apps\.com\/(ucp\/index|getGlobalData|.+\/reqplay\/),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/(i|newdrugs)\.dxy\.cn\/(snsapi\/username\/|app\/user\/(pro\/stat\?|init\?timestamp=)),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/(.+)\.(\w{2,3})(:?\d*)\/(api\/public\/\?service=Live\.checkLive$|public\/\/\?service=Live\.roomCharge$|lg\/video\/loadVideoFees\.do$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/www\.luqijianggushi\.com\/api\/v2\/user\/get,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://account.wps.*/api/users/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.gyrosco\.pe\/v1\/account\/$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api1\.dobenge\.cn\/api\/user\/getuserinfo,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.mvmtv\.com\/index\.php.*(c=user.*a=info|a=addr.*vid=.*),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/origin-prod-phoenix\.jibjab\.com\/v1\/user,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/api\.termius\.com\/api\/v3\/bulk\/account\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/viva\.v21xy\.com\/api\/rest\/u\/vip,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/biz\.caiyunapp\.com\/v2\/user\?app_name\=weather,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.gotokeep\.com\/(.+\/subject|.+\/dynamic),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/(api|api-cs)\.intsig\.net\/purchase\/cs\/query_property\?,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.vuevideo\.net\/api\/v1\/(users\/.+\/profile|subtitle\/prepare),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/mp\.bybutter\.com\/mood\/(official-templates|privileges),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.(picsart|meiease)\.c(n|om)\/users\/show\/me\.json,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/splice\.oracle\.\w+\.com\/devices\/me,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/pan\.baidu\.com\/rest\/2\.0\/membership\/user,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://.*\.snssdk\.com/bds/(feed/stream|comment/cell_reply|cell/cell_comment|cell/detail|ward/list|user/favorite|user/cell_coment|user/cell_userfeed|user/publish_list),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/viva-asia1\.vvbrd\.com\/api\/rest\/u\/vip*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.unfold\.app\/v1\/ios\/receipts$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/graph\.nhaccuatui\.com\/.*\/users\/info*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.memrise\.com\/.+\/(me\/$|dashboard\/$|leaderboards\/following\/),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/origin-prod-phoenix\.jibjab\.com\/v1\/user$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/buy\.itunes\.apple\.com\/verifyReceipt$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.sync\.me\/api\/purchases\/(report_purchases|get_purchases),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/pool\.elsanow\.io\/user\/api\/v1\/purchase$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/lambda\.us-east-1\.amazonaws\.com/.*/functions\/prod-4-syncPurchases\/invocations$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.mondlylanguages\.com\/v1\/ios\/user\/sync$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.busuu\.com\/users\/me*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/owa\.videoshowiosglobalserver\.com\/.*\/iosPayClient,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/accounts\.elevateapp\.net\/api\/users\?user%5Bauthentication_token*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api-intl\.mr\.meitu\.com/.*/subs_offer_elg$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/bmall\.camera360\.com\/api\/(iap\/check-receipt$|mix\/getinfo$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/api\.tv\.zing\.vn\/.*/user*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.calm\.com\/me$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/photos\.adobe\.io\/v2\/accounts*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/license\.pdfexpert\.com\/api\/1\.0\/pdfexpert6\/subscription\/(refresh$|check$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/subs\.platforms\.team\/.+\/apple\/verify$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/apic\.musixmatch\.com\/ws\/.*\/config\.get,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.getmimo\.com\/v1\/subscriptions$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.revenuecat\.com\/.+\/(receipts$|subscribers\/[a-zA-Z0-9_-]*$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.lingokids\.com\/v1\/renovate_session$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/www\.peacefulsoundsapp\.com\/api\/v1\/init$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/duolingo-leaderboards-prod\.duolingo\.com\/leaderboards*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/commerce-i18n-api\.faceu\.mobi\/commerce\/v1\/subscription\/user_info$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.global\.mp3\.zing\.vn\/1\.0\/getUserInfo\?data=*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.blinkist\.com\/v4\/(me$|me.json$|me\/access$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.sololearn\.com\/(Profile\/GetProfile$|authenticateDevice$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api-kinemaster-assetstore\.(nexstreaming|kinemasters)\.com\/.*\/product\/verifyReceipt$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.pushover\.net\/1\/messages\.json*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.overhq\.com\/(user\/token\/refresh$|subscription\/verifyReceipt$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/receipt-validator\.herewetest\.com\/apple\/verifyTransaction$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/license\.pdfexpert\.com\/api\/.*\/documents\/subscription\/(refresh$|check$),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/education\.github\.com\/api\/user$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/backend\.getdrafts\.com\/api\/.*\/verification*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/lcs-mobile-cops\.adobe\.io\/mobile_profile,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/ssl-api\.itranslateapp\.com\/.*\/subscriptions\/.*\/ios$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/sk\.ulysses\.app\/api\/v1\/itunes_receipt_verify$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/dayone\.me\/api\/(users|v2\/users\/account-status)$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api-production\.endel\.io\/.*\/user$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.shred\.app\/verifyReceipt$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/subscription\.grammarly\.com\/api\/v1$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/planner5d\.com\/api\/sets,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/secure\.istreamer\.com\/backend$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/www\.api\.monkeyuni\.net\/api\/.+\/mobile\/account\/load-update,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.textnow\.me\/api2.0\/users\/.*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/vipapi\.jxedt\.com\/vip\/check,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.interpreter\.caiyunai\.com\/v1\/user,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/buy\.itunes\.apple\.com\/verifyReceipt,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/pocketlists\.com\/api\/v1\/pocketlists.me.get,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/book\.haitunwallet\.com\/app\/vip\/status,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/mubu\.com\/api\/app\/user\/info,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/app\.xunjiepdf\.com\/api\/v4\/virtualactregister,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=http:\/\/miaow\.yiyongcad\.com\/api\/v4\/memprofile,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/api\.lennou\.com\/user\/info,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.gkocr\.com\/api\/userlogin1.php,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/vira\.llsapp\.com\/api\/v2\/readings\/(accessible|limitation),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.rr\.tv(\/user\/privilege\/list|\/ad\/getAll|\/rrtv-video\/v4plus\/season\/detail),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/pan\.baidu\.com\/s\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.revenuecat\.com\/v1\/(receipts|\d{1,})$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://(commerce-.*api|pay).(faceu|wecut).(com|mobi)/(commerce|apple)/(iosAppVerifyReceipt.php|v1/subscription/user_info),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/vip1\.kuwo\.cn\/(vip\/v2\/theme),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http:\/\/115\.com\/lx.*$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.lakecoloring\.com\/v1\/receipt,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http://ctrl.playcvn.com/app/(init|ads),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/dict\.eudic\.net\/jingting\/GetThisChapterTaskStatus?,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://m.client.10010.com/uniAdmsInterface/getHomePageAd,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.wakamoment\.ga\/init\?platform\=ios,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.diyidan\.net\/v0\.3\/(user\/personal_homepage|vip_user\/info|tv_series\/index\?appChanne),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.flexibits\.com\/v1\/(auth|account)\/(device|details|appstore-receipt)\/$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https?:\/\/api\.jiaonizuocai\.com,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/api\.sololearn\.com\/(authenticateDevice|challenge\/GetContestFeed|Profile\/GetProfile)$,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://tncj.hortorgames.com/chicken/fight/(answer|findQuiz),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/(www\.baidu.com2\.club|ayk\.tmdidi\.com|m\.pearkin\.com|souhu\.mett\.me|bkcd\.b-cdn\.net)\/(api\/movie\/WatchMovie|api\/Account\/CheckVip|api\/account\/IndexDetail),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/claritywallpaper\.com\/clarity\/api\/(userInfo|special\/queryByCatalogAll),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/bookapi\.ihuman\.com\/(v1\/get\_user\_info|v1\/get\_purchase\_list),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=http:\/\/rest\.zhibo\.tv\/room\/get\-room\-info\-v430,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://note.youdao.com/yws/(mapi/payment|api/self),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/billing\.peakcloud\.org\/billing\/2\/user\/me?,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/api\.ithome\.com\/json\/slide\/index,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/api\.ithome\.com\/json\/(newslist|listpage)\/news,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/www\.xmind\.cn\/\_res\/devices,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http?:\/\/.*\.arten.cn/login/login,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https://api.weiqire.com/api3/(visitor/|user/unlockCharpter),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://api.shimo.im/users/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/.*\.videostarapp\.com\/scripts\/subsNew\.php,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https:\/\/api\.revenuecat\.com\/v1\/(subscribers|receipts),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=https://app.api.versa-ai.com/pay/order/iap/check,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https\:\/\/hjapi\.bjxkhc\.com\/v2d2\/users\/.*\/member,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=http:\/\/api\.591master\.com\:8081\/(1.0|3.6.8)\/ui(forum|common)\/(downloadwallpaper|getuser),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http\:\/\/jdytv\.cn\/login\/login\/veifys,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=http://user.shywck.com/user/userinfo,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?:\/\/.*\.xunjie.*\.com\/api\/v\d\/*,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.psy-1\.com\/cosleep\/user\/info,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/snailsleep\.net\/snail\/v1\/profile\/get,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/mob2015\.kekenet\.com\/keke\/mobile\/index\.php,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/m\.samh\.xndm\.tech\/userapi\/info\/v1\/getuserinfo,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^http\:\/\/app\.flashdown365\.com\/ios\/login,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/avoscloud\.com\/1\.1\/users\/,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https\:\/\/api\.gao1gps\.cn\/v1\/user\/info,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/api\.meiyan\.com\/iap\/verify\.json,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https:\/\/bea\.sportq\.com\/SFitWeb\/sfit\/getUserBaseInfo,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig),script-path=eval_script.js + +eval_script.js = type=http-request,requires-body=1,pattern=^http://.+/amdc/mobileDispatch,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail,script-path=eval_script.js + +eval_script.js = type=http-request,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/warmer/.+type=show-ath,script-path=eval_script.js \ No newline at end of file diff --git a/eval_script/sg_script.sgmodule b/eval_script/sg_script.sgmodule new file mode 100644 index 000000000..39b0e6ebe --- /dev/null +++ b/eval_script/sg_script.sgmodule @@ -0,0 +1,19 @@ +#!name=eval_script.js module + +[MITM] +hostname = %INSERT% api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios.prod.ftl.netflix.com + +[Script] +eval_script.js = type=http-request,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig),script-path=eval_script.js + +eval_script.js = type=http-request,requires-body=1,pattern=^http://.+/amdc/mobileDispatch,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail,script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua),script-path=eval_script.js + +eval_script.js = type=http-response,requires-body=1,pattern=^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/photos/pic_recommend_status|video/tiny_stream_video_list),script-path=eval_script.js \ No newline at end of file diff --git a/jd_price.js b/jd_price.js new file mode 100644 index 000000000..cdec860f4 --- /dev/null +++ b/jd_price.js @@ -0,0 +1,373 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const path1 = "serverConfig"; +const path2 = "wareBusiness"; +const path3 = "basicConfig"; +const url = $request.url; +const body = $response.body; +const $tool = tool(); + +if (url.indexOf(path1) != -1) { + let obj = JSON.parse(body); + delete obj.serverConfig.httpdns; + delete obj.serverConfig.dnsvip; + delete obj.serverConfig.dnsvip_v6; + $done({ body: JSON.stringify(obj) }); +} + +if (url.indexOf(path3) != -1) { + let obj = JSON.parse(body); + let JDHttpToolKit = obj.data.JDHttpToolKit; + if (JDHttpToolKit) { + delete obj.data.JDHttpToolKit.httpdns; + delete obj.data.JDHttpToolKit.dnsvipV6; + } + $done({ body: JSON.stringify(obj) }); +} + +if (url.indexOf(path2) != -1) { + let obj = JSON.parse(body); + const floors = obj.floors; + const commodity_info = floors[floors.length - 1]; + const shareUrl = commodity_info.data.property.shareUrl; + let msg = ""; + request_history_price(shareUrl) + .then((data) => { + if (data.errno == -1) throw new Error("Whoops!"); + msg = priceSummary(data.data); + }) + .catch((error) => (msg = "暂无价格信息")) + .finally(() => { + const lowerword = adword_obj(); + lowerword.data.ad.textColor = "#fe0000"; + let bestIndex = 0; + for (let index = 0; index < floors.length; index++) { + const element = floors[index]; + if (element.mId == lowerword.mId) { + bestIndex = index + 1; + break; + } else { + if (element.sortId > lowerword.sortId) { + bestIndex = index; + break; + } + } + } + lowerword.data.ad.adword = msg; + floors.insert(bestIndex, lowerword); + $done({ body: JSON.stringify(obj) }); + }); +} + +function priceSummary(data) { + let summary = `🌨 当前: ${data.CurrentPrice}${getSpace(8)}最低: ${data.LowestPrice} (${data.LowestDate})`; + const list = historySummary(data.PricesHistory); + list.forEach((item, index) => { + summary += `\n${item.Name}${getSpace(8)}${item.Price}${getSpace(8)}${item.Date}${getSpace(8)}${item.Difference}`; + }); + return summary; +} + +function historySummary(list) { + let currentPrice, lowest30, lowest90, lowest180, lowest360, price11, price618; + list = list.reverse().slice(0, 360); + list.forEach((item, index) => { + const date = item.Date; + let price = item.Price; + if (index == 0) { + currentPrice = price; + price618 = { + Name: "六一八价格", + Price: "-", + Date: "-", + Difference: "-", + price: "-", + }; + price11 = { + Name: "双十一价格", + Price: "-", + Date: "-", + Difference: "-", + price: "-", + }; + lowest30 = { + Name: "三十天最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest90 = { + Name: "九十天最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest180 = { + Name: "一百八最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest360 = { + Name: "三百六最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + } + if (date.indexOf("06-18") != -1) { + price618.price = price; + price618.Price = `¥${String(price)}`; + price618.Date = date; + price618.Difference = difference(currentPrice, price); + } + if (date.indexOf("11-11") != -1) { + price11.price = price; + price11.Price = `¥${String(price)}`; + price11.Date = date; + price11.Difference = difference(currentPrice, price); + } + if (index < 30 && price < lowest30.price) { + lowest30.price = price; + lowest30.Price = `¥${String(price)}`; + lowest30.Date = date; + lowest30.Difference = difference(currentPrice, price); + } + if (index < 90 && price < lowest90.price) { + lowest90.price = price; + lowest90.Price = `¥${String(price)}`; + lowest90.Date = date; + lowest90.Difference = difference(currentPrice, price); + } + if (index < 180 && price < lowest180.price) { + lowest180.price = price; + lowest180.Price = `¥${String(price)}`; + lowest180.Date = date; + lowest180.Difference = difference(currentPrice, price); + } + if (index < 360 && price < lowest360.price) { + lowest360.price = price; + lowest360.Price = `¥${String(price)}`; + lowest360.Date = date; + lowest360.Difference = difference(currentPrice, price); + } + }); + return [lowest30, lowest90, lowest180, lowest360, price618, price11]; +} + +async function request_history_price(share_url) { + const options = { + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }; + + const priceTrend = new Promise(function (resolve, reject) { + options.url = "https://price.icharle.com/?product_id=" + share_url; + $tool.get(options, function (error, response, data) { + if (!error) { + resolve(JSON.parse(data)); + } else { + reject(error); + } + }); + }); + const priceTrendData = await priceTrend; + return priceTrendData; +} + +function getExactTime(time) { + var date = new Date(time * 1000); + var year = date.getFullYear() + "-"; + var month = + (date.getMonth() + 1 < 10 + ? "0" + (date.getMonth() + 1) + : date.getMonth() + 1) + "-"; + var dates = date.getDate(); + return year + month + dates; +} + +function difference(currentPrice, price) { + let difference = sub(currentPrice, price); + if (difference == 0) { + return "-"; + } else { + return `${difference > 0 ? "↑" : "↓"}${String(Math.abs(difference))}`; + } +} + +function sub(arg1, arg2) { + return add(arg1, -Number(arg2), arguments[2]); +} + +function add(arg1, arg2) { + (arg1 = arg1.toString()), (arg2 = arg2.toString()); + var arg1Arr = arg1.split("."), + arg2Arr = arg2.split("."), + d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", + d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""; + var maxLen = Math.max(d1.length, d2.length); + var m = Math.pow(10, maxLen); + var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen)); + var d = arguments[2]; + return typeof d === "number" ? Number(result.toFixed(d)) : result; +} + +function getSpace(length) { + let blank = ""; + for (let index = 0; index < length; index++) { + blank += " "; + } + return blank; +} + +function adword_obj() { + return { + bId: "eCustom_flo_199", + cf: { + bgc: "#ffffff", + spl: "empty", + }, + data: { + ad: { + adword: "", + textColor: "#8C8C8C", + color: "#f23030", + newALContent: true, + hasFold: true, + class: "com.jd.app.server.warecoresoa.domain.AdWordInfo.AdWordInfo", + adLinkContent: "", + adLink: "", + }, + }, + mId: "bpAdword", + refId: "eAdword_0000000028", + sortId: 13, + }; +} + +function tool() { + const isSurge = typeof $httpClient != "undefined"; + const isQuanX = typeof $task != "undefined"; + const isResponse = typeof $response != "undefined"; + const node = (() => { + if (typeof require == "function") { + const request = require("request"); + return { request }; + } else { + return null; + } + })(); + const notify = (title, subtitle, message) => { + if (isQuanX) $notify(title, subtitle, message); + if (isSurge) $notification.post(title, subtitle, message); + if (node) console.log(JSON.stringify({ title, subtitle, message })); + }; + const write = (value, key) => { + if (isQuanX) return $prefs.setValueForKey(value, key); + if (isSurge) return $persistentStore.write(value, key); + }; + const read = (key) => { + if (isQuanX) return $prefs.valueForKey(key); + if (isSurge) return $persistentStore.read(key); + }; + const adapterStatus = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status; + } else if (response.statusCode) { + response["status"] = response.statusCode; + } + } + return response; + }; + const get = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options }; + options["method"] = "GET"; + $task.fetch(options).then( + (response) => { + callback(null, adapterStatus(response), response.body); + }, + (reason) => callback(reason.error, null, null) + ); + } + if (isSurge) + $httpClient.get(options, (error, response, body) => { + callback(error, adapterStatus(response), body); + }); + if (node) { + node.request(options, (error, response, body) => { + callback(error, adapterStatus(response), body); + }); + } + }; + const post = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options }; + options["method"] = "POST"; + $task.fetch(options).then( + (response) => { + callback(null, adapterStatus(response), response.body); + }, + (reason) => callback(reason.error, null, null) + ); + } + if (isSurge) { + $httpClient.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body); + }); + } + if (node) { + node.request.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body); + }); + } + }; + return { isQuanX, isSurge, isResponse, notify, write, read, get, post }; +} + +Array.prototype.insert = function (index, item) { + this.splice(index, 0, item); +}; + +Date.prototype.format = function (fmt) { + var o = { + "y+": this.getFullYear(), + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "h+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + "S+": this.getMilliseconds(), + }; + for (var k in o) { + if (new RegExp("(" + k + ")").test(fmt)) { + if (k == "y+") { + fmt = fmt.replace(RegExp.$1, ("" + o[k]).substr(4 - RegExp.$1.length)); + } else if (k == "S+") { + var lens = RegExp.$1.length; + lens = lens == 1 ? 3 : lens; + fmt = fmt.replace( + RegExp.$1, + ("00" + o[k]).substr(("" + o[k]).length - 1, lens) + ); + } else { + fmt = fmt.replace( + RegExp.$1, + RegExp.$1.length == 1 + ? o[k] + : ("00" + o[k]).substr(("" + o[k]).length) + ); + } + } + } + return fmt; +}; diff --git a/jd_price_lite.js b/jd_price_lite.js new file mode 100644 index 000000000..82813f2c2 --- /dev/null +++ b/jd_price_lite.js @@ -0,0 +1,293 @@ +/* +README:https://github.com/yichahucha/surge/tree/master +^https?://api\.m\.jd\.com/(client\.action|api)\?functionId=(wareBusiness|serverConfig|basicConfig|lite_wareBusiness|pingou_item) + */ + +const path1 = "serverConfig"; +const path2 = "wareBusiness"; +const path2h = "wareBusiness.style"; +const path3 = "basicConfig"; +const path4 = "pingou_item"; +const consolelog = false; +const url = $request.url; +const body = $response.body; +const $tool = tool(); + +if (url.indexOf(path1) != -1) { + let obj = JSON.parse(body); + delete obj.serverConfig.httpdns; + delete obj.serverConfig.dnsvip; + delete obj.serverConfig.dnsvip_v6; + $done({ body: JSON.stringify(obj) }); +} + +if (url.indexOf(path3) != -1) { + let obj = JSON.parse(body); + let JDHttpToolKit = obj.data.JDHttpToolKit; + let jCommandConfig = obj.data.jCommandConfig; + if (JDHttpToolKit) { + delete obj.data.JDHttpToolKit.httpdns; + delete obj.data.JDHttpToolKit.dnsvipV6; + } + if (jCommandConfig) { + delete obj.data.jCommandConfig.httpdnsConfig; + } + $done({ body: JSON.stringify(obj) }); +} + +if (url.indexOf(path2) != -1 || url.indexOf(path4) != -1) { + if (!$tool.isQuanX) { + $done({ body }); + } + let obj = JSON.parse(body); + const floors = obj.floors; + const commodity_info = floors[floors.length - 1]; + const others = obj.others; + const domain = obj.domain; + const shareUrl = + url.indexOf(path4) != -1 + ? domain.h5Url + : url.indexOf(path2h) != -1 + ? others.property.shareUrl + : commodity_info.data.property.shareUrl; + request_history_price(shareUrl, function (data) { + if (data) { + if (data.ok == 1 && data.single) { + const lower = lowerMsgs(data.single) + const detail = priceSummary(data) + const tip = data.PriceRemark.Tip + $tool.notify("", "", `${lower}\n${tip}${detail}`) + } + if (data.ok == 0 && data.msg.length > 0) { + $tool.notify("", "", `⚠️ ${data.msg}`) + } + } + $done({ body }); + }) +} + +function lowerMsgs(data) { + const lower = data.lowerPriceyh + const lowerDate = dateFormat(data.lowerDateyh) + const lowerMsg = "🍵 历史最低到手价:¥" + String(lower) + ` (${lowerDate}) ` + return lowerMsg +} + + +function priceSummary(data) { + let summary = "" + let listPriceDetail = data.PriceRemark.ListPriceDetail.slice(0,4) + let list = listPriceDetail.concat(historySummary(data.single)) + list.forEach((item, index) => { + if (item.Name == "双11价格") { + item.Name = "双十一价格" + } else if (item.Name == "618价格") { + item.Name = "六一八价格" + } + let price = String(parseInt(item.Price.substr(1))); + summary += `\n${item.Name} ${isNaN(price) ? "-" : "¥" + price} ${item.Date} ${item.Difference}` + }) + return summary +} + +function historySummary(single) { + const rexMatch = /\[.*?\]/g; + const rexExec = /\[(.*),(.*),"(.*)".*\]/; + let currentPrice, lowest30, lowest90, lowest180, lowest360 + let list = single.jiagequshiyh.match(rexMatch); + list = list.reverse().slice(0, 360); + list.forEach((item, index) => { + if (item.length > 0) { + const result = rexExec.exec(item); + const dateUTC = new Date(eval(result[1])); + const date = dateUTC.format("yyyy-MM-dd"); + let price = parseFloat(result[2]); + if (index == 0) { + currentPrice = price + lowest30 = { Name: "三十天最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest90 = { Name: "九十天最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest180 = { Name: "一百八最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest360 = { Name: "三百六最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + } + if (index < 30 && price < lowest30.price) { + lowest30.price = price + lowest30.Price = `¥${String(price)}` + lowest30.Date = date + lowest30.Difference = difference(currentPrice, price) + } + if (index < 90 && price < lowest90.price) { + lowest90.price = price + lowest90.Price = `¥${String(price)}` + lowest90.Date = date + lowest90.Difference = difference(currentPrice, price) + } + if (index < 180 && price < lowest180.price) { + lowest180.price = price + lowest180.Price = `¥${String(price)}` + lowest180.Date = date + lowest180.Difference = difference(currentPrice, price) + } + if (index < 360 && price < lowest360.price) { + lowest360.price = price + lowest360.Price = `¥${String(price)}` + lowest360.Date = date + lowest360.Difference = difference(currentPrice, price) + } + } + }); + return [lowest30, lowest90, lowest180]; +} + +function difference(currentPrice, price) { + let difference = sub(currentPrice, price) + if (difference == 0) { + return "-" + } else { + return `${difference > 0 ? "↑" : "↓"}${String(difference)}` + } +} + +function sub(arg1, arg2) { + return add(arg1, -Number(arg2), arguments[2]); +} + +function add(arg1, arg2) { + arg1 = arg1.toString(), arg2 = arg2.toString(); + var arg1Arr = arg1.split("."), arg2Arr = arg2.split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""; + var maxLen = Math.max(d1.length, d2.length); + var m = Math.pow(10, maxLen); + var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen)); + var d = arguments[2]; + return typeof d === "number" ? Number((result).toFixed(d)) : result; +} + +function request_history_price(share_url, callback) { + const options = { + url: "https://apapia-history.manmanbuy.com/ChromeWidgetServices/WidgetServices.ashx", + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 - mmbWebBrowse - ios" + }, + body: "methodName=getHistoryTrend&p_url=" + encodeURIComponent(share_url) + } + $tool.post(options, function (error, response, data) { + if (!error) { + callback(JSON.parse(data)); + if (consolelog) console.log("Data:\n" + data); + } else { + callback(null, null); + if (consolelog) console.log("Error:\n" + error); + } + }) +} + +function dateFormat(cellval) { + const date = new Date(parseInt(cellval.replace("/Date(", "").replace(")/", ""), 10)); + const month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; + const currentDate = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); + return date.getFullYear() + "-" + month + "-" + currentDate; +} + +function tool() { + const isSurge = typeof $httpClient != "undefined" + const isQuanX = typeof $task != "undefined" + const node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + const notify = (title, subtitle, message) => { + if (isQuanX) $notify(title, subtitle, message) + if (isSurge) $notification.post(title, subtitle, message) + if (node) console.log(JSON.stringify({ title, subtitle, message })); + } + const setCache = (value, key) => { + if (isQuanX) return $prefs.setValueForKey(value, key) + if (isSurge) return $persistentStore.write(value, key) + } + const getCache = (key) => { + if (isQuanX) return $prefs.valueForKey(key) + if (isSurge) return $persistentStore.read(key) + } + const adapterStatus = (response) => { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + return response + } + const get = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { + callback(null, adapterStatus(response), response.body) + }, reason => callback(reason.error, null, null)) + } + if (isSurge) $httpClient.get(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + if (node) { + node.request(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + } + const post = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { + callback(null, adapterStatus(response), response.body) + }, reason => callback(reason.error, null, null)) + } + if (isSurge) { + $httpClient.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + if (node) { + node.request.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + } + return { isQuanX, isSurge, notify, setCache, getCache, get, post } +} + +Array.prototype.insert = function (index, item) { + this.splice(index, 0, item); +}; + +Date.prototype.format = function (fmt) { + var o = { + "y+": this.getFullYear(), + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "h+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + "S+": this.getMilliseconds() + }; + for (var k in o) { + if (new RegExp("(" + k + ")").test(fmt)) { + if (k == "y+") { + fmt = fmt.replace(RegExp.$1, ("" + o[k]).substr(4 - RegExp.$1.length)); + } + else if (k == "S+") { + var lens = RegExp.$1.length; + lens = lens == 1 ? 3 : lens; + fmt = fmt.replace(RegExp.$1, ("00" + o[k]).substr(("" + o[k]).length - 1, lens)); + } + else { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + } + } + } + return fmt; +} \ No newline at end of file diff --git a/loon_sub.conf b/loon_sub.conf new file mode 100644 index 000000000..337f6fa06 --- /dev/null +++ b/loon_sub.conf @@ -0,0 +1,12 @@ +http-request ^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js, tag=奈飞评分1 +http-response ^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js, tag=奈飞评分2 +http-response ^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/warmer/.+type=show-ath requires-body=1,max-size=0,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating_season.js, tag=单集评分 +http-response ^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_launch.js, tag=微博去广告 +http-response ^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/(photos/pic_recommend_status|live/media_homelist)|video/tiny_stream_video_list|photo/info) requires-body=1,max-size=-1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_ad.js, , tag=微博去广告 +http-response ^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig|basicConfig) requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js,tag=京东比价 +http-response ^http://.+/amdc/mobileDispatch requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js,tag=淘宝比价1(内置版) +http-response ^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js, tag=淘宝比价2(内置版) +http-response ^http://.+/amdc/mobileDispatch requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price_lite.js,tag=淘宝比价1 Lite(通知版) +http-response ^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail requires-body=1,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price_lite.js, tag=淘宝比价2 Lite(通知版) + +hostname = api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios-*.prod.ftl.netflix.com,ios.prod.ftl.netflix.com diff --git a/nf_rating.js b/nf_rating.js new file mode 100644 index 000000000..9dd4c49a7 --- /dev/null +++ b/nf_rating.js @@ -0,0 +1,562 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const $tool = new Tool() +const consoleLog = false; +const imdbApikeyCacheKey = "ImdbApikeyCacheKey"; +const netflixTitleCacheKey = "NetflixTitleCacheKey"; + +if (!$tool.isResponse) { + let url = $request.url; + const urlDecode = decodeURIComponent(url); + const videos = urlDecode.match(/"videos","(\d+)"/); + const videoID = videos[1]; + const map = getTitleMap(); + const title = map[videoID]; + const isEnglish = url.match(/languages=en/) ? true : false; + if (!title && !isEnglish) { + const currentSummary = urlDecode.match(/\["videos","(\d+)","current","summary"\]/); + if (currentSummary) { + url = url.replace("&path=" + encodeURIComponent(currentSummary[0]), ""); + } + url = url.replace(/&languages=(.*?)&/, "&languages=en-US&"); + } + url += "&path=" + encodeURIComponent(`[${videos[0]},"details"]`); + $done({ url }); +} else { + var IMDbApikeys = IMDbApikeys(); + var IMDbApikey = $tool.read(imdbApikeyCacheKey); + if (!IMDbApikey) updateIMDbApikey(); + let obj = JSON.parse($response.body); + if (consoleLog) console.log("Netflix Original Body:\n" + $response.body); + if (typeof (obj.paths[0][1]) == "string") { + const videoID = obj.paths[0][1]; + const video = obj.value.videos[videoID]; + const map = getTitleMap(); + let title = map[videoID]; + if (!title) { + title = video.summary.title; + setTitleMap(videoID, title, map); + } + let year = null; + let type = video.summary.type; + if (type == "show") { + type = "series"; + } + if (video.details) { + if (type == "movie") { + year = video.details.releaseYear; + } + delete video.details; + } + const requestRatings = async () => { + const IMDb = await requestIMDbRating(title, year, type); + const Douban = await requestDoubanRating(IMDb.id); + const IMDbrating = IMDb.msg.rating; + const tomatoes = IMDb.msg.tomatoes; + const country = IMDb.msg.country; + const awards = IMDb.msg.awards; + const doubanRating = Douban.rating; + const message = `${awards.length > 0 ? awards + "\n": ""}${country}\n${IMDbrating}\n${doubanRating}${tomatoes.length > 0 ? "\n" + tomatoes + "\n" : "\n"}`; + return message; + } + let msg = ""; + requestRatings() + .then(message => msg = message) + .catch(error => msg = error + "\n") + .finally(() => { + let summary = obj.value.videos[videoID].summary; + summary["supplementalMessage"] = `${msg}${summary && summary.supplementalMessage ? "\n" + summary.supplementalMessage : ""}`; + msg_obj = {"tagline":summary.supplementalMessage, "classification":"REGULAR"} + if (summary["supplementalMessages"]) { + summary["supplementalMessages"].push(msg_obj) + }else { + summary["supplementalMessages"] = [msg_obj] + } + if (consoleLog) console.log("Netflix Modified Body:\n" + JSON.stringify(obj)); + $done({ body: JSON.stringify(obj) }); + }); + } else { + $done({}); + } +} + +function getTitleMap() { + const map = $tool.read(netflixTitleCacheKey); + return map ? JSON.parse(map) : {}; +} + +function setTitleMap(id, title, map) { + map[id] = title; + $tool.write(JSON.stringify(map), netflixTitleCacheKey); +} + +function requestDoubanRating(imdbId) { + return new Promise(function (resolve, reject) { + const url = `https://www.douban.com/search?cat=1002&q=${imdbId}`; + if (consoleLog) console.log("Netflix Douban Rating URL:\n" + url); + $tool.get(url, function (error, response, data) { + if (!error) { + if (consoleLog) console.log("Netflix Douban Rating Data:\n" + data); + if (response.status == 200) { + const rating = get_douban_rating_message(data); + resolve({ rating }); + } else { + resolve({ rating: "Douban: " + errorTip().noData }); + } + } else { + if (consoleLog) console.log("Netflix Douban Rating Error:\n" + error); + resolve({ rating: "Douban: " + errorTip().error }); + } + }); + }); +} + +function requestIMDbRating(title, year, type) { + return new Promise(function (resolve, reject) { + let url = "https://www.omdbapi.com/?t=" + encodeURI(title) + "&apikey=" + IMDbApikey; + if (year) url += "&y=" + year; + if (type) url += "&type=" + type; + if (consoleLog) console.log("Netflix IMDb Rating URL:\n" + url); + $tool.get(url, function (error, response, data) { + if (!error) { + if (consoleLog) console.log("Netflix IMDb Rating Data:\n" + data); + if (response.status == 200) { + const obj = JSON.parse(data); + if (obj.Response != "False") { + const id = obj.imdbID; + const msg = get_IMDb_message(obj); + resolve({ id, msg }); + } else { + reject(errorTip().noData); + } + } else if (response.status == 401) { + if (IMDbApikeys.length > 1) { + updateIMDbApikey(); + requestIMDbRating(title, year, type); + } else { + reject(errorTip().noData); + } + } else { + reject(errorTip().noData); + } + } else { + if (consoleLog) console.log("Netflix IMDb Rating Error:\n" + error); + reject(errorTip().error); + } + }); + }); +} + +function updateIMDbApikey() { + if (IMDbApikey) IMDbApikeys.splice(IMDbApikeys.indexOf(IMDbApikey), 1); + const index = Math.floor(Math.random() * IMDbApikeys.length); + IMDbApikey = IMDbApikeys[index]; + $tool.write(IMDbApikey, imdbApikeyCacheKey); +} + +function get_IMDb_message(data) { + let rating_message = "IMDb: ⭐️ N/A"; + let tomatoes_message = ""; + let country_message = ""; + let ratings = data.Ratings; + let awards_message = ""; + if (data.Awards && data.Awards != "N/A") { + awards_message = "🏆 " + data.Awards; + } + if (ratings.length > 0) { + const imdb_source = ratings[0]["Source"]; + if (imdb_source == "Internet Movie Database") { + const imdb_votes = data.imdbVotes; + const imdb_rating = ratings[0]["Value"]; + rating_message = "IMDb: ⭐️ " + imdb_rating + " " + imdb_votes; + if (data.Type == "movie") { + if (ratings.length > 1) { + const source = ratings[1]["Source"]; + if (source == "Rotten Tomatoes") { + const tomatoes = ratings[1]["Value"]; + tomatoes_message = "Tomatoes: 🍅 " + tomatoes; + } + } + } + } + } + country_message = get_country_message(data.Country); + return { rating: rating_message, tomatoes: tomatoes_message, country: country_message, awards: awards_message } +} + +function get_douban_rating_message(data) { + const s = data.replace(/\n| |&#\d{2}/g, '') + .match(/\[(\u7535\u5f71|\u7535\u89c6\u5267)\].+?subject-cast\">.+?<\/span>/g); + const average = s ? s[0].split(/">(\d\.\d) { + emoji_country += countryEmoji(item) + " " + item + ", "; + }); + return emoji_country.slice(0, -2); +} + +function errorTip() { + return { noData: "⭐️ N/A", error: "❌ N/A" } +} + +function IMDbApikeys() { + const apikeys = [ + "f75e0253", "d8bb2d6b", + "ae64ce8d", "7218d678", + "b2650e38", "8c4a29ab", + "9bd135c2", "953dbabe", + "1a66ef12", "3e7ea721", + "457fc4ff", "d2131426", + "9cc1a9b7", "e53c2c11", + "f6dfce0e", "b9db622f", + "e6bde2b9", "d324dbab", + "d7904fa3", "aeaf88b9", + "4e89234e",]; + return apikeys; +} + +function countryEmoji(name) { + const emojiMap = { + "Chequered": "🏁", + "Triangular": "🚩", + "Crossed": "🎌", + "Black": "🏴", + "White": "🏳", + "Rainbow": "🏳️‍🌈", + "Pirate": "🏴‍☠️", + "Ascension Island": "🇦🇨", + "Andorra": "🇦🇩", + "United Arab Emirates": "🇦🇪", + "Afghanistan": "🇦🇫", + "Antigua & Barbuda": "🇦🇬", + "Anguilla": "🇦🇮", + "Albania": "🇦🇱", + "Armenia": "🇦🇲", + "Angola": "🇦🇴", + "Antarctica": "🇦🇶", + "Argentina": "🇦🇷", + "American Samoa": "🇦🇸", + "Austria": "🇦🇹", + "Australia": "🇦🇺", + "Aruba": "🇦🇼", + "Åland Islands": "🇦🇽", + "Azerbaijan": "🇦🇿", + "Bosnia & Herzegovina": "🇧🇦", + "Barbados": "🇧🇧", + "Bangladesh": "🇧🇩", + "Belgium": "🇧🇪", + "Burkina Faso": "🇧🇫", + "Bulgaria": "🇧🇬", + "Bahrain": "🇧🇭", + "Burundi": "🇧🇮", + "Benin": "🇧🇯", + "St. Barthélemy": "🇧🇱", + "Bermuda": "🇧🇲", + "Brunei": "🇧🇳", + "Bolivia": "🇧🇴", + "Caribbean Netherlands": "🇧🇶", + "Brazil": "🇧🇷", + "Bahamas": "🇧🇸", + "Bhutan": "🇧🇹", + "Bouvet Island": "🇧🇻", + "Botswana": "🇧🇼", + "Belarus": "🇧🇾", + "Belize": "🇧🇿", + "Canada": "🇨🇦", + "Cocos (Keeling) Islands": "🇨🇨", + "Congo - Kinshasa": "🇨🇩", + "Congo": "🇨🇩", + "Central African Republic": "🇨🇫", + "Congo - Brazzaville": "🇨🇬", + "Switzerland": "🇨🇭", + "Côte d’Ivoire": "🇨🇮", + "Cook Islands": "🇨🇰", + "Chile": "🇨🇱", + "Cameroon": "🇨🇲", + "China": "🇨🇳", + "Colombia": "🇨🇴", + "Clipperton Island": "🇨🇵", + "Costa Rica": "🇨🇷", + "Cuba": "🇨🇺", + "Cape Verde": "🇨🇻", + "Curaçao": "🇨🇼", + "Christmas Island": "🇨🇽", + "Cyprus": "🇨🇾", + "Czechia": "🇨🇿", + "Czech Republic": "🇨🇿", + "Germany": "🇩🇪", + "Diego Garcia": "🇩🇬", + "Djibouti": "🇩🇯", + "Denmark": "🇩🇰", + "Dominica": "🇩🇲", + "Dominican Republic": "🇩🇴", + "Algeria": "🇩🇿", + "Ceuta & Melilla": "🇪🇦", + "Ecuador": "🇪🇨", + "Estonia": "🇪🇪", + "Egypt": "🇪🇬", + "Western Sahara": "🇪🇭", + "Eritrea": "🇪🇷", + "Spain": "🇪🇸", + "Ethiopia": "🇪🇹", + "European Union": "🇪🇺", + "Finland": "🇫🇮", + "Fiji": "🇫🇯", + "Falkland Islands": "🇫🇰", + "Micronesia": "🇫🇲", + "Faroe Islands": "🇫🇴", + "France": "🇫🇷", + "Gabon": "🇬🇦", + "United Kingdom": "🇬🇧", + "UK": "🇬🇧", + "Grenada": "🇬🇩", + "Georgia": "🇬🇪", + "French Guiana": "🇬🇫", + "Guernsey": "🇬🇬", + "Ghana": "🇬🇭", + "Gibraltar": "🇬🇮", + "Greenland": "🇬🇱", + "Gambia": "🇬🇲", + "Guinea": "🇬🇳", + "Guadeloupe": "🇬🇵", + "Equatorial Guinea": "🇬🇶", + "Greece": "🇬🇷", + "South Georgia & South Sandwich Is lands": "🇬🇸", + "Guatemala": "🇬🇹", + "Guam": "🇬🇺", + "Guinea-Bissau": "🇬🇼", + "Guyana": "🇬🇾", + "Hong Kong SAR China": "🇭🇰", + "Hong Kong": "🇭🇰", + "Heard & McDonald Islands": "🇭🇲", + "Honduras": "🇭🇳", + "Croatia": "🇭🇷", + "Haiti": "🇭🇹", + "Hungary": "🇭🇺", + "Canary Islands": "🇮🇨", + "Indonesia": "🇮🇩", + "Ireland": "🇮🇪", + "Israel": "🇮🇱", + "Isle of Man": "🇮🇲", + "India": "🇮🇳", + "British Indian Ocean Territory": "🇮🇴", + "Iraq": "🇮🇶", + "Iran": "🇮🇷", + "Iceland": "🇮🇸", + "Italy": "🇮🇹", + "Jersey": "🇯🇪", + "Jamaica": "🇯🇲", + "Jordan": "🇯🇴", + "Japan": "🇯🇵", + "Kenya": "🇰🇪", + "Kyrgyzstan": "🇰🇬", + "Cambodia": "🇰🇭", + "Kiribati": "🇰🇮", + "Comoros": "🇰🇲", + "St. Kitts & Nevis": "🇰🇳", + "North Korea": "🇰🇵", + "South Korea": "🇰🇷", + "Kuwait": "🇰🇼", + "Cayman Islands": "🇰🇾", + "Kazakhstan": "🇰🇿", + "Laos": "🇱🇦", + "Lebanon": "🇱🇧", + "St. Lucia": "🇱🇨", + "Liechtenstein": "🇱🇮", + "Sri Lanka": "🇱🇰", + "Liberia": "🇱🇷", + "Lesotho": "🇱🇸", + "Lithuania": "🇱🇹", + "Luxembourg": "🇱🇺", + "Latvia": "🇱🇻", + "Libya": "🇱🇾", + "Morocco": "🇲🇦", + "Monaco": "🇲🇨", + "Moldova": "🇲🇩", + "Montenegro": "🇲🇪", + "St. Martin": "🇲🇫", + "Madagascar": "🇲🇬", + "Marshall Islands": "🇲🇭", + "North Macedonia": "🇲🇰", + "Mali": "🇲🇱", + "Myanmar (Burma)": "🇲🇲", + "Mongolia": "🇲🇳", + "Macau Sar China": "🇲🇴", + "Northern Mariana Islands": "🇲🇵", + "Martinique": "🇲🇶", + "Mauritania": "🇲🇷", + "Montserrat": "🇲🇸", + "Malta": "🇲🇹", + "Mauritius": "🇲🇺", + "Maldives": "🇲🇻", + "Malawi": "🇲🇼", + "Mexico": "🇲🇽", + "Malaysia": "🇲🇾", + "Mozambique": "🇲🇿", + "Namibia": "🇳🇦", + "New Caledonia": "🇳🇨", + "Niger": "🇳🇪", + "Norfolk Island": "🇳🇫", + "Nigeria": "🇳🇬", + "Nicaragua": "🇳🇮", + "Netherlands": "🇳🇱", + "Norway": "🇳🇴", + "Nepal": "🇳🇵", + "Nauru": "🇳🇷", + "Niue": "🇳🇺", + "New Zealand": "🇳🇿", + "Oman": "🇴🇲", + "Panama": "🇵🇦", + "Peru": "🇵🇪", + "French Polynesia": "🇵🇫", + "Papua New Guinea": "🇵🇬", + "Philippines": "🇵🇭", + "Pakistan": "🇵🇰", + "Poland": "🇵🇱", + "St. Pierre & Miquelon": "🇵🇲", + "Pitcairn Islands": "🇵🇳", + "Puerto Rico": "🇵🇷", + "Palestinian Territories": "🇵🇸", + "Portugal": "🇵🇹", + "Palau": "🇵🇼", + "Paraguay": "🇵🇾", + "Qatar": "🇶🇦", + "Réunion": "🇷🇪", + "Romania": "🇷🇴", + "Serbia": "🇷🇸", + "Russia": "🇷🇺", + "Rwanda": "🇷🇼", + "Saudi Arabia": "🇸🇦", + "Solomon Islands": "🇸🇧", + "Seychelles": "🇸🇨", + "Sudan": "🇸🇩", + "Sweden": "🇸🇪", + "Singapore": "🇸🇬", + "St. Helena": "🇸🇭", + "Slovenia": "🇸🇮", + "Svalbard & Jan Mayen": "🇸🇯", + "Slovakia": "🇸🇰", + "Sierra Leone": "🇸🇱", + "San Marino": "🇸🇲", + "Senegal": "🇸🇳", + "Somalia": "🇸🇴", + "Suriname": "🇸🇷", + "South Sudan": "🇸🇸", + "São Tomé & Príncipe": "🇸🇹", + "El Salvador": "🇸🇻", + "Sint Maarten": "🇸🇽", + "Syria": "🇸🇾", + "Swaziland": "🇸🇿", + "Tristan Da Cunha": "🇹🇦", + "Turks & Caicos Islands": "🇹🇨", + "Chad": "🇹🇩", + "French Southern Territories": "🇹🇫", + "Togo": "🇹🇬", + "Thailand": "🇹🇭", + "Tajikistan": "🇹🇯", + "Tokelau": "🇹🇰", + "Timor-Leste": "🇹🇱", + "Turkmenistan": "🇹🇲", + "Tunisia": "🇹🇳", + "Tonga": "🇹🇴", + "Turkey": "🇹🇷", + "Trinidad & Tobago": "🇹🇹", + "Tuvalu": "🇹🇻", + "Taiwan": "🇨🇳", + "Tanzania": "🇹🇿", + "Ukraine": "🇺🇦", + "Uganda": "🇺🇬", + "U.S. Outlying Islands": "🇺🇲", + "United Nations": "🇺🇳", + "United States": "🇺🇸", + "USA": "🇺🇸", + "Uruguay": "🇺🇾", + "Uzbekistan": "🇺🇿", + "Vatican City": "🇻🇦", + "St. Vincent & Grenadines": "🇻🇨", + "Venezuela": "🇻🇪", + "British Virgin Islands": "🇻🇬", + "U.S. Virgin Islands": "🇻🇮", + "Vietnam": "🇻🇳", + "Vanuatu": "🇻🇺", + "Wallis & Futuna": "🇼🇫", + "Samoa": "🇼🇸", + "Kosovo": "🇽🇰", + "Yemen": "🇾🇪", + "Mayotte": "🇾🇹", + "South Africa": "🇿🇦", + "Zambia": "🇿🇲", + "Zimbabwe": "🇿🇼", + "England": "🏴󠁧󠁢󠁥󠁮󠁧󠁿", + "Scotland": "🏴󠁧󠁢󠁳󠁣󠁴󠁿", + "Wales": "🏴󠁧󠁢󠁷󠁬󠁳󠁿", + } + return emojiMap[name] ? emojiMap[name] : emojiMap["Chequered"]; +} + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isResponse = typeof $response != "undefined" + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} diff --git a/nf_rating_season.js b/nf_rating_season.js new file mode 100644 index 000000000..0ba67e159 --- /dev/null +++ b/nf_rating_season.js @@ -0,0 +1,162 @@ +/** + * nf_rating_season.js = type=http-response,pattern=^https?://ios\.prod\.ftl\.netflix\.com/iosui/warmer/.+type=show-ath,requires-body=1,max-size=0,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating_season.js + */ + +const $tool = new Tool() +const url = $request.url +const body = $response.body +const imdbApikeyCacheKey = "ImdbApikeyCacheKey" +const netflixTitleCacheKey = "NetflixTitleCacheKey" +const map = $tool.read(netflixTitleCacheKey) +const id = url.match(/id=(\d+)/)[1] +const title = map ? JSON.parse(map)[id] : null +const obj = JSON.parse(body) + +if (title && obj.shows[id].seasons) { + const key = $tool.read(imdbApikeyCacheKey) + const seasons = obj.shows[id].seasons.map(item => item.season) + const promises = [] + for (let index = 0; index < seasons.length; index++) { + const url = `https://www.omdbapi.com/?t=${title}&apikey=${key}&type=series&Season=${seasons[index]}` + promises.push(requestSeasonRating(encodeURI(url))) + } + Promise.all(promises).then((values) => { + // console.log(values) + for (let index = 0; index < values.length; index++) { + const imdb = values[index] + if (imdb) { + const imdbSeasonNumber = imdb.Season + const imdbEpisodes = imdb.Episodes + const entities = obj.entities + for (let [key, value] of Object.entries(entities)) { + // console.log(`${key}: ${value}`); + const seasonNumber = value.summary.seasonNumber + const episodeNumber = value.summary.episodeNumber + let title = value.summary.title + if (parseInt(imdbSeasonNumber) == seasonNumber) { + const imdbEpisode = imdbEpisodes.find(element => parseInt(element.Episode) == episodeNumber) + title = `${title}${imdbEpisode && imdbEpisode.imdbRating ? ` ⭐️ ${imdbEpisode.imdbRating}` : " ⭐️ N/A"}` + value.summary.title = title + //console.log(title); + } + } + } + } + // console.log(JSON.stringify(obj)) + $tool.done({ body: JSON.stringify(obj) }) + }) +} else { + $tool.done({}) +} + +function requestSeasonRating(url) { + return new Promise((resolve) => { + $tool.get(url, (error, response, body) => { + if (!error) { + const code = response.statusCode + if (code == 200) { + const obj = JSON.parse(body) + if (obj.Response != "False") { + //console.log(`successfully: ${url}`) + resolve(obj) + } else { + //console.log(`failed: ${obj.Error} -> ${url}`) + resolve(null) + } + } else { + //console.log(`failed: ${response.statusCode} -> ${url}`) + resolve(null) + } + } else { + //console.log(`faile: ${error}`) + resolve(null) + } + }) + }) +} + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + _isTask = typeof $request == "undefined" + _isResponse = typeof $response != "undefined" + _isRequestBody = typeof $request != "undefined" && typeof $request.body != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isTask = _isTask + this.isResponse = _isResponse + this.isRequestBody = _isRequestBody + this.method = (() => { + if (!_isTask && (_isSurge || _isQuanX)) { + return $request.method + } + })() + this.scriptType = (() => { + if (_isResponse) { + return "response" + } else { + return "request" + } + })() + this.done = (obj) => { + if (_isQuanX) $done(obj) + if (_isSurge) $done(obj) + if (_node) console.log("script done."); + } + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + if (_node) console.log(`write success: ${key}`); + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + if (_node) console.log(`read success: ${key}`); + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.put = (options, callback) => { + if (_isQuanX) $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + if (_isSurge) $httpClient.put(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} diff --git a/qx_sub.txt b/qx_sub.txt new file mode 100644 index 000000000..29bd911d8 --- /dev/null +++ b/qx_sub.txt @@ -0,0 +1,9 @@ +hostname = api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios-*.prod.ftl.netflix.com,ios.prod.ftl.netflix.com + +^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-request-header https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig|basicConfig) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js +^http://.+/amdc/mobileDispatch url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/wb_launch.js +^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/(photos/pic_recommend_status|live/media_homelist)|video/tiny_stream_video_list|photo/info|remind/unread_count) url script-response-body https://raw.githubusercontent.com/yichahucha/surge/master/wb_ad.js diff --git a/sg_sub.sgmodule b/sg_sub.sgmodule new file mode 100644 index 000000000..4d1c89862 --- /dev/null +++ b/sg_sub.sgmodule @@ -0,0 +1,13 @@ +#!name=eval_script.js module + +[MITM] +hostname = %INSERT% api.weibo.cn,mapi.weibo.com,*.uve.weibo.com,trade-acs.m.taobao.com,api.m.jd.com,ios-*.prod.ftl.netflix.com,ios.prod.ftl.netflix.com + +[Script] +nf_rating.js = type=http-request,pattern=^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +nf_rating.js = type=http-response,requires-body=1,pattern=^https?://ios(-.*)?\.prod\.ftl\.netflix\.com/iosui/user/.+path=%5B%22videos%22%2C%\d+%22%2C%22summary%22%5D,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/nf_rating.js +jd_price.js = type=http-response,requires-body=1,pattern=^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|serverConfig|basicConfig),script-path=https://raw.githubusercontent.com/yichahucha/surge/master/jd_price.js +tb_price.js = type=http-response,requires-body=1,pattern=^http://.+/amdc/mobileDispatch,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +tb_price.js = type=http-response,requires-body=1,pattern=^https?://trade-acs\.m\.taobao\.com/gw/mtop\.taobao\.detail\.getdetail,script-path=https://raw.githubusercontent.com/yichahucha/surge/master/tb_price.js +wb_launch.js = type=http-response,requires-body=1,pattern=^https?://(sdk|wb)app\.uve\.weibo\.com(/interface/sdk/sdkad.php|/wbapplua/wbpullad.lua),script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_launch.js +wb_ad.js = type=http-response,requires-body=1,max-size=-1,pattern=^https?://m?api\.weibo\.c(n|om)/2/(statuses/(unread|extend|positives/get|(friends|video)(/|_)(mix)?timeline)|stories/(video_stream|home_list)|(groups|fangle)/timeline|profile/statuses|comments/build_comments|photo/recommend_list|service/picfeed|searchall|cardlist|page|!/(photos/pic_recommend_status|live/media_homelist)|video/tiny_stream_video_list|photo/info|remind/unread_count),script-path=https://raw.githubusercontent.com/yichahucha/surge/master/wb_ad.js diff --git a/surge.js b/surge.js deleted file mode 100644 index b53d20f49..000000000 --- a/surge.js +++ /dev/null @@ -1 +0,0 @@ -console.log(body) \ No newline at end of file diff --git a/tb_pre.js b/tb_pre.js new file mode 100644 index 000000000..0a29ac25d --- /dev/null +++ b/tb_pre.js @@ -0,0 +1,41 @@ +let headers = $request.headers +let body = $request.body +if (headers["User-Agent"].indexOf("%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D") != -1) { + let json = Qs2Json(body) + let domain = json.domain.split(" ") + let i = domain.length; + while (i--) { + const block = "trade-acs.m.taobao.com" + const element = domain[i]; + if (element == block) { + domain.splice(i, 1); + } + } + json.domain = domain.join(" ") + body = Json2Qs(json) +} +$done({ + body +}) + +function Qs2Json(url) { + var search = url.substring(url.lastIndexOf("?") + 1); + var obj = {}; + var reg = /([^?&=]+)=([^?&=]*)/g; + search.replace(reg, function(rs, $1, $2) { + var name = decodeURIComponent($1); + var val = decodeURIComponent($2); + val = String(val); + obj[name] = val; + return rs; + }); + return obj; +} + +function Json2Qs(json) { + var temp = []; + for (var k in json) { + temp.push(k + "=" + json[k]); + } + return temp.join("&"); +} \ No newline at end of file diff --git a/tb_price.js b/tb_price.js new file mode 100644 index 000000000..593e8ceda --- /dev/null +++ b/tb_price.js @@ -0,0 +1,524 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const $tool = new Tool() +const path1 = "/amdc/mobileDispatch" +const path2 = "/gw/mtop.taobao.detail.getdetail" +const consoleLog = false +const url = $request.url + +if (url.indexOf(path1) != -1) { + if ($tool.isResponse) { + const $base64 = new Base64() + let body = $response.body + let obj = JSON.parse($base64.decode(body)) + let dns = obj.dns + if (dns && dns.length > 0) { + let i = dns.length; + while (i--) { + const element = dns[i]; + let host = "trade-acs.m.taobao.com" + if (element.host == host) { + element.ips = [] + if (consoleLog) console.log(JSON.stringify(element)) + } + } + } + body = $base64.encode(JSON.stringify(obj)) + $done({ body }) + } else { + let headers = $request.headers + let body = $request.body + if (headers["User-Agent"].indexOf("%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D") != -1) { + let json = Qs2Json(body) + let domain = json.domain.split(" ") + let i = domain.length; + while (i--) { + const block = "trade-acs.m.taobao.com" + const element = domain[i]; + if (element == block) { + domain.splice(i, 1); + } + } + json.domain = domain.join(" ") + body = Json2Qs(json) + } + $done({ body }) + } +} + +if (url.indexOf(path2) != -1) { + const body = $response.body + let obj = JSON.parse(body) + let item = obj.data.item + let shareUrl = `https://item.taobao.com/item.htm?id=${item.itemId}` + let msg + request_history_price(shareUrl) + .then(data => { + if (data.errno == -1) throw new Error('Whoops!') + msg = data + }) + .catch(error => msg = "暂无价格信息") + .finally(() => { + if (obj.data.apiStack) { + let apiStack = obj.data.apiStack[0] + let value = JSON.parse(apiStack.value) + let tradeConsumerProtection = null + let consumerProtection = null + let trade = null + let vertical = null + if (value.global) { + tradeConsumerProtection = value.global.data.tradeConsumerProtection + consumerProtection = value.global.data.consumerProtection + trade = value.global.data.trade + vertical = value.global.data.vertical + } else { + tradeConsumerProtection = value.tradeConsumerProtection + consumerProtection = value.consumerProtection + trade = value.trade + vertical = value.vertical + } + if (trade && trade.useWap == "true") { + sendNotify(msg) + } else { + if (vertical && vertical.hasOwnProperty("tmallhkDirectSale")) { + sendNotify(msg) + } else if (tradeConsumerProtection) { + tradeConsumerProtection = setTradeConsumerProtection(msg, tradeConsumerProtection) + } else { + consumerProtection = setConsumerProtection(msg, consumerProtection) + } + apiStack.value = JSON.stringify(value) + } + } else { + sendNotify(msg) + } + $done({ body: JSON.stringify(obj) }) + }) +} + +function sendNotify(data) { + if (data.data.PricesHistory == null) { + $tool.notify("", "", `暂无历史价格`) + } else { + const detail = priceSummary(data.data) + $tool.notify("", "", `${detail}`) + } +} + +function setConsumerProtection(data, consumerProtection) { + let basicService = consumerProtection.serviceProtection.basicService + let items = consumerProtection.items + if (data.data.PricesHistory == null) { + let item = customItem("暂无历史价格", []) + basicService.services.unshift(item) + items.unshift(item) + } else { + const summary = priceSummary(data.data)[1] + const item = customItem("🌨 历史价格详情", [`${summary}`]) + basicService.services.unshift(item) + items.unshift(item) + } + return consumerProtection +} + +function setTradeConsumerProtection(data, tradeConsumerProtection) { + let service = tradeConsumerProtection.tradeConsumerService.service + if (data.data.PricesHistory == null) { + service.items.unshift(customItem("暂无历史价格", "")) + } else { + const tbitems = priceSummary(data.data)[0] + let nonService = tradeConsumerProtection.tradeConsumerService.nonService + service.items = service.items.concat(nonService.items) + nonService.title = "🌨 历史价格详情" + nonService.items = tbitems + } + return tradeConsumerProtection +} + +function priceSummary(data) { + let summary = `当前: ${data.CurrentPrice}${getSpace(8)}最低: ${data.LowestPrice} (${data.LowestDate})` + let tbitems = [customItem(summary)] + const list = historySummary(data.PricesHistory) + list.forEach((item, index) => { + summary += `\n${item.Name}${getSpace(4)}${item.Price}${getSpace(4)}${item.Date}${getSpace(4)}${item.Difference}` + let summaryItem = `${item.Name}${getSpace(3)}${item.Price}${getSpace(3)}${item.Date}${getSpace(3)}${item.Difference}` + tbitems.push(customItem(summaryItem)) + }); + return [tbitems, summary] +} + +function historySummary(list) { + let currentPrice, lowest30, lowest90, lowest180, lowest360, price11, price618; + list = list.reverse().slice(0, 360); + list.forEach((item, index) => { + const date = item.Date; + let price = item.Price; + if (index == 0) { + currentPrice = price; + price618 = { + Name: "六一八价格", + Price: "-", + Date: "-", + Difference: "-", + price: "-", + }; + price11 = { + Name: "双十一价格", + Price: "-", + Date: "-", + Difference: "-", + price: "-", + }; + lowest30 = { + Name: "三十天最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest90 = { + Name: "九十天最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest180 = { + Name: "一百八最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + lowest360 = { + Name: "三百六最低", + Price: `¥${String(price)}`, + Date: date, + Difference: difference(currentPrice, price), + price, + }; + } + if (date.indexOf("06-18") != -1) { + price618.price = price; + price618.Price = `¥${String(price)}`; + price618.Date = date; + price618.Difference = difference(currentPrice, price); + } + if (date.indexOf("11-11") != -1) { + price11.price = price; + price11.Price = `¥${String(price)}`; + price11.Date = date; + price11.Difference = difference(currentPrice, price); + } + if (index < 30 && price < lowest30.price) { + lowest30.price = price; + lowest30.Price = `¥${String(price)}`; + lowest30.Date = date; + lowest30.Difference = difference(currentPrice, price); + } + if (index < 90 && price < lowest90.price) { + lowest90.price = price; + lowest90.Price = `¥${String(price)}`; + lowest90.Date = date; + lowest90.Difference = difference(currentPrice, price); + } + if (index < 180 && price < lowest180.price) { + lowest180.price = price; + lowest180.Price = `¥${String(price)}`; + lowest180.Date = date; + lowest180.Difference = difference(currentPrice, price); + } + if (index < 360 && price < lowest360.price) { + lowest360.price = price; + lowest360.Price = `¥${String(price)}`; + lowest360.Date = date; + lowest360.Difference = difference(currentPrice, price); + } + }); + return [lowest30, lowest90, lowest180, lowest360, price618, price11]; +} + +async function request_history_price(share_url) { + const options = { + headers: { + "Content-Type": "application/json; charset=utf-8", + }, + }; + + const priceTrend = new Promise(function (resolve, reject) { + options.url = "https://price.icharle.com/?product_id=" + share_url; + $tool.get(options, function (error, response, data) { + if (!error) { + resolve(JSON.parse(data)); + } else { + reject(error); + } + }); + }); + const priceTrendData = await priceTrend; + return priceTrendData; +} + +function getExactTime(time) { + var date = new Date(time * 1000); + var year = date.getFullYear() + "-"; + var month = + (date.getMonth() + 1 < 10 + ? "0" + (date.getMonth() + 1) + : date.getMonth() + 1) + "-"; + var dates = date.getDate(); + return year + month + dates; +} + +function difference(currentPrice, price) { + let difference = sub(currentPrice, price) + if (difference == 0) { + return "-" + } else { + return `${difference > 0 ? "↑" : "↓"}${String(Math.abs(difference))}` + } +} + +function sub(arg1, arg2) { + return add(arg1, -Number(arg2), arguments[2]); +} + +function add(arg1, arg2) { + arg1 = arg1.toString(), arg2 = arg2.toString(); + var arg1Arr = arg1.split("."), arg2Arr = arg2.split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""; + var maxLen = Math.max(d1.length, d2.length); + var m = Math.pow(10, maxLen); + var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen)); + var d = arguments[2]; + return typeof d === "number" ? Number((result).toFixed(d)) : result; +} + +function getSpace(length) { + let blank = ""; + for (let index = 0; index < length; index++) { + blank += " "; + } + return blank; +} + +function customItem(title, desc) { + return { + icon: "https://s2.ax1x.com/2020/02/16/3STeIJ.png", + title: title, + name: title, + desc: desc + } +} + +function Qs2Json(url) { + var search = url.substring(url.lastIndexOf("?") + 1); + var obj = {}; + var reg = /([^?&=]+)=([^?&=]*)/g; + search.replace(reg, function (rs, $1, $2) { + var name = decodeURIComponent($1); + var val = decodeURIComponent($2); + val = String(val); + obj[name] = val; + return rs; + }); + return obj; +} + +function Json2Qs(json) { + var temp = []; + for (var k in json) { + temp.push(k + "=" + json[k]); + } + return temp.join("&"); +} + +Array.prototype.insert = function (index, item) { + this.splice(index, 0, item); +}; + +Date.prototype.format = function (fmt) { + var o = { + "y+": this.getFullYear(), + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "h+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + "S+": this.getMilliseconds() + }; + for (var k in o) { + if (new RegExp("(" + k + ")").test(fmt)) { + if (k == "y+") { + fmt = fmt.replace(RegExp.$1, ("" + o[k]).substr(4 - RegExp.$1.length)); + } + else if (k == "S+") { + var lens = RegExp.$1.length; + lens = lens == 1 ? 3 : lens; + fmt = fmt.replace(RegExp.$1, ("00" + o[k]).substr(("" + o[k]).length - 1, lens)); + } + else { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + } + } + } + return fmt; +} + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isResponse = typeof $response != "undefined" + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} + +function Base64() { + // private property + _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + // public method for encoding + this.encode = function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + input = _utf8_encode(input); + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + } + return output; + } + // public method for decoding + this.decode = function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + while (i < input.length) { + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + output = _utf8_decode(output); + return output; + } + // private method for UTF-8 encoding + _utf8_encode = function (string) { + string = string.replace(/\r\n/g, "\n"); + var utftext = ""; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + return utftext; + } + // private method for UTF-8 decoding + _utf8_decode = function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + while (i < utftext.length) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if ((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} diff --git a/tb_price_lite.js b/tb_price_lite.js new file mode 100644 index 000000000..57c57e03f --- /dev/null +++ b/tb_price_lite.js @@ -0,0 +1,402 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const $tool = new Tool() +const consoleLog = false +const url = $request.url +const path1 = "/amdc/mobileDispatch" +const path2 = "/gw/mtop.taobao.detail.getdetail" + +if (url.indexOf(path1) != -1) { + if ($tool.isResponse) { + const $base64 = new Base64() + let body = $response.body + let obj = JSON.parse($base64.decode(body)) + let dns = obj.dns + if (dns && dns.length > 0) { + let i = dns.length; + while (i--) { + const element = dns[i]; + let host = "trade-acs.m.taobao.com" + if (element.host == host) { + element.ips = [] + if (consoleLog) console.log(JSON.stringify(element)) + } + } + } + body = $base64.encode(JSON.stringify(obj)) + $done({ body }) + } else { + let headers = $request.headers + let body = $request.body + if (headers["User-Agent"].indexOf("%E6%89%8B%E6%9C%BA%E6%B7%98%E5%AE%9D") != -1) { + let json = Qs2Json(body) + let domain = json.domain.split(" ") + let i = domain.length; + while (i--) { + const block = "trade-acs.m.taobao.com" + const element = domain[i]; + if (element == block) { + domain.splice(i, 1); + } + } + json.domain = domain.join(" ") + body = Json2Qs(json) + } + $done({ body }) + } +} + +if (url.indexOf(path2) != -1) { + const body = $response.body + $done({ body }) + const obj = JSON.parse(body) + let item = obj.data.item + let shareUrl = `https://item.taobao.com/item.htm?id=${item.itemId}` + requestPrice(shareUrl, function (data) { + if (data) { + if (data.ok == 1 && data.single) { + const lower = lowerMsgs(data.single) + const detail = priceSummary(data) + const tip = data.PriceRemark.Tip + $tool.notify("", "", `${lower}\n${tip}${detail}`) + } + if (data.ok == 0 && data.msg.length > 0) { + $tool.notify("", "", `⚠️ ${data.msg}`) + } + } + }) +} + +function lowerMsgs(data) { + const lower = data.lowerPriceyh + const lowerDate = dateFormat(data.lowerDateyh) + const lowerMsg = "🍵 历史最低到手价:¥" + String(lower) + ` (${lowerDate}) ` + return lowerMsg +} + +function priceSummary(data) { + let summary = "" + let listPriceDetail = data.PriceRemark.ListPriceDetail.slice(0,4) + let list = listPriceDetail.concat(historySummary(data.single)) + list.forEach((item, index) => { + if (item.Name == "双11价格") { + item.Name = "双十一价格" + } else if (item.Name == "618价格") { + item.Name = "六一八价格" + } + let price = String(parseInt(item.Price.substr(1))); + summary += `\n${item.Name} ${isNaN(price) ? "-" : "¥" + price} ${item.Date} ${item.Difference}` + }) + return summary +} + +function historySummary(single) { + const rexMatch = /\[.*?\]/g; + const rexExec = /\[(.*),(.*),"(.*)".*\]/; + let currentPrice, lowest30, lowest90, lowest180, lowest360 + let list = single.jiagequshiyh.match(rexMatch); + list = list.reverse().slice(0, 360); + list.forEach((item, index) => { + if (item.length > 0) { + const result = rexExec.exec(item); + const dateUTC = new Date(eval(result[1])); + const date = dateUTC.format("yyyy-MM-dd"); + let price = parseFloat(result[2]); + if (index == 0) { + currentPrice = price + lowest30 = { Name: "三十天最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest90 = { Name: "九十天最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest180 = { Name: "一百八最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + lowest360 = { Name: "三百六最低", Price: `¥${String(price)}`, Date: date, Difference: difference(currentPrice, price), price } + } + if (index < 30 && price < lowest30.price) { + lowest30.price = price + lowest30.Price = `¥${String(price)}` + lowest30.Date = date + lowest30.Difference = difference(currentPrice, price) + } + if (index < 90 && price < lowest90.price) { + lowest90.price = price + lowest90.Price = `¥${String(price)}` + lowest90.Date = date + lowest90.Difference = difference(currentPrice, price) + } + if (index < 180 && price < lowest180.price) { + lowest180.price = price + lowest180.Price = `¥${String(price)}` + lowest180.Date = date + lowest180.Difference = difference(currentPrice, price) + } + if (index < 360 && price < lowest360.price) { + lowest360.price = price + lowest360.Price = `¥${String(price)}` + lowest360.Date = date + lowest360.Difference = difference(currentPrice, price) + } + } + }); + return [lowest30, lowest90, lowest180]; +} + +function difference(currentPrice, price) { + let difference = sub(currentPrice, price) + if (difference == 0) { + return "-" + } else { + return `${difference > 0 ? "↑" : "↓"}${String(difference)}` + } +} + +function sub(arg1, arg2) { + return add(arg1, -Number(arg2), arguments[2]); +} + +function add(arg1, arg2) { + arg1 = arg1.toString(), arg2 = arg2.toString(); + var arg1Arr = arg1.split("."), arg2Arr = arg2.split("."), d1 = arg1Arr.length == 2 ? arg1Arr[1] : "", d2 = arg2Arr.length == 2 ? arg2Arr[1] : ""; + var maxLen = Math.max(d1.length, d2.length); + var m = Math.pow(10, maxLen); + var result = Number(((arg1 * m + arg2 * m) / m).toFixed(maxLen)); + var d = arguments[2]; + return typeof d === "number" ? Number((result).toFixed(d)) : result; +} + +function requestPrice(share_url, callback) { + const options = { + url: "https://apapia-history.manmanbuy.com/ChromeWidgetServices/WidgetServices.ashx", + headers: { + "Content-Type": "application/x-www-form-urlencoded;charset=utf-8", + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 - mmbWebBrowse - ios" + }, + body: "methodName=getHistoryTrend&p_url=" + encodeURIComponent(share_url) + } + $tool.post(options, function (error, response, data) { + if (!error) { + callback(JSON.parse(data)); + if (consoleLog) console.log("Data:\n" + data); + } else { + callback(null, null); + if (consoleLog) console.log("Error:\n" + error); + } + }) +} + +function dateFormat(cellval) { + const date = new Date(parseInt(cellval.replace("/Date(", "").replace(")/", ""), 10)); + const month = date.getMonth() + 1 < 10 ? "0" + (date.getMonth() + 1) : date.getMonth() + 1; + const currentDate = date.getDate() < 10 ? "0" + date.getDate() : date.getDate(); + return date.getFullYear() + "-" + month + "-" + currentDate; +} + +function Qs2Json(url) { + var search = url.substring(url.lastIndexOf("?") + 1); + var obj = {}; + var reg = /([^?&=]+)=([^?&=]*)/g; + search.replace(reg, function (rs, $1, $2) { + var name = decodeURIComponent($1); + var val = decodeURIComponent($2); + val = String(val); + obj[name] = val; + return rs; + }); + return obj; +} + +function Json2Qs(json) { + var temp = []; + for (var k in json) { + temp.push(k + "=" + json[k]); + } + return temp.join("&"); +} + +Array.prototype.insert = function (index, item) { + this.splice(index, 0, item); +}; + +Date.prototype.format = function (fmt) { + var o = { + "y+": this.getFullYear(), + "M+": this.getMonth() + 1, + "d+": this.getDate(), + "h+": this.getHours(), + "m+": this.getMinutes(), + "s+": this.getSeconds(), + "q+": Math.floor((this.getMonth() + 3) / 3), + "S+": this.getMilliseconds() + }; + for (var k in o) { + if (new RegExp("(" + k + ")").test(fmt)) { + if (k == "y+") { + fmt = fmt.replace(RegExp.$1, ("" + o[k]).substr(4 - RegExp.$1.length)); + } + else if (k == "S+") { + var lens = RegExp.$1.length; + lens = lens == 1 ? 3 : lens; + fmt = fmt.replace(RegExp.$1, ("00" + o[k]).substr(("" + o[k]).length - 1, lens)); + } + else { + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + } + } + } + return fmt; +} + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isResponse = typeof $response != "undefined" + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} + +function Base64() { + // private property + _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; + // public method for encoding + this.encode = function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + input = _utf8_encode(input); + while (i < input.length) { + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + output = output + + _keyStr.charAt(enc1) + _keyStr.charAt(enc2) + + _keyStr.charAt(enc3) + _keyStr.charAt(enc4); + } + return output; + } + // public method for decoding + this.decode = function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + while (i < input.length) { + enc1 = _keyStr.indexOf(input.charAt(i++)); + enc2 = _keyStr.indexOf(input.charAt(i++)); + enc3 = _keyStr.indexOf(input.charAt(i++)); + enc4 = _keyStr.indexOf(input.charAt(i++)); + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + output = output + String.fromCharCode(chr1); + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + } + output = _utf8_decode(output); + return output; + } + // private method for UTF-8 encoding + _utf8_encode = function (string) { + string = string.replace(/\r\n/g, "\n"); + var utftext = ""; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + return utftext; + } + // private method for UTF-8 decoding + _utf8_decode = function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + while (i < utftext.length) { + c = utftext.charCodeAt(i); + if (c < 128) { + string += String.fromCharCode(c); + i++; + } else if ((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i + 1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } else { + c2 = utftext.charCodeAt(i + 1); + c3 = utftext.charCodeAt(i + 2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + } + return string; + } +} diff --git a/tool.js b/tool.js new file mode 100644 index 000000000..236a0b9f1 --- /dev/null +++ b/tool.js @@ -0,0 +1,167 @@ +// 统一 Surge、QuanX、Node 相关脚本 API, 方便开发、调试 +// 包括 Node(request 模块)、Surge($httpClient,$notification,$persistentStore 模块)、QuanX($task,$notify,$prefs 模块) + +const $tool = new Tool() +// notify +$tool.notify("title", "subtitle", "message") +// cache +$tool.write("value", "key") +$tool.read("key") +// get +$tool.get("http://www.baidu.com", function (error, response, body) { + // response.status response.statusCode response.headers + if (!error) { + if (response.statusCode == 200) { + console.log(body) + } + } else { + console.log(error) + } +}) +// post +const request = { + url: "https://www.baidu.com", + body: "" + //... +} +$tool.post(request, function (error, response, body) { + // response.status response.statusCode response.headers + if (!error) { + if (response.statusCode == 200) { + console.log(body) + } + } else { + console.log(error) + } +}) + +function Tool() { + _node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + _isSurge = typeof $httpClient != "undefined" + _isQuanX = typeof $task != "undefined" + this.isSurge = _isSurge + this.isQuanX = _isQuanX + this.isResponse = typeof $response != "undefined" + this.notify = (title, subtitle, message) => { + if (_isQuanX) $notify(title, subtitle, message) + if (_isSurge) $notification.post(title, subtitle, message) + if (_node) console.log(JSON.stringify({ title, subtitle, message })); + } + this.write = (value, key) => { + if (_isQuanX) return $prefs.setValueForKey(value, key) + if (_isSurge) return $persistentStore.write(value, key) + } + this.read = (key) => { + if (_isQuanX) return $prefs.valueForKey(key) + if (_isSurge) return $persistentStore.read(key) + } + this.get = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.get(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request(options, (error, response, body) => { callback(error, _status(response), body) }) + } + this.post = (options, callback) => { + if (_isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { callback(null, _status(response), response.body) }, reason => callback(reason.error, null, null)) + } + if (_isSurge) $httpClient.post(options, (error, response, body) => { callback(error, _status(response), body) }) + if (_node) _node.request.post(options, (error, response, body) => { callback(error, _status(response), body) }) + } + _status = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } +} + +function tool() { + const isSurge = typeof $httpClient != "undefined" + const isQuanX = typeof $task != "undefined" + const isResponse = typeof $response != "undefined" + const node = (() => { + if (typeof require == "function") { + const request = require('request') + return ({ request }) + } else { + return (null) + } + })() + const notify = (title, subtitle, message) => { + if (isQuanX) $notify(title, subtitle, message) + if (isSurge) $notification.post(title, subtitle, message) + if (node) console.log(JSON.stringify({ title, subtitle, message })); + } + const write = (value, key) => { + if (isQuanX) return $prefs.setValueForKey(value, key) + if (isSurge) return $persistentStore.write(value, key) + } + const read = (key) => { + if (isQuanX) return $prefs.valueForKey(key) + if (isSurge) return $persistentStore.read(key) + } + const adapterStatus = (response) => { + if (response) { + if (response.status) { + response["statusCode"] = response.status + } else if (response.statusCode) { + response["status"] = response.statusCode + } + } + return response + } + const get = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "GET" + $task.fetch(options).then(response => { + callback(null, adapterStatus(response), response.body) + }, reason => callback(reason.error, null, null)) + } + if (isSurge) $httpClient.get(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + if (node) { + node.request(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + } + const post = (options, callback) => { + if (isQuanX) { + if (typeof options == "string") options = { url: options } + options["method"] = "POST" + $task.fetch(options).then(response => { + callback(null, adapterStatus(response), response.body) + }, reason => callback(reason.error, null, null)) + } + if (isSurge) { + $httpClient.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + if (node) { + node.request.post(options, (error, response, body) => { + callback(error, adapterStatus(response), body) + }) + } + } + return { isQuanX, isSurge, isResponse, notify, write, read, get, post } +} \ No newline at end of file diff --git a/wb_ad.js b/wb_ad.js new file mode 100644 index 000000000..3de4ce491 --- /dev/null +++ b/wb_ad.js @@ -0,0 +1,293 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const path1 = "/groups/timeline"; +const path2 = "/statuses/unread"; +const path3 = "/statuses/extend"; +const path4 = "/comments/build_comments"; +const path5 = "/photo/recommend_list"; +const path6 = "/stories/video_stream"; +const path7 = "/statuses/positives/get"; +const path8 = "/stories/home_list"; +const path9 = "/profile/statuses"; +const path10 = "/statuses/friends/timeline"; +const path11 = "/service/picfeed"; +const path12 = "/fangle/timeline"; +const path13 = "/searchall"; +const path14 = "/cardlist"; +const path15 = "/statuses/video_timeline"; +const path16 = "/page"; +const path17 = "/statuses/friends_timeline"; +const path18 = "/!/photos/pic_recommend_status"; +const path19 = "/statuses/video_mixtimeline"; +const path20 = "/video/tiny_stream_video_list"; +const path21 = "/photo/info"; +const path22 = "/live/media_homelist"; +const path23 = "/remind/unread_count"; +const path24 = "/search/container_timeline" +const path25 = "/messageflow/notice" +const path26 = "/statuses/container_timeline_hot" +const path27 = "/search/finder" +const path28 = "/statuses/container_timeline_unread" +const path29 = "/statuses/container_timeline" +const path30 = "/profile/container_timeline" + +const url = $request.url; +let body = $response.body; + +if ( + url.indexOf(path1) != -1 || + url.indexOf(path2) != -1 || + url.indexOf(path10) != -1 || + url.indexOf(path15) != -1 || + url.indexOf(path17) != -1 || + url.indexOf(path20) != -1 +) { + let obj = JSON.parse(body); + if (obj.statuses) obj.statuses = filter_timeline_statuses(obj.statuses); + if (obj.advertises) obj.advertises = []; + if (obj.ad) obj.ad = []; + if (obj.num) obj.num = obj.original_num; + if (obj.trends) obj.trends = []; + body = JSON.stringify(obj); +} else if (url.indexOf(path3) != -1) { + let obj = JSON.parse(body); + if (obj.trend) delete obj.trend; + body = JSON.stringify(obj); +} else if (url.indexOf(path4) != -1) { + let obj = JSON.parse(body); + obj.recommend_max_id = 0; + if (obj.status) { + if (obj.top_hot_structs) { + obj.max_id = obj.top_hot_structs.call_back_struct.max_id; + delete obj.top_hot_structs; + } + if (obj.datas) obj.datas = filter_comments(obj.datas); + } else { + obj.datas = []; + } + body = JSON.stringify(obj); +} else if (url.indexOf(path5) != -1 || url.indexOf(path18) != -1) { + let obj = JSON.parse(body); + obj.data = {}; + body = JSON.stringify(obj); +} else if (url.indexOf(path6) != -1) { + let obj = JSON.parse(body); + let segments = obj.segments; + if (segments && segments.length > 0) { + let i = segments.length; + while (i--) { + const element = segments[i]; + let is_ad = element.is_ad; + if (is_ad && is_ad == true) segments.splice(i, 1); + } + } + body = JSON.stringify(obj); +} else if (url.indexOf(path7) != -1) { + let obj = JSON.parse(body); + obj.datas = []; + body = JSON.stringify(obj); +} else if (url.indexOf(path8) != -1) { + let obj = JSON.parse(body); + obj.story_list = []; + body = JSON.stringify(obj); +} else if (url.indexOf(path11) != -1 || url.indexOf(path22) != -1) { + let obj = JSON.parse(body); + obj.data = []; + body = JSON.stringify(obj); +} else if ( + url.indexOf(path9) != -1 || + url.indexOf(path12) != -1 || + url.indexOf(path13) != -1 || + url.indexOf(path14) != -1 || + url.indexOf(path16) != -1 +) { + let obj = JSON.parse(body); + if (obj.cards) obj.cards = filter_timeline_cards(obj.cards); + body = JSON.stringify(obj); +} else if (url.indexOf(path19) != -1) { + let obj = JSON.parse(body); + delete obj.expandable_view; + if (obj.hasOwnProperty("expandable_views")) delete obj.expandable_views; + body = JSON.stringify(obj); +} else if (url.indexOf(path21) != -1) { + if (body.indexOf("ad_params") != -1) { + body = JSON.stringify({}); + } +} else if (url.indexOf(path23) != -1) { + let obj = JSON.parse(body); + obj.video = {}; + body = JSON.stringify(obj); +} else if (url.indexOf(path24) != -1) { + let obj = JSON.parse(body); + filter_items_feed(obj) + body = JSON.stringify(obj); +} else if (url.indexOf(path25) != -1) { + let obj = JSON.parse(body); + filter_messageflow_notice(obj) + body = JSON.stringify(obj); +} else if ( url.indexOf(path30) != -1 || url.indexOf(path26) != -1 || url.indexOf(path28) != -1 || url.indexOf(path29) != -1) { + let obj = JSON.parse(body); + filter_items_feed(obj) + body = JSON.stringify(obj); +} else if (url.indexOf(path27) != -1) { + let obj = JSON.parse(body); + filter_search_finder(obj) + body = JSON.stringify(obj); +} + +$done({ body }); + +function filter_search_finder(data) { + channels = data["channelInfo"]["channels"] + for (let index = 0; index < channels.length; index++) { + const element = channels[index]; + if (element["en_name"] == "Discover") { + filter_finder_items(element["payload"]) + } + } +} + +function filter_finder_items(data) { + items = data["items"] + for (let index = items.length-1; index >= 0; index--) { + const item = items[index]; + if (item["category"] == "card") { + type = item["data"]["card_type"] + if (type == 118) { + items.splice(index,1) + } + }else if (item["category"] == "feed") { + type = item["data"]["mblogtype"] + if (type == 1) { + items.splice(index,1) + } + } + } +} + +function filter_messageflow_notice(data) { + items = data["messages"] + for (let index = items.length-1; index >= 0; index--) { + const item = items[index]; + if (item["isrecommend"] == true) { + items.splice(index,1) + } + } +} + +function filter_items_feed(data) { + items = data["items"] + for (let index = items.length-1; index >= 0; index--) { + const item = items[index]; + if (item["category"] == "feed") { + type = item["data"]["mblogtype"] + if (type == 1) { + items.splice(index,1) + } + } + } +} + +function filter_timeline_statuses(statuses) { + if (statuses && statuses.length > 0) { + let i = statuses.length; + while (i--) { + let element = statuses[i]; + if ( + is_timeline_likerecommend(element.title) || + is_timeline_ad(element) || + is_stream_video_ad(element) + ) { + statuses.splice(i, 1); + } + } + } + return statuses; +} + +function filter_comments(datas) { + if (datas && datas.length > 0) { + let i = datas.length; + while (i--) { + const element = datas[i]; + const type = element.type; + if (type == 5 || type == 1 || type == 6) datas.splice(i, 1); + } + } + return datas; +} + +function filter_timeline_cards(cards) { + if (cards && cards.length > 0) { + let j = cards.length; + while (j--) { + let item = cards[j]; + let card_group = item.card_group; + if (card_group && card_group.length > 0) { + if (item.itemid && item.itemid == "hotword") { + filter_top_search(card_group); + } else { + let i = card_group.length; + while (i--) { + let card_group_item = card_group[i]; + let card_type = card_group_item.card_type; + if (card_type) { + if (card_type == 9) { + if (is_timeline_ad(card_group_item.mblog)) + card_group.splice(i, 1); + } else if (card_type == 118 || card_type == 89) { + card_group.splice(i, 1); + } else if (card_type == 42) { + if ( + card_group_item.desc == + "\u53ef\u80fd\u611f\u5174\u8da3\u7684\u4eba" + ) { + cards.splice(j, 1); + break; + } + } else if (card_type == 17) { + filter_top_search(card_group_item.group); + } + } + } + } + } else { + let card_type = item.card_type; + if (card_type && card_type == 9) { + if (is_timeline_ad(item.mblog)) cards.splice(j, 1); + } + } + } + } + return cards; +} + +function filter_top_search(group) { + if (group && group.length > 0) { + let k = group.length; + while (k--) { + let group_item = group[k]; + if (group_item.hasOwnProperty("promotion")) { + group.splice(k, 1); + } + } + } +} + +function is_timeline_ad(mblog) { + if (!mblog) return false; + let promotiontype = + mblog.promotion && mblog.promotion.type && mblog.promotion.type == "ad"; + let mblogtype = mblog.mblogtype && mblog.mblogtype == 1; + return promotiontype || mblogtype ? true : false; +} + +function is_timeline_likerecommend(title) { + return title && title.type && title.type == "likerecommend" ? true : false; +} + +function is_stream_video_ad(item) { + return item.ad_state && item.ad_state == 1; +} diff --git a/wb_launch.js b/wb_launch.js new file mode 100644 index 000000000..6e2a613dd --- /dev/null +++ b/wb_launch.js @@ -0,0 +1,27 @@ +/* +README:https://github.com/yichahucha/surge/tree/master + */ + +const path1 = "/interface/sdk/sdkad.php"; +const path2 = "/wbapplua/wbpullad.lua"; + +const url = $request.url; +var body = $response.body; + +if (url.indexOf(path1) != -1) { + let re = /\{.*\}/; + body = body.match(re); + var obj = JSON.parse(body); + if (obj.background_delay_display_time) obj.background_delay_display_time = 60*60*24*365; + if (obj.show_push_splash_ad) obj.show_push_splash_ad = false; + if (obj.ads) obj.ads = []; + body = JSON.stringify(obj) + 'OK'; +} + +if (url.indexOf(path2) != -1) { + var obj = JSON.parse(body); + if (obj.cached_ad && obj.cached_ad.ads) obj.cached_ad.ads = []; + body = JSON.stringify(obj); +} + +$done({body});