├── 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 | 
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 |
--------------------------------------------------------------------------------