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))[1] || '' : '';
+ const numRaters = s ? s[0].split(/(\d+)\u4eba\u8bc4\u4ef7/)[1] || '' : '';
+ const rating_message = `Douban: ⭐️ ${average ? average + "/10" : "N/A"} ${!numRaters ? "" : parseFloat(numRaters).toLocaleString()}`;
+ return rating_message;
+}
+
+function get_country_message(data) {
+ const country = data;
+ const countrys = country.split(", ");
+ let emoji_country = "";
+ countrys.forEach(item => {
+ 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});