├── .deepsource.toml ├── .gitignore ├── LICENSE ├── README.md ├── apps ├── Blend.js ├── Config.js ├── Connect.js ├── Custom.js ├── Describe.js ├── FaceSwap.js ├── Fast.js ├── Imagine.js ├── Info.js ├── Main.js ├── Pan.js ├── Param.js ├── Relax.js ├── Reroll.js ├── Setting.js ├── Shorten.js ├── Update.js ├── Upscale.js ├── Variation.js ├── Vary.js └── Zoomout.js ├── components ├── BannedWords.js ├── Button.js ├── Config.js ├── Proxy.js └── Version.js ├── config ├── config │ └── .keep └── config_default.yaml ├── guoba.support.js ├── index.js ├── model ├── init.js └── path.js ├── package.json ├── resources ├── common │ ├── common.css │ ├── font │ │ ├── HYWH-65W.woff │ │ ├── NZBZ.woff │ │ └── tttgbnumber.woff │ └── layout │ │ ├── default.html │ │ └── elem.html ├── helpTemp │ ├── bg.jpg │ ├── helpTemp.css │ ├── helpTemp.html │ ├── icon.png │ └── main.png ├── listTemp │ ├── listTemp.css │ └── listTemp.html └── readme │ └── girl.png └── utils ├── logs.js └── translate.js /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | test_patterns = ["/**/*.js"] 4 | 5 | [[analyzers]] 6 | name = "javascript" 7 | 8 | [analyzers.meta] 9 | environment = ["nodejs"] 10 | 11 | [[transformers]] 12 | name = "prettier" 13 | 14 | [[transformers]] 15 | name = "standardjs" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | config/config/*.yaml 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 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 Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![mj-plugin](https://socialify.git.ci/CikeyQi/mj-plugin/image?description=1&font=Raleway&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto) 2 | 3 | 4 | 5 | # MJ-PLUGIN🥥 6 | 7 | - 一个适用于 [Yunzai 系列机器人框架](https://github.com/yhArcadia/Yunzai-Bot-plugins-index) 的 AI 绘图插件,让你在输入框中拥有便捷的 AI 绘画体验 8 | 9 | - 使用强大的 [Midjourney](https://www.midjourney.com) 作为后端,**付费**使用,生成的质量更高,理解能力更好,只需一句话即可生成高质量图片 10 | 11 | - **使用中遇到问题请加 QQ 群咨询:[707331865](https://qm.qq.com/q/TXTIS9KhO2)** 12 | 13 | > [!TIP] 14 | > 继 [AP-PLUGIN](https://github.com/AiPreface/ap-plugin) 的下一个 AI 绘图插件,这时 [渔火](https://github.com/yhArcadia) 就已经跑了。在我独自开发这个插件时,[二枣子](https://github.com/erzaozi) 加入了开发队伍,为我提供了很多帮助,帮我处理了繁琐的部分,让我专注于核心代码开发。 15 | 16 | ## 安装插件 17 | 18 | #### 1. 克隆仓库 19 | 20 | ``` 21 | git clone https://github.com/CikeyQi/mj-plugin.git ./plugins/mj-plugin 22 | ``` 23 | 24 | > [!NOTE] 25 | > 如果你的网络环境较差,无法连接到 Github,可以使用 [GitHub Proxy](https://mirror.ghproxy.com/) 提供的文件代理加速下载服务 26 | > 27 | > ``` 28 | > git clone https://mirror.ghproxy.com/https://github.com/CikeyQi/mj-plugin.git ./plugins/mj-plugin 29 | > ``` 30 | 31 | #### 2. 安装依赖 32 | 33 | ``` 34 | pnpm install --filter=mj-plugin 35 | ``` 36 | 37 | ## 插件配置 38 | 39 | > [!WARNING] 40 | > 非常不建议手动修改配置文件,本插件已兼容 [Guoba-plugin](https://github.com/guoba-yunzai/guoba-plugin) ,请使用锅巴插件对配置项进行修改 41 | 42 |
获取 Salai Token 43 | 44 | [登录 Discord](https://discord.com/channels/@me) F12 或者 [Ctrl + Shift + I] 或者 [Command + Option + I] 打开开发者工具,然后在 Console 中输入以下代码: 45 | 46 | ```javascript 47 | window.webpackChunkdiscord_app.push([ 48 | [Math.random()], 49 | {}, 50 | (req) => { 51 | for (const m of Object.keys(req.c) 52 | .map((x) => req.c[x].exports) 53 | .filter((x) => x)) { 54 | if (m.default && m.default.getToken !== undefined) { 55 | return copy(m.default.getToken()); 56 | } 57 | if (m.getToken !== undefined) { 58 | return copy(m.getToken()); 59 | } 60 | } 61 | }, 62 | ]); 63 | console.log("%cWorked!", "font-size: 50px"); 64 | console.log(`%您的Token在剪贴板了!`, "font-size: 16px"); 65 | ``` 66 | 67 | 也可以通过 查看 Network: [获取 Discord Token](https://www.androidauthority.com/get-discord-token-3149920/) 68 | 69 |
70 | 71 |
获取 Server ID 和 Channel ID 72 | 73 | [创建一个 Discord 服务器](https://discord.com/blog/starting-your-first-discord-server) 并邀请 [Midjourney Bot](https://docs.midjourney.com/docs/invite-the-bot) 74 | 75 | ```bash 76 | # 在浏览器中复制你的服务器网址 77 | # `https://discord.com/channels/$SERVER_ID/$CHANNEL_ID` 78 | ``` 79 | 80 |
81 | 82 | ## 功能列表 83 | 84 | 请使用 `#mj帮助` 获取完整帮助 85 | 86 | - [x] Imagine 想象/绘制 87 | - [x] Variation 变化 88 | - [x] Upscale 放大 89 | - [x] Reroll 重绘 90 | - [x] Blend 融合 91 | - [x] FaceSwap 换脸 92 | - [x] Shorten 优化 93 | - [x] Describe 描述 94 | - [x] Vary 调整 95 | - [x] Zoomout 拓展 96 | - [x] Custom 按钮 97 | - [x] Pan 平移 98 | - [x] Info 信息 99 | - [x] Setting 设置 100 | 101 | ## 常见问题 102 | 103 | 1. 我为什么连接不上? 104 | - 大陆服务器无法直接访问 Discord,需要使用代理服务器,请配置代理。 105 | - 请确保你的配置文件填写正确无误。 106 | 107 | ## 支持与贡献 108 | 109 | 如果你喜欢这个项目,请不妨点个 Star🌟,这是对开发者最大的动力, 当然,你可以对我 [爱发电](https://afdian.net/a/sumoqi) 赞助,呜咪~❤️ 110 | 111 | 有意见或者建议也欢迎提交 [Issues](https://github.com/CikeyQi/mj-plugin/issues) 和 [Pull requests](https://github.com/CikeyQi/mj-plugin/pulls)。 112 | 113 | ## 相关项目 114 | 115 | - [midjourney-api](https://github.com/erictik/midjourney-api):MidJourney client. Unofficial Node.js client 116 | 117 | ## 许可证 118 | 119 | 本项目使用 [GNU AGPLv3](https://choosealicense.com/licenses/agpl-3.0/) 作为开源许可证。 120 | -------------------------------------------------------------------------------- /apps/Blend.js: -------------------------------------------------------------------------------- 1 | import detectBannedWords from '../components/BannedWords.js' 2 | import { makeButton } from '../components/Button.js' 3 | import plugin from '../../../lib/plugins/plugin.js' 4 | import getPic from '../components/Proxy.js' 5 | import Log from '../utils/logs.js' 6 | 7 | export class Blend extends plugin { 8 | constructor () { 9 | super({ 10 | /** 功能名称 */ 11 | name: 'MJ-混合', 12 | /** 功能描述 */ 13 | dsc: 'Midjourney 混合', 14 | event: 'message', 15 | /** 优先级,数字越小等级越高 */ 16 | priority: 1009, 17 | rule: [ 18 | { 19 | /** 命令正则匹配 */ 20 | reg: '^#(mj|MJ)?混合$', 21 | /** 执行方法 */ 22 | fnc: 'blend' 23 | } 24 | ] 25 | }) 26 | } 27 | 28 | async blend(e) { 29 | 30 | if (!global.mjClient) { 31 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 32 | return true; 33 | } 34 | 35 | if (!e.img || e.img.length < 2) { 36 | await e.reply(`混合需要至少两张图片,请发送图片后重试`, true); 37 | return true; 38 | } 39 | 40 | const prompt = e.img.join(' '); 41 | 42 | const bannedWords = await detectBannedWords(e.msg); 43 | if (bannedWords.length) { 44 | await e.reply(`检测到敏感词:${bannedWords.join(',')},请修改后重试`, true); 45 | return true; 46 | } 47 | 48 | try { 49 | await e.reply('正在混合,请稍后...'); 50 | const response = await mjClient.Imagine(prompt, (progress, id) => { 51 | Log.i(`[${id}]绘制中,当前状态:${progress}`); 52 | }); 53 | 54 | await Promise.all([ 55 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 56 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 57 | ]); 58 | 59 | try { 60 | const base64 = await getPic(response.uri); 61 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 62 | 63 | await e.reply([ 64 | { ...segment.image('base64://' + base64), origin: true }, 65 | ...buttons 66 | ]); 67 | 68 | if (response.options.length > 0) { 69 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 70 | } 71 | } catch (err) { 72 | Log.e(err); 73 | await e.reply(response.uri); 74 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 75 | } 76 | } catch (err) { 77 | Log.e(err); 78 | await e.reply(`Midjourney 返回错误:\n${err}`, true); 79 | } 80 | return true; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /apps/Config.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import Log from '../utils/logs.js' 3 | import Config from '../components/Config.js' 4 | import Init from '../model/init.js' 5 | import { pluginResources } from '../model/path.js' 6 | import puppeteer from '../../../lib/puppeteer/puppeteer.js' 7 | 8 | export class Admin extends plugin { 9 | constructor() { 10 | super({ 11 | /** 功能名称 */ 12 | name: 'MJ-配置', 13 | /** 功能描述 */ 14 | dsc: 'Midjourney 配置', 15 | event: 'message', 16 | /** 优先级,数字越小等级越高 */ 17 | priority: 1009, 18 | rule: [ 19 | { 20 | /** 命令正则匹配 */ 21 | reg: '^#?(mj|MJ)配置.*$', 22 | /** 执行方法 */ 23 | fnc: 'admin', 24 | permission: 'master' 25 | } 26 | ] 27 | }) 28 | } 29 | 30 | async admin(e) { 31 | Init.initConfig() 32 | // 读取配置项 33 | var config = Config.getConfig() 34 | const key = /(服务器ID|频道ID|账号token|抱脸token|调试模式|代理(地址)?|使用翻译接口|(百度|有道)翻译(appid|appkey)|切换)/g.exec(e.msg)?.[1] 35 | let value = e.msg.replace(/#?(mj|MJ)配置/, '').replace(new RegExp(`${key}`), '').trim() 36 | // key匹配失败,value存在时 37 | if (!key && value != '') { 38 | e.reply(`配置项不存在,请检查输入`) 39 | return true 40 | } 41 | // 修改标志位,修改成功后修改为true 42 | let alterFlag = false 43 | switch (key) { 44 | case '服务器ID': 45 | config.server_id = value 46 | alterFlag = true 47 | break 48 | case '频道ID': 49 | config.channel_id = value 50 | alterFlag = true 51 | break 52 | case '账号token': 53 | config.salai_token = value 54 | alterFlag = true 55 | break 56 | case '抱脸token': 57 | config.huggingface_token = value 58 | alterFlag = true 59 | break 60 | case '调试模式': 61 | if (value.match(/(开启|关闭)/)) { 62 | if (value === '开启') { 63 | config.debug = true 64 | } else { 65 | config.debug = false 66 | } 67 | alterFlag = true 68 | } 69 | break 70 | case '代理': 71 | if (value.match(/(开启|关闭)/)) { 72 | if (value === '开启') { 73 | config.proxy = true 74 | } else { 75 | config.proxy = false 76 | } 77 | alterFlag = true 78 | } 79 | break 80 | case '代理地址': 81 | if (/^http:\/\/((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?):[\d]{1,5}$/.test(value)) { 82 | config.proxy_url = value 83 | alterFlag = true 84 | } else { 85 | e.reply('请输入正确的代理地址,如http://127.0.0.1:7890', true) 86 | return true 87 | } 88 | break 89 | case '百度翻译appid': 90 | config.baidu_translate.appid = value 91 | alterFlag = true 92 | break 93 | case '百度翻译appkey': 94 | config.baidu_translate.appkey = value 95 | alterFlag = true 96 | break 97 | case '有道翻译appid': 98 | config.youdao_translate.appid = value 99 | alterFlag = true 100 | break 101 | case '有道翻译appid': 102 | config.youdao_translate.appkey = value 103 | alterFlag = true 104 | break 105 | case '使用翻译接口': 106 | if (value.match(/(不|百度|有道)/)) { 107 | if (value === "百度") { 108 | config.translate_use = 1 109 | alterFlag = true 110 | } else if (value === "有道") { 111 | config.translate_use = 2 112 | alterFlag = true 113 | } else { 114 | config.translate_use = 0 115 | alterFlag = true 116 | } 117 | } 118 | break 119 | case '切换': 120 | if (value === 'mj') { 121 | config.bot_type = 'Midjourney' 122 | } else if (value === "niji") { 123 | config.bot_type = 'Nijijourney' 124 | } else { 125 | e.reply('请输入正确的Bot名称,如#mj配置切换mj/niji', true) 126 | return true 127 | } 128 | alterFlag = true 129 | break 130 | default: 131 | // 如果key为空且value为空则展示 132 | const TmpModels = [ 133 | [ 134 | { 135 | list1: '服务器ID', 136 | list2: config.server_id 137 | }, 138 | { 139 | list1: '频道ID', 140 | list2: config.channel_id 141 | }, 142 | { 143 | list1: '账号token', 144 | list2: config.salai_token ? config.salai_token.substring(0, 10) + '...' : '' 145 | }, 146 | { 147 | list1: '抱脸token', 148 | list2: config.huggingface_token ? config.huggingface_token.substring(0, 10) + '...' : '' 149 | }, 150 | { 151 | list1: '调试模式', 152 | list2: config.debug ? '开启' : '关闭' 153 | }, 154 | { 155 | list1: '代理', 156 | list2: config.proxy ? '开启' : '关闭' 157 | }, 158 | { 159 | list1: '代理地址', 160 | list2: config.proxy_url 161 | }, 162 | { 163 | list1: '翻译接口', 164 | list2: config.translate_use == 1 ? '百度' : config.translate_use == 2 ? '有道' : '未使用' 165 | }, 166 | { 167 | list1: '百度appid', 168 | list2: config.baidu_translate.appid ? config.baidu_translate.appid.substring(0, 10) + '...' : '' 169 | }, 170 | { 171 | list1: '百度appkey', 172 | list2: config.baidu_translate.appkey ? config.baidu_translate.appkey.substring(0, 10) + '...' : '' 173 | }, 174 | { 175 | list1: '有道appid', 176 | list2: config.youdao_translate.appid ? config.youdao_translate.appid.substring(0, 10) + '...' : '' 177 | }, 178 | { 179 | list1: '有道appkey', 180 | list2: config.youdao_translate.appkey ? config.youdao_translate.appkey.substring(0, 10) + '...' : '' 181 | }, 182 | { 183 | list1: 'Bot类型', 184 | list2: config.bot_type 185 | } 186 | ] 187 | ] 188 | const base64 = await puppeteer.screenshot('mj-plugin', { 189 | saveId: 'Admin', 190 | imgType: 'png', 191 | tplFile: `${pluginResources}/listTemp/listTemp.html`, 192 | pluginResources, 193 | header: 'Midjourney 配置', 194 | lable: 'Midjourney账号配置 代理配置 翻译配置', 195 | sidebar: '配置列表', 196 | list1: '配置项', 197 | list2: '配置状态', 198 | modelsGroup: TmpModels, 199 | notice: '需修改配置可发送#MJ帮助查看' 200 | }) 201 | await e.reply(base64) 202 | return true 203 | } 204 | if (alterFlag) { 205 | try { 206 | await Config.setConfig(config) 207 | let msg = [ 208 | key.match(/(服务器ID|频道ID|账号token|抱脸token|代理地址|(百度|有道)翻译(appid|appkey))/) ? `配置项${key}已设置为${value}` 209 | : key === '调试模式' || key === '代理' ? `设置项${key}已${value}` 210 | : key === '使用翻译接口' ? value == '不' ? '当前将不再使用翻译' : `翻译接口已修改为${value}` 211 | : key === '切换' ? value == 'mj' ? `已切换到Midjourney Bot,请使用 #mj重连 生效` : `已切换到Nijijourney Bot,请使用 #mj重连 生效` 212 | : '未知错误' 213 | ] 214 | Log.i('更新配置项', key, value) 215 | e.reply(msg, true) 216 | } catch (err) { 217 | Log.e(err) 218 | Log.e(err.message) 219 | return this.e.reply("配置失败。请查看控制台报错", true) 220 | } 221 | return true 222 | } else { 223 | e.reply(`设置项${key}无法修改为${value ? value : '空'}`, true) 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /apps/Connect.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import Config from '../components/Config.js' 3 | import { Main } from './Main.js' 4 | 5 | export class Connect extends plugin { 6 | constructor () { 7 | super({ 8 | /** 功能名称 */ 9 | name: 'MJ-连接', 10 | /** 功能描述 */ 11 | dsc: 'Midjourney 连接', 12 | event: 'message', 13 | /** 优先级,数字越小等级越高 */ 14 | priority: 1009, 15 | rule: [ 16 | { 17 | /** 命令正则匹配 */ 18 | reg: '^#?(mj|MJ)(连接|重连|断开)$', 19 | /** 执行方法 */ 20 | fnc: 'connect' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async connect (e) { 27 | if (e.msg.includes('连接')) { 28 | if (global.mjClient) { 29 | await e.reply( 30 | '当前已连接到 ' + Config.getConfig().bot_type + ',如需强制重连请使用 #mj重连' 31 | ) 32 | } else { 33 | await e.reply('正在连接 ' + Config.getConfig().bot_type + ',请稍后...') 34 | await Main() 35 | if (!global.mjClient) { 36 | await e.reply('连接 ' + Config.getConfig().bot_type + ' 失败,请检查配置后重试', true) 37 | } else { 38 | await e.reply('已连接到 ' + Config.getConfig().bot_type) 39 | } 40 | } 41 | } else if (e.msg.includes('重连')) { 42 | if (!global.mjClient) { 43 | await e.reply('当前未连接到 ' + Config.getConfig().bot_type + ',如需连接请使用 #mj连接') 44 | } else { 45 | await e.reply('正在连接 ' + Config.getConfig().bot_type + ',请稍后...') 46 | await Main() 47 | if (!global.mjClient) { 48 | await e.reply('连接 ' + Config.getConfig().bot_type + ' 失败,请检查配置后重试', true) 49 | } else { 50 | await e.reply('已连接到 ' + Config.getConfig().bot_type) 51 | } 52 | } 53 | } else if (e.msg.includes('断开')) { 54 | if (!global.mjClient) { 55 | await e.reply('当前未连接到 ' + Config.getConfig().bot_type + ',无需断开') 56 | } else { 57 | await global.mjClient.Close() 58 | await e.reply('已断开 ' + Config.getConfig().bot_type) 59 | } 60 | } 61 | return true 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /apps/Custom.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Custom extends plugin { 7 | constructor() { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-按钮', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 按钮', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?按钮([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'custom' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async custom(e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const msgId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 35 | const taskInfoRaw = await redis.get(`mj:${msgId}`); 36 | const taskInfo = JSON.parse(taskInfoRaw); 37 | 38 | const idError = e.msg.match(/\d{19}/) ? '未找到指定的绘制结果,请检查ID是否正确' : '未找到上一次的绘制结果,请先使用 #mj绘制'; 39 | if (!taskInfo) { 40 | await e.reply(idError, true); 41 | return true; 42 | } 43 | 44 | const index = e.msg.replace(/#(mj|MJ)?按钮/, '').replace(/\d{19}/, '').trim(); 45 | const rerollCustomID = taskInfo.options?.find(o => o.label === index)?.custom; 46 | 47 | if (!rerollCustomID) { 48 | await e.reply(`上一次的绘制结果不允许使用${index}`); 49 | return true; 50 | } 51 | 52 | if (typeof taskInfo.content === 'string') { 53 | taskInfo.content = taskInfo.content.match(/\*\*(.*)\*\*/)?.[1]; 54 | } 55 | 56 | try { 57 | await e.reply(`正在使用${index},请稍后...`); 58 | const response = await mjClient.Custom({ 59 | msgId: taskInfo.id.toString(), 60 | customId: rerollCustomID, 61 | flags: taskInfo.flags, 62 | content: taskInfo.content, 63 | loading: (uri, progress) => { 64 | Log.i(`[${progress}]绘制中,当前状态:${uri}`); 65 | } 66 | }); 67 | 68 | await Promise.all([ 69 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 70 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 71 | ]); 72 | 73 | try { 74 | const base64 = await getPic(response.uri); 75 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 76 | 77 | await e.reply([ 78 | { ...segment.image('base64://' + base64), origin: true }, 79 | ...buttons 80 | ]); 81 | 82 | if (response.options.length > 0) { 83 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 84 | } 85 | } catch (err) { 86 | Log.e(err); 87 | await e.reply(response.uri); 88 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 89 | } 90 | } catch (err) { 91 | Log.e(err); 92 | await e.reply('Midjourney 返回错误:\n' + err, true); 93 | } 94 | return true; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /apps/Describe.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import Log from '../utils/logs.js' 4 | 5 | export class Describe extends plugin { 6 | constructor () { 7 | super({ 8 | /** 功能名称 */ 9 | name: 'MJ-描述', 10 | /** 功能描述 */ 11 | dsc: 'Midjourney 描述', 12 | event: 'message', 13 | /** 优先级,数字越小等级越高 */ 14 | priority: 1009, 15 | rule: [ 16 | { 17 | /** 命令正则匹配 */ 18 | reg: '^#(mj|MJ)?描述$', 19 | /** 执行方法 */ 20 | fnc: 'describe' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async describe (e) { 27 | if (!global.mjClient) { 28 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true) 29 | return true 30 | } 31 | 32 | if (!e.img) { 33 | await e.reply('请发送要描述的图片', true) 34 | return true 35 | } 36 | 37 | try { 38 | await e.reply('正在描述,请稍后...') 39 | const response = await mjClient.Describe(e.img[0]) 40 | 41 | await Promise.all([ 42 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 43 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 44 | ]); 45 | 46 | try { 47 | let buttons = await makeButton(response.options.map(option => option.label), response.id) 48 | 49 | await e.reply([ 50 | response.descriptions.join('\n'), 51 | ...buttons 52 | ]); 53 | 54 | if (response.options.length > 0) { 55 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 56 | } 57 | } catch (err) { 58 | Log.e(err); 59 | await e.reply('发送内容遇到问题,错误已发送至控制台'); 60 | } 61 | } catch (err) { 62 | Log.e(err); 63 | await e.reply('Midjourney 返回错误:\n' + err, true); 64 | } 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /apps/FaceSwap.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class FaceSwap extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-换脸', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 换脸', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?换脸$', 20 | /** 执行方法 */ 21 | fnc: 'faceswap' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async faceswap (e) { 28 | if (!global.mjClient) { 29 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 30 | return true; 31 | } 32 | 33 | if (!e.img || e.img.length !== 2) { 34 | const errMsg = !e.img 35 | ? '请发送要换脸的图片,第一张为靶图片,第二张为源图片' 36 | : '请按正确顺序发送两张图片,第一张为靶图片,第二张为源图片'; 37 | await e.reply(errMsg, true); 38 | return true; 39 | } 40 | 41 | const [target, source] = e.img; 42 | 43 | try { 44 | await e.reply('正在换脸,请稍后...'); 45 | const response = await mjClient.FaceSwap(target, source); 46 | 47 | await Promise.all([ 48 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 49 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 50 | ]); 51 | 52 | try { 53 | const base64 = await getPic(response.uri); 54 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 55 | 56 | await e.reply([ 57 | { ...segment.image('base64://' + base64), origin: true }, 58 | ...buttons 59 | ]); 60 | 61 | if (response.options.length > 0) { 62 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 63 | } 64 | } catch (err) { 65 | Log.e(err); 66 | await e.reply(response.uri); 67 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 68 | } 69 | } catch (err) { 70 | Log.e(err); 71 | await e.reply('Midjourney 返回错误:\n' + err, true); 72 | } 73 | return true; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /apps/Fast.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | 3 | export class Fast extends plugin { 4 | constructor () { 5 | super({ 6 | /** 功能名称 */ 7 | name: 'MJ-快速模式', 8 | /** 功能描述 */ 9 | dsc: 'Midjourney 快速模式', 10 | event: 'message', 11 | /** 优先级,数字越小等级越高 */ 12 | priority: 1009, 13 | rule: [ 14 | { 15 | /** 命令正则匹配 */ 16 | reg: '^#(mj|MJ)?快速模式$', 17 | /** 执行方法 */ 18 | fnc: 'fast', 19 | /** 主人权限 */ 20 | permission: 'master' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async fast (e) { 27 | if (!global.mjClient) { 28 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true) 29 | return true 30 | } 31 | await mjClient.Fast() 32 | await e.reply('Midjourney 已切换到快速模式') 33 | return true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/Imagine.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import Translate from '../utils/translate.js' 4 | import getPic from '../components/Proxy.js' 5 | import Log from '../utils/logs.js' 6 | 7 | export class Imagine extends plugin { 8 | constructor () { 9 | super({ 10 | /** 功能名称 */ 11 | name: 'MJ-绘制', 12 | /** 功能描述 */ 13 | dsc: 'Midjourney 绘制', 14 | event: 'message', 15 | /** 优先级,数字越小等级越高 */ 16 | priority: 1009, 17 | rule: [ 18 | { 19 | /** 命令正则匹配 */ 20 | reg: '^#(mj|MJ)?(绘制|想象)([\\s\\S]*)$', 21 | /** 执行方法 */ 22 | fnc: 'imagine' 23 | } 24 | ] 25 | }) 26 | } 27 | 28 | async imagine(e) { 29 | 30 | if (!global.mjClient) { 31 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 32 | return true; 33 | } 34 | 35 | const msg = e.msg.replace(/#(mj|MJ)?(绘制|想象)/, '').trim(); 36 | 37 | const chineseText = msg.match(/[\u4e00-\u9fa5]+/g); 38 | let prompt = msg; 39 | if (chineseText) { 40 | for (let chineseSegment of chineseText) { 41 | const translation = await Translate.translate(chineseSegment); 42 | if (translation) { 43 | prompt = prompt.replace(chineseSegment, translation); 44 | } else { 45 | await e.reply('翻译失败了,请检查配置后再试', true); 46 | return true; 47 | } 48 | } 49 | } 50 | 51 | if (!e.isMaster) { 52 | prompt = prompt.replace(/--(fast|turbo)/g, ''); 53 | } 54 | 55 | try { 56 | await e.reply('正在绘制,请稍后...'); 57 | const response = await mjClient.Imagine(prompt, (progress, id) => { 58 | Log.i(`[${id}]绘制中,当前状态:${progress}`); 59 | }); 60 | 61 | await Promise.all([ 62 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 63 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 64 | ]); 65 | 66 | try { 67 | const base64 = await getPic(response.uri); 68 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 69 | 70 | await e.reply([ 71 | { ...segment.image('base64://' + base64), origin: true }, 72 | ...buttons 73 | ]); 74 | 75 | if (response.options.length > 0) { 76 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 77 | } 78 | } catch (err) { 79 | Log.e(err); 80 | await e.reply(response.uri); 81 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 82 | } 83 | } catch (err) { 84 | Log.e(err); 85 | await e.reply('Midjourney 返回错误:\n' + err, true); 86 | } 87 | return true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /apps/Info.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { pluginResources } from '../model/path.js' 3 | import puppeteer from '../../../lib/puppeteer/puppeteer.js' 4 | 5 | export class Info extends plugin { 6 | constructor () { 7 | super({ 8 | /** 功能名称 */ 9 | name: 'MJ-信息', 10 | /** 功能描述 */ 11 | dsc: 'Midjourney 信息', 12 | event: 'message', 13 | /** 优先级,数字越小等级越高 */ 14 | priority: 1009, 15 | rule: [ 16 | { 17 | /** 命令正则匹配 */ 18 | reg: '^#?(mj|MJ)信息$', 19 | /** 执行方法 */ 20 | fnc: 'info' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async info (e) { 27 | if (!global.mjClient) { 28 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true) 29 | return true 30 | } 31 | const response = await mjClient.Info() 32 | if (response.subscription.includes('/g 34 | const match = response.subscription.match(reg) 35 | for (let i = 0; i < match.length; i++) { 36 | const time = match[i].replace('', '') 37 | const date = new Date(time * 1000) 38 | response.subscription = response.subscription.replace( 39 | match[i], 40 | date.toLocaleString() 41 | ) 42 | } 43 | } 44 | const TmpModels = [ 45 | [ 46 | { list1: '订阅类型', list2: response.subscription }, 47 | { list1: '工作模式', list2: response.jobMode }, 48 | { list1: '可见性模式', list2: response.visibilityMode }, 49 | { list1: '快速模式剩余时间', list2: response.fastTimeRemaining }, 50 | { list1: '总使用量', list2: response.lifetimeUsage }, 51 | { list1: '舒缓模式使用统计', list2: response.relaxedUsage }, 52 | { list1: '快速模式队列', list2: response.queuedJobsFast }, 53 | { list1: '舒缓模式队列', list2: response.queuedJobsRelax }, 54 | { list1: '正在运行的任务', list2: response.runningJobs } 55 | ] 56 | ] 57 | const base64 = await puppeteer.screenshot('mj-plugin', { 58 | saveId: 'Info', 59 | imgType: 'png', 60 | tplFile: `${pluginResources}/listTemp/listTemp.html`, 61 | pluginResources, 62 | header: 'Midjourney 信息', 63 | lable: '查看您的账号信息,与 Midjourney剩余额度', 64 | sidebar: '我的信息', 65 | list1: '内容', 66 | list2: '状态', 67 | modelsGroup: TmpModels, 68 | notice: '' 69 | }) 70 | await e.reply(base64) 71 | return true 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /apps/Main.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { Midjourney } from 'midjourney' 3 | import fetch from 'node-fetch' 4 | import { HttpsProxyAgent } from 'https-proxy-agent' 5 | import WebSocket from 'isomorphic-ws' 6 | import _ from 'lodash' 7 | import fs from 'fs' 8 | import Log from '../utils/logs.js' 9 | import Config from '../components/Config.js' 10 | import { pluginRoot } from '../model/path.js' 11 | import Version from '../components/Version.js' 12 | 13 | const proxyFetch = async (input, init) => { 14 | const agent = new HttpsProxyAgent(Config.getConfig().proxy_url, { 15 | keepAlive: true 16 | }) 17 | if (!init) init = {} 18 | init.agent = agent 19 | return fetch(input, init) 20 | } 21 | 22 | class ProxyWebSocket extends WebSocket { 23 | constructor (address, options) { 24 | const agent = new HttpsProxyAgent(Config.getConfig().proxy_url, { 25 | keepAlive: true 26 | }) 27 | if (!options) options = {} 28 | options.agent = agent 29 | super(address, options) 30 | this.on('error', (err) => { 31 | Log.e(err) 32 | }) 33 | } 34 | } 35 | 36 | export async function Main () { 37 | try { 38 | const client = new Midjourney({ 39 | ServerId: Config.getConfig().server_id, 40 | ChannelId: Config.getConfig().channel_id, 41 | SalaiToken: Config.getConfig().salai_token, 42 | BotId: Config.getConfig().bot_type === 'Nijijourney' ? '1022952195194359889' : '936929561302675456', 43 | HuggingFaceToken: Config.getConfig().huggingface_token, 44 | Debug: Config.getConfig().debug, 45 | Ws: true, 46 | fetch: Config.getConfig().proxy ? proxyFetch : fetch, 47 | WebSocket: Config.getConfig().proxy ? ProxyWebSocket : WebSocket 48 | }) 49 | await client.init() 50 | global.mjClient = client 51 | } catch (err) { 52 | Log.e(err) 53 | } 54 | } 55 | 56 | export class Help extends plugin { 57 | constructor () { 58 | super({ 59 | /** 功能名称 */ 60 | name: 'MJ-帮助', 61 | /** 功能描述 */ 62 | dsc: 'Midjourney 帮助', 63 | event: 'message', 64 | /** 优先级,数字越小等级越高 */ 65 | priority: 1009, 66 | rule: [ 67 | { 68 | /** 命令正则匹配 */ 69 | reg: '^#?(mj|MJ)帮助$', 70 | /** 执行方法 */ 71 | fnc: 'help' 72 | } 73 | ] 74 | }) 75 | } 76 | 77 | async help (e) { 78 | const helpCfg = { 79 | themeSet: false, 80 | title: '#MJ-plugin帮助', 81 | subTitle: 'Yunzai-Bot & MJ-plugin', 82 | colWidth: 265, 83 | theme: 'all', 84 | themeExclude: ['default'], 85 | colCount: 2, 86 | bgBlur: true 87 | } 88 | const helpList = [ 89 | { 90 | group: 'MJ绘图', 91 | list: [ 92 | { 93 | icon: 1, 94 | title: '#MJ想象<描述>', 95 | desc: '根据您的提示生成低分辨率图像选项的网格' 96 | }, 97 | { 98 | icon: 54, 99 | title: '#MJ描述[图片]', 100 | desc: '根据您的图片生成相关提示词' 101 | }, 102 | { 103 | icon: 5, 104 | title: '#MJ混合[图片][图片]', 105 | desc: '2-5张图像合并成一个新颖的新图像' 106 | }, 107 | { 108 | icon: 7, 109 | title: '#MJ放大第<1|2|3|4>张', 110 | desc: '放大图像,生成所选图像的更大版本并添加更多细节' 111 | }, 112 | { 113 | icon: 38, 114 | title: '#MJ变化第<1|2|3|4>张', 115 | desc: '生成与所选图像的整体风格和构图相似的新图像网格' 116 | }, 117 | { 118 | icon: 11, 119 | title: '#MJ重绘', 120 | desc: '重新运行原始提示,生成新的图像网格' 121 | }, 122 | { 123 | icon: 86, 124 | title: '#MJ换脸[图片][图片]', 125 | desc: '换脸,顾名思义' 126 | }, 127 | { 128 | icon: 54, 129 | title: '#MJ调整(强大|微妙)', 130 | desc: '使用不同的升级选项重做图像' 131 | }, 132 | { 133 | icon: 3, 134 | title: '#MJ平移(上|下|左|右)', 135 | desc: '向对应方向扩大画布,增加更多细节' 136 | }, 137 | { 138 | icon: 35, 139 | title: '#MJ拓展', 140 | desc: '图片缩放,向外扩大画布,增加更多细节' 141 | }, 142 | { 143 | icon: 36, 144 | title: '#MJ优化', 145 | desc: '优化输入的描述,可能影响提示准确性' 146 | }, 147 | { 148 | icon: 30, 149 | title: '#MJ按钮', 150 | desc: '模拟与官方MJ机器人交互,兼容大部分按钮功能' 151 | }, 152 | { 153 | icon: 62, 154 | title: '#MJ参数', 155 | desc: '查看可选绘制参数,萌新必背' 156 | }, 157 | { 158 | icon: 62, 159 | title: '#MJ帮助', 160 | desc: 'MJ-Plugin插件帮助' 161 | } 162 | ] 163 | }, 164 | { 165 | group: 'MJ管理', 166 | list: [ 167 | { 168 | icon: 65, 169 | title: '#MJ(连接|重连|断开)', 170 | desc: 'Discord代理客户端连接设置' 171 | }, 172 | { 173 | icon: 64, 174 | title: '#MJ设置', 175 | desc: '当前MJ账号设置' 176 | }, 177 | { 178 | icon: 63, 179 | title: '#MJ快速模式', 180 | desc: '快速模式,请注意额度使用' 181 | }, 182 | { 183 | icon: 67, 184 | title: '#MJ舒缓模式', 185 | desc: '舒缓模式,没有额外费用,速度较慢' 186 | }, 187 | { 188 | icon: 68, 189 | title: '#MJ信息', 190 | desc: '查看当前帐号MJ额度等信息' 191 | }, 192 | { 193 | icon: 69, 194 | title: '#MJ[强制]更新', 195 | desc: '插件更新' 196 | } 197 | ] 198 | }, 199 | { 200 | group: 'MJ配置', 201 | list: [ 202 | { 203 | icon: 11, 204 | title: '#MJ配置服务器ID', 205 | desc: 'Discord服务器ID,服务器地址中前一项' 206 | }, 207 | { 208 | icon: 12, 209 | title: '#MJ配置频道ID', 210 | desc: 'Discord频道ID,服务器地址中后一项' 211 | }, 212 | { 213 | icon: 13, 214 | title: '#MJ配置账号token', 215 | desc: 'Discord账号token' 216 | }, 217 | { 218 | icon: 14, 219 | title: '#MJ配置抱脸token', 220 | desc: '换脸功能需要配置huggingface账号token' 221 | }, 222 | { 223 | icon: 15, 224 | title: '#MJ配置调试模式(开启|关闭)', 225 | desc: 'Discord连接调试模式,修改后请重连' 226 | }, 227 | { 228 | icon: 16, 229 | title: '#MJ配置代理(开启|关闭)', 230 | desc: '是否开启代理' 231 | }, 232 | { 233 | icon: 17, 234 | title: '#MJ配置代理地址', 235 | desc: '#MJ配置代理地址http://127.0.0.1:7890' 236 | }, 237 | { 238 | icon: 18, 239 | title: '#MJ配置(百度|有道)翻译appid', 240 | desc: '配置翻译接口appid' 241 | }, 242 | { 243 | icon: 19, 244 | title: '#MJ配置(百度|有道)翻译appkey', 245 | desc: '配置翻译接口appkey' 246 | }, 247 | { 248 | icon: 20, 249 | title: '#MJ配置使用翻译接口(百度|有道)', 250 | desc: '将当前翻译服务设置为百度|有道' 251 | }, 252 | { 253 | icon: 21, 254 | title: '#MJ配置不使用翻译接口', 255 | desc: '不使用翻译服务' 256 | }, 257 | { 258 | icon: 22, 259 | title: '#MJ配置切换mj/niji', 260 | desc: '使用Midjourney/Nijijourney Bot绘制图片' 261 | }, 262 | { 263 | icon: 23, 264 | title: '#MJ配置', 265 | desc: '查看当前所有配置项' 266 | } 267 | ] 268 | } 269 | ] 270 | const helpGroup = [] 271 | _.forEach(helpList, (group) => { 272 | _.forEach(group.list, (help) => { 273 | const icon = help.icon * 1 274 | if (!icon) { 275 | help.css = 'display:none' 276 | } else { 277 | const x = (icon - 1) % 10 278 | const y = (icon - x - 1) / 10 279 | help.css = `background-position:-${x * 50}px -${y * 50}px` 280 | } 281 | }) 282 | helpGroup.push(group) 283 | }) 284 | 285 | const themeData = await this.getThemeData(helpCfg, helpCfg) 286 | 287 | return await this.render( 288 | 'helpTemp/helpTemp', 289 | { 290 | helpCfg, 291 | helpGroup, 292 | ...themeData, 293 | element: 'default' 294 | }, 295 | { e } 296 | ) 297 | } 298 | 299 | getThemeCfg () { 300 | const resPath = '{{_res_path}}/helpTemp/' 301 | return { 302 | main: `${resPath}/main.png`, 303 | bg: `${resPath}/bg.jpg`, 304 | style: { 305 | // 主文字颜色 306 | fontColor: '#ceb78b', 307 | // 主文字阴影: 横向距离 垂直距离 阴影大小 阴影颜色 308 | // fontShadow: '0px 0px 1px rgba(6, 21, 31, .9)', 309 | fontShadow: 'none', 310 | // 描述文字颜色 311 | descColor: '#eee', 312 | 313 | /* 面板整体底色,会叠加在标题栏及帮助行之下,方便整体帮助有一个基础底色 314 | * 若无需此项可将rgba最后一位置为0即为完全透明 315 | * 注意若综合透明度较低,或颜色与主文字颜色过近或太透明可能导致阅读困难 */ 316 | contBgColor: 'rgba(6, 21, 31, .5)', 317 | 318 | // 面板底图毛玻璃效果,数字越大越模糊,0-10 ,可为小数 319 | contBgBlur: 3, 320 | 321 | // 板块标题栏底色 322 | headerBgColor: 'rgba(6, 21, 31, .4)', 323 | // 帮助奇数行底色 324 | rowBgColor1: 'rgba(6, 21, 31, .2)', 325 | // 帮助偶数行底色 326 | rowBgColor2: 'rgba(6, 21, 31, .35)' 327 | } 328 | } 329 | } 330 | 331 | async getThemeData (diyStyle, sysStyle) { 332 | const helpConfig = _.extend({}, diyStyle, sysStyle) 333 | const colCount = Math.min( 334 | 5, 335 | Math.max(parseInt(helpConfig?.colCount) || 3, 2) 336 | ) 337 | const colWidth = Math.min( 338 | 500, 339 | Math.max(100, parseInt(helpConfig?.colWidth) || 265) 340 | ) 341 | const width = Math.min(2500, Math.max(800, colCount * colWidth + 30)) 342 | const theme = this.getThemeCfg() 343 | const themeStyle = theme.style || {} 344 | const ret = [ 345 | ` 346 | body{background-image:url(${theme.bg});width:${width}px;} 347 | .container{background-image:url(${ 348 | theme.main 349 | });width:${width}px;background-size:cover} 350 | .help-table .td,.help-table .th{width:${100 / colCount}%} 351 | ` 352 | ] 353 | const css = function (sel, css, key, def, fn) { 354 | let val = (function () { 355 | for (const idx in arguments) { 356 | if (!_.isUndefined(arguments[idx])) { 357 | return arguments[idx] 358 | } 359 | } 360 | })(themeStyle[key], diyStyle[key], sysStyle[key], def) 361 | if (fn) { 362 | val = fn(val) 363 | } 364 | ret.push(`${sel}{${css}:${val}}`) 365 | } 366 | css('.help-title,.help-group', 'color', 'fontColor', '#ceb78b') 367 | css('.help-title,.help-group', 'text-shadow', 'fontShadow', 'none') 368 | css('.help-desc', 'color', 'descColor', '#eee') 369 | css('.cont-box', 'background', 'contBgColor', 'rgba(43, 52, 61, 0.8)') 370 | css('.cont-box', 'backdrop-filter', 'contBgBlur', 3, (n) => 371 | diyStyle.bgBlur === false ? 'none' : `blur(${n}px)` 372 | ) 373 | css('.help-group', 'background', 'headerBgColor', 'rgba(34, 41, 51, .4)') 374 | css( 375 | '.help-table .tr:nth-child(odd)', 376 | 'background', 377 | 'rowBgColor1', 378 | 'rgba(34, 41, 51, .2)' 379 | ) 380 | css( 381 | '.help-table .tr:nth-child(even)', 382 | 'background', 383 | 'rowBgColor2', 384 | 'rgba(34, 41, 51, .4)' 385 | ) 386 | return { 387 | style: ``, 388 | colCount 389 | } 390 | } 391 | 392 | async render (path, params, cfg) { 393 | const { e } = cfg 394 | if (!e.runtime) { 395 | console.log('未找到e.runtime,请升级至最新版Yunzai') 396 | } 397 | 398 | const BotName = Version.isMiao ? 'Miao-Yunzai' : 'Yunzai-Bot' 399 | let currentVersion = null 400 | const package_path = `${pluginRoot}/package.json` 401 | try { 402 | const package_json = JSON.parse(fs.readFileSync(package_path, 'utf-8')) 403 | if (package_json.version) { 404 | currentVersion = package_json.version 405 | } 406 | } catch (err) { 407 | Log.e('读取package.json失败', err) 408 | } 409 | return e.runtime.render('mj-plugin', path, params, { 410 | retType: cfg.retMsgId ? 'msgId' : 'default', 411 | beforeRender ({ data }) { 412 | const pluginName = 413 | 'mj-plugin' + `${currentVersion}` 414 | const resPath = data.pluResPath 415 | const layoutPath = 416 | process.cwd() + '/plugins/mj-plugin/resources/common/layout/' 417 | return { 418 | ...data, 419 | _res_path: resPath, 420 | _mj_path: resPath, 421 | defaultLayout: layoutPath + 'default.html', 422 | sys: { 423 | scale: 'style=transform:scale(1.8)' 424 | }, 425 | copyright: `Created By ${BotName}${Version.yunzai}${pluginName}` 426 | } 427 | } 428 | }) 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /apps/Pan.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Pan extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-平移', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 平移', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?平移([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'pan' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async pan (e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const msgId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 35 | const taskInfoRaw = await redis.get(`mj:${msgId}`); 36 | const taskInfo = JSON.parse(taskInfoRaw); 37 | 38 | if (!taskInfo) { 39 | const replyMsg = e.msg.match(/\d{19}/) 40 | ? '未找到指定的绘制结果,请检查ID是否正确' 41 | : '未找到上一次的绘制结果,请先使用 #mj绘制'; 42 | await e.reply(replyMsg, true); 43 | return true; 44 | } 45 | 46 | const index = e.msg.match(/上|下|左|右/)?.[0]; 47 | if (!index) { 48 | await e.reply('请指定要使用的平移方向,如“上”、“下”、“左”、“右”', true); 49 | return true; 50 | } 51 | 52 | const directionMap = { '上': '⬆️', '下': '⬇️', '左': '⬅️', '右': '➡️' }; 53 | const customName = directionMap[index]; 54 | 55 | const panCustomID = taskInfo.options?.find( 56 | (o) => o.label === customName 57 | )?.custom; 58 | 59 | if (!panCustomID) { 60 | await e.reply(`上一次的绘制结果不允许使用${index}方向,请先使用 #mj绘制生成新的内容`, true); 61 | return true; 62 | } 63 | 64 | const content = taskInfo.content?.match(/\*\*(.+?)\*\*/)?.[1]; 65 | 66 | try { 67 | await e.reply(`正在进行${customName}平移,请稍后...`); 68 | const response = await mjClient.Custom({ 69 | msgId: taskInfo.id.toString(), 70 | customId: panCustomID, 71 | content: content, 72 | flags: taskInfo.flags, 73 | }); 74 | 75 | await Promise.all([ 76 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 77 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 78 | ]); 79 | 80 | try { 81 | const base64 = await getPic(response.uri); 82 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 83 | 84 | await e.reply([ 85 | { ...segment.image('base64://' + base64), origin: true }, 86 | ...buttons 87 | ]); 88 | 89 | if (response.options.length > 0) { 90 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 91 | } 92 | } catch (err) { 93 | Log.e(err); 94 | await e.reply(response.uri); 95 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 96 | } 97 | } catch (err) { 98 | Log.e(err); 99 | await e.reply('Midjourney 返回错误:\n' + err, true); 100 | } 101 | return true; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /apps/Param.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { pluginResources } from '../model/path.js' 3 | import puppeteer from '../../../lib/puppeteer/puppeteer.js' 4 | 5 | export class Param extends plugin { 6 | constructor () { 7 | super({ 8 | /** 功能名称 */ 9 | name: 'MJ-参数列表', 10 | /** 功能描述 */ 11 | dsc: 'Midjourney 参数列表', 12 | event: 'message', 13 | /** 优先级,数字越小等级越高 */ 14 | priority: 1009, 15 | rule: [ 16 | { 17 | /** 命令正则匹配 */ 18 | reg: '^#?(mj|MJ)参数(列表)?$', 19 | /** 执行方法 */ 20 | fnc: 'param' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async param (e) { 27 | const TmpModels = [ 28 | [ 29 | { 30 | list1: '--ar n:n', 31 | list2: '纵横比,默认1:1。用于指定绘制图像的横纵比。' 32 | }, 33 | { 34 | list1: '--chaos ', 35 | list2: 36 | '变化程度,数值越高结果越不寻常和意想不到。用于控制图像的抽象程度。' 37 | }, 38 | { 39 | list1: '--fast', 40 | list2: 41 | '使用快速模式运行单个任务。加快任务完成速度,但可能降低图像质量。' 42 | }, 43 | { 44 | list1: '--iw <0–2>', 45 | list2: 46 | '设置图像提示权重相对于文本权重,默认值为1。用于控制图像与文本提示之间的权重分配。' 47 | }, 48 | { 49 | list1: '--no', 50 | list2: 51 | '反向提示,例如 --no plants 会尝试从图像中移除植物。用于在图像中排除某些元素。' 52 | }, 53 | { 54 | list1: '--q 清晰度', 55 | list2: 56 | '.25 .5 1 分别代表: 一般,清晰,高清,默认1。控制图像的清晰度,较高的值会提高图像质量,但可能需要更长的处理时间。' 57 | }, 58 | { 59 | list1: '--relax', 60 | list2: '使用放松模式运行单个任务。在较短的时间内生成较为轻松的图像。' 61 | }, 62 | { 63 | list1: '--seed <0–4294967295>', 64 | list2: 65 | '用于生成初始图像网格的种子数,相同的种子数和提示将产生相似的最终图像。用于控制图像的随机性。' 66 | }, 67 | { 68 | list1: '--stop <10–100>', 69 | list2: 70 | '在过程中部分完成任务,较早停止的任务可能产生模糊、细节较少的结果。用于在图像生成过程中提前停止,以节省时间。' 71 | }, 72 | { 73 | list1: 74 | '--style ', 75 | list2: 76 | '切换不同的Midjourney模型版本和Niji模型版本。选择不同的绘画风格。' 77 | }, 78 | { 79 | list1: '--s 1-1000', 80 | list2: 81 | '风格化 影响默认美学风格在任务中的应用程度。用于调整风格强度。' 82 | }, 83 | { 84 | list1: '--tile', 85 | list2: 86 | '生成可用于创建无缝图案的重复图块的图像。生成可用于平铺的图像。' 87 | }, 88 | { 89 | list1: '--turbo', 90 | list2: 91 | '使用涡轮模式运行单个任务。加快任务完成速度,但可能降低图像质量。' 92 | }, 93 | { 94 | list1: '--weird <0–3000>', 95 | list2: 96 | '使用实验性参数 --weird 探索不寻常的美学。生成具有独特风格的图像。' 97 | }, 98 | { 99 | list1: '--version <1, 2, 3, 4, 5, 5.1, or 5.2>', 100 | list2: '使用不同版本的Midjourney算法。选择不同的绘画算法版本。' 101 | }, 102 | { 103 | list1: '--niji', 104 | list2: 105 | '使用专注于动漫风格图像的替代模型。生成具有日本动漫风格的图像。' 106 | } 107 | ] 108 | ] 109 | const base64 = await puppeteer.screenshot('mj-plugin', { 110 | saveId: 'Param', 111 | imgType: 'png', 112 | tplFile: `${pluginResources}/listTemp/listTemp.html`, 113 | pluginResources, 114 | header: 'Midjourney 参数列表', 115 | lable: '查看绘制图片时可用的参数', 116 | sidebar: '参数列表', 117 | list1: '参数', 118 | list2: '说明', 119 | modelsGroup: TmpModels, 120 | notice: 121 | 'fast与trubo参数已经被禁用,仅主人可用,若想使用请进入 Discord 手动修改。' 122 | }) 123 | await e.reply(base64) 124 | return true 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /apps/Relax.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | 3 | export class Relax extends plugin { 4 | constructor () { 5 | super({ 6 | /** 功能名称 */ 7 | name: 'MJ-舒缓模式', 8 | /** 功能描述 */ 9 | dsc: 'Midjourney 舒缓模式', 10 | event: 'message', 11 | /** 优先级,数字越小等级越高 */ 12 | priority: 1009, 13 | rule: [ 14 | { 15 | /** 命令正则匹配 */ 16 | reg: '^#(mj|MJ)?舒缓模式$', 17 | /** 执行方法 */ 18 | fnc: 'relax', 19 | /** 主人权限 */ 20 | permission: 'master' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async relax (e) { 27 | if (!global.mjClient) { 28 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true) 29 | return true 30 | } 31 | await mjClient.Relax() 32 | await e.reply('Midjourney 已切换到舒缓模式') 33 | return true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /apps/Reroll.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Reroll extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-重绘', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 重绘', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?重绘([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'reroll' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async reroll(e) { 28 | if (!global.mjClient) { 29 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 30 | return true; 31 | } 32 | 33 | const taskId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 34 | const taskInfoJson = await redis.get(`mj:${taskId}`); 35 | const taskInfo = JSON.parse(taskInfoJson || 'null'); 36 | 37 | if (!taskInfo) { 38 | await e.reply( 39 | `未找到${taskId !== e.user_id ? '指定的' : '上一次的'}绘制结果,请${taskId !== e.user_id ? '检查ID是否正确' : '先使用 #mj绘制'}`, 40 | true 41 | ); 42 | return true; 43 | } 44 | 45 | const rerollCustomID = taskInfo.options?.find(o => o.label === '🔄')?.custom; 46 | if (!rerollCustomID) { 47 | await e.reply('上一次的绘制结果不允许使用🔄,请先使用 #mj绘制', true); 48 | return true; 49 | } 50 | 51 | try { 52 | await e.reply('正在重绘,请稍后...'); 53 | const response = await mjClient.Custom({ 54 | msgId: taskInfo.id.toString(), 55 | customId: rerollCustomID, 56 | flags: taskInfo.flags, 57 | loading: (uri, progress) => { 58 | Log.i(`[${progress}]绘制中,当前状态:${uri}`); 59 | } 60 | }); 61 | 62 | await Promise.all([ 63 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 64 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 65 | ]); 66 | 67 | try { 68 | const base64 = await getPic(response.uri); 69 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 70 | 71 | await e.reply([ 72 | { ...segment.image('base64://' + base64), origin: true }, 73 | ...buttons 74 | ]); 75 | 76 | if (response.options.length > 0) { 77 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 78 | } 79 | } catch (err) { 80 | Log.e(err); 81 | await e.reply(response.uri); 82 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 83 | } 84 | } catch (err) { 85 | Log.e(err); 86 | await e.reply('Midjourney 返回错误:\n' + err, true); 87 | } 88 | return true; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /apps/Setting.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { pluginResources } from '../model/path.js' 3 | import puppeteer from '../../../lib/puppeteer/puppeteer.js' 4 | 5 | export class Setting extends plugin { 6 | constructor() { 7 | super({ 8 | /** 功能名称 */ 9 | name: 'MJ-设置', 10 | /** 功能描述 */ 11 | dsc: 'Midjourney 设置', 12 | event: 'message', 13 | /** 优先级,数字越小等级越高 */ 14 | priority: 1009, 15 | rule: [ 16 | { 17 | /** 命令正则匹配 */ 18 | reg: '^#?(mj|MJ)设置$', 19 | /** 执行方法 */ 20 | fnc: 'setting' 21 | } 22 | ] 23 | }) 24 | } 25 | 26 | async setting(e) { 27 | if (!global.mjClient) { 28 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true) 29 | return true 30 | } 31 | const response = await mjClient.Settings() 32 | const TmpModels = [] 33 | const TmpItem = [] 34 | for ( 35 | let i = 0; 36 | i < response.content.components[0].components[0].options.length; 37 | i++ 38 | ) { 39 | const item = response.content.components[0].components[0].options[i] 40 | TmpItem.push({ 41 | list1: item.emoji ? item.emoji.name + item.label : item.label, 42 | list2: '选择器(单选)', 43 | able: !!item.default 44 | }) 45 | } 46 | TmpModels.push(TmpItem) 47 | 48 | for (let i = 1; i < response.content.components.length; i++) { 49 | const TmpItem = [] 50 | for ( 51 | let j = 0; 52 | j < response.content.components[i].components.length; 53 | j++ 54 | ) { 55 | const item = response.content.components[i].components[j] 56 | TmpItem.push({ 57 | list1: item.emoji ? item.emoji.name + item.label : item.label, 58 | list2: '按钮(单选)', 59 | able: item.style === 3 60 | }) 61 | } 62 | TmpModels.push(TmpItem) 63 | } 64 | const base64 = await puppeteer.screenshot('mj-plugin', { 65 | saveId: 'Setting', 66 | imgType: 'png', 67 | tplFile: `${pluginResources}/listTemp/listTemp.html`, 68 | pluginResources, 69 | header: 'Midjourney 设置', 70 | lable: '在此处设置的内容将会使账号全局生效,请谨慎设置', 71 | sidebar: `设置项:${response.content.components.length}个`, 72 | list1: '选项', 73 | list2: '类型', 74 | modelsGroup: TmpModels, 75 | notice: response.content.content 76 | }) 77 | await e.reply(base64) 78 | 79 | await redis.set(`mj:${e.user_id}`, JSON.stringify(response)) 80 | await redis.set(`mj:${response.id}`, JSON.stringify(response)) 81 | 82 | const optionList = [] 83 | for (let i = 0; i < response.options.length; i++) { 84 | optionList.push(`[${response.options[i].label}]`) 85 | } 86 | if (optionList.length > 0) { 87 | await e.reply( 88 | `[ID:${response.id}]\n可选的操作:\n${optionList.join(' | ')}` 89 | ) 90 | } 91 | return true 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /apps/Shorten.js: -------------------------------------------------------------------------------- 1 | import detectBannedWords from '../components/BannedWords.js' 2 | import { makeButton } from '../components/Button.js' 3 | import plugin from '../../../lib/plugins/plugin.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Shorten extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-优化', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 优化', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?优化([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'shorten' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async shorten (e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const prompt = e.msg.replace(/^#(mj|MJ)?优化/, '').trim() 35 | 36 | const bannedWords = await detectBannedWords(e.msg); 37 | if (bannedWords.length) { 38 | await e.reply(`检测到敏感词:${bannedWords.join(',')},请修改后重试`, true); 39 | return true; 40 | } 41 | 42 | try { 43 | await e.reply('正在优化,请稍后...') 44 | const response = await mjClient.Shorten(prompt) 45 | 46 | await Promise.all([ 47 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 48 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 49 | ]); 50 | 51 | try { 52 | let buttons = await makeButton(response.options.map(option => option.label), response.id) 53 | 54 | await e.reply([ 55 | response.prompts.join('\n'), 56 | ...buttons 57 | ]); 58 | 59 | if (response.options.length > 0) { 60 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 61 | } 62 | } catch (err) { 63 | Log.e(err); 64 | await e.reply('发送内容遇到问题,错误已发送至控制台'); 65 | } 66 | } catch (err) { 67 | Log.e(err); 68 | await e.reply('Midjourney 返回错误:\n' + err, true); 69 | } 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /apps/Update.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { createRequire } from 'module' 3 | import lodash from 'lodash' 4 | import { Restart } from '../../other/restart.js' 5 | 6 | const require = createRequire(import.meta.url) 7 | const { exec, execSync } = require('child_process') 8 | 9 | // 是否在更新中 10 | let uping = false 11 | 12 | /** 13 | * 处理插件更新 14 | */ 15 | export class update extends plugin { 16 | constructor () { 17 | super({ 18 | name: 'MJ-更新插件', 19 | event: 'message', 20 | priority: 1009, 21 | rule: [ 22 | { 23 | reg: '^#mj((插件)?(强制)?更新| update)$', 24 | fnc: 'update' 25 | } 26 | ] 27 | }) 28 | } 29 | 30 | /** 31 | * rule - 更新mj 32 | * @returns 33 | */ 34 | async update () { 35 | if (!this.e.isMaster) return false 36 | 37 | /** 检查是否正在更新中 */ 38 | if (uping) { 39 | await this.reply('已有命令更新中..请勿重复操作') 40 | return 41 | } 42 | 43 | /** 检查git安装 */ 44 | if (!(await this.checkGit())) return 45 | 46 | const isForce = this.e.msg.includes('强制') 47 | 48 | /** 执行更新 */ 49 | await this.runUpdate(isForce) 50 | 51 | /** 是否需要重启 */ 52 | if (this.isUp) { 53 | await this.reply('更新完毕,正在重启云崽以应用更新') 54 | setTimeout(() => this.restart(), 2000) 55 | } 56 | } 57 | 58 | restart () { 59 | new Restart(this.e).restart() 60 | } 61 | 62 | /** 63 | * 更新 64 | * @param {boolean} isForce 是否为强制更新 65 | * @returns 66 | */ 67 | async runUpdate (isForce) { 68 | let command = 'git -C ./plugins/mj-plugin/ pull --no-rebase' 69 | if (isForce) { 70 | command = `git -C ./plugins/mj-plugin/ checkout . && ${command}` 71 | this.e.reply('正在执行强制更新操作,请稍等') 72 | } else { 73 | this.e.reply('正在执行更新操作,请稍等') 74 | } 75 | /** 获取上次提交的commitId,用于获取日志时判断新增的更新日志 */ 76 | this.oldCommitId = await this.getcommitId('mj-plugin') 77 | uping = true 78 | const ret = await this.execSync(command) 79 | uping = false 80 | 81 | if (ret.error) { 82 | logger.mark(`${this.e.logFnc} 更新失败:mj-plugin`) 83 | this.gitErr(ret.error, ret.stdout) 84 | return false 85 | } 86 | 87 | /** 获取插件提交的最新时间 */ 88 | const time = await this.getTime('mj-plugin') 89 | 90 | if (/(Already up[ -]to[ -]date|已经是最新的)/.test(ret.stdout)) { 91 | await this.reply(`mj-plugin已经是最新版本\n最后更新时间:${time}`) 92 | } else { 93 | await this.reply(`mj-plugin\n最后更新时间:${time}`) 94 | this.isUp = true 95 | /** 获取mj-plugin的更新日志 */ 96 | const log = await this.getLog('mj-plugin') 97 | await this.reply(log) 98 | } 99 | 100 | logger.mark(`${this.e.logFnc} 最后更新时间:${time}`) 101 | 102 | return true 103 | } 104 | 105 | /** 106 | * 获取mj-plugin的更新日志 107 | * @param {string} plugin 插件名称 108 | * @returns 109 | */ 110 | async getLog (plugin = '') { 111 | const cm = `cd ./plugins/${plugin}/ && git log -20 --oneline --pretty=format:"%h||[%cd] %s" --date=format:"%m-%d %H:%M"` 112 | 113 | let logAll 114 | try { 115 | logAll = await execSync(cm, { encoding: 'utf-8' }) 116 | } catch (error) { 117 | logger.error(error.toString()) 118 | this.reply(error.toString()) 119 | } 120 | 121 | if (!logAll) return false 122 | 123 | logAll = logAll.split('\n') 124 | 125 | let log = [] 126 | for (let str of logAll) { 127 | str = str.split('||') 128 | if (str[0] == this.oldCommitId) break 129 | if (str[1].includes('Merge branch')) continue 130 | log.push(str[1]) 131 | } 132 | const line = log.length 133 | log = log.join('\n\n') 134 | 135 | if (log.length <= 0) return '' 136 | 137 | let end = '' 138 | end = 139 | '更多详细信息,请前往github查看\nhttps://github.com/CikeyQi/mj-plugin/commits/main' 140 | 141 | log = await this.makeForwardMsg(`mj-plugin更新日志,共${line}条`, log, end) 142 | 143 | return log 144 | } 145 | 146 | /** 147 | * 获取上次提交的commitId 148 | * @param {string} plugin 插件名称 149 | * @returns 150 | */ 151 | async getcommitId (plugin = '') { 152 | const cm = `git -C ./plugins/${plugin}/ rev-parse --short HEAD` 153 | 154 | let commitId = await execSync(cm, { encoding: 'utf-8' }) 155 | commitId = lodash.trim(commitId) 156 | 157 | return commitId 158 | } 159 | 160 | /** 161 | * 获取本次更新插件的最后一次提交时间 162 | * @param {string} plugin 插件名称 163 | * @returns 164 | */ 165 | async getTime (plugin = '') { 166 | const cm = `cd ./plugins/${plugin}/ && git log -1 --oneline --pretty=format:"%cd" --date=format:"%m-%d %H:%M"` 167 | 168 | let time = '' 169 | try { 170 | time = await execSync(cm, { encoding: 'utf-8' }) 171 | time = lodash.trim(time) 172 | } catch (error) { 173 | logger.error(error.toString()) 174 | time = '获取时间失败' 175 | } 176 | return time 177 | } 178 | 179 | /** 180 | * 制作转发消息 181 | * @param {string} title 标题 - 首条消息 182 | * @param {string} msg 日志信息 183 | * @param {string} end 最后一条信息 184 | * @returns 185 | */ 186 | async makeForwardMsg (title, msg, end) { 187 | let nickname = Bot.nickname 188 | if (this.e.isGroup) { 189 | const info = await Bot.getGroupMemberInfo(this.e.group_id, Bot.uin) 190 | nickname = info.card || info.nickname 191 | } 192 | const userInfo = { 193 | user_id: Bot.uin, 194 | nickname 195 | } 196 | 197 | let forwardMsg = [ 198 | { 199 | ...userInfo, 200 | message: title 201 | }, 202 | { 203 | ...userInfo, 204 | message: msg 205 | } 206 | ] 207 | 208 | if (end) { 209 | forwardMsg.push({ 210 | ...userInfo, 211 | message: end 212 | }) 213 | } 214 | 215 | /** 制作转发内容 */ 216 | if (this.e.isGroup) { 217 | forwardMsg = await this.e.group.makeForwardMsg(forwardMsg) 218 | } else { 219 | forwardMsg = await this.e.friend.makeForwardMsg(forwardMsg) 220 | } 221 | 222 | /** 处理描述 */ 223 | forwardMsg.data = forwardMsg.data 224 | .replace(/\n/g, '') 225 | .replace(/(.+?)<\/title>/g, '___') 226 | .replace(/___+/, `<title color="#777777" size="26">${title}`) 227 | 228 | return forwardMsg 229 | } 230 | 231 | /** 232 | * 处理更新失败的相关函数 233 | * @param {string} err 234 | * @param {string} stdout 235 | * @returns 236 | */ 237 | async gitErr (err, stdout) { 238 | const msg = '更新失败!' 239 | const errMsg = err.toString() 240 | stdout = stdout.toString() 241 | 242 | if (errMsg.includes('Timed out')) { 243 | const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') 244 | await this.reply(msg + `\n连接超时:${remote}`) 245 | return 246 | } 247 | 248 | if (/Failed to connect|unable to access/g.test(errMsg)) { 249 | const remote = errMsg.match(/'(.+?)'/g)[0].replace(/'/g, '') 250 | await this.reply(msg + `\n连接失败:${remote}`) 251 | return 252 | } 253 | 254 | if (errMsg.includes('be overwritten by merge')) { 255 | await this.reply( 256 | msg + 257 | `存在冲突:\n${errMsg}\n` + 258 | '请解决冲突后再更新,或者执行#强制更新,放弃本地修改' 259 | ) 260 | return 261 | } 262 | 263 | if (stdout.includes('CONFLICT')) { 264 | await this.reply([ 265 | msg + '存在冲突\n', 266 | errMsg, 267 | stdout, 268 | '\n请解决冲突后再更新,或者执行#强制更新,放弃本地修改' 269 | ]) 270 | return 271 | } 272 | 273 | await this.reply([errMsg, stdout]) 274 | } 275 | 276 | /** 277 | * 异步执行git相关命令 278 | * @param {string} cmd git命令 279 | * @returns 280 | */ 281 | async execSync (cmd) { 282 | return new Promise((resolve, reject) => { 283 | exec(cmd, { windowsHide: true }, (error, stdout, stderr) => { 284 | resolve({ error, stdout, stderr }) 285 | }) 286 | }) 287 | } 288 | 289 | /** 290 | * 检查git是否安装 291 | * @returns 292 | */ 293 | async checkGit () { 294 | const ret = await execSync('git --version', { encoding: 'utf-8' }) 295 | if (!ret || !ret.includes('git version')) { 296 | await this.reply('请先安装git') 297 | return false 298 | } 299 | return true 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /apps/Upscale.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Upscale extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-放大', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 放大', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?放大([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'upscale' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async upscale (e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const taskId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 35 | const taskInfoRaw = await redis.get(`mj:${taskId}`); 36 | const taskInfo = taskInfoRaw && JSON.parse(taskInfoRaw); 37 | 38 | if (!taskInfo) { 39 | const replyMsg = taskId !== e.user_id ? '未找到指定的绘制结果,请检查ID是否正确' : '未找到上一次的绘制结果,请先使用 #mj绘制'; 40 | await e.reply(replyMsg, true); 41 | return true; 42 | } 43 | 44 | const Num = e.msg.match(/第([一二三四五六七八九十\d]+)张/)?.[1]; 45 | if (!Num) { 46 | await e.reply('请指定要使用放大的图片,例如“#mj放大第一张”', true); 47 | return true; 48 | } 49 | const index = Num.replace(/[一二三四五六七八九十]/g, s => '一二三四五六七八九十'.indexOf(s) + 1); 50 | 51 | if (index < 1 || index > 4) { 52 | await e.reply('图片序号超出范围,请指定1-4之间的数字', true); 53 | return true; 54 | } 55 | 56 | const upscaleCustomID = taskInfo.options?.find(o => o.label === `U${index}`)?.custom; 57 | if (!upscaleCustomID) { 58 | await e.reply(`上一次的绘制结果不允许使用U${index},请先使用 #mj绘制`, true); 59 | return true; 60 | } 61 | 62 | try { 63 | await e.reply('正在放大,请稍后...') 64 | const response = await mjClient.Custom({ 65 | msgId: taskInfo.id.toString(), 66 | customId: upscaleCustomID, 67 | flags: taskInfo.flags, 68 | loading: (uri, progress) => { 69 | Log.i(`[${progress}]绘制中,当前状态:${uri}`) 70 | } 71 | }) 72 | 73 | await Promise.all([ 74 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 75 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 76 | ]); 77 | 78 | try { 79 | const base64 = await getPic(response.uri); 80 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 81 | 82 | await e.reply([ 83 | { ...segment.image('base64://' + base64), origin: true }, 84 | ...buttons 85 | ]); 86 | 87 | if (response.options.length > 0) { 88 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 89 | } 90 | } catch (err) { 91 | Log.e(err); 92 | await e.reply(response.uri); 93 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 94 | } 95 | } catch (err) { 96 | Log.e(err); 97 | await e.reply('Midjourney 返回错误:\n' + err, true); 98 | } 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /apps/Variation.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Variation extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-变化', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 变化', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?变化([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'variation' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async variation (e) { 28 | if (!global.mjClient) { 29 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 30 | return true; 31 | } 32 | 33 | const taskId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 34 | const taskInfoRaw = await redis.get(`mj:${taskId}`); 35 | const taskInfo = taskInfoRaw && JSON.parse(taskInfoRaw); 36 | 37 | if (!taskInfo) { 38 | const replyMsg = taskId !== e.user_id ? '未找到指定的绘制结果,请检查ID是否正确' : '未找到上一次的绘制结果,请先使用 #mj绘制'; 39 | await e.reply(replyMsg, true); 40 | return true; 41 | } 42 | 43 | const Num = e.msg.match(/第([一二三四五六七八九十\d]+)张/)?.[1]; 44 | if (!Num) { 45 | await e.reply('请指定要使用放大的图片,例如“#mj放大第一张”', true); 46 | return true; 47 | } 48 | const index = Num.replace(/[一二三四五六七八九十]/g, s => '一二三四五六七八九十'.indexOf(s) + 1); 49 | 50 | if (index < 1 || index > 4) { 51 | await e.reply('图片序号超出范围,请指定1-4之间的数字', true); 52 | return true; 53 | } 54 | 55 | const upscaleCustomID = taskInfo.options?.find(o => o.label === `V${index}`)?.custom; 56 | if (!upscaleCustomID) { 57 | await e.reply(`上一次的绘制结果不允许使用U${index},请先使用 #mj绘制`, true); 58 | return true; 59 | } 60 | 61 | try { 62 | await e.reply('正在变化,请稍后...') 63 | const response = await mjClient.Custom({ 64 | msgId: taskInfo.id.toString(), 65 | customId: variationCustomID, 66 | content: taskInfo.content?.match(/(?<=\*\*).+?(?=\*\*)/)?.[0], 67 | flags: taskInfo.flags, 68 | loading: (uri, progress) => { 69 | Log.i(`[${progress}]绘制中,当前状态:${uri}`) 70 | } 71 | }) 72 | 73 | await Promise.all([ 74 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 75 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 76 | ]); 77 | 78 | try { 79 | const base64 = await getPic(response.uri); 80 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 81 | 82 | await e.reply([ 83 | { ...segment.image('base64://' + base64), origin: true }, 84 | ...buttons 85 | ]); 86 | 87 | if (response.options.length > 0) { 88 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 89 | } 90 | } catch (err) { 91 | Log.e(err); 92 | await e.reply(response.uri); 93 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 94 | } 95 | } catch (err) { 96 | Log.e(err); 97 | await e.reply('Midjourney 返回错误:\n' + err, true); 98 | } 99 | return true; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /apps/Vary.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Vary extends plugin { 7 | constructor() { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-调整', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 调整', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?调整([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'vary' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async vary(e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const taskId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 35 | const taskInfoJson = await redis.get(`mj:${taskId}`); 36 | const taskInfo = JSON.parse(taskInfoJson || '{}'); 37 | 38 | if (!taskInfo || !taskInfoJson) { 39 | await e.reply( 40 | `未找到${taskId === e.user_id ? '上一次的' : '指定的'}绘制结果,请${e.msg.match(/\d{19}/) ? '检查ID是否正确' : '先使用 #mj绘制'}`, 41 | true 42 | ); 43 | return true; 44 | } 45 | 46 | const adjustment = e.msg.includes('微妙') ? 'Vary (Subtle)' 47 | : e.msg.includes('强大') ? 'Vary (Strong)' 48 | : undefined; 49 | 50 | if (!adjustment) { 51 | await e.reply('请指定要使用的调整选项,例如“#mj调整微妙/强大”', true); 52 | return true; 53 | } 54 | 55 | const varyCustomID = taskInfo.options?.find(o => o.label === adjustment)?.custom; 56 | 57 | if (!varyCustomID) { 58 | await e.reply(`上一次的绘制结果不允许使用${adjustment},请先使用 #mj放大`); 59 | return true; 60 | } 61 | 62 | const content = taskInfo.content?.match(/\*\*(.+?)\*\*/)?.[1]; 63 | 64 | try { 65 | await e.reply(`正在对图片${index}处理,请稍后...`) 66 | const response = await mjClient.Custom({ 67 | msgId: taskInfo.id.toString(), 68 | customId: varyCustomID, 69 | content: content, 70 | flags: taskInfo.flags, 71 | loading: (uri, progress) => { 72 | Log.i(`[${progress}]绘制中,当前状态:${uri}`) 73 | } 74 | }) 75 | 76 | await Promise.all([ 77 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 78 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 79 | ]); 80 | 81 | try { 82 | const base64 = await getPic(response.uri); 83 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 84 | 85 | await e.reply([ 86 | { ...segment.image('base64://' + base64), origin: true }, 87 | ...buttons 88 | ]); 89 | 90 | if (response.options.length > 0) { 91 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 92 | } 93 | } catch (err) { 94 | Log.e(err); 95 | await e.reply(response.uri); 96 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 97 | } 98 | } catch (err) { 99 | Log.e(err); 100 | await e.reply('Midjourney 返回错误:\n' + err, true); 101 | } 102 | return true; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /apps/Zoomout.js: -------------------------------------------------------------------------------- 1 | import plugin from '../../../lib/plugins/plugin.js' 2 | import { makeButton } from '../components/Button.js' 3 | import getPic from '../components/Proxy.js' 4 | import Log from '../utils/logs.js' 5 | 6 | export class Zoomout extends plugin { 7 | constructor () { 8 | super({ 9 | /** 功能名称 */ 10 | name: 'MJ-拓展', 11 | /** 功能描述 */ 12 | dsc: 'Midjourney 拓展', 13 | event: 'message', 14 | /** 优先级,数字越小等级越高 */ 15 | priority: 1009, 16 | rule: [ 17 | { 18 | /** 命令正则匹配 */ 19 | reg: '^#(mj|MJ)?拓展([\\s\\S]*)$', 20 | /** 执行方法 */ 21 | fnc: 'zoomout' 22 | } 23 | ] 24 | }) 25 | } 26 | 27 | async zoomout (e) { 28 | 29 | if (!global.mjClient) { 30 | await e.reply('未连接到 Midjourney Bot,请先使用 #mj连接', true); 31 | return true; 32 | } 33 | 34 | const taskId = e.msg.match(/\d{19}/)?.[0] || e.user_id; 35 | const taskInfoStr = await redis.get(`mj:${taskId}`); 36 | const taskInfo = taskInfoStr ? JSON.parse(taskInfoStr) : null; 37 | 38 | if (!taskInfo) { 39 | const replyMessage = taskId === e.user_id 40 | ? '未找到上一次的绘制结果,请先使用 #mj绘制' 41 | : '未找到指定的绘制结果,请检查ID是否正确'; 42 | await e.reply(replyMessage, true); 43 | return true; 44 | } 45 | const zoomRegex = /(?2x|1.5x|custom\d+)/; 46 | const zoomMatch = e.msg.match(zoomRegex); 47 | const zoom = zoomMatch ? zoomMatch.groups.zoom : null; 48 | let customName; 49 | 50 | switch (zoom) { 51 | case '2x': 52 | customName = 'Zoom Out 2x'; 53 | break; 54 | case '1.5x': 55 | customName = 'Zoom Out 1.5x'; 56 | break; 57 | default: 58 | if (zoom && zoom.startsWith('custom')) { 59 | const customNum = parseInt(zoom.replace('custom', ''), 10); 60 | if (isNaN(customNum) || customNum < 1 || customNum > 10) { 61 | await e.reply('拓展倍数超出范围或不正确,请指定1-10之间的数字', true); 62 | return true; 63 | } 64 | taskInfo.content += 65 | ` --zoom ${customNum}`; 66 | customName = 'Custom Zoom'; 67 | break; 68 | } 69 | 70 | await e.reply('请指定要使用的拓展倍数,例如“#mj拓展2x/1.5x/custom2”', true); 71 | return true; 72 | } 73 | 74 | const zoomoutCustomID = taskInfo.options?.find( 75 | (o) => o.label === customName 76 | )?.custom; 77 | 78 | if (!zoomoutCustomID) { 79 | await e.reply( 80 | `上一次的绘制结果不允许使用${customName},请先使用 #mj放大` 81 | ) 82 | return true; 83 | } 84 | 85 | try { 86 | await e.reply(`正在进行${customName}拓展,请稍后...`) 87 | const response = await mjClient.Custom({ 88 | msgId: taskInfo.id.toString(), 89 | customId: zoomoutCustomID, 90 | content: taskInfo.content?.match(/(?<=\*\*).+?(?=\*\*)/)?.[0], 91 | flags: taskInfo.flags, 92 | loading: (uri, progress) => { 93 | Log.i(`[${progress}]绘制中,当前状态:${uri}`) 94 | } 95 | }) 96 | 97 | await Promise.all([ 98 | redis.set(`mj:${e.user_id}`, JSON.stringify(response)), 99 | redis.set(`mj:${response.id}`, JSON.stringify(response)) 100 | ]); 101 | 102 | try { 103 | const base64 = await getPic(response.uri); 104 | let buttons = await makeButton(response.options.map(option => option.label), response.id); 105 | 106 | await e.reply([ 107 | { ...segment.image('base64://' + base64), origin: true }, 108 | ...buttons 109 | ]); 110 | 111 | if (response.options.length > 0) { 112 | await e.reply(`[ID:${response.id}]\n可选的操作:\n${response.options.map(option => `[${option.label}]`).join(' | ')}`); 113 | } 114 | } catch (err) { 115 | Log.e(err); 116 | await e.reply(response.uri); 117 | await e.reply('发送图片遇到问题,错误已发送至控制台'); 118 | } 119 | } catch (err) { 120 | Log.e(err); 121 | await e.reply('Midjourney 返回错误:\n' + err, true); 122 | } 123 | return true; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /components/BannedWords.js: -------------------------------------------------------------------------------- 1 | export const bannedWords = [ 2 | 'hot slut', 3 | 'blood', 4 | 'twerk', 5 | 'making love', 6 | 'voluptuous', 7 | 'naughty', 8 | 'wincest', 9 | 'orgy', 10 | 'no clothes', 11 | 'au naturel', 12 | 'no shirt', 13 | 'decapitate', 14 | 'bare', 15 | 'nude', 16 | 'barely dressed', 17 | 'nude', 18 | 'bra', 19 | 'risque', 20 | 'scantily clad', 21 | 'cleavage', 22 | 'stripped', 23 | 'infested', 24 | 'full frontal', 25 | 'unclothed', 26 | 'invisible clothes', 27 | 'wearing nothing', 28 | 'lingerie', 29 | 'with no shirt', 30 | 'naked', 31 | 'without clothes on', 32 | 'negligee', 33 | 'zero clothes', 34 | 'gruesome', 35 | 'fascist', 36 | 'nazi', 37 | 'prophet mohammed', 38 | 'slave', 39 | 'coon', 40 | 'honkey', 41 | 'cocaine', 42 | 'heroin', 43 | 'meth', 44 | 'crack', 45 | 'kill', 46 | 'belle delphine', 47 | 'hitler', 48 | 'jinping', 49 | 'lolita', 50 | 'president xi', 51 | 'torture', 52 | 'disturbing', 53 | 'farts', 54 | 'fart', 55 | 'poop', 56 | 'infected', 57 | 'warts', 58 | 'shit', 59 | 'brown pudding', 60 | 'bunghole', 61 | 'vomit', 62 | 'voluptuous', 63 | 'seductive', 64 | 'sperm', 65 | 'sexy', 66 | 'sadist', 67 | 'sensored', 68 | 'censored', 69 | 'silenced', 70 | 'deepfake', 71 | 'inappropriate', 72 | 'waifu', 73 | 'succubus', 74 | 'slaughter', 75 | 'surgery', 76 | 'reproduce', 77 | 'crucified', 78 | 'seductively', 79 | 'explicit', 80 | 'inappropriate', 81 | 'large bust', 82 | 'explicit', 83 | 'wang', 84 | 'inappropriate', 85 | 'teratoma', 86 | 'intimate', 87 | 'see through', 88 | 'tryphophobia', 89 | 'bloodbath', 90 | 'wound', 91 | 'cronenberg', 92 | 'khorne', 93 | 'cannibal', 94 | 'cannibalism', 95 | 'visceral', 96 | 'guts', 97 | 'bloodshot', 98 | 'gory', 99 | 'killing', 100 | 'crucifixion', 101 | 'surgery', 102 | 'vivisection', 103 | 'massacre', 104 | 'hemoglobin', 105 | 'suicide', 106 | 'arse', 107 | 'labia', 108 | 'ass', 109 | 'mammaries', 110 | 'badonkers', 111 | 'bloody', 112 | 'minge', 113 | 'big ass', 114 | 'mommy milker', 115 | 'booba', 116 | 'nipple', 117 | 'oppai', 118 | 'booty', 119 | 'organs', 120 | 'bosom', 121 | 'ovaries', 122 | 'flesh', 123 | 'breasts', 124 | 'penis', 125 | 'busty', 126 | 'phallus', 127 | 'clunge', 128 | 'sexy female', 129 | 'crotch', 130 | 'skimpy', 131 | 'dick', 132 | 'thick', 133 | 'bruises', 134 | 'girth', 135 | 'titty', 136 | 'honkers', 137 | 'vagina', 138 | 'hooters', 139 | 'veiny', 140 | 'knob', 141 | 'ahegao', 142 | 'pinup', 143 | 'ballgag', 144 | 'car crash', 145 | 'playboy', 146 | 'bimbo', 147 | 'pleasure', 148 | 'bodily fluids', 149 | 'pleasures', 150 | 'boudoir', 151 | 'rule34', 152 | 'brothel', 153 | 'seducing', 154 | 'dominatrix', 155 | 'corpse', 156 | 'seductive', 157 | 'erotic', 158 | 'seductive', 159 | 'fuck', 160 | 'sensual', 161 | 'hardcore', 162 | 'sexy', 163 | 'hentai', 164 | 'shag', 165 | 'horny', 166 | 'crucified', 167 | 'shibari', 168 | 'incest', 169 | 'smut', 170 | 'jav', 171 | 'succubus', 172 | 'jerk off king at pic', 173 | 'thot', 174 | 'kinbaku', 175 | 'legs spread', 176 | 'sensuality', 177 | 'belly button', 178 | 'porn', 179 | 'patriotic', 180 | 'bleed', 181 | 'excrement', 182 | 'petite', 183 | 'seduction', 184 | 'mccurry', 185 | 'provocative', 186 | 'sultry', 187 | 'erected', 188 | 'camisole', 189 | 'tight white', 190 | 'arrest', 191 | 'see-through', 192 | 'feces', 193 | 'anus', 194 | 'revealing clothing', 195 | 'vein', 196 | 'loli', 197 | '-edge', 198 | 'boobs', 199 | '-backed', 200 | 'tied up', 201 | 'zedong', 202 | 'bathing', 203 | 'jail', 204 | 'reticulum', 205 | 'rear end', 206 | 'sakimichan', 207 | 'behind bars', 208 | 'shirtless', 209 | 'sakimichan', 210 | 'seductive', 211 | 'sexi', 212 | 'sexualiz', 213 | 'sexual' 214 | ] 215 | export default function detectBannedWords(prompt) { 216 | return bannedWords.filter(word => prompt.toLowerCase().includes(word.toLowerCase())); 217 | } 218 | -------------------------------------------------------------------------------- /components/Button.js: -------------------------------------------------------------------------------- 1 | import Config from './Config.js'; 2 | 3 | export async function makeButton(data, id) { 4 | const count = await Config.getConfig().button_row; 5 | 6 | if (count === 0) { 7 | const totalLength = data.join('').length; 8 | const groupSize = Math.ceil(totalLength / 5); 9 | 10 | let groups = []; 11 | let currentGroup = []; 12 | let currentGroupLength = 0; 13 | 14 | data.forEach((item) => { 15 | if (currentGroupLength + item.length > groupSize && currentGroup.length > 0) { 16 | const overflowLength = currentGroupLength + item.length - groupSize; 17 | groups.push(segment.button(currentGroup)); 18 | currentGroup = []; 19 | currentGroupLength = -overflowLength; 20 | } 21 | 22 | currentGroup.push({ text: item, callback: `#mj按钮${item}${id ? ` ${id}` : ''}` }); 23 | currentGroupLength += item.length; 24 | }); 25 | 26 | if (currentGroup.length > 0) { 27 | groups.push(segment.button(currentGroup)); 28 | } 29 | 30 | return groups; 31 | } else { 32 | 33 | const result = []; 34 | 35 | for (let i = 0; i < data.length; i += count) { 36 | const buttons = data.slice(i, i + count).map(item => ({ 37 | text: item, 38 | callback: `#mj按钮${item}${id ? ` ${id}` : ''}` 39 | })); 40 | 41 | result.push(segment.button(buttons)); 42 | } 43 | return result; 44 | } 45 | } -------------------------------------------------------------------------------- /components/Config.js: -------------------------------------------------------------------------------- 1 | import YAML from 'yaml' 2 | import fs from 'fs' 3 | import { pluginRoot } from '../model/path.js' 4 | import Log from '../utils/logs.js' 5 | 6 | class Config { 7 | getConfig () { 8 | try { 9 | const config_yaml = YAML.parse( 10 | fs.readFileSync(`${pluginRoot}/config/config/config.yaml`, 'utf-8') 11 | ) 12 | return config_yaml 13 | } catch (err) { 14 | Log.e('读取config.yaml失败', err) 15 | return false 16 | } 17 | } 18 | 19 | getDefConfig () { 20 | try { 21 | const config_default_yaml = YAML.parse( 22 | fs.readFileSync(`${pluginRoot}/config/config_default.yaml`, 'utf-8') 23 | ) 24 | return config_default_yaml 25 | } catch (err) { 26 | Log.e('读取config_default.yaml失败', err) 27 | return false 28 | } 29 | } 30 | 31 | setConfig (config_data) { 32 | try { 33 | fs.writeFileSync( 34 | `${pluginRoot}/config/config/config.yaml`, 35 | YAML.stringify(config_data) 36 | ) 37 | return true 38 | } catch (err) { 39 | Log.e('写入config.yaml失败', err) 40 | return false 41 | } 42 | } 43 | } 44 | 45 | export default new Config() 46 | -------------------------------------------------------------------------------- /components/Proxy.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | import { HttpsProxyAgent } from 'https-proxy-agent' 3 | import Config from '../components/Config.js' 4 | 5 | async function getPic (imageUrl) { 6 | let agent = null 7 | if (Config.getConfig().proxy) { 8 | agent = new HttpsProxyAgent(Config.getConfig().proxy_url, { 9 | keepAlive: true 10 | }) 11 | } 12 | const res = await fetch(imageUrl, { 13 | agent 14 | }) 15 | const base64 = await res.arrayBuffer().then((buffer) => { 16 | return Buffer.from(buffer).toString('base64') 17 | }) 18 | return base64 19 | } 20 | 21 | export default getPic 22 | -------------------------------------------------------------------------------- /components/Version.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | let packageJson = JSON.parse(fs.readFileSync('package.json', 'utf8')) 4 | 5 | const yunzaiVersion = packageJson.version 6 | const isMiao = packageJson.name === 'miao-yunzai' 7 | const isTrss = Array.isArray(Bot.uin) ? true : false 8 | 9 | let Version = { 10 | isMiao, 11 | isTrss, 12 | get version() { 13 | return currentVersion 14 | }, 15 | get yunzai() { 16 | return yunzaiVersion 17 | } 18 | } 19 | 20 | export default Version 21 | -------------------------------------------------------------------------------- /config/config/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/config/config/.keep -------------------------------------------------------------------------------- /config/config_default.yaml: -------------------------------------------------------------------------------- 1 | server_id: "" 2 | channel_id: "" 3 | salai_token: "" 4 | bot_type: "Midjourney" 5 | huggingface_token: "" 6 | debug: false 7 | proxy: false 8 | proxy_url: "http://127.0.0.1:7890" 9 | button_row: 3 10 | translate_use: 0 11 | baidu_translate: 12 | appid: "" 13 | appkey: "" 14 | youdao_translate: 15 | appid: "" 16 | appkey: "" 17 | -------------------------------------------------------------------------------- /guoba.support.js: -------------------------------------------------------------------------------- 1 | import Config from "./components/Config.js"; 2 | import lodash from "lodash"; 3 | import path from "path"; 4 | import { pluginRoot } from "./model/path.js"; 5 | 6 | export function supportGuoba() { 7 | return { 8 | pluginInfo: { 9 | name: 'mj-plugin', 10 | title: 'AI绘制插件', 11 | author: ['@CikeyQi', '@erzaozi'], 12 | authorLink: ['https://github.com/CikeyQi', 'https://github.com/erzaozi'], 13 | link: 'https://github.com/CikeyQi/mj-plugin', 14 | isV3: true, 15 | isV2: false, 16 | showInMenu: true, 17 | description: '基于 Yunzai 的 AI 绘图插件,使用 Midjourney 接口', 18 | // 显示图标,此为个性化配置 19 | // 图标可在 https://icon-sets.iconify.design 这里进行搜索 20 | icon: 'icon-park:sailboat', 21 | // 图标颜色,例:#FF0000 或 rgb(255, 0, 0) 22 | iconColor: '#fffbf0', 23 | // 如果想要显示成图片,也可以填写图标路径(绝对路径) 24 | iconPath: path.join(pluginRoot, 'resources/readme/girl.png'), 25 | }, 26 | configInfo: { 27 | schemas: [ 28 | { 29 | component: "Divider", 30 | label: "Discord 相关配置", 31 | componentProps: { 32 | orientation: "left", 33 | plain: true, 34 | }, 35 | }, 36 | { 37 | field: "server_id", 38 | label: "服务器ID", 39 | bottomHelpMessage: "Discord服务器ID", 40 | component: "Input", 41 | componentProps: { 42 | placeholder: 'Server ID', 43 | maxlength: 19, 44 | }, 45 | }, 46 | { 47 | field: "channel_id", 48 | label: "频道ID", 49 | bottomHelpMessage: "Discord频道ID", 50 | component: "Input", 51 | componentProps: { 52 | placeholder: 'Channel ID', 53 | maxlength: 19, 54 | }, 55 | }, 56 | { 57 | field: "salai_token", 58 | label: "Discord Token", 59 | bottomHelpMessage: "Discord身份验证Token", 60 | component: "InputPassword", 61 | componentProps: { 62 | placeholder: 'Salai Token', 63 | visible: false, 64 | }, 65 | }, 66 | { 67 | component: "Divider", 68 | label: "Bot 相关配置", 69 | componentProps: { 70 | orientation: "left", 71 | plain: true, 72 | }, 73 | }, 74 | { 75 | field: "bot_type", 76 | label: "Bot类型选择", 77 | component: "Select", 78 | componentProps: { 79 | options: [ 80 | { label: 'Midjourney Bot', value: 'Midjourney' }, 81 | { label: 'niji・journey Bot', value: 'Nijijourney' }, 82 | ], 83 | }, 84 | }, 85 | { 86 | component: "Divider", 87 | label: "代理 相关配置", 88 | componentProps: { 89 | orientation: "left", 90 | plain: true, 91 | }, 92 | }, 93 | { 94 | field: "proxy", 95 | label: "使用代理", 96 | component: "Switch", 97 | }, 98 | { 99 | field: "proxy_url", 100 | label: "代理地址", 101 | component: "Input", 102 | componentProps: { 103 | placeholder: 'Proxy URL', 104 | }, 105 | }, 106 | { 107 | component: "Divider", 108 | label: "翻译 相关配置", 109 | componentProps: { 110 | orientation: "left", 111 | plain: true, 112 | }, 113 | }, 114 | { 115 | field: "translate_use", 116 | label: "使用翻译接口", 117 | component: "Select", 118 | componentProps: { 119 | options: [ 120 | { label: '椰奶有道', value: 0 }, 121 | { label: '百度翻译(需配置)', value: 1 }, 122 | { label: '有道翻译(需配置)', value: 2 }, 123 | ], 124 | }, 125 | }, 126 | { 127 | field: "baidu_translate.appid", 128 | label: "百度APP ID", 129 | component: "Input", 130 | componentProps: { 131 | placeholder: "请输入百度翻译APPID", 132 | maxlength: 17, 133 | pattern: "^[0-9]*$", 134 | visible: true, 135 | }, 136 | }, 137 | { 138 | field: "baidu_translate.appkey", 139 | label: "百度Secret Key", 140 | component: "InputPassword", 141 | componentProps: { 142 | placeholder: "请输入百度翻译APPKEY", 143 | maxlength: 20, 144 | visible: false, 145 | }, 146 | }, 147 | { 148 | field: "youdao_translate.appid", 149 | label: "有道APP ID", 150 | component: "Input", 151 | componentProps: { 152 | placeholder: "请输入有道翻译APPID", 153 | maxlength: 16, 154 | visible: true, 155 | }, 156 | }, 157 | { 158 | field: "youdao_translate.appkey", 159 | label: "有道Secret Key", 160 | component: "InputPassword", 161 | componentProps: { 162 | placeholder: "请输入有道翻译APPKEY", 163 | maxlength: 32, 164 | visible: false, 165 | }, 166 | }, 167 | { 168 | component: "Divider", 169 | label: "其他 相关配置", 170 | componentProps: { 171 | orientation: "left", 172 | plain: true, 173 | }, 174 | }, 175 | { 176 | field: "huggingface_token", 177 | label: "Huggingface Token", 178 | component: "Input", 179 | componentProps: { 180 | placeholder: 'HuggingFace Token', 181 | }, 182 | }, 183 | { 184 | field: "button_row", 185 | label: "单行按钮数量", 186 | component: "Select", 187 | componentProps: { 188 | options: [ 189 | { label: '自适应', value: 0}, 190 | { label: '一个按钮', value: 1 }, 191 | { label: '两个按钮', value: 2 }, 192 | { label: '三个按钮', value: 3 }, 193 | { label: '四个按钮', value: 4 }, 194 | { label: '五个按钮', value: 5 }, 195 | ], 196 | }, 197 | }, 198 | { 199 | field: "debug", 200 | label: "调试模式", 201 | component: "Switch", 202 | }, 203 | ], 204 | getConfigData() { 205 | let config = Config.getConfig() 206 | return config 207 | }, 208 | 209 | setConfigData(data, { Result }) { 210 | let config = {} 211 | for (let [keyPath, value] of Object.entries(data)) { 212 | lodash.set(config, keyPath, value) 213 | } 214 | config = lodash.merge({}, Config.getConfig(), config) 215 | config.proxy_url = config.proxy_url.replace(/\/$/, '') 216 | Config.setConfig(config) 217 | return Result.ok({}, '保存成功~') 218 | }, 219 | }, 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | if (!global.segment) { 4 | global.segment = (await import("oicq")).segment; 5 | } 6 | 7 | let ret = []; 8 | 9 | logger.info(logger.yellow("- 正在载入 MJ-PLUGIN")); 10 | 11 | const files = fs 12 | .readdirSync('./plugins/mj-plugin/apps') 13 | .filter((file) => file.endsWith('.js')); 14 | 15 | files.forEach((file) => { 16 | ret.push(import(`./apps/${file}`)) 17 | }) 18 | 19 | ret = await Promise.allSettled(ret); 20 | 21 | let apps = {}; 22 | for (let i in files) { 23 | let name = files[i].replace('.js', ''); 24 | 25 | if (ret[i].status !== 'fulfilled') { 26 | logger.error(`载入插件错误:${logger.red(name)}`); 27 | logger.error(ret[i].reason); 28 | continue; 29 | } 30 | apps[name] = ret[i].value[Object.keys(ret[i].value)[0]]; 31 | } 32 | 33 | logger.info(logger.green("- MJ-PLUGIN 载入成功")); 34 | logger.info(logger.magenta(`- 欢迎加入新组织【貓娘樂園🍥🏳️‍⚧️】(群号 707331865)`)); 35 | 36 | export { apps }; -------------------------------------------------------------------------------- /model/init.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { Main } from '../apps/Main.js' 3 | import Config from '../components/Config.js' 4 | import { pluginRoot } from '../model/path.js' 5 | import Log from '../utils/logs.js' 6 | 7 | class Init { 8 | constructor () { 9 | this.initConfig() 10 | } 11 | 12 | initConfig () { 13 | const config_default_path = `${pluginRoot}/config/config_default.yaml` 14 | if (!fs.existsSync(config_default_path)) { 15 | Log.e('默认设置文件不存在,请检查或重新安装插件') 16 | return true 17 | } 18 | const config_path = `${pluginRoot}/config/config/config.yaml` 19 | if (!fs.existsSync(config_path)) { 20 | Log.e('设置文件不存在,将使用默认设置文件') 21 | fs.copyFileSync(config_default_path, config_path) 22 | } 23 | const config_default_yaml = Config.getDefConfig() 24 | const config_yaml = Config.getConfig() 25 | for (const key in config_default_yaml) { 26 | if (!(key in config_yaml)) { 27 | config_yaml[key] = config_default_yaml[key] 28 | } 29 | } 30 | for (const key in config_yaml) { 31 | if (!(key in config_default_yaml)) { 32 | delete config_yaml[key] 33 | } 34 | } 35 | Config.setConfig(config_yaml) 36 | } 37 | 38 | initClient () { 39 | if ( 40 | Config.getConfig().server_id && 41 | Config.getConfig().channel_id && 42 | Config.getConfig().salai_token 43 | ) { 44 | try { 45 | Log.i('正在尝试登录 Midjourney Bot...') 46 | Main() 47 | } catch (err) { 48 | Log.e('自启动登录 Midjourney Bot 失败', err) 49 | } 50 | } 51 | } 52 | } 53 | 54 | export default new Init() 55 | -------------------------------------------------------------------------------- /model/path.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | const _path = process.cwd().replace(/\\/g, '/') 4 | 5 | // 插件名 6 | const pluginName = path.basename(path.join(import.meta.url, '../../')) 7 | // 插件根目录 8 | const pluginRoot = path.join(_path, 'plugins', pluginName) 9 | // 插件资源目录 10 | const pluginResources = path.join(pluginRoot, 'resources') 11 | 12 | export { _path, pluginName, pluginRoot, pluginResources } 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mj-plugin", 3 | "version": "2.0.1", 4 | "description": "Yunzai-Bot的AI绘图插件", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "type": "module", 10 | "module": "index.js", 11 | "dependencies": { 12 | "midjourney": "^4.3.17", 13 | "https-proxy-agent": "^7.0.1", 14 | "isomorphic-ws": "^5.0.0" 15 | }, 16 | "keywords": [ 17 | "Yunzai-Bot", 18 | "云崽", 19 | "Midjourney", 20 | "AI绘图" 21 | ], 22 | "author": "@CikeyQi", 23 | "license": "ISC" 24 | } -------------------------------------------------------------------------------- /resources/common/common.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Number'; 3 | src: url("./font/tttgbnumber.woff") format('woff'), url("./font/tttgbnumber.ttf") format('truetype'); 4 | } 5 | @font-face { 6 | font-family: 'NZBZ'; 7 | src: url("./font/NZBZ.woff") format('woff'), url("./font/NZBZ.ttf") format('truetype'); 8 | } 9 | @font-face { 10 | font-family: 'YS'; 11 | src: url("./font/HYWH-65W.woff") format('woff'), url("./font/HYWH-65W.ttf") format('truetype'); 12 | } 13 | .font-YS { 14 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 15 | } 16 | .font-NZBZ { 17 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 18 | } 19 | * { 20 | margin: 0; 21 | padding: 0; 22 | box-sizing: border-box; 23 | -webkit-user-select: none; 24 | user-select: none; 25 | } 26 | body { 27 | font-size: 18px; 28 | color: #1e1f20; 29 | font-family: Number, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 30 | transform: scale(1.4); 31 | transform-origin: 0 0; 32 | width: 600px; 33 | } 34 | .container { 35 | width: 600px; 36 | padding: 20px 15px 10px 15px; 37 | background-size: contain; 38 | } 39 | .head-box { 40 | border-radius: 15px; 41 | padding: 10px 20px; 42 | position: relative; 43 | color: #fff; 44 | margin-top: 30px; 45 | } 46 | .head-box .title { 47 | font-family: Number, "印品南征北战NZBZ体", NZBZ, "汉仪文黑-65W", YS, PingFangSC-Medium, "PingFang SC", sans-serif; 48 | font-size: 36px; 49 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 50 | } 51 | .head-box .title .label { 52 | display: inline-block; 53 | margin-left: 10px; 54 | } 55 | .head-box .genshin_logo { 56 | position: absolute; 57 | top: 1px; 58 | right: 15px; 59 | width: 97px; 60 | } 61 | .head-box .label { 62 | font-size: 16px; 63 | text-shadow: 0 0 1px #000, 1px 1px 3px rgba(0, 0, 0, 0.9); 64 | } 65 | .head-box .label span { 66 | color: #d3bc8e; 67 | padding: 0 2px; 68 | } 69 | .notice { 70 | color: #888; 71 | font-size: 12px; 72 | text-align: right; 73 | padding: 12px 5px 5px; 74 | } 75 | .notice-center { 76 | color: #fff; 77 | text-align: center; 78 | margin-bottom: 10px; 79 | text-shadow: 1px 1px 1px #333; 80 | } 81 | .copyright { 82 | font-size: 14px; 83 | text-align: center; 84 | color: #fff; 85 | position: relative; 86 | padding-left: 10px; 87 | text-shadow: 1px 1px 1px #000; 88 | margin: 10px 0; 89 | } 90 | .copyright .version { 91 | color: #d3bc8e; 92 | display: inline-block; 93 | padding: 0 3px; 94 | } 95 | /* */ 96 | .cons { 97 | display: inline-block; 98 | vertical-align: middle; 99 | padding: 0 5px; 100 | border-radius: 4px; 101 | } 102 | .cons-0 { 103 | background: #666; 104 | color: #fff; 105 | } 106 | .cons-n0 { 107 | background: #404949; 108 | color: #fff; 109 | } 110 | .cons-1 { 111 | background: #5cbac2; 112 | color: #fff; 113 | } 114 | .cons-2 { 115 | background: #339d61; 116 | color: #fff; 117 | } 118 | .cons-3 { 119 | background: #3e95b9; 120 | color: #fff; 121 | } 122 | .cons-4 { 123 | background: #3955b7; 124 | color: #fff; 125 | } 126 | .cons-5 { 127 | background: #531ba9cf; 128 | color: #fff; 129 | } 130 | .cons-6 { 131 | background: #ff5722; 132 | color: #fff; 133 | } 134 | .cons2-0 { 135 | border-radius: 4px; 136 | background: #666; 137 | color: #fff; 138 | } 139 | .cons2-1 { 140 | border-radius: 4px; 141 | background: #71b1b7; 142 | color: #fff; 143 | } 144 | .cons2-2 { 145 | border-radius: 4px; 146 | background: #369961; 147 | color: #fff; 148 | } 149 | .cons2-3 { 150 | border-radius: 4px; 151 | background: #4596b9; 152 | color: #fff; 153 | } 154 | .cons2-4 { 155 | border-radius: 4px; 156 | background: #4560b9; 157 | color: #fff; 158 | } 159 | .cons2-5 { 160 | border-radius: 4px; 161 | background: #531ba9cf; 162 | color: #fff; 163 | } 164 | .cons2-6 { 165 | border-radius: 4px; 166 | background: #ff5722; 167 | color: #fff; 168 | } 169 | /******** Fetter ********/ 170 | .fetter { 171 | width: 50px; 172 | height: 50px; 173 | display: inline-block; 174 | background: url('./item/fetter.png'); 175 | background-size: auto 100%; 176 | } 177 | .fetter.fetter1 { 178 | background-position: 0% 0; 179 | } 180 | .fetter.fetter2 { 181 | background-position: 11.11111111% 0; 182 | } 183 | .fetter.fetter3 { 184 | background-position: 22.22222222% 0; 185 | } 186 | .fetter.fetter4 { 187 | background-position: 33.33333333% 0; 188 | } 189 | .fetter.fetter5 { 190 | background-position: 44.44444444% 0; 191 | } 192 | .fetter.fetter6 { 193 | background-position: 55.55555556% 0; 194 | } 195 | .fetter.fetter7 { 196 | background-position: 66.66666667% 0; 197 | } 198 | .fetter.fetter8 { 199 | background-position: 77.77777778% 0; 200 | } 201 | .fetter.fetter9 { 202 | background-position: 88.88888889% 0; 203 | } 204 | .fetter.fetter10 { 205 | background-position: 100% 0; 206 | } 207 | /******** ELEM ********/ 208 | .elem-hydro .talent-icon { 209 | background-image: url("./bg/talent-hydro.png"); 210 | } 211 | .elem-hydro .elem-bg, 212 | .hydro-bg { 213 | background-image: url("./bg/bg-hydro.jpg"); 214 | } 215 | .elem-anemo .talent-icon { 216 | background-image: url("./bg/talent-anemo.png"); 217 | } 218 | .elem-anemo .elem-bg, 219 | .anemo-bg { 220 | background-image: url("./bg/bg-anemo.jpg"); 221 | } 222 | .elem-cryo .talent-icon { 223 | background-image: url("./bg/talent-cryo.png"); 224 | } 225 | .elem-cryo .elem-bg, 226 | .cryo-bg { 227 | background-image: url("./bg/bg-cryo.jpg"); 228 | } 229 | .elem-electro .talent-icon { 230 | background-image: url("./bg/talent-electro.png"); 231 | } 232 | .elem-electro .elem-bg, 233 | .electro-bg { 234 | background-image: url("./bg/bg-electro.jpg"); 235 | } 236 | .elem-geo .talent-icon { 237 | background-image: url("./bg/talent-geo.png"); 238 | } 239 | .elem-geo .elem-bg, 240 | .geo-bg { 241 | background-image: url("./bg/bg-geo.jpg"); 242 | } 243 | .elem-pyro .talent-icon { 244 | background-image: url("./bg/talent-pyro.png"); 245 | } 246 | .elem-pyro .elem-bg, 247 | .pyro-bg { 248 | background-image: url("./bg/bg-pyro.jpg"); 249 | } 250 | .elem-dendro .talent-icon { 251 | background-image: url("./bg/talent-dendro.png"); 252 | } 253 | .elem-dendro .elem-bg, 254 | .dendro-bg { 255 | background-image: url("./bg/bg-dendro.jpg"); 256 | } 257 | /* cont */ 258 | .cont { 259 | border-radius: 10px; 260 | background: url("../common/cont/card-bg.png") top left repeat-x; 261 | background-size: auto 100%; 262 | margin: 5px 15px 5px 10px; 263 | position: relative; 264 | box-shadow: 0 0 1px 0 #ccc, 2px 2px 4px 0 rgba(50, 50, 50, 0.8); 265 | overflow: hidden; 266 | color: #fff; 267 | font-size: 16px; 268 | } 269 | .cont-title { 270 | background: rgba(0, 0, 0, 0.4); 271 | box-shadow: 0 0 1px 0 #fff; 272 | color: #d3bc8e; 273 | padding: 10px 20px; 274 | text-align: left; 275 | border-radius: 10px 10px 0 0; 276 | } 277 | .cont-title span { 278 | font-size: 12px; 279 | color: #aaa; 280 | margin-left: 10px; 281 | font-weight: normal; 282 | } 283 | .cont-title.border-less { 284 | background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0)); 285 | box-shadow: none; 286 | padding-bottom: 5px; 287 | } 288 | .cont-body { 289 | padding: 10px 15px; 290 | font-size: 12px; 291 | background: rgba(0, 0, 0, 0.5); 292 | box-shadow: 0 0 1px 0 #fff; 293 | font-weight: normal; 294 | } 295 | .cont-footer { 296 | padding: 10px 15px; 297 | font-size: 12px; 298 | background: rgba(0, 0, 0, 0.5); 299 | font-weight: normal; 300 | } 301 | .cont > ul.cont-msg { 302 | display: block; 303 | padding: 5px 10px; 304 | background: rgba(0, 0, 0, 0.5); 305 | } 306 | ul.cont-msg, 307 | .cont-footer ul { 308 | padding-left: 15px; 309 | } 310 | ul.cont-msg li, 311 | .cont-footer ul li { 312 | margin: 5px 0; 313 | margin-left: 15px; 314 | } 315 | ul.cont-msg li strong, 316 | .cont-footer ul li strong { 317 | font-weight: normal; 318 | margin: 0 2px; 319 | color: #d3bc8e; 320 | } 321 | .cont-table { 322 | display: table; 323 | width: 100%; 324 | } 325 | .cont-table .tr { 326 | display: table-row; 327 | } 328 | .cont-table .tr:nth-child(even) { 329 | background: rgba(0, 0, 0, 0.4); 330 | } 331 | .cont-table .tr:nth-child(odd) { 332 | background: rgba(50, 50, 50, 0.4); 333 | } 334 | .cont-table .tr > div, 335 | .cont-table .tr > td { 336 | display: table-cell; 337 | box-shadow: 0 0 1px 0 #fff; 338 | } 339 | .cont-table .tr > div.value-full { 340 | display: table; 341 | width: 200%; 342 | } 343 | .cont-table .tr > div.value-none { 344 | box-shadow: none; 345 | } 346 | .cont-table .thead { 347 | text-align: center; 348 | } 349 | .cont-table .thead > div, 350 | .cont-table .thead > td { 351 | color: #d3bc8e; 352 | background: rgba(0, 0, 0, 0.4); 353 | line-height: 40px; 354 | height: 40px; 355 | } 356 | .cont-table .title, 357 | .cont-table .th { 358 | color: #d3bc8e; 359 | padding-right: 15px; 360 | text-align: right; 361 | background: rgba(0, 0, 0, 0.4); 362 | min-width: 100px; 363 | vertical-align: middle; 364 | } 365 | .logo { 366 | font-size: 18px; 367 | text-align: center; 368 | color: #fff; 369 | margin: 20px 0 10px 0; 370 | } 371 | /* item-icon */ 372 | .item-icon { 373 | width: 100%; 374 | height: 100%; 375 | border-radius: 4px; 376 | position: relative; 377 | overflow: hidden; 378 | } 379 | .item-icon .img { 380 | width: 100%; 381 | height: 100%; 382 | display: block; 383 | background-size: contain; 384 | background-position: center; 385 | background-repeat: no-repeat; 386 | } 387 | .item-icon.artis .img { 388 | width: 84%; 389 | height: 84%; 390 | margin: 8%; 391 | } 392 | .item-icon.star1 { 393 | background-image: url("../common/item/bg1.png"); 394 | } 395 | .item-icon.opacity-bg.star1 { 396 | background-image: url("../common/item/bg1-o.png"); 397 | } 398 | .item-icon.star2 { 399 | background-image: url("../common/item/bg2.png"); 400 | } 401 | .item-icon.opacity-bg.star2 { 402 | background-image: url("../common/item/bg2-o.png"); 403 | } 404 | .item-icon.star3 { 405 | background-image: url("../common/item/bg3.png"); 406 | } 407 | .item-icon.opacity-bg.star3 { 408 | background-image: url("../common/item/bg3-o.png"); 409 | } 410 | .item-icon.star4 { 411 | background-image: url("../common/item/bg4.png"); 412 | } 413 | .item-icon.opacity-bg.star4 { 414 | background-image: url("../common/item/bg4-o.png"); 415 | } 416 | .item-icon.star5 { 417 | background-image: url("../common/item/bg5.png"); 418 | } 419 | .item-icon.opacity-bg.star5 { 420 | background-image: url("../common/item/bg5-o.png"); 421 | } 422 | .item-icon.star-w { 423 | background: #fff; 424 | } 425 | .item-list { 426 | display: flex; 427 | } 428 | .item-list .item-card { 429 | width: 70px; 430 | background: #e7e5d9; 431 | } 432 | .item-list .item-icon { 433 | border-bottom-left-radius: 0; 434 | border-bottom-right-radius: 12px; 435 | } 436 | .item-list .item-title { 437 | color: #222; 438 | font-size: 13px; 439 | text-align: center; 440 | padding: 2px; 441 | white-space: nowrap; 442 | overflow: hidden; 443 | } 444 | .item-list .item-icon { 445 | height: initial; 446 | } 447 | .item-list .item-badge { 448 | position: absolute; 449 | display: block; 450 | left: 0; 451 | top: 0; 452 | background: rgba(0, 0, 0, 0.6); 453 | font-size: 12px; 454 | color: #fff; 455 | padding: 4px 5px 3px; 456 | border-radius: 0 0 6px 0; 457 | } 458 | /*# sourceMappingURL=common.css.map */ -------------------------------------------------------------------------------- /resources/common/font/HYWH-65W.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/common/font/HYWH-65W.woff -------------------------------------------------------------------------------- /resources/common/font/NZBZ.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/common/font/NZBZ.woff -------------------------------------------------------------------------------- /resources/common/font/tttgbnumber.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/common/font/tttgbnumber.woff -------------------------------------------------------------------------------- /resources/common/layout/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | miao-plugin 12 | {{block 'css'}} 13 | {{/block}} 14 | 15 | 16 |
17 | {{block 'main'}}{{/block}} 18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /resources/common/layout/elem.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | mc-plugin 12 | {{block 'css'}} 13 | {{/block}} 14 | 15 | 16 |
17 | {{block 'main'}}{{/block}} 18 | 19 |
20 | 21 | -------------------------------------------------------------------------------- /resources/helpTemp/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/helpTemp/bg.jpg -------------------------------------------------------------------------------- /resources/helpTemp/helpTemp.css: -------------------------------------------------------------------------------- 1 | /* body { 2 | transform: scale(1); 3 | width: 830px; 4 | background: url("../common/theme/bg-01.jpg"); 5 | } 6 | 7 | .container { 8 | background: url(../common/theme/main-01.png) top left no-repeat; 9 | background-size: 100% auto; 10 | width: 830px; 11 | } */ 12 | 13 | .head-box { 14 | margin: 60px 0 0 0; 15 | padding-bottom: 0; 16 | } 17 | 18 | .head-box .title { 19 | font-size: 50px; 20 | } 21 | 22 | .cont-box { 23 | border-radius: 15px; 24 | margin-top: 20px; 25 | margin-bottom: 20px; 26 | overflow: hidden; 27 | box-shadow: 0 5px 10px 0 rgba(0, 0, 0, 0.15); 28 | position: relative; 29 | } 30 | 31 | .help-group { 32 | font-size: 18px; 33 | font-weight: bold; 34 | padding: 15px 15px 10px 20px; 35 | } 36 | 37 | .help-table { 38 | text-align: center; 39 | border-collapse: collapse; 40 | margin: 0; 41 | border-radius: 0 0 10px 10px; 42 | display: table; 43 | overflow: hidden; 44 | width: 100%; 45 | color: #fff; 46 | } 47 | 48 | .help-table .tr { 49 | display: table-row; 50 | } 51 | 52 | .help-table .td, 53 | .help-table .th { 54 | font-size: 14px; 55 | display: table-cell; 56 | box-shadow: 0 0 1px 0 #888 inset; 57 | padding: 12px 0 12px 50px; 58 | line-height: 24px; 59 | position: relative; 60 | text-align: left; 61 | } 62 | 63 | .help-table .tr:last-child .td { 64 | padding-bottom: 12px; 65 | } 66 | 67 | .help-table .th { 68 | background: rgba(34, 41, 51, 0.5); 69 | } 70 | 71 | .help-icon { 72 | width: 40px; 73 | height: 40px; 74 | display: block; 75 | position: absolute; 76 | background: url("icon.png") 0 0 no-repeat; 77 | background-size: 500px auto; 78 | border-radius: 5px; 79 | left: 6px; 80 | top: 12px; 81 | transform: scale(0.85); 82 | } 83 | 84 | .help-title { 85 | display: block; 86 | color: #d3bc8e; 87 | font-size: 16px; 88 | line-height: 24px; 89 | } 90 | 91 | .help-desc { 92 | display: block; 93 | font-size: 13px; 94 | line-height: 18px; 95 | } 96 | 97 | /*# sourceMappingURL=index.css.map */ -------------------------------------------------------------------------------- /resources/helpTemp/helpTemp.html: -------------------------------------------------------------------------------- 1 | {{extend defaultLayout}} 2 | 3 | {{block 'css'}} 4 | 5 | <% style=style.replace(/{{_res_path}}/g, _res_path) %> 6 | {{@style}} 7 | {{/block}} 8 | 9 | {{block 'main'}} 10 | 11 |
12 |
13 |
{{helpCfg.title||"使用帮助"}}
14 |
{{helpCfg.subTitle || "Yunzai-Bot & Miao-Plugin"}}
15 |
16 |
17 | 18 | {{each helpGroup group}} 19 | {{set len = group?.list?.length || 0 }} 20 |
21 |
{{group.group}}
22 | {{if len > 0}} 23 |
24 |
25 | {{each group.list help idx}} 26 |
27 | 28 | {{help.title}} 29 | {{help.desc}} 30 |
31 | {{if idx%colCount === colCount-1 && idx>0 && idx< len-1}}
32 |
33 | {{/if}} 34 | {{/each}} 35 | <% for(let i=(len-1)%colCount; i< colCount-1 ; i++){ %> 36 |
37 | <% } %> 38 |
39 |
40 | {{/if}} 41 |
42 | {{/each}} 43 | {{/block}} -------------------------------------------------------------------------------- /resources/helpTemp/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/helpTemp/icon.png -------------------------------------------------------------------------------- /resources/helpTemp/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/helpTemp/main.png -------------------------------------------------------------------------------- /resources/listTemp/listTemp.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "tttgbnumber"; 3 | src: url("../../../../resources/font/tttgbnumber.ttf"); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | @font-face { 8 | font-family: "HYWenHei-55W"; 9 | src: url("../../../../resources/font/HYWenHei-55W.ttf"); 10 | font-weight: normal; 11 | font-style: normal; 12 | } 13 | * { 14 | margin: 0; 15 | padding: 0; 16 | box-sizing: border-box; 17 | user-select: none; 18 | } 19 | 20 | body { 21 | font-size: 12px; 22 | color: #1e1f20; 23 | font-family: 24 | PingFangSC-Medium, 25 | PingFang SC, 26 | sans-serif; 27 | transform: scale(1.7); 28 | transform-origin: 0 0; 29 | width: 465px; 30 | } 31 | 32 | .container { 33 | width: 465px; 34 | padding: 20px 15px 10px 15px; 35 | background-color: #ececec; 36 | } 37 | 38 | .head_box { 39 | border-radius: 15px; 40 | font-family: tttgbnumber; 41 | padding: 10px 20px; 42 | position: relative; 43 | box-shadow: 0 5px 10px 0 rgb(0 0 0 / 15%); 44 | } 45 | 46 | .head_box .id_text { 47 | font-size: 24px; 48 | } 49 | 50 | .logo { 51 | font-size: 12px; 52 | font-family: "tttgbnumber"; 53 | text-align: center; 54 | color: #7994a7; 55 | position: relative; 56 | text-align: center; 57 | padding-left: 10px; 58 | } 59 | 60 | .data_box { 61 | margin-top: 20px; 62 | margin-bottom: -60px; 63 | padding: 20px 15px 5px 15px; 64 | position: relative; 65 | } 66 | 67 | .data_box2 { 68 | border-radius: 15px; 69 | margin-top: 20px; 70 | margin-bottom: 20px; 71 | padding: 20px 15px 5px 15px; 72 | background: #f5f5f5; 73 | position: relative; 74 | box-shadow: 0 10px 10px -5px rgb(0 0 0 / 15%); 75 | } 76 | 77 | .tab-lable { 78 | position: absolute; 79 | top: -10px; 80 | left: -8px; 81 | background: #a98242; 82 | color: #fff; 83 | font-size: 14px; 84 | padding: 3px 10px; 85 | border-radius: 15px 0 15px 15px; 86 | z-index: 20; 87 | font-family: HYWenHei-55W; 88 | } 89 | 90 | .head_box { 91 | background-position-x: 42px; 92 | background-repeat: no-repeat; 93 | background-size: auto 101%; 94 | } 95 | 96 | .model_list:nth-child(3) { 97 | padding: 15px 0px 0px 0px; 98 | margin-top: 35px; 99 | } 100 | 101 | .model_list { 102 | display: table; 103 | width: calc(100% + 0px); 104 | font-size: 10px; 105 | overflow: hidden; 106 | background: #f5f5f5; 107 | border-radius: 15px; 108 | box-shadow: 0 10px 10px -5px rgb(0 0 0 / 15%); 109 | margin-top: 20px; 110 | margin-bottom: 20px; 111 | padding: 0px 0px 0px 0px; 112 | } 113 | 114 | .model_list .avatar { 115 | display: table-row; 116 | font-family: tttgbnumber; 117 | } 118 | 119 | .model_list .th { 120 | padding-left: 5px; 121 | padding-right: 5px; 122 | justify-content: center; 123 | font-family: HYWenHei-55W; 124 | } 125 | 126 | .model_list .avatar > div { 127 | box-shadow: 0 0 0.5px 0 #555 inset; 128 | display: table-cell; 129 | text-align: center; 130 | height: 30px; 131 | vertical-align: middle; 132 | line-height: 30px; 133 | } 134 | 135 | .model_list .avatar .index { 136 | color: #333; 137 | width: 25px; 138 | padding-left: 5px; 139 | } 140 | 141 | .model_list .true { 142 | background: rgba(239, 214, 137, 0.6); 143 | } 144 | 145 | .model_list .avatar .model-name { 146 | padding-left: 5px; 147 | padding-right: 5px; 148 | } 149 | .model_list .avatar.th > div { 150 | box-shadow: none; 151 | } 152 | 153 | .notice { 154 | color: #888; 155 | font-size: 12px; 156 | text-align: center; 157 | padding: 12px 5px 5px; 158 | } 159 | -------------------------------------------------------------------------------- /resources/listTemp/listTemp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
{{header}}
19 |
{{lable}}
20 |
21 |
22 |
{{sidebar}}
23 |
24 | {{each modelsGroup models}} 25 |
26 |
27 |
#
28 |
{{list1}}
29 |
{{list2}}
30 |
31 | {{each models model idx}} 32 |
33 |
{{idx+1}}
34 | {{if header === "Midjourney 设置"}} 35 |
{{model.list1}}
36 | {{else}} 37 |
{{model.list1}}
38 | {{/if}} 39 |
{{model.list2}}
40 |
41 | {{/each}} 42 |
43 | {{/each}} 44 | 45 |
46 |

{{notice}}

47 |
48 | 49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/readme/girl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CikeyQi/mj-plugin/a2eab99711bf0064a70f44ab66f8b84751367b4c/resources/readme/girl.png -------------------------------------------------------------------------------- /utils/logs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { pluginRoot } from '../model/path.js' 3 | let currentVersion 4 | const package_path = `${pluginRoot}/package.json` 5 | try { 6 | const package_json = JSON.parse(fs.readFileSync(package_path, 'utf-8')) 7 | if (package_json.version) { 8 | currentVersion = package_json.version 9 | } 10 | } catch (err) { 11 | console.log('读取package.json失败', err) 12 | } 13 | 14 | /** 快捷logger:i-info m-mark w-warn e-error 15 | */ 16 | class Log { 17 | constructor () { 18 | this.header = `【MJ-Plugin v${currentVersion}】` 19 | } 20 | 21 | /** 快捷执行logger.info( ) */ 22 | i (...msg) { 23 | logger.info(this.header, ...msg) 24 | } 25 | 26 | /** 快捷执行logger.mark( ) */ 27 | m (...msg) { 28 | logger.mark(this.header, ...msg) 29 | } 30 | 31 | /** 快捷执行logger.warn( ) */ 32 | w (...msg) { 33 | logger.warn(this.header, ...msg) 34 | } 35 | 36 | /** 快捷执行logger.error( ) */ 37 | e (...msg) { 38 | logger.error(this.header, ...msg) 39 | } 40 | 41 | /** 快捷执行console.log( ) */ 42 | c (...msg) { 43 | console.log(this.header, ...msg) 44 | } 45 | } 46 | export default new Log() 47 | -------------------------------------------------------------------------------- /utils/translate.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | import crypto from 'crypto' 3 | import Log from './logs.js' 4 | import Config from '../components/Config.js' 5 | import lodash from 'lodash' 6 | 7 | class Translate { 8 | translate (msg) { 9 | if (Config.getConfig().translate_use == 1) { 10 | return BaiduTranslate(msg) 11 | } else if (Config.getConfig().translate_use == 2) { 12 | return YoudaoTranslate(msg) 13 | } else { 14 | return yenai_youdao(msg) 15 | } 16 | } 17 | } 18 | 19 | async function BaiduTranslate (msg) { 20 | const translateAPI = 'http://api.fanyi.baidu.com/api/trans/vip/translate' 21 | const salt = Math.random().toString(36).substr(2) 22 | const sign = crypto 23 | .createHash('md5') 24 | .update( 25 | (await Config.getConfig()).baidu_translate.appid + 26 | msg + 27 | salt + 28 | (await Config.getConfig()).baidu_translate.appkey 29 | ) 30 | .digest('hex') 31 | const url = `${translateAPI}?q=${msg}&from=zh&to=en&appid=${ 32 | (await Config.getConfig()).baidu_translate.appid 33 | }&salt=${salt}&sign=${sign}` 34 | const res = await fetch(url) 35 | const json = await res.json() 36 | try { 37 | if (json.error_code) { 38 | Log.i('百度翻译报错:', json) 39 | return false 40 | } 41 | return json.trans_result[0].dst 42 | } catch (err) { 43 | Log.e('百度翻译报错:', err) 44 | return false 45 | } 46 | } 47 | 48 | async function YoudaoTranslate (msg) { 49 | const translateAPI = 'https://openapi.youdao.com/api' 50 | const len = msg.length 51 | if (len > 20) { 52 | var input = msg.substring(0, 10) + len + msg.substring(len - 10, len) 53 | } else { 54 | var input = msg 55 | } 56 | const salt = new Date().getTime() 57 | const curtime = Math.round(new Date().getTime() / 1000) 58 | const sign = crypto 59 | .createHash('sha256') 60 | .update( 61 | (await Config.getConfig()).youdao_translate.appid + 62 | input + 63 | salt + 64 | curtime + 65 | (await Config.getConfig()).youdao_translate.appkey 66 | ) 67 | .digest('hex') 68 | const url = `${translateAPI}?appKey=${ 69 | (await Config.getConfig()).youdao_translate.appid 70 | }&q=${msg}&from=auto&to=en&salt=${salt}&sign=${sign}&signType=v3&curtime=${curtime}` 71 | const res = await fetch(url) 72 | const json = await res.json() 73 | try { 74 | if (json.errorCode != 0) { 75 | Log.i('有道翻译报错:', json) 76 | return false 77 | } 78 | return json.translation[0] 79 | } catch (err) { 80 | Log.e('有道翻译报错:', err) 81 | return false 82 | } 83 | } 84 | 85 | async function yenai_youdao (msg) { 86 | const qs = (obj) => { 87 | let res = '' 88 | for (const [k, v] of Object.entries(obj)) { 89 | res += `${k}=${encodeURIComponent(v)}&` 90 | } 91 | return res.slice(0, res.length - 1) 92 | } 93 | const appVersion = '5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4750.0' 94 | const payload = { 95 | from: 'AUTO', 96 | to: 'AUTO', 97 | bv: crypto.createHash('md5').update(appVersion, 'utf-8').digest('hex'), 98 | client: 'fanyideskweb', 99 | doctype: 'json', 100 | version: '2.1', 101 | keyfrom: 'fanyi.web', 102 | action: 'FY_BY_DEFAULT', 103 | smartresult: 'dict' 104 | } 105 | const headers = { 106 | Host: 'fanyi.youdao.com', 107 | 'User-Agent': 108 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/98.0.4758.102', 109 | Referer: 'https://fanyi.youdao.com/', 110 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 111 | Cookie: 112 | 'OUTFOX_SEARCH_USER_ID_NCOO=133190305.98519628; OUTFOX_SEARCH_USER_ID="2081065877@10.169.0.102";' 113 | } 114 | const api = 115 | 'https://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule' 116 | const key = 'Ygy_4c=r#e#4EX^NUGUc5' 117 | 118 | const i = msg 119 | const lts = '' + new Date().getTime() 120 | const salt = lts + parseInt(String(10 * Math.random()), 10) 121 | const sign = crypto 122 | .createHash('md5') 123 | .update(payload.client + i + salt + key, 'utf-8') 124 | .digest('hex') 125 | const postData = qs(Object.assign({ i, lts, sign, salt }, payload)) 126 | try { 127 | let { errorCode, translateResult } = await fetch(api, { 128 | method: 'POST', 129 | body: postData, 130 | headers 131 | }) 132 | .then((res) => res.json()) 133 | .catch((err) => Log.e(err)) 134 | if (errorCode != 0) return false 135 | translateResult = lodash 136 | .flattenDeep(translateResult) 137 | ?.map((item) => item.tgt) 138 | .join('\n') 139 | if (!translateResult) return false 140 | return translateResult 141 | } catch (e) { 142 | Log.e('椰奶有道翻译报错:', e) 143 | return false 144 | } 145 | } 146 | 147 | export default new Translate() 148 | --------------------------------------------------------------------------------