├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── botConfig.json.example
├── package-lock.json
├── package.json
├── sdConfig.json.example
└── src
├── buttons
├── action
│ ├── cancelGeneration.js
│ ├── cancelUpscale.js
│ ├── loadCheckpoint.js
│ ├── redoImage.js
│ ├── saveImageToDM.js
│ └── upscaleImage.js
├── pagination
│ └── sdInfoPagination.js
└── selection
│ ├── saveImage.js
│ └── upscaleImageMenu.js
├── commands
├── imageCreation
│ ├── controlNet.js
│ └── generate.js
├── misc
│ └── ping.js
└── sdInfo
│ ├── changeModel.js
│ └── sdInfo.js
├── events
├── interactionCreate
│ ├── handleButtons.js
│ ├── handleCommands.js
│ └── handleSelectMenu.js
└── ready
│ ├── 01registerCommands.js
│ └── consoleLog.js
├── handlers
└── eventHandler.js
├── index.js
├── menus
├── SD
│ ├── modelDropdown.js
│ └── sdInfoDetailDropdown.js
└── upscaling
│ └── upscalerDropdown.js
└── utils
├── SD
├── base64ToAttachment.js
├── createImageEmbed.js
├── generateImage.js
├── getProgressEmbed.js
├── imageDataFromEmbed.js
├── progressUpdater.js
└── sendRequest.js
├── areCommandsDifferent.js
├── getAllFiles.js
├── getApplicationCommands.js
├── getLocalButtons.js
├── getLocalCommands.js
└── getLocalSelectMenus.js
/.env.example:
--------------------------------------------------------------------------------
1 | TOKEN=yourbottoken
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | botConfig.json
3 | sdConfig.json
4 | node_modules/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # StableDiffusionBot
3 |
4 | 
5 |
6 | A Discord Bot that uses Automatic1111's Stable Diffusion API to generate images.
7 |
8 | 
9 | [](#license)
10 | ## Features
11 |
12 | - 512x512 image generation.
13 | - Upscaling of images using Ultimate SD Upscale
14 | - Saving images to direct messages and downloads
15 | - Informative embeds on the generated images
16 | ## Screenshots
17 |
18 | **Generation**
19 | 
20 |
21 | **Upscaling**
22 | 
23 | ## Installation
24 |
25 | Requirements:
26 | - NodeJS
27 | - Git
28 |
29 | ### 1. Prerequisites
30 |
31 | **Stable Diffusion**
32 |
33 | To be able to generate images you are going to need to setup [AUTOMATIC111's Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) and make sure it is usable.
34 |
35 | You will then need to add the `--api` argument to the COMMANDLINE_ARGS in the webui-user file. Other arguments like `--xformers` and `--medvram` can be used alongside the api argument.
36 |
37 | Verify that the api is functional by going to `http://(stable-diffusion-url):(port)/docs/`.
38 |
39 | **Bot Token**
40 |
41 | Make sure you have created a discord bot token by going to the [Discord Developer Portal](https://discord.com/developers/applications), clicking `New Application`, going through the setup and inviting the bot into your server.
42 |
43 | ### 2. Installation
44 |
45 | Clone the directory
46 |
47 | `git clone https://github.com/ImDarkTom/StableDiffusionBot.git`
48 |
49 | Navigate to the cloned directory
50 |
51 | `cd StableDiffusionBot`
52 |
53 | Download any needed packages with npm
54 |
55 | `npm install`
56 |
57 | ### 3. Configuration
58 |
59 | Create the `sdConfig.json`, `botConfig.json`, and `.env` files based off the examples provided.
60 |
61 | Modify the `.env` file with your bot token and configure the `botConfig.json` files with a testServer (id of server the bot was added to), clientId (bot's client id), and a list of user id's for `devs` (ids of people that you want to run devOnly commands).
62 |
63 | sdConfig can but does not need to be modified if Stable Diffusion is using the default port.
64 |
65 | To disable live image previews you can set progressUpdateInterval in botConfig to -1.
66 |
67 | ### 4. Running the bot
68 |
69 | Once all previous steps have been completed, the bot can be ran with the `node .` command. After a few seconds you should see an output saying the bot is online.
70 |
71 | (Once running you may get an ExperimentalWarning about buffer.File when generating an image, you can ignore these)
72 |
73 | (If the bot crashes on first image generation, try restaring it with Ctrl+C and `node .`, this is due to stable diffusion taking longer than expected during the fist image generation after startup)
74 |
75 |
--------------------------------------------------------------------------------
/botConfig.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "testServer": "testserverid",
3 | "clientId": "botclientid",
4 | "devs": [
5 | "youruserid"
6 | ],
7 | "generation": {
8 | "showImageAuthor": true,
9 | "progressUpdateIntervalMs": 2500,
10 | "imagePreviewFormat": "jpg"
11 | }
12 | }
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stablediffusionbot",
3 | "version": "1.0.1",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "stablediffusionbot",
9 | "version": "1.0.1",
10 | "license": "ISC",
11 | "dependencies": {
12 | "axios": "^1.4.0",
13 | "discord.js": "^14.11.0",
14 | "dotenv": "^16.0.3",
15 | "sharp": "^0.32.1"
16 | }
17 | },
18 | "node_modules/@discordjs/builders": {
19 | "version": "1.6.3",
20 | "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.6.3.tgz",
21 | "integrity": "sha512-CTCh8NqED3iecTNuiz49mwSsrc2iQb4d0MjMdmS/8pb69Y4IlzJ/DIy/p5GFlgOrFbNO2WzMHkWKQSiJ3VNXaw==",
22 | "dependencies": {
23 | "@discordjs/formatters": "^0.3.1",
24 | "@discordjs/util": "^0.3.1",
25 | "@sapphire/shapeshift": "^3.8.2",
26 | "discord-api-types": "^0.37.41",
27 | "fast-deep-equal": "^3.1.3",
28 | "ts-mixer": "^6.0.3",
29 | "tslib": "^2.5.0"
30 | },
31 | "engines": {
32 | "node": ">=16.9.0"
33 | }
34 | },
35 | "node_modules/@discordjs/collection": {
36 | "version": "1.5.1",
37 | "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.1.tgz",
38 | "integrity": "sha512-aWEc9DCf3TMDe9iaJoOnO2+JVAjeRNuRxPZQA6GVvBf+Z3gqUuWYBy2NWh4+5CLYq5uoc3MOvUQ5H5m8CJBqOA==",
39 | "engines": {
40 | "node": ">=16.9.0"
41 | }
42 | },
43 | "node_modules/@discordjs/formatters": {
44 | "version": "0.3.1",
45 | "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.3.1.tgz",
46 | "integrity": "sha512-M7X4IGiSeh4znwcRGcs+49B5tBkNDn4k5bmhxJDAUhRxRHTiFAOTVUNQ6yAKySu5jZTnCbSvTYHW3w0rAzV1MA==",
47 | "dependencies": {
48 | "discord-api-types": "^0.37.41"
49 | },
50 | "engines": {
51 | "node": ">=16.9.0"
52 | }
53 | },
54 | "node_modules/@discordjs/rest": {
55 | "version": "1.7.1",
56 | "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-1.7.1.tgz",
57 | "integrity": "sha512-Ofa9UqT0U45G/eX86cURQnX7gzOJLG2oC28VhIk/G6IliYgQF7jFByBJEykPSHE4MxPhqCleYvmsrtfKh1nYmQ==",
58 | "dependencies": {
59 | "@discordjs/collection": "^1.5.1",
60 | "@discordjs/util": "^0.3.0",
61 | "@sapphire/async-queue": "^1.5.0",
62 | "@sapphire/snowflake": "^3.4.2",
63 | "discord-api-types": "^0.37.41",
64 | "file-type": "^18.3.0",
65 | "tslib": "^2.5.0",
66 | "undici": "^5.22.0"
67 | },
68 | "engines": {
69 | "node": ">=16.9.0"
70 | }
71 | },
72 | "node_modules/@discordjs/util": {
73 | "version": "0.3.1",
74 | "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-0.3.1.tgz",
75 | "integrity": "sha512-HxXKYKg7vohx2/OupUN/4Sd02Ev3PBJ5q0gtjdcvXb0ErCva8jNHWfe/v5sU3UKjIB/uxOhc+TDOnhqffj9pRA==",
76 | "engines": {
77 | "node": ">=16.9.0"
78 | }
79 | },
80 | "node_modules/@discordjs/ws": {
81 | "version": "0.8.3",
82 | "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-0.8.3.tgz",
83 | "integrity": "sha512-hcYtppanjHecbdNyCKQNH2I4RP9UrphDgmRgLYrATEQF1oo4sYSve7ZmGsBEXSzH72MO2tBPdWSThunbxUVk0g==",
84 | "dependencies": {
85 | "@discordjs/collection": "^1.5.1",
86 | "@discordjs/rest": "^1.7.1",
87 | "@discordjs/util": "^0.3.1",
88 | "@sapphire/async-queue": "^1.5.0",
89 | "@types/ws": "^8.5.4",
90 | "@vladfrangu/async_event_emitter": "^2.2.1",
91 | "discord-api-types": "^0.37.41",
92 | "tslib": "^2.5.0",
93 | "ws": "^8.13.0"
94 | },
95 | "engines": {
96 | "node": ">=16.9.0"
97 | }
98 | },
99 | "node_modules/@sapphire/async-queue": {
100 | "version": "1.5.0",
101 | "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.0.tgz",
102 | "integrity": "sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==",
103 | "engines": {
104 | "node": ">=v14.0.0",
105 | "npm": ">=7.0.0"
106 | }
107 | },
108 | "node_modules/@sapphire/shapeshift": {
109 | "version": "3.9.0",
110 | "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.0.tgz",
111 | "integrity": "sha512-iJpHmjAdwX9aSL6MvFpVyo+tkokDtInmSjoJHbz/k4VJfnim3DjvG0hgGEKWtWZgCu45RaLgcoNgR1fCPdIz3w==",
112 | "dependencies": {
113 | "fast-deep-equal": "^3.1.3",
114 | "lodash": "^4.17.21"
115 | },
116 | "engines": {
117 | "node": ">=v14.0.0",
118 | "npm": ">=7.0.0"
119 | }
120 | },
121 | "node_modules/@sapphire/snowflake": {
122 | "version": "3.5.1",
123 | "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.1.tgz",
124 | "integrity": "sha512-BxcYGzgEsdlG0dKAyOm0ehLGm2CafIrfQTZGWgkfKYbj+pNNsorZ7EotuZukc2MT70E0UbppVbtpBrqpzVzjNA==",
125 | "engines": {
126 | "node": ">=v14.0.0",
127 | "npm": ">=7.0.0"
128 | }
129 | },
130 | "node_modules/@tokenizer/token": {
131 | "version": "0.3.0",
132 | "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
133 | "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="
134 | },
135 | "node_modules/@types/node": {
136 | "version": "20.2.3",
137 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.2.3.tgz",
138 | "integrity": "sha512-pg9d0yC4rVNWQzX8U7xb4olIOFuuVL9za3bzMT2pu2SU0SNEi66i2qrvhE2qt0HvkhuCaWJu7pLNOt/Pj8BIrw=="
139 | },
140 | "node_modules/@types/ws": {
141 | "version": "8.5.4",
142 | "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
143 | "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==",
144 | "dependencies": {
145 | "@types/node": "*"
146 | }
147 | },
148 | "node_modules/@vladfrangu/async_event_emitter": {
149 | "version": "2.2.2",
150 | "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.2.2.tgz",
151 | "integrity": "sha512-HIzRG7sy88UZjBJamssEczH5q7t5+axva19UbZLO6u0ySbYPrwzWiXBcC0WuHyhKKoeCyneH+FvYzKQq/zTtkQ==",
152 | "engines": {
153 | "node": ">=v14.0.0",
154 | "npm": ">=7.0.0"
155 | }
156 | },
157 | "node_modules/asynckit": {
158 | "version": "0.4.0",
159 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
160 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
161 | },
162 | "node_modules/axios": {
163 | "version": "1.4.0",
164 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
165 | "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
166 | "dependencies": {
167 | "follow-redirects": "^1.15.0",
168 | "form-data": "^4.0.0",
169 | "proxy-from-env": "^1.1.0"
170 | }
171 | },
172 | "node_modules/base64-js": {
173 | "version": "1.5.1",
174 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
175 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
176 | "funding": [
177 | {
178 | "type": "github",
179 | "url": "https://github.com/sponsors/feross"
180 | },
181 | {
182 | "type": "patreon",
183 | "url": "https://www.patreon.com/feross"
184 | },
185 | {
186 | "type": "consulting",
187 | "url": "https://feross.org/support"
188 | }
189 | ]
190 | },
191 | "node_modules/bl": {
192 | "version": "4.1.0",
193 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
194 | "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
195 | "dependencies": {
196 | "buffer": "^5.5.0",
197 | "inherits": "^2.0.4",
198 | "readable-stream": "^3.4.0"
199 | }
200 | },
201 | "node_modules/buffer": {
202 | "version": "5.7.1",
203 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
204 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
205 | "funding": [
206 | {
207 | "type": "github",
208 | "url": "https://github.com/sponsors/feross"
209 | },
210 | {
211 | "type": "patreon",
212 | "url": "https://www.patreon.com/feross"
213 | },
214 | {
215 | "type": "consulting",
216 | "url": "https://feross.org/support"
217 | }
218 | ],
219 | "dependencies": {
220 | "base64-js": "^1.3.1",
221 | "ieee754": "^1.1.13"
222 | }
223 | },
224 | "node_modules/busboy": {
225 | "version": "1.6.0",
226 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
227 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
228 | "dependencies": {
229 | "streamsearch": "^1.1.0"
230 | },
231 | "engines": {
232 | "node": ">=10.16.0"
233 | }
234 | },
235 | "node_modules/chownr": {
236 | "version": "1.1.4",
237 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
238 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
239 | },
240 | "node_modules/color": {
241 | "version": "4.2.3",
242 | "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
243 | "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
244 | "dependencies": {
245 | "color-convert": "^2.0.1",
246 | "color-string": "^1.9.0"
247 | },
248 | "engines": {
249 | "node": ">=12.5.0"
250 | }
251 | },
252 | "node_modules/color-convert": {
253 | "version": "2.0.1",
254 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
255 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
256 | "dependencies": {
257 | "color-name": "~1.1.4"
258 | },
259 | "engines": {
260 | "node": ">=7.0.0"
261 | }
262 | },
263 | "node_modules/color-name": {
264 | "version": "1.1.4",
265 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
266 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
267 | },
268 | "node_modules/color-string": {
269 | "version": "1.9.1",
270 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
271 | "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
272 | "dependencies": {
273 | "color-name": "^1.0.0",
274 | "simple-swizzle": "^0.2.2"
275 | }
276 | },
277 | "node_modules/combined-stream": {
278 | "version": "1.0.8",
279 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
280 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
281 | "dependencies": {
282 | "delayed-stream": "~1.0.0"
283 | },
284 | "engines": {
285 | "node": ">= 0.8"
286 | }
287 | },
288 | "node_modules/decompress-response": {
289 | "version": "6.0.0",
290 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
291 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
292 | "dependencies": {
293 | "mimic-response": "^3.1.0"
294 | },
295 | "engines": {
296 | "node": ">=10"
297 | },
298 | "funding": {
299 | "url": "https://github.com/sponsors/sindresorhus"
300 | }
301 | },
302 | "node_modules/deep-extend": {
303 | "version": "0.6.0",
304 | "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
305 | "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
306 | "engines": {
307 | "node": ">=4.0.0"
308 | }
309 | },
310 | "node_modules/delayed-stream": {
311 | "version": "1.0.0",
312 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
313 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
314 | "engines": {
315 | "node": ">=0.4.0"
316 | }
317 | },
318 | "node_modules/detect-libc": {
319 | "version": "2.0.1",
320 | "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
321 | "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==",
322 | "engines": {
323 | "node": ">=8"
324 | }
325 | },
326 | "node_modules/discord-api-types": {
327 | "version": "0.37.42",
328 | "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.42.tgz",
329 | "integrity": "sha512-1Huaj9cQ1W7/uryS8MZs/tZemnoKB94thM1cE40lep3rpU3q7WHqkdjN/veX0prTkYlPhcyLd/DeF/pBO8X8oQ=="
330 | },
331 | "node_modules/discord.js": {
332 | "version": "14.11.0",
333 | "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.11.0.tgz",
334 | "integrity": "sha512-CkueWYFQ28U38YPR8HgsBR/QT35oPpMbEsTNM30Fs8loBIhnA4s70AwQEoy6JvLcpWWJO7GY0y2BUzZmuBMepQ==",
335 | "dependencies": {
336 | "@discordjs/builders": "^1.6.3",
337 | "@discordjs/collection": "^1.5.1",
338 | "@discordjs/formatters": "^0.3.1",
339 | "@discordjs/rest": "^1.7.1",
340 | "@discordjs/util": "^0.3.1",
341 | "@discordjs/ws": "^0.8.3",
342 | "@sapphire/snowflake": "^3.4.2",
343 | "@types/ws": "^8.5.4",
344 | "discord-api-types": "^0.37.41",
345 | "fast-deep-equal": "^3.1.3",
346 | "lodash.snakecase": "^4.1.1",
347 | "tslib": "^2.5.0",
348 | "undici": "^5.22.0",
349 | "ws": "^8.13.0"
350 | },
351 | "engines": {
352 | "node": ">=16.9.0"
353 | }
354 | },
355 | "node_modules/dotenv": {
356 | "version": "16.0.3",
357 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
358 | "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
359 | "engines": {
360 | "node": ">=12"
361 | }
362 | },
363 | "node_modules/end-of-stream": {
364 | "version": "1.4.4",
365 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
366 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
367 | "dependencies": {
368 | "once": "^1.4.0"
369 | }
370 | },
371 | "node_modules/expand-template": {
372 | "version": "2.0.3",
373 | "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
374 | "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
375 | "engines": {
376 | "node": ">=6"
377 | }
378 | },
379 | "node_modules/fast-deep-equal": {
380 | "version": "3.1.3",
381 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
382 | "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
383 | },
384 | "node_modules/file-type": {
385 | "version": "18.4.0",
386 | "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.4.0.tgz",
387 | "integrity": "sha512-o6MQrZKTAK6WpvmQk3jqTVUmqxYBxW5bloUfrdH1ZnRFDvvAPNr+l+rgOxM3nkqWT+3khaj3FRMDydWe0xhu+w==",
388 | "dependencies": {
389 | "readable-web-to-node-stream": "^3.0.2",
390 | "strtok3": "^7.0.0",
391 | "token-types": "^5.0.1"
392 | },
393 | "engines": {
394 | "node": ">=14.16"
395 | },
396 | "funding": {
397 | "url": "https://github.com/sindresorhus/file-type?sponsor=1"
398 | }
399 | },
400 | "node_modules/follow-redirects": {
401 | "version": "1.15.2",
402 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
403 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
404 | "funding": [
405 | {
406 | "type": "individual",
407 | "url": "https://github.com/sponsors/RubenVerborgh"
408 | }
409 | ],
410 | "engines": {
411 | "node": ">=4.0"
412 | },
413 | "peerDependenciesMeta": {
414 | "debug": {
415 | "optional": true
416 | }
417 | }
418 | },
419 | "node_modules/form-data": {
420 | "version": "4.0.0",
421 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
422 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
423 | "dependencies": {
424 | "asynckit": "^0.4.0",
425 | "combined-stream": "^1.0.8",
426 | "mime-types": "^2.1.12"
427 | },
428 | "engines": {
429 | "node": ">= 6"
430 | }
431 | },
432 | "node_modules/fs-constants": {
433 | "version": "1.0.0",
434 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
435 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
436 | },
437 | "node_modules/github-from-package": {
438 | "version": "0.0.0",
439 | "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
440 | "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="
441 | },
442 | "node_modules/ieee754": {
443 | "version": "1.2.1",
444 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
445 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
446 | "funding": [
447 | {
448 | "type": "github",
449 | "url": "https://github.com/sponsors/feross"
450 | },
451 | {
452 | "type": "patreon",
453 | "url": "https://www.patreon.com/feross"
454 | },
455 | {
456 | "type": "consulting",
457 | "url": "https://feross.org/support"
458 | }
459 | ]
460 | },
461 | "node_modules/inherits": {
462 | "version": "2.0.4",
463 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
464 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
465 | },
466 | "node_modules/ini": {
467 | "version": "1.3.8",
468 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
469 | "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
470 | },
471 | "node_modules/is-arrayish": {
472 | "version": "0.3.2",
473 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
474 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
475 | },
476 | "node_modules/lodash": {
477 | "version": "4.17.21",
478 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
479 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
480 | },
481 | "node_modules/lodash.snakecase": {
482 | "version": "4.1.1",
483 | "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
484 | "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
485 | },
486 | "node_modules/lru-cache": {
487 | "version": "6.0.0",
488 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
489 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
490 | "dependencies": {
491 | "yallist": "^4.0.0"
492 | },
493 | "engines": {
494 | "node": ">=10"
495 | }
496 | },
497 | "node_modules/mime-db": {
498 | "version": "1.52.0",
499 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
500 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
501 | "engines": {
502 | "node": ">= 0.6"
503 | }
504 | },
505 | "node_modules/mime-types": {
506 | "version": "2.1.35",
507 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
508 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
509 | "dependencies": {
510 | "mime-db": "1.52.0"
511 | },
512 | "engines": {
513 | "node": ">= 0.6"
514 | }
515 | },
516 | "node_modules/mimic-response": {
517 | "version": "3.1.0",
518 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
519 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
520 | "engines": {
521 | "node": ">=10"
522 | },
523 | "funding": {
524 | "url": "https://github.com/sponsors/sindresorhus"
525 | }
526 | },
527 | "node_modules/minimist": {
528 | "version": "1.2.8",
529 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
530 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
531 | "funding": {
532 | "url": "https://github.com/sponsors/ljharb"
533 | }
534 | },
535 | "node_modules/mkdirp-classic": {
536 | "version": "0.5.3",
537 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
538 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
539 | },
540 | "node_modules/napi-build-utils": {
541 | "version": "1.0.2",
542 | "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
543 | "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
544 | },
545 | "node_modules/node-abi": {
546 | "version": "3.44.0",
547 | "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.44.0.tgz",
548 | "integrity": "sha512-MYjZTiAETGG28/7fBH1RjuY7vzDwYC5q5U4whCgM4jNEQcC0gAvN339LxXukmL2T2tGpzYTfp+LZ5RN7E5DwEg==",
549 | "dependencies": {
550 | "semver": "^7.3.5"
551 | },
552 | "engines": {
553 | "node": ">=10"
554 | }
555 | },
556 | "node_modules/node-addon-api": {
557 | "version": "6.1.0",
558 | "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
559 | "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
560 | },
561 | "node_modules/once": {
562 | "version": "1.4.0",
563 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
564 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
565 | "dependencies": {
566 | "wrappy": "1"
567 | }
568 | },
569 | "node_modules/peek-readable": {
570 | "version": "5.0.0",
571 | "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.0.0.tgz",
572 | "integrity": "sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==",
573 | "engines": {
574 | "node": ">=14.16"
575 | },
576 | "funding": {
577 | "type": "github",
578 | "url": "https://github.com/sponsors/Borewit"
579 | }
580 | },
581 | "node_modules/prebuild-install": {
582 | "version": "7.1.1",
583 | "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
584 | "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==",
585 | "dependencies": {
586 | "detect-libc": "^2.0.0",
587 | "expand-template": "^2.0.3",
588 | "github-from-package": "0.0.0",
589 | "minimist": "^1.2.3",
590 | "mkdirp-classic": "^0.5.3",
591 | "napi-build-utils": "^1.0.1",
592 | "node-abi": "^3.3.0",
593 | "pump": "^3.0.0",
594 | "rc": "^1.2.7",
595 | "simple-get": "^4.0.0",
596 | "tar-fs": "^2.0.0",
597 | "tunnel-agent": "^0.6.0"
598 | },
599 | "bin": {
600 | "prebuild-install": "bin.js"
601 | },
602 | "engines": {
603 | "node": ">=10"
604 | }
605 | },
606 | "node_modules/proxy-from-env": {
607 | "version": "1.1.0",
608 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
609 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
610 | },
611 | "node_modules/pump": {
612 | "version": "3.0.0",
613 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
614 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
615 | "dependencies": {
616 | "end-of-stream": "^1.1.0",
617 | "once": "^1.3.1"
618 | }
619 | },
620 | "node_modules/rc": {
621 | "version": "1.2.8",
622 | "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
623 | "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
624 | "dependencies": {
625 | "deep-extend": "^0.6.0",
626 | "ini": "~1.3.0",
627 | "minimist": "^1.2.0",
628 | "strip-json-comments": "~2.0.1"
629 | },
630 | "bin": {
631 | "rc": "cli.js"
632 | }
633 | },
634 | "node_modules/readable-stream": {
635 | "version": "3.6.2",
636 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
637 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
638 | "dependencies": {
639 | "inherits": "^2.0.3",
640 | "string_decoder": "^1.1.1",
641 | "util-deprecate": "^1.0.1"
642 | },
643 | "engines": {
644 | "node": ">= 6"
645 | }
646 | },
647 | "node_modules/readable-web-to-node-stream": {
648 | "version": "3.0.2",
649 | "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz",
650 | "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==",
651 | "dependencies": {
652 | "readable-stream": "^3.6.0"
653 | },
654 | "engines": {
655 | "node": ">=8"
656 | },
657 | "funding": {
658 | "type": "github",
659 | "url": "https://github.com/sponsors/Borewit"
660 | }
661 | },
662 | "node_modules/safe-buffer": {
663 | "version": "5.2.1",
664 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
665 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
666 | "funding": [
667 | {
668 | "type": "github",
669 | "url": "https://github.com/sponsors/feross"
670 | },
671 | {
672 | "type": "patreon",
673 | "url": "https://www.patreon.com/feross"
674 | },
675 | {
676 | "type": "consulting",
677 | "url": "https://feross.org/support"
678 | }
679 | ]
680 | },
681 | "node_modules/semver": {
682 | "version": "7.5.1",
683 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
684 | "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
685 | "dependencies": {
686 | "lru-cache": "^6.0.0"
687 | },
688 | "bin": {
689 | "semver": "bin/semver.js"
690 | },
691 | "engines": {
692 | "node": ">=10"
693 | }
694 | },
695 | "node_modules/sharp": {
696 | "version": "0.32.1",
697 | "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.1.tgz",
698 | "integrity": "sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==",
699 | "hasInstallScript": true,
700 | "dependencies": {
701 | "color": "^4.2.3",
702 | "detect-libc": "^2.0.1",
703 | "node-addon-api": "^6.1.0",
704 | "prebuild-install": "^7.1.1",
705 | "semver": "^7.5.0",
706 | "simple-get": "^4.0.1",
707 | "tar-fs": "^2.1.1",
708 | "tunnel-agent": "^0.6.0"
709 | },
710 | "engines": {
711 | "node": ">=14.15.0"
712 | },
713 | "funding": {
714 | "url": "https://opencollective.com/libvips"
715 | }
716 | },
717 | "node_modules/simple-concat": {
718 | "version": "1.0.1",
719 | "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
720 | "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
721 | "funding": [
722 | {
723 | "type": "github",
724 | "url": "https://github.com/sponsors/feross"
725 | },
726 | {
727 | "type": "patreon",
728 | "url": "https://www.patreon.com/feross"
729 | },
730 | {
731 | "type": "consulting",
732 | "url": "https://feross.org/support"
733 | }
734 | ]
735 | },
736 | "node_modules/simple-get": {
737 | "version": "4.0.1",
738 | "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
739 | "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
740 | "funding": [
741 | {
742 | "type": "github",
743 | "url": "https://github.com/sponsors/feross"
744 | },
745 | {
746 | "type": "patreon",
747 | "url": "https://www.patreon.com/feross"
748 | },
749 | {
750 | "type": "consulting",
751 | "url": "https://feross.org/support"
752 | }
753 | ],
754 | "dependencies": {
755 | "decompress-response": "^6.0.0",
756 | "once": "^1.3.1",
757 | "simple-concat": "^1.0.0"
758 | }
759 | },
760 | "node_modules/simple-swizzle": {
761 | "version": "0.2.2",
762 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
763 | "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
764 | "dependencies": {
765 | "is-arrayish": "^0.3.1"
766 | }
767 | },
768 | "node_modules/streamsearch": {
769 | "version": "1.1.0",
770 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
771 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
772 | "engines": {
773 | "node": ">=10.0.0"
774 | }
775 | },
776 | "node_modules/string_decoder": {
777 | "version": "1.3.0",
778 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
779 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
780 | "dependencies": {
781 | "safe-buffer": "~5.2.0"
782 | }
783 | },
784 | "node_modules/strip-json-comments": {
785 | "version": "2.0.1",
786 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
787 | "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
788 | "engines": {
789 | "node": ">=0.10.0"
790 | }
791 | },
792 | "node_modules/strtok3": {
793 | "version": "7.0.0",
794 | "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.0.0.tgz",
795 | "integrity": "sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==",
796 | "dependencies": {
797 | "@tokenizer/token": "^0.3.0",
798 | "peek-readable": "^5.0.0"
799 | },
800 | "engines": {
801 | "node": ">=14.16"
802 | },
803 | "funding": {
804 | "type": "github",
805 | "url": "https://github.com/sponsors/Borewit"
806 | }
807 | },
808 | "node_modules/tar-fs": {
809 | "version": "2.1.1",
810 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
811 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
812 | "dependencies": {
813 | "chownr": "^1.1.1",
814 | "mkdirp-classic": "^0.5.2",
815 | "pump": "^3.0.0",
816 | "tar-stream": "^2.1.4"
817 | }
818 | },
819 | "node_modules/tar-stream": {
820 | "version": "2.2.0",
821 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
822 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
823 | "dependencies": {
824 | "bl": "^4.0.3",
825 | "end-of-stream": "^1.4.1",
826 | "fs-constants": "^1.0.0",
827 | "inherits": "^2.0.3",
828 | "readable-stream": "^3.1.1"
829 | },
830 | "engines": {
831 | "node": ">=6"
832 | }
833 | },
834 | "node_modules/token-types": {
835 | "version": "5.0.1",
836 | "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz",
837 | "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==",
838 | "dependencies": {
839 | "@tokenizer/token": "^0.3.0",
840 | "ieee754": "^1.2.1"
841 | },
842 | "engines": {
843 | "node": ">=14.16"
844 | },
845 | "funding": {
846 | "type": "github",
847 | "url": "https://github.com/sponsors/Borewit"
848 | }
849 | },
850 | "node_modules/ts-mixer": {
851 | "version": "6.0.3",
852 | "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",
853 | "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ=="
854 | },
855 | "node_modules/tslib": {
856 | "version": "2.5.2",
857 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz",
858 | "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA=="
859 | },
860 | "node_modules/tunnel-agent": {
861 | "version": "0.6.0",
862 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
863 | "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
864 | "dependencies": {
865 | "safe-buffer": "^5.0.1"
866 | },
867 | "engines": {
868 | "node": "*"
869 | }
870 | },
871 | "node_modules/undici": {
872 | "version": "5.22.1",
873 | "resolved": "https://registry.npmjs.org/undici/-/undici-5.22.1.tgz",
874 | "integrity": "sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==",
875 | "dependencies": {
876 | "busboy": "^1.6.0"
877 | },
878 | "engines": {
879 | "node": ">=14.0"
880 | }
881 | },
882 | "node_modules/util-deprecate": {
883 | "version": "1.0.2",
884 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
885 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
886 | },
887 | "node_modules/wrappy": {
888 | "version": "1.0.2",
889 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
890 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
891 | },
892 | "node_modules/ws": {
893 | "version": "8.13.0",
894 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
895 | "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
896 | "engines": {
897 | "node": ">=10.0.0"
898 | },
899 | "peerDependencies": {
900 | "bufferutil": "^4.0.1",
901 | "utf-8-validate": ">=5.0.2"
902 | },
903 | "peerDependenciesMeta": {
904 | "bufferutil": {
905 | "optional": true
906 | },
907 | "utf-8-validate": {
908 | "optional": true
909 | }
910 | }
911 | },
912 | "node_modules/yallist": {
913 | "version": "4.0.0",
914 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
915 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
916 | }
917 | }
918 | }
919 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "stablediffusionbot",
3 | "version": "1.0.1",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "axios": "^1.4.0",
14 | "discord.js": "^14.11.0",
15 | "dotenv": "^16.0.3",
16 | "sharp": "^0.32.1"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/sdConfig.json.example:
--------------------------------------------------------------------------------
1 | {
2 | "baseUrl": "http://localhost",
3 | "port": "7860",
4 | "useUltimateSdUpscale": false,
5 | "generationDefaults": {
6 | "defaultNegativePrompt": "(low quality, worst quality:1.4)",
7 | "defaultModel": "v1-5-pruned",
8 | "defaultCfg": 7,
9 | "defaultSteps": 20,
10 | "defaultUpscalerIndex": 5
11 | },
12 | "presets": {
13 | "performance": [
14 | {
15 | "name": "Speed",
16 | "value": "30,DPM++ 2M Karras,7",
17 | "default": true
18 | },
19 | {
20 | "name": "Quality",
21 | "value": "60,DPM++ 2M Karras,7"
22 | },
23 | {
24 | "name": "Extreme Speed",
25 | "value": "10,DDIM,8"
26 | }
27 | ]
28 | },
29 | "upscalers": [
30 | {
31 | "name": "Lanczos",
32 | "value": 1
33 | },
34 | {
35 | "name": "Nearest",
36 |
37 | "value": 2
38 | },
39 | {
40 | "name": "ESRGAN_4x",
41 | "value": 3
42 | },
43 | {
44 | "name": "LDSR",
45 | "value": 4
46 | },
47 | {
48 | "name": "R-ESRGAN_4x+",
49 | "value": 5
50 | },
51 | {
52 | "name": "R-ESRGAN 4x+ Anime6B",
53 | "value": 6
54 | },
55 | {
56 | "name": "ScuNET GAN",
57 | "value": 7
58 | },
59 | {
60 | "name": "ScuNET PSNR",
61 | "value": 8
62 | },
63 | {
64 | "name": "SwinIR 4x",
65 | "value": 9
66 | }
67 | ],
68 | "extensionConfigs": {
69 | "controlnet": {
70 | "enabled": false,
71 | "controlnetModels": [
72 | {
73 | "name": "Depth",
74 | "value": "control_v11f1p_sd15_depth [cfd03158]"
75 | },
76 | {
77 | "name": "Canny",
78 | "value": "control_v11p_sd15_canny [d14c016b]"
79 | },
80 | {
81 | "name": "Inpaint",
82 | "value": "control_v11p_sd15_inpaint [ebff9138]"
83 | },
84 | {
85 | "name": "Openpose",
86 | "value": "control_v11p_sd15_openpose [cab727d4]"
87 | }
88 | ]
89 | },
90 | "autoTlsHttps": {
91 | "enabled": false,
92 | "certFilePath": "path/to/webui.cert",
93 | "keyFilePath": "path/to/webui.key",
94 | "caFilePath": "path/to/webui.bundle"
95 | }
96 | }
97 | }
--------------------------------------------------------------------------------
/src/buttons/action/cancelGeneration.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ButtonInteraction, Client } = require("discord.js");
3 | const sendRequest = require("../../utils/SD/sendRequest");
4 |
5 | module.exports = {
6 | id: 'cancelGeneration',
7 | ownerOnly: true,
8 |
9 | /**
10 | * Callback to cancel the generation of an image
11 | * @param {Client} _client
12 | * @param {ButtonInteraction} interaction
13 | */
14 | callback: async (_client, interaction) => {
15 | await sendRequest('sdapi/v1/interrupt', {}, "post");
16 |
17 | interaction.reply({content: "Generating cancelled.", ephemeral: true});
18 | },
19 | };
--------------------------------------------------------------------------------
/src/buttons/action/cancelUpscale.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ButtonInteraction, Client } = require("discord.js");
3 |
4 | module.exports = {
5 | id: 'cancelUpscale',
6 | ownerOnly: true,
7 |
8 | /**
9 | *
10 | * @param {Client} _client
11 | * @param {ButtonInteraction} interaction
12 | */
13 | callback: async (_client, interaction) => {
14 | await interaction.message.delete();
15 | },
16 | };
--------------------------------------------------------------------------------
/src/buttons/action/loadCheckpoint.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ButtonInteraction, Client, EmbedBuilder } = require("discord.js");
3 | const sendRequest = require("../../utils/SD/sendRequest");
4 |
5 | module.exports = {
6 | id: 'loadCheckpoint',
7 | ownerOnly: false,
8 |
9 | /**
10 | *
11 | * @param {Client} _client
12 | * @param {ButtonInteraction} interaction
13 | */
14 | callback: async (_client, interaction) => {
15 | const modelName = interaction.message.embeds[0].title;
16 |
17 | await interaction.message.delete();
18 |
19 | const loadingEmbed = new EmbedBuilder()
20 | .setColor(0xfde395)
21 | .setTitle('Loading checkpoint...')
22 | .setDescription(`Loading "**${modelName}**".`)
23 |
24 | await interaction.reply({content: "", embeds: [loadingEmbed]});
25 |
26 | let options = await sendRequest('sdapi/v1/options', {}, "get");
27 |
28 | options["sd_model_checkpoint"] = modelName;
29 |
30 | await sendRequest('sdapi/v1/options', options, "post");
31 |
32 | const finishedEmbed = new EmbedBuilder()
33 | .setColor(0x82d17b)
34 | .setTitle('Checkpoint loaded!')
35 | .setDescription(`Checkpoint "**${modelName}**" has successfully loaded.`)
36 |
37 | await interaction.editReply({content: "", embeds: [finishedEmbed]});
38 | },
39 | };
--------------------------------------------------------------------------------
/src/buttons/action/redoImage.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const imageDataFromEmbed = require("../../utils/SD/imageDataFromEmbed");
3 | const generateImage = require("../../utils/SD/generateImage");
4 | const { Client, ButtonInteraction } = require("discord.js");
5 |
6 | module.exports = {
7 | id: 'redoImage',
8 | ownerOnly: false,
9 |
10 | /**
11 | *
12 | * @param {Client} _client
13 | * @param {ButtonInteraction} interaction
14 | */
15 | callback: async (_client, interaction) => {
16 | const originalImageData = await imageDataFromEmbed(interaction.message.embeds[0]);
17 |
18 | originalImageData.seed = -1;
19 |
20 | await generateImage(
21 | 'sdapi/v1/txt2img',
22 | originalImageData,
23 | interaction,
24 | {
25 | upscaleBtn: true,
26 | redoBtn: true,
27 | generatingText: "Redoing..."
28 | }
29 | );
30 | },
31 | };
--------------------------------------------------------------------------------
/src/buttons/action/saveImageToDM.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { Client, ButtonInteraction, User } = require("discord.js");
3 |
4 | module.exports = {
5 | id: 'saveImageToDM',
6 | ownerOnly: false,
7 |
8 | /**
9 | *
10 | * @param {Client} _client
11 | * @param {ButtonInteraction} interaction
12 | */
13 | callback: async (_client, interaction) => {
14 | if (interaction.channel && interaction.member) {
15 | const msgToSaveEmbed = await interaction.channel.messages.fetch(interaction.message.content);
16 |
17 | if (interaction.member.user instanceof User) {
18 | interaction.member.user.send({content: "", embeds: [msgToSaveEmbed.embeds[0]]});
19 | }
20 | }
21 | },
22 | };
--------------------------------------------------------------------------------
/src/buttons/action/upscaleImage.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const imageDataFromEmbed = require('../../utils/SD/imageDataFromEmbed');
3 | const sdConfig = require('../../../sdConfig.json');
4 | const generateImage = require("../../utils/SD/generateImage");
5 | const { Client, ButtonInteraction } = require('discord.js');
6 |
7 | module.exports = {
8 | id: 'upscaleImage',
9 | ownerOnly: true,
10 |
11 | /**
12 | *
13 | * @param {Client} _client
14 | * @param {ButtonInteraction} interaction
15 | */
16 | callback: async (_client, interaction) => {
17 | const messageContent = String(interaction.message.content).split('-');
18 |
19 | const upscaleMultiplier = Number(interaction.customId.split('-')[2]);
20 |
21 | if (!interaction.channel) {
22 | //Missing view channel intents
23 | return;
24 | }
25 | const originalMessageEmbed = (await interaction.channel.messages.fetch(messageContent[0])).embeds[0];
26 | const originalImageData = await imageDataFromEmbed(originalMessageEmbed, true);
27 |
28 | interaction.message.delete();
29 |
30 | let requestData;
31 |
32 | if (sdConfig.useUltimateSdUpscale == true) {
33 | requestData = {
34 | "init_images": [originalImageData.image],
35 | "denoising_strength": 0.2,
36 | "prompt": originalImageData.prompt,
37 | "seed": -1,
38 | "steps": originalImageData.steps,
39 | "cfg_scale": originalImageData.cfg_scale,
40 | "script_args": [null, 512, 512, 8, 32, 64, 0.2, 16, Number(messageContent[1]), true, 1, false, 4, 0, 2, 512, 512, upscaleMultiplier],
41 | "script_name": "ultimate sd upscale"
42 | };
43 | } else {
44 | requestData = {
45 | "init_images": [originalImageData.image],
46 | "denoising_strength": 0.2,
47 | "prompt": originalImageData.prompt,
48 | "seed": -1,
49 | "steps": originalImageData.steps,
50 | "cfg_scale": originalImageData.cfg_scale,
51 | "script_args": [null, 64, Number(messageContent[1]), upscaleMultiplier],
52 | "script_name": "sd upscale"
53 | };
54 | }
55 |
56 | await generateImage(
57 | 'sdapi/v1/img2img',
58 | requestData,
59 | interaction,
60 | {
61 | upscaleBtn: false,
62 | redoBtn: false,
63 | additionalTitleText: `Upscaled to ${originalImageData.width*upscaleMultiplier}x${originalImageData.height*upscaleMultiplier} with upscaler value '${messageContent[1]}'`,
64 | generatingText: "Upscaling..."
65 | }
66 | );
67 | },
68 | };
--------------------------------------------------------------------------------
/src/buttons/pagination/sdInfoPagination.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { EmbedBuilder, ButtonBuilder, ActionRowBuilder, StringSelectMenuOptionBuilder, StringSelectMenuBuilder, Client, ButtonInteraction } = require("discord.js");
3 | const sendRequest = require("../../utils/SD/sendRequest");
4 |
5 | module.exports = {
6 | id: 'sdInfoPagination',
7 | ownerOnly: false,
8 |
9 | /**
10 | *
11 | * @param {Client} _client
12 | * @param {ButtonInteraction} interaction
13 | */
14 | callback: async (_client, interaction) => {
15 | const direction = interaction.customId.split('-')[1];
16 |
17 | const originalMessage = interaction.message;
18 | const originalEmbed = new EmbedBuilder(originalMessage.embeds[0].data);
19 | const originalComponents = originalMessage.components;
20 |
21 | const [prevButton, nextButton] = originalComponents[1].components.map((originalButton) => new ButtonBuilder(originalButton.data));
22 |
23 | //@ts-ignore
24 | const [modelType, page, totalPages, modelAmount] = originalEmbed.data.footer.text.split('.');
25 |
26 |
27 | let newPage;
28 |
29 | if (direction == "next") {
30 | newPage = Number(page) + 1;
31 | } else {
32 | newPage = Number(page) - 1;
33 | }
34 |
35 | if (newPage == 1) {
36 | prevButton.setDisabled(true);
37 | } else if (newPage == Number(totalPages)) {
38 | nextButton.setDisabled(true);
39 | } else {
40 | prevButton.setDisabled(false);
41 | nextButton.setDisabled(false);
42 | }
43 |
44 | originalEmbed
45 | .setTitle(`List of ${modelType} | Page ${newPage} of ${totalPages}`)
46 | .setFooter({text: `${modelType}.${newPage}.${totalPages}.${modelAmount}`})
47 |
48 | const newButtonRow = new ActionRowBuilder()
49 | .addComponents(prevButton, nextButton)
50 |
51 |
52 | const responseData = await sendRequest(`sdapi/v1/${modelType}`, {}, "get");
53 |
54 | const newSelectComponent = new StringSelectMenuBuilder()
55 | .setCustomId(`sdInfoDetailDropdown-${modelType}`)
56 | .setPlaceholder("Select an item to see details.")
57 |
58 | const startIndex = -25 + (newPage * 25);
59 | const endIndex = -1 + (newPage * 25);
60 |
61 | switch (modelType) {
62 | case "loras":
63 | const lorasList = responseData.slice(startIndex, endIndex);
64 |
65 | for (const item of lorasList) {
66 | newSelectComponent.addOptions(
67 | new StringSelectMenuOptionBuilder()
68 | .setLabel(item.name)
69 | .setDescription(`${item.name}, ${item.alias}`)
70 | .setValue(`${item.name}||${item.alias}`)
71 | )
72 | };
73 | break;
74 |
75 | case "hypernetworks":
76 | const hypernetsList = responseData.slice(startIndex, endIndex);
77 |
78 | for (const item of hypernetsList) {
79 | const hypernetName = item.name;
80 |
81 | newSelectComponent.addOptions(
82 | new StringSelectMenuOptionBuilder()
83 | .setLabel(hypernetName)
84 | .setDescription(``)
85 | .setValue(hypernetName)
86 | )
87 | };
88 | break;
89 |
90 | case "embeddings":
91 | const embeddingNames = Object.keys(responseData.loaded);
92 |
93 | const embeddingsList = embeddingNames.slice(startIndex, endIndex);
94 |
95 | for (const name of embeddingsList) {
96 | newSelectComponent.addOptions(
97 | new StringSelectMenuOptionBuilder()
98 | .setLabel(name)
99 | .setDescription(name)
100 | .setValue(name)
101 | )
102 | };
103 | break;
104 | }
105 |
106 | const newSelectComponentRow = new ActionRowBuilder()
107 | .addComponents(newSelectComponent)
108 |
109 | //@ts-ignore
110 | originalMessage.edit({content: "", embeds: [originalEmbed], components: [newSelectComponentRow, newButtonRow]});
111 |
112 | interaction.deferUpdate();
113 | },
114 | };
--------------------------------------------------------------------------------
/src/buttons/selection/saveImage.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, ButtonInteraction } = require('discord.js');
3 |
4 | module.exports = {
5 | id: 'saveImage',
6 | ownerOnly: false,
7 |
8 | /**
9 | *
10 | * @param {Client} _client
11 | * @param {ButtonInteraction} interaction
12 | */
13 | callback: (_client, interaction) => {
14 | const interactionMsg = interaction.message;
15 |
16 | const downloadBtn = new ButtonBuilder()
17 | //@ts-ignore
18 | .setURL(interaction.message.embeds[0].image.url)
19 | .setLabel('Download')
20 | .setStyle(ButtonStyle.Link)
21 |
22 | const saveToDM = new ButtonBuilder()
23 | .setCustomId('saveImageToDM')
24 | .setLabel('Save to DM')
25 | .setEmoji('📥')
26 | .setStyle(ButtonStyle.Primary)
27 |
28 | const row = new ActionRowBuilder()
29 | .addComponents(downloadBtn, saveToDM)
30 |
31 | //@ts-ignore
32 | interaction.reply({content: interactionMsg.id, ephemeral: true, components: [row]});
33 | },
34 | };
--------------------------------------------------------------------------------
/src/buttons/selection/upscaleImageMenu.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ButtonStyle, ButtonBuilder, ActionRowBuilder, StringSelectMenuBuilder, ButtonInteraction, Client } = require("discord.js");
3 | const sdConfig = require("../../../sdConfig.json");
4 |
5 | module.exports = {
6 | id: 'upscaleImageMenu',
7 | ownerOnly: false,
8 |
9 | /**
10 | *
11 | * @param {Client} _client
12 | * @param {ButtonInteraction} interaction
13 | */
14 | callback: async (_client, interaction) => {
15 | const interactionMsg = interaction.message;
16 |
17 | const upscale2x = new ButtonBuilder()
18 | .setCustomId(`upscaleImage-${interaction.user.id}-2`)
19 | .setLabel('2x')
20 | .setEmoji('⬆️')
21 | .setStyle(ButtonStyle.Secondary)
22 |
23 | const upscale4x = new ButtonBuilder()
24 | .setCustomId(`upscaleImage-${interaction.user.id}-4`)
25 | .setLabel('4x')
26 | .setEmoji('⬆️')
27 | .setStyle(ButtonStyle.Secondary)
28 |
29 | const cancel = new ButtonBuilder()
30 | .setCustomId(`cancelUpscale-${interaction.user.id}`)
31 | .setLabel('Cancel')
32 | .setEmoji('✖')
33 | .setStyle(ButtonStyle.Danger)
34 |
35 | const dropdown = new StringSelectMenuBuilder()
36 | .setCustomId(`upscalerDropdown-${interaction.user.id}`)
37 |
38 | for (const upscaler of sdConfig.upscalers) {
39 | dropdown.addOptions([
40 | {
41 | label: upscaler.name,
42 | description: String(upscaler.value),
43 | value: String(upscaler.value),
44 | default: upscaler.default || false
45 | }
46 | ])
47 | }
48 |
49 | const row = new ActionRowBuilder()
50 | .addComponents(upscale2x, upscale4x, cancel)
51 |
52 | const selectionRow = new ActionRowBuilder()
53 | .addComponents(dropdown)
54 |
55 | //@ts-ignore
56 | interaction.reply({content: `${interactionMsg.id}-${sdConfig.generationDefaults.defaultUpscalerIndex}`, ephemeral: false, components: [selectionRow, row]});
57 | },
58 | };
--------------------------------------------------------------------------------
/src/commands/imageCreation/controlNet.js:
--------------------------------------------------------------------------------
1 | const { ApplicationCommandOptionType, Client, ChatInputCommandInteraction } = require("discord.js");
2 | const sdConfig = require('../../../sdConfig.json');
3 | const { steps_choices, cfg_choices, extensionConfigs } = require('../../../sdConfig.json');
4 | const axios = require('axios');
5 | const sharp = require('sharp');
6 | const generateImage = require("../../utils/SD/generateImage");
7 |
8 | module.exports = {
9 | //deleted: true,
10 | name: 'controlnet',
11 | description: 'Generates an image with ControlNet.',
12 | // devOnly: Boolean,
13 | // testOnly: Boolean,
14 | options: [
15 | {
16 | name: 'prompt',
17 | description: 'What to generate.',
18 | required: true,
19 | type: ApplicationCommandOptionType.String,
20 | },
21 | {
22 | name: 'cn-model',
23 | description: 'Which ControlNet model to use.',
24 | required: true,
25 | type: ApplicationCommandOptionType.String,
26 | choices: extensionConfigs.controlnet.controlnetModels
27 | },
28 | {
29 | name: 'cn-image',
30 | description: 'Image to be processed in ControlNet.',
31 | required: true,
32 | type: ApplicationCommandOptionType.Attachment,
33 | },
34 | {
35 | name: 'steps',
36 | description: "How much time to spend generating.",
37 | required: false,
38 | type: ApplicationCommandOptionType.Integer,
39 | choices: steps_choices
40 | },
41 | {
42 | name: 'cfg',
43 | description: "How close the image should be to the prompt.",
44 | required: false,
45 | type: ApplicationCommandOptionType.Integer,
46 | choices: cfg_choices
47 | }
48 | ],
49 |
50 | /**
51 | *
52 | * @param {Client} _client
53 | * @param {ChatInputCommandInteraction} interaction
54 | * @returns
55 | */
56 | callback: async (_client, interaction) => {
57 | if (sdConfig.extensionConfigs.controlnet.enabled == false) {
58 | await interaction.reply({content: "Controlnet is disabled, you can enable it in the `sdConfig.json` file."});
59 | return;
60 | }
61 |
62 | const passedAttachment = interaction.options.get('cn-image').attachment;
63 |
64 | if (!["image/png", "image/jpg"].includes(passedAttachment.contentType)) {
65 | //If not image
66 | await interaction.reply({content: "The file you provided is not an image. Please provide a jpg/png file.", ephemeral: true});
67 | return;
68 | }
69 |
70 | const controlnetModel = interaction.options.get('cn-model').value;
71 | let controlnetModule;
72 |
73 | for (const model of sdConfig.extensionConfigs.controlnet.controlnetModels) {
74 | if (model.value === controlnetModel) {
75 | controlnetModule = model.name.toLowerCase();
76 | }
77 | }
78 |
79 | let controlnetImageBase64;
80 |
81 | await axios.get(passedAttachment.url, { responseType: 'arraybuffer' })
82 | .then(response => response.data)
83 | .then(imageData => sharp(imageData).toBuffer())
84 | .then(buffer => buffer.toString('base64'))
85 | .then(base64String => controlnetImageBase64 = base64String)
86 |
87 | await generateImage(
88 | 'sdapi/v1/txt2img',
89 | {
90 | prompt: interaction.options.get('prompt').value,
91 | negative_prompt: sdConfig.generationDefaults.defaultNegativePrompt,
92 | steps: interaction.options.get('steps')?.value || sdConfig.generationDefaults.defaultSteps,
93 | cfg_scale: interaction.options.get('cfg')?.value || sdConfig.generationDefaults.defaultCfg,
94 | alwayson_scripts: {
95 | controlnet: {
96 | args: [
97 | {
98 | input_image: controlnetImageBase64,
99 | module: controlnetModule,
100 | model: controlnetModel
101 | }
102 | ]
103 | }
104 | }
105 | },
106 | interaction,
107 | {
108 | upscaleBtn: true,
109 | redoBtn: true,
110 | generatingText: "Generating with ControlNet..."
111 | }
112 | )
113 | },
114 | };
--------------------------------------------------------------------------------
/src/commands/imageCreation/generate.js:
--------------------------------------------------------------------------------
1 | const { ApplicationCommandOptionType, Client, ChatInputCommandInteraction } = require('discord.js');
2 | const sdConfig = require('../../../sdConfig.json');
3 | const generateImage = require('../../utils/SD/generateImage');
4 |
5 | module.exports = {
6 | //deleted: true,
7 | name: 'generate',
8 | description: 'Generates an image.',
9 | // devOnly: Boolean,
10 | // testOnly: Boolean,
11 | options: [
12 | {
13 | name: 'basic',
14 | description: 'Generate an image.',
15 | type: ApplicationCommandOptionType.Subcommand,
16 | options: [
17 | {
18 | name: 'prompt',
19 | description: 'What to generate.',
20 | required: true,
21 | type: ApplicationCommandOptionType.String,
22 | },
23 | {
24 | name: 'performance',
25 | description: "What generation performance should be focused on.",
26 | required: true,
27 | type: ApplicationCommandOptionType.String,
28 | choices: sdConfig.presets.performance
29 | },
30 | {
31 | name: 'batch_count',
32 | description: "How many images to generate.",
33 | required: false,
34 | type: ApplicationCommandOptionType.Integer
35 | }
36 | ]
37 | },
38 | {
39 | name: 'advanced',
40 | description: 'Fine-tune the generation parameters.',
41 | type: ApplicationCommandOptionType.Subcommand,
42 | options: [
43 | {
44 | name: 'prompt',
45 | description: 'What to generate.',
46 | required: true,
47 | type: ApplicationCommandOptionType.String
48 | },
49 | {
50 | name: 'negative_prompt',
51 | description: "What to avoid generating.",
52 | required: false,
53 | type: ApplicationCommandOptionType.String
54 | },
55 | {
56 | name: 'seed',
57 | description: "Used to randomise images. Using the same seed multiple times gives same results.",
58 | required: false,
59 | type: ApplicationCommandOptionType.Integer
60 | },
61 | {
62 | name: 'width',
63 | description: "Image width.",
64 | required: false,
65 | type: ApplicationCommandOptionType.Integer
66 | },
67 | {
68 | name: 'height',
69 | description: "Image height.",
70 | required: false,
71 | type: ApplicationCommandOptionType.Integer
72 | },
73 | {
74 | name: 'steps',
75 | description: "Number of sampling steps for the denoising process. More gives higher quality but also takes longer.",
76 | required: false,
77 | type: ApplicationCommandOptionType.Integer
78 | },
79 | {
80 | name: 'cfg_scale',
81 | description: "How much the model should stick to your prompt. Less = more creative, more = strictly follow prompt.",
82 | required: false,
83 | type: ApplicationCommandOptionType.Number
84 | },
85 | {
86 | name: 'sampler_name',
87 | description: "Algorithm for the denoising process. Must be full name such as DPM++ 2M Karras, Euler a, DDIM, etc.",
88 | required: false,
89 | type: ApplicationCommandOptionType.String
90 | },
91 | {
92 | name: 'batch_count',
93 | description: "How many images to generate.",
94 | required: false,
95 | type: ApplicationCommandOptionType.Integer
96 | }
97 | ]
98 | }
99 | ],
100 |
101 | /**
102 | *
103 | * @param {Client} _client
104 | * @param {ChatInputCommandInteraction} interaction
105 | */
106 | callback: async (_client, interaction) => {
107 | const commandOptions = interaction.options;
108 | const getCommandOption = (key) => commandOptions.get(key)?.value;
109 |
110 | //Universally used
111 | const prompt = getCommandOption('prompt');
112 | let batchCount = getCommandOption('batch_count');
113 |
114 | if (batchCount < 1) {
115 | batchCount = 1;
116 | }
117 |
118 | const subcommand = commandOptions.getSubcommand();
119 |
120 | if (subcommand == "basic") {
121 | const performance_setting = getCommandOption('performance');
122 | const [preset_steps, preset_sampler, preset_cfg_scale] = performance_setting.split(',');
123 |
124 | await generateImage(
125 | 'sdapi/v1/txt2img',
126 | {
127 | prompt: prompt,
128 | negative_prompt: sdConfig.generationDefaults.defaultNegativePrompt,
129 | steps: preset_steps,
130 | cfg_scale: preset_cfg_scale,
131 | sampler_name: preset_sampler,
132 | n_iter: batchCount
133 | },
134 | interaction,
135 | {
136 | upscaleBtn: true,
137 | redoBtn: true
138 | }
139 | );
140 | } else {
141 | //Advanced
142 | await generateImage(
143 | 'sdapi/v1/txt2img',
144 | {
145 | prompt: prompt,
146 | negative_prompt: getCommandOption('negative_prompt'),
147 | seed: getCommandOption('seed'),
148 | width: getCommandOption('width'),
149 | height: getCommandOption('height'),
150 | steps: getCommandOption('steps'),
151 | cfg_scale: getCommandOption('cfg_scale'),
152 | sampler_name: getCommandOption('sampler_name'),
153 | n_iter: batchCount
154 | },
155 | interaction,
156 | {
157 | upscaleBtn: true,
158 | redoBtn: true
159 | }
160 | );
161 | }
162 | }
163 | };
--------------------------------------------------------------------------------
/src/commands/misc/ping.js:
--------------------------------------------------------------------------------
1 | const { Client, ChatInputCommandInteraction } = require("discord.js");
2 |
3 | module.exports = {
4 | name: 'ping',
5 | description: 'Pong! Checks the bot ping.',
6 | // devOnly: Boolean,
7 | testOnly: true,
8 | // options: Object[],
9 | // deleted: Boolean,
10 |
11 | /**
12 | *
13 | * @param {Client} client
14 | * @param {ChatInputCommandInteraction} interaction
15 | */
16 | callback: (client, interaction) => {
17 | interaction.reply(`Pong! ${client.ws.ping}ms`);
18 | },
19 | };
--------------------------------------------------------------------------------
/src/commands/sdInfo/changeModel.js:
--------------------------------------------------------------------------------
1 | const { ActionRowBuilder, StringSelectMenuBuilder, Client, ChatInputCommandInteraction, ApplicationCommandOptionType } = require("discord.js");
2 | const sdConfig = require('../../../sdConfig.json')
3 | const sendRequest = require("../../utils/SD/sendRequest");
4 |
5 | module.exports = {
6 | name: 'changemodel',
7 | description: 'Change the stable diffusion model.',
8 | // devOnly: Boolean,
9 | // testOnly: Boolean,
10 | // deleted: Boolean,
11 | options: [
12 | {
13 | name: 'checkpoint',
14 | description: 'Change the current checkpoint.',
15 | type: ApplicationCommandOptionType.Subcommand
16 | }
17 | ],
18 |
19 | /**
20 | *
21 | * @param {Client} _client
22 | * @param {ChatInputCommandInteraction} interaction
23 | */
24 | callback: async (_client, interaction) => {
25 | let modelList = [];
26 | const modelsResponse = await sendRequest('sdapi/v1/sd-models', {}, "get");
27 |
28 | for (const model of modelsResponse) {
29 | modelList.push({
30 | label: model.title,
31 | description: model.hash ? model.hash : "Unknown hash",
32 | value: model.model_name
33 | });
34 | }
35 |
36 | const dropdown = new StringSelectMenuBuilder()
37 | .setCustomId(`modelDropdown-${interaction.user.id}`)
38 | .setPlaceholder(`Default: ${sdConfig.generationDefaults.defaultModel}`)
39 | .addOptions(modelList)
40 |
41 | const row = new ActionRowBuilder()
42 | .addComponents(dropdown)
43 |
44 | await interaction.reply({content: "Select a checkpoint", components: [row]})
45 | },
46 | };
--------------------------------------------------------------------------------
/src/commands/sdInfo/sdInfo.js:
--------------------------------------------------------------------------------
1 | const { ApplicationCommandOptionType, EmbedBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, ChatInputCommandInteraction } = require("discord.js");
2 | const sendRequest = require("../../utils/SD/sendRequest");
3 |
4 | module.exports = {
5 | name: 'sdinfo',
6 | description: 'Get info on the Stable Diffusion instance.',
7 | // devOnly: Boolean,
8 | // testOnly: Boolean,
9 | options: [
10 | {
11 | name: "loras",
12 | description: "Gets a list of available LoRAs.",
13 | type: ApplicationCommandOptionType.Subcommand,
14 | },
15 | {
16 | name: "embeddings",
17 | description: "Gets a list of available embeddings.",
18 | type: ApplicationCommandOptionType.Subcommand
19 | },
20 | {
21 | name: "hypernetworks",
22 | description: "Gets a list of available hypernetworks.",
23 | type: ApplicationCommandOptionType.Subcommand
24 | }
25 | ],
26 | // deleted: Boolean,
27 |
28 | /**
29 | *
30 | * @param {Client} _client
31 | * @param {ChatInputCommandInteraction} interaction
32 | */
33 | callback: async (_client, interaction) => {
34 | const subcommandName = interaction.options.getSubcommand();
35 |
36 | const select = new StringSelectMenuBuilder()
37 | .setCustomId(`sdInfoDetailDropdown-${subcommandName}`)
38 | .setPlaceholder("Select an item to see details.")
39 |
40 | const embed = new EmbedBuilder()
41 | .setColor("Orange")
42 | .setTitle(`List of ${subcommandName}`)
43 |
44 |
45 | const responseData = await sendRequest(`sdapi/v1/${subcommandName}`, {}, "get");
46 | let addPaginationButtons = false;
47 |
48 | let totalPages;
49 | let pageData;
50 | let modelsCount;
51 |
52 | switch (subcommandName) {
53 | case "loras":
54 | const loraCount = responseData.length;
55 | modelsCount = loraCount;
56 |
57 | if (loraCount < 25) {
58 | for (const item of responseData) {
59 | select.addOptions(
60 | new StringSelectMenuOptionBuilder()
61 | .setLabel(item.name)
62 | .setDescription(`${item.name}, ${item.alias}`)
63 | .setValue(`${item.name}||${item.alias}`)
64 | )
65 | };
66 | break;
67 | }
68 |
69 | totalPages = Math.ceil(loraCount/25);
70 |
71 | embed
72 | .setFooter({ text: `loras.1.${totalPages}.${loraCount}` })
73 | .setTitle(`List of ${subcommandName} | Page 1 of ${totalPages}`)
74 |
75 | pageData = responseData.slice(0, 24);
76 |
77 | for (const item of pageData) {
78 | select.addOptions(
79 | new StringSelectMenuOptionBuilder()
80 | .setLabel(item.name)
81 | .setDescription(`${item.name}, ${item.alias}`)
82 | .setValue(`${item.name}||${item.alias}`)
83 | )
84 | };
85 |
86 | addPaginationButtons = true;
87 | break;
88 |
89 | case "hypernetworks":
90 | const hypernetsCount = responseData.length;
91 | modelsCount = hypernetsCount;
92 |
93 | if (hypernetsCount < 25) {
94 | for (const item of responseData) {
95 | const hypernetName = item.name;
96 |
97 | select.addOptions(
98 | new StringSelectMenuOptionBuilder()
99 | .setLabel(hypernetName)
100 | .setDescription(``)
101 | .setValue(hypernetName)
102 | )
103 | };
104 | break;
105 |
106 | }
107 |
108 | totalPages = Math.ceil(hypernetsCount/25);
109 |
110 | embed
111 | .setFooter({ text: `hypernetworks.1.${totalPages}.${hypernetsCount}` })
112 | .setTitle(`List of ${subcommandName} | Page 1 of ${totalPages}`)
113 |
114 | pageData = responseData.slice(0, 24);
115 |
116 | for (const item of pageData) {
117 | const hypernetName = item.name;
118 |
119 | select.addOptions(
120 | new StringSelectMenuOptionBuilder()
121 | .setLabel(hypernetName)
122 | .setDescription(``)
123 | .setValue(hypernetName)
124 | )
125 | };
126 |
127 | addPaginationButtons = true;
128 | break;
129 |
130 |
131 | case "embeddings":
132 | const embeddingNames = Object.keys(responseData.loaded);
133 |
134 | const embeddingsCount = embeddingNames.length;
135 | modelsCount = embeddingsCount;
136 |
137 | if (embeddingsCount < 25) {
138 | for (const name of embeddingNames) {
139 | select.addOptions(
140 | new StringSelectMenuOptionBuilder()
141 | .setLabel(name)
142 | .setDescription(name)
143 | .setValue(name)
144 | )
145 | };
146 | break;
147 |
148 | }
149 |
150 | totalPages = Math.ceil(embeddingsCount/25);
151 |
152 | embed
153 | .setFooter({ text: `hypernetworks.1.${totalPages}.${embeddingsCount}` })
154 | .setTitle(`List of ${subcommandName} | Page 1 of ${totalPages}`)
155 |
156 | pageData = embeddingNames.slice(0, 24);
157 |
158 | for (const name of embeddingNames) {
159 | select.addOptions(
160 | new StringSelectMenuOptionBuilder()
161 | .setLabel(name)
162 | .setDescription(name)
163 | .setValue(name)
164 | )
165 | };
166 |
167 | addPaginationButtons = true;
168 | break;
169 | }
170 |
171 | const selectionRow = new ActionRowBuilder()
172 | .addComponents(select);
173 |
174 | const componentsList = [];
175 | componentsList.push(selectionRow)
176 |
177 | if (addPaginationButtons) {
178 | const prevButton = new ButtonBuilder()
179 | .setCustomId('sdInfoPagination-prev')
180 | .setLabel('Prev. Page')
181 | .setStyle(ButtonStyle.Secondary)
182 | .setDisabled(true)
183 |
184 | const nextButton = new ButtonBuilder()
185 | .setCustomId('sdInfoPagination-next')
186 | .setLabel('Next Page')
187 | .setStyle(ButtonStyle.Secondary)
188 |
189 | const buttonRow = new ActionRowBuilder()
190 | .addComponents(prevButton, nextButton);
191 |
192 | componentsList.push(buttonRow);
193 | }
194 |
195 | embed.setDescription(`Total ${subcommandName}: ${modelsCount}`);
196 |
197 | interaction.reply({content: "", embeds: [embed], components: componentsList, ephemeral: false});
198 | },
199 | };
--------------------------------------------------------------------------------
/src/events/interactionCreate/handleButtons.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { Client } = require('discord.js');
3 | const getLocalButtons = require('../../utils/getLocalButtons');
4 |
5 | /**
6 | *
7 | * @param {Client} client
8 | * @param {import('discord.js').Interaction} interaction
9 | * @returns
10 | */
11 | module.exports = async (client, interaction) => {
12 | if (!interaction.isButton()) return;
13 |
14 | const localButtons = getLocalButtons();
15 | const customId = interaction.customId.split('-');
16 |
17 | const buttonObject = localButtons.find(
18 | (btn) => btn.id === customId[0]
19 | );
20 |
21 | if (!buttonObject) return;
22 |
23 | if (buttonObject.ownerOnly) {
24 | if (customId[1] != interaction.user.id) {
25 | interaction.reply({
26 | content: 'Only the caller of the command can use this!',
27 | ephemeral: true,
28 | });
29 | return;
30 | }
31 | }
32 |
33 | await buttonObject.callback(client, interaction);
34 | };
--------------------------------------------------------------------------------
/src/events/interactionCreate/handleCommands.js:
--------------------------------------------------------------------------------
1 | const { Client } = require('discord.js');
2 | const { devs, testServer } = require('../../../botConfig.json');
3 | const getLocalCommands = require('../../utils/getLocalCommands');
4 |
5 | /**
6 | *
7 | * @param {Client} client
8 | * @param {import('discord.js').Interaction} interaction
9 | * @returns
10 | */
11 | module.exports = async (client, interaction) => {
12 | if (!interaction.isChatInputCommand()) return;
13 |
14 | const localCommands = getLocalCommands();
15 |
16 | const commandObject = localCommands.find(
17 | (cmd) => cmd.name === interaction.commandName
18 | );
19 |
20 | if (!commandObject) return;
21 |
22 | if (commandObject.devOnly) {
23 | if (!devs.includes(interaction.member.id)) {
24 | interaction.reply({
25 | content: 'Only developers are allowed to run this command.',
26 | ephemeral: true,
27 | });
28 | return;
29 | }
30 | }
31 |
32 | if (commandObject.testOnly) {
33 | if (!(interaction.guild.id === testServer)) {
34 | interaction.reply({
35 | content: 'This command cannot be ran here.',
36 | ephemeral: true,
37 | });
38 | return;
39 | }
40 | }
41 |
42 | if (commandObject.permissionsRequired?.length) {
43 | for (const permission of commandObject.permissionsRequired) {
44 | if (!interaction.member.permissions.has(permission)) {
45 | interaction.reply({
46 | content: 'Not enough permissions.',
47 | ephemeral: true,
48 | });
49 | return;
50 | }
51 | }
52 | }
53 |
54 | if (commandObject.botPermissions?.length) {
55 | for (const permission of commandObject.botPermissions) {
56 | const bot = interaction.guild.members.me;
57 |
58 | if (!bot.permissions.has(permission)) {
59 | interaction.reply({
60 | content: "I don't have enough permissions.",
61 | ephemeral: true,
62 | });
63 | return;
64 | }
65 | }
66 | }
67 |
68 | await commandObject.callback(client, interaction);
69 | };
--------------------------------------------------------------------------------
/src/events/interactionCreate/handleSelectMenu.js:
--------------------------------------------------------------------------------
1 | const { Client } = require('discord.js');
2 | const getLocalSelectMenus = require('../../utils/getLocalSelectMenus');
3 |
4 | /**
5 | *
6 | * @param {Client} client
7 | * @param {import('discord.js').Interaction} interaction
8 | * @returns
9 | */
10 | module.exports = async (client, interaction) => {
11 | if (!interaction.isStringSelectMenu()) return;
12 |
13 | const localMenus = getLocalSelectMenus();
14 | const customId = interaction.customId.split('-');
15 |
16 | const menuObject = localMenus.find(
17 | (menu) => menu.id === customId[0]
18 | );
19 |
20 | if (!menuObject) return;
21 |
22 | if (menuObject.ownerOnly) {
23 | if (customId[1] != interaction.user.id) {
24 | interaction.reply({
25 | content: 'Only the caller of the command can use this!',
26 | ephemeral: true,
27 | });
28 | return;
29 | }
30 | }
31 |
32 | await menuObject.callback(client, interaction);
33 | };
--------------------------------------------------------------------------------
/src/events/ready/01registerCommands.js:
--------------------------------------------------------------------------------
1 | const { Client } = require('discord.js');
2 | const { testServer } = require('../../../botConfig.json');
3 | const areCommandsDifferent = require('../../utils/areCommandsDifferent');
4 | const getApplicationCommands = require('../../utils/getApplicationCommands');
5 | const getLocalCommands = require('../../utils/getLocalCommands');
6 |
7 | /**
8 | *
9 | * @param {Client} client
10 | */
11 | module.exports = async (client) => {
12 | try {
13 | const localCommands = getLocalCommands();
14 | const applicationCommands = await getApplicationCommands(
15 | client,
16 | testServer
17 | );
18 |
19 | for (const localCommand of localCommands) {
20 | const { name, description, options } = localCommand;
21 |
22 | const existingCommand = await applicationCommands.cache.find(
23 | (cmd) => cmd.name === name
24 | );
25 |
26 | if (existingCommand) {
27 | if (localCommand.deleted) {
28 | await applicationCommands.delete(existingCommand.id);
29 | console.log(`🗑 Deleted command "${name}".`);
30 | continue;
31 | }
32 |
33 | if (areCommandsDifferent(existingCommand, localCommand)) {
34 | await applicationCommands.edit(existingCommand.id, {
35 | description,
36 | options,
37 | });
38 |
39 | console.log(`🔁 Edited command "${name}".`);
40 | }
41 | } else {
42 | if (localCommand.deleted) {
43 | console.log(`⏩ Skipping registering command "${name}" as it's set to delete.`);
44 | continue;
45 | }
46 |
47 | await applicationCommands.create({
48 | name,
49 | description,
50 | options,
51 | });
52 |
53 | console.log(`👍 Registered command "${name}."`);
54 | }
55 | }
56 | } catch (error) {
57 | console.log(`There was an error: ${error}`);
58 | }
59 | };
--------------------------------------------------------------------------------
/src/events/ready/consoleLog.js:
--------------------------------------------------------------------------------
1 | const { Client } = require("discord.js");
2 |
3 | /**
4 | *
5 | * @param {Client} client
6 | */
7 | module.exports = (client) => {
8 | console.log(`${client.user.tag} is online.`);
9 | };
--------------------------------------------------------------------------------
/src/handlers/eventHandler.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const getAllFiles = require('../utils/getAllFiles');
3 | const { Client } = require('discord.js');
4 |
5 | /**
6 | *
7 | * @param {Client} client
8 | */
9 | module.exports = (client) => {
10 | const eventFolders = getAllFiles(path.join(__dirname, '..', 'events'), true);
11 |
12 | for (const eventFolder of eventFolders) {
13 | const eventFiles = getAllFiles(eventFolder);
14 | eventFiles.sort((a, b) => a > b);
15 |
16 | const eventName = eventFolder.replace(/\\/g, '/').split('/').pop();
17 |
18 | client.on(eventName, async (arg) => {
19 | for (const eventFile of eventFiles) {
20 | const eventFunction = require(eventFile);
21 | await eventFunction(client, arg);
22 | }
23 | });
24 | }
25 | };
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const { Client, IntentsBitField } = require('discord.js');
3 | const eventHandler = require('./handlers/eventHandler');
4 | const axios = require("axios");
5 | const https = require('https');
6 | const fs = require('fs');
7 | const sendRequest = require('./utils/SD/sendRequest');
8 | const { baseUrl, port, useUltimateSdUpscale, extensionConfigs } = require('../sdConfig.json');
9 |
10 | const client = new Client({
11 | intents: [
12 | IntentsBitField.Flags.Guilds,
13 | IntentsBitField.Flags.GuildMembers,
14 | IntentsBitField.Flags.GuildMessages,
15 | IntentsBitField.Flags.MessageContent,
16 | ],
17 | });
18 |
19 | (async () => {
20 | //SD
21 | await axios.get(`${baseUrl}:${port}/internal/ping`)
22 | .then(response => {
23 | console.log(`✅ ${response.status}: Stable Diffusion is running.`);
24 | })
25 | .catch(error => {
26 | if (error.code == "DEPTH_ZERO_SELF_SIGNED_CERT") {
27 | const autoTlsHttpsConfig = extensionConfigs.autoTlsHttps;
28 |
29 | try {
30 | const certFile = fs.readFileSync(autoTlsHttpsConfig.certFilePath);
31 | const keyFile = fs.readFileSync(autoTlsHttpsConfig.keyFilePath);
32 | const caFile = fs.readFileSync(autoTlsHttpsConfig.caFilePath);
33 |
34 | axios.defaults.httpsAgent = new https.Agent({
35 | cert: certFile,
36 | key: keyFile,
37 | ca: caFile
38 | });
39 |
40 | console.log("✅ Loaded SSL cert.")
41 | return;
42 |
43 | } catch (err) {
44 | if (err.code === 'ENOENT') {
45 | console.warn("⚠ SSL cert files not set in config or unavailable, ignoring SSL...")
46 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
47 | }
48 | }
49 |
50 | } else {
51 | console.error(`❌ ${error.code}: Stable Diffusion was not found. Please verify that you are using the correct url and port in the sdConfig.json file.`)
52 | process.exit([1])
53 |
54 | }
55 | })
56 |
57 | //SD Api
58 | await axios.get(`${baseUrl}:${port}/docs`)
59 | .then(response => {
60 | console.log(`✅ ${response.status}: Stable Diffusion API is running.`);
61 | })
62 | .catch(error => {
63 | console.error(`❌ ${error.response.status}: Stable Diffusion API was not found. Make sure you are using the '--api' argument in the COMMANDLINE_ARGS of the webui-user file.`)
64 | process.exit([1])
65 | })
66 |
67 | //Check for Ultimate SD upscale
68 | const scripts = await sendRequest('sdapi/v1/scripts', {}, "get");
69 |
70 | if (scripts.img2img.indexOf("ultimate sd upscale") == -1) {
71 | if (useUltimateSdUpscale) {
72 | console.warn("⚠ useUltimateSdUpscale is set to true but the extension was not detected, you can set this to false to use default sd upscaling instead.");
73 | }
74 | }
75 |
76 | //Check for controlnet
77 | if (extensionConfigs.controlnet.enabled) {
78 | axios.get(`${baseUrl}:${port}/controlnet/version`)
79 | .then(response => {
80 | console.log(`✅ ${response.status} ControlNet extension found.`);
81 | })
82 | .catch(error => {
83 | console.warn(`⚠ ${error.code}: ControlNet was not found. The /controlnet command will not work.`)
84 | })
85 | }
86 | })();
87 |
88 | eventHandler(client);
89 |
90 | client.login(process.env.TOKEN);
--------------------------------------------------------------------------------
/src/menus/SD/modelDropdown.js:
--------------------------------------------------------------------------------
1 | const { EmbedBuilder, Client, StringSelectMenuInteraction, AttachmentBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
2 | const sendRequest = require('../../utils/SD/sendRequest');
3 | const { default:axios } = require('axios');
4 | const sharp = require('sharp');
5 | const sdConfig = require('../../../sdConfig.json');
6 |
7 | module.exports = {
8 | id: 'modelDropdown',
9 | ownerOnly: true,
10 |
11 | /**
12 | *
13 | * @param {Client} _client
14 | * @param {StringSelectMenuInteraction} interaction
15 | */
16 | callback: async (_client, interaction) => {
17 | const selectedModelName = interaction.values[0];
18 |
19 | const allModelsInfo = await sendRequest('sdapi/v1/sd-models', {}, "get");
20 | const selectedModelInfo = allModelsInfo.filter((model) => model.model_name == selectedModelName)[0];
21 | const modelFileName = selectedModelInfo.filename;
22 |
23 | //Get model thumbnail
24 | let thumbnailImageBuffer, thumbnailFileExtension;
25 | const fileNameNoExt = modelFileName.substring(0, modelFileName.lastIndexOf('.'));
26 | for (const fileExtension of ['png', 'jpg', 'jpeg', 'webp', 'gif']) {
27 | const generatedImageFileName = `${fileNameNoExt}.${fileExtension}`;
28 | const generatedUrl = `${sdConfig.baseUrl}:${sdConfig.port}/sd_extra_networks/thumb?filename=${generatedImageFileName}`;
29 |
30 | try {
31 | const response = await axios.get(generatedUrl, { responseType: 'arraybuffer'});
32 |
33 | if (response.status === 404) {
34 | continue;
35 | }
36 |
37 | thumbnailImageBuffer = sharp(response.data);
38 | thumbnailFileExtension = fileExtension;
39 | break;
40 |
41 | } catch (error) {
42 | if (!(error.response && error.response.status == 404)) {
43 | //If not 404
44 | console.error(`Error fetching ${generatedUrl}: ${error.message}`);
45 | }
46 | }
47 | }
48 |
49 | if (thumbnailImageBuffer == undefined) {
50 | const response = await axios.get(`${sdConfig.baseUrl}:${sdConfig.port}/file=html/card-no-preview.png`, { responseType: 'arraybuffer' });
51 |
52 | thumbnailImageBuffer = sharp(response.data);
53 | thumbnailFileExtension = "png";
54 | }
55 |
56 | const attachment = new AttachmentBuilder(thumbnailImageBuffer, {name: `thumbnail.${thumbnailFileExtension}`, description: ""});
57 |
58 | const embed = new EmbedBuilder()
59 | .setTitle(selectedModelInfo.model_name)
60 | .setDescription(selectedModelInfo.title)
61 | .setThumbnail(`attachment://thumbnail.${thumbnailFileExtension}`)
62 | .setColor('Aqua')
63 |
64 | const buttonRow = new ActionRowBuilder();
65 |
66 | const loadButton = new ButtonBuilder()
67 | .setCustomId('loadCheckpoint')
68 | .setLabel('Load Checkpoint')
69 | .setEmoji('💾')
70 | .setStyle(ButtonStyle.Primary)
71 |
72 | buttonRow.addComponents(loadButton);
73 |
74 | await interaction.reply({content: "", embeds: [embed], files: [attachment], components: [buttonRow] });
75 | },
76 | };
--------------------------------------------------------------------------------
/src/menus/SD/sdInfoDetailDropdown.js:
--------------------------------------------------------------------------------
1 | const { EmbedBuilder, Client, StringSelectMenuInteraction } = require('discord.js');
2 |
3 | module.exports = {
4 | id: 'sdInfoDetailDropdown',
5 | ownerOnly: false,
6 |
7 | /**
8 | *
9 | * @param {Client} _client
10 | * @param {StringSelectMenuInteraction} interaction
11 | */
12 | callback: async (_client, interaction) => {
13 | const modelType = interaction.customId.split('-')[1];
14 | const name = interaction.values[0];
15 |
16 | const modelTypeSingular = modelType.match(/^(.*?)(s)?$/)[1];
17 |
18 | const embed = new EmbedBuilder()
19 | .setFooter({text: modelTypeSingular})
20 |
21 | switch (modelTypeSingular) {
22 | case 'lora':
23 | const [loraName, loraAlias] = name.split('||');
24 |
25 | const usageString = loraName == loraAlias ? `\`\`` : `\`\` or \`\``;
26 |
27 | embed
28 | .setTitle(loraName)
29 | .setColor("DarkAqua")
30 | .setDescription(`Usage in prompt: ${usageString}`)
31 |
32 | break
33 |
34 | case 'embedding':
35 | embed
36 | .setTitle(name)
37 | .setColor("DarkGreen")
38 | .setDescription(`Usage in prompt: \`${name}\``)
39 |
40 | break
41 |
42 | case 'hypernetwork':
43 | embed
44 | .setTitle(name)
45 | .setColor("DarkOrange")
46 | .setDescription(`Usage in prompt: \`\``)
47 | }
48 |
49 | await interaction.reply({content: "", embeds: [embed], ephemeral: true});
50 | },
51 | };
--------------------------------------------------------------------------------
/src/menus/upscaling/upscalerDropdown.js:
--------------------------------------------------------------------------------
1 | const { EmbedBuilder, Client, StringSelectMenuInteraction } = require("discord.js");
2 | const sdConfig = require('../../../sdConfig.json');
3 |
4 | module.exports = {
5 | id: 'upscalerDropdown',
6 | ownerOnly: true,
7 |
8 | /**
9 | *
10 | * @param {Client} _client
11 | * @param {StringSelectMenuInteraction} interaction
12 | */
13 | callback: async (_client, interaction) => {
14 | const upscalerIndex = interaction.values[0];
15 | let upscalerName;
16 |
17 | for (const upscaler of sdConfig.upscalers) {
18 | if (String(upscaler.value) === upscalerIndex) {
19 | upscalerName = upscaler.name;
20 | }
21 | }
22 |
23 | const embed = new EmbedBuilder()
24 | .setTitle(`Selected '${upscalerName}' upscaler.`)
25 | .setColor(0x0080b0)
26 |
27 | await interaction.update({content: `${interaction.message.content.split("-")[0]}-${interaction.values[0]}`, components: [interaction.message.components[1]], embeds: [embed]});
28 | },
29 | };
--------------------------------------------------------------------------------
/src/utils/SD/base64ToAttachment.js:
--------------------------------------------------------------------------------
1 | const { AttachmentBuilder, Attachment } = require('discord.js');
2 | const sharp = require('sharp');
3 |
4 | /**
5 | * Turns a Base64 encoded image into an attachment.
6 | * @param {String} encodedString Base64 encoded image string.
7 | * @param {keyof sharp.FormatEnum|String} resultFormat The image format to return the buffer as, default png.
8 | * @param {String} filename The filename of the atachment, default is 'image'.
9 | * @returns {Promise}
10 | */
11 | module.exports = async (encodedString, resultFormat = "png", filename = "image") => {
12 | const buffer = Buffer.from(encodedString, 'base64');
13 |
14 | const bufferAsImage = await sharp(buffer).toFormat(resultFormat).toBuffer();
15 |
16 | const attachment = new AttachmentBuilder(bufferAsImage, {name: `${filename}.${resultFormat}`, description: ""});
17 |
18 | return attachment;
19 | };
--------------------------------------------------------------------------------
/src/utils/SD/createImageEmbed.js:
--------------------------------------------------------------------------------
1 | const { EmbedBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder, User } = require("discord.js");
2 | const base64ToAttachment = require("./base64ToAttachment");
3 | const botConfig = require('../../../botConfig.json')
4 |
5 |
6 | /**
7 | *
8 | * @param {Object[]} data
9 | * @param {Object} settings
10 | * @param {boolean} settings.upscaleBtn Should the image be able to be upscaled?
11 | * @param {boolean} settings.redoBtn Should the image be able to be redone?
12 | * @param {string} settings.additionalTitleText Text to add alongside the prompt in the title.
13 | * @param {User} user The user that requested the image.
14 | * @returns {Promise}
15 | */
16 | module.exports = async (data, settings = {upscaleBtn: true, redoBtn: false, additionalTitleText: ""}, user) => {
17 | const additionalTitleText = settings.additionalTitleText;
18 | const formattedAdditionalTitleText = additionalTitleText == undefined ? "" : `${additionalTitleText} - `;
19 |
20 | const [progressData, imageData] = data;
21 | const cancelled = progressData.state.interrupted;
22 |
23 | const imageParams = JSON.parse(imageData.info); //imageData.parameters doesn't contain info such as seed or sampler_name.
24 |
25 | let embed = new EmbedBuilder()
26 | .addFields([
27 | {
28 | name: "Seed(s)",
29 | value: imageParams.all_seeds.join(', ').toString(),
30 | inline: true
31 | },
32 | {
33 | name: "Steps",
34 | value: imageParams.steps.toString(),
35 | inline: true
36 | },
37 | {
38 | name: "CFG Scale",
39 | value: imageParams.cfg_scale.toString(),
40 | inline: true
41 | },
42 | {
43 | name: "Sampler Name",
44 | value: imageParams.sampler_name.toString(),
45 | inline: true
46 | },
47 | {
48 | name: "Negative Prompt",
49 | value: imageParams.negative_prompt.toString() || " ",
50 | inline: true
51 | }
52 | ])
53 | .setURL('http://data.notaurl/') // Will be later used to store imageParams to make imageDataFromEmbed better.
54 | .setTitle(`${cancelled ? "Cancelled - ": ""}${formattedAdditionalTitleText}"${imageParams.prompt}"`)
55 | .setFooter({ text: imageParams.infotexts[0].match(/Model: ([^,]+)/)[1] })
56 | .setColor(cancelled ? "#bb0000" : "#00bb00")
57 |
58 | if (botConfig.generation.showImageAuthor) {
59 | embed.setAuthor({name: user.username, iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=256`});
60 | }
61 |
62 | const embedsList = [];
63 | const attachmentsList = [];
64 |
65 | for (const [index, imageBase64] of imageData.images.entries()) { //To get both item and index
66 | const imageAttachment = await base64ToAttachment(imageBase64, "png", `output${index}`);
67 |
68 | const clonedEmbed = new EmbedBuilder(embed.data);
69 | clonedEmbed.setImage(`attachment://output${index}.png`)
70 |
71 | embedsList.push(clonedEmbed);
72 | attachmentsList.push(imageAttachment)
73 | }
74 |
75 | const row = new ActionRowBuilder()
76 |
77 | if (settings.redoBtn) {
78 | const redoBtn = new ButtonBuilder()
79 | .setCustomId('redoImage')
80 | .setLabel('Redo')
81 | .setEmoji('🔁')
82 | .setStyle(ButtonStyle.Primary)
83 | row.addComponents(redoBtn);
84 | }
85 |
86 | const saveBtn = new ButtonBuilder()
87 | .setCustomId('saveImage')
88 | .setLabel('Save')
89 | .setEmoji('💾')
90 | .setStyle(ButtonStyle.Secondary)
91 |
92 | row.addComponents(saveBtn);
93 |
94 | if (settings.upscaleBtn && !cancelled) {
95 | const upscaleBtn = new ButtonBuilder()
96 | .setCustomId('upscaleImageMenu')
97 | .setLabel('Upscale')
98 | .setEmoji('🖼️')
99 | .setStyle(ButtonStyle.Secondary)
100 |
101 | row.addComponents(upscaleBtn);
102 | }
103 |
104 | return {content: "", embeds: embedsList, files: attachmentsList, components: [row]};
105 | };
--------------------------------------------------------------------------------
/src/utils/SD/generateImage.js:
--------------------------------------------------------------------------------
1 | //@ts-check
2 | const { ChatInputCommandInteraction, ButtonInteraction } = require('discord.js')
3 | const createImageEmbed = require("./createImageEmbed");
4 | const progressUpdater = require("./progressUpdater");
5 | const sendRequest = require("./sendRequest");
6 |
7 | /**
8 | *
9 | * @param {string} route The SD api route e.g "sdapi/v1/txt2img".
10 | * @param {object} requestData JSON data to be sent with axios request to api.
11 | * @param {ChatInputCommandInteraction | ButtonInteraction} interaction Command interaction to reply to.
12 | * @param {object} embedData createImageEmbed parameters.
13 | */
14 | module.exports = async (route, requestData, interaction, embedData) => {
15 | await interaction.reply({ content: "Waiting for Stable Diffusion..." });
16 |
17 | const imagePromise = sendRequest(route, requestData);
18 |
19 | const progressFinish = await progressUpdater(imagePromise, interaction, embedData.generatingText);
20 |
21 | await interaction.editReply(await createImageEmbed(progressFinish, embedData, interaction.user));
22 | }
--------------------------------------------------------------------------------
/src/utils/SD/getProgressEmbed.js:
--------------------------------------------------------------------------------
1 | const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, User } = require("discord.js");
2 | const sendRequest = require("./sendRequest");
3 | const botConfig = require('../../../botConfig.json');
4 | const base64ToAttachment = require("./base64ToAttachment");
5 |
6 | /**
7 | *
8 | * @param {User} user
9 | * @param {string} generatingText Text to add alongside the progress embed.
10 | * @param {boolean} addCancelButton
11 | * @returns {Promise}
12 | */
13 | module.exports = async (user, generatingText, addCancelButton = true) => {
14 | const imageProgressData = await sendRequest('sdapi/v1/progress?skip_current_image=false', {}, "get");
15 |
16 | if (imageProgressData.current_image == null) { // Make better later
17 | const embed = new EmbedBuilder()
18 | .setColor('Yellow')
19 | .setTitle('Starting...')
20 |
21 | if (botConfig.generation.showImageAuthor) {
22 | embed.setAuthor({name: user.username, iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=256`});
23 | }
24 |
25 | return {content: "", embeds: [embed]};
26 | }
27 |
28 | //Embed
29 | const progress = Math.round(imageProgressData.progress*100);
30 | const progressBarCompletion = Math.ceil(progress/10);
31 |
32 | const imagePreviewFormat = botConfig.generation.imagePreviewFormat;
33 |
34 | const image = await base64ToAttachment(imageProgressData.current_image, imagePreviewFormat, "progress");
35 |
36 | const embed = new EmbedBuilder()
37 | .setTitle(`${generatingText}${progress}%\n|${"▓".repeat(progressBarCompletion)}${"░".repeat(10 - progressBarCompletion)}|`)
38 | .setFooter({text: `ETA: ${imageProgressData.eta_relative.toFixed(1)}s`})
39 | .setImage(`attachment://progress.${imagePreviewFormat}`)
40 | .setColor("Yellow")
41 |
42 | if (botConfig.generation.showImageAuthor) {
43 | embed.setAuthor({name: user.username, iconURL: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}?size=256`});
44 | }
45 |
46 | //Buttons
47 | const row = new ActionRowBuilder();
48 |
49 | if (addCancelButton) {
50 | const cancelBtn = new ButtonBuilder()
51 | .setCustomId(`cancelGeneration-${user.id}`)
52 | .setLabel('Cancel')
53 | .setEmoji('🚫')
54 | .setStyle(ButtonStyle.Danger)
55 |
56 | row.addComponents(cancelBtn);
57 | }
58 |
59 | return {content: "", embeds: [embed], files: [image], components: [row]};
60 | };
--------------------------------------------------------------------------------
/src/utils/SD/imageDataFromEmbed.js:
--------------------------------------------------------------------------------
1 | const axios = require("axios");
2 | const { Embed } = require("discord.js");
3 |
4 | /**
5 | *
6 | * @param {Embed} embed
7 | * @param {boolean} returnImageB64
8 | * @returns {Promise