├── LICENSE ├── README.md ├── development ├── api.mjs ├── infos.json └── keyMap.json ├── docker ├── Dockerfile ├── config.toml ├── dianzhongdian │ └── __init__.py └── utils.py ├── karin-meme.js └── meme.js /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 | # yunzai-meme 2 | ![3F53077B8088F0C03FA7C81B2E4CC62A](https://user-images.githubusercontent.com/21212372/228231810-3202ff31-e5ed-4ab8-a93f-a55ab6d48f58.gif) 3 | 4 | 基于meme-generator的Yunzai机器人的表情包插件 5 | 6 | ## 搭建meme服务器 7 | 8 | ### 默认的免费API 9 | 10 | 提供了一个默认的免费API([状态](https://avocado-status.ikechan8370.com/status/chatgpt-meme)),要求高稳定性的建议自己搭建,然后修改第9行的baseUrl为你的自建meme api,参考[这里](https://github.com/MeetWq/meme-generator)搭建。可选加入[扩展包](https://github.com/MeetWq/meme-generator-contrib) 11 | 12 | 更新:提供了一个docker镜像,一键搭建:`docker run -d -p 2233:2233 --restart=always geyinchi/meme-generator:latest` 13 | 14 | 更新:ARM版镜像,由@Regalia提供:`regaliaf/meme-generator` 15 | 16 | 更新:MeetWq/meme-generator仓库也提供了docker镜像和Dockfile 17 | 18 | 要求不高或者没条件可以用内置的API。 19 | 20 | ### 自行搭建meme服务器 21 | 22 | - 使用脚本搭建meme服务器 23 | ```sh 24 | bash <(curl -sL https://raw.githubusercontent.com/misaka20002/Bot-Install-Shell/refs/heads/master/Manage/meme_generator.sh) 25 | # 或使用 ghfast 加速: 26 | # bash <(curl -sL https://ghfast.top/https://raw.githubusercontent.com/misaka20002/Bot-Install-Shell/refs/heads/master/Manage/meme_generator.sh) 27 | ``` 28 | 29 | ### huggingface搭建api 30 | 31 | 支持试用huggingface搭建api了,可以duplicate我的space:https://huggingface.co/spaces/ikechan8370/meme-generator 32 | 然后api填https://[username]-meme-generator.hf.space,例如我的就是https://ikechan8370-meme-generator.hf.space 33 | 原API将转发到该仓库,对大陆用户可能友好一些 34 | 35 | ## 安装 36 | 37 | 直接把meme.js扔到plugins/example目录下即可 38 | 39 | 下载链接: 40 | 41 | https://raw.githubusercontent.com/ikechan8370/yunzai-meme/main/meme.js 42 | 43 | 或者大陆服务器可以用gitee,不一定有github更新及时 44 | 45 | https://gitee.com/ikechan/yunzai-meme/raw/main/meme.js 46 | 47 | 安装后可能需要重启,如果没响应就重启一下试试 48 | 可以发送meme更新进行资源的在线更新。 49 | 50 | ## 食用方法 51 | 52 | 使用`meme帮助`查看帮助 53 | 54 | 建议先查看https://github.com/MeetWq/meme-generator 了解支持的表情包及合成要求。 55 | 56 | 需要图片合成表情包的可以通过回复、艾特(取头像)、默认(自己的头像)获取素材图片 57 | 58 | 需要文字合成表情包的需要在指令中添加文字,并用/隔开,如:可达鸭我爱你/你爱我 59 | 60 | 0626更新:`#meme更新`进行在线更新 61 | 62 | > 如果觉得有帮助,请帮我点一个免费的Star,谢谢! 63 | 64 | ## 致谢 65 | 66 | * https://github.com/MeetWq/meme-generator 67 | * https://github.com/MeetWq/meme-generator-contrib 68 | -------------------------------------------------------------------------------- /development/api.mjs: -------------------------------------------------------------------------------- 1 | // import fetch from 'node-fetch'; 2 | import fs from 'fs' 3 | const baseUrl = '' 4 | 5 | let keysRes = await fetch(`${baseUrl}/memes/keys`) 6 | let keys = await keysRes.json() 7 | 8 | let keyMapTmp = {} 9 | let infosTmp = {} 10 | for (const key of keys) { 11 | let keyInfoRes = await fetch(`${baseUrl}/memes/${key}/info`) 12 | let info = await keyInfoRes.json() 13 | info.keywords.forEach(keyword => { 14 | keyMapTmp[keyword] = key 15 | }) 16 | infosTmp[key] = info 17 | } 18 | let infos = infosTmp 19 | let keyMap = keyMapTmp 20 | fs.writeFileSync('keyMap.json', JSON.stringify(keyMap)) 21 | fs.writeFileSync('infos.json', JSON.stringify(infos)) -------------------------------------------------------------------------------- /development/infos.json: -------------------------------------------------------------------------------- 1 | { 2 | "universal": { 3 | "key": "universal", 4 | "keywords": [ 5 | "万能表情", 6 | "空白表情" 7 | ], 8 | "patterns": [], 9 | "params": { 10 | "min_images": 1, 11 | "max_images": 1, 12 | "min_texts": 1, 13 | "max_texts": 10, 14 | "default_texts": [ 15 | "在此处添加文字" 16 | ], 17 | "args": [] 18 | } 19 | }, 20 | "overtime": { 21 | "key": "overtime", 22 | "keywords": [ 23 | "加班" 24 | ], 25 | "patterns": [], 26 | "params": { 27 | "min_images": 1, 28 | "max_images": 1, 29 | "min_texts": 0, 30 | "max_texts": 0, 31 | "default_texts": [], 32 | "args": [] 33 | } 34 | }, 35 | "not_call_me": { 36 | "key": "not_call_me", 37 | "keywords": [ 38 | "不喊我" 39 | ], 40 | "patterns": [], 41 | "params": { 42 | "min_images": 0, 43 | "max_images": 0, 44 | "min_texts": 1, 45 | "max_texts": 1, 46 | "default_texts": [ 47 | "开银趴不喊我是吧" 48 | ], 49 | "args": [] 50 | } 51 | }, 52 | "rip": { 53 | "key": "rip", 54 | "keywords": [ 55 | "撕" 56 | ], 57 | "patterns": [], 58 | "params": { 59 | "min_images": 1, 60 | "max_images": 2, 61 | "min_texts": 0, 62 | "max_texts": 0, 63 | "default_texts": [], 64 | "args": [] 65 | } 66 | }, 67 | "roll": { 68 | "key": "roll", 69 | "keywords": [ 70 | "滚" 71 | ], 72 | "patterns": [], 73 | "params": { 74 | "min_images": 1, 75 | "max_images": 1, 76 | "min_texts": 0, 77 | "max_texts": 0, 78 | "default_texts": [], 79 | "args": [] 80 | } 81 | }, 82 | "alike": { 83 | "key": "alike", 84 | "keywords": [ 85 | "一样" 86 | ], 87 | "patterns": [], 88 | "params": { 89 | "min_images": 1, 90 | "max_images": 1, 91 | "min_texts": 0, 92 | "max_texts": 0, 93 | "default_texts": [], 94 | "args": [] 95 | } 96 | }, 97 | "confuse": { 98 | "key": "confuse", 99 | "keywords": [ 100 | "迷惑" 101 | ], 102 | "patterns": [], 103 | "params": { 104 | "min_images": 1, 105 | "max_images": 1, 106 | "min_texts": 0, 107 | "max_texts": 0, 108 | "default_texts": [], 109 | "args": [] 110 | } 111 | }, 112 | "coupon": { 113 | "key": "coupon", 114 | "keywords": [ 115 | "兑换券" 116 | ], 117 | "patterns": [], 118 | "params": { 119 | "min_images": 1, 120 | "max_images": 1, 121 | "min_texts": 0, 122 | "max_texts": 1, 123 | "default_texts": [], 124 | "args": [] 125 | } 126 | }, 127 | "look_flat": { 128 | "key": "look_flat", 129 | "keywords": [ 130 | "看扁" 131 | ], 132 | "patterns": [], 133 | "params": { 134 | "min_images": 1, 135 | "max_images": 1, 136 | "min_texts": 0, 137 | "max_texts": 1, 138 | "default_texts": [ 139 | "可恶...被人看扁了" 140 | ], 141 | "args": [ 142 | { 143 | "name": "ratio", 144 | "type": "integer", 145 | "description": "图片“压扁”比例", 146 | "default": 2, 147 | "enum": null 148 | } 149 | ] 150 | } 151 | }, 152 | "beat_head": { 153 | "key": "beat_head", 154 | "keywords": [ 155 | "拍头" 156 | ], 157 | "patterns": [], 158 | "params": { 159 | "min_images": 1, 160 | "max_images": 1, 161 | "min_texts": 0, 162 | "max_texts": 1, 163 | "default_texts": [], 164 | "args": [] 165 | } 166 | }, 167 | "rise_dead": { 168 | "key": "rise_dead", 169 | "keywords": [ 170 | "诈尸", 171 | "秽土转生" 172 | ], 173 | "patterns": [], 174 | "params": { 175 | "min_images": 1, 176 | "max_images": 1, 177 | "min_texts": 0, 178 | "max_texts": 0, 179 | "default_texts": [], 180 | "args": [] 181 | } 182 | }, 183 | "teach": { 184 | "key": "teach", 185 | "keywords": [ 186 | "讲课", 187 | "敲黑板" 188 | ], 189 | "patterns": [], 190 | "params": { 191 | "min_images": 1, 192 | "max_images": 1, 193 | "min_texts": 0, 194 | "max_texts": 1, 195 | "default_texts": [ 196 | "我老婆" 197 | ], 198 | "args": [] 199 | } 200 | }, 201 | "china_flag": { 202 | "key": "china_flag", 203 | "keywords": [ 204 | "国旗" 205 | ], 206 | "patterns": [], 207 | "params": { 208 | "min_images": 1, 209 | "max_images": 1, 210 | "min_texts": 0, 211 | "max_texts": 0, 212 | "default_texts": [], 213 | "args": [] 214 | } 215 | }, 216 | "luoyonghao_say": { 217 | "key": "luoyonghao_say", 218 | "keywords": [ 219 | "罗永浩说" 220 | ], 221 | "patterns": [], 222 | "params": { 223 | "min_images": 0, 224 | "max_images": 0, 225 | "min_texts": 1, 226 | "max_texts": 1, 227 | "default_texts": [ 228 | "又不是不能用" 229 | ], 230 | "args": [] 231 | } 232 | }, 233 | "read_book": { 234 | "key": "read_book", 235 | "keywords": [ 236 | "看书" 237 | ], 238 | "patterns": [], 239 | "params": { 240 | "min_images": 1, 241 | "max_images": 1, 242 | "min_texts": 0, 243 | "max_texts": 0, 244 | "default_texts": [], 245 | "args": [] 246 | } 247 | }, 248 | "rip_angrily": { 249 | "key": "rip_angrily", 250 | "keywords": [ 251 | "怒撕" 252 | ], 253 | "patterns": [], 254 | "params": { 255 | "min_images": 1, 256 | "max_images": 1, 257 | "min_texts": 0, 258 | "max_texts": 0, 259 | "default_texts": [], 260 | "args": [] 261 | } 262 | }, 263 | "capoo_strike": { 264 | "key": "capoo_strike", 265 | "keywords": [ 266 | "咖波撞", 267 | "咖波头槌" 268 | ], 269 | "patterns": [], 270 | "params": { 271 | "min_images": 1, 272 | "max_images": 1, 273 | "min_texts": 0, 274 | "max_texts": 0, 275 | "default_texts": [], 276 | "args": [] 277 | } 278 | }, 279 | "charpic": { 280 | "key": "charpic", 281 | "keywords": [ 282 | "字符画" 283 | ], 284 | "patterns": [], 285 | "params": { 286 | "min_images": 1, 287 | "max_images": 1, 288 | "min_texts": 0, 289 | "max_texts": 0, 290 | "default_texts": [], 291 | "args": [] 292 | } 293 | }, 294 | "shock": { 295 | "key": "shock", 296 | "keywords": [ 297 | "震惊" 298 | ], 299 | "patterns": [], 300 | "params": { 301 | "min_images": 1, 302 | "max_images": 1, 303 | "min_texts": 0, 304 | "max_texts": 0, 305 | "default_texts": [], 306 | "args": [] 307 | } 308 | }, 309 | "crawl": { 310 | "key": "crawl", 311 | "keywords": [ 312 | "爬" 313 | ], 314 | "patterns": [], 315 | "params": { 316 | "min_images": 1, 317 | "max_images": 1, 318 | "min_texts": 0, 319 | "max_texts": 0, 320 | "default_texts": [], 321 | "args": [ 322 | { 323 | "name": "number", 324 | "type": "integer", 325 | "description": "图片编号,范围为 1~92", 326 | "default": 0, 327 | "enum": null 328 | } 329 | ] 330 | } 331 | }, 332 | "wish_fail": { 333 | "key": "wish_fail", 334 | "keywords": [ 335 | "许愿失败" 336 | ], 337 | "patterns": [], 338 | "params": { 339 | "min_images": 0, 340 | "max_images": 0, 341 | "min_texts": 1, 342 | "max_texts": 1, 343 | "default_texts": [ 344 | "我要对象" 345 | ], 346 | "args": [] 347 | } 348 | }, 349 | "tightly": { 350 | "key": "tightly", 351 | "keywords": [ 352 | "紧贴", 353 | "紧紧贴着" 354 | ], 355 | "patterns": [], 356 | "params": { 357 | "min_images": 1, 358 | "max_images": 1, 359 | "min_texts": 0, 360 | "max_texts": 0, 361 | "default_texts": [], 362 | "args": [] 363 | } 364 | }, 365 | "my_wife": { 366 | "key": "my_wife", 367 | "keywords": [ 368 | "我老婆", 369 | "这是我老婆" 370 | ], 371 | "patterns": [], 372 | "params": { 373 | "min_images": 1, 374 | "max_images": 1, 375 | "min_texts": 0, 376 | "max_texts": 0, 377 | "default_texts": [], 378 | "args": [] 379 | } 380 | }, 381 | "klee_eat": { 382 | "key": "klee_eat", 383 | "keywords": [ 384 | "可莉吃" 385 | ], 386 | "patterns": [], 387 | "params": { 388 | "min_images": 1, 389 | "max_images": 1, 390 | "min_texts": 0, 391 | "max_texts": 0, 392 | "default_texts": [], 393 | "args": [] 394 | } 395 | }, 396 | "wangjingze": { 397 | "key": "wangjingze", 398 | "keywords": [ 399 | "王境泽" 400 | ], 401 | "patterns": [], 402 | "params": { 403 | "min_images": 0, 404 | "max_images": 0, 405 | "min_texts": 4, 406 | "max_texts": 4, 407 | "default_texts": [ 408 | "我就是饿死", 409 | "死外边 从这里跳下去", 410 | "不会吃你们一点东西", 411 | "真香" 412 | ], 413 | "args": [] 414 | } 415 | }, 416 | "weisuoyuwei": { 417 | "key": "weisuoyuwei", 418 | "keywords": [ 419 | "为所欲为" 420 | ], 421 | "patterns": [], 422 | "params": { 423 | "min_images": 0, 424 | "max_images": 0, 425 | "min_texts": 9, 426 | "max_texts": 9, 427 | "default_texts": [ 428 | "好啊", 429 | "就算你是一流工程师", 430 | "就算你出报告再完美", 431 | "我叫你改报告你就要改", 432 | "毕竟我是客户", 433 | "客户了不起啊", 434 | "Sorry 客户真的了不起", 435 | "以后叫他天天改报告", 436 | "天天改 天天改" 437 | ], 438 | "args": [] 439 | } 440 | }, 441 | "chanshenzi": { 442 | "key": "chanshenzi", 443 | "keywords": [ 444 | "馋身子" 445 | ], 446 | "patterns": [], 447 | "params": { 448 | "min_images": 0, 449 | "max_images": 0, 450 | "min_texts": 3, 451 | "max_texts": 3, 452 | "default_texts": [ 453 | "你那叫喜欢吗?", 454 | "你那是馋她身子", 455 | "你下贱!" 456 | ], 457 | "args": [] 458 | } 459 | }, 460 | "qiegewala": { 461 | "key": "qiegewala", 462 | "keywords": [ 463 | "切格瓦拉" 464 | ], 465 | "patterns": [], 466 | "params": { 467 | "min_images": 0, 468 | "max_images": 0, 469 | "min_texts": 6, 470 | "max_texts": 6, 471 | "default_texts": [ 472 | "没有钱啊 肯定要做的啊", 473 | "不做的话没有钱用", 474 | "那你不会去打工啊", 475 | "有手有脚的", 476 | "打工是不可能打工的", 477 | "这辈子不可能打工的" 478 | ], 479 | "args": [] 480 | } 481 | }, 482 | "shuifandui": { 483 | "key": "shuifandui", 484 | "keywords": [ 485 | "谁反对" 486 | ], 487 | "patterns": [], 488 | "params": { 489 | "min_images": 0, 490 | "max_images": 0, 491 | "min_texts": 4, 492 | "max_texts": 4, 493 | "default_texts": [ 494 | "我话说完了", 495 | "谁赞成", 496 | "谁反对", 497 | "我反对" 498 | ], 499 | "args": [] 500 | } 501 | }, 502 | "zengxiaoxian": { 503 | "key": "zengxiaoxian", 504 | "keywords": [ 505 | "曾小贤" 506 | ], 507 | "patterns": [], 508 | "params": { 509 | "min_images": 0, 510 | "max_images": 0, 511 | "min_texts": 4, 512 | "max_texts": 4, 513 | "default_texts": [ 514 | "平时你打电子游戏吗", 515 | "偶尔", 516 | "星际还是魔兽", 517 | "连连看" 518 | ], 519 | "args": [] 520 | } 521 | }, 522 | "yalidaye": { 523 | "key": "yalidaye", 524 | "keywords": [ 525 | "压力大爷" 526 | ], 527 | "patterns": [], 528 | "params": { 529 | "min_images": 0, 530 | "max_images": 0, 531 | "min_texts": 3, 532 | "max_texts": 3, 533 | "default_texts": [ 534 | "外界都说我们压力大", 535 | "我觉得吧压力也没有那么大", 536 | "主要是28岁了还没媳妇儿" 537 | ], 538 | "args": [] 539 | } 540 | }, 541 | "nihaosaoa": { 542 | "key": "nihaosaoa", 543 | "keywords": [ 544 | "你好骚啊" 545 | ], 546 | "patterns": [], 547 | "params": { 548 | "min_images": 0, 549 | "max_images": 0, 550 | "min_texts": 3, 551 | "max_texts": 3, 552 | "default_texts": [ 553 | "既然追求刺激", 554 | "就贯彻到底了", 555 | "你好骚啊" 556 | ], 557 | "args": [] 558 | } 559 | }, 560 | "shishilani": { 561 | "key": "shishilani", 562 | "keywords": [ 563 | "食屎啦你" 564 | ], 565 | "patterns": [], 566 | "params": { 567 | "min_images": 0, 568 | "max_images": 0, 569 | "min_texts": 4, 570 | "max_texts": 4, 571 | "default_texts": [ 572 | "穿西装打领带", 573 | "拿大哥大有什么用", 574 | "跟着这样的大哥", 575 | "食屎啦你" 576 | ], 577 | "args": [] 578 | } 579 | }, 580 | "wunian": { 581 | "key": "wunian", 582 | "keywords": [ 583 | "五年怎么过的" 584 | ], 585 | "patterns": [], 586 | "params": { 587 | "min_images": 0, 588 | "max_images": 0, 589 | "min_texts": 4, 590 | "max_texts": 4, 591 | "default_texts": [ 592 | "五年", 593 | "你知道我这五年是怎么过的吗", 594 | "我每天躲在家里玩贪玩蓝月", 595 | "你知道有多好玩吗" 596 | ], 597 | "args": [] 598 | } 599 | }, 600 | "chase_train": { 601 | "key": "chase_train", 602 | "keywords": [ 603 | "追列车", 604 | "追火车" 605 | ], 606 | "patterns": [], 607 | "params": { 608 | "min_images": 1, 609 | "max_images": 1, 610 | "min_texts": 0, 611 | "max_texts": 0, 612 | "default_texts": [], 613 | "args": [] 614 | } 615 | }, 616 | "listen_music": { 617 | "key": "listen_music", 618 | "keywords": [ 619 | "听音乐" 620 | ], 621 | "patterns": [], 622 | "params": { 623 | "min_images": 1, 624 | "max_images": 1, 625 | "min_texts": 0, 626 | "max_texts": 0, 627 | "default_texts": [], 628 | "args": [] 629 | } 630 | }, 631 | "hit_screen": { 632 | "key": "hit_screen", 633 | "keywords": [ 634 | "打穿", 635 | "打穿屏幕" 636 | ], 637 | "patterns": [], 638 | "params": { 639 | "min_images": 1, 640 | "max_images": 1, 641 | "min_texts": 0, 642 | "max_texts": 0, 643 | "default_texts": [], 644 | "args": [] 645 | } 646 | }, 647 | "bocchi_draft": { 648 | "key": "bocchi_draft", 649 | "keywords": [ 650 | "波奇手稿" 651 | ], 652 | "patterns": [], 653 | "params": { 654 | "min_images": 1, 655 | "max_images": 1, 656 | "min_texts": 0, 657 | "max_texts": 0, 658 | "default_texts": [], 659 | "args": [] 660 | } 661 | }, 662 | "symmetric": { 663 | "key": "symmetric", 664 | "keywords": [ 665 | "对称" 666 | ], 667 | "patterns": [], 668 | "params": { 669 | "min_images": 1, 670 | "max_images": 1, 671 | "min_texts": 0, 672 | "max_texts": 0, 673 | "default_texts": [], 674 | "args": [ 675 | { 676 | "name": "direction", 677 | "type": "string", 678 | "description": "对称方向", 679 | "default": "left", 680 | "enum": [ 681 | "left", 682 | "right", 683 | "top", 684 | "bottom" 685 | ] 686 | } 687 | ] 688 | } 689 | }, 690 | "mihoyo": { 691 | "key": "mihoyo", 692 | "keywords": [ 693 | "米哈游" 694 | ], 695 | "patterns": [], 696 | "params": { 697 | "min_images": 1, 698 | "max_images": 1, 699 | "min_texts": 0, 700 | "max_texts": 0, 701 | "default_texts": [], 702 | "args": [] 703 | } 704 | }, 705 | "walnut_pad": { 706 | "key": "walnut_pad", 707 | "keywords": [ 708 | "胡桃平板" 709 | ], 710 | "patterns": [], 711 | "params": { 712 | "min_images": 1, 713 | "max_images": 1, 714 | "min_texts": 0, 715 | "max_texts": 0, 716 | "default_texts": [], 717 | "args": [] 718 | } 719 | }, 720 | "lim_x_0": { 721 | "key": "lim_x_0", 722 | "keywords": [ 723 | "等价无穷小" 724 | ], 725 | "patterns": [], 726 | "params": { 727 | "min_images": 1, 728 | "max_images": 1, 729 | "min_texts": 0, 730 | "max_texts": 0, 731 | "default_texts": [], 732 | "args": [] 733 | } 734 | }, 735 | "dont_touch": { 736 | "key": "dont_touch", 737 | "keywords": [ 738 | "别碰" 739 | ], 740 | "patterns": [], 741 | "params": { 742 | "min_images": 1, 743 | "max_images": 1, 744 | "min_texts": 0, 745 | "max_texts": 0, 746 | "default_texts": [], 747 | "args": [] 748 | } 749 | }, 750 | "acg_entrance": { 751 | "key": "acg_entrance", 752 | "keywords": [ 753 | "二次元入口" 754 | ], 755 | "patterns": [], 756 | "params": { 757 | "min_images": 1, 758 | "max_images": 1, 759 | "min_texts": 0, 760 | "max_texts": 1, 761 | "default_texts": [ 762 | "走,跟我去二次元吧" 763 | ], 764 | "args": [] 765 | } 766 | }, 767 | "run": { 768 | "key": "run", 769 | "keywords": [ 770 | "快跑" 771 | ], 772 | "patterns": [], 773 | "params": { 774 | "min_images": 0, 775 | "max_images": 0, 776 | "min_texts": 1, 777 | "max_texts": 1, 778 | "default_texts": [ 779 | "快跑" 780 | ], 781 | "args": [] 782 | } 783 | }, 784 | "addiction": { 785 | "key": "addiction", 786 | "keywords": [ 787 | "上瘾", 788 | "毒瘾发作" 789 | ], 790 | "patterns": [], 791 | "params": { 792 | "min_images": 1, 793 | "max_images": 1, 794 | "min_texts": 0, 795 | "max_texts": 1, 796 | "default_texts": [], 797 | "args": [] 798 | } 799 | }, 800 | "hug_leg": { 801 | "key": "hug_leg", 802 | "keywords": [ 803 | "抱大腿" 804 | ], 805 | "patterns": [], 806 | "params": { 807 | "min_images": 1, 808 | "max_images": 1, 809 | "min_texts": 0, 810 | "max_texts": 0, 811 | "default_texts": [], 812 | "args": [] 813 | } 814 | }, 815 | "bronya_holdsign": { 816 | "key": "bronya_holdsign", 817 | "keywords": [ 818 | "布洛妮娅举牌", 819 | "大鸭鸭举牌" 820 | ], 821 | "patterns": [], 822 | "params": { 823 | "min_images": 0, 824 | "max_images": 0, 825 | "min_texts": 1, 826 | "max_texts": 1, 827 | "default_texts": [ 828 | "V我50" 829 | ], 830 | "args": [] 831 | } 832 | }, 833 | "nokia": { 834 | "key": "nokia", 835 | "keywords": [ 836 | "诺基亚", 837 | "有内鬼" 838 | ], 839 | "patterns": [], 840 | "params": { 841 | "min_images": 0, 842 | "max_images": 0, 843 | "min_texts": 1, 844 | "max_texts": 1, 845 | "default_texts": [ 846 | "无内鬼,继续交易" 847 | ], 848 | "args": [] 849 | } 850 | }, 851 | "garbage": { 852 | "key": "garbage", 853 | "keywords": [ 854 | "垃圾", 855 | "垃圾桶" 856 | ], 857 | "patterns": [], 858 | "params": { 859 | "min_images": 1, 860 | "max_images": 1, 861 | "min_texts": 0, 862 | "max_texts": 0, 863 | "default_texts": [], 864 | "args": [] 865 | } 866 | }, 867 | "smash": { 868 | "key": "smash", 869 | "keywords": [ 870 | "砸" 871 | ], 872 | "patterns": [], 873 | "params": { 874 | "min_images": 1, 875 | "max_images": 1, 876 | "min_texts": 0, 877 | "max_texts": 0, 878 | "default_texts": [], 879 | "args": [] 880 | } 881 | }, 882 | "tankuku_raisesign": { 883 | "key": "tankuku_raisesign", 884 | "keywords": [ 885 | "唐可可举牌" 886 | ], 887 | "patterns": [], 888 | "params": { 889 | "min_images": 1, 890 | "max_images": 1, 891 | "min_texts": 0, 892 | "max_texts": 0, 893 | "default_texts": [], 894 | "args": [] 895 | } 896 | }, 897 | "capoo_say": { 898 | "key": "capoo_say", 899 | "keywords": [ 900 | "咖波说" 901 | ], 902 | "patterns": [], 903 | "params": { 904 | "min_images": 0, 905 | "max_images": 0, 906 | "min_texts": 1, 907 | "max_texts": 10, 908 | "default_texts": [ 909 | "寄" 910 | ], 911 | "args": [] 912 | } 913 | }, 914 | "good_news": { 915 | "key": "good_news", 916 | "keywords": [ 917 | "喜报" 918 | ], 919 | "patterns": [], 920 | "params": { 921 | "min_images": 0, 922 | "max_images": 0, 923 | "min_texts": 1, 924 | "max_texts": 1, 925 | "default_texts": [ 926 | "悲报" 927 | ], 928 | "args": [] 929 | } 930 | }, 931 | "scroll": { 932 | "key": "scroll", 933 | "keywords": [ 934 | "滚屏" 935 | ], 936 | "patterns": [], 937 | "params": { 938 | "min_images": 0, 939 | "max_images": 0, 940 | "min_texts": 1, 941 | "max_texts": 1, 942 | "default_texts": [ 943 | "你们说话啊" 944 | ], 945 | "args": [] 946 | } 947 | }, 948 | "suck": { 949 | "key": "suck", 950 | "keywords": [ 951 | "吸", 952 | "嗦" 953 | ], 954 | "patterns": [], 955 | "params": { 956 | "min_images": 1, 957 | "max_images": 1, 958 | "min_texts": 0, 959 | "max_texts": 0, 960 | "default_texts": [], 961 | "args": [] 962 | } 963 | }, 964 | "kirby_hammer": { 965 | "key": "kirby_hammer", 966 | "keywords": [ 967 | "卡比锤", 968 | "卡比重锤" 969 | ], 970 | "patterns": [], 971 | "params": { 972 | "min_images": 1, 973 | "max_images": 1, 974 | "min_texts": 0, 975 | "max_texts": 0, 976 | "default_texts": [], 977 | "args": [ 978 | { 979 | "name": "circle", 980 | "type": "boolean", 981 | "description": "是否将图片变为圆形", 982 | "default": false, 983 | "enum": null 984 | } 985 | ] 986 | } 987 | }, 988 | "nijika_holdsign": { 989 | "key": "nijika_holdsign", 990 | "keywords": [ 991 | "伊地知虹夏举牌", 992 | "虹夏举牌" 993 | ], 994 | "patterns": [], 995 | "params": { 996 | "min_images": 0, 997 | "max_images": 0, 998 | "min_texts": 1, 999 | "max_texts": 1, 1000 | "default_texts": [ 1001 | "你可少看点二次元吧" 1002 | ], 1003 | "args": [] 1004 | } 1005 | }, 1006 | "douyin": { 1007 | "key": "douyin", 1008 | "keywords": [ 1009 | "douyin" 1010 | ], 1011 | "patterns": [], 1012 | "params": { 1013 | "min_images": 0, 1014 | "max_images": 0, 1015 | "min_texts": 1, 1016 | "max_texts": 1, 1017 | "default_texts": [ 1018 | "douyin" 1019 | ], 1020 | "args": [] 1021 | } 1022 | }, 1023 | "marriage": { 1024 | "key": "marriage", 1025 | "keywords": [ 1026 | "结婚申请", 1027 | "结婚登记" 1028 | ], 1029 | "patterns": [], 1030 | "params": { 1031 | "min_images": 1, 1032 | "max_images": 1, 1033 | "min_texts": 0, 1034 | "max_texts": 0, 1035 | "default_texts": [], 1036 | "args": [] 1037 | } 1038 | }, 1039 | "pat": { 1040 | "key": "pat", 1041 | "keywords": [ 1042 | "拍" 1043 | ], 1044 | "patterns": [], 1045 | "params": { 1046 | "min_images": 1, 1047 | "max_images": 1, 1048 | "min_texts": 0, 1049 | "max_texts": 0, 1050 | "default_texts": [], 1051 | "args": [] 1052 | } 1053 | }, 1054 | "what_he_wants": { 1055 | "key": "what_he_wants", 1056 | "keywords": [ 1057 | "最想要的东西" 1058 | ], 1059 | "patterns": [], 1060 | "params": { 1061 | "min_images": 1, 1062 | "max_images": 1, 1063 | "min_texts": 0, 1064 | "max_texts": 1, 1065 | "default_texts": [ 1066 | "今年520" 1067 | ], 1068 | "args": [] 1069 | } 1070 | }, 1071 | "wave": { 1072 | "key": "wave", 1073 | "keywords": [ 1074 | "波纹" 1075 | ], 1076 | "patterns": [], 1077 | "params": { 1078 | "min_images": 1, 1079 | "max_images": 1, 1080 | "min_texts": 0, 1081 | "max_texts": 0, 1082 | "default_texts": [], 1083 | "args": [] 1084 | } 1085 | }, 1086 | "pass_the_buck": { 1087 | "key": "pass_the_buck", 1088 | "keywords": [ 1089 | "推锅", 1090 | "甩锅" 1091 | ], 1092 | "patterns": [], 1093 | "params": { 1094 | "min_images": 1, 1095 | "max_images": 1, 1096 | "min_texts": 0, 1097 | "max_texts": 1, 1098 | "default_texts": [ 1099 | "你写!" 1100 | ], 1101 | "args": [] 1102 | } 1103 | }, 1104 | "hold_tight": { 1105 | "key": "hold_tight", 1106 | "keywords": [ 1107 | "抱紧" 1108 | ], 1109 | "patterns": [], 1110 | "params": { 1111 | "min_images": 1, 1112 | "max_images": 1, 1113 | "min_texts": 0, 1114 | "max_texts": 0, 1115 | "default_texts": [], 1116 | "args": [] 1117 | } 1118 | }, 1119 | "pornhub": { 1120 | "key": "pornhub", 1121 | "keywords": [ 1122 | "ph", 1123 | "pornhub" 1124 | ], 1125 | "patterns": [], 1126 | "params": { 1127 | "min_images": 0, 1128 | "max_images": 0, 1129 | "min_texts": 2, 1130 | "max_texts": 2, 1131 | "default_texts": [ 1132 | "You", 1133 | "Tube" 1134 | ], 1135 | "args": [] 1136 | } 1137 | }, 1138 | "capoo_rub": { 1139 | "key": "capoo_rub", 1140 | "keywords": [ 1141 | "咖波蹭", 1142 | "咖波贴" 1143 | ], 1144 | "patterns": [], 1145 | "params": { 1146 | "min_images": 1, 1147 | "max_images": 1, 1148 | "min_texts": 0, 1149 | "max_texts": 0, 1150 | "default_texts": [], 1151 | "args": [] 1152 | } 1153 | }, 1154 | "caoshen_bite": { 1155 | "key": "caoshen_bite", 1156 | "keywords": [ 1157 | "草神啃" 1158 | ], 1159 | "patterns": [], 1160 | "params": { 1161 | "min_images": 1, 1162 | "max_images": 1, 1163 | "min_texts": 0, 1164 | "max_texts": 0, 1165 | "default_texts": [], 1166 | "args": [] 1167 | } 1168 | }, 1169 | "blood_pressure": { 1170 | "key": "blood_pressure", 1171 | "keywords": [ 1172 | "高血压" 1173 | ], 1174 | "patterns": [], 1175 | "params": { 1176 | "min_images": 1, 1177 | "max_images": 1, 1178 | "min_texts": 0, 1179 | "max_texts": 0, 1180 | "default_texts": [], 1181 | "args": [] 1182 | } 1183 | }, 1184 | "high_EQ": { 1185 | "key": "high_EQ", 1186 | "keywords": [ 1187 | "低情商xx高情商xx" 1188 | ], 1189 | "patterns": [ 1190 | "低情商[\\s::]*(.+?)\\s+高情商[\\s::]*(.+)" 1191 | ], 1192 | "params": { 1193 | "min_images": 0, 1194 | "max_images": 0, 1195 | "min_texts": 2, 1196 | "max_texts": 2, 1197 | "default_texts": [ 1198 | "高情商", 1199 | "低情商" 1200 | ], 1201 | "args": [] 1202 | } 1203 | }, 1204 | "loading": { 1205 | "key": "loading", 1206 | "keywords": [ 1207 | "加载中" 1208 | ], 1209 | "patterns": [], 1210 | "params": { 1211 | "min_images": 1, 1212 | "max_images": 1, 1213 | "min_texts": 0, 1214 | "max_texts": 0, 1215 | "default_texts": [], 1216 | "args": [] 1217 | } 1218 | }, 1219 | "kaleidoscope": { 1220 | "key": "kaleidoscope", 1221 | "keywords": [ 1222 | "万花筒", 1223 | "万花镜" 1224 | ], 1225 | "patterns": [], 1226 | "params": { 1227 | "min_images": 1, 1228 | "max_images": 1, 1229 | "min_texts": 0, 1230 | "max_texts": 0, 1231 | "default_texts": [], 1232 | "args": [ 1233 | { 1234 | "name": "circle", 1235 | "type": "boolean", 1236 | "description": "是否将图片变为圆形", 1237 | "default": false, 1238 | "enum": null 1239 | } 1240 | ] 1241 | } 1242 | }, 1243 | "scratchcard": { 1244 | "key": "scratchcard", 1245 | "keywords": [ 1246 | "刮刮乐" 1247 | ], 1248 | "patterns": [], 1249 | "params": { 1250 | "min_images": 0, 1251 | "max_images": 0, 1252 | "min_texts": 1, 1253 | "max_texts": 1, 1254 | "default_texts": [ 1255 | "谢谢参与" 1256 | ], 1257 | "args": [] 1258 | } 1259 | }, 1260 | "wooden_fish": { 1261 | "key": "wooden_fish", 1262 | "keywords": [ 1263 | "木鱼" 1264 | ], 1265 | "patterns": [], 1266 | "params": { 1267 | "min_images": 1, 1268 | "max_images": 1, 1269 | "min_texts": 0, 1270 | "max_texts": 0, 1271 | "default_texts": [], 1272 | "args": [] 1273 | } 1274 | }, 1275 | "walnut_zoom": { 1276 | "key": "walnut_zoom", 1277 | "keywords": [ 1278 | "胡桃放大" 1279 | ], 1280 | "patterns": [], 1281 | "params": { 1282 | "min_images": 1, 1283 | "max_images": 1, 1284 | "min_texts": 0, 1285 | "max_texts": 0, 1286 | "default_texts": [], 1287 | "args": [] 1288 | } 1289 | }, 1290 | "flash_blind": { 1291 | "key": "flash_blind", 1292 | "keywords": [ 1293 | "闪瞎" 1294 | ], 1295 | "patterns": [], 1296 | "params": { 1297 | "min_images": 1, 1298 | "max_images": 1, 1299 | "min_texts": 0, 1300 | "max_texts": 1, 1301 | "default_texts": [ 1302 | "闪瞎你们的狗眼" 1303 | ], 1304 | "args": [] 1305 | } 1306 | }, 1307 | "kiss": { 1308 | "key": "kiss", 1309 | "keywords": [ 1310 | "亲", 1311 | "亲亲" 1312 | ], 1313 | "patterns": [], 1314 | "params": { 1315 | "min_images": 2, 1316 | "max_images": 2, 1317 | "min_texts": 0, 1318 | "max_texts": 0, 1319 | "default_texts": [], 1320 | "args": [] 1321 | } 1322 | }, 1323 | "luxun_say": { 1324 | "key": "luxun_say", 1325 | "keywords": [ 1326 | "鲁迅说", 1327 | "鲁迅说过" 1328 | ], 1329 | "patterns": [], 1330 | "params": { 1331 | "min_images": 0, 1332 | "max_images": 0, 1333 | "min_texts": 1, 1334 | "max_texts": 1, 1335 | "default_texts": [ 1336 | "我没有说过这句话" 1337 | ], 1338 | "args": [] 1339 | } 1340 | }, 1341 | "anti_kidnap": { 1342 | "key": "anti_kidnap", 1343 | "keywords": [ 1344 | "防诱拐" 1345 | ], 1346 | "patterns": [], 1347 | "params": { 1348 | "min_images": 1, 1349 | "max_images": 1, 1350 | "min_texts": 0, 1351 | "max_texts": 0, 1352 | "default_texts": [], 1353 | "args": [] 1354 | } 1355 | }, 1356 | "slap": { 1357 | "key": "slap", 1358 | "keywords": [ 1359 | "一巴掌" 1360 | ], 1361 | "patterns": [], 1362 | "params": { 1363 | "min_images": 0, 1364 | "max_images": 0, 1365 | "min_texts": 1, 1366 | "max_texts": 1, 1367 | "default_texts": [], 1368 | "args": [] 1369 | } 1370 | }, 1371 | "no_response": { 1372 | "key": "no_response", 1373 | "keywords": [ 1374 | "无响应" 1375 | ], 1376 | "patterns": [], 1377 | "params": { 1378 | "min_images": 1, 1379 | "max_images": 1, 1380 | "min_texts": 0, 1381 | "max_texts": 0, 1382 | "default_texts": [], 1383 | "args": [] 1384 | } 1385 | }, 1386 | "my_friend": { 1387 | "key": "my_friend", 1388 | "keywords": [ 1389 | "我朋友说" 1390 | ], 1391 | "patterns": [], 1392 | "params": { 1393 | "min_images": 1, 1394 | "max_images": 1, 1395 | "min_texts": 1, 1396 | "max_texts": 10, 1397 | "default_texts": [ 1398 | "让我康康" 1399 | ], 1400 | "args": [ 1401 | { 1402 | "name": "name", 1403 | "type": "string", 1404 | "description": "指定名字", 1405 | "default": "", 1406 | "enum": null 1407 | } 1408 | ] 1409 | } 1410 | }, 1411 | "repeat": { 1412 | "key": "repeat", 1413 | "keywords": [ 1414 | "复读" 1415 | ], 1416 | "patterns": [], 1417 | "params": { 1418 | "min_images": 1, 1419 | "max_images": 5, 1420 | "min_texts": 1, 1421 | "max_texts": 1, 1422 | "default_texts": [ 1423 | "救命啊" 1424 | ], 1425 | "args": [] 1426 | } 1427 | }, 1428 | "fencing": { 1429 | "key": "fencing", 1430 | "keywords": [ 1431 | "击剑", 1432 | "🤺" 1433 | ], 1434 | "patterns": [], 1435 | "params": { 1436 | "min_images": 2, 1437 | "max_images": 2, 1438 | "min_texts": 0, 1439 | "max_texts": 0, 1440 | "default_texts": [], 1441 | "args": [] 1442 | } 1443 | }, 1444 | "jiujiu": { 1445 | "key": "jiujiu", 1446 | "keywords": [ 1447 | "啾啾" 1448 | ], 1449 | "patterns": [], 1450 | "params": { 1451 | "min_images": 1, 1452 | "max_images": 1, 1453 | "min_texts": 0, 1454 | "max_texts": 0, 1455 | "default_texts": [], 1456 | "args": [] 1457 | } 1458 | }, 1459 | "jiji_king": { 1460 | "key": "jiji_king", 1461 | "keywords": [ 1462 | "急急国王" 1463 | ], 1464 | "patterns": [], 1465 | "params": { 1466 | "min_images": 1, 1467 | "max_images": 11, 1468 | "min_texts": 0, 1469 | "max_texts": 11, 1470 | "default_texts": [], 1471 | "args": [ 1472 | { 1473 | "name": "circle", 1474 | "type": "boolean", 1475 | "description": "是否将图片变为圆形", 1476 | "default": false, 1477 | "enum": null 1478 | } 1479 | ] 1480 | } 1481 | }, 1482 | "love_you": { 1483 | "key": "love_you", 1484 | "keywords": [ 1485 | "永远爱你" 1486 | ], 1487 | "patterns": [], 1488 | "params": { 1489 | "min_images": 1, 1490 | "max_images": 1, 1491 | "min_texts": 0, 1492 | "max_texts": 0, 1493 | "default_texts": [], 1494 | "args": [] 1495 | } 1496 | }, 1497 | "interview": { 1498 | "key": "interview", 1499 | "keywords": [ 1500 | "采访" 1501 | ], 1502 | "patterns": [], 1503 | "params": { 1504 | "min_images": 1, 1505 | "max_images": 2, 1506 | "min_texts": 0, 1507 | "max_texts": 1, 1508 | "default_texts": [ 1509 | "采访大佬经验" 1510 | ], 1511 | "args": [] 1512 | } 1513 | }, 1514 | "windmill_turn": { 1515 | "key": "windmill_turn", 1516 | "keywords": [ 1517 | "风车转" 1518 | ], 1519 | "patterns": [], 1520 | "params": { 1521 | "min_images": 1, 1522 | "max_images": 1, 1523 | "min_texts": 0, 1524 | "max_texts": 0, 1525 | "default_texts": [], 1526 | "args": [] 1527 | } 1528 | }, 1529 | "hold_grudge": { 1530 | "key": "hold_grudge", 1531 | "keywords": [ 1532 | "记仇" 1533 | ], 1534 | "patterns": [], 1535 | "params": { 1536 | "min_images": 0, 1537 | "max_images": 0, 1538 | "min_texts": 1, 1539 | "max_texts": 1, 1540 | "default_texts": [ 1541 | "群友不发涩图" 1542 | ], 1543 | "args": [] 1544 | } 1545 | }, 1546 | "trance": { 1547 | "key": "trance", 1548 | "keywords": [ 1549 | "恍惚" 1550 | ], 1551 | "patterns": [], 1552 | "params": { 1553 | "min_images": 1, 1554 | "max_images": 1, 1555 | "min_texts": 0, 1556 | "max_texts": 0, 1557 | "default_texts": [], 1558 | "args": [] 1559 | } 1560 | }, 1561 | "why_have_hands": { 1562 | "key": "why_have_hands", 1563 | "keywords": [ 1564 | "为什么要有手" 1565 | ], 1566 | "patterns": [], 1567 | "params": { 1568 | "min_images": 1, 1569 | "max_images": 1, 1570 | "min_texts": 0, 1571 | "max_texts": 1, 1572 | "default_texts": [], 1573 | "args": [] 1574 | } 1575 | }, 1576 | "dont_go_near": { 1577 | "key": "dont_go_near", 1578 | "keywords": [ 1579 | "不要靠近" 1580 | ], 1581 | "patterns": [], 1582 | "params": { 1583 | "min_images": 1, 1584 | "max_images": 1, 1585 | "min_texts": 0, 1586 | "max_texts": 0, 1587 | "default_texts": [], 1588 | "args": [] 1589 | } 1590 | }, 1591 | "little_angel": { 1592 | "key": "little_angel", 1593 | "keywords": [ 1594 | "小天使" 1595 | ], 1596 | "patterns": [], 1597 | "params": { 1598 | "min_images": 1, 1599 | "max_images": 1, 1600 | "min_texts": 0, 1601 | "max_texts": 1, 1602 | "default_texts": [], 1603 | "args": [] 1604 | } 1605 | }, 1606 | "safe_sense": { 1607 | "key": "safe_sense", 1608 | "keywords": [ 1609 | "安全感" 1610 | ], 1611 | "patterns": [], 1612 | "params": { 1613 | "min_images": 1, 1614 | "max_images": 1, 1615 | "min_texts": 0, 1616 | "max_texts": 1, 1617 | "default_texts": [ 1618 | "你给我的安全感\n远不及它的万分之一" 1619 | ], 1620 | "args": [] 1621 | } 1622 | }, 1623 | "call_110": { 1624 | "key": "call_110", 1625 | "keywords": [ 1626 | "遇到困难请拨打" 1627 | ], 1628 | "patterns": [], 1629 | "params": { 1630 | "min_images": 2, 1631 | "max_images": 2, 1632 | "min_texts": 0, 1633 | "max_texts": 0, 1634 | "default_texts": [], 1635 | "args": [] 1636 | } 1637 | }, 1638 | "thump_wildly": { 1639 | "key": "thump_wildly", 1640 | "keywords": [ 1641 | "捶爆", 1642 | "爆捶" 1643 | ], 1644 | "patterns": [], 1645 | "params": { 1646 | "min_images": 1, 1647 | "max_images": 1, 1648 | "min_texts": 0, 1649 | "max_texts": 0, 1650 | "default_texts": [], 1651 | "args": [] 1652 | } 1653 | }, 1654 | "applaud": { 1655 | "key": "applaud", 1656 | "keywords": [ 1657 | "鼓掌" 1658 | ], 1659 | "patterns": [], 1660 | "params": { 1661 | "min_images": 1, 1662 | "max_images": 1, 1663 | "min_texts": 0, 1664 | "max_texts": 0, 1665 | "default_texts": [], 1666 | "args": [] 1667 | } 1668 | }, 1669 | "find_chips": { 1670 | "key": "find_chips", 1671 | "keywords": [ 1672 | "整点薯条" 1673 | ], 1674 | "patterns": [], 1675 | "params": { 1676 | "min_images": 0, 1677 | "max_images": 0, 1678 | "min_texts": 4, 1679 | "max_texts": 4, 1680 | "default_texts": [ 1681 | "我们要飞向何方", 1682 | "我打算待会去码头整点薯条", 1683 | "我说的是归根结底,活着是为了什么", 1684 | "为了待会去码头整点薯条" 1685 | ], 1686 | "args": [] 1687 | } 1688 | }, 1689 | "always": { 1690 | "key": "always", 1691 | "keywords": [ 1692 | "一直" 1693 | ], 1694 | "patterns": [], 1695 | "params": { 1696 | "min_images": 1, 1697 | "max_images": 1, 1698 | "min_texts": 0, 1699 | "max_texts": 0, 1700 | "default_texts": [], 1701 | "args": [ 1702 | { 1703 | "name": "mode", 1704 | "type": "string", 1705 | "description": "生成模式", 1706 | "default": "normal", 1707 | "enum": [ 1708 | "normal", 1709 | "loop", 1710 | "circle" 1711 | ] 1712 | } 1713 | ] 1714 | } 1715 | }, 1716 | "cyan": { 1717 | "key": "cyan", 1718 | "keywords": [ 1719 | "群青" 1720 | ], 1721 | "patterns": [], 1722 | "params": { 1723 | "min_images": 1, 1724 | "max_images": 1, 1725 | "min_texts": 0, 1726 | "max_texts": 0, 1727 | "default_texts": [], 1728 | "args": [] 1729 | } 1730 | }, 1731 | "punch": { 1732 | "key": "punch", 1733 | "keywords": [ 1734 | "打拳" 1735 | ], 1736 | "patterns": [], 1737 | "params": { 1738 | "min_images": 1, 1739 | "max_images": 1, 1740 | "min_texts": 0, 1741 | "max_texts": 0, 1742 | "default_texts": [], 1743 | "args": [] 1744 | } 1745 | }, 1746 | "name_generator": { 1747 | "key": "name_generator", 1748 | "keywords": [ 1749 | "亚文化取名机", 1750 | "亚名" 1751 | ], 1752 | "patterns": [], 1753 | "params": { 1754 | "min_images": 1, 1755 | "max_images": 1, 1756 | "min_texts": 0, 1757 | "max_texts": 0, 1758 | "default_texts": [], 1759 | "args": [] 1760 | } 1761 | }, 1762 | "incivilization": { 1763 | "key": "incivilization", 1764 | "keywords": [ 1765 | "不文明" 1766 | ], 1767 | "patterns": [], 1768 | "params": { 1769 | "min_images": 1, 1770 | "max_images": 1, 1771 | "min_texts": 0, 1772 | "max_texts": 1, 1773 | "default_texts": [ 1774 | "你刚才说的话不是很礼貌!" 1775 | ], 1776 | "args": [] 1777 | } 1778 | }, 1779 | "painter": { 1780 | "key": "painter", 1781 | "keywords": [ 1782 | "小画家" 1783 | ], 1784 | "patterns": [], 1785 | "params": { 1786 | "min_images": 1, 1787 | "max_images": 1, 1788 | "min_texts": 0, 1789 | "max_texts": 0, 1790 | "default_texts": [], 1791 | "args": [] 1792 | } 1793 | }, 1794 | "turn": { 1795 | "key": "turn", 1796 | "keywords": [ 1797 | "转" 1798 | ], 1799 | "patterns": [], 1800 | "params": { 1801 | "min_images": 1, 1802 | "max_images": 1, 1803 | "min_texts": 0, 1804 | "max_texts": 0, 1805 | "default_texts": [], 1806 | "args": [] 1807 | } 1808 | }, 1809 | "prpr": { 1810 | "key": "prpr", 1811 | "keywords": [ 1812 | "舔", 1813 | "舔屏", 1814 | "prpr" 1815 | ], 1816 | "patterns": [], 1817 | "params": { 1818 | "min_images": 1, 1819 | "max_images": 1, 1820 | "min_texts": 0, 1821 | "max_texts": 0, 1822 | "default_texts": [], 1823 | "args": [] 1824 | } 1825 | }, 1826 | "twist": { 1827 | "key": "twist", 1828 | "keywords": [ 1829 | "搓" 1830 | ], 1831 | "patterns": [], 1832 | "params": { 1833 | "min_images": 1, 1834 | "max_images": 1, 1835 | "min_texts": 0, 1836 | "max_texts": 0, 1837 | "default_texts": [], 1838 | "args": [] 1839 | } 1840 | }, 1841 | "murmur": { 1842 | "key": "murmur", 1843 | "keywords": [ 1844 | "低语" 1845 | ], 1846 | "patterns": [], 1847 | "params": { 1848 | "min_images": 0, 1849 | "max_images": 0, 1850 | "min_texts": 1, 1851 | "max_texts": 1, 1852 | "default_texts": [ 1853 | "你的假期余额不足" 1854 | ], 1855 | "args": [] 1856 | } 1857 | }, 1858 | "rub": { 1859 | "key": "rub", 1860 | "keywords": [ 1861 | "贴", 1862 | "贴贴", 1863 | "蹭", 1864 | "蹭蹭" 1865 | ], 1866 | "patterns": [], 1867 | "params": { 1868 | "min_images": 2, 1869 | "max_images": 2, 1870 | "min_texts": 0, 1871 | "max_texts": 0, 1872 | "default_texts": [], 1873 | "args": [] 1874 | } 1875 | }, 1876 | "bubble_tea": { 1877 | "key": "bubble_tea", 1878 | "keywords": [ 1879 | "奶茶" 1880 | ], 1881 | "patterns": [], 1882 | "params": { 1883 | "min_images": 1, 1884 | "max_images": 1, 1885 | "min_texts": 0, 1886 | "max_texts": 0, 1887 | "default_texts": [], 1888 | "args": [ 1889 | { 1890 | "name": "position", 1891 | "type": "string", 1892 | "description": "奶茶的位置", 1893 | "default": "right", 1894 | "enum": [ 1895 | "right", 1896 | "left", 1897 | "both" 1898 | ] 1899 | } 1900 | ] 1901 | } 1902 | }, 1903 | "step_on": { 1904 | "key": "step_on", 1905 | "keywords": [ 1906 | "踩" 1907 | ], 1908 | "patterns": [], 1909 | "params": { 1910 | "min_images": 1, 1911 | "max_images": 1, 1912 | "min_texts": 0, 1913 | "max_texts": 0, 1914 | "default_texts": [], 1915 | "args": [] 1916 | } 1917 | }, 1918 | "sit_still": { 1919 | "key": "sit_still", 1920 | "keywords": [ 1921 | "坐得住", 1922 | "坐的住" 1923 | ], 1924 | "patterns": [], 1925 | "params": { 1926 | "min_images": 1, 1927 | "max_images": 1, 1928 | "min_texts": 0, 1929 | "max_texts": 1, 1930 | "default_texts": [], 1931 | "args": [] 1932 | } 1933 | }, 1934 | "throw": { 1935 | "key": "throw", 1936 | "keywords": [ 1937 | "丢", 1938 | "扔" 1939 | ], 1940 | "patterns": [], 1941 | "params": { 1942 | "min_images": 1, 1943 | "max_images": 1, 1944 | "min_texts": 0, 1945 | "max_texts": 0, 1946 | "default_texts": [], 1947 | "args": [] 1948 | } 1949 | }, 1950 | "thump": { 1951 | "key": "thump", 1952 | "keywords": [ 1953 | "捶" 1954 | ], 1955 | "patterns": [], 1956 | "params": { 1957 | "min_images": 1, 1958 | "max_images": 1, 1959 | "min_texts": 0, 1960 | "max_texts": 0, 1961 | "default_texts": [], 1962 | "args": [] 1963 | } 1964 | }, 1965 | "ask": { 1966 | "key": "ask", 1967 | "keywords": [ 1968 | "问问" 1969 | ], 1970 | "patterns": [], 1971 | "params": { 1972 | "min_images": 1, 1973 | "max_images": 1, 1974 | "min_texts": 0, 1975 | "max_texts": 1, 1976 | "default_texts": [], 1977 | "args": [] 1978 | } 1979 | }, 1980 | "slogan": { 1981 | "key": "slogan", 1982 | "keywords": [ 1983 | "口号" 1984 | ], 1985 | "patterns": [], 1986 | "params": { 1987 | "min_images": 0, 1988 | "max_images": 0, 1989 | "min_texts": 6, 1990 | "max_texts": 6, 1991 | "default_texts": [ 1992 | "我们是谁?", 1993 | "浙大人!", 1994 | "到浙大来做什么?", 1995 | "混!", 1996 | "将来毕业后要做什么样的人?", 1997 | "混混!" 1998 | ], 1999 | "args": [] 2000 | } 2001 | }, 2002 | "potato": { 2003 | "key": "potato", 2004 | "keywords": [ 2005 | "土豆" 2006 | ], 2007 | "patterns": [], 2008 | "params": { 2009 | "min_images": 1, 2010 | "max_images": 1, 2011 | "min_texts": 0, 2012 | "max_texts": 0, 2013 | "default_texts": [], 2014 | "args": [] 2015 | } 2016 | }, 2017 | "note_for_leave": { 2018 | "key": "note_for_leave", 2019 | "keywords": [ 2020 | "请假条" 2021 | ], 2022 | "patterns": [], 2023 | "params": { 2024 | "min_images": 1, 2025 | "max_images": 1, 2026 | "min_texts": 0, 2027 | "max_texts": 1, 2028 | "default_texts": [ 2029 | "想玩" 2030 | ], 2031 | "args": [ 2032 | { 2033 | "name": "time", 2034 | "type": "string", 2035 | "description": "指定时间", 2036 | "default": "", 2037 | "enum": null 2038 | }, 2039 | { 2040 | "name": "name", 2041 | "type": "string", 2042 | "description": "指定名字", 2043 | "default": "", 2044 | "enum": null 2045 | } 2046 | ] 2047 | } 2048 | }, 2049 | "cover_face": { 2050 | "key": "cover_face", 2051 | "keywords": [ 2052 | "捂脸" 2053 | ], 2054 | "patterns": [], 2055 | "params": { 2056 | "min_images": 1, 2057 | "max_images": 1, 2058 | "min_texts": 0, 2059 | "max_texts": 0, 2060 | "default_texts": [], 2061 | "args": [] 2062 | } 2063 | }, 2064 | "scratch_head": { 2065 | "key": "scratch_head", 2066 | "keywords": [ 2067 | "挠头" 2068 | ], 2069 | "patterns": [], 2070 | "params": { 2071 | "min_images": 1, 2072 | "max_images": 1, 2073 | "min_texts": 0, 2074 | "max_texts": 0, 2075 | "default_texts": [], 2076 | "args": [] 2077 | } 2078 | }, 2079 | "capoo_draw": { 2080 | "key": "capoo_draw", 2081 | "keywords": [ 2082 | "咖波画" 2083 | ], 2084 | "patterns": [], 2085 | "params": { 2086 | "min_images": 1, 2087 | "max_images": 1, 2088 | "min_texts": 0, 2089 | "max_texts": 0, 2090 | "default_texts": [], 2091 | "args": [] 2092 | } 2093 | }, 2094 | "play": { 2095 | "key": "play", 2096 | "keywords": [ 2097 | "顶", 2098 | "玩" 2099 | ], 2100 | "patterns": [], 2101 | "params": { 2102 | "min_images": 1, 2103 | "max_images": 1, 2104 | "min_texts": 0, 2105 | "max_texts": 0, 2106 | "default_texts": [], 2107 | "args": [] 2108 | } 2109 | }, 2110 | "hutao_bite": { 2111 | "key": "hutao_bite", 2112 | "keywords": [ 2113 | "胡桃啃" 2114 | ], 2115 | "patterns": [], 2116 | "params": { 2117 | "min_images": 1, 2118 | "max_images": 1, 2119 | "min_texts": 0, 2120 | "max_texts": 0, 2121 | "default_texts": [], 2122 | "args": [] 2123 | } 2124 | }, 2125 | "look_this_icon": { 2126 | "key": "look_this_icon", 2127 | "keywords": [ 2128 | "看图标" 2129 | ], 2130 | "patterns": [], 2131 | "params": { 2132 | "min_images": 1, 2133 | "max_images": 1, 2134 | "min_texts": 0, 2135 | "max_texts": 1, 2136 | "default_texts": [ 2137 | "朋友\n先看看这个图标再说话" 2138 | ], 2139 | "args": [] 2140 | } 2141 | }, 2142 | "bite": { 2143 | "key": "bite", 2144 | "keywords": [ 2145 | "啃" 2146 | ], 2147 | "patterns": [], 2148 | "params": { 2149 | "min_images": 1, 2150 | "max_images": 1, 2151 | "min_texts": 0, 2152 | "max_texts": 0, 2153 | "default_texts": [], 2154 | "args": [] 2155 | } 2156 | }, 2157 | "youtube": { 2158 | "key": "youtube", 2159 | "keywords": [ 2160 | "yt", 2161 | "youtube" 2162 | ], 2163 | "patterns": [], 2164 | "params": { 2165 | "min_images": 0, 2166 | "max_images": 0, 2167 | "min_texts": 2, 2168 | "max_texts": 2, 2169 | "default_texts": [ 2170 | "Porn", 2171 | "Hub" 2172 | ], 2173 | "args": [] 2174 | } 2175 | }, 2176 | "gun": { 2177 | "key": "gun", 2178 | "keywords": [ 2179 | "手枪" 2180 | ], 2181 | "patterns": [], 2182 | "params": { 2183 | "min_images": 1, 2184 | "max_images": 1, 2185 | "min_texts": 0, 2186 | "max_texts": 0, 2187 | "default_texts": [], 2188 | "args": [ 2189 | { 2190 | "name": "position", 2191 | "type": "string", 2192 | "description": "枪的位置", 2193 | "default": "left", 2194 | "enum": [ 2195 | "left", 2196 | "right", 2197 | "both" 2198 | ] 2199 | } 2200 | ] 2201 | } 2202 | }, 2203 | "oshi_no_ko": { 2204 | "key": "oshi_no_ko", 2205 | "keywords": [ 2206 | "我推的网友" 2207 | ], 2208 | "patterns": [ 2209 | "我推的(\\S+)" 2210 | ], 2211 | "params": { 2212 | "min_images": 1, 2213 | "max_images": 1, 2214 | "min_texts": 0, 2215 | "max_texts": 1, 2216 | "default_texts": [ 2217 | "网友" 2218 | ], 2219 | "args": [] 2220 | } 2221 | }, 2222 | "fill_head": { 2223 | "key": "fill_head", 2224 | "keywords": [ 2225 | "满脑子" 2226 | ], 2227 | "patterns": [ 2228 | "满脑子都是(\\S+)" 2229 | ], 2230 | "params": { 2231 | "min_images": 1, 2232 | "max_images": 1, 2233 | "min_texts": 0, 2234 | "max_texts": 1, 2235 | "default_texts": [], 2236 | "args": [] 2237 | } 2238 | }, 2239 | "add_chaos": { 2240 | "key": "add_chaos", 2241 | "keywords": [ 2242 | "添乱", 2243 | "给社会添乱" 2244 | ], 2245 | "patterns": [], 2246 | "params": { 2247 | "min_images": 1, 2248 | "max_images": 1, 2249 | "min_texts": 0, 2250 | "max_texts": 0, 2251 | "default_texts": [], 2252 | "args": [] 2253 | } 2254 | }, 2255 | "police": { 2256 | "key": "police", 2257 | "keywords": [ 2258 | "出警" 2259 | ], 2260 | "patterns": [], 2261 | "params": { 2262 | "min_images": 1, 2263 | "max_images": 1, 2264 | "min_texts": 0, 2265 | "max_texts": 0, 2266 | "default_texts": [], 2267 | "args": [] 2268 | } 2269 | }, 2270 | "police1": { 2271 | "key": "police1", 2272 | "keywords": [ 2273 | "警察" 2274 | ], 2275 | "patterns": [], 2276 | "params": { 2277 | "min_images": 1, 2278 | "max_images": 1, 2279 | "min_texts": 0, 2280 | "max_texts": 0, 2281 | "default_texts": [], 2282 | "args": [] 2283 | } 2284 | }, 2285 | "decent_kiss": { 2286 | "key": "decent_kiss", 2287 | "keywords": [ 2288 | "像样的亲亲" 2289 | ], 2290 | "patterns": [], 2291 | "params": { 2292 | "min_images": 1, 2293 | "max_images": 1, 2294 | "min_texts": 0, 2295 | "max_texts": 0, 2296 | "default_texts": [], 2297 | "args": [] 2298 | } 2299 | }, 2300 | "shutup": { 2301 | "key": "shutup", 2302 | "keywords": [ 2303 | "别说了" 2304 | ], 2305 | "patterns": [], 2306 | "params": { 2307 | "min_images": 0, 2308 | "max_images": 0, 2309 | "min_texts": 1, 2310 | "max_texts": 1, 2311 | "default_texts": [ 2312 | "你不要再说了" 2313 | ], 2314 | "args": [] 2315 | } 2316 | }, 2317 | "google": { 2318 | "key": "google", 2319 | "keywords": [ 2320 | "google" 2321 | ], 2322 | "patterns": [], 2323 | "params": { 2324 | "min_images": 0, 2325 | "max_images": 0, 2326 | "min_texts": 1, 2327 | "max_texts": 1, 2328 | "default_texts": [ 2329 | "Google" 2330 | ], 2331 | "args": [] 2332 | } 2333 | }, 2334 | "throw_gif": { 2335 | "key": "throw_gif", 2336 | "keywords": [ 2337 | "抛", 2338 | "掷" 2339 | ], 2340 | "patterns": [], 2341 | "params": { 2342 | "min_images": 1, 2343 | "max_images": 1, 2344 | "min_texts": 0, 2345 | "max_texts": 0, 2346 | "default_texts": [], 2347 | "args": [] 2348 | } 2349 | }, 2350 | "raise_sign": { 2351 | "key": "raise_sign", 2352 | "keywords": [ 2353 | "举牌" 2354 | ], 2355 | "patterns": [], 2356 | "params": { 2357 | "min_images": 0, 2358 | "max_images": 0, 2359 | "min_texts": 1, 2360 | "max_texts": 1, 2361 | "default_texts": [ 2362 | "大佬带带我" 2363 | ], 2364 | "args": [] 2365 | } 2366 | }, 2367 | "worship": { 2368 | "key": "worship", 2369 | "keywords": [ 2370 | "膜", 2371 | "膜拜" 2372 | ], 2373 | "patterns": [], 2374 | "params": { 2375 | "min_images": 1, 2376 | "max_images": 1, 2377 | "min_texts": 0, 2378 | "max_texts": 0, 2379 | "default_texts": [], 2380 | "args": [] 2381 | } 2382 | }, 2383 | "captain": { 2384 | "key": "captain", 2385 | "keywords": [ 2386 | "舰长" 2387 | ], 2388 | "patterns": [], 2389 | "params": { 2390 | "min_images": 2, 2391 | "max_images": 5, 2392 | "min_texts": 0, 2393 | "max_texts": 0, 2394 | "default_texts": [], 2395 | "args": [] 2396 | } 2397 | }, 2398 | "bad_news": { 2399 | "key": "bad_news", 2400 | "keywords": [ 2401 | "悲报" 2402 | ], 2403 | "patterns": [], 2404 | "params": { 2405 | "min_images": 0, 2406 | "max_images": 0, 2407 | "min_texts": 1, 2408 | "max_texts": 1, 2409 | "default_texts": [ 2410 | "喜报" 2411 | ], 2412 | "args": [] 2413 | } 2414 | }, 2415 | "divorce": { 2416 | "key": "divorce", 2417 | "keywords": [ 2418 | "离婚协议", 2419 | "离婚申请" 2420 | ], 2421 | "patterns": [], 2422 | "params": { 2423 | "min_images": 1, 2424 | "max_images": 1, 2425 | "min_texts": 0, 2426 | "max_texts": 0, 2427 | "default_texts": [], 2428 | "args": [] 2429 | } 2430 | }, 2431 | "back_to_work": { 2432 | "key": "back_to_work", 2433 | "keywords": [ 2434 | "继续干活", 2435 | "打工人" 2436 | ], 2437 | "patterns": [], 2438 | "params": { 2439 | "min_images": 1, 2440 | "max_images": 1, 2441 | "min_texts": 0, 2442 | "max_texts": 0, 2443 | "default_texts": [], 2444 | "args": [] 2445 | } 2446 | }, 2447 | "kick_ball": { 2448 | "key": "kick_ball", 2449 | "keywords": [ 2450 | "踢球" 2451 | ], 2452 | "patterns": [], 2453 | "params": { 2454 | "min_images": 1, 2455 | "max_images": 1, 2456 | "min_texts": 0, 2457 | "max_texts": 0, 2458 | "default_texts": [], 2459 | "args": [] 2460 | } 2461 | }, 2462 | "hammer": { 2463 | "key": "hammer", 2464 | "keywords": [ 2465 | "锤" 2466 | ], 2467 | "patterns": [], 2468 | "params": { 2469 | "min_images": 1, 2470 | "max_images": 1, 2471 | "min_texts": 0, 2472 | "max_texts": 0, 2473 | "default_texts": [], 2474 | "args": [] 2475 | } 2476 | }, 2477 | "knock": { 2478 | "key": "knock", 2479 | "keywords": [ 2480 | "敲" 2481 | ], 2482 | "patterns": [], 2483 | "params": { 2484 | "min_images": 1, 2485 | "max_images": 1, 2486 | "min_texts": 0, 2487 | "max_texts": 0, 2488 | "default_texts": [], 2489 | "args": [] 2490 | } 2491 | }, 2492 | "learn": { 2493 | "key": "learn", 2494 | "keywords": [ 2495 | "偷学" 2496 | ], 2497 | "patterns": [], 2498 | "params": { 2499 | "min_images": 1, 2500 | "max_images": 1, 2501 | "min_texts": 0, 2502 | "max_texts": 1, 2503 | "default_texts": [ 2504 | "偷学群友数理基础" 2505 | ], 2506 | "args": [] 2507 | } 2508 | }, 2509 | "5000choyen": { 2510 | "key": "5000choyen", 2511 | "keywords": [ 2512 | "5000兆" 2513 | ], 2514 | "patterns": [], 2515 | "params": { 2516 | "min_images": 0, 2517 | "max_images": 0, 2518 | "min_texts": 2, 2519 | "max_texts": 2, 2520 | "default_texts": [ 2521 | "我去", 2522 | "洛天依" 2523 | ], 2524 | "args": [] 2525 | } 2526 | }, 2527 | "petpet": { 2528 | "key": "petpet", 2529 | "keywords": [ 2530 | "摸", 2531 | "摸摸", 2532 | "摸头", 2533 | "rua" 2534 | ], 2535 | "patterns": [], 2536 | "params": { 2537 | "min_images": 1, 2538 | "max_images": 1, 2539 | "min_texts": 0, 2540 | "max_texts": 0, 2541 | "default_texts": [], 2542 | "args": [ 2543 | { 2544 | "name": "circle", 2545 | "type": "boolean", 2546 | "description": "是否将图片变为圆形", 2547 | "default": false, 2548 | "enum": null 2549 | } 2550 | ] 2551 | } 2552 | }, 2553 | "ascension": { 2554 | "key": "ascension", 2555 | "keywords": [ 2556 | "升天" 2557 | ], 2558 | "patterns": [], 2559 | "params": { 2560 | "min_images": 0, 2561 | "max_images": 0, 2562 | "min_texts": 1, 2563 | "max_texts": 1, 2564 | "default_texts": [ 2565 | "学的是机械" 2566 | ], 2567 | "args": [] 2568 | } 2569 | }, 2570 | "karyl_point": { 2571 | "key": "karyl_point", 2572 | "keywords": [ 2573 | "凯露指" 2574 | ], 2575 | "patterns": [], 2576 | "params": { 2577 | "min_images": 1, 2578 | "max_images": 1, 2579 | "min_texts": 0, 2580 | "max_texts": 0, 2581 | "default_texts": [], 2582 | "args": [] 2583 | } 2584 | }, 2585 | "wallpaper": { 2586 | "key": "wallpaper", 2587 | "keywords": [ 2588 | "墙纸" 2589 | ], 2590 | "patterns": [], 2591 | "params": { 2592 | "min_images": 1, 2593 | "max_images": 1, 2594 | "min_texts": 0, 2595 | "max_texts": 0, 2596 | "default_texts": [], 2597 | "args": [] 2598 | } 2599 | }, 2600 | "eat": { 2601 | "key": "eat", 2602 | "keywords": [ 2603 | "吃" 2604 | ], 2605 | "patterns": [], 2606 | "params": { 2607 | "min_images": 1, 2608 | "max_images": 1, 2609 | "min_texts": 0, 2610 | "max_texts": 0, 2611 | "default_texts": [], 2612 | "args": [] 2613 | } 2614 | }, 2615 | "play_game": { 2616 | "key": "play_game", 2617 | "keywords": [ 2618 | "玩游戏" 2619 | ], 2620 | "patterns": [], 2621 | "params": { 2622 | "min_images": 1, 2623 | "max_images": 1, 2624 | "min_texts": 0, 2625 | "max_texts": 1, 2626 | "default_texts": [ 2627 | "来玩休闲游戏啊" 2628 | ], 2629 | "args": [] 2630 | } 2631 | }, 2632 | "capoo_rip": { 2633 | "key": "capoo_rip", 2634 | "keywords": [ 2635 | "咖波撕" 2636 | ], 2637 | "patterns": [], 2638 | "params": { 2639 | "min_images": 1, 2640 | "max_images": 1, 2641 | "min_texts": 0, 2642 | "max_texts": 0, 2643 | "default_texts": [], 2644 | "args": [] 2645 | } 2646 | }, 2647 | "perfect": { 2648 | "key": "perfect", 2649 | "keywords": [ 2650 | "完美" 2651 | ], 2652 | "patterns": [], 2653 | "params": { 2654 | "min_images": 1, 2655 | "max_images": 1, 2656 | "min_texts": 0, 2657 | "max_texts": 0, 2658 | "default_texts": [], 2659 | "args": [] 2660 | } 2661 | }, 2662 | "funny_mirror": { 2663 | "key": "funny_mirror", 2664 | "keywords": [ 2665 | "哈哈镜" 2666 | ], 2667 | "patterns": [], 2668 | "params": { 2669 | "min_images": 1, 2670 | "max_images": 1, 2671 | "min_texts": 0, 2672 | "max_texts": 0, 2673 | "default_texts": [], 2674 | "args": [] 2675 | } 2676 | }, 2677 | "distracted": { 2678 | "key": "distracted", 2679 | "keywords": [ 2680 | "注意力涣散" 2681 | ], 2682 | "patterns": [], 2683 | "params": { 2684 | "min_images": 1, 2685 | "max_images": 1, 2686 | "min_texts": 0, 2687 | "max_texts": 0, 2688 | "default_texts": [], 2689 | "args": [] 2690 | } 2691 | }, 2692 | "make_friend": { 2693 | "key": "make_friend", 2694 | "keywords": [ 2695 | "交个朋友" 2696 | ], 2697 | "patterns": [], 2698 | "params": { 2699 | "min_images": 1, 2700 | "max_images": 1, 2701 | "min_texts": 0, 2702 | "max_texts": 1, 2703 | "default_texts": [], 2704 | "args": [] 2705 | } 2706 | }, 2707 | "wujing": { 2708 | "key": "wujing", 2709 | "keywords": [ 2710 | "吴京xx中国xx" 2711 | ], 2712 | "patterns": [ 2713 | "吴京[\\s::]*(.*?)中国(.*)" 2714 | ], 2715 | "params": { 2716 | "min_images": 0, 2717 | "max_images": 0, 2718 | "min_texts": 2, 2719 | "max_texts": 2, 2720 | "default_texts": [ 2721 | "不买华为不是", 2722 | "人" 2723 | ], 2724 | "args": [] 2725 | } 2726 | }, 2727 | "together": { 2728 | "key": "together", 2729 | "keywords": [ 2730 | "一起" 2731 | ], 2732 | "patterns": [], 2733 | "params": { 2734 | "min_images": 1, 2735 | "max_images": 1, 2736 | "min_texts": 0, 2737 | "max_texts": 1, 2738 | "default_texts": [], 2739 | "args": [] 2740 | } 2741 | }, 2742 | "paint": { 2743 | "key": "paint", 2744 | "keywords": [ 2745 | "这像画吗" 2746 | ], 2747 | "patterns": [], 2748 | "params": { 2749 | "min_images": 1, 2750 | "max_images": 1, 2751 | "min_texts": 0, 2752 | "max_texts": 0, 2753 | "default_texts": [], 2754 | "args": [] 2755 | } 2756 | }, 2757 | "why_at_me": { 2758 | "key": "why_at_me", 2759 | "keywords": [ 2760 | "为什么@我" 2761 | ], 2762 | "patterns": [], 2763 | "params": { 2764 | "min_images": 1, 2765 | "max_images": 1, 2766 | "min_texts": 0, 2767 | "max_texts": 0, 2768 | "default_texts": [], 2769 | "args": [] 2770 | } 2771 | }, 2772 | "dog_of_vtb": { 2773 | "key": "dog_of_vtb", 2774 | "keywords": [ 2775 | "管人痴" 2776 | ], 2777 | "patterns": [], 2778 | "params": { 2779 | "min_images": 1, 2780 | "max_images": 1, 2781 | "min_texts": 0, 2782 | "max_texts": 0, 2783 | "default_texts": [], 2784 | "args": [] 2785 | } 2786 | }, 2787 | "think_what": { 2788 | "key": "think_what", 2789 | "keywords": [ 2790 | "想什么" 2791 | ], 2792 | "patterns": [], 2793 | "params": { 2794 | "min_images": 1, 2795 | "max_images": 1, 2796 | "min_texts": 0, 2797 | "max_texts": 0, 2798 | "default_texts": [], 2799 | "args": [] 2800 | } 2801 | }, 2802 | "fanatic": { 2803 | "key": "fanatic", 2804 | "keywords": [ 2805 | "狂爱", 2806 | "狂粉" 2807 | ], 2808 | "patterns": [], 2809 | "params": { 2810 | "min_images": 0, 2811 | "max_images": 0, 2812 | "min_texts": 1, 2813 | "max_texts": 1, 2814 | "default_texts": [ 2815 | "洛天依" 2816 | ], 2817 | "args": [] 2818 | } 2819 | }, 2820 | "support": { 2821 | "key": "support", 2822 | "keywords": [ 2823 | "精神支柱" 2824 | ], 2825 | "patterns": [], 2826 | "params": { 2827 | "min_images": 1, 2828 | "max_images": 1, 2829 | "min_texts": 0, 2830 | "max_texts": 0, 2831 | "default_texts": [], 2832 | "args": [] 2833 | } 2834 | }, 2835 | "meteor": { 2836 | "key": "meteor", 2837 | "keywords": [ 2838 | "流星" 2839 | ], 2840 | "patterns": [], 2841 | "params": { 2842 | "min_images": 0, 2843 | "max_images": 0, 2844 | "min_texts": 1, 2845 | "max_texts": 1, 2846 | "default_texts": [ 2847 | "我要对象" 2848 | ], 2849 | "args": [] 2850 | } 2851 | }, 2852 | "keep_away": { 2853 | "key": "keep_away", 2854 | "keywords": [ 2855 | "远离" 2856 | ], 2857 | "patterns": [], 2858 | "params": { 2859 | "min_images": 1, 2860 | "max_images": 8, 2861 | "min_texts": 0, 2862 | "max_texts": 1, 2863 | "default_texts": [ 2864 | "如何提高社交质量 : \n远离以下头像的人" 2865 | ], 2866 | "args": [] 2867 | } 2868 | }, 2869 | "anya_suki": { 2870 | "key": "anya_suki", 2871 | "keywords": [ 2872 | "阿尼亚喜欢" 2873 | ], 2874 | "patterns": [], 2875 | "params": { 2876 | "min_images": 1, 2877 | "max_images": 1, 2878 | "min_texts": 0, 2879 | "max_texts": 1, 2880 | "default_texts": [ 2881 | "阿尼亚喜欢这个" 2882 | ], 2883 | "args": [] 2884 | } 2885 | }, 2886 | "need": { 2887 | "key": "need", 2888 | "keywords": [ 2889 | "需要", 2890 | "你可能需要" 2891 | ], 2892 | "patterns": [], 2893 | "params": { 2894 | "min_images": 1, 2895 | "max_images": 1, 2896 | "min_texts": 0, 2897 | "max_texts": 0, 2898 | "default_texts": [], 2899 | "args": [] 2900 | } 2901 | }, 2902 | "printing": { 2903 | "key": "printing", 2904 | "keywords": [ 2905 | "打印" 2906 | ], 2907 | "patterns": [], 2908 | "params": { 2909 | "min_images": 1, 2910 | "max_images": 1, 2911 | "min_texts": 0, 2912 | "max_texts": 0, 2913 | "default_texts": [], 2914 | "args": [] 2915 | } 2916 | }, 2917 | "dinosaur": { 2918 | "key": "dinosaur", 2919 | "keywords": [ 2920 | "恐龙", 2921 | "小恐龙" 2922 | ], 2923 | "patterns": [], 2924 | "params": { 2925 | "min_images": 1, 2926 | "max_images": 1, 2927 | "min_texts": 0, 2928 | "max_texts": 0, 2929 | "default_texts": [], 2930 | "args": [] 2931 | } 2932 | }, 2933 | "follow": { 2934 | "key": "follow", 2935 | "keywords": [ 2936 | "关注" 2937 | ], 2938 | "patterns": [], 2939 | "params": { 2940 | "min_images": 1, 2941 | "max_images": 1, 2942 | "min_texts": 0, 2943 | "max_texts": 1, 2944 | "default_texts": [], 2945 | "args": [] 2946 | } 2947 | }, 2948 | "imprison": { 2949 | "key": "imprison", 2950 | "keywords": [ 2951 | "坐牢" 2952 | ], 2953 | "patterns": [], 2954 | "params": { 2955 | "min_images": 0, 2956 | "max_images": 0, 2957 | "min_texts": 1, 2958 | "max_texts": 1, 2959 | "default_texts": [ 2960 | "我发涩图被抓起来了" 2961 | ], 2962 | "args": [] 2963 | } 2964 | }, 2965 | "dianzhongdian": { 2966 | "key": "dianzhongdian", 2967 | "keywords": [ 2968 | "入典", 2969 | "典中典", 2970 | "黑白草图" 2971 | ], 2972 | "patterns": [], 2973 | "params": { 2974 | "min_images": 1, 2975 | "max_images": 1, 2976 | "min_texts": 1, 2977 | "max_texts": 2, 2978 | "default_texts": [ 2979 | "救命啊" 2980 | ], 2981 | "args": [] 2982 | } 2983 | }, 2984 | "psyduck": { 2985 | "key": "psyduck", 2986 | "keywords": [ 2987 | "可达鸭" 2988 | ], 2989 | "patterns": [], 2990 | "params": { 2991 | "min_images": 0, 2992 | "max_images": 0, 2993 | "min_texts": 2, 2994 | "max_texts": 2, 2995 | "default_texts": [ 2996 | "来份", 2997 | "涩图" 2998 | ], 2999 | "args": [] 3000 | } 3001 | }, 3002 | "always_like": { 3003 | "key": "always_like", 3004 | "keywords": [ 3005 | "我永远喜欢" 3006 | ], 3007 | "patterns": [], 3008 | "params": { 3009 | "min_images": 1, 3010 | "max_images": 6, 3011 | "min_texts": 0, 3012 | "max_texts": 6, 3013 | "default_texts": [], 3014 | "args": [] 3015 | } 3016 | }, 3017 | "wakeup": { 3018 | "key": "wakeup", 3019 | "keywords": [ 3020 | "xx起来了" 3021 | ], 3022 | "patterns": [ 3023 | "(.+?)\\s+起来了" 3024 | ], 3025 | "params": { 3026 | "min_images": 0, 3027 | "max_images": 0, 3028 | "min_texts": 1, 3029 | "max_texts": 1, 3030 | "default_texts": [ 3031 | "好" 3032 | ], 3033 | "args": [] 3034 | } 3035 | }, 3036 | "pound": { 3037 | "key": "pound", 3038 | "keywords": [ 3039 | "捣" 3040 | ], 3041 | "patterns": [], 3042 | "params": { 3043 | "min_images": 1, 3044 | "max_images": 1, 3045 | "min_texts": 0, 3046 | "max_texts": 0, 3047 | "default_texts": [], 3048 | "args": [] 3049 | } 3050 | }, 3051 | "nekoha_holdsign": { 3052 | "key": "nekoha_holdsign", 3053 | "keywords": [ 3054 | "猫羽雫举牌", 3055 | "猫猫举牌" 3056 | ], 3057 | "patterns": [], 3058 | "params": { 3059 | "min_images": 0, 3060 | "max_images": 0, 3061 | "min_texts": 1, 3062 | "max_texts": 1, 3063 | "default_texts": [ 3064 | "V我50" 3065 | ], 3066 | "args": [] 3067 | } 3068 | }, 3069 | "can_can_need": { 3070 | "key": "can_can_need", 3071 | "keywords": [ 3072 | "看看你的" 3073 | ], 3074 | "patterns": [], 3075 | "params": { 3076 | "min_images": 2, 3077 | "max_images": 2, 3078 | "min_texts": 0, 3079 | "max_texts": 0, 3080 | "default_texts": [], 3081 | "args": [] 3082 | } 3083 | }, 3084 | "do": { 3085 | "key": "do", 3086 | "keywords": [ 3087 | "撅", 3088 | "狠狠地撅" 3089 | ], 3090 | "patterns": [], 3091 | "params": { 3092 | "min_images": 2, 3093 | "max_images": 2, 3094 | "min_texts": 0, 3095 | "max_texts": 0, 3096 | "default_texts": [], 3097 | "args": [] 3098 | } 3099 | }, 3100 | "empathy": { 3101 | "key": "empathy", 3102 | "keywords": [ 3103 | "换位思考" 3104 | ], 3105 | "patterns": [], 3106 | "params": { 3107 | "min_images": 1, 3108 | "max_images": 1, 3109 | "min_texts": 0, 3110 | "max_texts": 0, 3111 | "default_texts": [], 3112 | "args": [] 3113 | } 3114 | }, 3115 | "fleshlight": { 3116 | "key": "fleshlight", 3117 | "keywords": [ 3118 | "飞机杯" 3119 | ], 3120 | "patterns": [], 3121 | "params": { 3122 | "min_images": 1, 3123 | "max_images": 1, 3124 | "min_texts": 0, 3125 | "max_texts": 0, 3126 | "default_texts": [], 3127 | "args": [] 3128 | } 3129 | }, 3130 | "forbid": { 3131 | "key": "forbid", 3132 | "keywords": [ 3133 | "禁止", 3134 | "禁" 3135 | ], 3136 | "patterns": [], 3137 | "params": { 3138 | "min_images": 1, 3139 | "max_images": 1, 3140 | "min_texts": 0, 3141 | "max_texts": 0, 3142 | "default_texts": [], 3143 | "args": [] 3144 | } 3145 | }, 3146 | "grab": { 3147 | "key": "grab", 3148 | "keywords": [ 3149 | "抓" 3150 | ], 3151 | "patterns": [], 3152 | "params": { 3153 | "min_images": 1, 3154 | "max_images": 1, 3155 | "min_texts": 0, 3156 | "max_texts": 0, 3157 | "default_texts": [], 3158 | "args": [] 3159 | } 3160 | }, 3161 | "looklook": { 3162 | "key": "looklook", 3163 | "keywords": [ 3164 | "瞧瞧你那样" 3165 | ], 3166 | "patterns": [], 3167 | "params": { 3168 | "min_images": 1, 3169 | "max_images": 1, 3170 | "min_texts": 0, 3171 | "max_texts": 0, 3172 | "default_texts": [], 3173 | "args": [ 3174 | { 3175 | "name": "mirror", 3176 | "type": "boolean", 3177 | "description": "是否镜像翻转", 3178 | "default": false, 3179 | "enum": null 3180 | } 3181 | ] 3182 | } 3183 | }, 3184 | "operator_generator": { 3185 | "key": "operator_generator", 3186 | "keywords": [ 3187 | "合成大干员" 3188 | ], 3189 | "patterns": [], 3190 | "params": { 3191 | "min_images": 1, 3192 | "max_images": 1, 3193 | "min_texts": 0, 3194 | "max_texts": 1, 3195 | "default_texts": [], 3196 | "args": [] 3197 | } 3198 | }, 3199 | "stretch": { 3200 | "key": "stretch", 3201 | "keywords": [ 3202 | "双手", 3203 | "伸展" 3204 | ], 3205 | "patterns": [], 3206 | "params": { 3207 | "min_images": 1, 3208 | "max_images": 1, 3209 | "min_texts": 0, 3210 | "max_texts": 0, 3211 | "default_texts": [], 3212 | "args": [] 3213 | } 3214 | } 3215 | } -------------------------------------------------------------------------------- /development/keyMap.json: -------------------------------------------------------------------------------- 1 | { 2 | "万能表情": "universal", 3 | "空白表情": "universal", 4 | "加班": "overtime", 5 | "不喊我": "not_call_me", 6 | "撕": "rip", 7 | "滚": "roll", 8 | "一样": "alike", 9 | "迷惑": "confuse", 10 | "兑换券": "coupon", 11 | "看扁": "look_flat", 12 | "拍头": "beat_head", 13 | "诈尸": "rise_dead", 14 | "秽土转生": "rise_dead", 15 | "讲课": "teach", 16 | "敲黑板": "teach", 17 | "国旗": "china_flag", 18 | "罗永浩说": "luoyonghao_say", 19 | "看书": "read_book", 20 | "怒撕": "rip_angrily", 21 | "咖波撞": "capoo_strike", 22 | "咖波头槌": "capoo_strike", 23 | "字符画": "charpic", 24 | "震惊": "shock", 25 | "爬": "crawl", 26 | "许愿失败": "wish_fail", 27 | "紧贴": "tightly", 28 | "紧紧贴着": "tightly", 29 | "我老婆": "my_wife", 30 | "这是我老婆": "my_wife", 31 | "可莉吃": "klee_eat", 32 | "王境泽": "wangjingze", 33 | "为所欲为": "weisuoyuwei", 34 | "馋身子": "chanshenzi", 35 | "切格瓦拉": "qiegewala", 36 | "谁反对": "shuifandui", 37 | "曾小贤": "zengxiaoxian", 38 | "压力大爷": "yalidaye", 39 | "你好骚啊": "nihaosaoa", 40 | "食屎啦你": "shishilani", 41 | "五年怎么过的": "wunian", 42 | "追列车": "chase_train", 43 | "追火车": "chase_train", 44 | "听音乐": "listen_music", 45 | "打穿": "hit_screen", 46 | "打穿屏幕": "hit_screen", 47 | "波奇手稿": "bocchi_draft", 48 | "对称": "symmetric", 49 | "米哈游": "mihoyo", 50 | "胡桃平板": "walnut_pad", 51 | "等价无穷小": "lim_x_0", 52 | "别碰": "dont_touch", 53 | "二次元入口": "acg_entrance", 54 | "快跑": "run", 55 | "上瘾": "addiction", 56 | "毒瘾发作": "addiction", 57 | "抱大腿": "hug_leg", 58 | "布洛妮娅举牌": "bronya_holdsign", 59 | "大鸭鸭举牌": "bronya_holdsign", 60 | "诺基亚": "nokia", 61 | "有内鬼": "nokia", 62 | "垃圾": "garbage", 63 | "垃圾桶": "garbage", 64 | "砸": "smash", 65 | "唐可可举牌": "tankuku_raisesign", 66 | "咖波说": "capoo_say", 67 | "喜报": "good_news", 68 | "滚屏": "scroll", 69 | "吸": "suck", 70 | "嗦": "suck", 71 | "卡比锤": "kirby_hammer", 72 | "卡比重锤": "kirby_hammer", 73 | "伊地知虹夏举牌": "nijika_holdsign", 74 | "虹夏举牌": "nijika_holdsign", 75 | "douyin": "douyin", 76 | "结婚申请": "marriage", 77 | "结婚登记": "marriage", 78 | "拍": "pat", 79 | "最想要的东西": "what_he_wants", 80 | "波纹": "wave", 81 | "推锅": "pass_the_buck", 82 | "甩锅": "pass_the_buck", 83 | "抱紧": "hold_tight", 84 | "ph": "pornhub", 85 | "pornhub": "pornhub", 86 | "咖波蹭": "capoo_rub", 87 | "咖波贴": "capoo_rub", 88 | "草神啃": "caoshen_bite", 89 | "高血压": "blood_pressure", 90 | "低情商xx高情商xx": "high_EQ", 91 | "加载中": "loading", 92 | "万花筒": "kaleidoscope", 93 | "万花镜": "kaleidoscope", 94 | "刮刮乐": "scratchcard", 95 | "木鱼": "wooden_fish", 96 | "胡桃放大": "walnut_zoom", 97 | "闪瞎": "flash_blind", 98 | "亲": "kiss", 99 | "亲亲": "kiss", 100 | "鲁迅说": "luxun_say", 101 | "鲁迅说过": "luxun_say", 102 | "防诱拐": "anti_kidnap", 103 | "一巴掌": "slap", 104 | "无响应": "no_response", 105 | "我朋友说": "my_friend", 106 | "复读": "repeat", 107 | "击剑": "fencing", 108 | "🤺": "fencing", 109 | "啾啾": "jiujiu", 110 | "急急国王": "jiji_king", 111 | "永远爱你": "love_you", 112 | "采访": "interview", 113 | "风车转": "windmill_turn", 114 | "记仇": "hold_grudge", 115 | "恍惚": "trance", 116 | "为什么要有手": "why_have_hands", 117 | "不要靠近": "dont_go_near", 118 | "小天使": "little_angel", 119 | "安全感": "safe_sense", 120 | "遇到困难请拨打": "call_110", 121 | "捶爆": "thump_wildly", 122 | "爆捶": "thump_wildly", 123 | "鼓掌": "applaud", 124 | "整点薯条": "find_chips", 125 | "一直": "always", 126 | "群青": "cyan", 127 | "打拳": "punch", 128 | "亚文化取名机": "name_generator", 129 | "亚名": "name_generator", 130 | "不文明": "incivilization", 131 | "小画家": "painter", 132 | "转": "turn", 133 | "舔": "prpr", 134 | "舔屏": "prpr", 135 | "prpr": "prpr", 136 | "搓": "twist", 137 | "低语": "murmur", 138 | "贴": "rub", 139 | "贴贴": "rub", 140 | "蹭": "rub", 141 | "蹭蹭": "rub", 142 | "奶茶": "bubble_tea", 143 | "踩": "step_on", 144 | "坐得住": "sit_still", 145 | "坐的住": "sit_still", 146 | "丢": "throw", 147 | "扔": "throw", 148 | "捶": "thump", 149 | "问问": "ask", 150 | "口号": "slogan", 151 | "土豆": "potato", 152 | "请假条": "note_for_leave", 153 | "捂脸": "cover_face", 154 | "挠头": "scratch_head", 155 | "咖波画": "capoo_draw", 156 | "顶": "play", 157 | "玩": "play", 158 | "胡桃啃": "hutao_bite", 159 | "看图标": "look_this_icon", 160 | "啃": "bite", 161 | "yt": "youtube", 162 | "youtube": "youtube", 163 | "手枪": "gun", 164 | "我推的网友": "oshi_no_ko", 165 | "满脑子": "fill_head", 166 | "添乱": "add_chaos", 167 | "给社会添乱": "add_chaos", 168 | "出警": "police", 169 | "警察": "police1", 170 | "像样的亲亲": "decent_kiss", 171 | "别说了": "shutup", 172 | "google": "google", 173 | "抛": "throw_gif", 174 | "掷": "throw_gif", 175 | "举牌": "raise_sign", 176 | "膜": "worship", 177 | "膜拜": "worship", 178 | "舰长": "captain", 179 | "悲报": "bad_news", 180 | "离婚协议": "divorce", 181 | "离婚申请": "divorce", 182 | "继续干活": "back_to_work", 183 | "打工人": "back_to_work", 184 | "踢球": "kick_ball", 185 | "锤": "hammer", 186 | "敲": "knock", 187 | "偷学": "learn", 188 | "5000兆": "5000choyen", 189 | "摸": "petpet", 190 | "摸摸": "petpet", 191 | "摸头": "petpet", 192 | "rua": "petpet", 193 | "升天": "ascension", 194 | "凯露指": "karyl_point", 195 | "墙纸": "wallpaper", 196 | "吃": "eat", 197 | "玩游戏": "play_game", 198 | "咖波撕": "capoo_rip", 199 | "完美": "perfect", 200 | "哈哈镜": "funny_mirror", 201 | "注意力涣散": "distracted", 202 | "交个朋友": "make_friend", 203 | "吴京xx中国xx": "wujing", 204 | "一起": "together", 205 | "这像画吗": "paint", 206 | "为什么@我": "why_at_me", 207 | "管人痴": "dog_of_vtb", 208 | "想什么": "think_what", 209 | "狂爱": "fanatic", 210 | "狂粉": "fanatic", 211 | "精神支柱": "support", 212 | "流星": "meteor", 213 | "远离": "keep_away", 214 | "阿尼亚喜欢": "anya_suki", 215 | "需要": "need", 216 | "你可能需要": "need", 217 | "打印": "printing", 218 | "恐龙": "dinosaur", 219 | "小恐龙": "dinosaur", 220 | "关注": "follow", 221 | "坐牢": "imprison", 222 | "入典": "dianzhongdian", 223 | "典中典": "dianzhongdian", 224 | "黑白草图": "dianzhongdian", 225 | "可达鸭": "psyduck", 226 | "我永远喜欢": "always_like", 227 | "xx起来了": "wakeup", 228 | "捣": "pound", 229 | "猫羽雫举牌": "nekoha_holdsign", 230 | "猫猫举牌": "nekoha_holdsign", 231 | "看看你的": "can_can_need", 232 | "撅": "do", 233 | "狠狠地撅": "do", 234 | "换位思考": "empathy", 235 | "飞机杯": "fleshlight", 236 | "禁止": "forbid", 237 | "禁": "forbid", 238 | "抓": "grab", 239 | "瞧瞧你那样": "looklook", 240 | "合成大干员": "operator_generator", 241 | "双手": "stretch", 242 | "伸展": "stretch" 243 | } -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | RUN apt update && apt install -y python3-pip fonts-noto-cjk fonts-noto-color-emoji git fontconfig 4 | 5 | RUN git clone https://github.com/MeetWq/meme-generator.git && mkdir /usr/share/fonts/meme && mv meme-generator/resources/fonts/* /usr/share/fonts/meme 6 | 7 | RUN fc-cache -fv 8 | 9 | RUN pip install poetry 10 | 11 | RUN git clone https://github.com/MeetWq/meme-generator-contrib && mkdir /meme-extend && mv meme-generator-contrib/memes/* /meme-extend 12 | 13 | ADD config.toml /root/.config/meme_generator/config.toml 14 | 15 | RUN cd meme-generator && poetry install && . .venv/bin/activate && python -m meme_generator.download && cd .. 16 | 17 | RUN rm -rf meme-generator-contrib && rm -rf $HOME/meme-generator 18 | 19 | ADD utils.py meme-generator/meme_generator 20 | 21 | ADD dianzhongdian/__init__.py meme-generator/meme_generator/memes/dianzhongdian/ 22 | # 如果有自己扩展包 23 | # ADD extends/ /meme-extend 24 | 25 | CMD cd meme-generator && . .venv/bin/activate && python3 -m meme_generator.app 26 | -------------------------------------------------------------------------------- /docker/config.toml: -------------------------------------------------------------------------------- 1 | [meme] 2 | load_builtin_memes = true # 是否加载内置表情包 3 | meme_dirs = ["/meme-extend"] # 加载其他位置的表情包,填写文件夹路径 4 | meme_disabled_list = [] # 禁用的表情包列表,填写表情的 `key` 5 | 6 | [resource] 7 | resource_urls = [ 8 | "https://raw.githubusercontent.com/MeetWq/meme-generator/", 9 | "https://ghproxy.com/https://raw.githubusercontent.com/MeetWq/meme-generator/", 10 | "https://fastly.jsdelivr.net/gh/MeetWq/meme-generator@", 11 | "https://raw.fastgit.org/MeetWq/meme-generator/", 12 | "https://raw.fgit.ml/MeetWq/meme-generator/", 13 | "https://raw.gitmirror.com/MeetWq/meme-generator/", 14 | "https://raw.kgithub.com/MeetWq/meme-generator/", 15 | ] 16 | 17 | [gif] 18 | gif_max_size = 10.0 # 限制生成的 gif 文件大小,单位为 Mb 19 | gif_max_frames = 100 # 限制生成的 gif 文件帧数 20 | 21 | [translate] 22 | baidu_trans_appid = "" # 百度翻译api相关,表情包 `dianzhongdian` 需要使用 23 | baidu_trans_apikey = "" # 可在 百度翻译开放平台 (http://api.fanyi.baidu.com) 申请 24 | 25 | [server] 26 | host = "0.0.0.0" # web server 监听地址 27 | port = 2233 # web server 端口 28 | -------------------------------------------------------------------------------- /docker/dianzhongdian/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from pil_utils import BuildImage 4 | 5 | from meme_generator import add_meme 6 | from meme_generator.exception import TextOverLength 7 | from meme_generator.utils import run_sync, translate, translate_microsoft 8 | 9 | 10 | @run_sync 11 | def _dianzhongdian(img: BuildImage, text: str, trans: str): 12 | img = img.convert("L").resize_width(500) 13 | text_img1 = BuildImage.new("RGBA", (500, 60)) 14 | text_img2 = BuildImage.new("RGBA", (500, 35)) 15 | 16 | try: 17 | text_img1.draw_text( 18 | (20, 0, text_img1.width - 20, text_img1.height), 19 | text, 20 | max_fontsize=50, 21 | min_fontsize=25, 22 | fill="white", 23 | ) 24 | except ValueError: 25 | raise TextOverLength(text) 26 | 27 | try: 28 | text_img2.draw_text( 29 | (20, 0, text_img2.width - 20, text_img2.height), 30 | trans, 31 | max_fontsize=25, 32 | min_fontsize=10, 33 | fill="white", 34 | ) 35 | except ValueError: 36 | raise TextOverLength(text) 37 | 38 | frame = BuildImage.new("RGBA", (500, img.height + 100), "black") 39 | frame.paste(img, alpha=True) 40 | frame.paste(text_img1, (0, img.height), alpha=True) 41 | frame.paste(text_img2, (0, img.height + 60), alpha=True) 42 | return frame.save_jpg() 43 | 44 | 45 | async def dianzhongdian(images: List[BuildImage], texts: List[str], args): 46 | if len(texts) == 1: 47 | text = texts[0] 48 | trans = await translate_microsoft(text, lang_to="jp") 49 | else: 50 | text = texts[0] 51 | trans = texts[1] 52 | 53 | return await _dianzhongdian(images[0], text, trans) 54 | 55 | 56 | add_meme( 57 | "dianzhongdian", 58 | dianzhongdian, 59 | min_images=1, 60 | max_images=1, 61 | min_texts=1, 62 | max_texts=2, 63 | default_texts=["救命啊"], 64 | keywords=["入典", "典中典", "黑白草图"], 65 | ) 66 | -------------------------------------------------------------------------------- /docker/utils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import inspect 4 | import math 5 | import random 6 | import time 7 | from dataclasses import dataclass 8 | from enum import Enum 9 | from functools import partial, wraps 10 | from io import BytesIO 11 | from typing import ( 12 | TYPE_CHECKING, 13 | Any, 14 | Callable, 15 | Coroutine, 16 | List, 17 | Literal, 18 | Optional, 19 | Protocol, 20 | Tuple, 21 | TypeVar, 22 | ) 23 | 24 | import httpx 25 | from PIL.Image import Image as IMG 26 | from pil_utils import BuildImage, Text2Image 27 | from pil_utils.types import ColorType, FontStyle, FontWeight 28 | from typing_extensions import ParamSpec 29 | 30 | from .config import meme_config 31 | from .exception import MemeGeneratorException 32 | 33 | if TYPE_CHECKING: 34 | from .meme import Meme 35 | 36 | P = ParamSpec("P") 37 | R = TypeVar("R") 38 | 39 | 40 | def run_sync(call: Callable[P, R]) -> Callable[P, Coroutine[None, None, R]]: 41 | """一个用于包装 sync function 为 async function 的装饰器 42 | 参数: 43 | call: 被装饰的同步函数 44 | """ 45 | 46 | @wraps(call) 47 | async def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R: 48 | loop = asyncio.get_running_loop() 49 | pfunc = partial(call, *args, **kwargs) 50 | result = await loop.run_in_executor(None, pfunc) 51 | return result 52 | 53 | return _wrapper 54 | 55 | 56 | def is_coroutine_callable(call: Callable[..., Any]) -> bool: 57 | """检查 call 是否是一个 callable 协程函数""" 58 | if inspect.isroutine(call): 59 | return inspect.iscoroutinefunction(call) 60 | if inspect.isclass(call): 61 | return False 62 | func_ = getattr(call, "__call__", None) 63 | return inspect.iscoroutinefunction(func_) 64 | 65 | 66 | def save_gif(frames: List[IMG], duration: float) -> BytesIO: 67 | output = BytesIO() 68 | frames[0].save( 69 | output, 70 | format="GIF", 71 | save_all=True, 72 | append_images=frames[1:], 73 | duration=duration * 1000, 74 | loop=0, 75 | disposal=2, 76 | optimize=False, 77 | ) 78 | 79 | # 没有超出最大大小,直接返回 80 | nbytes = output.getbuffer().nbytes 81 | if nbytes <= meme_config.gif.gif_max_size * 10**6: 82 | return output 83 | 84 | # 超出最大大小,帧数超出最大帧数时,缩减帧数 85 | n_frames = len(frames) 86 | gif_max_frames = meme_config.gif.gif_max_frames 87 | if n_frames > gif_max_frames: 88 | index = range(n_frames) 89 | ratio = n_frames / gif_max_frames 90 | index = (int(i * ratio) for i in range(gif_max_frames)) 91 | new_duration = duration * ratio 92 | new_frames = [frames[i] for i in index] 93 | return save_gif(new_frames, new_duration) 94 | 95 | # 超出最大大小,帧数没有超出最大帧数时,缩小尺寸 96 | new_frames = [ 97 | frame.resize((int(frame.width * 0.9), int(frame.height * 0.9))) 98 | for frame in frames 99 | ] 100 | return save_gif(new_frames, duration) 101 | 102 | 103 | class Maker(Protocol): 104 | def __call__(self, img: BuildImage) -> BuildImage: 105 | ... 106 | 107 | 108 | class GifMaker(Protocol): 109 | def __call__(self, i: int) -> Maker: 110 | ... 111 | 112 | 113 | def get_avg_duration(image: IMG) -> float: 114 | if not getattr(image, "is_animated", False): 115 | return 0 116 | total_duration = 0 117 | for i in range(image.n_frames): 118 | image.seek(i) 119 | total_duration += image.info["duration"] 120 | return total_duration / image.n_frames 121 | 122 | 123 | def split_gif(image: IMG) -> List[IMG]: 124 | frames: List[IMG] = [] 125 | 126 | update_mode = "full" 127 | for i in range(image.n_frames): 128 | image.seek(i) 129 | if image.tile: # type: ignore 130 | update_region = image.tile[0][1][2:] # type: ignore 131 | if update_region != image.size: 132 | update_mode = "partial" 133 | break 134 | 135 | last_frame: Optional[IMG] = None 136 | for i in range(image.n_frames): 137 | image.seek(i) 138 | frame = image.copy() 139 | if update_mode == "partial" and last_frame: 140 | frame = last_frame.copy().paste(frame) 141 | frames.append(frame) 142 | image.seek(0) 143 | if image.info.__contains__("transparency"): 144 | frames[0].info["transparency"] = image.info["transparency"] 145 | return frames 146 | 147 | 148 | def make_jpg_or_gif( 149 | img: BuildImage, func: Maker, keep_transparency: bool = False 150 | ) -> BytesIO: 151 | """ 152 | 制作静图或者动图 153 | :params 154 | * ``img``: 输入图片 155 | * ``func``: 图片处理函数,输入img,返回处理后的图片 156 | * ``keep_transparency``: 传入gif时,是否保留该gif的透明度 157 | """ 158 | image = img.image 159 | if not getattr(image, "is_animated", False): 160 | return func(img).save_jpg() 161 | else: 162 | frames = split_gif(image) 163 | duration = get_avg_duration(image) / 1000 164 | frames = [func(BuildImage(frame)).image for frame in frames] 165 | if keep_transparency: 166 | image.seek(0) 167 | if image.info.__contains__("transparency"): 168 | frames[0].info["transparency"] = image.info["transparency"] 169 | return save_gif(frames, duration) 170 | 171 | 172 | def make_png_or_gif( 173 | img: BuildImage, func: Maker, keep_transparency: bool = False 174 | ) -> BytesIO: 175 | """ 176 | 制作静图或者动图 177 | :params 178 | * ``img``: 输入图片 179 | * ``func``: 图片处理函数,输入img,返回处理后的图片 180 | * ``keep_transparency``: 传入gif时,是否保留该gif的透明度 181 | """ 182 | image = img.image 183 | if not getattr(image, "is_animated", False): 184 | return func(img).save_png() 185 | else: 186 | frames = split_gif(image) 187 | duration = get_avg_duration(image) / 1000 188 | frames = [func(BuildImage(frame)).image for frame in frames] 189 | if keep_transparency: 190 | image.seek(0) 191 | if image.info.__contains__("transparency"): 192 | frames[0].info["transparency"] = image.info["transparency"] 193 | return save_gif(frames, duration) 194 | 195 | 196 | class FrameAlignPolicy(Enum): 197 | """ 198 | 要叠加的gif长度大于基准gif时,是否延长基准gif长度以对齐两个gif 199 | """ 200 | 201 | no_extend = 0 202 | """不延长""" 203 | extend_first = 1 204 | """延长第一帧""" 205 | extend_last = 2 206 | """延长最后一帧""" 207 | extend_loop = 3 208 | """以循环方式延长""" 209 | 210 | 211 | def make_gif_or_combined_gif( 212 | img: BuildImage, 213 | maker: GifMaker, 214 | frame_num: int, 215 | duration: float, 216 | frame_align: FrameAlignPolicy = FrameAlignPolicy.no_extend, 217 | input_based: bool = False, 218 | keep_transparency: bool = False, 219 | ) -> BytesIO: 220 | """ 221 | 使用静图或动图制作gif 222 | :params 223 | * ``img``: 输入图片,如头像 224 | * ``maker``: 图片处理函数生成,传入第几帧,返回对应的图片处理函数 225 | * ``frame_num``: 目标gif的帧数 226 | * ``duration``: 相邻帧之间的时间间隔,单位为秒 227 | * ``frame_align``: 要叠加的gif长度大于基准gif时,gif长度对齐方式 228 | * ``input_based``: 是否以输入gif为基准合成gif,默认为`False`,即以目标gif为基准 229 | * ``keep_transparency``: 传入gif时,是否保留该gif的透明度 230 | """ 231 | image = img.image 232 | if not getattr(image, "is_animated", False): 233 | return save_gif([maker(i)(img).image for i in range(frame_num)], duration) 234 | 235 | frame_num_in = image.n_frames 236 | duration_in = get_avg_duration(image) / 1000 237 | total_duration_in = frame_num_in * duration_in 238 | total_duration = frame_num * duration 239 | 240 | if input_based: 241 | frame_num_base = frame_num_in 242 | frame_num_fit = frame_num 243 | duration_base = duration_in 244 | duration_fit = duration 245 | total_duration_base = total_duration_in 246 | total_duration_fit = total_duration 247 | else: 248 | frame_num_base = frame_num 249 | frame_num_fit = frame_num_in 250 | duration_base = duration 251 | duration_fit = duration_in 252 | total_duration_base = total_duration 253 | total_duration_fit = total_duration_in 254 | 255 | frame_idxs: List[int] = list(range(frame_num_base)) 256 | diff_duration = total_duration_fit - total_duration_base 257 | diff_num = int(diff_duration / duration_base) 258 | 259 | if diff_duration >= duration_base: 260 | if frame_align == FrameAlignPolicy.extend_first: 261 | frame_idxs = [0] * diff_num + frame_idxs 262 | 263 | elif frame_align == FrameAlignPolicy.extend_last: 264 | frame_idxs += [frame_num_base - 1] * diff_num 265 | 266 | elif frame_align == FrameAlignPolicy.extend_loop: 267 | frame_num_total = frame_num_base 268 | # 重复基准gif,直到两个gif总时长之差在1个间隔以内,或总帧数超出最大帧数 269 | while frame_num_total + frame_num_base <= meme_config.gif.gif_max_frames: 270 | frame_num_total += frame_num_base 271 | frame_idxs += list(range(frame_num_base)) 272 | multiple = round(frame_num_total * duration_base / total_duration_fit) 273 | if ( 274 | math.fabs( 275 | total_duration_fit * multiple - frame_num_total * duration_base 276 | ) 277 | <= duration_base 278 | ): 279 | break 280 | 281 | frames: List[IMG] = [] 282 | frame_idx_fit = 0 283 | time_start = 0 284 | for i, idx in enumerate(frame_idxs): 285 | while frame_idx_fit < frame_num_fit: 286 | if ( 287 | frame_idx_fit * duration_fit 288 | <= i * duration_base - time_start 289 | < (frame_idx_fit + 1) * duration_fit 290 | ): 291 | if input_based: 292 | idx_in = idx 293 | idx_maker = frame_idx_fit 294 | else: 295 | idx_in = frame_idx_fit 296 | idx_maker = idx 297 | 298 | func = maker(idx_maker) 299 | image.seek(idx_in) 300 | frames.append(func(BuildImage(image.copy())).image) 301 | break 302 | else: 303 | frame_idx_fit += 1 304 | if frame_idx_fit >= frame_num_fit: 305 | frame_idx_fit = 0 306 | time_start += total_duration_fit 307 | 308 | if keep_transparency: 309 | image.seek(0) 310 | if image.info.__contains__("transparency"): 311 | frames[0].info["transparency"] = image.info["transparency"] 312 | 313 | return save_gif(frames, duration) 314 | 315 | 316 | async def translate(text: str, lang_from: str = "auto", lang_to: str = "zh") -> str: 317 | appid = meme_config.translate.baidu_trans_appid 318 | apikey = meme_config.translate.baidu_trans_apikey 319 | if not appid or not apikey: 320 | raise MemeGeneratorException( 321 | "The `baidu_trans_appid` or `baidu_trans_apikey` is not set." 322 | "Please check your config file!" 323 | ) 324 | salt = str(round(time.time() * 1000)) 325 | sign_raw = appid + text + salt + apikey 326 | sign = hashlib.md5(sign_raw.encode("utf8")).hexdigest() 327 | params = { 328 | "q": text, 329 | "from": lang_from, 330 | "to": lang_to, 331 | "appid": appid, 332 | "salt": salt, 333 | "sign": sign, 334 | } 335 | url = "https://fanyi-api.baidu.com/api/trans/vip/translate" 336 | async with httpx.AsyncClient() as client: 337 | resp = await client.get(url, params=params) 338 | result = resp.json() 339 | return result["trans_result"][0]["dst"] 340 | async def translate_microsoft(text: str, lang_from: str = "zh-CN", lang_to: str = "ja") -> str: 341 | if lang_to == 'jp': 342 | lang_to = 'ja' 343 | params = { 344 | "text": text, 345 | "from": lang_from, 346 | "to": lang_to, 347 | } 348 | url = "https://api.pawan.krd/mtranslate" 349 | async with httpx.AsyncClient() as client: 350 | resp = await client.get(url, params=params) 351 | result = resp.json() 352 | return result["translated"] 353 | 354 | def random_text() -> str: 355 | return random.choice(["刘一", "陈二", "张三", "李四", "王五", "赵六", "孙七", "周八", "吴九", "郑十"]) 356 | 357 | 358 | def random_image() -> BytesIO: 359 | text = random.choice(["😂", "😅", "🤗", "🤤", "🥵", "🥰", "😍", "😭", "😋", "😏"]) 360 | return ( 361 | BuildImage.new("RGBA", (500, 500), "white") 362 | .draw_text((0, 0, 500, 500), text, max_fontsize=400) 363 | .save_png() 364 | ) 365 | 366 | 367 | @dataclass 368 | class TextProperties: 369 | fill: ColorType = "black" 370 | style: FontStyle = "normal" 371 | weight: FontWeight = "normal" 372 | stroke_width: int = 0 373 | stroke_fill: Optional[ColorType] = None 374 | 375 | 376 | def default_template(meme: "Meme", number: int) -> str: 377 | return f"{number}. {'/'.join(meme.keywords)}" 378 | 379 | 380 | def render_meme_list( 381 | meme_list: List[Tuple["Meme", TextProperties]], 382 | *, 383 | template: Callable[["Meme", int], str] = default_template, 384 | order_direction: Literal["row", "column"] = "column", 385 | columns: int = 4, 386 | column_align: Literal["left", "center", "right"] = "left", 387 | item_padding: Tuple[int, int] = (15, 6), 388 | image_padding: Tuple[int, int] = (50, 50), 389 | bg_color: ColorType = "white", 390 | fontsize: int = 30, 391 | fontname: str = "", 392 | fallback_fonts: List[str] = [], 393 | ) -> BytesIO: 394 | item_images: List[Text2Image] = [] 395 | for i, (meme, properties) in enumerate(meme_list, start=1): 396 | text = template(meme, i) 397 | t2m = Text2Image.from_text( 398 | text, 399 | fontsize=fontsize, 400 | style=properties.style, 401 | weight=properties.weight, 402 | fill=properties.fill, 403 | stroke_width=properties.stroke_width, 404 | stroke_fill=properties.stroke_fill, 405 | fontname=fontname, 406 | fallback_fonts=fallback_fonts, 407 | ) 408 | item_images.append(t2m) 409 | char_A = ( 410 | Text2Image.from_text( 411 | "A", fontsize=fontsize, fontname=fontname, fallback_fonts=fallback_fonts 412 | ) 413 | .lines[0] 414 | .chars[0] 415 | ) 416 | num_per_col = math.ceil(len(item_images) / columns) 417 | column_images: List[BuildImage] = [] 418 | for col in range(columns): 419 | if order_direction == "column": 420 | images = item_images[col * num_per_col : (col + 1) * num_per_col] 421 | else: 422 | images = [ 423 | item_images[num * columns + col] 424 | for num in range((len(item_images) - col - 1) // columns + 1) 425 | ] 426 | img_w = max((t2m.width for t2m in images)) + item_padding[0] * 2 427 | img_h = (char_A.ascent + item_padding[1] * 2) * len(images) + char_A.descent 428 | image = BuildImage.new("RGB", (img_w, img_h), bg_color) 429 | y = item_padding[1] 430 | for t2m in images: 431 | if column_align == "left": 432 | x = 0 433 | elif column_align == "center": 434 | x = (img_w - t2m.width - item_padding[0] * 2) // 2 435 | else: 436 | x = img_w - t2m.width - item_padding[0] * 2 437 | t2m.draw_on_image(image.image, (x, y)) 438 | y += char_A.ascent + item_padding[1] * 2 439 | column_images.append(image) 440 | 441 | img_w = sum((img.width for img in column_images)) + image_padding[0] * 2 442 | img_h = max((img.height for img in column_images)) + image_padding[1] * 2 443 | image = BuildImage.new("RGB", (img_w, img_h), bg_color) 444 | x, y = image_padding 445 | for img in column_images: 446 | image.paste(img, (x, y)) 447 | x += img.width 448 | return image.save_jpg() 449 | -------------------------------------------------------------------------------- /karin-meme.js: -------------------------------------------------------------------------------- 1 | import { plugin, segment, config, common } from 'node-karin' 2 | import fs from 'fs' 3 | import path from 'node:path' 4 | import _ from 'lodash' 5 | 6 | const baseUrl = 'https://memes.ikechan8370.com' 7 | /** 8 | * 机器人发表情是否引用回复用户 9 | * @type {boolean} 10 | */ 11 | const reply = true 12 | /** 13 | * 是否强制使用#触发命令 14 | */ 15 | const forceSharp = false 16 | /** 17 | * 主人保护,撅主人时会被反撅 (暂时只支持QQ) 18 | * @type {boolean} 19 | */ 20 | const masterProtectDo = true 21 | /** 22 | * 用户输入的图片,最大支持的文件大小,单位为MB 23 | * @type {number} 24 | */ 25 | const maxFileSize = 10 26 | 27 | let keyMap = {} 28 | 29 | let infos = {} 30 | class memes extends plugin { 31 | constructor() { 32 | let option = { 33 | /** 功能名称 */ 34 | name: '表情包', 35 | /** 功能描述 */ 36 | dsc: '表情包制作', 37 | /** https://oicqjs.github.io/oicq/#events */ 38 | event: 'message', 39 | /** 优先级,数字越小等级越高 */ 40 | priority: 5000, 41 | rule: [ 42 | { 43 | /** 命令正则匹配 */ 44 | reg: '^(#)?(meme(s)?|表情包)列表$', 45 | /** 执行方法 */ 46 | fnc: 'memesList' 47 | }, 48 | { 49 | /** 命令正则匹配 */ 50 | reg: '^#?随机(meme(s)?|表情包)', 51 | /** 执行方法 */ 52 | fnc: 'randomMemes' 53 | }, 54 | { 55 | /** 命令正则匹配 */ 56 | reg: '^#?(meme(s)?|表情包)帮助', 57 | /** 执行方法 */ 58 | fnc: 'memesHelp' 59 | }, 60 | { 61 | /** 命令正则匹配 */ 62 | reg: '^#?(meme(s)?|表情包)搜索', 63 | /** 执行方法 */ 64 | fnc: 'memesSearch' 65 | }, 66 | { 67 | /** 命令正则匹配 */ 68 | reg: '^#?(meme(s)?|表情包)更新', 69 | /** 执行方法 */ 70 | fnc: 'memesUpdate' 71 | } 72 | 73 | ] 74 | } 75 | Object.keys(keyMap).forEach(key => { 76 | let reg = forceSharp ? `^#${key}` : `^#?${key}` 77 | option.rule.push({ 78 | /** 命令正则匹配 */ 79 | reg, 80 | /** 执行方法 */ 81 | fnc: 'memes' 82 | }) 83 | }) 84 | 85 | super(option) 86 | 87 | /* 暂时不知道Karin怎么热更正则, 故自动更新貌似无法生效, 先注释掉 */ 88 | 89 | // // generated by ChatGPT 90 | // function generateCronExpression () { 91 | // // 生成每天的半夜2-4点之间的小时值(随机选择) 92 | // const hour = Math.floor(Math.random() * 3) + 2 93 | 94 | // // 生成每小时的随机分钟值(0到59之间的随机数) 95 | // const minute = Math.floor(Math.random() * 60) 96 | 97 | // // 构建 cron 表达式 98 | // return `${minute} ${hour} * * *` 99 | // } 100 | 101 | // this.task = { 102 | // // 每天的凌晨3点执行 103 | // cron: generateCronExpression(), 104 | // name: 'memes自动更新任务', 105 | // fnc: this.init.bind(this) 106 | // } 107 | } 108 | 109 | async init() { 110 | common.mkdir('./data/karin-plugin-example/memes') 111 | keyMap = {} 112 | infos = {} 113 | if (fs.existsSync('data/karin-plugin-example/memes/infos.json')) { 114 | infos = fs.readFileSync('data/karin-plugin-example/memes/infos.json') 115 | infos = JSON.parse(infos) 116 | } 117 | if (fs.existsSync('data/karin-plugin-example/memes/keyMap.json')) { 118 | keyMap = fs.readFileSync('data/karin-plugin-example/memes/keyMap.json') 119 | keyMap = JSON.parse(keyMap) 120 | } 121 | if (Object.keys(infos).length === 0) { 122 | logger.mark('yunzai-meme infos资源本地不存在,正在远程拉取中') 123 | let infosRes = await fetch(`${baseUrl}/memes/static/infos.json`) 124 | if (infosRes.status === 200) { 125 | infos = await infosRes.json() 126 | fs.writeFileSync('data/karin-plugin-example/memes/infos.json', JSON.stringify(infos)) 127 | } 128 | } 129 | if (Object.keys(keyMap).length === 0) { 130 | logger.mark('yunzai-meme keyMap资源本地不存在,正在远程拉取中') 131 | let keyMapRes = await fetch(`${baseUrl}/memes/static/keyMap.json`) 132 | if (keyMapRes.status === 200) { 133 | keyMap = await keyMapRes.json() 134 | fs.writeFileSync('data/karin-plugin-example/memes/keyMap.json', JSON.stringify(keyMap)) 135 | } 136 | } 137 | if (Object.keys(infos).length === 0 || Object.keys(keyMap).length === 0) { 138 | // 只能本地生成了 139 | let keysRes = await fetch(`${baseUrl}/memes/keys`) 140 | let keys = await keysRes.json() 141 | 142 | let keyMapTmp = {} 143 | let infosTmp = {} 144 | for (const key of keys) { 145 | let keyInfoRes = await fetch(`${baseUrl}/memes/${key}/info`) 146 | let info = await keyInfoRes.json() 147 | info.keywords.forEach(keyword => { 148 | keyMapTmp[keyword] = key 149 | }) 150 | infosTmp[key] = info 151 | } 152 | infos = infosTmp 153 | keyMap = keyMapTmp 154 | fs.writeFileSync('data/karin-plugin-example/memes/keyMap.json', JSON.stringify(keyMap)) 155 | fs.writeFileSync('data/karin-plugin-example/memes/infos.json', JSON.stringify(infos)) 156 | } 157 | let rules = [] 158 | Object.keys(keyMap).forEach(key => { 159 | let reg = forceSharp ? `^#${key}` : `^#?${key}` 160 | rules.push({ 161 | /** 命令正则匹配 */ 162 | reg, 163 | /** 执行方法 */ 164 | fnc: 'memes' 165 | }) 166 | }) 167 | this.rule = rules 168 | } 169 | 170 | async memesUpdate(e) { 171 | await e.reply('yunzai-memes更新中') 172 | if (fs.existsSync('data/karin-plugin-example/memes/infos.json')) { 173 | fs.unlinkSync('data/karin-plugin-example/memes/infos.json') 174 | } 175 | if (fs.existsSync('data/karin-plugin-example/memes/keyMap.json')) { 176 | fs.unlinkSync('data/karin-plugin-example/memes/keyMap.json') 177 | } 178 | try { 179 | await this.init() 180 | } catch (err) { 181 | await e.reply('更新失败:' + err.message) 182 | } 183 | await e.reply('更新完成') 184 | } 185 | 186 | async memesHelp(e) { 187 | e.reply('【memes列表】:查看支持的memes列表\n【{表情名称}】:memes列表中的表情名称,根据提供的文字或图片制作表情包\n【随机meme】:随机制作一些表情包\n【meme搜索+关键词】:搜索表情包关键词\n【{表情名称}+详情】:查看该表情所支持的参数') 188 | } 189 | 190 | async memesSearch(e) { 191 | let search = e.msg.replace(/^#?(meme(s)?|表情包)搜索/, '').trim() 192 | if (!search) { 193 | await e.reply('你要搜什么?') 194 | return true 195 | } 196 | let hits = Object.keys(keyMap).filter(k => k.indexOf(search) > -1) 197 | let result = '搜索结果' 198 | if (hits.length > 0) { 199 | for (let i = 0; i < hits.length; i++) { 200 | result += `\n${i + 1}. ${hits[i]}` 201 | } 202 | } else { 203 | result += '\n无' 204 | } 205 | await e.reply(result, { reply: e.isGroup }) 206 | } 207 | 208 | async memesList(e) { 209 | let resultFileLoc = 'temp/karin-plugin-example/memes/render_list1.jpg' 210 | if (fs.existsSync(resultFileLoc)) { 211 | await e.reply(segment.image(`file://${path.resolve(resultFileLoc)}`)) 212 | return true 213 | } 214 | let response = await fetch(baseUrl + '/memes/render_list', { 215 | method: 'POST' 216 | }) 217 | const resultBlob = await response.blob() 218 | const resultArrayBuffer = await resultBlob.arrayBuffer() 219 | const resultBuffer = Buffer.from(resultArrayBuffer) 220 | await fs.promises.writeFile(resultFileLoc, resultBuffer) 221 | await e.reply(segment.image(`file://${path.resolve(resultFileLoc)}`)) 222 | setTimeout(async () => { 223 | fs.unlink(resultFileLoc, () => { }) 224 | }, 3600) 225 | return true 226 | } 227 | 228 | async randomMemes(e) { 229 | let keys = Object.keys(infos).filter(key => infos[key].params_type.min_images === 1 && infos[key].params_type.min_texts === 0) 230 | let index = _.random(0, keys.length - 1, false) 231 | console.log(keys, index) 232 | e.msg = infos[keys[index]].keywords[0] 233 | return await this.memes(e) 234 | } 235 | 236 | /** 237 | * #memes 238 | * @param e oicq传递的事件参数e 239 | */ 240 | async memes(e) { 241 | // console.log(e) 242 | let msg = e.msg.replace('#', '') 243 | /** 244 | * 智能匹配最长关键词 245 | * @param {string} msg 用户消息 246 | * @param {Object} keyMap 关键词映射对象 247 | * @returns {string} 匹配到的最长关键词,如果没有匹配则返回null 248 | */ 249 | function findLongestMatchingKey(msg, keyMap) { 250 | // 找出所有匹配消息开头的关键词 251 | const matchingKeys = Object.keys(keyMap).filter(k => msg.startsWith(k)); 252 | if (matchingKeys.length === 0) { 253 | return null; // 没有匹配项 254 | } 255 | // 按关键词长度降序排序,选择最长的一个 256 | return matchingKeys.sort((a, b) => b.length - a.length)[0]; 257 | } 258 | // 替换原有的硬编码匹配逻辑 259 | let target = findLongestMatchingKey(msg, keyMap); 260 | 261 | let targetCode = keyMap[target] 262 | // let target = e.msg.replace(/^#?meme(s)?/, '') 263 | let text1 = _.trimStart(e.msg, '#').replace(target, '') 264 | if (text1.trim() === '详情' || text1.trim() === '帮助') { 265 | await e.reply(detail(targetCode)) 266 | return false 267 | } 268 | let [text, args = ''] = text1.split('#') 269 | let userInfos 270 | let formData = new FormData() 271 | let info = infos[targetCode] 272 | let fileLoc = [] 273 | if (info.params_type.max_images > 0) { 274 | // 可以有图,来从回复、发送和头像找图 275 | let imgUrls = [] 276 | if (e.reply_id) { 277 | // 优先从回复找图 278 | let reply = (await e.bot.GetMessage(e.contact, e.reply_id)).elements 279 | for (let val of reply) { 280 | if (val.type === 'image') { 281 | console.log(val) 282 | imgUrls.push(val.file) 283 | } 284 | } 285 | } else if (e.image.length) { 286 | // 一起发的图 287 | imgUrls.push(...e.image) 288 | } else if (e.at.length) { 289 | // 艾特的用户的头像 290 | imgUrls = e.at.map(qq => `https://q1.qlogo.cn/g?b=qq&s=160&nk=${qq}`) 291 | } 292 | if (!imgUrls || imgUrls.length === 0) { 293 | // 如果都没有,用发送者的头像 294 | imgUrls = [await getAvatar(e)] 295 | // 如果再不够,加上机器人的头像 296 | if (imgUrls.length < info.params_type.min_images) { 297 | imgUrls = [(await e.bot.getAvatarUrl())].concat(imgUrls) 298 | } 299 | } 300 | if (imgUrls.length < info.params_type.min_images && imgUrls.indexOf(await getAvatar(e)) === -1) { 301 | // 如果数量不够,补上发送者头像,且放到最前面 302 | let me = [await getAvatar(e)] 303 | let done = false 304 | if (targetCode === 'do' && masterProtectDo) { 305 | let masters = await getMasterQQ() 306 | if (imgUrls[0].startsWith('https://q1.qlogo.cn')) { 307 | let split = imgUrls[0].split('=') 308 | let targetQQ = split[split.length - 1] 309 | if (masters.map(q => q + '').indexOf(targetQQ) > -1) { 310 | imgUrls = imgUrls.concat(me) 311 | done = true 312 | } 313 | } 314 | } 315 | if (!done) { 316 | imgUrls = me.concat(imgUrls) 317 | } 318 | // imgUrls.push(`https://q1.qlogo.cn/g?b=qq&s=160&nk=${e.msg.sender.user_id}`) 319 | } 320 | imgUrls = imgUrls.slice(0, Math.min(info.params_type.max_images, imgUrls.length)) 321 | for (let i = 0; i < imgUrls.length; i++) { 322 | let imgUrl = imgUrls[i] 323 | const imageResponse = await fetch(imgUrl) 324 | const blob = await imageResponse.blob() 325 | const arrayBuffer = await blob.arrayBuffer() 326 | const buffer = Buffer.from(arrayBuffer) 327 | formData.append('images', new File([buffer], `avatar_${i}.jpg`, { type: 'image/jpeg' })) 328 | } 329 | } 330 | if (text && info.params_type.max_texts === 0) { 331 | return false 332 | } 333 | if (!text && info.params_type.min_texts > 0) { 334 | if (e.at.length) { 335 | text = e.elements.filter(m => m.type === 'at')[0].name 336 | } else { 337 | text = e.sender.card || e.sender.nick 338 | } 339 | } 340 | let texts = text.split('/', info.params_type.max_texts) 341 | if (texts.length < info.params_type.min_texts) { 342 | await e.reply(`字不够!要至少${info.params_type.min_texts}个用/隔开!`, { reply: true }) 343 | return true 344 | } 345 | texts.forEach(t => { 346 | formData.append('texts', t) 347 | }) 348 | if (info.params_type.max_texts > 0 && formData.getAll('texts').length === 0) { 349 | if (formData.getAll('texts').length < info.params_type.max_texts) { 350 | if (e.at.length) { 351 | formData.append('texts', e.elements.filter(m => m.type === 'at')[0].name) 352 | } else { 353 | formData.append('texts', e.sender.card || e.sender.nick) 354 | } 355 | } 356 | } 357 | if (e.at.length) { 358 | userInfos = await Promise.all(e.at.map(async (ui) => { 359 | let user = await e.bot.GetGroupMemberInfo(e.group_id, ui) 360 | return { text: user.card || user.nick, gender: user.sex } 361 | })) 362 | } 363 | if (!userInfos) { 364 | userInfos = [{ text: e.sender.card || e.sender.nick, gender: e.sender.sex }] 365 | } 366 | args = handleArgs(targetCode, args, userInfos) 367 | if (args) { 368 | formData.set('args', args) 369 | } 370 | const images = formData.getAll('images') 371 | if (checkFileSize(images)) { 372 | return this.e.reply(`文件大小超出限制,最多支持${maxFileSize}MB`) 373 | } 374 | console.log('input', { target, targetCode, images, texts: formData.getAll('texts'), args: formData.getAll('args') }) 375 | let response = await fetch(baseUrl + '/memes/' + targetCode + '/', { 376 | method: 'POST', 377 | body: formData 378 | // headers: { 379 | // 'Content-Type': 'multipart/form-data' 380 | // } 381 | }) 382 | // console.log(response.status) 383 | if (response.status > 299) { 384 | let error = await response.text() 385 | console.error(error) 386 | await e.reply(error, { reply: true }) 387 | return true 388 | } 389 | const resultBlob = await response.blob() 390 | const resultArrayBuffer = await resultBlob.arrayBuffer() 391 | const resultBase64 = Buffer.from(resultArrayBuffer).toString('base64') 392 | await e.reply(segment.image("base64://" + resultBase64), { reply }) 393 | } 394 | } 395 | 396 | function handleArgs(key, args, userInfos) { 397 | if (!args) { 398 | args = '' 399 | } 400 | 401 | let argsObj = {} 402 | 403 | // 检查是否有参数类型定义 404 | if (infos[key]?.params_type?.args_type) { 405 | const argsType = infos[key].params_type.args_type; 406 | const argsModel = argsType.args_model; 407 | const parserOptions = argsType.parser_options || []; 408 | 409 | // 处理枚举类型参数 410 | for (const prop in argsModel.properties) { 411 | if (prop === 'user_infos') continue; // 用户信息单独处理 412 | 413 | const propInfo = argsModel.properties[prop]; 414 | 415 | // 查找相关的parser选项 416 | const relatedOptions = parserOptions.filter(opt => 417 | opt.dest === prop || 418 | (opt.args && opt.args.some(arg => arg.name === prop)) 419 | ); 420 | 421 | if (propInfo.enum && relatedOptions.length > 0) { 422 | // 为枚举类型创建映射表 423 | const valueMap = {}; 424 | 425 | // 从parser options中提取名称映射 426 | relatedOptions.forEach(opt => { 427 | if (opt.action?.type === 0) { 428 | opt.names.forEach(name => { 429 | // 处理非选项形式(如"左", "右")和选项形式(如"--right") 430 | if (!/^-/.test(name)) { 431 | valueMap[name] = opt.action.value; 432 | } else if (name.startsWith('--')) { 433 | // 处理选项形式,去掉前缀-- 434 | const simpleName = name.substring(2); 435 | valueMap[simpleName] = opt.action.value; 436 | } 437 | }); 438 | } 439 | }); 440 | 441 | // 设置默认值 442 | const trimmedArg = args.trim(); 443 | argsObj[prop] = valueMap[trimmedArg] || propInfo.default; 444 | } 445 | // 处理数字类型参数 446 | else if (propInfo.type === 'integer' || propInfo.type === 'number') { 447 | const trimmedArg = args.trim(); 448 | // 尝试将参数解析为数字 449 | if (/^\d+$/.test(trimmedArg)) { 450 | const numValue = parseInt(trimmedArg); 451 | argsObj[prop] = numValue; 452 | } 453 | } 454 | } 455 | } 456 | 457 | argsObj.user_infos = userInfos.map(u => { 458 | return { 459 | name: _.trim(u.text, '@'), 460 | gender: u.gender || 'unknown' 461 | } 462 | }) 463 | return JSON.stringify(argsObj) 464 | } 465 | 466 | const detail = code => { 467 | let d = infos[code] 468 | let keywords = d.keywords.join('、') 469 | let ins = `【代码】${d.key}\n【名称】${keywords}\n【最大图片数量】${d.params_type.max_images}\n【最小图片数量】${d.params_type.min_images}\n【最大文本数量】${d.params_type.max_texts}\n【最小文本数量】${d.params_type.min_texts}\n【默认文本】${d.params_type.default_texts.join('/')}\n` 470 | 471 | // 检查是否有参数类型定义 472 | if (d.params_type.args_type?.parser_options?.length > 0) { 473 | let supportArgs = generateSupportArgsText(d); 474 | ins += `【支持参数】${supportArgs}`; 475 | } 476 | 477 | return ins; 478 | }; 479 | 480 | // 辅助函数:根据infos生成参数说明文本 481 | function generateSupportArgsText(info) { 482 | try { 483 | const argsType = info.params_type.args_type; 484 | const props = argsType.args_model.properties; 485 | const options = argsType.parser_options; 486 | 487 | // 寻找主要参数及其描述 488 | let mainParam = ''; 489 | let description = ''; 490 | 491 | for (const prop in props) { 492 | if (prop !== 'user_infos') { 493 | const propInfo = props[prop]; 494 | mainParam = prop; 495 | 496 | // 寻找参数说明 497 | const option = options.find(opt => 498 | opt.dest === prop || 499 | (opt.args && opt.args.some(arg => arg.name === prop)) 500 | ); 501 | 502 | if (option?.help_text) { 503 | description = option.help_text; 504 | } else if (propInfo.description) { 505 | description = propInfo.description; 506 | } 507 | 508 | // 如果是枚举类型,列出可能的值 509 | if (propInfo.enum) { 510 | // 收集中文参数名称(非选项形式) 511 | const chineseNames = options 512 | .filter(opt => opt.action?.type === 0 && opt.action?.value && opt.dest === prop) 513 | .flatMap(opt => opt.names.filter(name => !/^-/.test(name))); 514 | 515 | // 收集英文参数名称(从选项形式提取) 516 | const englishNames = options 517 | .filter(opt => opt.action?.type === 0 && opt.action?.value && opt.dest === prop) 518 | .flatMap(opt => opt.names 519 | .filter(name => name.startsWith('--')) 520 | .map(name => name.substring(2)) 521 | ); 522 | 523 | // 合并中文和英文参数名称 524 | const valueNames = [...new Set([...chineseNames, ...englishNames])]; 525 | 526 | if (valueNames.length > 0) { 527 | const valuesText = valueNames.join('、'); 528 | // 优先使用中文名称作为示例 529 | const exampleName = chineseNames.length > 0 ? chineseNames[0] : valueNames[0]; 530 | return `${description || prop},可选值:${valuesText}。如#${exampleName}`; 531 | } 532 | // 处理数字类型 533 | else if (propInfo.type === 'integer' || propInfo.type === 'number') { 534 | // 添加数字范围说明(如果有) 535 | let rangeText = ''; 536 | if (propInfo.minimum !== undefined && propInfo.maximum !== undefined) { 537 | rangeText = `范围为${propInfo.minimum}~${propInfo.maximum}`; 538 | } else if (propInfo.description && propInfo.description.includes('范围')) { 539 | rangeText = propInfo.description; 540 | } 541 | 542 | return `${description || prop}${rangeText ? ',' + rangeText : ''}。如#1`; 543 | } 544 | } 545 | 546 | break; 547 | } 548 | } 549 | 550 | return description || `${mainParam}参数`; 551 | 552 | } catch (e) { 553 | console.error(`生成参数说明出错: ${e.message}`); 554 | return '支持额外参数'; 555 | } 556 | } 557 | 558 | // 最大支持的文件大小(字节) 559 | const maxFileSizeByte = maxFileSize * 1024 * 1024 560 | 561 | // 如果有任意一个文件大于 maxSize,则返回true 562 | function checkFileSize(files) { 563 | let fileList = Array.isArray(files) ? files : [files] 564 | fileList = fileList.filter(file => !!(file?.size)) 565 | if (fileList.length === 0) { 566 | return false 567 | } 568 | return fileList.some(file => file.size >= maxFileSizeByte) 569 | } 570 | 571 | async function getMasterQQ() { 572 | return config.master 573 | } 574 | 575 | async function getAvatar(e, userId = e.user_id) { 576 | if (typeof e.bot.getAvatarUrl === 'function') { 577 | return await e.bot.getAvatarUrl(userId) 578 | } 579 | return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${userId}` 580 | } 581 | 582 | await new memes().init() 583 | 584 | export { memes } 585 | -------------------------------------------------------------------------------- /meme.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../lib/plugins/plugin.js' 2 | import fetch, { File, FormData } from 'node-fetch' 3 | import fs from 'fs' 4 | import path from 'node:path' 5 | import _ from 'lodash' 6 | 7 | if (!global.segment) { 8 | global.segment = (await import('oicq')).segment 9 | } 10 | const baseUrl = 'https://memes.ikechan8370.com' 11 | /** 12 | * 机器人发表情是否引用回复用户 13 | * @type {boolean} 14 | */ 15 | const reply = true 16 | /** 17 | * 是否强制使用#触发命令 18 | */ 19 | const forceSharp = false 20 | /** 21 | * 主人保护,撅主人时会被反撅 (暂时只支持QQ) 22 | * @type {boolean} 23 | */ 24 | const masterProtectDo = true 25 | /** 26 | * 用户输入的图片,最大支持的文件大小,单位为MB 27 | * @type {number} 28 | */ 29 | const maxFileSize = 10 30 | 31 | let keyMap = {} 32 | 33 | let infos = {} 34 | 35 | /** 36 | * 主人保护list 如['lash','do','beat_up','little_do'] 37 | */ 38 | let protectList = ['lash', 'do', 'beat_up', 'little_do'] 39 | 40 | export class memes extends plugin { 41 | constructor() { 42 | let option = { 43 | /** 功能名称 */ 44 | name: '表情包', 45 | /** 功能描述 */ 46 | dsc: '表情包制作', 47 | /** https://oicqjs.github.io/oicq/#events */ 48 | event: 'message', 49 | /** 优先级,数字越小等级越高 */ 50 | priority: 5000, 51 | rule: [ 52 | { 53 | /** 命令正则匹配 */ 54 | reg: '^(#)?(meme(s)?|表情包)列表$', 55 | /** 执行方法 */ 56 | fnc: 'memesList' 57 | }, 58 | { 59 | /** 命令正则匹配 */ 60 | reg: '^#?随机(meme(s)?|表情包)', 61 | /** 执行方法 */ 62 | fnc: 'randomMemes' 63 | }, 64 | { 65 | /** 命令正则匹配 */ 66 | reg: '^#?(meme(s)?|表情包)帮助', 67 | /** 执行方法 */ 68 | fnc: 'memesHelp' 69 | }, 70 | { 71 | /** 命令正则匹配 */ 72 | reg: '^#?(meme(s)?|表情包)搜索', 73 | /** 执行方法 */ 74 | fnc: 'memesSearch' 75 | }, 76 | { 77 | /** 命令正则匹配 */ 78 | reg: '^#?(meme(s)?|表情包)更新', 79 | /** 执行方法 */ 80 | fnc: 'memesUpdate' 81 | } 82 | 83 | ] 84 | 85 | } 86 | Object.keys(keyMap).forEach(key => { 87 | let reg = forceSharp ? `^#${key}` : `^#?${key}` 88 | option.rule.push({ 89 | /** 命令正则匹配 */ 90 | reg, 91 | /** 执行方法 */ 92 | fnc: 'memes' 93 | }) 94 | }) 95 | 96 | super(option) 97 | 98 | // generated by ChatGPT 99 | function generateCronExpression() { 100 | // 生成每天的半夜2-4点之间的小时值(随机选择) 101 | const hour = Math.floor(Math.random() * 3) + 2 102 | 103 | // 生成每小时的随机分钟值(0到59之间的随机数) 104 | const minute = Math.floor(Math.random() * 60) 105 | 106 | // 构建 cron 表达式 107 | return `${minute} ${hour} * * *` 108 | } 109 | 110 | this.task = { 111 | // 每天的凌晨3点执行 112 | cron: generateCronExpression(), 113 | name: 'memes自动更新任务', 114 | fnc: this.init.bind(this) 115 | } 116 | } 117 | 118 | async init() { 119 | mkdirs('data/memes') 120 | keyMap = {} 121 | infos = {} 122 | if (fs.existsSync('data/memes/infos.json')) { 123 | infos = fs.readFileSync('data/memes/infos.json') 124 | infos = JSON.parse(infos) 125 | } 126 | if (fs.existsSync('data/memes/keyMap.json')) { 127 | keyMap = fs.readFileSync('data/memes/keyMap.json') 128 | keyMap = JSON.parse(keyMap) 129 | } 130 | if (Object.keys(infos).length === 0) { 131 | logger.mark('yunzai-meme infos资源本地不存在,正在远程拉取中') 132 | let infosRes = await fetch(`${baseUrl}/memes/static/infos.json`) 133 | if (infosRes.status === 200) { 134 | infos = await infosRes.json() 135 | fs.writeFileSync('data/memes/infos.json', JSON.stringify(infos)) 136 | } 137 | } 138 | if (Object.keys(keyMap).length === 0) { 139 | logger.mark('yunzai-meme keyMap资源本地不存在,正在远程拉取中') 140 | let keyMapRes = await fetch(`${baseUrl}/memes/static/keyMap.json`) 141 | if (keyMapRes.status === 200) { 142 | keyMap = await keyMapRes.json() 143 | fs.writeFileSync('data/memes/keyMap.json', JSON.stringify(keyMap)) 144 | } 145 | } 146 | if (Object.keys(infos).length === 0 || Object.keys(keyMap).length === 0) { 147 | // 只能本地生成了 148 | let keysRes = await fetch(`${baseUrl}/memes/keys`) 149 | let keys = await keysRes.json() 150 | 151 | let keyMapTmp = {} 152 | let infosTmp = {} 153 | for (const key of keys) { 154 | let keyInfoRes = await fetch(`${baseUrl}/memes/${key}/info`) 155 | let info = await keyInfoRes.json() 156 | info.keywords.forEach(keyword => { 157 | keyMapTmp[keyword] = key 158 | }) 159 | infosTmp[key] = info 160 | } 161 | infos = infosTmp 162 | keyMap = keyMapTmp 163 | fs.writeFileSync('data/memes/keyMap.json', JSON.stringify(keyMap)) 164 | fs.writeFileSync('data/memes/infos.json', JSON.stringify(infos)) 165 | } 166 | let rules = [] 167 | Object.keys(keyMap).forEach(key => { 168 | let reg = forceSharp ? `^#${key}` : `^#?${key}` 169 | rules.push({ 170 | /** 命令正则匹配 */ 171 | reg, 172 | /** 执行方法 */ 173 | fnc: 'memes' 174 | }) 175 | }) 176 | this.rule = rules 177 | } 178 | 179 | async memesUpdate(e) { 180 | await e.reply('yunzai-memes更新中') 181 | if (fs.existsSync('data/memes/infos.json')) { 182 | fs.unlinkSync('data/memes/infos.json') 183 | } 184 | if (fs.existsSync('data/memes/keyMap.json')) { 185 | fs.unlinkSync('data/memes/keyMap.json') 186 | } 187 | try { 188 | await this.init() 189 | } catch (err) { 190 | await e.reply('更新失败:' + err.message) 191 | } 192 | await e.reply('更新完成') 193 | } 194 | 195 | async memesHelp(e) { 196 | e.reply('【memes列表】:查看支持的memes列表\n【{表情名称}】:memes列表中的表情名称,根据提供的文字或图片制作表情包\n【随机meme】:随机制作一些表情包\n【meme搜索+关键词】:搜索表情包关键词\n【{表情名称}+详情】:查看该表情所支持的参数') 197 | } 198 | 199 | async memesSearch(e) { 200 | let search = e.msg.replace(/^#?(meme(s)?|表情包)搜索/, '').trim() 201 | if (!search) { 202 | await e.reply('你要搜什么?') 203 | return true 204 | } 205 | let hits = Object.keys(keyMap).filter(k => k.indexOf(search) > -1) 206 | let result = '搜索结果' 207 | if (hits.length > 0) { 208 | for (let i = 0; i < hits.length; i++) { 209 | result += `\n${i + 1}. ${hits[i]}` 210 | } 211 | } else { 212 | result += '\n无' 213 | } 214 | await e.reply(result, e.isGroup) 215 | } 216 | 217 | async memesList(e) { 218 | let resultFileLoc = 'data/memes/render_list1.jpg' 219 | if (fs.existsSync(resultFileLoc)) { 220 | await e.reply(segment.image(`${process.cwd()}/${resultFileLoc}`)) 221 | return true 222 | } 223 | let response = await fetch(baseUrl + '/memes/render_list', { 224 | method: 'POST' 225 | }) 226 | const resultBlob = await response.blob() 227 | const resultArrayBuffer = await resultBlob.arrayBuffer() 228 | const resultBuffer = Buffer.from(resultArrayBuffer) 229 | await fs.writeFileSync(resultFileLoc, resultBuffer) 230 | await e.reply(segment.image(`${process.cwd()}/${resultFileLoc}`)) 231 | setTimeout(async () => { 232 | await fs.unlinkSync(resultFileLoc) 233 | }, 3600) 234 | return true 235 | } 236 | 237 | /** 238 | * @description: 239 | * @param {*} e 240 | * @return {*} 241 | */ 242 | async randomMemes(e) { 243 | let keys = Object.keys(infos).filter(key => infos[key].params_type.min_images === 1 && infos[key].params_type.min_texts === 0) 244 | let index = _.random(0, keys.length - 1, false) 245 | logger.debug(keys, index) 246 | e.msg = infos[keys[index]].keywords[0] 247 | return await this.memes(e) 248 | } 249 | 250 | /** 251 | * #memes 252 | * @param e oicq传递的事件参数e 253 | */ 254 | async memes(e) { 255 | // console.log(e) 256 | let msg = e.msg.replace('#', '') 257 | /** 258 | * 智能匹配最长关键词 259 | * @param {string} msg 用户消息 260 | * @param {Object} keyMap 关键词映射对象 261 | * @returns {string} 匹配到的最长关键词,如果没有匹配则返回null 262 | */ 263 | function findLongestMatchingKey(msg, keyMap) { 264 | // 找出所有匹配消息开头的关键词 265 | const matchingKeys = Object.keys(keyMap).filter(k => msg.startsWith(k)); 266 | if (matchingKeys.length === 0) { 267 | return null; // 没有匹配项 268 | } 269 | // 按关键词长度降序排序,选择最长的一个 270 | return matchingKeys.sort((a, b) => b.length - a.length)[0]; 271 | } 272 | // 替换原有的硬编码匹配逻辑 273 | let target = findLongestMatchingKey(msg, keyMap); 274 | 275 | let targetCode = keyMap[target] 276 | // let target = e.msg.replace(/^#?meme(s)?/, '') 277 | let text1 = _.trimStart(e.msg, '#').replace(target, '') 278 | if (text1.trim() === '详情' || text1.trim() === '帮助') { 279 | await e.reply(detail(targetCode)) 280 | return true 281 | } 282 | let [text, args = ''] = text1.split('#') 283 | let userInfos 284 | let formData = new FormData() 285 | let info = infos[targetCode] 286 | let fileLoc 287 | if (info.params_type.max_images > 0) { 288 | // 可以有图,来从回复、发送和头像找图 289 | let imgUrls = [] 290 | if (e.source || e.reply_id) { 291 | // 优先从回复找图 292 | let reply 293 | if (this.e.getReply) { 294 | reply = await this.e.getReply() 295 | } else if (this.e.source) { 296 | if (this.e.group?.getChatHistory) 297 | reply = (await this.e.group.getChatHistory(this.e.source.seq, 1)).pop() 298 | else if (this.e.friend?.getChatHistory) 299 | reply = (await this.e.friend.getChatHistory(this.e.source.time, 1)).pop() 300 | } 301 | if (reply?.message) { 302 | for (let val of reply.message) { 303 | if (val.type === 'image') { 304 | console.log(val) 305 | imgUrls.push(val.url) 306 | } 307 | } 308 | } 309 | } else if (e.img) { 310 | // 一起发的图 311 | imgUrls.push(...e.img) 312 | } else if (e.message.filter(m => m.type === 'at').length > 0) { 313 | // 艾特的用户的头像 314 | let ats = e.message.filter(m => m.type === 'at') 315 | imgUrls = ats.map(at => at.qq).map(qq => `https://q1.qlogo.cn/g?b=qq&s=160&nk=${qq}`) 316 | } 317 | if (!imgUrls || imgUrls.length === 0) { 318 | // 如果都没有,用发送者的头像 319 | imgUrls = [await getAvatar(e)] 320 | } 321 | if (imgUrls.length < info.params_type.min_images && imgUrls.indexOf(await getAvatar(e)) === -1) { 322 | // 如果数量不够,补上发送者头像,且放到最前面 323 | let me = [await getAvatar(e)] 324 | 325 | imgUrls = me.concat(imgUrls) 326 | // imgUrls.push(`https://q1.qlogo.cn/g?b=qq&s=160&nk=${e.msg.sender.user_id}`) 327 | } 328 | logger.debug('imgUrls:', imgUrls) 329 | if (protectList.includes(targetCode) && masterProtectDo) { 330 | let me = [await getAvatar(e)] 331 | let masters = await getMasterQQ() 332 | // 有些meme只需要传一张图,此时如果targetQQ是主人,那meme的人就是他自己 333 | if (imgUrls.length === 1) { 334 | if (imgUrls[0].startsWith('https://q1.qlogo.cn')) { 335 | let split = imgUrls[0].split('=') 336 | let targetQQ = split[split.length - 1] 337 | if (masters.map(q => q + '').indexOf(targetQQ) > -1) { 338 | imgUrls[0] = me 339 | } 340 | } 341 | } else { 342 | if (imgUrls[1].startsWith('https://q1.qlogo.cn')) { 343 | let split = imgUrls[1].split('=') 344 | let targetQQ = split[split.length - 1] 345 | if (masters.map(q => q + '').indexOf(targetQQ) > -1) { 346 | imgUrls = [imgUrls[1]].concat(me) 347 | } 348 | } 349 | } 350 | } 351 | imgUrls = imgUrls.slice(0, Math.min(info.params_type.max_images, imgUrls.length)) 352 | for (let i = 0; i < imgUrls.length; i++) { 353 | let imgUrl = imgUrls[i] 354 | const imageResponse = await fetch(imgUrl) 355 | const blob = await imageResponse.blob() 356 | const arrayBuffer = await blob.arrayBuffer() 357 | const buffer = Buffer.from(arrayBuffer) 358 | formData.append('images', new File([buffer], `avatar_${i}.jpg`, { type: 'image/jpeg' })) 359 | } 360 | } 361 | if (text && info.params_type.max_texts === 0) { 362 | return false 363 | } 364 | if (!text && info.params_type.min_texts > 0) { 365 | if (e.message.filter(m => m.type === 'at').length > 0) { 366 | text = _.trim(e.message.filter(m => m.type === 'at')[0].text, '@') 367 | } else { 368 | text = e.sender.card || e.sender.nickname 369 | } 370 | } 371 | let texts = text.split('/', info.params_type.max_texts) 372 | if (texts.length < info.params_type.min_texts) { 373 | await e.reply(`字不够!要至少${info.params_type.min_texts}个用/隔开!`, true) 374 | return true 375 | } 376 | texts.forEach(t => { 377 | formData.append('texts', t) 378 | }) 379 | if (info.params_type.max_texts > 0 && formData.getAll('texts').length === 0) { 380 | if (formData.getAll('texts').length < info.params_type.max_texts) { 381 | if (e.message.filter(m => m.type === 'at').length > 0) { 382 | formData.append('texts', _.trim(e.message.filter(m => m.type === 'at')[0].text, '@')) 383 | } else { 384 | formData.append('texts', e.sender.card || e.sender.nickname) 385 | } 386 | } 387 | } 388 | if (e.message.filter(m => m.type === 'at').length > 0) { 389 | userInfos = e.message.filter(m => m.type === 'at') 390 | let mm = await e.group.getMemberMap() 391 | userInfos.forEach(ui => { 392 | let user = mm.get(ui.qq) 393 | if (user) { 394 | ui.gender = user.sex 395 | ui.text = user.card || user.nickname 396 | } 397 | }) 398 | } 399 | if (!userInfos) { 400 | userInfos = [{ text: e.sender.card || e.sender.nickname, gender: e.sender.sex }] 401 | } 402 | args = handleArgs(targetCode, args, userInfos) 403 | if (args) { 404 | formData.set('args', args) 405 | } 406 | const images = formData.getAll('images') 407 | if (checkFileSize(images)) { 408 | return this.e.reply(`文件大小超出限制,最多支持${maxFileSize}MB`) 409 | } 410 | console.log('input', { target, targetCode, images, texts: formData.getAll('texts'), args: formData.getAll('args') }) 411 | let response = await fetch(baseUrl + '/memes/' + targetCode + '/', { 412 | method: 'POST', 413 | body: formData 414 | // headers: { 415 | // 'Content-Type': 'multipart/form-data' 416 | // } 417 | }) 418 | // console.log(response.status) 419 | if (response.status > 299) { 420 | let error = await response.text() 421 | console.error(error) 422 | await e.reply(error, true) 423 | return true 424 | } 425 | const resultBlob = await response.blob() 426 | const resultArrayBuffer = await resultBlob.arrayBuffer() 427 | const resultBase64 = Buffer.from(resultArrayBuffer).toString('base64') 428 | await e.reply(segment.image("base64://" + resultBase64), reply) 429 | } 430 | } 431 | 432 | function handleArgs(key, args, userInfos) { 433 | if (!args) { 434 | args = '' 435 | } 436 | 437 | let argsObj = {} 438 | 439 | // 检查是否有参数类型定义 440 | if (infos[key]?.params_type?.args_type) { 441 | const argsType = infos[key].params_type.args_type; 442 | const argsModel = argsType.args_model; 443 | const parserOptions = argsType.parser_options || []; 444 | 445 | // 处理枚举类型参数 446 | for (const prop in argsModel.properties) { 447 | if (prop === 'user_infos') continue; // 用户信息单独处理 448 | 449 | const propInfo = argsModel.properties[prop]; 450 | 451 | // 查找相关的parser选项 452 | const relatedOptions = parserOptions.filter(opt => 453 | opt.dest === prop || 454 | (opt.args && opt.args.some(arg => arg.name === prop)) 455 | ); 456 | 457 | if (propInfo.enum && relatedOptions.length > 0) { 458 | // 为枚举类型创建映射表 459 | const valueMap = {}; 460 | 461 | // 从parser options中提取名称映射 462 | relatedOptions.forEach(opt => { 463 | if (opt.action?.type === 0) { 464 | opt.names.forEach(name => { 465 | // 处理非选项形式(如"左", "右")和选项形式(如"--right") 466 | if (!/^-/.test(name)) { 467 | valueMap[name] = opt.action.value; 468 | } else if (name.startsWith('--')) { 469 | // 处理选项形式,去掉前缀-- 470 | const simpleName = name.substring(2); 471 | valueMap[simpleName] = opt.action.value; 472 | } 473 | }); 474 | } 475 | }); 476 | 477 | // 设置默认值 478 | const trimmedArg = args.trim(); 479 | argsObj[prop] = valueMap[trimmedArg] || propInfo.default; 480 | } 481 | // 处理数字类型参数 482 | else if (propInfo.type === 'integer' || propInfo.type === 'number') { 483 | const trimmedArg = args.trim(); 484 | // 尝试将参数解析为数字 485 | if (/^\d+$/.test(trimmedArg)) { 486 | const numValue = parseInt(trimmedArg); 487 | argsObj[prop] = numValue; 488 | } 489 | } 490 | } 491 | } 492 | 493 | argsObj.user_infos = userInfos.map(u => { 494 | return { 495 | name: _.trim(u.text, '@'), 496 | gender: u.gender || 'unknown' 497 | } 498 | }) 499 | 500 | return JSON.stringify(argsObj); 501 | } 502 | 503 | const detail = code => { 504 | let d = infos[code]; 505 | let keywords = d.keywords.join('、'); 506 | let ins = `【代码】${d.key}\n【名称】${keywords}\n【最大图片数量】${d.params_type.max_images}\n【最小图片数量】${d.params_type.min_images}\n【最大文本数量】${d.params_type.max_texts}\n【最小文本数量】${d.params_type.min_texts}\n【默认文本】${d.params_type.default_texts.join('/')}\n`; 507 | 508 | // 检查是否有参数类型定义 509 | if (d.params_type.args_type?.parser_options?.length > 0) { 510 | let supportArgs = generateSupportArgsText(d); 511 | ins += `【支持参数】${supportArgs}`; 512 | } 513 | 514 | return ins; 515 | }; 516 | 517 | // 辅助函数:根据infos生成参数说明文本 518 | function generateSupportArgsText(info) { 519 | try { 520 | const argsType = info.params_type.args_type; 521 | const props = argsType.args_model.properties; 522 | const options = argsType.parser_options; 523 | 524 | // 寻找主要参数及其描述 525 | let mainParam = ''; 526 | let description = ''; 527 | 528 | for (const prop in props) { 529 | if (prop !== 'user_infos') { 530 | const propInfo = props[prop]; 531 | mainParam = prop; 532 | 533 | // 寻找参数说明 534 | const option = options.find(opt => 535 | opt.dest === prop || 536 | (opt.args && opt.args.some(arg => arg.name === prop)) 537 | ); 538 | 539 | if (option?.help_text) { 540 | description = option.help_text; 541 | } else if (propInfo.description) { 542 | description = propInfo.description; 543 | } 544 | 545 | // 如果是枚举类型,列出可能的值 546 | if (propInfo.enum) { 547 | // 收集中文参数名称(非选项形式) 548 | const chineseNames = options 549 | .filter(opt => opt.action?.type === 0 && opt.action?.value && opt.dest === prop) 550 | .flatMap(opt => opt.names.filter(name => !/^-/.test(name))); 551 | 552 | // 收集英文参数名称(从选项形式提取) 553 | const englishNames = options 554 | .filter(opt => opt.action?.type === 0 && opt.action?.value && opt.dest === prop) 555 | .flatMap(opt => opt.names 556 | .filter(name => name.startsWith('--')) 557 | .map(name => name.substring(2)) 558 | ); 559 | 560 | // 合并中文和英文参数名称 561 | const valueNames = [...new Set([...chineseNames, ...englishNames])]; 562 | 563 | if (valueNames.length > 0) { 564 | const valuesText = valueNames.join('、'); 565 | // 优先使用中文名称作为示例 566 | const exampleName = chineseNames.length > 0 ? chineseNames[0] : valueNames[0]; 567 | return `${description || prop},可选值:${valuesText}。如#${exampleName}`; 568 | } 569 | } 570 | // 处理数字类型 571 | else if (propInfo.type === 'integer' || propInfo.type === 'number') { 572 | // 添加数字范围说明(如果有) 573 | let rangeText = ''; 574 | if (propInfo.minimum !== undefined && propInfo.maximum !== undefined) { 575 | rangeText = `范围为${propInfo.minimum}~${propInfo.maximum}`; 576 | } else if (propInfo.description && propInfo.description.includes('范围')) { 577 | rangeText = propInfo.description; 578 | } 579 | 580 | return `${description || prop}${rangeText ? ',' + rangeText : ''}。如#1`; 581 | } 582 | 583 | break; 584 | } 585 | } 586 | 587 | return description || `${mainParam}参数`; 588 | 589 | } catch (e) { 590 | console.error(`生成参数说明出错: ${e.message}`); 591 | return '支持额外参数'; 592 | } 593 | } 594 | 595 | // 最大支持的文件大小(字节) 596 | const maxFileSizeByte = maxFileSize * 1024 * 1024 597 | 598 | // 如果有任意一个文件大于 maxSize,则返回true 599 | function checkFileSize(files) { 600 | let fileList = Array.isArray(files) ? files : [files] 601 | fileList = fileList.filter(file => !!(file?.size)) 602 | if (fileList.length === 0) { 603 | return false 604 | } 605 | return fileList.some(file => file.size >= maxFileSizeByte) 606 | } 607 | 608 | function mkdirs(dirname) { 609 | if (fs.existsSync(dirname)) { 610 | return true 611 | } else { 612 | if (mkdirs(path.dirname(dirname))) { 613 | fs.mkdirSync(dirname) 614 | return true 615 | } 616 | } 617 | } 618 | 619 | async function getMasterQQ () { 620 | return (await import('../../lib/config/config.js')).default.masterQQ 621 | } 622 | 623 | async function getAvatar(e, userId = e.sender.user_id) { 624 | if (typeof e.getAvatarUrl === 'function') { 625 | return await e.getAvatarUrl(0) 626 | } 627 | return `https://q1.qlogo.cn/g?b=qq&s=0&nk=${userId}` 628 | } 629 | --------------------------------------------------------------------------------