├── .github └── workflows │ ├── ci.yml │ └── gh-pages.yml ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── book.toml ├── deps.ts ├── discordeno_deps.ts ├── doc_mod.ts ├── docs ├── SUMMARY.md ├── basics │ ├── basic-inhibitors.md │ ├── basic-listeners.md │ └── extending.md ├── commands │ ├── arguments.md │ └── slash.md └── readme.md ├── examples ├── LICENSE ├── arguments │ ├── commands │ │ ├── advanced.ts │ │ ├── ping.ts │ │ └── say.ts │ └── mod.ts ├── basic-commands │ ├── commands │ │ ├── ping.ts │ │ └── say.ts │ └── mod.ts ├── basic-inhibitors │ ├── commands │ │ ├── ping.ts │ │ └── say.ts │ └── mod.ts ├── basic-listeners │ ├── commands │ │ └── ping.ts │ ├── listeners │ │ ├── commandStarted.ts │ │ └── ready.ts │ └── mod.ts ├── basic-tasks │ └── mod.ts ├── button-handler │ ├── buttons │ │ └── button.ts │ ├── commands │ │ ├── button.ts │ │ └── ping.ts │ └── mod.ts ├── deps.ts ├── extending │ ├── commands │ │ └── ping.ts │ ├── extensions │ │ └── command.ts │ ├── mod.ts │ └── readme.md ├── plugging.ts ├── readme.md └── template │ ├── commands │ ├── ping.ts │ ├── say.ts │ └── say │ │ ├── channel.ts │ │ └── dm.ts │ ├── inhibitors │ └── notTricked.ts │ ├── listeners │ ├── commandStarted.ts │ └── ready.ts │ ├── mod.ts │ ├── readme.md │ └── tasks │ └── hello.ts ├── mod.ts └── src ├── mod.ts ├── plugins └── NaticoPlugin.ts ├── struct ├── NaticoClient.ts ├── NaticoHandler.ts ├── NaticoModule.ts ├── buttons │ ├── Button.ts │ └── ButtonHandler.ts ├── commands │ ├── ArgumentGenerator.ts │ ├── Command.ts │ ├── CommandHandler.ts │ ├── SubCommand.ts │ └── commandUtil.ts ├── inhibitors │ ├── Inhibitor.ts │ └── InhibitorHandler.ts ├── listeners │ ├── Listener.ts │ └── ListenerHandler.ts ├── mod.ts └── tasks │ ├── Task.ts │ └── TaskHandler.ts └── util ├── ClientUtil.ts ├── Collection.ts ├── Components.ts ├── Constants.ts ├── Embed.ts ├── Interfaces.ts ├── MessageCollector.ts ├── applyOptions.ts └── mod.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Prettier 2 | 3 | on: [push] 4 | 5 | jobs: 6 | prettier: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | # Make sure the actual branch is checked out when running on pull requests 14 | ref: ${{ github.head_ref }} 15 | # This is important to fetch the changes to the previous commit 16 | fetch-depth: 0 17 | 18 | - name: Prettify code 19 | uses: creyD/prettier_action@v3.3 20 | with: 21 | commit_message: "change: prettier code" 22 | # This part is also where you can pass other options, for example: 23 | prettier_options: --write **/* 24 | only_changed: True 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 27 | -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - "docs/**" 9 | - ".github/**" 10 | - "book.toml" 11 | 12 | jobs: 13 | deploy: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Setup mdBook 19 | uses: peaceiris/actions-mdbook@v1 20 | with: 21 | mdbook-version: "0.4.10" 22 | # mdbook-version: 'latest' 23 | 24 | - run: mdbook build 25 | 26 | - name: Deploy 27 | uses: peaceiris/actions-gh-pages@v3 28 | with: 29 | github_token: ${{ secrets.TOKEN }} 30 | publish_dir: ./book 31 | cname: natico.mod.land/framework 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | book 2 | test.ts 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true, 3 | "deno.lint": true, 4 | "deno.unstable": true, 5 | "deno.suggest.imports.hosts": { 6 | "https://deno.land": false 7 | }, 8 | "cSpell.enabled": false 9 | } 10 | -------------------------------------------------------------------------------- /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 | # **DEPRECATED USE [oasis](https://github.com/yuzudev/oasis) INSTEAD** and make sure to star the oasis repo! 2 | 3 | 4 | 5 | # Natico 6 | 7 | Natico is designed to be a low level and extendable framework for [Discordeno](https://github.com/discordeno/discordeno) 8 | 9 | [![Discord](https://img.shields.io/discord/748956745409232945?style=plastic&color=7289da&logo=discord&logoColor=dark)](https://discord.gg/KkMKCchJb8) 10 | [![lines](https://img.shields.io/tokei/lines/github/naticoo/framework?style=plastic&color=7289da&logo=superuser&logoColor=dark)](https://deno.land/x/natico) 11 | [![lines](https://img.shields.io/website?style=plastic&up_message=online&url=https%3A%2F%2Fnatico.mod.land&color=7289da&logo=React&logoColor=dark)](https://natico.mod.land) 12 | 13 | ## Simple setup 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 53 | 89 | 90 |
Normal naticoUsing the natico plugin
22 | 23 | ```ts 24 | import { 25 | NaticoClient, 26 | NaticoClientOptions, 27 | NaticoCommandHandler 28 | } from "https://deno.land/x/natico/mod.ts"; 29 | class BotClient extends NaticoClient { 30 | constructor(public options: NaticoClientOptions) { 31 | super(options); 32 | } 33 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 34 | directory: "./commands", 35 | prefix: "!", 36 | }); 37 | async start() { 38 | await this.commandHandler.loadALL(); 39 | return this.login(); 40 | } 41 | } 42 | const botClient = new BotClient({ 43 | intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], 44 | token: "your token", 45 | }); 46 | botClient.start(); 47 | ``` 48 | 49 | you will have to apply the plugins manually using the naticoclient.plugn() function 50 | 51 | 52 | 54 | 55 | 56 | ```ts 57 | import { enableNaticoPlugin, NaticoBot, NaticoPluginOptions, withPlugins } from "../src/plugins/NaticoPlugin.ts"; 58 | import { enableCachePlugin, enableCacheSweepers } from "https://deno.land/x/discordeno_cache_plugin@0.0.9/mod.ts"; 59 | 60 | const pluginOps: NaticoPluginOptions = { 61 | commandHandler: { 62 | directory: "examples/template/commands", 63 | prefix: "!", 64 | }, 65 | }; 66 | 67 | const bot = withPlugins>( 68 | //@ts-ignore - 69 | { 70 | token: Deno.env.get("DISCORD_TOKEN")!, 71 | intents: ["Guilds", "GuildMessages"], 72 | botId: BigInt(Deno.env.get("BOT_ID")!), 73 | cache: { 74 | isAsync: false, 75 | }, 76 | }, 77 | [enableNaticoPlugin, pluginOps], 78 | enableCachePlugin, 79 | enableCacheSweepers 80 | ); 81 | async function startUp() { 82 | await bot.commandHandler.loadALL(); 83 | return await bot.login(); 84 | } 85 | startUp(); 86 | ``` 87 | 88 |
91 | 92 | ### Features 93 | 94 | - flexible 95 | - Natico is built using classes allowing you to extend everything and add 96 | features to your liking 97 | - Command handling 98 | - Natico is a slashcommand only framework 99 | - Listeners 100 | - Natico comes included with a listener(events) handler which makes it very 101 | easy to use events 102 | - Inhibitors 103 | - Natico has easy to use inhibitors 104 | - Interaction handling 105 | - Theres built in button and interaction support 106 | - And much more 107 | 108 | This project is inspired by [discord-akairo](https://github.com/discord-akairo/discord-akairo) 109 | 110 | For more information/docs visit the [examples](https://github.com/naticoo/examplebot) page 111 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["SkyBlockDev"] 3 | language = "en" 4 | multilingual = false 5 | src = "docs" 6 | title = "Natico" 7 | default-theme = "Ayu" -------------------------------------------------------------------------------- /deps.ts: -------------------------------------------------------------------------------- 1 | export { EventEmitter } from "https://deno.land/std@0.98.0/node/events.ts"; 2 | export type { GenericFunction, WrappedFunction } from "https://deno.land/std@0.98.0/node/events.ts"; 3 | export * from "./discordeno_deps.ts"; 4 | export type { NaticoMessage as DiscordenoMessage } from "./src/util/Interfaces.ts"; 5 | -------------------------------------------------------------------------------- /discordeno_deps.ts: -------------------------------------------------------------------------------- 1 | export * from "https://deno.land/x/discordeno@13.0.0-rc14/mod.ts"; 2 | -------------------------------------------------------------------------------- /doc_mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/mod.ts"; 2 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- 1 | ## Natico 2 | 3 | [Intro](readme.md) 4 | 5 | - [Basics](basics/basic-inhibitors.md) 6 | 7 | - [Inhibitors](basics/basic-inhibitors.md) 8 | - [Listeners](basics/basic-listeners.md) 9 | - [Extending](basics/extending.md) 10 | 11 | - [Commands](commands/slash.md) 12 | - [Slash Commands](commands/slash.md) 13 | - [Arguments](commands/arguments.md) 14 | -------------------------------------------------------------------------------- /docs/basics/basic-inhibitors.md: -------------------------------------------------------------------------------- 1 | # Basic inhibitor setup 2 | 3 | Inhibitors are a great way to prevent users from using certain commands or all 4 | 5 | In this tutoroial ill show how to them up 6 | 7 | this guide assumes that you are already using the intro bot 8 | 9 | ```ts 10 | //mod.ts 11 | ... 12 | //You are going to add this under the command handler 13 | inhibitorHandler: NaticoInhibitorHandler = new NaticoInhibitorHandler(this, { 14 | directory: './inhibitors', 15 | }); 16 | ... 17 | async start(token: string) { 18 | //Set the inhibitor handler to be used 19 | this.commandHandler.setInhibitorHandler(this.inhibitorHandler); 20 | //Then load the commands as usual 21 | await this.commandHandler.loadALL(); 22 | //And the inhibitors 23 | await this.inhibitorHandler.loadALL(); 24 | return this.login(token); 25 | } 26 | ... 27 | ``` 28 | 29 | ## Creating a inhibitor 30 | 31 | ```ts 32 | //inhibitors/notTricked.ts 33 | import { DiscordenoMessage, NaticoCommand, NaticoInhibitor } from "../deps.ts"; 34 | export default class notTricked extends NaticoInhibitor { 35 | constructor() { 36 | super("notTricked", { 37 | //Higher priotiry = earlier fire 38 | priority: 1, 39 | }); 40 | } 41 | 42 | exec(message: DiscordenoMessage, command: NaticoCommand): boolean { 43 | //This checks if the command thats being ran has the name ping 44 | if (command.name == "ping") { 45 | //Checks if the user their id is the one from tricked and if it isnt returns true 46 | //Returning true means the command is blocked 47 | if (message.authorId !== 336465356304678913n) { 48 | message.reply("You are not allowed to run this command"); 49 | return true; 50 | } 51 | } 52 | //Otherwise it just runs the command 53 | return false; 54 | } 55 | } 56 | ``` 57 | 58 | Resulting code can be found [here](https://github.com/naticoo/examplebot/tree/main/basic-inhibitors) 59 | -------------------------------------------------------------------------------- /docs/basics/basic-listeners.md: -------------------------------------------------------------------------------- 1 | # Basic listeners setup 2 | 3 | Listeners are a way of getting events and running code on them 4 | 5 | this guide assumes that you are already using the intro bot 6 | 7 | ```ts 8 | //mod.ts 9 | //Set the directory of the listener handler 10 | ... 11 | listenerHandler: NaticoListenerHandler = new NaticoListenerHandler(this, { 12 | directory: './listeners', 13 | }); 14 | 15 | async start(token: string) { 16 | //Set the emitters 17 | //Emitter must be set before loading any listeners otherwise it will cause errors 18 | this.listenerHandler.setEmitters({ 19 | commandHandler: this.commandHandler, 20 | }); 21 | //Loading the stuff 22 | await this.listenerHandler.loadALL(); 23 | await this.commandHandler.loadALL(); 24 | return this.login(token); 25 | } 26 | ... 27 | 28 | ``` 29 | 30 | ### Creating a listener 31 | 32 | ```ts 33 | //listeners/ready.ts 34 | import { NaticoListener } from "../deps.ts"; 35 | export default class ready extends NaticoListener { 36 | constructor() { 37 | super("ready", { 38 | //The emitter of the event 39 | emitter: "client", 40 | //THe event your listening for 41 | event: "ready", 42 | }); 43 | } 44 | 45 | exec() { 46 | console.log("Bot has started"); 47 | } 48 | } 49 | ``` 50 | 51 | This a listener for the command handler but you can also listen to other events, For example the commandStarted event: 52 | 53 | ```js 54 | import { ConvertedOptions, discordenoMessage, NaticoCommand, NaticoListener } from "../deps.ts"; 55 | export default class commandStarted extends NaticoListener { 56 | constructor() { 57 | super("commandStarted", { 58 | //In this example we are using the commandHandler that was specified in the mod.ts 59 | emitter: "commandHandler", 60 | event: "commandStarted", 61 | }); 62 | } 63 | 64 | exec(message: discordenoMessage, command: NaticoCommand, args: ConvertedOptions) { 65 | console.log("command:", command.id, "started with args", args); 66 | } 67 | } 68 | ``` 69 | 70 | Resulting code can be found [here](https://github.com/naticoo/examplebot/tree/main/basic-listeners) 71 | -------------------------------------------------------------------------------- /docs/basics/extending.md: -------------------------------------------------------------------------------- 1 | # Extending 2 | 3 | this guide assumes that you are already using the basic-listeners bot 4 | 5 | Extending is often useful to add new types to properties 6 | 7 | ```ts 8 | //extensions/command.ts 9 | import { NaticoCommand } from "../deps.ts"; 10 | import { BotClient } from "../mod.ts"; 11 | export class botCommand extends NaticoCommand { 12 | //Overwriting the old NaticoClient that was here with your custom one 13 | declare client: BotClient; 14 | } 15 | ``` 16 | 17 | ### Using your custom command 18 | 19 | After extending NaticoCommand you need to import it to your commands 20 | 21 | ```ts 22 | import { botCommand } from "../extensions/command.ts"; 23 | export default class listeners extends botCommand { 24 | constructor() { 25 | super("listeners", { 26 | name: "listeners", 27 | aliases: ["listeners"], 28 | description: "View the amount of listeners the bot has loaded", 29 | }); 30 | } 31 | exec(message: discordenoMessage) { 32 | //Without extending this would have caused a type error 33 | message.reply(`Pong ${this.client.listenerHandler.modules.size} listeners`); 34 | } 35 | } 36 | ``` 37 | 38 | You can find the resulting code [here](https://github.com/naticoo/examplebot/tree/main/extending) 39 | -------------------------------------------------------------------------------- /docs/commands/arguments.md: -------------------------------------------------------------------------------- 1 | Arguments are as easy as making slash commands in natico heres a quick example 2 | 3 | A good way to make slash command arguments is to use [rauf.wtf](https://rauf.wtf/slash/) 4 | 5 | ```ts 6 | //Some command.ts 7 | import { NaticoCommand, DiscordenoMessage, cache, Matches, DiscordenoMember } from "../../deps.ts"; 8 | export default class bigSay extends NaticoCommand { 9 | constructor() { 10 | super("bigsay", { 11 | name: "bigsay", 12 | aliases: ["bigsay"], 13 | ownerOnly: true, 14 | options: [ 15 | { 16 | type: 3, 17 | name: "user", 18 | description: "The user you want to dm", 19 | required: true, 20 | match: Matches.rest, 21 | customType: (message, content) => { 22 | //The last item in the array will be the new rest 23 | return [cache.members.get(BigInt(content.split(" ")[0])), content.split(" ").slice(1).join(" ")]; 24 | }, 25 | }, 26 | { 27 | type: 3, 28 | name: "text", 29 | description: "text you want the bot to say", 30 | required: true, 31 | match: Matches.rest, 32 | customType: (message, content) => { 33 | return content.split(" ").reverse().join("🦀"); 34 | }, 35 | }, 36 | ], 37 | }); 38 | } 39 | async exec(message: DiscordenoMessage, { text, user }: { text: string; user: DiscordenoMember }) { 40 | if (user) await user.sendDM(text); 41 | message.reply(`dmed ${user.name} ${text}`); 42 | } 43 | } 44 | ``` 45 | 46 | Even subcommands are handled the same way heres a quick example 47 | 48 | ```ts 49 | import { NaticoClient, NaticoCommandHandler, NaticoClientOptions } from "../../deps.ts"; 50 | class BotClient extends NaticoClient { 51 | ... 52 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 53 | subType: "multiple", 54 | }); 55 | ... 56 | } 57 | ``` 58 | 59 | ```ts 60 | import { NaticoCommand, DiscordenoMessage } from "../../../deps.ts"; 61 | export default class say extends NaticoCommand { 62 | constructor() { 63 | super("say", { 64 | name: "say", 65 | aliases: ["say"], 66 | options: [ 67 | { 68 | type: 1, 69 | name: "channel", 70 | description: "says the stuff in the current channel", 71 | options: [ 72 | { 73 | type: 3, 74 | name: "text", 75 | description: "stuff you want to say in the channel", 76 | required: false, 77 | }, 78 | ], 79 | }, 80 | 81 | { 82 | type: 1, 83 | name: "dm", 84 | description: "dms you the stuff instead", 85 | options: [ 86 | { 87 | type: 3, 88 | name: "text", 89 | description: "Things you want to dm the user", 90 | required: false, 91 | }, 92 | ], 93 | }, 94 | ], 95 | }); 96 | } 97 | exec(message: DiscordenoMessage) { 98 | //It will default to this without arguments 99 | message.reply("Please chooose between " + this.options!.map((option) => option.name).join(", ")); 100 | } 101 | } 102 | ``` 103 | 104 | ```ts 105 | import { DiscordenoMessage, NaticoSubCommand } from "../../../../deps.ts"; 106 | export default class channel extends NaticoSubCommand { 107 | constructor() { 108 | super("channel", { 109 | name: "channel", 110 | subOf: "say", 111 | options: [ 112 | { 113 | type: 3, 114 | name: "text", 115 | description: "stuff you want to say in the channel", 116 | required: false, 117 | }, 118 | ], 119 | }); 120 | } 121 | exec(message: DiscordenoMessage, a: { text: string }) { 122 | message.channel!.send(a.text || "nothing"); 123 | } 124 | } 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/commands/slash.md: -------------------------------------------------------------------------------- 1 | This page is in construction. 2 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Tutorial Intro 2 | 3 | Let's discover **Natico in less than 5 minutes**. 4 | 5 | ## Getting Started 6 | 7 | Get started by **Downloading [deno](https://deno.land/)**. 8 | 9 | First of you are going to create a deps.ts file with the following code we do this so we can import everything from the deps.ts file when you need to import more things 10 | 11 | ```ts 12 | //deps.ts 13 | export * from "https://deno.land/x/natico/mod.ts"; 14 | ``` 15 | 16 | ### Creating the bot client 17 | 18 | Then create a mod.ts file with the following code 19 | 20 | ```ts 21 | //mod.ts 22 | import { NaticoCommandHandler, NaticoClient } from "./deps.ts"; 23 | class BotClient extends NaticoClient { 24 | constructor(public options: NaticoClientOptions) { 25 | super(options); 26 | } 27 | // This is the command handler that will be handling your commands 28 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 29 | directory: "./commands", 30 | prefix: "!", 31 | }); 32 | async start(token: string) { 33 | // Loading its modules 34 | await this.commandHandler.loadALL(); 35 | //Then just simply login 36 | return this.login(token); 37 | } 38 | } 39 | const botClient = new BotClient({ 40 | intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], 41 | }); 42 | botClient.start("Insert your token here"); 43 | ``` 44 | 45 | ### Creating your first command 46 | 47 | Then we will be making our first command 48 | 49 | ```ts 50 | //commands/ping.ts 51 | import { NaticoCommand, DiscordenoMessage } from "../deps.ts"; 52 | export default class ping extends NaticoCommand { 53 | constructor() { 54 | super("ping", { 55 | name: "ping", 56 | aliases: ["ping"], 57 | }); 58 | } 59 | exec(message: DiscordenoMessage) { 60 | message.reply("Pong"); 61 | } 62 | } 63 | ``` 64 | 65 | And now your all set to start creating your bot with discordeno and natico 66 | 67 | All resulting code can be found [here](https://github.com/naticoo/examplebot/tree/main/basic-commands) 68 | 69 | api documentation can be found [here](https://doc.deno.land/https/deno.land/x/natico/mod.ts) once the deno team learns how to code 70 | 71 | Full bot examples can be found [here](https://github.com/naticoo/examplebot/tree/main) 72 | -------------------------------------------------------------------------------- /examples/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 naticoo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/arguments/commands/advanced.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage, cache, Matches, DiscordenoMember } from "../../deps.ts"; 2 | export default class bigSay extends NaticoCommand { 3 | constructor() { 4 | super("bigsay", { 5 | name: "bigsay", 6 | aliases: ["bigsay"], 7 | ownerOnly: true, 8 | options: [ 9 | { 10 | type: 3, 11 | name: "user", 12 | description: "The user you want to dm", 13 | required: true, 14 | match: Matches.rest, 15 | customType: (message, content) => { 16 | //The last item in the array will be the new rest 17 | return [cache.members.get(BigInt(content.split(" ")[0])), content.split(" ").slice(1).join(" ")]; 18 | }, 19 | }, 20 | { 21 | type: 3, 22 | name: "text", 23 | description: "text you want the bot to say", 24 | required: true, 25 | match: Matches.rest, 26 | customType: (message, content) => { 27 | return content.split(" ").reverse().join("🦀"); 28 | }, 29 | }, 30 | ], 31 | }); 32 | } 33 | async exec(message: DiscordenoMessage, { text, user }: { text: string; user: DiscordenoMember }) { 34 | if (user) await user.sendDM(text); 35 | message.reply(`dmed ${user.name} ${text}`); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/arguments/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage } from "../../deps.ts"; 2 | export default class ping extends NaticoCommand { 3 | constructor() { 4 | super("ping", { 5 | name: "ping", 6 | aliases: ["ping"], 7 | }); 8 | } 9 | exec(message: DiscordenoMessage) { 10 | message.reply("Pong"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/arguments/commands/say.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage, Matches } from "../../deps.ts"; 2 | export default class say extends NaticoCommand { 3 | constructor() { 4 | super("say", { 5 | name: "say", 6 | aliases: ["say"], 7 | options: [ 8 | { 9 | type: 3, 10 | name: "text", 11 | description: "text you want the bot to say", 12 | required: true, 13 | match: Matches.rest, 14 | customType: (message, content) => { 15 | return content.split(" ").reverse().join("🦀"); 16 | }, 17 | }, 18 | ], 19 | }); 20 | } 21 | exec(message: DiscordenoMessage, { text }: { text: string }) { 22 | message.reply(text); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/arguments/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoClientOptions } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: "!", 9 | owners: [336465356304678913n], 10 | guildonly: false, 11 | }); 12 | async start() { 13 | await this.commandHandler.loadALL(); 14 | return this.login(); 15 | } 16 | } 17 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 18 | botClient.start(); 19 | -------------------------------------------------------------------------------- /examples/basic-commands/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage } from "../../deps.ts"; 2 | export default class ping extends NaticoCommand { 3 | constructor() { 4 | super("ping", { 5 | name: "ping", 6 | aliases: ["ping"], 7 | }); 8 | } 9 | exec(message: DiscordenoMessage) { 10 | message.reply("Pong"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/basic-commands/commands/say.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, Matches, DiscordenoMessage } from "../../deps.ts"; 2 | export default class say extends NaticoCommand { 3 | constructor() { 4 | super("say", { 5 | name: "say", 6 | aliases: ["say"], 7 | options: [ 8 | { 9 | //Args are setup like slash commands, later on ill make a arg parser for now this always gives strings 10 | type: 3, 11 | name: "text", 12 | description: "text you want the bot to say", 13 | required: true, 14 | match: Matches.rest, 15 | }, 16 | ], 17 | }); 18 | } 19 | exec(message: DiscordenoMessage, { text }: { text: string }) { 20 | message.reply("Pong" + text); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/basic-commands/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoClientOptions } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: "!", 9 | owners: [336465356304678913n], 10 | guildonly: false, 11 | }); 12 | async start() { 13 | await this.commandHandler.loadALL(); 14 | return this.login(); 15 | } 16 | } 17 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 18 | botClient.start(); 19 | -------------------------------------------------------------------------------- /examples/basic-inhibitors/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage } from "../../deps.ts"; 2 | export default class ping extends NaticoCommand { 3 | constructor() { 4 | super("ping", { 5 | name: "ping", 6 | aliases: ["ping"], 7 | }); 8 | } 9 | exec(message: DiscordenoMessage) { 10 | message.reply("Pong"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/basic-inhibitors/commands/say.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, Matches, DiscordenoMessage } from "../../deps.ts"; 2 | export default class say extends NaticoCommand { 3 | constructor() { 4 | super("say", { 5 | name: "say", 6 | aliases: ["say"], 7 | options: [ 8 | { 9 | //Args are setup like slash commands, later on ill make a arg parser for now this always gives strings 10 | type: 3, 11 | name: "text", 12 | description: "text you want the bot to say", 13 | required: true, 14 | match: Matches.rest, 15 | }, 16 | ], 17 | }); 18 | } 19 | exec(message: DiscordenoMessage, { text }: { text: string }) { 20 | message.reply("Pong" + text); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/basic-inhibitors/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoInhibitorHandler, NaticoClientOptions } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: "!", 9 | owners: [336465356304678913n], 10 | guildonly: false, 11 | }); 12 | inhibitorHandler: NaticoInhibitorHandler = new NaticoInhibitorHandler(this, { 13 | directory: "./inhibitors", 14 | }); 15 | async start() { 16 | //Set the inhibitor handler to be used 17 | this.commandHandler.setInhibitorHandler(this.inhibitorHandler); 18 | await this.commandHandler.loadALL(); 19 | await this.inhibitorHandler.loadALL(); 20 | return this.login(); 21 | } 22 | } 23 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 24 | botClient.start(); 25 | -------------------------------------------------------------------------------- /examples/basic-listeners/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand } from "../../deps.ts"; 2 | export default class ping extends NaticoCommand { 3 | constructor() { 4 | super("ping", { 5 | name: "ping", 6 | aliases: ["ping"], 7 | }); 8 | } 9 | exec(message) { 10 | message.reply("Pong"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/basic-listeners/listeners/commandStarted.ts: -------------------------------------------------------------------------------- 1 | import { ConvertedOptions, discordenoMessage, NaticoCommand, NaticoListener } from "../../deps.ts"; 2 | export default class commandStarted extends NaticoListener { 3 | constructor() { 4 | super("commandStarted", { 5 | emitter: "commandHandler", 6 | event: "commandStarted", 7 | }); 8 | } 9 | 10 | exec(message: discordenoMessage, command: NaticoCommand, args: ConvertedOptions) { 11 | console.log("command:", command.id, "started with args", args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/basic-listeners/listeners/ready.ts: -------------------------------------------------------------------------------- 1 | import { NaticoListener } from "../../deps.ts"; 2 | export default class ready extends NaticoListener { 3 | constructor() { 4 | super("ready", { 5 | emitter: "client", 6 | event: "ready", 7 | }); 8 | } 9 | 10 | exec() { 11 | console.log("Bot has started"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/basic-listeners/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoListenerHandler, NaticoClientOptions } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: "!", 9 | }); 10 | listenerHandler: NaticoListenerHandler = new NaticoListenerHandler(this, { 11 | directory: "./listeners", 12 | }); 13 | async start() { 14 | this.listenerHandler.setEmitters({ 15 | commandHandler: this.commandHandler, 16 | }); 17 | await this.listenerHandler.loadALL(); 18 | await this.commandHandler.loadALL(); 19 | return this.login(); 20 | } 21 | } 22 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 23 | botClient.start(); 24 | -------------------------------------------------------------------------------- /examples/basic-tasks/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoTaskHandler, NaticoClientOptions } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | taskHandler: NaticoTaskHandler = new NaticoTaskHandler(this, { 7 | directory: "./tasks", 8 | }); 9 | 10 | async start() { 11 | await this.taskHandler.loadALL(); 12 | return this.login(); 13 | } 14 | } 15 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 16 | botClient.start(); 17 | -------------------------------------------------------------------------------- /examples/button-handler/buttons/button.ts: -------------------------------------------------------------------------------- 1 | import { 2 | sendInteractionResponse, 3 | DiscordenoMember, 4 | ComponentInteraction, 5 | NaticoButton, 6 | DiscordInteractionResponseTypes, 7 | } from "../../deps.ts"; 8 | export default class button extends NaticoButton { 9 | constructor() { 10 | super("say", { 11 | trigger: "dothings", 12 | }); 13 | } 14 | exec(interaction: ComponentInteraction, member: DiscordenoMember, params: string[]) { 15 | return sendInteractionResponse(interaction.id, interaction.token, { 16 | type: DiscordInteractionResponseTypes.ChannelMessageWithSource, 17 | private: false, 18 | data: { 19 | content: `You ( ${member.tag} ) Clicked <@${params}>'s button :flushed:`, 20 | }, 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/button-handler/commands/button.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage, NaticoComponents } from "../../deps.ts"; 2 | export default class button extends NaticoCommand { 3 | constructor() { 4 | super("button", { 5 | name: "button", 6 | aliases: ["button"], 7 | }); 8 | } 9 | exec(message: DiscordenoMessage) { 10 | return message.util.reply({ 11 | content: "Look a wild button!", 12 | components: new NaticoComponents().addButton("Clicky boi", "Primary", `dothings-${message.authorId}`), 13 | }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/button-handler/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, DiscordenoMessage } from "../../deps.ts"; 2 | export default class ping extends NaticoCommand { 3 | constructor() { 4 | super("ping", { 5 | name: "ping", 6 | aliases: ["ping"], 7 | }); 8 | } 9 | exec(message: DiscordenoMessage) { 10 | message.reply("Pong"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/button-handler/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoClientOptions, NaticoButtonHandler } from "../deps.ts"; 2 | class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: "!", 9 | owners: [336465356304678913n], 10 | guildonly: false, 11 | }); 12 | buttonHandler: NaticoButtonHandler = new NaticoButtonHandler(this, { 13 | directory: "./buttons", 14 | }); 15 | async start() { 16 | await this.commandHandler.loadALL(); 17 | await this.buttonHandler.loadALL(); 18 | return this.login(); 19 | } 20 | } 21 | const botClient = new BotClient({ 22 | intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], 23 | token: "", 24 | }); 25 | botClient.start(); 26 | -------------------------------------------------------------------------------- /examples/deps.ts: -------------------------------------------------------------------------------- 1 | // export * from "https://deno.land/x/natico/mod.ts"; 2 | 3 | export * from "../mod.ts"; 4 | -------------------------------------------------------------------------------- /examples/extending/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { botCommand } from "../extensions/command.ts"; 2 | export default class listeners extends botCommand { 3 | constructor() { 4 | super("listeners", { 5 | name: "listeners", 6 | aliases: ["listeners"], 7 | description: "View the amount of listeners the bot has loaded", 8 | }); 9 | } 10 | exec(message: discordenoMessage) { 11 | //Without extending this would have caused a type error 12 | message.reply(`Pong ${this.client.listenerHandler.modules.size} listeners`); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/extending/extensions/command.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand } from "../../deps.ts"; 2 | import { BotClient } from "../mod.ts"; 3 | export class botCommand extends NaticoCommand { 4 | declare client: BotClient; 5 | } 6 | -------------------------------------------------------------------------------- /examples/extending/mod.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient, NaticoCommandHandler, NaticoListenerHandler, NaticoClientOptions } from "../deps.ts"; 2 | export class BotClient extends NaticoClient { 3 | constructor(public options: NaticoClientOptions) { 4 | super(options); 5 | } 6 | commandHandler: NaticoCommandHandler = new NaticoCommandHandler(this, { 7 | directory: "./commands", 8 | prefix: ["!"], 9 | }); 10 | listenerHandler: NaticoListenerHandler = new NaticoListenerHandler(this, { 11 | directory: "./listeners", 12 | }); 13 | async start() { 14 | this.listenerHandler.setEmitters({ 15 | commandHandler: this.commandHandler, 16 | }); 17 | await this.listenerHandler.loadALL(); 18 | await this.commandHandler.loadALL(); 19 | return this.login(); 20 | } 21 | } 22 | const botClient = new BotClient({ intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], token: "" }); 23 | botClient.start(); 24 | -------------------------------------------------------------------------------- /examples/extending/readme.md: -------------------------------------------------------------------------------- 1 | extending things allows you to change the types of certain things as seen in this example 2 | -------------------------------------------------------------------------------- /examples/plugging.ts: -------------------------------------------------------------------------------- 1 | import { enableNaticoPlugin, NaticoBot, NaticoPluginOptions, withPlugins } from "../src/plugins/NaticoPlugin.ts"; 2 | import { enableCachePlugin, enableCacheSweepers } from "https://deno.land/x/discordeno_cache_plugin@0.0.9/mod.ts"; 3 | 4 | const pluginOps: NaticoPluginOptions = { 5 | commandHandler: { 6 | directory: "examples/template/commands", 7 | prefix: "!", 8 | }, 9 | }; 10 | 11 | const bot = withPlugins>( 12 | //@ts-ignore - 13 | { 14 | token: Deno.env.get("DISCORD_TOKEN")!, 15 | intents: ["Guilds", "GuildMessages"], 16 | botId: BigInt(Deno.env.get("BOT_ID")!), 17 | cache: { 18 | isAsync: false, 19 | }, 20 | }, 21 | [enableNaticoPlugin, pluginOps], 22 | enableCachePlugin, 23 | enableCacheSweepers 24 | ); 25 | async function startUp() { 26 | await bot.commandHandler.loadALL(); 27 | return await bot.login(); 28 | } 29 | startUp(); 30 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | ## Examples of using the natico framework 2 | 3 | ### Not all examples are updated 4 | 5 | - please use common sense when trying to make these examples work 6 | - all examples except for template are outdated and i (skyblockdev) do not feel like updating them if anyone else does feel free to make a pull request 7 | 8 | ### OpenSource bots using natico 9 | 10 | - [hippobot](https://github.com/HypeLevels/hippobot) 11 | - [template](template/readme.md) 12 | 13 | ### Docs 14 | 15 | - https://doc.deno.land/https/deno.land/x/natico/mod.ts 16 | 17 | ### To-do's 18 | 19 | - Add buttons to template 20 | - Extensions to Template 21 | - More argument stuff 22 | - Slash command info 23 | - Comment more things 24 | - More example commands 25 | 26 | Feel free to contribute these things 27 | 28 | ### License 29 | 30 | > Examples are licensed under the terms of the [MIT license](LICENSE) 31 | -------------------------------------------------------------------------------- /examples/template/commands/ping.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyOptions, 3 | DiscordenoInteraction, 4 | NaticoCommand, 5 | NaticoCommandOptions, 6 | sendInteractionResponse, 7 | InteractionApplicationCommandCallbackData, 8 | } from "../../deps.ts"; 9 | 10 | //@deno-fmt-ignore 11 | @applyOptions({ 12 | id: "stats", 13 | name: "stats", 14 | aliases: ["stats", "stats"], 15 | }) 16 | export default class ping extends NaticoCommand { 17 | async exec(message: DiscordenoInteraction) { 18 | await this.client.helpers.sendInteractionResponse(message.id, message.token, { 19 | type: 4, 20 | data: { 21 | content: "My response", 22 | }, 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/template/commands/say.ts: -------------------------------------------------------------------------------- 1 | import { applyOptions, DiscordenoMessage, NaticoCommand, NaticoCommandOptions } from "../../deps.ts"; 2 | 3 | //@deno-fmt-ignore 4 | @applyOptions({ 5 | id: "say", 6 | name: "say", 7 | aliases: ["say"], 8 | options: [ 9 | //You dont have to add the options here but its recomended once natico is compatible with slash commands 10 | { 11 | type: 1, 12 | name: "channel", 13 | description: "says the stuff in the current channel", 14 | }, 15 | 16 | { 17 | type: 1, 18 | name: "dm", 19 | description: "dms you the stuff instead", 20 | }, 21 | ], 22 | }) 23 | export default class say extends NaticoCommand { 24 | exec(message: DiscordenoMessage) { 25 | //It will default to this without arguments 26 | message.reply("Please chooose between " + this.options!.map((option) => option.name).join(", ")); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /examples/template/commands/say/channel.ts: -------------------------------------------------------------------------------- 1 | import { applyOptions, DiscordenoMessage, NaticoSubCommand, NaticoSubCommandOptions } from "../../../deps.ts"; 2 | 3 | //@deno-fmt-ignore 4 | @applyOptions({ 5 | id: "saychannel", 6 | name: "channel", 7 | subOf: "say", 8 | options: [ 9 | { 10 | type: 3, 11 | name: "text", 12 | description: "stuff you want to say in the channel", 13 | required: false, 14 | }, 15 | ], 16 | }) 17 | export default class channel extends NaticoSubCommand { 18 | exec(message: DiscordenoMessage, a: { text: string }) { 19 | message.channel!.send(a.text || "nothing"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/template/commands/say/dm.ts: -------------------------------------------------------------------------------- 1 | import { applyOptions, DiscordenoMessage, NaticoSubCommand, NaticoSubCommandOptions } from "../../../deps.ts"; 2 | 3 | //@deno-fmt-ignore 4 | @applyOptions({ 5 | id: "dm", 6 | name: "dm", 7 | subOf: "say", 8 | options: [ 9 | { 10 | type: 3, 11 | name: "text", 12 | description: "text to dm", 13 | required: false, 14 | }, 15 | ], 16 | }) 17 | export default class dm extends NaticoSubCommand { 18 | exec(message: DiscordenoMessage, a: { text: string }) { 19 | message.member!.sendDM(a.text || "nothing"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/template/inhibitors/notTricked.ts: -------------------------------------------------------------------------------- 1 | import { applyOptions, DiscordenoMessage, NaticoCommand, NaticoInhibitor, NaticoInhibitorOptions } from "../../deps.ts"; 2 | 3 | //@deno-fmt-ignore 4 | @applyOptions({ 5 | id: "notTricked", 6 | //Higher priotiry = earlier fire 7 | priority: 1, 8 | }) 9 | export default class notTricked extends NaticoInhibitor { 10 | exec(message: DiscordenoMessage, command: NaticoCommand): boolean { 11 | if (command.name == "say") { 12 | if (message.authorId !== 336465356304678913n) return true; 13 | } 14 | message.reply("You are not allowed to run this command"); 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/template/listeners/commandStarted.ts: -------------------------------------------------------------------------------- 1 | import { 2 | applyOptions, 3 | ConvertedOptions, 4 | DiscordenoMessage, 5 | NaticoCommand, 6 | NaticoListener, 7 | NaticoListenerOptions, 8 | } from "../../deps.ts"; 9 | 10 | //@deno-fmt-ignore 11 | @applyOptions({ 12 | id: "commandStarted", 13 | emitter: "commandHandler", 14 | event: "commandStarted", 15 | }) 16 | export default class commandStarted extends NaticoListener { 17 | exec(message: DiscordenoMessage, command: NaticoCommand, args: ConvertedOptions) { 18 | console.log("command:", command.id, "started with args", args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/template/listeners/ready.ts: -------------------------------------------------------------------------------- 1 | import { applyOptions, NaticoListener, NaticoListenerOptions } from "../../deps.ts"; 2 | 3 | //@deno-fmt-ignore 4 | @applyOptions({ 5 | id: "ready", 6 | emitter: "client", 7 | event: "ready", 8 | }) 9 | export default class ready extends NaticoListener { 10 | async exec() { 11 | console.log(`Bot has started with id ${this.client.applicationId}`); 12 | //@ts-ignore - 13 | await this.client.commandHandler.enableSlash(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/template/mod.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DiscordenoMember, 3 | NaticoClient, 4 | NaticoClientOptions, 5 | NaticoCommandHandler, 6 | NaticoInhibitorHandler, 7 | NaticoListenerHandler, 8 | NaticoTaskHandler, 9 | } from "../deps.ts"; 10 | class BotClient extends NaticoClient { 11 | constructor(options: NaticoClientOptions) { 12 | super(options); 13 | } 14 | 15 | commandHandler = new NaticoCommandHandler(this, { 16 | directory: "./commands", 17 | prefix: "!", 18 | owners: [336465356304678913n], 19 | guildonly: false, 20 | subType: "single", 21 | }); 22 | listenerHandler = new NaticoListenerHandler(this, { 23 | directory: "./listeners", 24 | }); 25 | inhibitorHandler = new NaticoInhibitorHandler(this, { 26 | directory: "./inhibitors", 27 | }); 28 | taskHandler = new NaticoTaskHandler(this, { 29 | directory: "./tasks", 30 | }); 31 | async start() { 32 | await this.commandHandler.loadALL(); 33 | this.listenerHandler.setEmitters({ 34 | commandHandler: this.commandHandler, 35 | }); 36 | await this.listenerHandler.loadALL(); 37 | await this.inhibitorHandler.loadALL(); 38 | await this.taskHandler.loadALL(); 39 | 40 | return this.login(); 41 | } 42 | } 43 | const botClient = new BotClient({ 44 | intents: ["Guilds", "GuildMessages", "GuildVoiceStates"], 45 | //Replace this with your token 46 | token: Deno.env.get("TOKEN")!, 47 | cache: { isAsync: false }, 48 | applicationId: 826480286169956413n, 49 | }); 50 | //DO NOT AWAIT THIS IT WILL CAUSE ISSUES 51 | botClient.start(); 52 | -------------------------------------------------------------------------------- /examples/template/readme.md: -------------------------------------------------------------------------------- 1 | ## Template 2 | 3 | This is the template starting of this example should make using natico easier 4 | some things may be broken and you should fix those things yourself using common sense 5 | -------------------------------------------------------------------------------- /examples/template/tasks/hello.ts: -------------------------------------------------------------------------------- 1 | import { NaticoTask, applyOptions, NaticoTaskOptions } from "../../deps.ts"; 2 | @applyOptions({ 3 | id: "say hello", 4 | delay: 600000, //10 minutes this function runs every 10 minutes 5 | runOnStart: true, //run it when the client is ready 6 | }) 7 | export default class invite extends NaticoTask { 8 | exec() { 9 | console.log("Hello"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./doc_mod.ts"; 2 | export * from "./discordeno_deps.ts"; 3 | export type { NaticoMessage as DiscordenoMessage } from "./src/util/Interfaces.ts"; 4 | -------------------------------------------------------------------------------- /src/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./util/mod.ts"; 2 | export * from "./struct/mod.ts"; 3 | -------------------------------------------------------------------------------- /src/plugins/NaticoPlugin.ts: -------------------------------------------------------------------------------- 1 | import { Bot, startBot, CreateBotOptions, createBot, EventEmitter, EventHandlers, Collection } from "../../deps.ts"; 2 | import { 3 | NaticoButtonHandler, 4 | NaticoClient, 5 | NaticoCommandHandler, 6 | NaticoCommandHandlerOptions, 7 | NaticoInhibitorHandler, 8 | NaticoListenerHandler, 9 | NaticoTaskHandler, 10 | } from "../mod.ts"; 11 | 12 | export interface NaticoPluginOptions { 13 | commandHandler?: NaticoCommandHandlerOptions | false; 14 | buttonHandler?: { directory: string } | false; 15 | inhibitorHandler?: { directory: string } | false; 16 | listenerHandler?: { directory: string } | false; 17 | taskHandler?: { directory: string } | false; 18 | } 19 | 20 | export interface NaticoBot extends Bot, NaticoClient { 21 | //@ts-ignore - 22 | commandHandler: NaticoCommandHandler; 23 | //@ts-ignore - 24 | buttonHandler: NaticoButtonHandler; 25 | //@ts-ignore - 26 | inhibitorHandler: NaticoInhibitorHandler; 27 | //@ts-ignore - 28 | listenerHandler: NaticoListenerHandler; 29 | //@ts-ignore - 30 | taskHandler: NaticoTaskHandler; 31 | } 32 | 33 | // PLUGINS MUST TAKE A BOT ARGUMENT WHICH WILL BE MODIFIED 34 | export function enableNaticoPlugin( 35 | inputBot: Partial> & Bot, 36 | naticoOptions: NaticoPluginOptions 37 | ): NaticoBot & NaticoClient { 38 | //@ts-ignore - 39 | const bot = new (class extends NaticoClient {})({ intents: [], cache: { isAsync: false } }) as any; 40 | Object.assign(bot, inputBot); 41 | // MARK THIS PLUGIN BEING USED 42 | bot.enabledPlugins!.add("NATICO"); 43 | 44 | // CUSTOMIZATION GOES HERE 45 | //@ts-ignore - 46 | if (naticoOptions.commandHandler) bot.commandHandler = new NaticoCommandHandler(bot, naticoOptions.commandHandler); 47 | //@ts-ignore - 48 | if (naticoOptions.buttonHandler) bot.buttonHandler = new NaticoButtonHandler(bot, naticoOptions.buttonHandler); 49 | if (naticoOptions.inhibitorHandler) 50 | //@ts-ignore - 51 | bot.inhibitorHandler = new NaticoInhibitorHandler(bot, naticoOptions.inhibitorHandler); 52 | if (naticoOptions.listenerHandler) 53 | //@ts-ignore - 54 | bot.listenerHandler = new NaticoListenerHandler(bot, naticoOptions.listenerHandler); 55 | //@ts-ignore - 56 | if (naticoOptions.taskHandler) bot.taskHandler = new NaticoTaskHandler(bot, naticoOptions.taskHandler); 57 | 58 | return bot as unknown as any; 59 | } 60 | 61 | //@ts-ignore - 62 | export function withPlugins(botOptions: CreateBotOptions, ...plugins: WithPluginsArgs[]): T { 63 | let bot = createBot(botOptions); 64 | for (const plugin of plugins) { 65 | if (Array.isArray(plugin)) { 66 | const fun = plugin[0]; 67 | const rest = plugin.slice(1); 68 | bot = fun(bot, ...rest); 69 | } else { 70 | let r = plugin(bot); 71 | if (r) bot = r; 72 | } 73 | } 74 | //@ts-ignore - 75 | if (!bot.cache.users) bot.cache.users = new Collection(); 76 | //@ts-ignore - 77 | if (!bot.cache.activeGuildIds) bot.cache.activeGuildIds = new Set(); 78 | if (!bot.events.debug) bot.events.debug = function () {}; 79 | return bot as any; 80 | } 81 | -------------------------------------------------------------------------------- /src/struct/NaticoClient.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Bot, 3 | EventEmitter, 4 | startBot, 5 | GenericFunction, 6 | WrappedFunction, 7 | createUtils, 8 | createTransformers, 9 | createRestManager, 10 | createHelpers, 11 | createGatewayManager, 12 | DiscordGatewayPayload, 13 | GatewayDispatchEventNames, 14 | EventHandlers, 15 | createBot, 16 | createBotConstants, 17 | CreateBotOptions, 18 | createEventHandlers, 19 | createBotGatewayHandlers, 20 | // createCache, 21 | } from "../../deps.ts"; 22 | import { Events } from "../util/Interfaces.ts"; 23 | import { NaticoClientUtil } from "../util/ClientUtil.ts"; 24 | import { DiscordGatewayIntents } from "https://deno.land/x/discordeno@13.0.0-rc1/mod.ts"; 25 | export interface NaticoClientOptions extends Omit { 26 | util: boolean; 27 | } 28 | // deno-lint-ignore no-empty-interface 29 | export interface NaticoClient extends Bot {} 30 | export abstract class NaticoClient extends EventEmitter implements Bot { 31 | events = {} as EventHandlers; 32 | util!: NaticoClientUtil; 33 | 34 | constructor(config: NaticoClientOptions) { 35 | super(); 36 | 37 | this.id = config.botId; 38 | this.applicationId = config.applicationId || config.botId; 39 | this.token = `Bot ${config.token}`; 40 | this.events = createEventHandlers({}); 41 | //@ts-ignore - 42 | this.intents = config.intents.reduce((bits, next) => (bits |= DiscordGatewayIntents[next]), 0); 43 | this.botGatewayData = config.botGatewayData; 44 | this.activeGuildIds = new Set(); 45 | this.constants = createBotConstants(); 46 | this.handlers = createBotGatewayHandlers({}); 47 | this.enabledPlugins = new Set(); 48 | this.handleDiscordPayload = config.handleDiscordPayload; 49 | 50 | // @ts-ignore itoh cache types plz 51 | // this.cache = createCache(this as Bot, config.cache); 52 | 53 | if (config?.util) this.util = new NaticoClientUtil(this); 54 | } 55 | 56 | plugin(plugin: any) { 57 | plugin(this); 58 | return this; 59 | } 60 | 61 | on(eventName: string | symbol, listener: GenericFunction | WrappedFunction) { 62 | this.addEvent(eventName.toString() as keyof EventHandlers); 63 | return super.on(eventName, listener); 64 | } 65 | /** 66 | * @param event Add a event to be emitted 67 | */ 68 | addEvent(event: keyof EventHandlers) { 69 | //Removing the first argument since thats the bot every time! 70 | this.events[event] = (...args: unknown[]) => this.emit(event, ...args.slice(1)); 71 | } 72 | 73 | /** 74 | * Start the natico bot 75 | */ 76 | async login() { 77 | return await startBot(this); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/struct/NaticoHandler.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file 2 | import { EventEmitter } from "../../deps.ts"; 3 | import { NaticoClient } from "./NaticoClient.ts"; 4 | 5 | export class NaticoHandler extends EventEmitter { 6 | client: T; 7 | directory: any; 8 | modules: any; 9 | constructor(client: any, { directory }: any) { 10 | super(); 11 | this.client = client; 12 | this.directory = directory; 13 | } 14 | 15 | async load(thing: any) { 16 | let mod = await import("file://" + thing); 17 | mod = new mod.default(); 18 | 19 | this.register(mod, thing); 20 | 21 | return mod; 22 | } 23 | remove(id: any) { 24 | const mod = this.modules.get(id.toString()); 25 | if (!mod) return; 26 | this.deregister(mod); 27 | return mod; 28 | } 29 | async reload(id: any) { 30 | const mod = this.modules.get(id); 31 | if (!mod) return; 32 | this.deregister(mod); 33 | 34 | const filepath = mod.filepath; 35 | const newMod = await this.load(filepath); 36 | return newMod; 37 | } 38 | deregister(mod: any) { 39 | this.modules.delete(mod.id); 40 | } 41 | async reloadAll() { 42 | for (const m of Array.from(this.modules.values()) as any[]) { 43 | if (m.filepath) await this.reload(m.id); 44 | } 45 | 46 | return this.modules; 47 | } 48 | async loadALL(dirPath?: any) { 49 | dirPath = await Deno.realPath(dirPath || this.directory); 50 | const entries = Deno.readDir(dirPath); 51 | for await (const entry of entries) { 52 | if (entry.isFile) { 53 | await this.load(`${dirPath}/${entry.name}`); 54 | continue; 55 | } 56 | 57 | await this.loadALL(`${dirPath}/${entry.name}`); 58 | } 59 | } 60 | register(mod: any, filepath: any) { 61 | mod.filepath = filepath; 62 | mod.handler = this; 63 | mod.client = this.client; 64 | this.modules.set(mod.id, mod); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/struct/NaticoModule.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "./NaticoClient.ts"; 2 | export interface NaticoModuleOptions { 3 | id: string; 4 | } 5 | 6 | export abstract class NaticoModule { 7 | handler!: any; 8 | client!: NaticoClient; 9 | id!: string; 10 | filepath!: string; 11 | constructor() {} 12 | 13 | reload() { 14 | return this.handler.reload(this.id); 15 | } 16 | 17 | remove() { 18 | return this.handler.remove(this.id); 19 | } 20 | 21 | toString() { 22 | return this.id; 23 | } 24 | abstract exec(...args: unknown[]): unknown | Promise; 25 | } 26 | -------------------------------------------------------------------------------- /src/struct/buttons/Button.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoModule, NaticoModuleOptions } from "../NaticoModule.ts"; 3 | import { NaticoButtonHandler } from "./ButtonHandler.ts"; 4 | export interface NaticoButtonOptions extends NaticoModuleOptions { 5 | trigger: string; 6 | } 7 | export abstract class NaticoButton extends NaticoModule { 8 | declare handler: NaticoButtonHandler; 9 | trigger!: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/struct/buttons/ButtonHandler.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoButton } from "./Button.ts"; 3 | import { NaticoHandler } from "../NaticoHandler.ts"; 4 | // import { NaticoInhibitor } from "./Inhibitor.ts"; 5 | import { Collection, DiscordenoMember, InteractionTypes } from "../../../deps.ts"; 6 | 7 | export class NaticoButtonHandler extends NaticoHandler { 8 | declare modules: Collection; 9 | directory: string; 10 | 11 | constructor(client: T, { directory }: { directory: string }) { 12 | super(client, { 13 | directory, 14 | }); 15 | this.directory = directory; 16 | this.modules = new Collection(); 17 | this.start(); 18 | } 19 | start() { 20 | this.client.on("interactionCreate", async (data: ComponentInteraction, member: DiscordenoMember) => { 21 | if (data.type === InteractionTypes.MessageComponent) { 22 | const iDontKnowAnameForThis = data.data?.customId.split("-"); 23 | if (!iDontKnowAnameForThis) return; 24 | const button = this.findButton(iDontKnowAnameForThis.shift()); 25 | if (!button) return; 26 | try { 27 | if (button) return await button.exec(data, member, iDontKnowAnameForThis); 28 | } catch (error) { 29 | this.emit("buttonError", error, button); 30 | } 31 | } 32 | }); 33 | } 34 | findButton(trigger?: string) { 35 | return this.modules.find((b) => b.trigger == trigger); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/struct/commands/ArgumentGenerator.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoCommand } from "./Command.ts"; 3 | 4 | import { Matches, ArgOptions } from "../../util/Interfaces.ts"; 5 | import { MessageCollector } from "../../util/MessageCollector.ts"; 6 | import { 7 | DiscordenoMessage, 8 | fetchMembers, 9 | Collection, 10 | DiscordenoRole, 11 | DiscordenoChannel, 12 | DiscordenoMember, 13 | } from "../../../deps.ts"; 14 | 15 | // All the items the parsers return 16 | export type returnItems = DiscordenoRole | DiscordenoChannel | DiscordenoMember | number | string | boolean | undefined; 17 | 18 | export type Argument = ( 19 | message: DiscordenoMessage, 20 | args: string 21 | ) => [returnItems, string | undefined] | Promise<[returnItems, string | undefined]>; 22 | 23 | export type ReturnType = [returnItems, string | undefined]; 24 | 25 | export type Arguments = { [name: string]: returnItems }; 26 | 27 | export class ArgumentGenerator { 28 | client: NaticoClient; 29 | arguments: Collection; 30 | constructor(client: NaticoClient) { 31 | this.client = client; 32 | this.arguments = new Collection(); 33 | this.start(); 34 | } 35 | 36 | start() { 37 | //I would load the files dynamically but deno file loading is way to slow for that to be a good aproach 38 | this.arguments.set("3", (_, c) => [c, undefined]); 39 | this.arguments.set("4", (_, c) => [parseInt(c), undefined]); 40 | this.arguments.set("5", this.boolean); 41 | this.arguments.set("6", this.parseUser); 42 | this.arguments.set("7", this.parseChannel); 43 | this.arguments.set("8", this.parseRole); 44 | } 45 | 46 | /** 47 | * Parses the command arguments :stonks: 48 | * @param command 49 | * @param message 50 | * @param args 51 | * @returns 52 | */ 53 | async handleArgs(command: NaticoCommand, message: DiscordenoMessage, args?: string) { 54 | if (!args) return {}; 55 | const data: Arguments = {}; 56 | const rest = args.split(" "); 57 | if (command?.options) { 58 | let restContent: string | undefined = rest.join(" "); 59 | 60 | for (const option of command.options) { 61 | const name = option.name; 62 | if (option.type && !option.customType && option.type !== 1) { 63 | const res: [returnItems, string | undefined] = await this.arguments.get(option.type.toString())!( 64 | message, 65 | restContent as string 66 | ); 67 | restContent = res[1]; 68 | data[name] = res[0]; 69 | } 70 | 71 | //Rest means that everything will be cut off 72 | if (option?.match == Matches.rest) { 73 | data[name] = args; 74 | if (option.customType) { 75 | const info: string = await option.customType(message, restContent as string); 76 | if (Array.isArray(info) && info.length == 2) { 77 | restContent = info[1]; 78 | 79 | data[name] = info[0]; 80 | } else { 81 | data[name] = info; 82 | } 83 | } else data[name] = restContent; 84 | } else if (option?.match == Matches.content) { 85 | if (option.customType) { 86 | data[name] = await option.customType(message, args); 87 | } else data[name] = args; 88 | } 89 | } 90 | } 91 | 92 | return data; 93 | } 94 | 95 | async handleMissingArgs(message: DiscordenoMessage, command: NaticoCommand, args: Arguments) { 96 | const argKeys = Object.keys(args); 97 | 98 | let index = 0; 99 | 100 | for await (const arg of command.options as ArgOptions[]) { 101 | if (argKeys[index] !== arg.name && arg.required && arg.prompt) { 102 | const prompt = await message.reply(`${arg.prompt}\nThe command will be automatically cancelled in 30 seconds.`); 103 | 104 | const collector = new MessageCollector(this.client, message, undefined, { time: 30 * 1000 }); 105 | const msg = (await collector.collect).first(); 106 | 107 | if (!msg) return null; 108 | const fn = arg.customType ? arg.customType : this.arguments.get(arg.type.toString())!; 109 | args[arg.name] = (await fn(msg, msg.content))[0]; 110 | await prompt.delete(); 111 | } 112 | 113 | index++; 114 | } 115 | 116 | return args; 117 | } 118 | 119 | boolean(_: DiscordenoMessage, args: string) { 120 | if (!args) return [undefined, args] as ReturnType; 121 | const trues = ["true", "on", "enable"]; 122 | const falses = ["false", "off", "disable"]; 123 | if (trues.includes(args.split(" ")[0])) { 124 | args = args.split(" ").slice(1).join(" "); 125 | return [true, args] as ReturnType; 126 | } else if (falses.includes(args.split(" ")[0])) { 127 | args = args.split(" ").slice(1).join(" "); 128 | return [false, args] as ReturnType; 129 | } 130 | return [undefined, args] as ReturnType; 131 | } 132 | 133 | async parseUser(message: DiscordenoMessage, args: string) { 134 | if (!args) return [undefined, args] as ReturnType; 135 | const item = args?.trim()?.split(" ")[0]?.replace(/ /gi, ""); 136 | const reg = /<@!?(\d{17,19})>/; 137 | const id = args?.match(reg); 138 | 139 | let user = message.guild!.members.find((member) => { 140 | if (id && member.id == BigInt(id[1])) return true; 141 | if (!item.length) return false; 142 | if (member.name(message.guildId).toLowerCase().includes(item)) { 143 | return true; 144 | } 145 | if (member.tag.toLowerCase().includes(item)) true; 146 | return false; 147 | }); 148 | 149 | if (user) return [user, args.split(" ").slice(1).join(" ")] as ReturnType; 150 | 151 | if (id && id[1]) { 152 | user = ( 153 | await fetchMembers(message?.guild?.id!, message.guild?.shardId!, { 154 | userIds: [BigInt(id[1])], 155 | limit: 1, 156 | }).catch(() => undefined) 157 | )?.first(); 158 | } 159 | if (user) return [user, args.split(" ").slice(1).join(" ")] as ReturnType; 160 | return [undefined, args] as ReturnType; 161 | } 162 | 163 | parseChannel(message: DiscordenoMessage, args: string) { 164 | if (!args) return [undefined, args] as ReturnType; 165 | const guild = message.guild; 166 | 167 | const id = args.split(" ")[0].replace(/<|#|!|>|&|/gi, ""); 168 | if ((id || args) && guild) { 169 | const channel = guild.channels.find((c) => { 170 | if (c.name == args.split(" ")[0]) return true; 171 | if (c.id.toString() == id || " ") return true; 172 | return false; 173 | }); 174 | 175 | return [channel, args.split(" ").slice(1).join(" ")] as ReturnType; 176 | } 177 | return [undefined, args] as ReturnType; 178 | } 179 | 180 | parseRole(message: DiscordenoMessage, args: string) { 181 | if (!args) return [undefined, args] as ReturnType; 182 | const guild = message.guild; 183 | 184 | const id = args.split(" ")[0].replace(/<|@|!|>|&|/gi, ""); 185 | if ((id || args) && guild) { 186 | const role = guild.roles.find((c) => { 187 | if (c.name == args.split(" ")[0]) return true; 188 | if (c.id.toString() == id || " ") return true; 189 | return false; 190 | }); 191 | 192 | return [role, args.split(" ").slice(1).join(" ")] as ReturnType; 193 | } 194 | return [undefined, args] as ReturnType; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/struct/commands/Command.ts: -------------------------------------------------------------------------------- 1 | import { PermissionStrings } from "../../../deps.ts"; 2 | import { NaticoModule } from "../NaticoModule.ts"; 3 | import { NaticoCommandHandler } from "./CommandHandler.ts"; 4 | import { ArgOptions } from "../../util/Interfaces.ts"; 5 | import { NaticoClient } from "../NaticoClient.ts"; 6 | 7 | export interface NaticoCommandOptions { 8 | options?: ArgOptions[]; 9 | name?: string; 10 | aliases?: string[]; 11 | examples?: string[]; 12 | description?: string; 13 | slash?: boolean; 14 | category?: string; 15 | ownerOnly?: boolean; 16 | superUserOnly?: boolean; 17 | clientPermissions?: PermissionStrings[]; 18 | userPermissions?: PermissionStrings[]; 19 | ratelimit?: number; 20 | //Cooldown in ms 21 | cooldown?: number; 22 | id: string; 23 | } 24 | /** 25 | * Create a natico command 26 | * 27 | * @example 28 | *```typescript 29 | * @applyOptions({ 30 | * name: "ping", 31 | * description: "A simple ping command" 32 | * }) 33 | * export default class Ping extends NaticoCommand { 34 | * exec(message:DiscordenoMessage){ 35 | * return message.util.send({ content: "Hello from natico!" }) 36 | * } 37 | * } 38 | *``` 39 | */ 40 | export abstract class NaticoCommand extends NaticoModule { 41 | declare handler: NaticoCommandHandler; 42 | category = "default"; 43 | name: string = this.id; 44 | aliases: string[] | undefined = [this.name]; 45 | examples: string[] | undefined; 46 | ownerOnly: boolean | undefined; 47 | required: boolean | undefined; 48 | description: string | undefined; 49 | slash: boolean | undefined; 50 | enabled: boolean | undefined; 51 | superUserOnly: boolean | undefined; 52 | options?: ArgOptions[]; 53 | clientPermissions: PermissionStrings[] | undefined; 54 | userPermissions: PermissionStrings[] | undefined; 55 | cooldown?: number; 56 | ratelimit = 3; 57 | } 58 | -------------------------------------------------------------------------------- /src/struct/commands/CommandHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Collection, 3 | EditGlobalApplicationCommand, 4 | DiscordenoInteraction, 5 | ApplicationCommandOptionTypes, 6 | InteractionTypes, 7 | } from "../../../deps.ts"; 8 | // import { NaticoCommandUtil } from "./commandUtil.ts"; 9 | import { NaticoClient } from "../NaticoClient.ts"; 10 | import { ArgumentGenerator, Arguments } from "./ArgumentGenerator.ts"; 11 | import { NaticoInhibitorHandler } from "../inhibitors/InhibitorHandler.ts"; 12 | import { NaticoCommand } from "./Command.ts"; 13 | import { NaticoSubCommand } from "./SubCommand.ts"; 14 | import { NaticoHandler } from "../NaticoHandler.ts"; 15 | import { ConvertedOptions, prefixFn, ArgOptions } from "../../util/Interfaces.ts"; 16 | import { CommandHandlerEvents } from "../../util/Constants.ts"; 17 | export interface NaticoCommandHandlerOptions { 18 | directory?: string; 19 | prefix?: prefixFn | string | string[]; 20 | IgnoreCD?: bigint[]; 21 | owners?: bigint[]; 22 | /** 23 | * cooldown in millieseconds 24 | */ 25 | cooldown?: number; 26 | ratelimit?: number; 27 | superusers?: bigint[]; 28 | /** 29 | * Commands will only work in guild channels with this on 30 | */ 31 | guildonly?: boolean; 32 | handleEdits?: boolean; 33 | /** 34 | * Single means all subcommands in the same file; multiple means in every file 35 | */ 36 | subType?: "single" | "multiple"; 37 | commandUtil?: boolean; 38 | storeMessages?: boolean; 39 | mentionPrefix?: boolean; 40 | handleSlashCommands?: boolean; 41 | handleArgs?: boolean; 42 | // handleSlashes?: boolean; 43 | } 44 | export class NaticoCommandHandler extends NaticoHandler { 45 | declare modules: Collection; 46 | IgnoreCD: bigint[]; 47 | owners: bigint[]; 48 | cooldown: number; 49 | superusers: bigint[]; 50 | guildonly: boolean; 51 | prefix: prefixFn | string | string[]; 52 | handleEdits: boolean; 53 | handleArgs: boolean; 54 | inhibitorHandler!: NaticoInhibitorHandler; 55 | generator: ArgumentGenerator; 56 | commandUtil: boolean; 57 | storeMessages: boolean; 58 | mentionPrefix: boolean; 59 | handleSlashCommands: boolean; 60 | ratelimit: number; 61 | cooldowns: Collection; 62 | /** 63 | * Single means all subcommands in the same file; multiple means in every file 64 | */ 65 | 66 | subType: "single" | "multiple"; 67 | // handleSlashes: boolean; 68 | constructor( 69 | client: T, 70 | { 71 | directory = "./commands", 72 | prefix = "!", 73 | IgnoreCD = [], 74 | owners = [], 75 | cooldown = 5000, 76 | ratelimit = 3, 77 | superusers = [], 78 | guildonly = false, 79 | handleEdits = false, 80 | handleArgs = false, 81 | subType = "single", 82 | commandUtil = true, 83 | storeMessages = true, 84 | mentionPrefix = true, 85 | handleSlashCommands = false, 86 | }: NaticoCommandHandlerOptions 87 | ) { 88 | super(client, { 89 | directory, 90 | }); 91 | this.handleSlashCommands = handleSlashCommands; 92 | this.commandUtil = commandUtil; 93 | this.handleEdits = handleEdits; 94 | this.handleArgs = handleArgs; 95 | this.client = client; 96 | this.prefix = prefix; 97 | this.owners = owners; 98 | this.cooldown = cooldown; 99 | this.superusers = [...owners, ...superusers]; 100 | this.IgnoreCD = [...IgnoreCD, ...this.superusers]; 101 | this.guildonly = guildonly; 102 | this.ratelimit = ratelimit; 103 | this.subType = subType; 104 | 105 | this.storeMessages = storeMessages; 106 | this.mentionPrefix = mentionPrefix; 107 | this.start(); 108 | 109 | this.commandUtils = new Collection(); 110 | this.modules = new Collection(); 111 | this.generator = new ArgumentGenerator(this.client); 112 | this.cooldowns = new Collection(); 113 | } 114 | start() { 115 | this.client.on("interactionCreate", async (data: SlashCommandInteraction) => { 116 | if (data.type === InteractionTypes.ApplicationCommand) return await this.handleSlashCommand(data); 117 | }); 118 | } 119 | 120 | get categories() { 121 | const categories = new Collection>(); 122 | for (const data of this.modules.values()) { 123 | if (data instanceof NaticoSubCommand == false) { 124 | const exists = categories.get(data.category!); 125 | if (exists) exists.set(data.id, data); 126 | else { 127 | const cc = new Collection(); 128 | cc.set(data.id, data as NaticoCommand); 129 | 130 | categories.set(data.category!, cc); 131 | } 132 | } 133 | } 134 | return categories; 135 | } 136 | 137 | // Code taken from https://github.com/discord-akairo/discord-akairo/blob/master/src/struct/commands/CommandHandler.js#L705 138 | // Modified to work with natico 139 | runCooldowns(message: DiscordenoMessage, command: NaticoCommand) { 140 | const id = message.authorId; 141 | if (this.IgnoreCD.includes(message.authorId)) return false; 142 | 143 | const time = command.cooldown != null ? command.cooldown : this.cooldown; 144 | if (!time) return false; 145 | 146 | const endTime = message.timestamp + time; 147 | 148 | if (!this.cooldowns.has(id)) this.cooldowns.set(id, {}); 149 | 150 | if (!this.cooldowns.get(id)[command.id]) { 151 | this.cooldowns.get(id)[command.id] = { 152 | timer: setTimeout(() => { 153 | if (this.cooldowns.get(id)[command.id]) { 154 | clearTimeout(this.cooldowns.get(id)[command.id].timer); 155 | } 156 | this.cooldowns.get(id)[command.id] = null; 157 | 158 | if (!Object.keys(this.cooldowns.get(id)).length) { 159 | this.cooldowns.delete(id); 160 | } 161 | }, time), 162 | end: endTime, 163 | uses: 0, 164 | }; 165 | } 166 | 167 | const entry = this.cooldowns.get(id)[command.id]; 168 | 169 | if (entry.uses >= command.ratelimit) { 170 | const end = this.cooldowns.get(id)[command.id].end; 171 | const diff = end - message.timestamp; 172 | 173 | this.emit(CommandHandlerEvents.COOLDOWN, message, command, diff); 174 | return true; 175 | } 176 | 177 | entry.uses++; 178 | return false; 179 | } 180 | 181 | async commandChecks(command: NaticoCommand, message: DiscordenoMessage, args: string | undefined) { 182 | if (this.inhibitorHandler) { 183 | if (await this.inhibitorHandler.runChecks(message, command)) return true; 184 | } 185 | 186 | const authorId = message.authorId.toString(); 187 | if (!this.superusers.includes(message.authorId)) { 188 | //Otherwise you would get on cooldown 189 | if (command instanceof NaticoSubCommand == false) 190 | if (this.cooldowns.has(authorId)) { 191 | if (!this.IgnoreCD.includes(message.authorId)) { 192 | this.emit(CommandHandlerEvents.COOLDOWN, message, command, args); 193 | return true; 194 | } 195 | } 196 | 197 | if (this.guildonly) { 198 | if (!message.guildId) { 199 | this.emit(CommandHandlerEvents.GUILDONLY, message, command, args); 200 | return true; 201 | } 202 | } 203 | 204 | if (command.userPermissions) { 205 | throw new Error("Permissions are not supported sorry"); 206 | const missingPermissions = await this.client.helpers.getMissingChannelPermissions( 207 | this.client, 208 | message!.channelId, 209 | message.authorId, 210 | command.userPermissions 211 | ); 212 | if (missingPermissions[0]) { 213 | this.emit(CommandHandlerEvents.USERPERMISSIONS, message, command, args, missingPermissions); 214 | return true; 215 | } 216 | } 217 | if (command.clientPermissions) { 218 | throw new Error("Permissions are not supported sorry"); 219 | const missingPermissions = await getMissingChannelPermissions( 220 | this.client, 221 | message!.channelId, 222 | this.client.id, 223 | command.clientPermissions 224 | ); 225 | if (missingPermissions[0]) { 226 | this.emit(CommandHandlerEvents.CLIENTPERMISSIONS, message, command, args, missingPermissions); 227 | return true; 228 | } 229 | } 230 | } 231 | if (this.runCooldowns(message, command)) { 232 | return true; 233 | } 234 | if (command.ownerOnly) { 235 | if (!this.owners.includes(message.authorId)) { 236 | this.emit(CommandHandlerEvents.OWNERONLY, message, command, args); 237 | return true; 238 | } 239 | } 240 | 241 | if (command.superUserOnly) { 242 | if (!this.superusers.includes(message.authorId)) { 243 | this.emit(CommandHandlerEvents.SUPERUSERRONLY, message, command, args); 244 | return true; 245 | } 246 | } 247 | return false; 248 | } 249 | 250 | /** 251 | * 252 | * @param command - Command that gets executed 253 | * @param message - Message object to be passed through 254 | * @param args - arguments to be passed though 255 | * @returns - What the ran command returned 256 | */ 257 | public async runCommand(command: NaticoCommand, message: DiscordenoMessage, args?: string) { 258 | if (await this.commandChecks(command, message, args)) return false; 259 | 260 | try { 261 | let sub: string | null = null; 262 | let savedOptions: ArgOptions[] | undefined = undefined; 263 | 264 | if (command?.options && args) { 265 | if (command?.options[0]?.type == ApplicationCommandOptionTypes.SubCommand) { 266 | //Thing needs to be defined to not cause mutation 267 | const thing = args.split(" ")[0].toLowerCase(); 268 | 269 | for (const option of command.options) { 270 | if (option.name === thing) { 271 | if (this.subType == "multiple") { 272 | args = args.split(" ").slice(1).join(" "); 273 | sub = option.name; 274 | savedOptions = command.options as ArgOptions[]; 275 | command.options = option.options; 276 | } else { 277 | const mod = this.modules.find((mod) => { 278 | if (mod instanceof NaticoSubCommand && mod.subOf == command.name && mod.name == option.name) { 279 | return true; 280 | } 281 | return false; 282 | }); 283 | if (mod) { 284 | this.runCommand(mod, message, args.split(" ").slice(1).join(" ")); 285 | return; 286 | } 287 | } 288 | } 289 | } 290 | } 291 | } 292 | let data = await this.generator.handleArgs(command, message, args); 293 | 294 | if (this.handleArgs && command.options) 295 | data = (await this.generator.handleMissingArgs(message, command, data)) as Arguments; 296 | 297 | if (!data) return this.emit("commandEnded", message, command, {}, null); 298 | 299 | if (savedOptions) command.options = savedOptions; 300 | 301 | this.emit("commandStarted", message, command, data); 302 | 303 | const res = sub 304 | ? //@ts-ignore - 305 | await command[sub](message, data) 306 | : await command.exec(message, data); 307 | this.emit("commandEnded", message, command, data, res); 308 | /** 309 | * Adding the user to a set and deleting them later! 310 | */ 311 | } catch (e: unknown) { 312 | this.emit("commandError", message, command, e); 313 | } 314 | } 315 | 316 | public async handleCommand(message: DiscordenoMessage) { 317 | if (!message?.content) return; 318 | if (message.isBot) return; 319 | 320 | const prefixes = typeof this.prefix == "function" ? await this.prefix(message) : this.prefix; 321 | const parsedPrefixes = []; 322 | 323 | if (Array.isArray(prefixes)) parsedPrefixes.push(...prefixes); 324 | else parsedPrefixes.push(prefixes); 325 | if (this.mentionPrefix) parsedPrefixes.push(`<@!${this.client.id}>`, `<@${this.client.id}>`); 326 | 327 | for (const prefix of parsedPrefixes) { 328 | if (await this.prefixCheck(prefix, message)) return; 329 | } 330 | } 331 | async prefixCheck(prefix: string, message: DiscordenoMessage) { 332 | if (message.content.toLowerCase().startsWith(prefix)) { 333 | const commandName = message.content.toLowerCase().slice(prefix.length).trim().split(" ")[0]; 334 | const command = this.findCommand(commandName); 335 | if (command) { 336 | if (this.commandUtil) { 337 | if (this.commandUtils.has(message.id)) { 338 | message.util = this.commandUtils.get(message.id)!; 339 | } else { 340 | message.util = new NaticoCommandUtil(this, message); 341 | this.commandUtils.set(message.id, message.util); 342 | } 343 | } 344 | message.util?.setParsed({ prefix, alias: commandName }); 345 | const args = message.content.slice(prefix.length).trim().slice(commandName.length).trim(); 346 | await this.runCommand(command, message, args); 347 | return true; 348 | } 349 | } 350 | } 351 | 352 | /** 353 | * Simple function to find a command could be useful outside of the handler 354 | * @param command - Command you want to search for 355 | * @returns Command object or undefined 356 | */ 357 | public findCommand(command: string | undefined): NaticoCommand | undefined { 358 | return this.modules.find((cmd) => { 359 | if (cmd instanceof NaticoSubCommand) return false; 360 | if (cmd.name == command) { 361 | return true; 362 | } 363 | if (cmd.aliases) { 364 | if (cmd.aliases.includes(command)) { 365 | return true; 366 | } 367 | } 368 | return false; 369 | }); 370 | } 371 | /** 372 | * Check if commands have slash data and if they do it will activete it 373 | * be carefull to no accidentally enable them globally, 374 | * first searches if the command is already enabled and if it changed since and edit it accordingly otherwise creates a command 375 | * also deletes unused slash commands 376 | * @param guildID - Specific guild to enable slash commands on 377 | * @returns - List of enabled commands 378 | */ 379 | async enableSlash(guildID?: bigint) { 380 | const slashed = this.slashed(); 381 | await this.client.helpers.upsertApplicationCommands(slashed, guildID); 382 | return slashed; 383 | } 384 | slashed(): any { 385 | const commands: EditGlobalApplicationCommand[] = []; 386 | const data = this.modules.filter((command) => command.slash || false); 387 | data.forEach((command: NaticoCommand) => { 388 | const slashdata: MakeRequired = { 389 | name: command.name || command.id, 390 | description: command.description || "no description", 391 | }; 392 | const options: ArgOptions[] = []; 393 | if (command.options) { 394 | command.options.forEach((option) => { 395 | options.push({ 396 | name: option.name, 397 | description: option.description, 398 | choices: option.choices, 399 | options: option.options, 400 | type: option.type, 401 | required: option.required, 402 | channel_types: option.channel_types, 403 | min_value: option.min_value, 404 | max_value: option.max_value, 405 | autocomplete: option.autocomplete, 406 | }); 407 | }); 408 | } 409 | if (command.options) slashdata["options"] = options; 410 | commands.push(slashdata); 411 | }); 412 | return commands; 413 | } 414 | async handleSlashCommand(interaction: DiscordenoInteraction) { 415 | const args: ConvertedOptions = {}; 416 | if (interaction?.data?.options) 417 | for (const option of interaction.data?.options) { 418 | if (option?.value) { 419 | args[option.name] = option.value; 420 | } 421 | } 422 | const command = this.findCommand(interaction?.data?.name); 423 | if (!command) return; 424 | let sub: string | null = null; 425 | if (command?.options) { 426 | if (command?.options[0]?.type == ApplicationCommandOptionTypes.SubCommand) { 427 | sub = interaction?.data?.options?.[0]?.name!; 428 | if (interaction?.data?.options?.[0]?.options) 429 | for (const option of interaction.data?.options[0]?.options) { 430 | if (option?.value) { 431 | args[option.name] = option.value; 432 | } 433 | } 434 | for (const option of command.options) { 435 | if (option.name === sub) { 436 | if (this.subType == "multiple") { 437 | command.options = option.options; 438 | } else { 439 | const mod = this.modules.find((mod) => { 440 | if (mod instanceof NaticoSubCommand && mod.subOf == command.name && mod.name == option.name) { 441 | return true; 442 | } 443 | return false; 444 | }); 445 | if (mod) { 446 | try { 447 | this.emit("commandStarted", interaction, command, args); 448 | const res = await mod.exec(interaction, args); 449 | this.emit("commandEnded", interaction, command, args, res); 450 | return; 451 | } catch (e) { 452 | this.emit("commandError", interaction, command, e); 453 | } 454 | } 455 | } 456 | } 457 | } 458 | } 459 | } 460 | try { 461 | this.emit("commandStarted", interaction, command, args); 462 | //@ts-ignore - 463 | const res = sub ? await command[sub](interaction, args) : await command.exec(interaction, args); 464 | this.emit("commandEnded", interaction, command, args, res); 465 | } catch (e) { 466 | this.emit("commandError", interaction, command, e); 467 | } 468 | } 469 | setInhibitorHandler(inhibitorHandler: NaticoInhibitorHandler) { 470 | this.inhibitorHandler = inhibitorHandler; 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /src/struct/commands/SubCommand.ts: -------------------------------------------------------------------------------- 1 | import { NaticoCommand, NaticoCommandOptions } from "./Command.ts"; 2 | export interface NaticoSubCommandOptions extends NaticoCommandOptions { 3 | subOf: string; 4 | } 5 | export abstract class NaticoSubCommand extends NaticoCommand { 6 | subOf!: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/struct/commands/commandUtil.ts: -------------------------------------------------------------------------------- 1 | // //Code semi-taken from https://github.com/SkyBlockDev/discord-akairo/blob/master/src/struct/commands/CommandUtil.js 2 | // import { NaticoCommandHandler } from "./CommandHandler.ts"; 3 | // import { 4 | // Collection, 5 | // DiscordenoMessage, 6 | // CreateMessage, 7 | // SlashCommandInteraction, 8 | // editMessage, 9 | // sendMessage, 10 | // } from "../../../deps.ts"; 11 | // import { NaticoClient } from "../NaticoClient.ts"; 12 | 13 | // /** 14 | // * The command util from natico which allows responding to interactions and much more 15 | // */ 16 | // export class NaticoCommandUtil { 17 | // handler: NaticoCommandHandler; 18 | // message: DiscordenoMessage; 19 | // shouldEdit: boolean; 20 | // lastResponse!: DiscordenoMessage; 21 | // isSlash = false; 22 | // parsed!: { 23 | // prefix: string; 24 | // alias: string; 25 | // isSlash?: boolean; 26 | // }; 27 | // data: { 28 | // defered: boolean; 29 | // interaction?: SlashCommandInteraction; 30 | // } = { 31 | // defered: false, 32 | // }; 33 | // // The message responses 34 | // messages: Collection = new Collection(); 35 | // constructor(handler: NaticoCommandHandler, message: DiscordenoMessage) { 36 | // this.handler = handler; 37 | 38 | // this.message = message; 39 | 40 | // this.shouldEdit = false; 41 | 42 | // if (this.handler.storeMessages) { 43 | // this.messages = new Collection(); 44 | // } else { 45 | // this.messages.clear(); 46 | // } 47 | // } 48 | // /** 49 | // * Set the last response 50 | // */ 51 | // setLastResponse(message: DiscordenoMessage) { 52 | // if (Array.isArray(message)) { 53 | // this.lastResponse = message.slice(-1)[0]; 54 | // } else { 55 | // this.lastResponse = message; 56 | // } 57 | 58 | // return this.lastResponse; 59 | // } 60 | 61 | // addMessage(message: DiscordenoMessage) { 62 | // if (this.handler.storeMessages) { 63 | // if (Array.isArray(message)) { 64 | // for (const msg of message) { 65 | // this.messages.set(msg.id, msg); 66 | // } 67 | // } else { 68 | // this.messages.set(message.id, message); 69 | // } 70 | // } 71 | 72 | // return message; 73 | // } 74 | // setParsed(data: { prefix: string; alias: string }) { 75 | // this.parsed = data; 76 | // } 77 | 78 | // setEditable(state: boolean): this { 79 | // this.shouldEdit = Boolean(state); 80 | // return this; 81 | // } 82 | 83 | // async send(content: string | CreateMessage): Promise { 84 | // if (this.shouldEdit) { 85 | // if (typeof content !== "string" && !content.embeds) content.embeds = []; 86 | // if (typeof content !== "string" && !content.content) content.content = ""; 87 | 88 | // return (await editMessage( 89 | // this.handler.client, 90 | // this.lastResponse.channelId, 91 | // this.lastResponse.id, 92 | // content 93 | // )) as DiscordenoMessage; 94 | // } 95 | // if (this.parsed.isSlash) { 96 | // const oldContent = content; 97 | // content = { 98 | // //@ts-ignore - 99 | // type: 4, 100 | // data: oldContent, 101 | // }; 102 | // } 103 | // const sent = await sendMessage(this.handler.client, this.message.channelId, content); 104 | // if (!this?.parsed?.isSlash) { 105 | // const lastSent = this.setLastResponse(sent as DiscordenoMessage); 106 | // this.setEditable(!lastSent?.attachments?.length); 107 | // } 108 | 109 | // return sent as DiscordenoMessage; 110 | // } 111 | 112 | // async sendNew(content: string | CreateMessage) { 113 | // const sent = await sendMessage(content); 114 | // const lastSent = this.setLastResponse(sent as DiscordenoMessage); 115 | // this.setEditable(!lastSent?.attachments?.length); 116 | // return sent; 117 | // } 118 | 119 | // reply(options: string | CreateMessage): Promise { 120 | // //TODO: remove any type when dd fixes messageReference type 121 | // let newOptions: any = {}; 122 | // if (!this?.parsed?.isSlash) { 123 | // if (typeof options == "string") { 124 | // newOptions.content = options; 125 | // } else { 126 | // newOptions = options; 127 | // } 128 | 129 | // if (!this.shouldEdit) { 130 | // newOptions.messageReference = { 131 | // messageId: this.message.id, 132 | // }; 133 | // } 134 | // } else newOptions = options; 135 | 136 | // return this.send(newOptions); 137 | // } 138 | 139 | // edit(content: string | CreateMessage) { 140 | // return this.lastResponse.edit(content); 141 | // } 142 | // } 143 | 144 | // /** 145 | // * Extra properties applied to the Discord.js message object. 146 | // * @typedef {Object} MessageExtensions 147 | // * @prop {?CommandUtil} util - Utilities for command responding. 148 | // * Available on all messages after 'all' inhibitors and built-in inhibitors (bot, client). 149 | // * Not all properties of the util are available, depending on the input. 150 | // */ 151 | -------------------------------------------------------------------------------- /src/struct/inhibitors/Inhibitor.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoModule, NaticoModuleOptions } from "../NaticoModule.ts"; 3 | import { NaticoInhibitorHandler } from "./InhibitorHandler.ts"; 4 | 5 | export interface NaticoInhibitorOptions extends NaticoModuleOptions { 6 | priority?: number; 7 | } 8 | 9 | export abstract class NaticoInhibitor extends NaticoModule { 10 | declare handler: NaticoInhibitorHandler; 11 | priority!: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/struct/inhibitors/InhibitorHandler.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoCommand } from "../commands/Command.ts"; 3 | import { NaticoHandler } from "../NaticoHandler.ts"; 4 | import { NaticoInhibitor } from "./Inhibitor.ts"; 5 | import { Collection, DiscordenoMessage } from "../../../deps.ts"; 6 | export class NaticoInhibitorHandler extends NaticoHandler { 7 | declare modules: Collection; 8 | directory: string; 9 | 10 | constructor(client: T, { directory }: { directory: string }) { 11 | super(client, { 12 | directory, 13 | }); 14 | this.directory = directory; 15 | this.modules = new Collection(); 16 | } 17 | async runChecks(message: DiscordenoMessage, command: NaticoCommand): Promise { 18 | const inhibitors = [...this.modules.entries()].sort((a, b) => b[1].priority - a[1].priority); 19 | for await (const [, inhibitor] of inhibitors) { 20 | if (await inhibitor.exec(message, command)) return true; 21 | else continue; 22 | } 23 | return false; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/struct/listeners/Listener.ts: -------------------------------------------------------------------------------- 1 | import { NaticoModule, NaticoModuleOptions } from "../NaticoModule.ts"; 2 | import { NaticoListenerHandler } from "./ListenerHandler.ts"; 3 | import { EventHandlers } from "../../../discordeno_deps.ts"; 4 | import { NaticoClient } from "../NaticoClient.ts"; 5 | 6 | export interface NaticoListenerOptions extends NaticoModuleOptions { 7 | event: T; 8 | emitter: string; 9 | } 10 | 11 | export type NaticoListenerOptionsDiscordenoEvents = NaticoListenerOptions; 12 | 13 | export abstract class NaticoListener extends NaticoModule { 14 | declare handler: NaticoListenerHandler; 15 | event!: string; 16 | emitter!: string; 17 | } 18 | -------------------------------------------------------------------------------- /src/struct/listeners/ListenerHandler.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoHandler } from "../NaticoHandler.ts"; 3 | import { NaticoListener } from "./Listener.ts"; 4 | import { Collection } from "../../../deps.ts"; 5 | import { ListenerHandlerEvents } from "../../util/Constants.ts"; 6 | export class NaticoListenerHandler extends NaticoHandler { 7 | declare modules: Collection; 8 | emitters: Collection; 9 | directory: string; 10 | 11 | constructor(client: T, { directory }: { directory: string }) { 12 | super(client, { 13 | directory, 14 | }); 15 | this.directory = directory; 16 | this.modules = new Collection(); 17 | this.emitters = new Collection(); 18 | this.emitters.set("client", this.client); 19 | } 20 | 21 | register(listener: NaticoListener, filepath: string) { 22 | super.register(listener, filepath); 23 | listener.exec = listener.exec.bind(listener); 24 | this.addToEmitter(listener.id); 25 | return listener; 26 | } 27 | 28 | addToEmitter(id: string) { 29 | const listener = this.modules.get(id.toString()); 30 | if (!listener) return; 31 | 32 | const emitter = this.emitters.get(listener.emitter); 33 | emitter.on(listener.event, listener.exec); 34 | return listener; 35 | } 36 | setEmitters(emitters: any) { 37 | for (const [key, value] of Object.entries(emitters)) { 38 | this.emitters.set(key, value); 39 | } 40 | 41 | return this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/struct/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./NaticoClient.ts"; 2 | export * from "./NaticoHandler.ts"; 3 | export * from "./NaticoModule.ts"; 4 | 5 | export * from "./commands/Command.ts"; 6 | export * from "./commands/SubCommand.ts"; 7 | export * from "./commands/CommandHandler.ts"; 8 | export * from "./commands/ArgumentGenerator.ts"; 9 | export * from "./commands/commandUtil.ts"; 10 | 11 | export * from "./listeners/Listener.ts"; 12 | export * from "./listeners/ListenerHandler.ts"; 13 | 14 | export * from "./tasks/Task.ts"; 15 | export * from "./tasks/TaskHandler.ts"; 16 | 17 | export * from "./inhibitors/Inhibitor.ts"; 18 | export * from "./inhibitors/InhibitorHandler.ts"; 19 | 20 | export * from "./buttons/Button.ts"; 21 | export * from "./buttons/ButtonHandler.ts"; 22 | -------------------------------------------------------------------------------- /src/struct/tasks/Task.ts: -------------------------------------------------------------------------------- 1 | import { NaticoTaskHandler } from "./TaskHandler.ts"; 2 | import { NaticoModule, NaticoModuleOptions } from "../NaticoModule.ts"; 3 | import { NaticoClient } from "../NaticoClient.ts"; 4 | 5 | export interface NaticoTaskOptions extends NaticoModuleOptions { 6 | delay: number | delayFN; 7 | runOnStart?: boolean; 8 | } 9 | 10 | export abstract class NaticoTask extends NaticoModule { 11 | declare handler: NaticoTaskHandler; 12 | delay?: number | delayFN; 13 | runOnStart?: boolean; 14 | } 15 | export type delayFN = (client: T) => number; 16 | -------------------------------------------------------------------------------- /src/struct/tasks/TaskHandler.ts: -------------------------------------------------------------------------------- 1 | import { NaticoClient } from "../NaticoClient.ts"; 2 | import { NaticoHandler } from "../NaticoHandler.ts"; 3 | import { NaticoTask } from "./Task.ts"; 4 | import { Collection } from "../../../deps.ts"; 5 | import { TaskHandlerEvents } from "../../util/Constants.ts"; 6 | export class NaticoTaskHandler extends NaticoHandler { 7 | declare modules: Collection>; 8 | directory: string; 9 | 10 | constructor(client: T, { directory }: { directory: string }) { 11 | super(client, { 12 | directory, 13 | }); 14 | this.directory = directory; 15 | this.modules = new Collection(); 16 | } 17 | 18 | register(task: NaticoTask, filepath: string) { 19 | task.client = this.client; 20 | if (task.runOnStart) { 21 | try { 22 | task.exec(); 23 | } catch (e) { 24 | this.emit(TaskHandlerEvents.TASKERROR, task, e); 25 | } 26 | } 27 | if (task.delay) { 28 | const delay = typeof task.delay == "function" ? task.delay(this.client) : task.delay; 29 | setInterval(() => { 30 | try { 31 | task.exec(); 32 | } catch (e) { 33 | this.emit(TaskHandlerEvents.TASKERROR, task, e); 34 | } 35 | }, delay); 36 | } 37 | return super.register(task, filepath); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/util/ClientUtil.ts: -------------------------------------------------------------------------------- 1 | import { NaticoEmbed } from "./Embed.ts"; 2 | import { NaticoClient } from "../struct/NaticoClient.ts"; 3 | 4 | export class NaticoClientUtil { 5 | client: NaticoClient; 6 | constructor(client: NaticoClient) { 7 | this.client = client; 8 | } 9 | 10 | /** 11 | * @returns a sneaky embed 12 | */ 13 | embed() { 14 | return new NaticoEmbed(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/util/Collection.ts: -------------------------------------------------------------------------------- 1 | export class Collection extends Map { 2 | constructor(entries?: (readonly (readonly [K, V])[] | null) | Map) { 3 | super(entries ?? []); 4 | } 5 | 6 | array() { 7 | return [...this.values()]; 8 | } 9 | 10 | /** Retrieve the value of the first element in this collection */ 11 | first(): V | undefined { 12 | return this.values().next().value; 13 | } 14 | 15 | last(): V | undefined { 16 | return [...this.values()][this.size - 1]; 17 | } 18 | 19 | random(): V | undefined { 20 | const array = [...this.values()]; 21 | return array[Math.floor(Math.random() * array.length)]; 22 | } 23 | 24 | find(callback: (value: V, key: K) => boolean) { 25 | for (const key of this.keys()) { 26 | const value = this.get(key)!; 27 | if (callback(value, key)) return value; 28 | } 29 | // If nothing matched 30 | return; 31 | } 32 | 33 | filter(callback: (value: V, key: K) => boolean) { 34 | const relevant = new Collection(); 35 | this.forEach((value, key) => { 36 | if (callback(value, key)) relevant.set(key, value); 37 | }); 38 | 39 | return relevant; 40 | } 41 | 42 | map(callback: (value: V, key: K) => T) { 43 | const results = []; 44 | for (const key of this.keys()) { 45 | const value = this.get(key)!; 46 | results.push(callback(value, key)); 47 | } 48 | return results; 49 | } 50 | 51 | some(callback: (value: V, key: K) => boolean) { 52 | for (const key of this.keys()) { 53 | const value = this.get(key)!; 54 | if (callback(value, key)) return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | every(callback: (value: V, key: K) => boolean) { 61 | for (const key of this.keys()) { 62 | const value = this.get(key)!; 63 | if (!callback(value, key)) return false; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | reduce(callback: (accumulator: T, value: V, key: K) => T, initialValue?: T): T { 70 | let accumulator: T = initialValue!; 71 | 72 | for (const key of this.keys()) { 73 | const value = this.get(key)!; 74 | accumulator = callback(accumulator, value, key); 75 | } 76 | 77 | return accumulator; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/util/Components.ts: -------------------------------------------------------------------------------- 1 | // Source: 2021 Discordeno - https://github.com/discordeno/template/blob/48165243c6053fb9568c8df4fc25fd1273a92c11/src/utils/Components.ts 2 | import { ActionRow, ButtonStyles, MessageComponentTypes } from "../../deps.ts"; 3 | 4 | const snowflakeRegex = /[0-9]{17,19}/; 5 | //Prefixes with Natico to not cause type errors 6 | export class NaticoComponents extends Array { 7 | constructor(...args: ActionRow[]) { 8 | super(...args); 9 | 10 | return this; 11 | } 12 | 13 | addActionRow() { 14 | // Don't allow more than 5 Action Rows 15 | if (this.length === 5) return this; 16 | 17 | this.push({ 18 | type: 1, 19 | components: [] as unknown as ActionRow["components"], 20 | }); 21 | return this; 22 | } 23 | 24 | addButton( 25 | label: string, 26 | style: keyof typeof ButtonStyles, 27 | idOrLink: string, 28 | options?: { emoji?: string | bigint; disabled?: boolean } 29 | ) { 30 | // No Action Row has been created so do it 31 | if (!this.length) this.addActionRow(); 32 | 33 | // Get the last Action Row 34 | let row = this[this.length - 1]; 35 | 36 | // If the Action Row already has 5 buttons create a new one 37 | if (row.components.length === 5) { 38 | this.addActionRow(); 39 | row = this[this.length - 1]; 40 | 41 | // Apperandly there are already 5 Full Action Rows so don't add the button 42 | if (row.components.length === 5) return this; 43 | } 44 | 45 | row.components.push({ 46 | type: MessageComponentTypes.Button, 47 | label: label, 48 | customId: style !== "Link" ? idOrLink : undefined, 49 | style: ButtonStyles[style], 50 | emoji: this.#stringToEmoji(options?.emoji), 51 | url: style === "Link" ? idOrLink : undefined, 52 | disabled: options?.disabled, 53 | }); 54 | return this; 55 | } 56 | 57 | #stringToEmoji(emoji?: string | bigint) { 58 | if (!emoji) return; 59 | 60 | emoji = emoji.toString(); 61 | 62 | // A snowflake id was provided 63 | if (snowflakeRegex.test(emoji)) { 64 | return { 65 | id: emoji.match(snowflakeRegex)![0], 66 | }; 67 | } 68 | 69 | // A unicode emoji was provided 70 | return { 71 | name: emoji, 72 | }; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/util/Constants.ts: -------------------------------------------------------------------------------- 1 | export enum CommandHandlerEvents { 2 | /** Message-Command-args */ 3 | COOLDOWN = "cooldownBlocked", 4 | /** Message-Command-args */ 5 | GUILDONLY = "guildOnly", 6 | /** Message-Command-args */ 7 | USERPERMISSIONS = "userPermissions", 8 | /** Message-Command-args */ 9 | CLIENTPERMISSIONS = "clientPermissions", 10 | /** Message-Command-args */ 11 | OWNERONLY = "owerOnly", 12 | /** Message-Command-args */ 13 | SUPERUSERRONLY = "superUserOnly", 14 | /** Message-Command-args */ 15 | COMMANDSTARTED = "commandStarted", 16 | /** Message-Command-args-commandResponse */ 17 | COMMANDENDED = "commandEnded", 18 | /** Message-Command-error */ 19 | COMMANDERROR = "commandError", 20 | } 21 | export enum TaskHandlerEvents { 22 | /** task - error */ 23 | TASKERROR = "taskError", 24 | } 25 | export enum ListenerHandlerEvents { 26 | /** task - error */ 27 | LISTENERERROR = "ListenerError", 28 | } 29 | -------------------------------------------------------------------------------- /src/util/Embed.ts: -------------------------------------------------------------------------------- 1 | /** Source: 2021 Discordeno - https://github.com/discordeno/template/blob/main/src/utils/Embed.ts not modified */ 2 | 3 | import { EmbedAuthor, EmbedField, EmbedFooter, EmbedImage } from "../../deps.ts"; 4 | const embedLimits = { 5 | title: 256, 6 | description: 4096, 7 | fieldName: 256, 8 | fieldValue: 1024, 9 | footerText: 2048, 10 | authorName: 256, 11 | fields: 25, 12 | total: 8048, 13 | }; 14 | //Prefixes with Natico to not cause type errors 15 | export class NaticoEmbed { 16 | /** The amount of characters in the embed. */ 17 | currentTotal = 0; 18 | /** Whether the limits should be enforced or not. */ 19 | enforceLimits = true; 20 | /** If a file is attached to the message it will be added here. */ 21 | file?: EmbedFile; 22 | 23 | color = 0x41ebf4; 24 | fields: EmbedField[] = []; 25 | author?: EmbedAuthor; 26 | description?: string; 27 | footer?: EmbedFooter; 28 | image?: EmbedImage; 29 | timestamp?: string; 30 | title?: string; 31 | thumbnail?: EmbedImage; 32 | url?: string; 33 | 34 | constructor(enforceLimits = true) { 35 | // By default we will always want to enforce discord limits but this option allows us to bypass for whatever reason. 36 | if (!enforceLimits) this.enforceLimits = false; 37 | 38 | return this; 39 | } 40 | 41 | fitData(data: string, max: number) { 42 | // If the string is bigger then the allowed max shorten it. 43 | if (data.length > max) data = data.substring(0, max); 44 | // Check the amount of characters left for this embed 45 | const availableCharacters = embedLimits.total - this.currentTotal; 46 | // If it is maxed out already return empty string as nothing can be added anymore 47 | if (!availableCharacters) return ``; 48 | // If the string breaks the maximum embed limit then shorten it. 49 | if (this.currentTotal + data.length > embedLimits.total) { 50 | return data.substring(0, availableCharacters); 51 | } 52 | // Return the data as is with no changes. 53 | return data; 54 | } 55 | 56 | setAuthor(name: string, icon?: string, url?: string) { 57 | const finalName = this.enforceLimits ? this.fitData(name, embedLimits.authorName) : name; 58 | this.author = { name: finalName, iconUrl: icon, url }; 59 | 60 | return this; 61 | } 62 | 63 | setColor(color: string) { 64 | this.color = 65 | color.toLowerCase() === `random` 66 | ? // Random color 67 | Math.floor(Math.random() * (0xffffff + 1)) 68 | : // Convert the hex to a acceptable color for discord 69 | parseInt(color.replace("#", ""), 16); 70 | 71 | return this; 72 | } 73 | 74 | setDescription(description: string | string[]) { 75 | if (Array.isArray(description)) description = description.join("\n"); 76 | this.description = this.fitData(description, embedLimits.description); 77 | 78 | return this; 79 | } 80 | 81 | addField(name: string, value: string, inline = false) { 82 | if (this.fields.length >= 25) return this; 83 | 84 | this.fields.push({ 85 | name: this.fitData(name, embedLimits.fieldName), 86 | value: this.fitData(value, embedLimits.fieldValue), 87 | inline, 88 | }); 89 | 90 | return this; 91 | } 92 | 93 | addBlankField(inline = false) { 94 | return this.addField("\u200B", "\u200B", inline); 95 | } 96 | 97 | attachFile(file: unknown, name: string) { 98 | this.file = { 99 | blob: file, 100 | name, 101 | }; 102 | this.setImage(`attachment://${name}`); 103 | 104 | return this; 105 | } 106 | 107 | setFooter(text: string, icon?: string) { 108 | this.footer = { 109 | text: this.fitData(text, embedLimits.footerText), 110 | iconUrl: icon, 111 | }; 112 | 113 | return this; 114 | } 115 | 116 | setImage(url: string) { 117 | this.image = { url }; 118 | 119 | return this; 120 | } 121 | 122 | setTimestamp(time = Date.now()) { 123 | this.timestamp = new Date(time).toISOString(); 124 | 125 | return this; 126 | } 127 | 128 | setTitle(title: string, url?: string) { 129 | this.title = this.fitData(title, embedLimits.title); 130 | if (url) this.url = url; 131 | 132 | return this; 133 | } 134 | 135 | setThumbnail(url: string) { 136 | this.thumbnail = { url }; 137 | 138 | return this; 139 | } 140 | } 141 | 142 | export interface EmbedFile { 143 | blob: unknown; 144 | name: string; 145 | } 146 | -------------------------------------------------------------------------------- /src/util/Interfaces.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommandOption, DiscordenoMessage } from "../../discordeno_deps.ts"; 2 | import { NaticoCommandUtil } from "../struct/commands/commandUtil.ts"; 3 | export interface NaticoMessage extends DiscordenoMessage { 4 | util: NaticoCommandUtil; 5 | } 6 | export interface Events { 7 | [name: string]: (...args: any[]) => Promise | any; 8 | } 9 | export interface ConvertedOptions { 10 | [name: string]: any; 11 | } 12 | export interface prefixFn { 13 | (message: NaticoMessage): string | string[] | Promise; 14 | } 15 | export interface ArgOptions extends ApplicationCommandOption { 16 | match?: Matches; 17 | //The custom function that will be ran instead of the default argument function 18 | customType?: customType; 19 | //The prompt that will be given when a user doesnt provide that argument 20 | prompt?: string; 21 | //If the option is a channel type, the channels shown will be restricted to these types 22 | channel_types?: string[]; 23 | //If the option is an INTEGER or NUMBER type, the minimum value permitted 24 | min_value?: number; 25 | //If the options is an INTEGER or NUMBER type, the maximum value permitted 26 | max_value?: number; 27 | //Enable autocomplete interactions for this option 28 | autocomplete?: boolean; 29 | } 30 | export type customType = (message: NaticoMessage | DiscordenoMessage | any, content: string) => any | Promise; 31 | 32 | export enum Matches { 33 | rest, 34 | content, 35 | } 36 | export enum ArgumentTypes { 37 | string = 3, 38 | interger = 4, 39 | boolean = 5, 40 | user = 6, 41 | channel = 7, 42 | subCommand = 1, 43 | subCOmmandGroup = 2, 44 | } 45 | -------------------------------------------------------------------------------- /src/util/MessageCollector.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter, Collection, DiscordenoMessage } from "../../deps.ts"; 2 | import { NaticoClient } from "../struct/NaticoClient.ts"; 3 | 4 | interface CollectorOptions { 5 | time?: number; 6 | max?: number; 7 | errors?: Array<"timeout" | "limit">; 8 | } 9 | 10 | type MessageCollection = Collection; 11 | type CollectorFilter = (message: DiscordenoMessage) => boolean; 12 | 13 | export class MessageCollector extends EventEmitter { 14 | private collection: MessageCollection; 15 | private collected: number; 16 | private ended: boolean; 17 | private options: CollectorOptions; 18 | 19 | constructor( 20 | client: NaticoClient, 21 | message: DiscordenoMessage, 22 | filter: CollectorFilter = () => { 23 | return true; 24 | }, 25 | options: CollectorOptions = { max: 1, time: 10 * 1000, errors: [] } 26 | ) { 27 | super(); 28 | this.collection = new Collection(); 29 | this.collected = 0; 30 | this.ended = false; 31 | this.options = options; 32 | 33 | const messageListener = (msg: DiscordenoMessage) => { 34 | if (message.channelId === msg.channelId && filter(msg) === true) { 35 | this.collection.set(msg.id, msg); 36 | this.collected += 1; 37 | this.emit("collect", msg); 38 | } 39 | 40 | if ((options.max ?? 1) <= this.collected && !this.ended) { 41 | client.removeListener("messageCreate", messageListener); 42 | this.emit("end", "limit", this.collection); 43 | this.ended = true; 44 | return; 45 | } 46 | }; 47 | 48 | setTimeout(() => { 49 | if (this.ended) return; 50 | client.removeListener("messageCreate", messageListener); 51 | this.emit("end", "timeout", this.collection); 52 | this.ended = true; 53 | }, options.time); 54 | 55 | client.on("messageCreate", messageListener); 56 | } 57 | 58 | collect = new Promise((resolve, reject) => { 59 | this.once("end", (reason, collection) => { 60 | if (!this.options.errors?.includes(reason)) resolve(collection); 61 | else reject(reason); 62 | }); 63 | }); 64 | } 65 | -------------------------------------------------------------------------------- /src/util/applyOptions.ts: -------------------------------------------------------------------------------- 1 | export const applyOptions = 2 | (options: T) => 3 | (Module: any): any => { 4 | //@ts-ignore - 5 | return class extends Module { 6 | constructor() { 7 | super(); 8 | Object.assign(this, options); 9 | } 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /src/util/mod.ts: -------------------------------------------------------------------------------- 1 | export * from "./ClientUtil.ts"; 2 | export * from "./Embed.ts"; 3 | export * from "./Interfaces.ts"; 4 | export * from "./Constants.ts"; 5 | // export * from "./createNaticoInteraction.ts"; 6 | export * from "./MessageCollector.ts"; 7 | export * from "./Components.ts"; 8 | export * from "./applyOptions.ts"; 9 | --------------------------------------------------------------------------------