├── .gitignore ├── LICENSE ├── README.md ├── bilibili ├── README.md ├── fans.js └── screenshot.jpg ├── bing ├── README.md ├── latest.js └── screenshot.jpg ├── dev ├── README.md ├── app.js ├── debug.js ├── package-lock.json ├── package.json └── www │ └── latest.js ├── loader.gitee.js ├── loader.github.js ├── one ├── README.md ├── latest.js └── screenshot.jpg ├── template.example.js ├── update.notify.json ├── v2ex ├── README.md ├── api.js ├── go.js ├── latest.js ├── screenshot.jpg └── tab.js ├── weibo ├── README.md ├── latest.js └── screenshot.jpg ├── welcome ├── README.md └── latest.js ├── zhihu ├── README.md ├── billboard.js ├── latest.js └── screenshot.jpg ├── 一言 ├── README.md ├── latest.js └── screenshot.jpg ├── 今天是周五吗 ├── README.md ├── latest.js └── screenshot.jpeg ├── 彩云天气 ├── README.md └── latest.js ├── 彩票开奖 ├── README.md ├── latest.js └── 彩票开奖.jpg ├── 微信扫一扫 ├── README.md ├── latest.js └── 微信扫一扫.jpg ├── 支付宝盒子 ├── README.md ├── latest.js └── 支付宝盒子.jpg ├── 毒鸡汤 ├── README.md ├── latest.js └── screenshot.jpg ├── 知乎日报 ├── README.md ├── latest.js └── screenshot.jpg ├── 网易云音乐 ├── README.md ├── latest.js ├── screenshot.jpg └── 热评.js └── 锦囊妙计 ├── README.md ├── latest.js ├── screenshot1.jpg └── screenshot2.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | dev/node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scriptables 2 | iOS14桌面组件神器(Scriptable)原创框架,脚本开发教程、精美作品分享! 3 | 本项目的框架和插件代码,都是本人手机上一个一个字符屏幕敲出来,一行一行代码调试的用心作品 4 | 如果能得到您的喜欢,欢迎点个 ★ Star ★ 给予小支持,感谢您的使用,也同时欢迎大家一起参与改进完善 ❤️ 5 | 6 | 👉 [点击查看 5 分钟演示视频](https://v.qq.com/txp/iframe/player.html?chid=17&vid=c31599njg4i&autoplay=true&full=true&show1080p=true) 7 | 8 | ![screenshot.jpg](https://i.loli.net/2020/10/13/hTVMr3EWO1xCNGL.jpg) 9 | 10 | # 快速使用 11 | 1. iPhone 上下载 [Scriptable](https://apps.apple.com/cn/app/scriptable/id1405459188) App(确保你的系统已更新为 iOS14+) 12 | 2. Safari点击下载:[国内Gitee源,推荐](https://im3x.cn/scriptables/Loader.Gitee.scriptable)、[GitHub版源](https://im3x.cn/scriptables/Loader.Github.scriptable),然后点击下载的文件,用`Scriptable` App打开 13 | 2. 手动版:打开App,点击右上角 + 号,复制项目中对应的 [loader.github.js](loader.github.js) 或 [loader.gitee.js](loader.gitee.js) 代码 14 | 3. 长按桌面,添加组件,选择 `Scriptable`,然后点击组件配置,选择刚刚保存的脚本,下方的参数格式为:`插件名@版本号:自定义参数` 15 | 例如,我要显示`one`每日图文组件,配置下方输入`one`或`one@latest`即可(显示昨天的文章输入配置`one:1`,依此类推)。 16 | 17 | ![](https://i.loli.net/2020/10/12/xf5utXvBWdC3F1g.jpg) 18 | 19 | **更多插件的配置参数,请查阅插件目录的`README.md`说明** 20 | 21 | # 组件列表 22 | > 参数的意思,就是添加桌面组件时,选择加载器,然后下方参数中输入的内容 23 | > 更多参数配置,可以进入项目代码库中组件的目录,有更详细说明 24 | 25 | |名称|说明|示例参数| 26 | |---|---|---| 27 | |[one](one/)|韩寒ONE·一个图文|`one`| 28 | |[v2ex](v2ex/)|V2EX 最新、最热文章|`v2ex@api`| 29 | |[bing](bing/)|Bing 每日必应壁纸|`bing`| 30 | |[zhihu](zhihu/)|知乎热榜等 |`zhihu`| 31 | |[weibo](weibo/)|微博热搜榜|`weibo`| 32 | |[一言](一言/)|随机更新一句话|`一言`| 33 | |[毒鸡汤](毒鸡汤/)|随机更新一条毒鸡汤|`毒鸡汤`| 34 | |[彩云天气](彩云天气/)|显示当前位置的天气预报情况|`彩云天气`| 35 | |[彩票开奖](彩票开奖/)|展示最近一起开奖内容|`彩票开奖`| 36 | |[支付宝盒子](支付宝盒子/)|展示、打开支付宝常用功能|`支付宝盒子`| 37 | |[微信扫一扫](微信扫一扫/)|展示、打开微信扫一扫功能|`微信扫一扫`| 38 | |[知乎日报](知乎日报/)|显示知乎日报文章信息|`知乎日报`| 39 | |[网易云音乐](网易云音乐/)|网易云音乐热评等|`网易云音乐@热评`| 40 | |[bilibili](bilibili/)|哔哩哔哩粉丝数|`bilibili@fans:446791792`| 41 | |[今天是周五吗](今天是周五吗/)|即刻今天是周五吗|`今天是周五吗`| 42 | |[锦囊妙计](锦囊妙计/)|锦囊妙计 一个包含众多骚话的插件|`锦囊妙计` 或 `锦囊妙计:舔狗日记` 或 `锦囊妙计:网抑云` ...| 43 | 44 | ## 框架优势 45 | 1. 简单方便,无需像其他组件一样,一个个复制代码编辑保存添加。框架直接添加一个加载器,后续全靠灵活的配置 46 | 2. 灵活更新,插件有新功能,直接在线更新最新版本,而无需再手动复制代码保存等操作 47 | 3. 功能强劲,经过多次的代码重构,解决了无数个坑的经验,保证了插件的更稳定运行环境 48 | 4. 开发速度,直接套用模板,获取数据、展示数据,非常的简单, **插件代码还可以直接单独脱离框架执行** 49 | 50 | ## 插件开发 51 | 每一个项目,都创建一个文件夹,可以是中英文,最好不要有其他特殊符号。 52 | 文件夹中存放该项目的版本号等文件,比如: 53 | 1. `latest.js` 最新版本代码文件 54 | 2. `README.md` 插件说明使用文档 55 | 3. `v2.0.0.js` 其他版本或功能区分文件 56 | 57 | 测试的时候,添加桌面组件,选择加载器,然后参数输入格式:`项目文件夹名@版本号:参数`,比如`v2ex`项目中有个`api.js`代码文件,传递:`v2ex@api:hot`类似格式的配置,也可以直接输入项目名即可(版本号默认latest,参数默认脚本定义) 58 | 59 | 插件代码,请直接复制项目中的`template.sample.js`模板编辑 60 | 61 | 62 | **2020/10/13 17:00更新:加载器支持输入第三方开发者的插件配置** 63 | 64 | 比如开发者 `user-a`, fork 了本项目到自己的仓库,编辑了 `welcome/latest.js` 组件脚本,那么用户在添加桌面组件时,输入参数:`user-a/welcome@latest` 即可加载第三方开发者仓库的组件代码。 65 | 66 | > 注意:由于用户可能使用的是`Gitee`源加载器,所以你应该也需要前往 [Gitee](https://gitee.com),创建同名用户+仓库,同步 github 仓库项目 67 | 68 | (和之前的参数区别就是前边多了 `用户名/` 格式) 69 | 70 | 开发者可以直接复制 [template.sample.js](template.sample.js) 代码,打开 Scriptable 应用,添加粘贴代码,然后根据底部的注释取消注释相关代码,即可进行测试、独立运行操作。 71 | 待测试完毕,可直接上传到自己的仓库项目中,然后根据上边方法进行远程加载测试。 72 | 73 | 如果测试都没问题,可以直接向本项目PR更改,谢谢每一位参与研究的开发者! 74 | 75 | ## 教程系列 76 | > 公众号集合文章:[#scriptable](https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzI5NTIwMDQxOA==&action=getalbum&album_id=1546917207903928321&scene=173#wechat_redirect) 77 | 78 | 1. [Scriptable 神器试玩,创建一个显示自定义标题内容的 iOS14桌面组件](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484293&idx=1&sn=128fd10f72e8bf0778d9e7575fa85c4a&chksm=ec567048db21f95eb223ad4504405de12612b94f5caa4c4cd611c448ee3b374a059d66c7acbe&scene=178#rd) 79 | 2. [Scriptable 神器实战2 —— 给桌面组件添加自定义背景图片](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484299&idx=1&sn=cddb9bc6af87eb8b63fb2b893e382111&chksm=ec567046db21f950b700d5845fe3778099c3888983ffd0a173f3f2dde7092bf3f862161add90&scene=178#rd) 80 | 3. [Scriptable 神器实战3 —— 夜间模式动态展示](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484312&idx=1&sn=967781d268224b794a21ddb940324f77&chksm=ec567055db21f943979e092ebb4195864590212393b1b8f7f5b3d4ea84f7fdf11eec7f56b48f&scene=178#rd) 81 | 4. [Scriptable 神器实战4 —— 获取桌面组件的大小以自动展示内容](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484324&idx=1&sn=b7bc2a4a513f719ce6e6423d03ba6803&chksm=ec567069db21f97ff3407d053aa708d408058c525d1cb9fc80a64ce9ca0e6b9133f4568a20e0&scene=178#rd) 82 | 5. [Scriptable 神器实战5 —— 给桌面组件添加一个渐变色背景](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484331&idx=1&sn=82802dd0d11fee43587f71cac6ce6109&chksm=ec567066db21f970ac13bf9ff902cee43475919a9e136a16eb2766c9cef5a6518b6d14bcab57&scene=178#rd) 83 | 6. [Scriptable神器实战6 —— 给背景图片加上半透明遮罩](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484345&idx=1&sn=7ebaa57bdf09ca5517b9ca58a12f88b8&chksm=ec567074db21f96234bd0591530b02c0c9bb3c951923fee19ebbb877e52aac56d5b8bd4d27af&scene=178#rd) 84 | 7. [Scriptable神器实战7 —— 获取用户添加组件时的自定义参数](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484350&idx=1&sn=f4c5b25b2d9f7e66bdfbe9150e234864&chksm=ec567073db21f965a5164e2ab27bacfc2b246e4be31ca3a4f23fb96e9c5d543e4ac97310b7bc&token=1302596105&lang=zh_CN#rd) 85 | 8. **[Scriptable 实战之 —— 桌面组件交互之王](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484386&idx=1&sn=c88ddafedad97a3bcfed50f92d16ac5a&chksm=ec56702fdb21f939a3b45305a7a9056fd25bbcb41b69d6f9c7ced34ff46f5f32a913d40ba1ae&scene=178#rd)**. 86 | 9. [Scriptable神器实战 8 —— 本地存储 Keychain 用法](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484386&idx=2&sn=1b481bb66e0c373d8fd39dfede72575a&chksm=ec56702fdb21f939f07701aeead4375abc02450177565c96d937f9bfa0262ed12e378bca108a&scene=178#rd) 87 | 10. [Scriptable神器实战 9 —— Base64 数据编码/解码操作](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484392&idx=1&sn=957336092b12715d60261fda1b6f1d4b&chksm=ec567025db21f9338f0d7659a589cac8cf7a286ec1a940fb960cae63da038d29a0f5ee0fdc91&scene=178#rd) 88 | 11. [Scriptable神器实战 10 —— MD5 加密字符串](https://mp.weixin.qq.com/s?__biz=MzI5NTIwMDQxOA==&mid=2247484403&idx=1&sn=9c8e97fa311ed83d4b5e6763d7f7fd07&chksm=ec56703edb21f9288ff6058c170f1423b9a996d6e776eaf86e6c0df7839b28261f87eef08d40&scene=178#rd) 89 | 90 | 更多文章连载更新中,扫一扫关注【古人云】公众号,第一时间获取更新: 91 | ![](https://i.loli.net/2020/10/13/9hXdRNUg5qSreHk.jpg) 92 | -------------------------------------------------------------------------------- /bilibili/README.md: -------------------------------------------------------------------------------- 1 | ![](screenshot.jpg) 2 | 3 | # bilibili 粉丝数 4 | > “实时”显示你的B站粉丝数(不是真正的实时,搞不懂小组件的刷新时间频率) 5 | 6 | # 支持尺寸 7 | 1. 小尺寸 8 | 2. 中尺寸 9 | 10 | # 参数设置 11 | `bilibili@fans:446791792` 12 | 请把“446791792”改成你的B站MID,即个人空间网址后面那段数字 https://space.bilibili.com/446791792 13 | 14 | # 参考资料 15 | API接口: http://api.bilibili.com/x/relation/stat?vmid=446791792 16 | -------------------------------------------------------------------------------- /bilibili/fans.js: -------------------------------------------------------------------------------- 1 | // 哔哩哔哩粉丝数 2 | // 作者:azoon 3 | // 调用参数 bilibili@fans:446791792 4 | 5 | class Im3xWidget { 6 | /** 7 | * 初始化 8 | * @param arg 外部传递过来的参数 9 | */ 10 | constructor (arg) { 11 | this.arg = arg 12 | this.widgetSize = config.widgetFamily 13 | } 14 | 15 | //渲染组件 16 | async render () { 17 | if (this.widgetSize === 'medium') { 18 | return await this.renderSmall() 19 | } else if (this.widgetSize === 'large') { 20 | return await this.renderLarge() 21 | } else { 22 | return await this.renderSmall() 23 | } 24 | } 25 | 26 | //渲染小尺寸组件 27 | async renderSmall () { 28 | let data = await this.getData() 29 | let w = new ListWidget() 30 | 31 | let header = w.addStack() 32 | let icon = header.addImage(await this.getImage('https://www.bilibili.com/favicon.ico')) 33 | icon.imageSize = new Size(15, 15) 34 | header.addSpacer(10) 35 | let title = header.addText("哔哩哔哩粉丝") 36 | title.textOpacity = 0.9 37 | title.font = Font.systemFont(14) 38 | w.addSpacer(20) 39 | 40 | if(data.code !=0){ 41 | var flTxt = w.addText('请填写B站MID') 42 | flTxt.textColor = new Color("#fb7299") 43 | flTxt.font = Font.systemFont(14) 44 | }else{ 45 | var flTxt = w.addText(this.toThousands(data.data['follower'])) 46 | flTxt.textColor = new Color("#fb7299") 47 | flTxt.font = Font.boldRoundedSystemFont(this.getFontsize(data.data['follower'])) 48 | } 49 | flTxt.centerAlignText() 50 | w.addSpacer(20) 51 | 52 | let utTxt = w.addText('更新于:'+this.nowTime()) 53 | utTxt.font = Font.systemFont(12) 54 | utTxt.centerAlignText() 55 | utTxt.textOpacity = 0.5 56 | 57 | w.url = 'bilibili://' 58 | return w 59 | } 60 | 61 | //渲染中尺寸组件 62 | async renderMedium () { 63 | let w = new ListWidget() 64 | w.addText("暂不支持该尺寸组件") 65 | return w 66 | } 67 | 68 | //渲染大尺寸组件 69 | async renderLarge () { 70 | let w = new ListWidget() 71 | w.addText("暂不支持该尺寸组件") 72 | return w 73 | } 74 | 75 | //加载B站数据 76 | async getData () { 77 | let api = 'http://api.bilibili.com/x/relation/stat?vmid='+this.arg 78 | let req = new Request(api) 79 | let res = await req.loadJSON() 80 | return res 81 | } 82 | 83 | //加载远程图片 84 | async getImage (url) { 85 | let req = new Request(url) 86 | return await req.loadImage() 87 | } 88 | 89 | //格式化粉丝数量,加入千分号 90 | toThousands(num) { 91 | return (num || 0).toString().replace(/(\d)(?=(?:\d{3})+$)/g, '$1,'); 92 | } 93 | 94 | //返回脚本运行时的时间,作为更新时间 95 | nowTime(){ 96 | let date = new Date() 97 | return date.toLocaleTimeString('chinese', { hour12: false }) 98 | } 99 | 100 | //根据粉丝数量返回不同的字体大小 101 | getFontsize(num){ 102 | if(num<99){ 103 | return 38 104 | }else if(num<9999 && num>100){ 105 | return 30 106 | }else if(num<99999 && num>10000){ 107 | return 28 108 | }else if(num<999999 && num>100000){ 109 | return 24 110 | }else if(num<9999999 && num>1000000){ 111 | return 22 112 | }else{ 113 | return 20 114 | } 115 | } 116 | 117 | //编辑测试使用 118 | async test(){ 119 | if (config.runsInWidget) return 120 | this.widgetSize = 'small' 121 | let w1 = await this.render() 122 | await w1.presentSmall() 123 | this.widgetSize = 'medium' 124 | let w2 = await this.render() 125 | await w2.presentMedium() 126 | this.widgetSize = 'large' 127 | let w3 = await this.render() 128 | await w3.presentLarge() 129 | } 130 | 131 | //组件单独在桌面运行时调用 132 | async init(){ 133 | if (!config.runsInWidget) return 134 | let widget = await this.render() 135 | Script.setWidget(widget) 136 | Script.complete() 137 | } 138 | } 139 | 140 | module.exports = Im3xWidget 141 | 142 | // 如果是在编辑器内编辑、运行、测试,则取消注释这行,便于调试: 143 | //await new Im3xWidget().test() 144 | 145 | // 如果是组件单独使用(桌面配置选择这个组件使用,则取消注释这一行: 146 | //await new Im3xWidget(args.widgetParameter).init() -------------------------------------------------------------------------------- /bilibili/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zooPanda/Scriptables/bbdf64070c4b3324955b2bca346ef557ed574272/bilibili/screenshot.jpg -------------------------------------------------------------------------------- /bing/README.md: -------------------------------------------------------------------------------- 1 | ![](screenshot.jpg) 2 | 3 | # Bing —— 必应每日壁纸 4 | > 可展示最近一周的壁纸数据,点击后打开壁纸页面 5 | 6 | # 支持尺寸 7 | 1. 小尺寸 8 | 2. 中尺寸 9 | 3. 大尺寸 10 | 11 | # 参数设置 12 | `bing` 13 | 如果是获取昨天的壁纸,则输入参数:`bing:1`,以此类推 14 | 15 | # 参考资料 16 | API接口: https://api.66mz8.com/docs-bing.html 17 | -------------------------------------------------------------------------------- /bing/latest.js: -------------------------------------------------------------------------------- 1 | // 2 | // 每日必应壁纸 3 | // 项目地址:https://github.com/im3x/Scriptables 4 | // 5 | 6 | class Im3xWidget { 7 | /** 8 | * 初始化 9 | * @param arg 外部传递过来的参数 10 | */ 11 | constructor (arg) { 12 | this.arg = arg 13 | this.widgetSize = config.widgetFamily 14 | } 15 | /** 16 | * 渲染组件 17 | */ 18 | async render () { 19 | if (this.widgetSize === 'medium') { 20 | return await this.renderMedium() 21 | } else if (this.widgetSize === 'large') { 22 | return await this.renderLarge() 23 | } else { 24 | return await this.renderSmall() 25 | } 26 | } 27 | 28 | /** 29 | * 渲染小尺寸组件 30 | */ 31 | async renderSmall () { 32 | let w = new ListWidget() 33 | let data = await this.getData() 34 | w.backgroundImage = await this.getImage(data['img_url']) 35 | w.url = data['img_url'] 36 | return w 37 | } 38 | /** 39 | * 渲染中尺寸组件 40 | */ 41 | async renderMedium () { 42 | let w = new ListWidget() 43 | let data = await this.getData() 44 | w.backgroundImage = await this.getImage(data['img_url']) 45 | w.url = data['img_url'] 46 | let t = w.addText(data['title']) 47 | t.font = Font.lightSystemFont(14) 48 | t.textColor = Color.white() 49 | t.centerAlignText() 50 | return w 51 | } 52 | /** 53 | * 渲染大尺寸组件 54 | */ 55 | async renderLarge () { 56 | let w = new ListWidget() 57 | let data = await this.getData() 58 | w.backgroundImage = await this.getImage(data['img_url']) 59 | w.url = data['img_url'] 60 | let t = w.addText(data['title']) 61 | t.font = Font.lightSystemFont(14) 62 | t.textColor = Color.white() 63 | t.centerAlignText() 64 | return w 65 | } 66 | 67 | async getData () { 68 | let idx = parseInt(this.arg) 69 | if (!Number.isInteger(idx)) idx = 0 70 | if (idx > 7) idx = 7 71 | if (idx < 0) idx = 0 72 | let api = `https://api.66mz8.com/api/bing.php?format=json&idx=${idx}` 73 | let req = new Request(api) 74 | let res = await req.loadJSON() 75 | return res 76 | } 77 | 78 | /** 79 | * 加载远程图片 80 | * @param url string 图片地址 81 | * @return image 82 | */ 83 | async getImage (url) { 84 | let req = new Request(url) 85 | return await req.loadImage() 86 | } 87 | 88 | /** 89 | * 给图片加上半透明遮罩 90 | * @param img 要处理的图片对象 91 | * @return image 92 | */ 93 | async shadowImage (img) { 94 | let ctx = new DrawContext() 95 | ctx.size = img.size 96 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 97 | // 图片遮罩颜色、透明度设置 98 | ctx.setFillColor(new Color("#000000", 0.7)) 99 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 100 | let res = await ctx.getImage() 101 | return res 102 | } 103 | 104 | /** 105 | * 编辑测试使用 106 | */ 107 | async test () { 108 | if (config.runsInWidget) return 109 | this.widgetSize = 'small' 110 | let w1 = await this.render() 111 | await w1.presentSmall() 112 | this.widgetSize = 'medium' 113 | let w2 = await this.render() 114 | await w2.presentMedium() 115 | this.widgetSize = 'large' 116 | let w3 = await this.render() 117 | await w3.presentLarge() 118 | } 119 | 120 | /** 121 | * 组件单独在桌面运行时调用 122 | */ 123 | async init () { 124 | if (!config.runsInWidget) return 125 | let widget = await this.render() 126 | Script.setWidget(widget) 127 | Script.complete() 128 | } 129 | } 130 | 131 | module.exports = Im3xWidget 132 | 133 | // 如果是在编辑器内编辑、运行、测试,则取消注释这行,便于调试: 134 | // await new Im3xWidget().test() 135 | 136 | // 如果是组件单独使用(桌面配置选择这个组件使用,则取消注释这一行: 137 | // await new Im3xWidget(args.widgetParameter).init() 138 | -------------------------------------------------------------------------------- /bing/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zooPanda/Scriptables/bbdf64070c4b3324955b2bca346ef557ed574272/bing/screenshot.jpg -------------------------------------------------------------------------------- /dev/README.md: -------------------------------------------------------------------------------- 1 | ## 组件远程调试服务 2 | 3 | 使用前,确保手机和node运行的服务在同一个局域网。 4 | 5 | 1. 在本目录运行node服务 6 | 7 | ```bash 8 | npm install 9 | npm run serve 10 | ``` 11 | 正确的话,会在console得到已开启调试的提示。 12 | 13 | 2. 服务运行起来之后,修改`debug.js`的ip和端口。 14 | 15 | ```bash 16 | const nodeServeUrl = `http://**:8023` // 8023为默认端口 17 | ``` 18 | 19 | 3. 确保步骤2完成之后,将`debug.js`的代码粘贴到scriptable上。当然要在桌面小组件上查看啦。 20 | 21 | 4. 编写你的小组件代码,位于`/www/latest.js`。 22 | 23 | 5. 改动之后,到scriptable粘贴的`debug.js`脚本代码上点击运行。回到桌面查看修改,由于scripatable的更新机制,或许需要多次运行才行。 24 | 25 | 6. 组件代码完成之后就可以放到最后的目录上啦。 -------------------------------------------------------------------------------- /dev/app.js: -------------------------------------------------------------------------------- 1 | require('colors') 2 | const express = require("express") 3 | const app = express() 4 | 5 | app.use(express.static("www")) 6 | 7 | // 端口可自定义 8 | app.listen(8023, () => { 9 | console.log("已开启小组件调试...".green) 10 | console.log("编写www/latest.js模板。完成编写后,即可将latest.js粘贴到对应的目录上完成最终编写。".cyan) 11 | console.log("**注意粘贴ip和端口到/debug.js".red) 12 | }) -------------------------------------------------------------------------------- /dev/debug.js: -------------------------------------------------------------------------------- 1 | // node服务跑起来之后,将这个脚本粘贴到scriptable以调试组件 2 | // 能访问到的远程脚本url(调试的url) 3 | /** 4 | * node服务跑起来之后,将这个脚本粘贴到scriptable以调试组件 5 | * localJsUrl为能访问到的远程脚本url(调试的url) 6 | * 注意填写自己调试服务器的ip和端口(默认8023) 7 | * 注意保证调试手机和调试服务在同一个局域网上 8 | */ 9 | const nodeServeUrl = `http://{YOU_IP_ADDRESS}:8023` 10 | const localJsUrl = `${nodeServeUrl}/latest.js?d=${new Date().getTime()}` 11 | let req = new Request(localJsUrl) 12 | let data = await req.loadString() 13 | // 存储脚本的路径 14 | const filePrePath = `${FileManager.local().documentsDirectory()}` 15 | 16 | // 存储 17 | await FileManager.local().writeString(`${filePrePath}/latest.js`, data) 18 | 19 | const Module = importModule(`${filePrePath}/latest.js`) 20 | 21 | const m = new Module() 22 | 23 | m.init() -------------------------------------------------------------------------------- /dev/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "accepts": { 8 | "version": "1.3.7", 9 | "resolved": "https://registry.npm.taobao.org/accepts/download/accepts-1.3.7.tgz", 10 | "integrity": "sha1-UxvHJlF6OytB+FACHGzBXqq1B80=", 11 | "requires": { 12 | "mime-types": "~2.1.24", 13 | "negotiator": "0.6.2" 14 | } 15 | }, 16 | "array-flatten": { 17 | "version": "1.1.1", 18 | "resolved": "https://registry.npm.taobao.org/array-flatten/download/array-flatten-1.1.1.tgz?cache=0&sync_timestamp=1574313384951&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-flatten%2Fdownload%2Farray-flatten-1.1.1.tgz", 19 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 20 | }, 21 | "body-parser": { 22 | "version": "1.19.0", 23 | "resolved": "https://registry.npm.taobao.org/body-parser/download/body-parser-1.19.0.tgz", 24 | "integrity": "sha1-lrJwnlfJxOCab9Zqj9l5hE9p8Io=", 25 | "requires": { 26 | "bytes": "3.1.0", 27 | "content-type": "~1.0.4", 28 | "debug": "2.6.9", 29 | "depd": "~1.1.2", 30 | "http-errors": "1.7.2", 31 | "iconv-lite": "0.4.24", 32 | "on-finished": "~2.3.0", 33 | "qs": "6.7.0", 34 | "raw-body": "2.4.0", 35 | "type-is": "~1.6.17" 36 | } 37 | }, 38 | "bytes": { 39 | "version": "3.1.0", 40 | "resolved": "http://registry.npm.taobao.org/bytes/download/bytes-3.1.0.tgz", 41 | "integrity": "sha1-9s95M6Ng4FiPqf3oVlHNx/gF0fY=" 42 | }, 43 | "colors": { 44 | "version": "1.4.0", 45 | "resolved": "https://registry.npm.taobao.org/colors/download/colors-1.4.0.tgz", 46 | "integrity": "sha1-xQSRR51MG9rtLJztMs98fcI2D3g=" 47 | }, 48 | "content-disposition": { 49 | "version": "0.5.3", 50 | "resolved": "https://registry.npm.taobao.org/content-disposition/download/content-disposition-0.5.3.tgz", 51 | "integrity": "sha1-4TDK9+cnkIfFYWwgB9BIVpiYT70=", 52 | "requires": { 53 | "safe-buffer": "5.1.2" 54 | } 55 | }, 56 | "content-type": { 57 | "version": "1.0.4", 58 | "resolved": "http://registry.npm.taobao.org/content-type/download/content-type-1.0.4.tgz", 59 | "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=" 60 | }, 61 | "cookie": { 62 | "version": "0.4.0", 63 | "resolved": "https://registry.npm.taobao.org/cookie/download/cookie-0.4.0.tgz?cache=0&sync_timestamp=1587525865178&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcookie%2Fdownload%2Fcookie-0.4.0.tgz", 64 | "integrity": "sha1-vrQ35wIrO21JAZ0IhmUwPr6cFLo=" 65 | }, 66 | "cookie-signature": { 67 | "version": "1.0.6", 68 | "resolved": "https://registry.npm.taobao.org/cookie-signature/download/cookie-signature-1.0.6.tgz", 69 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 70 | }, 71 | "debug": { 72 | "version": "2.6.9", 73 | "resolved": "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz?cache=0&sync_timestamp=1600502894812&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-2.6.9.tgz", 74 | "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", 75 | "requires": { 76 | "ms": "2.0.0" 77 | } 78 | }, 79 | "depd": { 80 | "version": "1.1.2", 81 | "resolved": "http://registry.npm.taobao.org/depd/download/depd-1.1.2.tgz", 82 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 83 | }, 84 | "destroy": { 85 | "version": "1.0.4", 86 | "resolved": "http://registry.npm.taobao.org/destroy/download/destroy-1.0.4.tgz", 87 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 88 | }, 89 | "ee-first": { 90 | "version": "1.1.1", 91 | "resolved": "http://registry.npm.taobao.org/ee-first/download/ee-first-1.1.1.tgz", 92 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 93 | }, 94 | "encodeurl": { 95 | "version": "1.0.2", 96 | "resolved": "https://registry.npm.taobao.org/encodeurl/download/encodeurl-1.0.2.tgz", 97 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 98 | }, 99 | "escape-html": { 100 | "version": "1.0.3", 101 | "resolved": "http://registry.npm.taobao.org/escape-html/download/escape-html-1.0.3.tgz", 102 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 103 | }, 104 | "etag": { 105 | "version": "1.8.1", 106 | "resolved": "https://registry.npm.taobao.org/etag/download/etag-1.8.1.tgz", 107 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 108 | }, 109 | "express": { 110 | "version": "4.17.1", 111 | "resolved": "https://registry.npm.taobao.org/express/download/express-4.17.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fexpress%2Fdownload%2Fexpress-4.17.1.tgz", 112 | "integrity": "sha1-RJH8OGBc9R+GKdOcK10Cb5ikwTQ=", 113 | "requires": { 114 | "accepts": "~1.3.7", 115 | "array-flatten": "1.1.1", 116 | "body-parser": "1.19.0", 117 | "content-disposition": "0.5.3", 118 | "content-type": "~1.0.4", 119 | "cookie": "0.4.0", 120 | "cookie-signature": "1.0.6", 121 | "debug": "2.6.9", 122 | "depd": "~1.1.2", 123 | "encodeurl": "~1.0.2", 124 | "escape-html": "~1.0.3", 125 | "etag": "~1.8.1", 126 | "finalhandler": "~1.1.2", 127 | "fresh": "0.5.2", 128 | "merge-descriptors": "1.0.1", 129 | "methods": "~1.1.2", 130 | "on-finished": "~2.3.0", 131 | "parseurl": "~1.3.3", 132 | "path-to-regexp": "0.1.7", 133 | "proxy-addr": "~2.0.5", 134 | "qs": "6.7.0", 135 | "range-parser": "~1.2.1", 136 | "safe-buffer": "5.1.2", 137 | "send": "0.17.1", 138 | "serve-static": "1.14.1", 139 | "setprototypeof": "1.1.1", 140 | "statuses": "~1.5.0", 141 | "type-is": "~1.6.18", 142 | "utils-merge": "1.0.1", 143 | "vary": "~1.1.2" 144 | } 145 | }, 146 | "finalhandler": { 147 | "version": "1.1.2", 148 | "resolved": "https://registry.npm.taobao.org/finalhandler/download/finalhandler-1.1.2.tgz", 149 | "integrity": "sha1-t+fQAP/RGTjQ/bBTUG9uur6fWH0=", 150 | "requires": { 151 | "debug": "2.6.9", 152 | "encodeurl": "~1.0.2", 153 | "escape-html": "~1.0.3", 154 | "on-finished": "~2.3.0", 155 | "parseurl": "~1.3.3", 156 | "statuses": "~1.5.0", 157 | "unpipe": "~1.0.0" 158 | } 159 | }, 160 | "forwarded": { 161 | "version": "0.1.2", 162 | "resolved": "https://registry.npm.taobao.org/forwarded/download/forwarded-0.1.2.tgz", 163 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 164 | }, 165 | "fresh": { 166 | "version": "0.5.2", 167 | "resolved": "https://registry.npm.taobao.org/fresh/download/fresh-0.5.2.tgz", 168 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 169 | }, 170 | "http-errors": { 171 | "version": "1.7.2", 172 | "resolved": "https://registry.npm.taobao.org/http-errors/download/http-errors-1.7.2.tgz?cache=0&sync_timestamp=1593407676624&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fhttp-errors%2Fdownload%2Fhttp-errors-1.7.2.tgz", 173 | "integrity": "sha1-T1ApzxMjnzEDblsuVSkrz7zIXI8=", 174 | "requires": { 175 | "depd": "~1.1.2", 176 | "inherits": "2.0.3", 177 | "setprototypeof": "1.1.1", 178 | "statuses": ">= 1.5.0 < 2", 179 | "toidentifier": "1.0.0" 180 | } 181 | }, 182 | "iconv-lite": { 183 | "version": "0.4.24", 184 | "resolved": "https://registry.npm.taobao.org/iconv-lite/download/iconv-lite-0.4.24.tgz?cache=0&sync_timestamp=1594184264130&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ficonv-lite%2Fdownload%2Ficonv-lite-0.4.24.tgz", 185 | "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", 186 | "requires": { 187 | "safer-buffer": ">= 2.1.2 < 3" 188 | } 189 | }, 190 | "inherits": { 191 | "version": "2.0.3", 192 | "resolved": "https://registry.npm.taobao.org/inherits/download/inherits-2.0.3.tgz", 193 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 194 | }, 195 | "ip": { 196 | "version": "1.1.5", 197 | "resolved": "http://registry.npm.taobao.org/ip/download/ip-1.1.5.tgz", 198 | "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" 199 | }, 200 | "ipaddr.js": { 201 | "version": "1.9.1", 202 | "resolved": "https://registry.npm.taobao.org/ipaddr.js/download/ipaddr.js-1.9.1.tgz", 203 | "integrity": "sha1-v/OFQ+64mEglB5/zoqjmy9RngbM=" 204 | }, 205 | "media-typer": { 206 | "version": "0.3.0", 207 | "resolved": "https://registry.npm.taobao.org/media-typer/download/media-typer-0.3.0.tgz", 208 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 209 | }, 210 | "merge-descriptors": { 211 | "version": "1.0.1", 212 | "resolved": "https://registry.npm.taobao.org/merge-descriptors/download/merge-descriptors-1.0.1.tgz", 213 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 214 | }, 215 | "methods": { 216 | "version": "1.1.2", 217 | "resolved": "https://registry.npm.taobao.org/methods/download/methods-1.1.2.tgz", 218 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 219 | }, 220 | "mime": { 221 | "version": "1.6.0", 222 | "resolved": "https://registry.npm.taobao.org/mime/download/mime-1.6.0.tgz?cache=0&sync_timestamp=1590596706367&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime%2Fdownload%2Fmime-1.6.0.tgz", 223 | "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=" 224 | }, 225 | "mime-db": { 226 | "version": "1.44.0", 227 | "resolved": "https://registry.npm.taobao.org/mime-db/download/mime-db-1.44.0.tgz?cache=0&sync_timestamp=1600831210195&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.44.0.tgz", 228 | "integrity": "sha1-+hHF6wrKEzS0Izy01S8QxaYnL5I=" 229 | }, 230 | "mime-types": { 231 | "version": "2.1.27", 232 | "resolved": "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.27.tgz", 233 | "integrity": "sha1-R5SfmOJ56lMRn1ci4PNOUpvsAJ8=", 234 | "requires": { 235 | "mime-db": "1.44.0" 236 | } 237 | }, 238 | "ms": { 239 | "version": "2.0.0", 240 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz", 241 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 242 | }, 243 | "negotiator": { 244 | "version": "0.6.2", 245 | "resolved": "https://registry.npm.taobao.org/negotiator/download/negotiator-0.6.2.tgz", 246 | "integrity": "sha1-/qz3zPUlp3rpY0Q2pkiD/+yjRvs=" 247 | }, 248 | "on-finished": { 249 | "version": "2.3.0", 250 | "resolved": "https://registry.npm.taobao.org/on-finished/download/on-finished-2.3.0.tgz", 251 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 252 | "requires": { 253 | "ee-first": "1.1.1" 254 | } 255 | }, 256 | "parseurl": { 257 | "version": "1.3.3", 258 | "resolved": "https://registry.npm.taobao.org/parseurl/download/parseurl-1.3.3.tgz", 259 | "integrity": "sha1-naGee+6NEt/wUT7Vt2lXeTvC6NQ=" 260 | }, 261 | "path-to-regexp": { 262 | "version": "0.1.7", 263 | "resolved": "https://registry.npm.taobao.org/path-to-regexp/download/path-to-regexp-0.1.7.tgz", 264 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 265 | }, 266 | "proxy-addr": { 267 | "version": "2.0.6", 268 | "resolved": "https://registry.npm.taobao.org/proxy-addr/download/proxy-addr-2.0.6.tgz", 269 | "integrity": "sha1-/cIzZQVEfT8vLGOO0nLK9hS7sr8=", 270 | "requires": { 271 | "forwarded": "~0.1.2", 272 | "ipaddr.js": "1.9.1" 273 | } 274 | }, 275 | "qs": { 276 | "version": "6.7.0", 277 | "resolved": "https://registry.npm.taobao.org/qs/download/qs-6.7.0.tgz", 278 | "integrity": "sha1-QdwaAV49WB8WIXdr4xr7KHapsbw=" 279 | }, 280 | "range-parser": { 281 | "version": "1.2.1", 282 | "resolved": "https://registry.npm.taobao.org/range-parser/download/range-parser-1.2.1.tgz", 283 | "integrity": "sha1-PPNwI9GZ4cJNGlW4SADC8+ZGgDE=" 284 | }, 285 | "raw-body": { 286 | "version": "2.4.0", 287 | "resolved": "https://registry.npm.taobao.org/raw-body/download/raw-body-2.4.0.tgz", 288 | "integrity": "sha1-oc5vucm8NWylLoklarWQWeE9AzI=", 289 | "requires": { 290 | "bytes": "3.1.0", 291 | "http-errors": "1.7.2", 292 | "iconv-lite": "0.4.24", 293 | "unpipe": "1.0.0" 294 | } 295 | }, 296 | "safe-buffer": { 297 | "version": "5.1.2", 298 | "resolved": "https://registry.npm.taobao.org/safe-buffer/download/safe-buffer-5.1.2.tgz", 299 | "integrity": "sha1-mR7GnSluAxN0fVm9/St0XDX4go0=" 300 | }, 301 | "safer-buffer": { 302 | "version": "2.1.2", 303 | "resolved": "http://registry.npm.taobao.org/safer-buffer/download/safer-buffer-2.1.2.tgz", 304 | "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" 305 | }, 306 | "send": { 307 | "version": "0.17.1", 308 | "resolved": "https://registry.npm.taobao.org/send/download/send-0.17.1.tgz", 309 | "integrity": "sha1-wdiwWfeQD3Rm3Uk4vcROEd2zdsg=", 310 | "requires": { 311 | "debug": "2.6.9", 312 | "depd": "~1.1.2", 313 | "destroy": "~1.0.4", 314 | "encodeurl": "~1.0.2", 315 | "escape-html": "~1.0.3", 316 | "etag": "~1.8.1", 317 | "fresh": "0.5.2", 318 | "http-errors": "~1.7.2", 319 | "mime": "1.6.0", 320 | "ms": "2.1.1", 321 | "on-finished": "~2.3.0", 322 | "range-parser": "~1.2.1", 323 | "statuses": "~1.5.0" 324 | }, 325 | "dependencies": { 326 | "ms": { 327 | "version": "2.1.1", 328 | "resolved": "https://registry.npm.taobao.org/ms/download/ms-2.1.1.tgz", 329 | "integrity": "sha1-MKWGTrPrsKZvLr5tcnrwagnYbgo=" 330 | } 331 | } 332 | }, 333 | "serve-static": { 334 | "version": "1.14.1", 335 | "resolved": "https://registry.npm.taobao.org/serve-static/download/serve-static-1.14.1.tgz", 336 | "integrity": "sha1-Zm5jbcTwEPfvKZcKiKZ0MgiYsvk=", 337 | "requires": { 338 | "encodeurl": "~1.0.2", 339 | "escape-html": "~1.0.3", 340 | "parseurl": "~1.3.3", 341 | "send": "0.17.1" 342 | } 343 | }, 344 | "setprototypeof": { 345 | "version": "1.1.1", 346 | "resolved": "https://registry.npm.taobao.org/setprototypeof/download/setprototypeof-1.1.1.tgz", 347 | "integrity": "sha1-fpWsskqpL1iF4KvvW6ExMw1K5oM=" 348 | }, 349 | "statuses": { 350 | "version": "1.5.0", 351 | "resolved": "https://registry.npm.taobao.org/statuses/download/statuses-1.5.0.tgz?cache=0&sync_timestamp=1587327902535&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fstatuses%2Fdownload%2Fstatuses-1.5.0.tgz", 352 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 353 | }, 354 | "toidentifier": { 355 | "version": "1.0.0", 356 | "resolved": "https://registry.npm.taobao.org/toidentifier/download/toidentifier-1.0.0.tgz", 357 | "integrity": "sha1-fhvjRw8ed5SLxD2Uo8j013UrpVM=" 358 | }, 359 | "type-is": { 360 | "version": "1.6.18", 361 | "resolved": "https://registry.npm.taobao.org/type-is/download/type-is-1.6.18.tgz", 362 | "integrity": "sha1-TlUs0F3wlGfcvE73Od6J8s83wTE=", 363 | "requires": { 364 | "media-typer": "0.3.0", 365 | "mime-types": "~2.1.24" 366 | } 367 | }, 368 | "unpipe": { 369 | "version": "1.0.0", 370 | "resolved": "http://registry.npm.taobao.org/unpipe/download/unpipe-1.0.0.tgz", 371 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 372 | }, 373 | "utils-merge": { 374 | "version": "1.0.1", 375 | "resolved": "https://registry.npm.taobao.org/utils-merge/download/utils-merge-1.0.1.tgz", 376 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 377 | }, 378 | "vary": { 379 | "version": "1.1.2", 380 | "resolved": "https://registry.npm.taobao.org/vary/download/vary-1.1.2.tgz", 381 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "debug", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "serve": "node app.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "colors": "^1.4.0", 13 | "express": "^4.17.1" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /dev/www/latest.js: -------------------------------------------------------------------------------- 1 | // 2 | // 通用框架插件模版代码 3 | // 项目地址:https://github.com/im3x/Scriptables 4 | // 5 | 6 | class Im3xWidget { 7 | /** 8 | * 初始化 9 | * @param arg 外部传递过来的参数 10 | */ 11 | constructor(arg, loader) { 12 | this.arg = arg 13 | this.loader = loader 14 | this.fileName = module.filename.split('Documents/')[1] 15 | this.widgetSize = config.widgetFamily 16 | } 17 | /** 18 | * 渲染组件 19 | */ 20 | async render() { 21 | if (this.widgetSize === 'medium') { 22 | return await this.renderMedium() 23 | } else if (this.widgetSize === 'large') { 24 | return await this.renderLarge() 25 | } else { 26 | return await this.renderSmall() 27 | } 28 | } 29 | 30 | /** 31 | * 渲染小尺寸组件 32 | */ 33 | async renderSmall() { 34 | let w = new ListWidget() 35 | w.addText("已开启调试!") 36 | return w 37 | } 38 | /** 39 | * 渲染中尺寸组件 40 | */ 41 | async renderMedium() { 42 | let w = new ListWidget() 43 | w.addText("已开启调试!") 44 | return w 45 | } 46 | /** 47 | * 渲染大尺寸组件 48 | */ 49 | async renderLarge() { 50 | let w = new ListWidget() 51 | w.addText("已开启调试!") 52 | return w 53 | } 54 | 55 | /** 56 | * 用户传递的组件自定义点击操作 57 | */ 58 | async runActions() { 59 | let { act, data } = this.parseQuery() 60 | if (!act) return 61 | } 62 | 63 | // 获取跳转自身 urlscheme 64 | // w.url = this.getURIScheme("copy", "data-to-copy") 65 | getURIScheme(act, data) { 66 | let _raw = typeof data === 'object' ? JSON.stringify(data) : data 67 | let _data = Data.fromString(_raw) 68 | let _b64 = _data.toBase64String() 69 | return `${URLScheme.forRunningScript()}?&act=${act}&data=${_b64}` 70 | } 71 | // 解析 urlscheme 参数 72 | // { act: "copy", data: "copy" } 73 | parseQuery() { 74 | const { act, data } = args['queryParameters'] 75 | if (!act) return { act } 76 | let _data = Data.fromBase64String(data) 77 | let _raw = _data.toRawString() 78 | let result = _raw 79 | try { 80 | result = JSON.parse(_raw) 81 | } catch (e) { } 82 | return { 83 | act, 84 | data: result 85 | } 86 | } 87 | 88 | /** 89 | * 渲染标题 90 | * @param widget 组件对象 91 | * @param icon 图标url地址 92 | * @param title 标题 93 | */ 94 | async renderHeader(widget, icon, title) { 95 | let header = widget.addStack() 96 | header.centerAlignContent() 97 | let _icon = header.addImage(await this.getImage(icon)) 98 | _icon.imageSize = new Size(14, 14) 99 | _icon.cornerRadius = 4 100 | header.addSpacer(10) 101 | let _title = header.addText(title) 102 | _title.textColor = Color.white() 103 | _title.textOpacity = 0.7 104 | _title.font = Font.boldSystemFont(12) 105 | widget.addSpacer(15) 106 | return widget 107 | } 108 | 109 | /** 110 | * 获取api数据 111 | * @param api api地址 112 | * @param json 接口数据是否是 json 格式,如果不是(纯text),则传递 false 113 | * @return 数据 || null 114 | */ 115 | async getData(api, json = true) { 116 | let data = null 117 | const cacheKey = `${this.fileName}_cache` 118 | try { 119 | let req = new Request(api) 120 | data = await (json ? req.loadJSON() : req.loadString()) 121 | } catch (e) { } 122 | // 判断数据是否为空(加载失败) 123 | if (!data) { 124 | // 判断是否有缓存 125 | if (Keychain.contains(cacheKey)) { 126 | let cache = Keychain.get(cacheKey) 127 | return json ? JSON.parse(cache) : cache 128 | } else { 129 | // 刷新 130 | return null 131 | } 132 | } 133 | // 存储缓存 134 | Keychain.set(cacheKey, json ? JSON.stringify(data) : data) 135 | return data 136 | } 137 | /** 138 | * 加载远程图片 139 | * @param url string 图片地址 140 | * @return image 141 | */ 142 | async getImage(url) { 143 | try { 144 | let req = new Request(url) 145 | return await req.loadImage() 146 | } catch (e) { 147 | let ctx = new DrawContext() 148 | ctx.size = new Size(100, 100) 149 | ctx.setFillColor(Color.red()) 150 | ctx.fillRect(new Rect(0, 0, 100, 100)) 151 | return await ctx.getImage() 152 | } 153 | } 154 | 155 | /** 156 | * 给图片加上半透明遮罩 157 | * @param img 要处理的图片对象 158 | * @return image 159 | */ 160 | async shadowImage(img) { 161 | let ctx = new DrawContext() 162 | ctx.size = img.size 163 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 164 | // 图片遮罩颜色、透明度设置 165 | ctx.setFillColor(new Color("#000000", 0.7)) 166 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 167 | let res = await ctx.getImage() 168 | return res 169 | } 170 | 171 | /** 172 | * 编辑测试使用 173 | */ 174 | async test() { 175 | if (config.runsInWidget) return 176 | this.widgetSize = 'small' 177 | let w1 = await this.render() 178 | await w1.presentSmall() 179 | this.widgetSize = 'medium' 180 | let w2 = await this.render() 181 | await w2.presentMedium() 182 | this.widgetSize = 'large' 183 | let w3 = await this.render() 184 | await w3.presentLarge() 185 | } 186 | 187 | /** 188 | * 组件单独在桌面运行时调用 189 | */ 190 | async init() { 191 | if (!config.runsInWidget) return await this.runActions() 192 | let widget = await this.render() 193 | Script.setWidget(widget) 194 | Script.complete() 195 | } 196 | } 197 | 198 | module.exports = Im3xWidget 199 | 200 | // 如果是在编辑器内编辑、运行、测试,则取消注释这行,便于调试: 201 | // await new Im3xWidget('').test() 202 | 203 | // 如果是组件单独使用(桌面配置选择这个组件使用,则取消注释这一行: 204 | // await new Im3xWidget(args.widgetParameter, true).init() -------------------------------------------------------------------------------- /loader.gitee.js: -------------------------------------------------------------------------------- 1 | // 2 | // scriptable 加载器 3 | // 用于加载远程 scriptable 桌面组件插件 4 | // author@im3x 5 | // 公众号@古人云 6 | // https://github.com/im3x/Scriptables 7 | // 8 | 9 | class Im3xLoader { 10 | constructor (git = 'github') { 11 | // 仓库源 12 | this.git = git 13 | this.ver = 202010131700 14 | // 解析参数 15 | this.opt = { 16 | name: 'welcome', 17 | args: '', 18 | version: 'latest', 19 | developer: 'im3x' 20 | } 21 | let arg = args.widgetParameter || args['queryParameters']['__widget__'] 22 | // widget@version:params 23 | // 第三方开发者源:user-name/widget@version:params 24 | if (arg) { 25 | let _args = arg.split(":") 26 | let _plug = _args[0].split("@") 27 | if (_plug.length === 2) { 28 | this.opt['version'] = _plug[1] 29 | } 30 | let _name = _plug[0].split('/') 31 | if (_name.length === 2) { 32 | this.opt['name'] = _name[1] 33 | this.opt['developer'] = _name[0] 34 | } else { 35 | this.opt['name'] = _name[0] 36 | } 37 | if (_args.length === 2) this.opt['args'] = _args[1] 38 | } 39 | // 缓存路径 40 | this.filename = `${this.opt['developer']}_${this.opt['name']}@${this.opt['version']}.js.im3x` 41 | this.filepath = FileManager.local().documentsDirectory() + '/' + this.filename 42 | this.notify() 43 | this.update() 44 | } 45 | 46 | async init () { 47 | // 判断文件是否存在 48 | let rendered = false 49 | let widget 50 | if (FileManager.local().fileExists(this.filepath)) { 51 | try { 52 | rendered = true 53 | widget = await this.render() 54 | } catch(e){ 55 | rendered = false 56 | } 57 | } 58 | // 加载代码,存储 59 | try { 60 | let req = new Request(`https://${this.git}.com/${this.opt['developer']}/Scriptables/raw/main/${encodeURIComponent(this.opt['name'])}/${encodeURIComponent(this.opt['version'])}.js?_=${+new Date}`) 61 | let data = await req.loadString() 62 | // 如果404 63 | if (req.response['statusCode'] === 404) { 64 | return await this.renderFail('插件不存在') 65 | } 66 | await FileManager.local().writeString(this.filepath, data) 67 | if (!rendered) { 68 | widget = await this.render() 69 | } 70 | } catch (e) { 71 | // 网络加载失败,返回错误提示 72 | // 如果已经渲染了(有本地缓存,直接返回本地代码) 73 | if (rendered) return widget 74 | return await this.renderFail(e.message) 75 | } 76 | 77 | return widget 78 | } 79 | // 加载失败提示 80 | async renderFail (err) { 81 | let w = new ListWidget() 82 | let t1 = w.addText("⚠️") 83 | t1.centerAlignText() 84 | w.addSpacer(10) 85 | let t2 = w.addText(err) 86 | t2.textColor = Color.red() 87 | t2.font = Font.lightSystemFont(14) 88 | t2.centerAlignText() 89 | w.url = `https://github.com/${this.opt['developer']}/Scriptables` 90 | return w 91 | } 92 | // 初始化组件并渲染 93 | async render () { 94 | let M = importModule(this.filename) 95 | let m = new M(this.opt['args'], this) 96 | // 执行组件自定义方法操作 97 | if (!config.runsInWidget && typeof m['runActions'] === 'function') { 98 | try { 99 | let func = m.runActions.bind(m) 100 | await func() 101 | } catch (e) { 102 | let alert = new Alert() 103 | alert.title = "执行失败" 104 | alert.message = e.message 105 | alert.presentAlert() 106 | } 107 | return false 108 | } 109 | let w = await m.render() 110 | return w 111 | } 112 | 113 | // 通知 114 | async notify () { 115 | let req = new Request(`https://${this.git}.com/im3x/Scriptables/raw/main/update.notify.json?_=${+new Date}`) 116 | let res = await req.loadJSON() 117 | if (!res || !res['id']) return 118 | // 判断是否已经通知过 119 | let key = 'im3x_loader_notify' 120 | if (Keychain.contains(key)) { 121 | let cache = Keychain.get(key) 122 | if (cache === res['id']) return 123 | } 124 | // 通知 125 | let n = new Notification() 126 | n = Object.assign(n, res) 127 | n.schedule() 128 | // 设置已通知 129 | Keychain.set(key, res['id']) 130 | } 131 | // 更新加载器 132 | async update () { 133 | let req = new Request(`https://gitee.com/api/v5/repos/im3x/Scriptables/commits?path=loader.${this.git}.js&page=1&per_page=1`) 134 | let res = await req.loadJSON() 135 | let commit = res[0] 136 | let key = 'im3x_loader_update' 137 | if (Keychain.contains(key)) { 138 | let cache = Keychain.get(key) 139 | if (cache === commit['sha']) return 140 | } 141 | // 加载远程代码内容 142 | let req1 = new Request(`https://gitee.com/im3x/Scriptables/raw/main/loader.${this.git}.js`) 143 | let res1 = await req1.loadString() 144 | // 当前脚本的路径 145 | let self = module.filename 146 | // 读取前三行代码(包含图标信息) 147 | let selfContent = FileManager.local().readString(self) 148 | let tmp = selfContent.split("\n") 149 | // 放到前三行 150 | let new_code = `${tmp[0]}\n${tmp[1]}\n${tmp[2]}\n${res1}` 151 | // 写入文件 152 | FileManager.local().writeString(self, new_code) 153 | Keychain.set(key, commit['sha']) 154 | } 155 | } 156 | const Loader = new Im3xLoader('gitee') 157 | const widget = await Loader.init() 158 | if (config.runsInWidget && widget) { 159 | Script.setWidget(widget) 160 | } 161 | Script.complete() -------------------------------------------------------------------------------- /loader.github.js: -------------------------------------------------------------------------------- 1 | // 2 | // scriptable 加载器 3 | // 用于加载远程 scriptable 桌面组件插件 4 | // author@im3x 5 | // 公众号@古人云 6 | // https://github.com/im3x/Scriptables 7 | // 8 | 9 | class Im3xLoader { 10 | constructor (git = 'github') { 11 | // 仓库源 12 | this.git = git 13 | this.ver = 202010131700 14 | // 解析参数 15 | this.opt = { 16 | name: 'welcome', 17 | args: '', 18 | version: 'latest', 19 | developer: 'im3x' 20 | } 21 | let arg = args.widgetParameter || args['queryParameters']['__widget__'] 22 | // widget@version:params 23 | // 第三方开发者源:user-name/widget@version:params 24 | if (arg) { 25 | let _args = arg.split(":") 26 | let _plug = _args[0].split("@") 27 | if (_plug.length === 2) { 28 | this.opt['version'] = _plug[1] 29 | } 30 | let _name = _plug[0].split('/') 31 | if (_name.length === 2) { 32 | this.opt['name'] = _name[1] 33 | this.opt['developer'] = _name[0] 34 | } else { 35 | this.opt['name'] = _name[0] 36 | } 37 | if (_args.length === 2) this.opt['args'] = _args[1] 38 | } 39 | // 缓存路径 40 | this.filename = `${this.opt['developer']}_${this.opt['name']}@${this.opt['version']}.js.im3x` 41 | this.filepath = FileManager.local().documentsDirectory() + '/' + this.filename 42 | this.notify() 43 | this.update() 44 | } 45 | 46 | async init () { 47 | // 判断文件是否存在 48 | let rendered = false 49 | let widget 50 | if (FileManager.local().fileExists(this.filepath)) { 51 | try { 52 | rendered = true 53 | widget = await this.render() 54 | } catch(e){ 55 | rendered = false 56 | } 57 | } 58 | // 加载代码,存储 59 | try { 60 | let req = new Request(`https://${this.git}.com/${this.opt['developer']}/Scriptables/raw/main/${encodeURIComponent(this.opt['name'])}/${encodeURIComponent(this.opt['version'])}.js?_=${+new Date}`) 61 | let data = await req.loadString() 62 | // 如果404 63 | if (req.response['statusCode'] === 404) { 64 | return await this.renderFail('插件不存在') 65 | } 66 | await FileManager.local().writeString(this.filepath, data) 67 | if (!rendered) { 68 | widget = await this.render() 69 | } 70 | } catch (e) { 71 | // 网络加载失败,返回错误提示 72 | // 如果已经渲染了(有本地缓存,直接返回本地代码) 73 | if (rendered) return widget 74 | return await this.renderFail(e.message) 75 | } 76 | 77 | return widget 78 | } 79 | // 加载失败提示 80 | async renderFail (err) { 81 | let w = new ListWidget() 82 | let t1 = w.addText("⚠️") 83 | t1.centerAlignText() 84 | w.addSpacer(10) 85 | let t2 = w.addText(err) 86 | t2.textColor = Color.red() 87 | t2.font = Font.lightSystemFont(14) 88 | t2.centerAlignText() 89 | w.url = `https://github.com/${this.opt['developer']}/Scriptables` 90 | return w 91 | } 92 | // 初始化组件并渲染 93 | async render () { 94 | let M = importModule(this.filename) 95 | let m = new M(this.opt['args'], this) 96 | // 执行组件自定义方法操作 97 | if (!config.runsInWidget && typeof m['runActions'] === 'function') { 98 | try { 99 | let func = m.runActions.bind(m) 100 | await func() 101 | } catch (e) { 102 | let alert = new Alert() 103 | alert.title = "执行失败" 104 | alert.message = e.message 105 | alert.presentAlert() 106 | } 107 | return false 108 | } 109 | let w = await m.render() 110 | return w 111 | } 112 | 113 | // 通知 114 | async notify () { 115 | let req = new Request(`https://${this.git}.com/im3x/Scriptables/raw/main/update.notify.json?_=${+new Date}`) 116 | let res = await req.loadJSON() 117 | if (!res || !res['id']) return 118 | // 判断是否已经通知过 119 | let key = 'im3x_loader_notify' 120 | if (Keychain.contains(key)) { 121 | let cache = Keychain.get(key) 122 | if (cache === res['id']) return 123 | } 124 | // 通知 125 | let n = new Notification() 126 | n = Object.assign(n, res) 127 | n.schedule() 128 | // 设置已通知 129 | Keychain.set(key, res['id']) 130 | } 131 | // 更新加载器 132 | async update () { 133 | let req = new Request(`https://gitee.com/api/v5/repos/im3x/Scriptables/commits?path=loader.${this.git}.js&page=1&per_page=1`) 134 | let res = await req.loadJSON() 135 | let commit = res[0] 136 | let key = 'im3x_loader_update' 137 | if (Keychain.contains(key)) { 138 | let cache = Keychain.get(key) 139 | if (cache === commit['sha']) return 140 | } 141 | // 加载远程代码内容 142 | let req1 = new Request(`https://gitee.com/im3x/Scriptables/raw/main/loader.${this.git}.js`) 143 | let res1 = await req1.loadString() 144 | // 当前脚本的路径 145 | let self = module.filename 146 | // 读取前三行代码(包含图标信息) 147 | let selfContent = FileManager.local().readString(self) 148 | let tmp = selfContent.split("\n") 149 | // 放到前三行 150 | let new_code = `${tmp[0]}\n${tmp[1]}\n${tmp[2]}\n${res1}` 151 | // 写入文件 152 | FileManager.local().writeString(self, new_code) 153 | Keychain.set(key, commit['sha']) 154 | } 155 | } 156 | const Loader = new Im3xLoader() 157 | const widget = await Loader.init() 158 | if (config.runsInWidget && widget) { 159 | Script.setWidget(widget) 160 | } 161 | Script.complete() -------------------------------------------------------------------------------- /one/README.md: -------------------------------------------------------------------------------- 1 | ![](/one/screenshot.jpg) 2 | 3 | 4 | # 插件介绍 5 | 「ONE · 一个」 每日更新一张精美的图片和文字,展示在桌面组件上。 6 | > 原创作者 by 小鱼,公众号:古人云,转载请注明 7 | 8 | # 插件使用 9 | 添加桌面组件,选择 `Scriptable` 应用,然后点击组件配置,选择这个项目的加载代码`Loader.Github`或`Loader.Gitee`,然后参数输入参考如下说明,默认填写`one`即可使用本组件。 10 | 11 | # 支持组件尺寸 12 | 1. 小尺寸 Small 13 | 2. 中尺寸 Medium 14 | 3. 大尺寸 Large 15 | 16 | # 接受参数 17 | int类型,为要显示的第几个内容,默认0,就是当天的数据,如果是1,则为昨天的数据,以此类推,最大支持9 18 | 19 | 比如参数直接填写:`one`、`one:1`、`one@latest:1` 都是可以的 20 | -------------------------------------------------------------------------------- /one/latest.js: -------------------------------------------------------------------------------- 1 | 2 | // 3 | // ONE·一个图文 4 | // iOS14 桌面组件插件 for Scriptable 5 | // author@古人云 6 | // https://github.com/im3x/Scriptables 7 | // 8 | 9 | class Im3xWidget { 10 | // 初始化,接收参数 11 | constructor (arg, loader) { 12 | this.loader = loader 13 | this.arg = parseInt(arg) 14 | if (!Number.isInteger(this.arg)) this.arg = 0 15 | } 16 | // 渲染组件 17 | async render () { 18 | let data = await this.getData() 19 | let widget = await (config.widgetFamily === 'large') ? this.renderLarge(data) : this.renderSmall(data) 20 | return widget 21 | } 22 | 23 | async renderLarge (one) { 24 | let w = new ListWidget() 25 | 26 | if (!one) return await this.renderErr(w) 27 | 28 | w.url = this.loader ? this.getURIScheme('open-url', one['url']) : one["url"] 29 | 30 | // 时间 31 | const dates = one["date"].split(" / ") 32 | let date1 = w.addText(dates[2]) 33 | date1.font = Font.lightSystemFont(60) 34 | date1.centerAlignText() 35 | date1.textColor = Color.white() 36 | 37 | let line = w.addText("————".repeat(2)) 38 | line.textOpacity = 0.5 39 | line.centerAlignText() 40 | line.textColor = Color.white() 41 | 42 | let date2 = w.addText(dates[0] + " / " + dates[1]) 43 | date2.font = Font.lightMonospacedSystemFont(30) 44 | date2.centerAlignText() 45 | date2.textColor = Color.white() 46 | date2.textOpacity = 0.7 47 | 48 | // 换行 49 | w.addSpacer(20) 50 | // 内容 51 | let body = w.addText(one["content"]) 52 | body.font = Font.lightSystemFont(22) 53 | body.textColor = Color.white() 54 | 55 | w.addSpacer(50) 56 | 57 | let author = w.addText("—— " + one["text_authors"]) 58 | author.rightAlignText() 59 | author.font = Font.lightSystemFont(14) 60 | author.textColor = Color.white() 61 | author.textOpacity = 0.8 62 | 63 | // 加载背景图片 64 | let bg = await this.getImage(one["img_url"]) 65 | 66 | w.backgroundImage = await this.shadowImage(bg) 67 | // 记得最后返回组件 68 | return w 69 | } 70 | 71 | async renderSmall (one) { 72 | console.log('create.small.widget') 73 | let w = new ListWidget() 74 | 75 | if (!one) return await this.renderErr(w) 76 | 77 | w.url = this.loader ? this.getURIScheme('open-url', one['url']) : one["url"] 78 | 79 | w = await this.renderHeader(w, 'http://image.wufazhuce.com/apple-touch-icon.png', '「ONE · 一个」') 80 | console.log('render.header.done') 81 | 82 | let body = w.addText(one['content']) 83 | body.textColor = Color.white() 84 | body.font = Font.lightSystemFont(config.widgetFamily === 'small' ? 14 : 16) 85 | w.addSpacer(10) 86 | let footer = w.addText('—— ' + one['text_authors']) 87 | footer.rightAlignText() 88 | footer.textColor = Color.white() 89 | footer.textOpacity = 0.8 90 | footer.font = Font.lightSystemFont(12) 91 | 92 | // 加载背景图片 93 | let bg = await this.getImage(one["img_url"]) 94 | 95 | w.backgroundImage = await this.shadowImage(bg) 96 | console.log('create.small.widget.done') 97 | return w 98 | } 99 | 100 | async renderErr (widget) { 101 | let err = widget.addText("💔 加载失败,稍后重试..") 102 | err.textColor = Color.red() 103 | err.centerAlignText() 104 | return widget 105 | } 106 | /** 107 | * 渲染标题 108 | * @param widget 组件对象 109 | * @param icon 图标url地址 110 | * @param title 标题 111 | */ 112 | async renderHeader (widget, icon, title) { 113 | let header = widget.addStack() 114 | header.centerAlignContent() 115 | let _icon = header.addImage(await this.getImage(icon)) 116 | _icon.imageSize = new Size(14, 14) 117 | _icon.cornerRadius = 4 118 | header.addSpacer(10) 119 | let _title = header.addText(title) 120 | _title.textColor = Color.white() 121 | _title.textOpacity = 0.7 122 | _title.font = Font.boldSystemFont(12) 123 | widget.addSpacer(15) 124 | return widget 125 | } 126 | async getData () { 127 | const API = "http://m.wufazhuce.com/one"; 128 | const req1 = new Request(API) 129 | // await req1.load() 130 | const body1 = await req1.loadString() 131 | const token = body1.split("One.token = '")[1].split("'")[0] 132 | 133 | const API2 = "http://m.wufazhuce.com/one/ajaxlist/0?_token=" + token 134 | const req2 = new Request(API2) 135 | const res2 = await req2.loadJSON() 136 | const data = res2["data"] 137 | console.log('arg====') 138 | 139 | return data ? data[this.arg] : false 140 | } 141 | async getImage (url) { 142 | let r = new Request(url) 143 | return await r.loadImage() 144 | } 145 | async shadowImage (img) { 146 | let ctx = new DrawContext() 147 | // 获取图片的尺寸 148 | ctx.size = img.size 149 | 150 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 151 | ctx.setFillColor(new Color("#000000", 0.7)) 152 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 153 | 154 | let res = await ctx.getImage() 155 | return res 156 | } 157 | 158 | /** 159 | * 用户传递的组件自定义点击操作 160 | */ 161 | async runActions () { 162 | let { act, data } = this.parseQuery() 163 | if (!act) return 164 | if (act === 'open-url') { 165 | Safari.openInApp(data, false) 166 | } 167 | } 168 | 169 | // 获取跳转自身 urlscheme 170 | // w.url = this.getURIScheme("copy", "data-to-copy") 171 | getURIScheme (act, data) { 172 | let _raw = typeof data === 'object' ? JSON.stringify(data) : data 173 | let _data = Data.fromString(_raw) 174 | let _b64 = _data.toBase64String() 175 | return `scriptable:///run?scriptName=${encodeURIComponent(Script.name())}&act=${act}&data=${_b64}&__widget__=${encodeURIComponent(args['widgetParameter'])}` 176 | } 177 | // 解析 urlscheme 参数 178 | // { act: "copy", data: "copy" } 179 | parseQuery () { 180 | const { act, data } = args['queryParameters'] 181 | if (!act) return { act } 182 | let _data = Data.fromBase64String(data) 183 | let _raw = _data.toRawString() 184 | let result = _raw 185 | try { 186 | result = JSON.parse(_raw) 187 | } catch (e) {} 188 | return { 189 | act, 190 | data: result 191 | } 192 | } 193 | // 用于测试 194 | async test () { 195 | if (config.runsInWidget) return 196 | let widget = await this.render() 197 | widget.presentSmall() 198 | } 199 | // 单独运行 200 | async init () { 201 | if (!config.runsInWidget) return await this.runActions() 202 | try { 203 | this.arg = parseInt(args.widgetParameter) 204 | if (!Number.isInteger(this.arg)) this.arg = 0 205 | } catch (e) {} 206 | let widget = await this.render() 207 | Script.setWidget(widget) 208 | Script.complete() 209 | } 210 | } 211 | 212 | module.exports = Im3xWidget 213 | 214 | // test 215 | // await new Im3xWidget().test() 216 | 217 | // init 218 | // await new Im3xWidget(0, true).init() -------------------------------------------------------------------------------- /one/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zooPanda/Scriptables/bbdf64070c4b3324955b2bca346ef557ed574272/one/screenshot.jpg -------------------------------------------------------------------------------- /template.example.js: -------------------------------------------------------------------------------- 1 | // 2 | // 通用框架插件模版代码 3 | // 项目地址:https://github.com/im3x/Scriptables 4 | // 5 | 6 | class Im3xWidget { 7 | /** 8 | * 初始化 9 | * @param arg 外部传递过来的参数 10 | */ 11 | constructor (arg, loader) { 12 | this.arg = arg 13 | this.loader = loader 14 | this.fileName = module.filename.split('Documents/')[1] 15 | this.widgetSize = config.widgetFamily 16 | } 17 | /** 18 | * 渲染组件 19 | */ 20 | async render () { 21 | if (this.widgetSize === 'medium') { 22 | return await this.renderMedium() 23 | } else if (this.widgetSize === 'large') { 24 | return await this.renderLarge() 25 | } else { 26 | return await this.renderSmall() 27 | } 28 | } 29 | 30 | /** 31 | * 渲染小尺寸组件 32 | */ 33 | async renderSmall () { 34 | let w = new ListWidget() 35 | w.addText("不支持尺寸") 36 | return w 37 | } 38 | /** 39 | * 渲染中尺寸组件 40 | */ 41 | async renderMedium () { 42 | let w = new ListWidget() 43 | w.addText("不支持尺寸") 44 | return w 45 | } 46 | /** 47 | * 渲染大尺寸组件 48 | */ 49 | async renderLarge () { 50 | let w = new ListWidget() 51 | w.addText("不支持尺寸") 52 | return w 53 | } 54 | 55 | /** 56 | * 用户传递的组件自定义点击操作 57 | */ 58 | async runActions () { 59 | let { act, data } = this.parseQuery() 60 | if (!act) return 61 | } 62 | 63 | // 获取跳转自身 urlscheme 64 | // w.url = this.getURIScheme("copy", "data-to-copy") 65 | getURIScheme (act, data) { 66 | let _raw = typeof data === 'object' ? JSON.stringify(data) : data 67 | let _data = Data.fromString(_raw) 68 | let _b64 = _data.toBase64String() 69 | return `${URLScheme.forRunningScript()}?&act=${act}&data=${_b64}` 70 | } 71 | // 解析 urlscheme 参数 72 | // { act: "copy", data: "copy" } 73 | parseQuery () { 74 | const { act, data } = args['queryParameters'] 75 | if (!act) return { act } 76 | let _data = Data.fromBase64String(data) 77 | let _raw = _data.toRawString() 78 | let result = _raw 79 | try { 80 | result = JSON.parse(_raw) 81 | } catch (e) {} 82 | return { 83 | act, 84 | data: result 85 | } 86 | } 87 | 88 | /** 89 | * 渲染标题 90 | * @param widget 组件对象 91 | * @param icon 图标url地址 92 | * @param title 标题 93 | */ 94 | async renderHeader (widget, icon, title) { 95 | let header = widget.addStack() 96 | header.centerAlignContent() 97 | let _icon = header.addImage(await this.getImage(icon)) 98 | _icon.imageSize = new Size(14, 14) 99 | _icon.cornerRadius = 4 100 | header.addSpacer(10) 101 | let _title = header.addText(title) 102 | _title.textColor = Color.white() 103 | _title.textOpacity = 0.7 104 | _title.font = Font.boldSystemFont(12) 105 | widget.addSpacer(15) 106 | return widget 107 | } 108 | 109 | /** 110 | * 获取api数据 111 | * @param api api地址 112 | * @param json 接口数据是否是 json 格式,如果不是(纯text),则传递 false 113 | * @return 数据 || null 114 | */ 115 | async getData (api, json = true) { 116 | let data = null 117 | const cacheKey = `${this.fileName}_cache` 118 | try { 119 | let req = new Request(api) 120 | data = await (json ? req.loadJSON() : req.loadString()) 121 | } catch (e) {} 122 | // 判断数据是否为空(加载失败) 123 | if (!data) { 124 | // 判断是否有缓存 125 | if (Keychain.contains(cacheKey)) { 126 | let cache = Keychain.get(cacheKey) 127 | return json ? JSON.parse(cache) : cache 128 | } else { 129 | // 刷新 130 | return null 131 | } 132 | } 133 | // 存储缓存 134 | Keychain.set(cacheKey, json ? JSON.stringify(data) : data) 135 | return data 136 | } 137 | /** 138 | * 加载远程图片 139 | * @param url string 图片地址 140 | * @return image 141 | */ 142 | async getImage (url) { 143 | try { 144 | let req = new Request(url) 145 | return await req.loadImage() 146 | } catch (e) { 147 | let ctx = new DrawContext() 148 | ctx.size = new Size(100, 100) 149 | ctx.setFillColor(Color.red()) 150 | ctx.fillRect(new Rect(0, 0, 100, 100)) 151 | return await ctx.getImage() 152 | } 153 | } 154 | 155 | /** 156 | * 给图片加上半透明遮罩 157 | * @param img 要处理的图片对象 158 | * @return image 159 | */ 160 | async shadowImage (img) { 161 | let ctx = new DrawContext() 162 | ctx.size = img.size 163 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 164 | // 图片遮罩颜色、透明度设置 165 | ctx.setFillColor(new Color("#000000", 0.7)) 166 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 167 | let res = await ctx.getImage() 168 | return res 169 | } 170 | 171 | /** 172 | * 编辑测试使用 173 | */ 174 | async test () { 175 | if (config.runsInWidget) return 176 | this.widgetSize = 'small' 177 | let w1 = await this.render() 178 | await w1.presentSmall() 179 | this.widgetSize = 'medium' 180 | let w2 = await this.render() 181 | await w2.presentMedium() 182 | this.widgetSize = 'large' 183 | let w3 = await this.render() 184 | await w3.presentLarge() 185 | } 186 | 187 | /** 188 | * 组件单独在桌面运行时调用 189 | */ 190 | async init () { 191 | if (!config.runsInWidget) return await this.runActions() 192 | let widget = await this.render() 193 | Script.setWidget(widget) 194 | Script.complete() 195 | } 196 | } 197 | 198 | module.exports = Im3xWidget 199 | 200 | // 如果是在编辑器内编辑、运行、测试,则取消注释这行,便于调试: 201 | // await new Im3xWidget('').test() 202 | 203 | // 如果是组件单独使用(桌面配置选择这个组件使用,则取消注释这一行: 204 | // await new Im3xWidget(args.widgetParameter, true).init() -------------------------------------------------------------------------------- /update.notify.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": false, 3 | "title": "[ im3x/Scriptables ]", 4 | "body": "新的组件发布:知乎热榜\n参数:zhihu", 5 | "openURL": "https://github.com/im3x/Scriptables/tree/main/zhihu", 6 | "sound": "piano_success" 7 | } 8 | -------------------------------------------------------------------------------- /v2ex/README.md: -------------------------------------------------------------------------------- 1 | ![](/v2ex/screenshot.jpg) 2 | 3 | # v2ex for scriptable 4 | > iOS14 桌面组件展示 v2ex 站点的文章,点击后跳转文章详情页面 5 | 6 | 7 | ## api 版本 8 | 最新文章参数:`v2ex@api` 9 | 最热文章参数:`v2ex@api:hot` 10 | 11 | ## tab 版本 12 | > 就是 [v2ex](https://www.v2ex.com) 站点的首页顶部 tab 分类,比如全部分类是`all`、创意分类是`creative`,依次类推 13 | 14 | 技术分类:`v2ex@tab:tech` 15 | 创意:`v2ex@tab:creative` 16 | 好玩:`v2ex@tab:play` 17 | Apple:`v2ex@tab:apple` 18 | 酷工作:`v2ex@tab:jobs` 19 | 交易:`v2ex@tab:deals` 20 | 城市:`v2ex@tab:city` 21 | 问与答:`v2ex@tab:qna` 22 | 最热:`v2ex@tab:hot` 23 | 全部:`v2ex@tab:all` (默认,也可以直接输入参数`v2ex@tab`) 24 | R2:`v2ex@tab:r2` 25 | 26 | ## go 节点版本 27 | > 就是点击节点进入的界面,比如[分享创造](https://www.v2ex.com/go/create),节点就是:`create`,对应参数:`v2ex@go:create` 28 | 29 | 各种节点参数请参考官方网站列表:https://www.v2ex.com/planes 30 | 31 | ## 如何使用 32 | 桌面添加组件,然后选择加载器,`Parameter` 参数中填写如下格式: 33 | 34 | 1. 最新文章:`v2ex@api` 35 | 2. 最热文章:`v2ex@api:hot` 36 | 37 | ## 其他说明 38 | 目前该组件仅支持展示最新、最热文章(调用官方api接口) 39 | -------------------------------------------------------------------------------- /v2ex/api.js: -------------------------------------------------------------------------------- 1 | // 2 | // v2ex for scriptables 3 | // author:hack_fish 4 | // 项目地址:https://github.com/im3x/Scriptables 5 | // 功能说明:调用v2ex的官方api,展示最新、最热的内容,点击后跳转相应URL 6 | // 参数列表: 7 | // v2ex@api:hot 8 | // v2ex@api:latest(默认) 9 | // 10 | 11 | class Im3xWidget { 12 | // 初始化,接收参数 13 | constructor (arg, loader) { 14 | this.arg = 'latest' 15 | this.loader = loader 16 | this.fileName = module.filename.split('Documents/')[1] 17 | this.widgetSize = config.widgetFamily 18 | if (arg === 'hot') this.arg = 'hot' 19 | } 20 | // 渲染组件 21 | async render () { 22 | if (this.widgetSize === 'medium') { 23 | return await this.renderMedium() 24 | } else if (this.widgetSize === 'large') { 25 | return await this.renderLarge() 26 | } else { 27 | return await this.renderSmall() 28 | } 29 | } 30 | async renderSmall () { 31 | let w = new ListWidget() 32 | let data = await this.getData() 33 | let topic = data[0] 34 | w.url = this.loader ? this.getURIScheme('open-url', { 35 | url: topic['url'] 36 | }) : topic['url'] 37 | w = await this.renderHeader(w) 38 | let content = w.addText(topic['title']) 39 | content.font = Font.lightSystemFont(16) 40 | content.textColor = Color.white() 41 | content.lineLimit = 3 42 | 43 | w.backgroundImage = await this.shadowImage(await this.getImage(topic['member']['avatar_large'].replace('mini', 'large'))) 44 | 45 | w.addSpacer(10) 46 | let footer = w.addText(`@${topic['member']['username']} / ${topic['node']['title']}`) 47 | footer.font = Font.lightSystemFont(10) 48 | footer.textColor = Color.white() 49 | footer.textOpacity = 0.5 50 | footer.lineLimit = 1 51 | return w 52 | } 53 | // 中尺寸组件 54 | async renderMedium () { 55 | let w = new ListWidget() 56 | let data = await this.getData() 57 | w.addSpacer(10) 58 | w = await this.renderHeader(w, false) 59 | for (let i = 0; i < 2; i ++) { 60 | w = await this.renderCell(w, data[i]) 61 | w.addSpacer(5) 62 | } 63 | 64 | return w 65 | } 66 | // 大尺寸组件 67 | async renderLarge () { 68 | let w = new ListWidget() 69 | let data = await this.getData() 70 | w.addSpacer(10) 71 | w = await this.renderHeader(w, false) 72 | for (let i = 0; i < 5; i ++) { 73 | w = await this.renderCell(w, data[i]) 74 | w.addSpacer(10) 75 | } 76 | 77 | return w 78 | } 79 | async renderCell (widget, topic) { 80 | let body = widget.addStack() 81 | body.url = this.loader ? this.getURIScheme('open-url', { 82 | url: topic['url'] 83 | }) : topic['url'] 84 | 85 | let left = body.addStack() 86 | let avatar = left.addImage(await this.getImage(topic['member']['avatar_large'].replace('mini', 'large'))) 87 | avatar.imageSize = new Size(35, 35) 88 | avatar.cornerRadius = 5 89 | 90 | body.addSpacer(10) 91 | 92 | let right = body.addStack() 93 | right.layoutVertically() 94 | let content = right.addText(topic['title']) 95 | content.font = Font.lightSystemFont(14) 96 | content.lineLimit = 2 97 | 98 | right.addSpacer(5) 99 | 100 | let info = right.addText(`@${topic['member']['username']} / ${topic['node']['title']}`) 101 | info.font = Font.lightSystemFont(10) 102 | info.textOpacity = 0.6 103 | info.lineLimit = 2 104 | 105 | widget.addSpacer(10) 106 | 107 | return widget 108 | } 109 | async renderHeader (widget, customStyle = true) { 110 | let _icon = await this.getImage("https://www.v2ex.com/static/img/icon_rayps_64.png") 111 | let _title = "V2EX·" + (this.arg === 'hot' ? '最热' : '最新') 112 | 113 | let header = widget.addStack() 114 | header.centerAlignContent() 115 | let icon = header.addImage(_icon) 116 | icon.imageSize = new Size(14, 14) 117 | icon.cornerRadius = 4 118 | header.addSpacer(10) 119 | let title = header.addText(_title) 120 | if (customStyle) title.textColor = Color.white() 121 | title.textOpacity = 0.7 122 | title.font = Font.boldSystemFont(12) 123 | 124 | widget.addSpacer(15) 125 | return widget 126 | } 127 | /** 128 | * 用户传递的组件自定义点击操作 129 | */ 130 | async runActions () { 131 | let { act, data } = this.parseQuery() 132 | if (!act) return 133 | if (act === 'open-url') { 134 | Safari.openInApp(data['url'], false) 135 | } 136 | } 137 | 138 | // 获取跳转自身 urlscheme 139 | // w.url = this.getURIScheme("copy", "data-to-copy") 140 | getURIScheme (act, data) { 141 | let _raw = typeof data === 'object' ? JSON.stringify(data) : data 142 | let _data = Data.fromString(_raw) 143 | let _b64 = _data.toBase64String() 144 | return `scriptable:///run?scriptName=${encodeURIComponent(Script.name())}&act=${act}&data=${_b64}&__widget__=${encodeURIComponent(args['widgetParameter'])}` 145 | } 146 | // 解析 urlscheme 参数 147 | // { act: "copy", data: "copy" } 148 | parseQuery () { 149 | const { act, data } = args['queryParameters'] 150 | if (!act) return { act } 151 | let _data = Data.fromBase64String(data) 152 | let _raw = _data.toRawString() 153 | let result = _raw 154 | try { 155 | result = JSON.parse(_raw) 156 | } catch (e) {} 157 | return { 158 | act, 159 | data: result 160 | } 161 | } 162 | // 获取远程图片 163 | async getImage (url) { 164 | try { 165 | let req = new Request(url) 166 | return await req.loadImage() 167 | } catch (e) { 168 | let ctx = new DrawContext() 169 | ctx.size = new Size(100, 100) 170 | ctx.setFillColor(Color.red()) 171 | ctx.fillRect(new Rect(0, 0, 100, 100)) 172 | return await ctx.getImage() 173 | } 174 | } 175 | // 给图片加透明遮罩 176 | async shadowImage (img) { 177 | let ctx = new DrawContext() 178 | // 获取图片的尺寸 179 | ctx.size = img.size 180 | 181 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 182 | ctx.setFillColor(new Color("#000000", 0.7)) 183 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 184 | 185 | let res = await ctx.getImage() 186 | return res 187 | } 188 | // 加载数据 189 | async getData () { 190 | let api = `https://www.v2ex.com/api/topics/${this.arg}.json` 191 | let data = await this.fetchAPI(api) 192 | return data 193 | } 194 | async fetchAPI (api, json = true) { 195 | let data = null 196 | const cacheKey = `${this.fileName}_cache` 197 | try { 198 | let req = new Request(api) 199 | data = await (json ? req.loadJSON() : req.loadString()) 200 | } catch (e) {} 201 | // 判断数据是否为空(加载失败) 202 | if (!data) { 203 | // 判断是否有缓存 204 | if (Keychain.contains(cacheKey)) { 205 | let cache = Keychain.get(cacheKey) 206 | return json ? JSON.parse(cache) : cache 207 | } else { 208 | // 刷新 209 | return null 210 | } 211 | } 212 | // 存储缓存 213 | Keychain.set(cacheKey, json ? JSON.stringify(data) : data) 214 | return data 215 | } 216 | // 用于测试 217 | async test () { 218 | if (config.runsInWidget) return 219 | this.widgetSize = 'small' 220 | let w1 = await this.render() 221 | await w1.presentSmall() 222 | this.widgetSize = 'medium' 223 | let w2 = await this.render() 224 | await w2.presentMedium() 225 | this.widgetSize = 'large' 226 | let w3 = await this.render() 227 | await w3.presentLarge() 228 | } 229 | // 单独运行 230 | async init () { 231 | if (!config.runsInWidget) return await this.runActions() 232 | let widget = await this.render() 233 | Script.setWidget(widget) 234 | Script.complete() 235 | } 236 | } 237 | 238 | module.exports = Im3xWidget 239 | 240 | // 如果是在编辑器内编辑、运行、测试,则取消注释这行,便于调试: 241 | // await new Im3xWidget('', true).test() 242 | 243 | // 如果是组件单独使用(桌面配置选择这个组件使用,则取消注释这一行: 244 | // await new Im3xWidget(args.widgetParameter, true).init() -------------------------------------------------------------------------------- /v2ex/go.js: -------------------------------------------------------------------------------- 1 | // 2 | // v2ex for scriptables 3 | // author:hack_fish 4 | // 项目地址:https://github.com/im3x/Scriptables 5 | // 功能说明:解析v2ex网站的源码并展示数据(节点页面) 6 | // 参数列表: 7 | // v2ex@go 8 | // v2ex@go:create(分享创造,默认) 9 | // v2ex@go:share 10 | // ... 11 | // 12 | 13 | class Im3xWidget { 14 | // 初始化,接收参数 15 | constructor (arg, loader) { 16 | this.arg = arg || 'create' 17 | this.widgetSize = config.widgetFamily 18 | this.loader = loader 19 | this.fileName = module.filename.split('Documents/')[1] 20 | } 21 | // 渲染组件 22 | async render () { 23 | if (this.widgetSize === 'medium') { 24 | return await this.renderMedium() 25 | } else if (this.widgetSize === 'large') { 26 | return await this.renderLarge() 27 | } else { 28 | return await this.renderSmall() 29 | } 30 | } 31 | async renderSmall () { 32 | let w = new ListWidget() 33 | let data = await this.getData() 34 | let topic = data[0] 35 | w.url = this.loader ? this.getURIScheme('open-url', { 36 | url: topic['url'] 37 | }) : topic['url'] 38 | w = await this.renderHeader(w) 39 | let content = w.addText(topic['title']) 40 | content.font = Font.lightSystemFont(16) 41 | content.textColor = Color.white() 42 | content.lineLimit = 3 43 | 44 | w.backgroundImage = await this.shadowImage(await this.getImage(topic['member']['avatar_large'].replace('mini', 'large'))) 45 | 46 | w.addSpacer(10) 47 | let footer = w.addText(`@${topic['member']['username']} / ${topic['node']['title']}`) 48 | footer.font = Font.lightSystemFont(10) 49 | footer.textColor = Color.white() 50 | footer.textOpacity = 0.5 51 | footer.lineLimit = 1 52 | return w 53 | } 54 | // 中尺寸组件 55 | async renderMedium () { 56 | let w = new ListWidget() 57 | let data = await this.getData() 58 | w.addSpacer(10) 59 | w = await this.renderHeader(w, false) 60 | for (let i = 0; i < 2; i ++) { 61 | w = await this.renderCell(w, data[i]) 62 | w.addSpacer(5) 63 | } 64 | 65 | return w 66 | } 67 | // 大尺寸组件 68 | async renderLarge () { 69 | let w = new ListWidget() 70 | let data = await this.getData() 71 | w.addSpacer(10) 72 | w = await this.renderHeader(w, false) 73 | for (let i = 0; i < 5; i ++) { 74 | w = await this.renderCell(w, data[i]) 75 | w.addSpacer(10) 76 | } 77 | 78 | return w 79 | } 80 | async renderCell (widget, topic) { 81 | let body = widget.addStack() 82 | body.url = this.loader ? this.getURIScheme('open-url', { 83 | url: topic['url'] 84 | }) : topic['url'] 85 | 86 | let left = body.addStack() 87 | let avatar = left.addImage(await this.getImage(topic['member']['avatar_large'].replace('mini', 'large'))) 88 | avatar.imageSize = new Size(35, 35) 89 | avatar.cornerRadius = 5 90 | 91 | body.addSpacer(10) 92 | 93 | let right = body.addStack() 94 | right.layoutVertically() 95 | let content = right.addText(topic['title']) 96 | content.font = Font.lightSystemFont(14) 97 | content.lineLimit = 2 98 | 99 | right.addSpacer(5) 100 | 101 | let info = right.addText(`@${topic['member']['username']} / ${topic['node']['title']}`) 102 | info.font = Font.lightSystemFont(10) 103 | info.textOpacity = 0.6 104 | info.lineLimit = 2 105 | 106 | widget.addSpacer(10) 107 | 108 | return widget 109 | } 110 | async renderHeader (widget, customStyle = true) { 111 | let _icon = await this.getImage("https://www.v2ex.com/static/img/icon_rayps_64.png") 112 | let _title = "V2EX / 节点" 113 | 114 | let header = widget.addStack() 115 | header.centerAlignContent() 116 | let icon = header.addImage(_icon) 117 | icon.imageSize = new Size(14, 14) 118 | icon.cornerRadius = 4 119 | header.addSpacer(10) 120 | let title = header.addText(_title) 121 | if (customStyle) title.textColor = Color.white() 122 | title.textOpacity = 0.7 123 | title.font = Font.boldSystemFont(12) 124 | 125 | widget.addSpacer(15) 126 | return widget 127 | } 128 | async runActions () { 129 | let { act, data } = this.parseQuery() 130 | if (!act) return 131 | if (act === 'open-url') { 132 | Safari.openInApp(data['url'], false) 133 | } 134 | } 135 | 136 | // 获取跳转自身 urlscheme 137 | // w.url = this.getURIScheme("copy", "data-to-copy") 138 | getURIScheme (act, data) { 139 | let _raw = typeof data === 'object' ? JSON.stringify(data) : data 140 | let _data = Data.fromString(_raw) 141 | let _b64 = _data.toBase64String() 142 | return `scriptable:///run?scriptName=${encodeURIComponent(Script.name())}&act=${act}&data=${_b64}&__widget__=${encodeURIComponent(args['widgetParameter'])}` 143 | } 144 | // 解析 urlscheme 参数 145 | // { act: "copy", data: "copy" } 146 | parseQuery () { 147 | const { act, data } = args['queryParameters'] 148 | if (!act) return { act } 149 | let _data = Data.fromBase64String(data) 150 | let _raw = _data.toRawString() 151 | let result = _raw 152 | try { 153 | result = JSON.parse(_raw) 154 | } catch (e) {} 155 | return { 156 | act, 157 | data: result 158 | } 159 | } 160 | // 获取远程图片 161 | async getImage (url) { 162 | try { 163 | let req = new Request(url) 164 | return await req.loadImage() 165 | } catch (e) { 166 | let ctx = new DrawContext() 167 | ctx.size = new Size(100, 100) 168 | ctx.setFillColor(Color.red()) 169 | ctx.fillRect(new Rect(0, 0, 100, 100)) 170 | return await ctx.getImage() 171 | } 172 | } 173 | // 给图片加透明遮罩 174 | async shadowImage (img) { 175 | let ctx = new DrawContext() 176 | // 获取图片的尺寸 177 | ctx.size = img.size 178 | 179 | ctx.drawImageInRect(img, new Rect(0, 0, img.size['width'], img.size['height'])) 180 | ctx.setFillColor(new Color("#000000", 0.7)) 181 | ctx.fillRect(new Rect(0, 0, img.size['width'], img.size['height'])) 182 | 183 | let res = await ctx.getImage() 184 | return res 185 | } 186 | // 加载数据 187 | async getData () { 188 | let url = `https://www.v2ex.com/go/${this.arg}` 189 | let html = await this.fetchAPI(url, false) 190 | 191 | // 解析html 192 | let tmp = html.split(`
`)[1].split(`