Compare commits
421 Commits
Author | SHA1 | Date |
---|---|---|
zeromomentum121 | 7d621128c3 | |
DjDeveloper | a25aef6400 | |
DjDeveloperr | 90e4bd6ccf | |
DjDeveloperr | 7570dfffbe | |
Helloyunho | d52d636641 | |
DjDeveloper | a7547f85ca | |
mierenmanz | 5df70e5904 | |
DjDeveloper | ce455c50c3 | |
lamebox | a5fd1de25e | |
lamebox | c08565cde9 | |
DjDeveloper | 53030ea941 | |
DjDeveloper | b686c02cdc | |
DjDeveloperr | 258ec12906 | |
DjDeveloperr | b22563ffc3 | |
Helloyunho | 461e1557c5 | |
Helloyunho | 60e7c75113 | |
DjDeveloperr | e8371408e2 | |
DjDeveloper | fee3e0cfa0 | |
mierenmanz | 6e084af4a3 | |
mierenmanz | 52ec39a24c | |
DjDeveloper | 8e49dc4306 | |
DjDeveloperr | b64bc29fbe | |
mierenmanz | a013388ec9 | |
mierenmanz | a8bb3cc49b | |
mierenmanz | 54b0356a72 | |
mierenmanz | 057b3ede53 | |
mierenmanz | de4e207d85 | |
mierenmanz | 222f1d0fa8 | |
DjDeveloper | b1aa995808 | |
DjDeveloperr | 718a4658eb | |
mierenmanz | b3220fa155 | |
mierenmanz | 8c4f0d948d | |
mierenmanz | 36d3e944ff | |
mierenmanz | 9d6feaff8d | |
mierenmanz | 1c9d17263b | |
mierenmanz | bffead6680 | |
mierenmanz | dcbf635860 | |
mierenmanz | 0a871023c8 | |
mierenmanz | e6c0c378de | |
mierenmanz | 9c18ec2d1f | |
mierenmanz | 97298f17f8 | |
mierenmanz | eac9c4bceb | |
mierenmanz | 1c25e2d3a1 | |
mierenmanz | 58ad6fcd3d | |
mierenmanz | e7715b75cf | |
DjDeveloperr | c88e4a67b2 | |
DjDeveloperr | 2cdb671fb1 | |
mierenmanz | 8ac716d6a9 | |
mierenmanz | ff655e0b7d | |
mierenmanz | 7f96dfebc3 | |
mierenmanz | d9925a4ac4 | |
DjDeveloperr | 1beccb57ba | |
DjDeveloperr | 332f0f2742 | |
DjDeveloperr | 3fe3a84928 | |
DjDeveloperr | a7c442d340 | |
DjDeveloperr | 45bfbff3a3 | |
DjDeveloperr | 381cb13e6c | |
DjDeveloperr | 67d81c1bf0 | |
DjDeveloper | 66031d56c2 | |
DjDeveloperr | 9d88c5d113 | |
DjDeveloper | 962d90aca8 | |
DjDeveloperr | addf9cffb6 | |
DjDeveloperr | 945bcc6f40 | |
DjDeveloperr | a1ead7e15e | |
DjDeveloperr | b97ec3c225 | |
DjDeveloperr | 17e74dce45 | |
DjDeveloper | 3868d29e3e | |
DjDeveloperr | 30cd8e10dc | |
DjDeveloperr | ea221f8962 | |
DjDeveloperr | b324263a7b | |
DjDeveloper | 8d08378413 | |
DjDeveloperr | b13cdbe480 | |
DjDeveloper | 229f5f079c | |
DjDeveloperr | c3d1714a6e | |
DjDeveloperr | d0e3dc9fba | |
DjDeveloper | ef3dcacf0b | |
DjDeveloperr | ca3d68e6a0 | |
DjDeveloper | ab19a6b624 | |
DjDeveloperr | 9a72f66931 | |
DjDeveloper | 34e261a9e1 | |
DjDeveloperr | f3da37bcb1 | |
AkiaCode | cacf8e6d14 | |
DjDeveloper | 2745d5c445 | |
DjDeveloper | f8ecaba024 | |
DjDeveloper | 30be804166 | |
Helloyunho | 5e2a057462 | |
Helloyunho | d17e845b2b | |
DjDeveloper | 7018bf6094 | |
DjDeveloperr | 12e6971b0a | |
DjDeveloperr | bd5153fd15 | |
DjDeveloperr | fe6ee7ddac | |
DjDeveloper | ce843b189b | |
DjDeveloperr | 758db4cfb2 | |
DjDeveloperr | 8d18f6b1ab | |
DjDeveloperr | 97ce6d1f36 | |
DjDeveloperr | d681dc41a7 | |
DjDeveloperr | bd530ecb15 | |
DjDeveloperr | 08f2f653f9 | |
DjDeveloperr | f2a8c86dde | |
DjDeveloperr | b479cdc743 | |
DjDeveloperr | 5e65673107 | |
DjDeveloperr | 1e53d7d770 | |
DjDeveloperr | 2123978715 | |
DjDeveloperr | 6293a1f940 | |
DjDeveloperr | d63d4f4f12 | |
Helloyunho | 6f4f06fd8b | |
DjDeveloperr | d829c91c3e | |
DjDeveloperr | a8c9c2e7e0 | |
DjDeveloperr | 66e7e48bed | |
DjDeveloperr | eca3cf4e0e | |
DjDeveloperr | f7adfd0f7e | |
DjDeveloperr | 38c02bb981 | |
DjDeveloperr | 1c7416628d | |
DjDeveloperr | de7996d552 | |
DjDeveloperr | 46cbd66166 | |
DjDeveloperr | f812e06d17 | |
DjDeveloperr | 22e041f440 | |
DjDeveloper | 7dc316c76f | |
DjDeveloperr | 2b46b38908 | |
DjDeveloperr | 38b11f4076 | |
DjDeveloperr | f8d7cbccc7 | |
DjDeveloperr | 8bf2c1e99d | |
DjDeveloperr | 22eb7e0437 | |
DjDeveloperr | 0dfbcd7a7b | |
DjDeveloperr | 582b296393 | |
DjDeveloper | 01f92b35bf | |
DjDeveloper | 5add0283f9 | |
MegaPixel | 5a8514949c | |
MegaPixel | 5e89e3b8e4 | |
Helloyunho | 3fe5ce3063 | |
Helloyunho | 96b273ed04 | |
Helloyunho | 51b1d71e9a | |
Helloyunho | d2cedeceb1 | |
Helloyunho | 064c3a6d76 | |
Helloyunho | 9c5aea1ef8 | |
Helloyunho | b4a1ae389d | |
DjDeveloperr | 82431168d3 | |
DjDeveloperr | 95145c1bc2 | |
DjDeveloperr | 75620ee7ea | |
DjDeveloperr | 68cf1105c1 | |
DjDeveloperr | 218e3e7ddf | |
DjDeveloperr | a89318c3c3 | |
DjDeveloperr | 768fec7195 | |
DjDeveloperr | 62b2aa07de | |
DjDeveloperr | 4462d7d832 | |
DjDeveloperr | b775d3e323 | |
DjDeveloperr | 5fc58bd901 | |
DjDeveloper | d3ed30ce17 | |
DjDeveloperr | 0e57401a6b | |
DjDeveloperr | 0692276f3a | |
DjDeveloperr | 004bf22126 | |
DjDeveloperr | 4dc18f7266 | |
DjDeveloperr | 010a48c7f0 | |
Helloyunho | 37de39daa7 | |
Helloyunho | 1cdcf045ad | |
DjDeveloperr | 3f7372d6a7 | |
DjDeveloperr | ab365f9878 | |
DjDeveloperr | 1738204406 | |
DjDeveloperr | a89ab53fa1 | |
DjDeveloper | 7669ded67c | |
waterflamev8 | 7e9c86cc2b | |
waterflamev8 | aac841101a | |
DjDeveloper | 2472f3c348 | |
Helloyunho | f6545b0cbd | |
Helloyunho | f3115af2b9 | |
Helloyunho | 07dae683dc | |
Helloyunho | 21ccf4c054 | |
DjDeveloper | b532a99eb4 | |
DjDeveloperr | 11fa1281cf | |
DjDeveloperr | 9023606faa | |
DjDeveloperr | fccac82fdc | |
DjDeveloperr | 4f71717047 | |
DjDeveloperr | 6641778578 | |
DjDeveloperr | 2286c9af3f | |
DjDeveloperr | 711f78002e | |
DjDeveloperr | c1a14fdac1 | |
DjDeveloperr | b02b16ffcc | |
DjDeveloperr | 1b77ea0411 | |
DjDeveloperr | 2ca5005517 | |
DjDeveloperr | dcb07a9aaa | |
DjDeveloperr | e65fa8fded | |
DjDeveloper | 5f75fc3e71 | |
DjDeveloper | c836ca8f42 | |
DjDeveloper | 5b015c6c08 | |
DjDeveloper | 90159fa2da | |
DjDeveloper | b94253284e | |
DjDeveloperr | 1c515dcb37 | |
DjDeveloperr | a47d5ae770 | |
Aki | 7624d2c3c6 | |
Helloyunho | 3e81002c33 | |
Helloyunho | 88519d9420 | |
Helloyunho | d26059fdc8 | |
Helloyunho | 9cd9eb9c6f | |
DjDeveloperr | 00dae42f7b | |
Helloyunho | c8d976762b | |
DjDeveloperr | 30fa9429c5 | |
Helloyunho | d5634f676f | |
DjDeveloper | 2b0db63f82 | |
Helloyunho | 8e5a76dbe8 | |
Helloyunho | ff80750ca4 | |
Helloyunho | 548b6bf2ad | |
Helloyunho | de089ce610 | |
Helloyunho | d5a8207690 | |
DjDeveloperr | f2f292b0ee | |
DjDeveloper | 4d93d66757 | |
DjDeveloper | b222d91f7c | |
DjDeveloperr | 2d6f3d46f3 | |
DjDeveloperr | 84baae279e | |
DjDeveloperr | dffce5bd0b | |
DjDeveloperr | 7bdbb165f4 | |
DjDeveloperr | 98874dd7e7 | |
DjDeveloperr | a800f394ac | |
DjDeveloperr | f4b4c640b3 | |
DjDeveloperr | 086e4a95c3 | |
DjDeveloper | 5007dc6029 | |
DjDeveloperr | e0edb1a088 | |
DjDeveloper | cc7a587040 | |
DjDeveloperr | 921d7323a3 | |
DjDeveloperr | 361baa39eb | |
DjDeveloperr | 950243af5c | |
DjDeveloper | 17a625032b | |
Martín (Netux) Rodríguez | 7af4553fbe | |
Helloyunho | 6a8e4ddc43 | |
DjDeveloperr | 57863a6ed5 | |
DjDeveloper | 24c3ea45ca | |
DjDeveloper | 3828a6b911 | |
invalidCards | 38dea00369 | |
invalidCards | 96bba8d68e | |
DjDeveloperr | ee51609c8c | |
DjDeveloperr | fb609c18bf | |
DjDeveloperr | b844a053e5 | |
DjDeveloper | dbb80f30b4 | |
DjDeveloperr | b7fee5a41f | |
DjDeveloperr | aa5075451b | |
DjDeveloperr | a16d209a20 | |
DjDeveloper | b4dd929a3e | |
ayntee | d7647a88ee | |
ayntee | 6a58a69031 | |
DjDeveloper | 6d21c762de | |
DjDeveloperr | 60e2655085 | |
Helloyunho | bf0c82f273 | |
Helloyunho | 2a38fc1e00 | |
DjDeveloper | be3a793fa1 | |
DjDeveloperr | 87c15a9283 | |
Helloyunho | 54e0e41dc7 | |
Helloyunho | 926d360d90 | |
DjDeveloperr | 11fc7e1e21 | |
DjDeveloperr | 0edc74b1db | |
DjDeveloperr | c2e7916aa3 | |
DjDeveloperr | d36922c0b7 | |
DjDeveloperr | dce4c99cbd | |
DjDeveloperr | c1cd6276ae | |
Helloyunho | 30b5a17492 | |
DjDeveloper | 108bd3ea62 | |
DjDeveloper | c904d854d9 | |
Helloyunho | c880a73771 | |
DjDeveloperr | 04c4e7760b | |
DjDeveloperr | 2dcce6e2bb | |
Helloyunho | dfeb784dfc | |
DjDeveloper | 71ded8ea2e | |
DjDeveloperr | 0b995d732d | |
DjDeveloperr | ebb4be897a | |
Helloyunho | ad3a0d4b79 | |
DjDeveloper | 2402795006 | |
DjDeveloper | d4b557e95b | |
DjDeveloper | 28268e779f | |
DjDeveloper | 1827eb4a9b | |
DjDeveloper | 32f55b875e | |
DjDeveloperr | 8734e9a7a6 | |
❥sora | 928e18f586 | |
❥sora | dcbbe15f38 | |
DjDeveloper | 3dde9688ab | |
DjDeveloperr | 0cf60b7def | |
DjDeveloper | b2a93769ff | |
DjDeveloperr | e83dd3353e | |
DjDeveloperr | 65e9583445 | |
DjDeveloperr | dc1e2bbc6e | |
DjDeveloper | 06997a1c18 | |
DjDeveloper | 8927939dea | |
DjDeveloperr | eae49afcbe | |
DjDeveloperr | 77b08747ab | |
DjDeveloperr | cc75a34d56 | |
Helloyunho | cc657cc4d8 | |
Helloyunho | 617e3b6204 | |
Helloyunho | e99bf61f3a | |
Helloyunho | 8b564a4b49 | |
Helloyunho | 2d27068f5c | |
Helloyunho | 989706e71a | |
DjDeveloperr | e7b0804616 | |
Helloyunho | 0cae198f2c | |
Helloyunho | 8ad1a2ac6f | |
Helloyunho | 78ae0bbb56 | |
Aki | a54f434d02 | |
Aki | 52314cea27 | |
Aki | 556a3c5e2f | |
Helloyunho | 19e2020e38 | |
Helloyunho | 7e11b8a351 | |
Helloyunho | 35da2dbe98 | |
Helloyunho | d8e65a4328 | |
DjDeveloperr | 3f436b2b3f | |
Aki | e3033b4cbf | |
Aki | 68029084c4 | |
DjDeveloper | 0de9b57204 | |
DjDeveloperr | 48976e779b | |
DjDeveloper | a630e9466c | |
ZiomaleQ | 1d562f6997 | |
Aki | 3fe0001e44 | |
Aki | 53b49e45aa | |
ZiomaleQ | 2fadcfa407 | |
ZiomaleQ | fbd6eae244 | |
ZiomaleQ | 734133bcca | |
ZiomaleQ | 60164122bf | |
ZiomaleQ | 6b5c15d19a | |
ZiomaleQ | c4a3fbc45c | |
ZiomaleQ | 4364d3879b | |
ZiomaleQ | e009ae3127 | |
ZiomaleQ | 4cbc2b344d | |
Helloyunho | 68fa36ce3c | |
DjDeveloper | 39c7761a96 | |
DjDeveloper | cff738b2e9 | |
Radoslaw Partyka | 055a030c4e | |
Radoslaw Partyka | 6c3f71669d | |
Radoslaw Partyka | c322c25fb0 | |
Radoslaw Partyka | 6e8af1f7da | |
DjDeveloperr | e9f461fef4 | |
DjDeveloperr | b112b6ae36 | |
DjDeveloperr | 33f103279d | |
DjDeveloperr | 432555e2fb | |
DjDeveloperr | 8f4433dc9f | |
DjDeveloperr | c3fafdfcf0 | |
DjDeveloperr | e3bce85f09 | |
DjDeveloperr | b344c2e24a | |
DjDeveloperr | 8edef36ead | |
DjDeveloperr | df66f4ea3e | |
DjDeveloperr | 7a2b71b648 | |
DjDeveloperr | 417854b1bb | |
Aki | ae61efe73b | |
DjDeveloper | 2e28093e33 | |
Helloyunho | e1a8a8526a | |
Helloyunho | 41891b45c7 | |
DjDeveloperr | 2ddb6bf5a5 | |
DjDeveloper | 8463e59d4f | |
DjDeveloper | 205f88281b | |
DjDeveloperr | 63ae6f4312 | |
Helloyunho | 492ebe91f8 | |
DjDeveloper | 8bd59f925b | |
Helloyunho | 991770d211 | |
Fishuke | e217036dd8 | |
Fishuke | 28734cea17 | |
Fishuke | 7d441688e2 | |
Fishuke | 36e174a50a | |
Aki | 61531e2349 | |
DjDeveloperr | 9178e6cec1 | |
Ayyan | e37e0c3885 | |
Ayyan | b7a93a280f | |
ayntee | 93eee2f15d | |
ayntee | 4cf153aaac | |
ayntee | 900f6c3c3b | |
ayntee | 95c2200386 | |
ayntee | c7d117cd54 | |
ayntee | e25b7a8f5b | |
ayntee | 678e988aec | |
ayntee | 0a066a32e4 | |
ayntee | a14104ad14 | |
ayntee | a69988b653 | |
ayntee | 73e9ba9a25 | |
ayntee | 6c576bec0a | |
Helloyunho | 8d693157da | |
Helloyunho | 4129ea699b | |
Helloyunho | 65b5c50ec3 | |
DjDeveloper | d46cd0cbe5 | |
DjDeveloper | 31b3c08580 | |
DjDeveloper | 6be099398f | |
DjDeveloper | 523fc432e9 | |
DjDeveloper | 0e6814690d | |
DjDeveloper | 3a80e162c0 | |
ayntee | 1e753cf51d | |
ayntee | f2ee69c130 | |
ayntee | 104aa4249e | |
ayntee | e919ad38de | |
ayntee | 6dd40afd3e | |
ayntee | bef4d45a40 | |
ayntee | f3fa4ca380 | |
ayntee | 6c6334ee07 | |
ayntee | c59d8f1639 | |
ayntee | 9c048686c8 | |
ayntee | 264d6b49fb | |
ayntee | f9e07f4f43 | |
Helloyunho | 4ab29bc91c | |
Helloyunho | 65eace4046 | |
ayntee | 30724dc791 | |
ayntee | 255bc2d4e9 | |
ayntee | 0a14264b0e | |
ayntee | b76c26ff7c | |
ayntee | be662a230b | |
ayntee | d1fcce7b83 | |
Helloyunho | 540f3b1877 | |
Helloyunho | eac5d52d83 | |
Helloyunho | 315fd0d0da | |
Helloyunho | 9e6d2c1047 | |
DjDeveloperr | 015b7951d8 | |
DjDeveloperr | 94a447921d | |
DjDeveloperr | a9338ad88a | |
DjDeveloperr | 03ea5df551 | |
DjDeveloperr | da0bfc12c7 | |
Aki | 3695b81de6 | |
DjDeveloperr | cac3c5c9e3 | |
Aki | a8fc3ef463 | |
Helloyunho | 9688d248e3 | |
DjDeveloperr | 4d8eabcc2c | |
DjDeveloperr | 5fae38fce6 | |
Helloyunho | dced786e60 | |
Helloyunho | dd70d96d50 | |
Helloyunho | 837f045bdd | |
Helloyunho | b0da81f308 | |
Helloyunho | a04f6ab973 | |
Helloyunho | 920cf133bc | |
DjDeveloper | 6c8d1bc44c | |
Helloyunho | 7a67fb67d9 | |
Helloyunho | d7f4b244c1 | |
Aki | e27002fc55 |
|
@ -0,0 +1,3 @@
|
|||
extends .gitignore
|
||||
./src/test/**/*
|
||||
|
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
},
|
||||
plugins: ['@typescript-eslint'],
|
||||
rules: {
|
||||
'@typescript-eslint/restrict-template-expressions': 'off'
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug] "
|
||||
labels: "bug \U0001F41B"
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What's the bug?**
|
||||
<!-- Please tell us about the bug as short and easy to understand. -->
|
||||
|
||||
**How do we reproduce it?**
|
||||
<!-- Do:
|
||||
1. Use `something function` in `messageCreate` event
|
||||
2. Make a message
|
||||
3. Bamm!
|
||||
|
||||
Don't:
|
||||
1. Run code
|
||||
2. Error!
|
||||
-->
|
||||
|
||||
**What should have happened?**
|
||||
<!-- We might not know what you were expecting. Please tell us. -->
|
||||
|
||||
**What is actually happening?**
|
||||
<!-- Please tell us the result of the code. -->
|
||||
|
||||
**What versions you're using?**
|
||||
- OS: <!-- [e.g. macOS 11.1 Big Sur] -->
|
||||
- Deno: <!-- [e.g. Deno 1.6.1] -->
|
||||
- Harmony: <!-- [e.g. Harmony 0.9.0] -->
|
||||
|
||||
**Do you have anything to tell us more about the bug?**
|
||||
<!-- If you do, please tell us more in here. -->
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[Feature Request] "
|
||||
labels: enhancement ✨
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What do you want to request?**
|
||||
<!-- Please tell us about your request as short and easy to understand. -->
|
||||
|
||||
<!-- !!REMOVE THIS TAG IF YOU ALREADY HAVE(..or thought about) SOME SOLUTIONS!!
|
||||
**Do you have any solutions?**
|
||||
If you have, please tell us so we can add(or at least think about) your solution!
|
||||
-->
|
||||
|
||||
**Do you have anything to tell us about your request?**
|
||||
<!-- If you do, please tell us more in here. -->
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
name: Question
|
||||
about: Question about this project
|
||||
title: "[Question] "
|
||||
labels: question ❓
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**What question do you have?**
|
||||
<!-- Please tell us your question as short and easy to understand. -->
|
|
@ -0,0 +1,9 @@
|
|||
## About
|
||||
|
||||
<!-- describe what changes does this PR introduce and why are these needed -->
|
||||
|
||||
## Status
|
||||
|
||||
- [ ] These changes have been tested against Discord API or contain no API change.
|
||||
- [ ] This PR includes only documentation changes, no code change.
|
||||
- [ ] This PR introduces some Breaking changes.
|
|
@ -20,14 +20,14 @@ jobs:
|
|||
|
||||
strategy:
|
||||
matrix:
|
||||
deno: ['v1.x', 'nightly']
|
||||
deno: ['v1.x', 'canary']
|
||||
|
||||
steps:
|
||||
- name: Setup repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Deno
|
||||
uses: denolib/setup-deno@v2.3.0
|
||||
uses: denoland/setup-deno@main
|
||||
with:
|
||||
deno-version: ${{ matrix.deno }} # tests across multiple Deno versions
|
||||
|
||||
|
|
|
@ -109,9 +109,13 @@ yarn.lock
|
|||
|
||||
# PRIVACY XDDDD
|
||||
src/test/config.ts
|
||||
test/config.ts
|
||||
.vscode
|
||||
|
||||
# macOS is shit xD
|
||||
**/.DS_Store
|
||||
|
||||
# Webstorm dont forget this duude :)
|
||||
.idea/
|
||||
|
||||
src/test/music.mp3
|
|
@ -0,0 +1,76 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at djdeveloperr@gmail.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2020 Harmony Org
|
||||
Copyright (c) 2020-2021 Harmonyland
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
75
README.md
75
README.md
|
@ -3,22 +3,19 @@
|
|||
<p align=center><i><b>An easy to use Discord API Library for Deno</b></i></p>
|
||||
<p align=center>
|
||||
<img src="https://img.shields.io/badge/standard--readme-OK-green.svg?style=for-the-badge"/>
|
||||
<a href=https://discord.gg/WVN2JF2FRv>
|
||||
<a href=https://discord.gg/harmony>
|
||||
<img src="https://img.shields.io/discord/783319033205751809.svg?label=Discord&logo=Discord&colorB=7289da&style=for-the-badge" alt="Support">
|
||||
</a>
|
||||
</p>
|
||||
<br>
|
||||
|
||||
- Lightweight and easy to use.
|
||||
- Built-in Command Framework,
|
||||
- Easily build Commands on the fly.
|
||||
- Completely Customizable.
|
||||
- Complete Object-Oriented approach.
|
||||
- 100% Discord API Coverage.
|
||||
- Customizable caching.
|
||||
- Built in support for Redis.
|
||||
- Write Custom Cache Adapters.
|
||||
- Complete TypeScript support.
|
||||
- Complete Object-Oriented approach.
|
||||
- Slash Commands supported.
|
||||
- Built-in Commands framework.
|
||||
- Customizable Caching, with Redis support.
|
||||
- Use `@decorators` to easily make things!
|
||||
- Made with ❤️ TypeScript.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
@ -31,7 +28,9 @@
|
|||
|
||||
## Usage
|
||||
|
||||
You can import the package from https://deno.land/x/harmony/mod.ts (with latest version) or can add a version too, and raw GitHub URL (latest unpublished version) https://raw.githubusercontent.com/harmony-org/harmony/main/mod.ts too.
|
||||
You can import the package from https://deno.land/x/harmony/mod.ts (with latest version) or can add a version too, and raw GitHub URL (latest unpublished version) https://raw.githubusercontent.com/harmonyland/harmony/main/mod.ts too.
|
||||
|
||||
You can also check(not import) the module in https://nest.land/package/harmony (link for importing is in the site).
|
||||
|
||||
For a quick example, run this:
|
||||
|
||||
|
@ -39,12 +38,16 @@ For a quick example, run this:
|
|||
deno run --allow-net https://deno.land/x/harmony/examples/ping.ts
|
||||
```
|
||||
|
||||
And input your bot's token and Intents.
|
||||
And input your bot's token.
|
||||
|
||||
Here is a small example of how to use harmony,
|
||||
|
||||
```ts
|
||||
import { Client, Message, Intents } from 'https://deno.land/x/harmony/mod.ts'
|
||||
import {
|
||||
Client,
|
||||
Message,
|
||||
GatewayIntents
|
||||
} from 'https://deno.land/x/harmony/mod.ts'
|
||||
|
||||
const client = new Client()
|
||||
|
||||
|
@ -56,13 +59,16 @@ client.on('ready', () => {
|
|||
// Listen for event whenever a Message is sent
|
||||
client.on('messageCreate', (msg: Message): void => {
|
||||
if (msg.content === '!ping') {
|
||||
msg.channel.send(`Pong! WS Ping: ${client.ping}`)
|
||||
msg.channel.send(`Pong! WS Ping: ${client.gateway.ping}`)
|
||||
}
|
||||
})
|
||||
|
||||
// Connect to gateway
|
||||
// Replace with your bot's token and intents (Intents.All, Intents.None, Intents.Presence, Intents.GuildMembers)
|
||||
client.connect('super secret token comes here', Intents.All)
|
||||
client.connect('super secret token comes here', [
|
||||
GatewayIntents.DIRECT_MESSAGES,
|
||||
GatewayIntents.GUILDS,
|
||||
GatewayIntents.GUILD_MESSAGES
|
||||
])
|
||||
```
|
||||
|
||||
Or with CommandClient!
|
||||
|
@ -72,8 +78,7 @@ import {
|
|||
CommandClient,
|
||||
Command,
|
||||
CommandContext,
|
||||
Message,
|
||||
Intents
|
||||
GatewayIntents
|
||||
} from 'https://deno.land/x/harmony/mod.ts'
|
||||
|
||||
const client = new CommandClient({
|
||||
|
@ -90,25 +95,29 @@ class PingCommand extends Command {
|
|||
name = 'ping'
|
||||
|
||||
execute(ctx: CommandContext) {
|
||||
ctx.message.reply(`pong! Ping: ${ctx.client.ping}ms`)
|
||||
ctx.message.reply(`pong! Ping: ${ctx.client.gateway.ping}ms`)
|
||||
}
|
||||
}
|
||||
|
||||
client.commands.add(PingCommand)
|
||||
|
||||
// Connect to gateway
|
||||
// Replace with your bot's token and intents (Intents.All, Intents.None, Intents.Presence, Intents.GuildMembers)
|
||||
client.connect('super secret token comes here', Intents.All)
|
||||
client.connect('super secret token comes here', [
|
||||
GatewayIntents.DIRECT_MESSAGES,
|
||||
GatewayIntents.GUILDS,
|
||||
GatewayIntents.GUILD_MESSAGES
|
||||
])
|
||||
```
|
||||
|
||||
Or with Decorator!
|
||||
Or with Decorators!
|
||||
|
||||
```ts
|
||||
import {
|
||||
CommandClient,
|
||||
event,
|
||||
Intents,
|
||||
CommandClient,
|
||||
command,
|
||||
CommandContext,
|
||||
GatewayIntents
|
||||
} from 'https://deno.land/x/harmony/mod.ts'
|
||||
|
||||
class MyClient extends CommandClient {
|
||||
|
@ -130,22 +139,24 @@ class MyClient extends CommandClient {
|
|||
}
|
||||
}
|
||||
|
||||
// Connect to gateway
|
||||
// Replace with your bot's token and intents (Intents.All, Intents.None, Intents.Presence, Intents.GuildMembers)
|
||||
client.connect('super secret token comes here', Intents.All)
|
||||
new MyClient().connect('super secret token comes here', [
|
||||
GatewayIntents.DIRECT_MESSAGES,
|
||||
GatewayIntents.GUILDS,
|
||||
GatewayIntents.GUILD_MESSAGES
|
||||
])
|
||||
```
|
||||
|
||||
|
||||
## Docs
|
||||
|
||||
Documentation is available for `main` (branch) and `stable` (release).
|
||||
|
||||
- [Main](https://doc.deno.land/https/raw.githubusercontent.com/harmony-org/harmony/main/mod.ts)
|
||||
- [Main](https://doc.deno.land/https/raw.githubusercontent.com/harmonyland/harmony/main/mod.ts)
|
||||
- [Stable](https://doc.deno.land/https/deno.land/x/harmony/mod.ts)
|
||||
- [Guide](https://harmony.mod.land)
|
||||
|
||||
## Found a bug or want support? Join our discord server!
|
||||
|
||||
[![Widget for the Discord Server](https://discord.com/api/guilds/783319033205751809/widget.png?style=banner1)](https://discord.gg/WVN2JF2FRv)
|
||||
[![Widget for the Discord Server](https://discord.com/api/guilds/783319033205751809/widget.png?style=banner1)](https://discord.gg/harmony)
|
||||
|
||||
## Maintainer
|
||||
|
||||
|
@ -161,6 +172,6 @@ Small note: If editing the README, please conform to the [standard-readme](https
|
|||
|
||||
## License
|
||||
|
||||
[MIT © 2020 Harmony Org](LICENSE)
|
||||
[MIT © 2020-2021 Harmonyland](LICENSE)
|
||||
|
||||
#### Made with ❤ by Harmony-org
|
||||
#### Made with ❤ by Harmonyland
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
import { Interaction } from './src/structures/interactions.ts'
|
||||
import {
|
||||
SlashCommandsManager,
|
||||
SlashClient,
|
||||
SlashCommandHandlerCallback,
|
||||
SlashCommandHandler
|
||||
} from './src/interactions/mod.ts'
|
||||
import {
|
||||
InteractionResponseType,
|
||||
InteractionType
|
||||
} from './src/types/interactions.ts'
|
||||
|
||||
export interface DeploySlashInitOptions {
|
||||
env?: boolean
|
||||
publicKey?: string
|
||||
token?: string
|
||||
path?: string
|
||||
}
|
||||
|
||||
/** Current Slash Client being used to handle commands */
|
||||
let client: SlashClient
|
||||
/** Manage Slash Commands right in Deploy */
|
||||
let commands: SlashCommandsManager
|
||||
|
||||
/**
|
||||
* Initialize Slash Commands Handler for [Deno Deploy](https://deno.com/deploy).
|
||||
* Easily create Serverless Slash Commands on the fly.
|
||||
*
|
||||
* **Examples**
|
||||
*
|
||||
* ```ts
|
||||
* init({
|
||||
* publicKey: "my public key",
|
||||
* token: "my bot's token", // only required if you want to manage slash commands in code
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* ```ts
|
||||
* // takes up `PUBLIC_KEY` and `TOKEN` from ENV
|
||||
* init({ env: true })
|
||||
* ```
|
||||
*
|
||||
* @param options Initialization options
|
||||
*/
|
||||
export function init(options: { env: boolean; path?: string }): void
|
||||
export function init(options: {
|
||||
publicKey: string
|
||||
token?: string
|
||||
path?: string
|
||||
}): void
|
||||
export function init(options: DeploySlashInitOptions): void {
|
||||
if (client !== undefined) throw new Error('Already initialized')
|
||||
if (options.env === true) {
|
||||
options.publicKey = Deno.env.get('PUBLIC_KEY')
|
||||
options.token = Deno.env.get('TOKEN')
|
||||
}
|
||||
|
||||
if (options.publicKey === undefined)
|
||||
throw new Error('Public Key not provided')
|
||||
|
||||
client = new SlashClient({
|
||||
token: options.token,
|
||||
publicKey: options.publicKey
|
||||
})
|
||||
|
||||
commands = client.commands
|
||||
|
||||
const cb = async (evt: {
|
||||
respondWith: CallableFunction
|
||||
request: Request
|
||||
}): Promise<void> => {
|
||||
if (options.path !== undefined) {
|
||||
if (new URL(evt.request.url).pathname !== options.path) return
|
||||
}
|
||||
try {
|
||||
// we have to wrap because there are some weird scope errors
|
||||
const d = await client.verifyFetchEvent({
|
||||
respondWith: (...args: any[]) => evt.respondWith(...args),
|
||||
request: evt.request
|
||||
})
|
||||
if (d === false) {
|
||||
await evt.respondWith(
|
||||
new Response('Not Authorized', {
|
||||
status: 400
|
||||
})
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (d.type === InteractionType.PING) {
|
||||
await d.respond({ type: InteractionResponseType.PONG })
|
||||
client.emit('ping')
|
||||
return
|
||||
}
|
||||
|
||||
await (client as any)._process(d)
|
||||
} catch (e) {
|
||||
await client.emit('interactionError', e)
|
||||
}
|
||||
}
|
||||
|
||||
addEventListener('fetch', cb as any)
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Slash Command handler.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```ts
|
||||
* handle("ping", (interaction) => {
|
||||
* interaction.reply("Pong!")
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Also supports Sub Command and Group handling out of the box!
|
||||
* ```ts
|
||||
* handle("command-name group-name sub-command", (i) => {
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
* handle("command-name sub-command", (i) => {
|
||||
* // ...
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param cmd Command to handle. Either Handler object or command name followed by handler function in next parameter.
|
||||
* @param handler Handler function (required if previous argument was command name)
|
||||
*/
|
||||
export function handle(
|
||||
cmd: string | SlashCommandHandler,
|
||||
handler?: SlashCommandHandlerCallback
|
||||
): void {
|
||||
if (client === undefined)
|
||||
throw new Error('Slash Client not initialized. Call `init` first')
|
||||
client.handle(cmd, handler)
|
||||
}
|
||||
|
||||
export function interactions(cb: (i: Interaction) => any): void {
|
||||
client.on('interaction', cb)
|
||||
}
|
||||
|
||||
export { commands, client }
|
||||
export * from './src/types/slashCommands.ts'
|
||||
export * from './src/types/interactions.ts'
|
||||
export * from './src/structures/slash.ts'
|
||||
export * from './src/interactions/mod.ts'
|
||||
export * from './src/types/channel.ts'
|
||||
export * from './src/structures/interactions.ts'
|
||||
export * from './src/structures/message.ts'
|
||||
export * from './src/structures/embed.ts'
|
|
@ -0,0 +1,6 @@
|
|||
export { EventEmitter } from 'https://deno.land/x/event@1.0.0/mod.ts'
|
||||
export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts'
|
||||
export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts'
|
||||
export { walk } from 'https://deno.land/std@0.95.0/fs/walk.ts'
|
||||
export { join } from 'https://deno.land/std@0.95.0/path/mod.ts'
|
||||
export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0'
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"$schema": "https://x.nest.land/eggs@0.3.4/src/schema.json",
|
||||
"name": "harmony",
|
||||
"entry": "./mod.ts",
|
||||
"description": "An easy to use Discord API Library for Deno.",
|
||||
"homepage": "https://github.com/harmonyland/harmony",
|
||||
"version": "v1.1.4",
|
||||
"files": [
|
||||
"./src/**/*",
|
||||
"./deps.ts",
|
||||
"./README.md",
|
||||
"./LICENSE",
|
||||
"./banner.png",
|
||||
"./CONTRIBUTING.md",
|
||||
"./CODE_OF_CONDUCT.md",
|
||||
"./examples/*"
|
||||
],
|
||||
"checkFormat": "npx eslint src",
|
||||
"checkTests": false,
|
||||
"checkInstallation": false,
|
||||
"check": true,
|
||||
"unlisted": false,
|
||||
"ignore": []
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { Client, Message, Intents } from '../mod.ts'
|
||||
import { Client, Message, GatewayIntents } from '../mod.ts'
|
||||
|
||||
const client = new Client()
|
||||
|
||||
|
@ -13,7 +13,7 @@ client.on('messageCreate', (msg: Message) => {
|
|||
}
|
||||
})
|
||||
|
||||
console.log('harmony - ping example')
|
||||
console.log('Harmony - Ping Example')
|
||||
|
||||
const token = prompt('Input Bot Token:')
|
||||
if (token === null) {
|
||||
|
@ -21,23 +21,8 @@ if (token === null) {
|
|||
Deno.exit()
|
||||
}
|
||||
|
||||
const intents = prompt(
|
||||
'Input Intents (0 = All, 1 = Presence, 2 = Server Members, 3 = None):'
|
||||
)
|
||||
if (intents === null || !['0', '1', '2', '3'].includes(intents)) {
|
||||
console.log('No intents provided')
|
||||
Deno.exit()
|
||||
}
|
||||
|
||||
let ints
|
||||
if (intents === '0') {
|
||||
ints = Intents.All
|
||||
} else if (intents === '1') {
|
||||
ints = Intents.Presence
|
||||
} else if (intents === '2') {
|
||||
ints = Intents.GuildMembers
|
||||
} else {
|
||||
ints = Intents.None
|
||||
}
|
||||
|
||||
client.connect(token, ints)
|
||||
client.connect(token, [
|
||||
GatewayIntents.GUILD_MESSAGES,
|
||||
GatewayIntents.GUILDS,
|
||||
GatewayIntents.DIRECT_MESSAGES
|
||||
])
|
||||
|
|
126
mod.ts
126
mod.ts
|
@ -1,38 +1,47 @@
|
|||
export { GatewayIntents } from './src/types/gateway.ts'
|
||||
export { default as EventEmitter } from 'https://deno.land/std@0.74.0/node/events.ts'
|
||||
export { Base } from './src/structures/base.ts'
|
||||
export { Gateway } from './src/gateway/index.ts'
|
||||
export type { ClientEvents } from './src/gateway/handlers/index.ts'
|
||||
export * from './src/models/client.ts'
|
||||
export * from './src/models/slashClient.ts'
|
||||
export { RESTManager } from './src/models/rest.ts'
|
||||
export * from './src/models/cacheAdapter.ts'
|
||||
export { Gateway } from './src/gateway/mod.ts'
|
||||
export type { GatewayTypedEvents } from './src/gateway/mod.ts'
|
||||
export type { ClientEvents } from './src/gateway/handlers/mod.ts'
|
||||
export * from './src/client/mod.ts'
|
||||
export * from './src/interactions/mod.ts'
|
||||
export {
|
||||
RESTManager,
|
||||
TokenType,
|
||||
HttpResponseCode,
|
||||
DiscordAPIError
|
||||
} from './src/rest/mod.ts'
|
||||
export * from './src/rest/mod.ts'
|
||||
export * from './src/cache/adapter.ts'
|
||||
export {
|
||||
Command,
|
||||
CommandBuilder,
|
||||
CommandCategory,
|
||||
CommandsManager,
|
||||
CategoriesManager
|
||||
} from './src/models/command.ts'
|
||||
export type { CommandContext, CommandOptions } from './src/models/command.ts'
|
||||
CategoriesManager,
|
||||
CommandsLoader
|
||||
} from './src/commands/command.ts'
|
||||
export type { CommandContext, CommandOptions } from './src/commands/command.ts'
|
||||
export {
|
||||
Extension,
|
||||
ExtensionCommands,
|
||||
ExtensionsManager
|
||||
} from './src/models/extensions.ts'
|
||||
export { SlashModule } from './src/models/slashModule.ts'
|
||||
export { CommandClient, command } from './src/models/commandClient.ts'
|
||||
export type { CommandClientOptions } from './src/models/commandClient.ts'
|
||||
} from './src/commands/extension.ts'
|
||||
export { SlashModule } from './src/interactions/slashModule.ts'
|
||||
export { CommandClient, command } from './src/commands/client.ts'
|
||||
export type { CommandClientOptions } from './src/commands/client.ts'
|
||||
export { BaseManager } from './src/managers/base.ts'
|
||||
export { BaseChildManager } from './src/managers/baseChild.ts'
|
||||
export { ChannelsManager } from './src/managers/channels.ts'
|
||||
export { EmojisManager } from './src/managers/emojis.ts'
|
||||
export { GatewayCache } from './src/managers/gatewayCache.ts'
|
||||
export { GuildChannelsManager } from './src/managers/guildChannels.ts'
|
||||
export type { GuildChannel } from './src/managers/guildChannels.ts'
|
||||
export { GuildManager } from './src/managers/guilds.ts'
|
||||
export * from './src/structures/base.ts'
|
||||
export * from './src/structures/slash.ts'
|
||||
export * from './src/types/slash.ts'
|
||||
export * from './src/structures/interactions.ts'
|
||||
export * from './src/types/slashCommands.ts'
|
||||
export * from './src/types/interactions.ts'
|
||||
export { GuildEmojisManager } from './src/managers/guildEmojis.ts'
|
||||
export { MembersManager } from './src/managers/members.ts'
|
||||
export { MessageReactionsManager } from './src/managers/messageReactions.ts'
|
||||
|
@ -40,9 +49,11 @@ export { ReactionUsersManager } from './src/managers/reactionUsers.ts'
|
|||
export { MessagesManager } from './src/managers/messages.ts'
|
||||
export { RolesManager } from './src/managers/roles.ts'
|
||||
export { UsersManager } from './src/managers/users.ts'
|
||||
export { InviteManager } from './src/managers/invites.ts'
|
||||
export { Application } from './src/structures/application.ts'
|
||||
// export { ImageURL } from './src/structures/cdn.ts'
|
||||
export { Channel } from './src/structures/channel.ts'
|
||||
export { ImageURL } from './src/structures/cdn.ts'
|
||||
export { Channel, GuildChannel } from './src/structures/channel.ts'
|
||||
export type { EditOverwriteOptions } from './src/structures/channel.ts'
|
||||
export { DMChannel } from './src/structures/dmChannel.ts'
|
||||
export { Embed } from './src/structures/embed.ts'
|
||||
export { Emoji } from './src/structures/emoji.ts'
|
||||
|
@ -58,7 +69,11 @@ export { NewsChannel } from './src/structures/guildNewsChannel.ts'
|
|||
export { VoiceChannel } from './src/structures/guildVoiceChannel.ts'
|
||||
export { Invite } from './src/structures/invite.ts'
|
||||
export * from './src/structures/member.ts'
|
||||
export { Message } from './src/structures/message.ts'
|
||||
export {
|
||||
Message,
|
||||
MessageAttachment,
|
||||
MessageInteraction
|
||||
} from './src/structures/message.ts'
|
||||
export { MessageMentions } from './src/structures/messageMentions.ts'
|
||||
export {
|
||||
Presence,
|
||||
|
@ -66,8 +81,14 @@ export {
|
|||
ActivityTypes
|
||||
} from './src/structures/presence.ts'
|
||||
export { Role } from './src/structures/role.ts'
|
||||
export { Snowflake } from './src/structures/snowflake.ts'
|
||||
export { TextChannel, GuildTextChannel } from './src/structures/textChannel.ts'
|
||||
export { Snowflake } from './src/utils/snowflake.ts'
|
||||
export { TextChannel } from './src/structures/textChannel.ts'
|
||||
export {
|
||||
GuildTextBasedChannel,
|
||||
GuildTextChannel,
|
||||
checkGuildTextBasedChannel
|
||||
} from './src/structures/guildTextChannel.ts'
|
||||
export type { AllMessageOptions } from './src/structures/textChannel.ts'
|
||||
export { MessageReaction } from './src/structures/messageReaction.ts'
|
||||
export { User } from './src/structures/user.ts'
|
||||
export { Webhook } from './src/structures/webhook.ts'
|
||||
|
@ -76,7 +97,8 @@ export { Intents } from './src/utils/intents.ts'
|
|||
// export { getBuildInfo } from './src/utils/buildInfo.ts'
|
||||
export * from './src/utils/permissions.ts'
|
||||
export { UserFlagsManager } from './src/utils/userFlags.ts'
|
||||
export type { EveryChannelTypes } from './src/utils/getChannelByType.ts'
|
||||
export { HarmonyEventEmitter } from './src/utils/events.ts'
|
||||
export type { EveryChannelTypes } from './src/utils/channel.ts'
|
||||
export * from './src/utils/bitfield.ts'
|
||||
export type {
|
||||
ActivityGame,
|
||||
|
@ -84,7 +106,15 @@ export type {
|
|||
ClientStatus,
|
||||
StatusType
|
||||
} from './src/types/presence.ts'
|
||||
export { ChannelTypes } from './src/types/channel.ts'
|
||||
export {
|
||||
ChannelTypes,
|
||||
OverwriteType,
|
||||
OverrideType
|
||||
} from './src/types/channel.ts'
|
||||
export type {
|
||||
OverwriteAsOptions,
|
||||
OverwritePayload
|
||||
} from './src/types/channel.ts'
|
||||
export type { ApplicationPayload } from './src/types/application.ts'
|
||||
export type { ImageFormats, ImageSize } from './src/types/cdn.ts'
|
||||
export type {
|
||||
|
@ -92,18 +122,37 @@ export type {
|
|||
ChannelPayload,
|
||||
FollowedChannel,
|
||||
GuildNewsChannelPayload,
|
||||
GuildChannelCategoryPayload,
|
||||
GuildCategoryChannelPayload,
|
||||
GuildChannelPayload,
|
||||
GuildTextChannelPayload,
|
||||
GuildVoiceChannelPayload,
|
||||
GroupDMChannelPayload
|
||||
GroupDMChannelPayload,
|
||||
MessageOptions,
|
||||
MessagePayload,
|
||||
MessageInteractionPayload,
|
||||
MessageReference,
|
||||
MessageActivity,
|
||||
MessageActivityTypes,
|
||||
MessageApplication,
|
||||
MessageFlags,
|
||||
MessageStickerFormatTypes,
|
||||
MessageStickerPayload,
|
||||
MessageTypes,
|
||||
OverwriteAsArg,
|
||||
Overwrite
|
||||
} from './src/types/channel.ts'
|
||||
export type { EmojiPayload } from './src/types/emoji.ts'
|
||||
export { Verification } from './src/types/guild.ts'
|
||||
export type {
|
||||
GuildIntegrationPayload,
|
||||
GuildPayload,
|
||||
GuildBanPayload,
|
||||
GuildFeatures,
|
||||
GuildIntegrationPayload,
|
||||
GuildPayload
|
||||
GuildChannels,
|
||||
GuildTextBasedChannels,
|
||||
GuildCreateOptions,
|
||||
GuildCreateChannelOptions,
|
||||
GuildCreateRolePayload
|
||||
} from './src/types/guild.ts'
|
||||
export type { InvitePayload, PartialInvitePayload } from './src/types/invite.ts'
|
||||
export { PermissionFlags } from './src/types/permissionFlags.ts'
|
||||
|
@ -123,3 +172,26 @@ export type { UserPayload } from './src/types/user.ts'
|
|||
export { UserFlags } from './src/types/userFlags.ts'
|
||||
export type { VoiceStatePayload } from './src/types/voice.ts'
|
||||
export type { WebhookPayload } from './src/types/webhook.ts'
|
||||
export * from './src/client/collectors.ts'
|
||||
export type { Dict } from './src/utils/dict.ts'
|
||||
export * from './src/cache/redis.ts'
|
||||
export { ColorUtil } from './src/utils/colorutil.ts'
|
||||
export type { Colors } from './src/utils/colorutil.ts'
|
||||
export { StoreChannel } from './src/structures/guildStoreChannel.ts'
|
||||
export { StageVoiceChannel } from './src/structures/guildVoiceStageChannel.ts'
|
||||
export {
|
||||
isCategoryChannel,
|
||||
isDMChannel,
|
||||
isGroupDMChannel,
|
||||
isGuildBasedTextChannel,
|
||||
isGuildChannel,
|
||||
isGuildTextChannel,
|
||||
isNewsChannel,
|
||||
isStageVoiceChannel,
|
||||
isStoreChannel,
|
||||
isTextChannel,
|
||||
isVoiceChannel,
|
||||
default as getChannelByType
|
||||
} from './src/utils/channel.ts'
|
||||
export * from './src/utils/interactions.ts'
|
||||
export * from "./src/utils/command.ts"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "harmony",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.3",
|
||||
"description": "Discord Deno API that is easy to use.",
|
||||
"main": "mod.ts",
|
||||
"repository": "https://github.com/harmony-org/harmony.git",
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
|
||||
*
|
||||
* Methods can return Promises too.
|
||||
*/
|
||||
export interface ICacheAdapter {
|
||||
/** Gets a key from a Cache */
|
||||
get: (cacheName: string, key: string) => Promise<any> | any
|
||||
/** Sets a key to value in a Cache Name with optional expire value in MS */
|
||||
set: (
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
) => Promise<any> | any
|
||||
/** Deletes a key from a Cache */
|
||||
delete: (cacheName: string, key: string) => Promise<boolean> | boolean
|
||||
/** Gets array of all values in a Cache */
|
||||
array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined>
|
||||
/** Entirely deletes a Cache */
|
||||
deleteCache: (cacheName: string) => any
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import type { ICacheAdapter } from './adapter.ts'
|
||||
|
||||
/** Default Cache Adapter for in-memory caching. */
|
||||
export class DefaultCacheAdapter implements ICacheAdapter {
|
||||
data: {
|
||||
[name: string]: Collection<string, any>
|
||||
} = {}
|
||||
|
||||
async get(cacheName: string, key: string): Promise<undefined | any> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.get(key)
|
||||
}
|
||||
|
||||
async set(
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
): Promise<any> {
|
||||
let cache = this.data[cacheName]
|
||||
if (cache === undefined) {
|
||||
this.data[cacheName] = new Collection()
|
||||
cache = this.data[cacheName]
|
||||
}
|
||||
cache.set(key, value)
|
||||
if (expire !== undefined)
|
||||
setTimeout(() => {
|
||||
cache.delete(key)
|
||||
}, expire)
|
||||
}
|
||||
|
||||
async delete(cacheName: string, key: string): Promise<boolean> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return false
|
||||
return cache.delete(key)
|
||||
}
|
||||
|
||||
async array(cacheName: string): Promise<any[] | undefined> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.array()
|
||||
}
|
||||
|
||||
async deleteCache(cacheName: string): Promise<boolean> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
return delete this.data[cacheName]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './adapter.ts'
|
||||
export * from './default.ts'
|
||||
// Not exported by default
|
||||
// export * from './redis.ts'
|
|
@ -1,82 +1,12 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import { ICacheAdapter } from './adapter.ts'
|
||||
// Not in deps.ts to allow optional dep loading
|
||||
import {
|
||||
connect,
|
||||
Redis,
|
||||
RedisConnectOptions
|
||||
} from 'https://denopkg.com/keroxp/deno-redis/mod.ts'
|
||||
} from 'https://deno.land/x/redis@v0.22.0/mod.ts'
|
||||
|
||||
/**
|
||||
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
|
||||
*
|
||||
* Methods can return Promises too.
|
||||
*/
|
||||
export interface ICacheAdapter {
|
||||
/** Gets a key from a Cache */
|
||||
get: (cacheName: string, key: string) => Promise<any> | any
|
||||
/** Sets a key to value in a Cache Name with optional expire value in MS */
|
||||
set: (
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
) => Promise<any> | any
|
||||
/** Deletes a key from a Cache */
|
||||
delete: (cacheName: string, key: string) => Promise<boolean> | boolean
|
||||
/** Gets array of all values in a Cache */
|
||||
array: (cacheName: string) => undefined | any[] | Promise<any[] | undefined>
|
||||
/** Entirely deletes a Cache */
|
||||
deleteCache: (cacheName: string) => any
|
||||
}
|
||||
|
||||
/** Default Cache Adapter for in-memory caching. */
|
||||
export class DefaultCacheAdapter implements ICacheAdapter {
|
||||
data: {
|
||||
[name: string]: Collection<string, any>
|
||||
} = {}
|
||||
|
||||
async get(cacheName: string, key: string): Promise<undefined | any> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.get(key)
|
||||
}
|
||||
|
||||
async set(
|
||||
cacheName: string,
|
||||
key: string,
|
||||
value: any,
|
||||
expire?: number
|
||||
): Promise<any> {
|
||||
let cache = this.data[cacheName]
|
||||
if (cache === undefined) {
|
||||
this.data[cacheName] = new Collection()
|
||||
cache = this.data[cacheName]
|
||||
}
|
||||
cache.set(key, value)
|
||||
if (expire !== undefined)
|
||||
setTimeout(() => {
|
||||
cache.delete(key)
|
||||
}, expire)
|
||||
}
|
||||
|
||||
async delete(cacheName: string, key: string): Promise<boolean> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return false
|
||||
return cache.delete(key)
|
||||
}
|
||||
|
||||
async array(cacheName: string): Promise<any[] | undefined> {
|
||||
const cache = this.data[cacheName]
|
||||
if (cache === undefined) return
|
||||
return cache.array()
|
||||
}
|
||||
|
||||
async deleteCache(cacheName: string): Promise<boolean> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
return delete this.data[cacheName]
|
||||
}
|
||||
}
|
||||
|
||||
/** Redis Cache Adatper for using Redis as a cache-provider. */
|
||||
/** Redis Cache Adapter for using Redis as a cache-provider. */
|
||||
export class RedisCacheAdapter implements ICacheAdapter {
|
||||
_redis: Promise<Redis>
|
||||
redis?: Redis
|
|
@ -0,0 +1,462 @@
|
|||
/* eslint-disable @typescript-eslint/method-signature-style */
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { GatewayIntents } from '../types/gateway.ts'
|
||||
import { Gateway } from '../gateway/mod.ts'
|
||||
import { RESTManager, RESTOptions, TokenType } from '../rest/mod.ts'
|
||||
import { DefaultCacheAdapter, ICacheAdapter } from '../cache/mod.ts'
|
||||
import { UsersManager } from '../managers/users.ts'
|
||||
import { GuildManager } from '../managers/guilds.ts'
|
||||
import { ChannelsManager } from '../managers/channels.ts'
|
||||
import { ClientPresence } from '../structures/presence.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
import { ActivityGame, ClientActivity } from '../types/presence.ts'
|
||||
import type { Extension } from '../commands/extension.ts'
|
||||
import { SlashClient } from '../interactions/slashClient.ts'
|
||||
import { ShardManager } from './shard.ts'
|
||||
import { Application } from '../structures/application.ts'
|
||||
import { Invite } from '../structures/invite.ts'
|
||||
import { INVITE } from '../types/endpoint.ts'
|
||||
import type { ClientEvents } from '../gateway/handlers/mod.ts'
|
||||
import type { Collector } from './collectors.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import type { VoiceRegion } from '../types/voice.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
import type { DMChannel } from '../structures/dmChannel.ts'
|
||||
import { Template } from '../structures/template.ts'
|
||||
import { VoiceManager } from './voice.ts'
|
||||
|
||||
/** OS related properties sent with Gateway Identify */
|
||||
export interface ClientProperties {
|
||||
os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string
|
||||
browser?: 'harmony' | string
|
||||
device?: 'harmony' | string
|
||||
}
|
||||
|
||||
/** Some Client Options to modify behaviour */
|
||||
export interface ClientOptions {
|
||||
/** ID of the Client/Application to initialize Slash Client REST */
|
||||
id?: string
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: Array<GatewayIntents | keyof typeof GatewayIntents>
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
forceNewSession?: boolean
|
||||
/** Startup presence of client */
|
||||
presence?: ClientPresence | ClientActivity | ActivityGame
|
||||
/** Force all requests to Canary API */
|
||||
canary?: boolean
|
||||
/** Time till which Messages are to be cached, in MS. Default is 3600000 */
|
||||
messageCacheLifetime?: number
|
||||
/** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */
|
||||
reactionCacheLifetime?: number
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions?: boolean
|
||||
/** Client Properties */
|
||||
clientProperties?: ClientProperties
|
||||
/** Enable/Disable Slash Commands Integration (enabled by default) */
|
||||
enableSlash?: boolean
|
||||
/** Disable taking token from env if not provided (token is taken from env if present by default) */
|
||||
disableEnvToken?: boolean
|
||||
/** Override REST Options */
|
||||
restOptions?: RESTOptions
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo?: boolean
|
||||
/** ADVANCED: Shard ID to launch on */
|
||||
shard?: number
|
||||
/** ADVACNED: Shard count. */
|
||||
shardCount?: number | 'auto'
|
||||
}
|
||||
|
||||
/**
|
||||
* Harmony Client. Provides high-level interface over the REST and WebSocket API.
|
||||
*/
|
||||
export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||
/** REST Manager - used to make all requests */
|
||||
rest: RESTManager
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
|
||||
#token?: string
|
||||
|
||||
/** Token of the Bot/User */
|
||||
get token(): string | undefined {
|
||||
return this.#token
|
||||
}
|
||||
|
||||
set token(val: string | undefined) {
|
||||
this.#token = val
|
||||
}
|
||||
|
||||
/** Cache Adapter */
|
||||
get cache(): ICacheAdapter {
|
||||
return this.#cache
|
||||
}
|
||||
|
||||
set cache(val: ICacheAdapter) {
|
||||
this.#cache = val
|
||||
}
|
||||
|
||||
#cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
forceNewSession?: boolean
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
messageCacheLifetime: number = 3600000
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
reactionCacheLifetime: number = 3600000
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions: boolean = false
|
||||
|
||||
/** Client Properties */
|
||||
readonly clientProperties!: ClientProperties
|
||||
|
||||
/** Slash-Commands Management client */
|
||||
slash: SlashClient
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo: boolean = true
|
||||
|
||||
/** Voice Connections Manager */
|
||||
readonly voice = new VoiceManager(this)
|
||||
|
||||
/** Users Manager, containing all Users cached */
|
||||
readonly users: UsersManager = new UsersManager(this)
|
||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||
readonly guilds: GuildManager = new GuildManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
readonly channels: ChannelsManager = new ChannelsManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
readonly emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Last READY timestamp */
|
||||
upSince?: Date
|
||||
|
||||
/** Client's presence. Startup one if set before connecting */
|
||||
presence: ClientPresence = new ClientPresence()
|
||||
|
||||
_id?: string
|
||||
|
||||
/** Shard on which this Client is */
|
||||
shard?: number
|
||||
/** Shard Count */
|
||||
shardCount: number | 'auto' = 'auto'
|
||||
/** Shard Manager of this Client if Sharded */
|
||||
shards: ShardManager
|
||||
/** Collectors set */
|
||||
collectors: Set<Collector> = new Set()
|
||||
|
||||
/** Since when is Client online (ready). */
|
||||
get uptime(): number {
|
||||
if (this.upSince === undefined) return 0
|
||||
else {
|
||||
const dif = Date.now() - this.upSince.getTime()
|
||||
if (dif < 0) return 0
|
||||
else return dif
|
||||
}
|
||||
}
|
||||
|
||||
/** Get Shard 0's Gateway */
|
||||
get gateway(): Gateway {
|
||||
return this.shards.list.get('0')!
|
||||
}
|
||||
|
||||
applicationID?: string
|
||||
applicationFlags?: number
|
||||
|
||||
constructor(options: ClientOptions = {}) {
|
||||
super()
|
||||
this._id = options.id
|
||||
this.token = options.token
|
||||
this.intents = options.intents?.map((e) =>
|
||||
typeof e === 'string' ? GatewayIntents[e] : e
|
||||
)
|
||||
this.shards = new ShardManager(this)
|
||||
this.forceNewSession = options.forceNewSession
|
||||
if (options.cache !== undefined) this.cache = options.cache
|
||||
if (options.presence !== undefined)
|
||||
this.presence =
|
||||
options.presence instanceof ClientPresence
|
||||
? options.presence
|
||||
: new ClientPresence(options.presence)
|
||||
if (options.messageCacheLifetime !== undefined)
|
||||
this.messageCacheLifetime = options.messageCacheLifetime
|
||||
if (options.reactionCacheLifetime !== undefined)
|
||||
this.reactionCacheLifetime = options.reactionCacheLifetime
|
||||
if (options.fetchUncachedReactions === true)
|
||||
this.fetchUncachedReactions = true
|
||||
|
||||
if (
|
||||
(this as any)._decoratedEvents !== undefined &&
|
||||
Object.keys((this as any)._decoratedEvents).length !== 0
|
||||
) {
|
||||
Object.entries((this as any)._decoratedEvents).forEach((entry) => {
|
||||
this.on(entry[0] as keyof ClientEvents, (entry as any)[1].bind(this))
|
||||
})
|
||||
;(this as any)._decoratedEvents = undefined
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'clientProperties', {
|
||||
value:
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties,
|
||||
enumerable: false
|
||||
})
|
||||
|
||||
if (options.shard !== undefined) this.shard = options.shard
|
||||
if (options.shardCount !== undefined) this.shardCount = options.shardCount
|
||||
|
||||
this.fetchGatewayInfo = options.fetchGatewayInfo ?? true
|
||||
|
||||
if (this.token === undefined) {
|
||||
try {
|
||||
const token = Deno.env.get('DISCORD_TOKEN')
|
||||
if (token !== undefined) {
|
||||
this.token = token
|
||||
this.debug('Info', 'Found token in ENV')
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const restOptions: RESTOptions = {
|
||||
token: () => this.token,
|
||||
tokenType: TokenType.Bot,
|
||||
canary: options.canary,
|
||||
client: this
|
||||
}
|
||||
|
||||
if (options.restOptions !== undefined)
|
||||
Object.assign(restOptions, options.restOptions)
|
||||
this.rest = new RESTManager(restOptions)
|
||||
|
||||
this.slash = new SlashClient({
|
||||
id: () => this.getEstimatedID(),
|
||||
client: this,
|
||||
enabled: options.enableSlash
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Cache Adapter
|
||||
*
|
||||
* Should NOT be set after bot is already logged in or using current cache.
|
||||
* Please look into using `cache` option.
|
||||
*/
|
||||
setAdapter(adapter: ICacheAdapter): Client {
|
||||
this.cache = adapter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Changes Presence of Client */
|
||||
setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void {
|
||||
if (presence instanceof ClientPresence) {
|
||||
this.presence = presence
|
||||
} else this.presence = new ClientPresence(presence)
|
||||
this.gateway?.sendPresence(this.presence.create())
|
||||
}
|
||||
|
||||
/** Emits debug event */
|
||||
debug(tag: string, msg: string): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.emit('debug', `[${tag}] ${msg}`)
|
||||
}
|
||||
|
||||
getEstimatedID(): string {
|
||||
if (this.user !== undefined) return this.user.id
|
||||
else if (this.token !== undefined) {
|
||||
try {
|
||||
return atob(this.token.split('.')[0])
|
||||
} catch (e) {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
} else {
|
||||
return this._id ?? 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
/** Fetch Application of the Client */
|
||||
async fetchApplication(): Promise<Application> {
|
||||
const app = await this.rest.api.oauth2.applications['@me'].get()
|
||||
return new Application(this, app)
|
||||
}
|
||||
|
||||
/** Fetch an Invite */
|
||||
async fetchInvite(id: string): Promise<Invite> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.rest
|
||||
.get(INVITE(id))
|
||||
.then((data) => {
|
||||
resolve(new Invite(this, data))
|
||||
})
|
||||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is used for connecting to discord.
|
||||
* @param token Your token. This is required if not given in ClientOptions.
|
||||
* @param intents Gateway intents in array. This is required if not given in ClientOptions.
|
||||
*/
|
||||
async connect(
|
||||
token?: string,
|
||||
intents?: Array<GatewayIntents | keyof typeof GatewayIntents>
|
||||
): Promise<Client> {
|
||||
token ??= this.token
|
||||
if (token === undefined) throw new Error('No Token Provided')
|
||||
this.token = token
|
||||
if (intents !== undefined && this.intents !== undefined) {
|
||||
this.debug(
|
||||
'client',
|
||||
'Intents were set in both client and connect function. Using the one in the connect function...'
|
||||
)
|
||||
} else if (intents === undefined && this.intents !== undefined) {
|
||||
intents = this.intents
|
||||
} else if (intents !== undefined && this.intents === undefined) {
|
||||
this.intents = intents.map((e) =>
|
||||
typeof e === 'string' ? GatewayIntents[e] : e
|
||||
)
|
||||
} else throw new Error('No Gateway Intents were provided')
|
||||
|
||||
this.rest.token = token
|
||||
if (this.shard !== undefined) {
|
||||
if (typeof this.shardCount === 'number')
|
||||
this.shards.cachedShardCount = this.shardCount
|
||||
await this.shards.launch(this.shard)
|
||||
} else await this.shards.connect()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Destroy the Gateway connection */
|
||||
async destroy(): Promise<Client> {
|
||||
this.gateway.initialized = false
|
||||
this.gateway.sequenceID = undefined
|
||||
this.gateway.sessionID = undefined
|
||||
await this.gateway.cache.delete('seq')
|
||||
await this.gateway.cache.delete('session_id')
|
||||
this.gateway.close()
|
||||
this.user = undefined
|
||||
this.upSince = undefined
|
||||
return this
|
||||
}
|
||||
|
||||
/** Attempt to Close current Gateway connection and Resume */
|
||||
async reconnect(): Promise<Client> {
|
||||
this.gateway.close()
|
||||
this.gateway.initWebsocket()
|
||||
return this.waitFor('ready', () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Add a new Collector */
|
||||
addCollector(collector: Collector): boolean {
|
||||
if (this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.add(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/** Remove a Collector */
|
||||
removeCollector(collector: Collector): boolean {
|
||||
if (!this.collectors.has(collector)) return false
|
||||
else {
|
||||
this.collectors.delete(collector)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
async emit(event: keyof ClientEvents, ...args: any[]): Promise<void> {
|
||||
const collectors: Collector[] = []
|
||||
for (const collector of this.collectors.values()) {
|
||||
if (collector.event === event) collectors.push(collector)
|
||||
}
|
||||
if (collectors.length !== 0) {
|
||||
this.collectors.forEach((collector) => collector._fire(...args))
|
||||
}
|
||||
// TODO(DjDeveloperr): Fix this ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error
|
||||
// @ts-ignore
|
||||
return super.emit(event, ...args)
|
||||
}
|
||||
|
||||
/** Returns an array of voice region objects that can be used when creating servers. */
|
||||
async fetchVoiceRegions(): Promise<VoiceRegion[]> {
|
||||
return this.rest.api.voice.regions.get()
|
||||
}
|
||||
|
||||
/** Modify current (Client) User. */
|
||||
async editUser(data: {
|
||||
username?: string
|
||||
avatar?: string
|
||||
}): Promise<Client> {
|
||||
if (data.username === undefined && data.avatar === undefined)
|
||||
throw new Error(
|
||||
'Either username or avatar or both must be specified to edit'
|
||||
)
|
||||
|
||||
if (data.avatar?.startsWith('http') === true) {
|
||||
data.avatar = await fetchAuto(data.avatar)
|
||||
}
|
||||
|
||||
await this.rest.api.users['@me'].patch({
|
||||
username: data.username,
|
||||
avatar: data.avatar
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/** Change Username of the Client User */
|
||||
async setUsername(username: string): Promise<Client> {
|
||||
return await this.editUser({ username })
|
||||
}
|
||||
|
||||
/** Change Avatar of the Client User */
|
||||
async setAvatar(avatar: string): Promise<Client> {
|
||||
return await this.editUser({ avatar })
|
||||
}
|
||||
|
||||
/** Create a DM Channel with a User */
|
||||
async createDM(user: User | string): Promise<DMChannel> {
|
||||
const id = typeof user === 'object' ? user.id : user
|
||||
const dmPayload = await this.rest.api.users['@me'].channels.post({
|
||||
recipient_id: id
|
||||
})
|
||||
await this.channels.set(dmPayload.id, dmPayload)
|
||||
return (this.channels.get<DMChannel>(dmPayload.id) as unknown) as DMChannel
|
||||
}
|
||||
|
||||
/** Returns a template object for the given code. */
|
||||
async fetchTemplate(code: string): Promise<Template> {
|
||||
const payload = await this.rest.api.guilds.templates[code].get()
|
||||
return new Template(this, payload)
|
||||
}
|
||||
}
|
||||
|
||||
/** Event decorator to create an Event handler from function */
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function event(name?: keyof ClientEvents) {
|
||||
return function (
|
||||
client: Client | Extension,
|
||||
prop: keyof ClientEvents | string
|
||||
) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const c = client as any
|
||||
const listener = ((client as unknown) as {
|
||||
[name in keyof ClientEvents]: (...args: ClientEvents[name]) => any
|
||||
})[(prop as unknown) as keyof ClientEvents]
|
||||
if (typeof listener !== 'function')
|
||||
throw new Error('@event decorator requires a function')
|
||||
|
||||
if (c._decoratedEvents === undefined) c._decoratedEvents = {}
|
||||
const key = name === undefined ? prop : name
|
||||
|
||||
c._decoratedEvents[key] = listener
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import type { Client } from '../client/client.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
|
||||
export type CollectorFilter = (...args: any[]) => boolean | Promise<boolean>
|
||||
|
||||
export interface CollectorOptions {
|
||||
/** Event name to listen for */
|
||||
event: string
|
||||
/** Optionally Client object for deinitOnEnd functionality */
|
||||
client?: Client
|
||||
/** Filter function */
|
||||
filter?: CollectorFilter
|
||||
/** Max entries to collect */
|
||||
max?: number
|
||||
/** Whether or not to de-initialize on end */
|
||||
deinitOnEnd?: boolean
|
||||
/** Timeout to end the Collector if not fulfilled if any filter or max */
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type CollectorEvents = {
|
||||
start: []
|
||||
end: []
|
||||
collect: any
|
||||
}
|
||||
|
||||
export class Collector extends HarmonyEventEmitter<CollectorEvents> {
|
||||
client?: Client
|
||||
private _started: boolean = false
|
||||
event: string
|
||||
filter: CollectorFilter = () => true
|
||||
collected: Collection<string, any[]> = new Collection()
|
||||
max?: number
|
||||
deinitOnEnd: boolean = false
|
||||
timeout?: number
|
||||
private _timer?: number
|
||||
|
||||
get started(): boolean {
|
||||
return this._started
|
||||
}
|
||||
|
||||
set started(d: boolean) {
|
||||
if (d !== this._started) {
|
||||
this._started = d
|
||||
if (d) this.emit('start')
|
||||
else {
|
||||
if (this.deinitOnEnd && this.client !== undefined)
|
||||
this.deinit(this.client)
|
||||
this.emit('end')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(options: CollectorOptions | string) {
|
||||
super()
|
||||
if (typeof options === 'string') this.event = options
|
||||
else {
|
||||
this.event = options.event
|
||||
this.client = options.client
|
||||
this.filter = options.filter ?? (() => true)
|
||||
this.max = options.max
|
||||
this.deinitOnEnd = options.deinitOnEnd ?? false
|
||||
this.timeout = options.timeout
|
||||
}
|
||||
}
|
||||
|
||||
/** Start collecting */
|
||||
collect(): Collector {
|
||||
this.started = true
|
||||
if (this.client !== undefined) this.init(this.client)
|
||||
if (this._timer !== undefined) clearTimeout(this._timer)
|
||||
if (this.timeout !== undefined) {
|
||||
this._timer = setTimeout(() => {
|
||||
this.end()
|
||||
}, this.timeout)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/** End collecting */
|
||||
end(): Collector {
|
||||
this.started = false
|
||||
if (this._timer !== undefined) clearTimeout(this._timer)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Reset collector and start again */
|
||||
reset(): Collector {
|
||||
this.collected = new Collection()
|
||||
this.collect()
|
||||
return this
|
||||
}
|
||||
|
||||
/** Init the Collector on Client */
|
||||
init(client: Client): Collector {
|
||||
this.client = client
|
||||
client.addCollector(this)
|
||||
return this
|
||||
}
|
||||
|
||||
/** De initialize the Collector i.e. remove cleanly */
|
||||
deinit(client: Client): Collector {
|
||||
client.removeCollector(this)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Checks we may want to perform on an extended version of Collector */
|
||||
protected check(..._args: any[]): boolean | Promise<boolean> {
|
||||
return true
|
||||
}
|
||||
|
||||
/** Fire the Collector */
|
||||
async _fire(...args: any[]): Promise<void> {
|
||||
if (!this.started) return
|
||||
const check = await this.check(...args)
|
||||
if (!check) return
|
||||
const filter = await this.filter(...args)
|
||||
if (!filter) return
|
||||
this.collected.set((Number(this.collected.size) + 1).toString(), args)
|
||||
this.emit('collect', ...args)
|
||||
if (
|
||||
this.max !== undefined &&
|
||||
// linter: send help
|
||||
this.max < Number(this.collected.size) + 1
|
||||
) {
|
||||
this.end()
|
||||
}
|
||||
}
|
||||
|
||||
/** Set filter of the Collector */
|
||||
when(filter: CollectorFilter): Collector {
|
||||
this.filter = filter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Add a new listener for 'collect' event */
|
||||
each(handler: CallableFunction): Collector {
|
||||
this.on('collect', () => handler())
|
||||
return this
|
||||
}
|
||||
|
||||
/** Returns a Promise resolved when Collector ends or a timeout occurs */
|
||||
async wait(timeout?: number): Promise<Collector> {
|
||||
if (timeout === undefined) timeout = this.timeout ?? 0
|
||||
return await new Promise((resolve, reject) => {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (!timeout)
|
||||
throw new Error(
|
||||
'Timeout is required parameter if not given in CollectorOptions'
|
||||
)
|
||||
|
||||
let done = false
|
||||
const onend = (): void => {
|
||||
done = true
|
||||
this.off('end', onend)
|
||||
resolve(this)
|
||||
}
|
||||
|
||||
this.on('end', onend)
|
||||
setTimeout(() => {
|
||||
if (!done) {
|
||||
this.off('end', onend)
|
||||
reject(new Error('Timeout'))
|
||||
}
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './client.ts'
|
||||
export * from './collectors.ts'
|
||||
export * from './shard.ts'
|
|
@ -0,0 +1,133 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import type { Client } from './client.ts'
|
||||
import { RESTManager } from '../rest/mod.ts'
|
||||
import { Gateway } from '../gateway/mod.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { GatewayEvents } from '../types/gateway.ts'
|
||||
import { delay } from '../utils/delay.ts'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type ShardManagerEvents = {
|
||||
launch: [number]
|
||||
shardReady: [number]
|
||||
shardDisconnect: [number, number | undefined, string | undefined]
|
||||
shardError: [number, Error, ErrorEvent]
|
||||
shardResume: [number]
|
||||
}
|
||||
|
||||
export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
|
||||
list: Collection<string, Gateway> = new Collection()
|
||||
client: Client
|
||||
cachedShardCount?: number
|
||||
queueProcessing: boolean = false
|
||||
queue: CallableFunction[] = []
|
||||
|
||||
get rest(): RESTManager {
|
||||
return this.client.rest
|
||||
}
|
||||
|
||||
constructor(client: Client) {
|
||||
super()
|
||||
this.client = client
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
this.client.debug('Shards', msg)
|
||||
}
|
||||
|
||||
enqueueIdentify(fn: CallableFunction): ShardManager {
|
||||
this.queue.push(fn)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
if (!this.queueProcessing) this.processQueue()
|
||||
return this
|
||||
}
|
||||
|
||||
private async processQueue(): Promise<void> {
|
||||
if (this.queueProcessing || this.queue.length === 0) return
|
||||
this.queueProcessing = true
|
||||
const item = this.queue[0]
|
||||
await item()
|
||||
this.queue.shift()
|
||||
await delay(5000)
|
||||
this.queueProcessing = false
|
||||
if (this.queue.length === 0) {
|
||||
this.queueProcessing = false
|
||||
} else {
|
||||
await this.processQueue()
|
||||
}
|
||||
}
|
||||
|
||||
async getShardCount(): Promise<number> {
|
||||
let shardCount: number
|
||||
if (this.cachedShardCount !== undefined) shardCount = this.cachedShardCount
|
||||
else {
|
||||
if (
|
||||
this.client.shardCount === 'auto' &&
|
||||
this.client.fetchGatewayInfo !== false
|
||||
) {
|
||||
this.debug('Fetch /gateway/bot...')
|
||||
const info = await this.client.rest.api.gateway.bot.get()
|
||||
this.debug(`Recommended Shards: ${info.shards}`)
|
||||
this.debug('=== Session Limit Info ===')
|
||||
this.debug(
|
||||
`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`
|
||||
)
|
||||
this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
|
||||
shardCount = info.shards as number
|
||||
} else
|
||||
shardCount =
|
||||
typeof this.client.shardCount === 'string'
|
||||
? 1
|
||||
: this.client.shardCount ?? 1
|
||||
}
|
||||
this.cachedShardCount = shardCount
|
||||
return this.cachedShardCount
|
||||
}
|
||||
|
||||
/** Launches a new Shard */
|
||||
async launch(id: number): Promise<ShardManager> {
|
||||
if (this.list.has(id.toString()) === true)
|
||||
throw new Error(`Shard ${id} already launched`)
|
||||
|
||||
this.debug(`Launching Shard: ${id}`)
|
||||
const shardCount = await this.getShardCount()
|
||||
|
||||
const gw = new Gateway(this.client, [Number(id), shardCount])
|
||||
this.list.set(id.toString(), gw)
|
||||
|
||||
gw.initWebsocket()
|
||||
this.emit('launch', id)
|
||||
|
||||
gw.on(GatewayEvents.Ready, () => this.emit('shardReady', id))
|
||||
gw.on('error', (err: Error, evt: ErrorEvent) =>
|
||||
this.emit('shardError', id, err, evt)
|
||||
)
|
||||
gw.on(GatewayEvents.Resumed, () => this.emit('shardResume', id))
|
||||
gw.on('close', (code: number, reason: string) =>
|
||||
this.emit('shardDisconnect', id, code, reason)
|
||||
)
|
||||
|
||||
return gw.waitFor(GatewayEvents.Ready, () => true).then(() => this)
|
||||
}
|
||||
|
||||
/** Launches all Shards */
|
||||
async connect(): Promise<ShardManager> {
|
||||
const shardCount = await this.getShardCount()
|
||||
this.client.shardCount = shardCount
|
||||
this.debug(`Launching ${shardCount} shard${shardCount === 1 ? '' : 's'}...`)
|
||||
const startTime = Date.now()
|
||||
for (let i = 0; i < shardCount; i++) {
|
||||
await this.launch(i)
|
||||
}
|
||||
const endTime = Date.now()
|
||||
const diff = endTime - startTime
|
||||
this.debug(
|
||||
`Launched ${shardCount} shards! Time taken: ${Math.floor(diff / 1000)}s`
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
get(id: number): Gateway | undefined {
|
||||
return this.list.get(id.toString())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
import type { VoiceServerUpdateData } from '../gateway/handlers/mod.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { VoiceStateOptions } from '../gateway/mod.ts'
|
||||
import { VoiceState } from '../structures/voiceState.ts'
|
||||
import { ChannelTypes } from '../types/channel.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import type { Client } from './client.ts'
|
||||
|
||||
export interface VoiceServerData extends VoiceServerUpdateData {
|
||||
userID: string
|
||||
sessionID: string
|
||||
}
|
||||
|
||||
export interface VoiceChannelJoinOptions extends VoiceStateOptions {
|
||||
timeout?: number
|
||||
}
|
||||
|
||||
export class VoiceManager extends HarmonyEventEmitter<{
|
||||
voiceStateUpdate: [VoiceState]
|
||||
}> {
|
||||
#pending = new Map<string, [number, CallableFunction]>()
|
||||
|
||||
readonly client!: Client
|
||||
|
||||
constructor(client: Client) {
|
||||
super()
|
||||
Object.defineProperty(this, 'client', {
|
||||
value: client,
|
||||
enumerable: false
|
||||
})
|
||||
}
|
||||
|
||||
async join(
|
||||
channel: string | VoiceChannel,
|
||||
options?: VoiceChannelJoinOptions
|
||||
): Promise<VoiceServerData> {
|
||||
const id = typeof channel === 'string' ? channel : channel.id
|
||||
const chan = await this.client.channels.get<VoiceChannel>(id)
|
||||
if (chan === undefined) throw new Error('Voice Channel not cached')
|
||||
if (
|
||||
chan.type !== ChannelTypes.GUILD_VOICE &&
|
||||
chan.type !== ChannelTypes.GUILD_STAGE_VOICE
|
||||
)
|
||||
throw new Error('Cannot join non-voice channel')
|
||||
|
||||
const pending = this.#pending.get(chan.guild.id)
|
||||
if (pending !== undefined) {
|
||||
clearTimeout(pending[0])
|
||||
pending[1](new Error('Voice Connection timed out'))
|
||||
this.#pending.delete(chan.guild.id)
|
||||
}
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
let vcdata: VoiceServerData
|
||||
let done = 0
|
||||
|
||||
const onVoiceStateAdd = (state: VoiceState): void => {
|
||||
if (state.user.id !== this.client.user?.id) return
|
||||
if (state.channel?.id !== id) return
|
||||
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
||||
done++
|
||||
vcdata = vcdata ?? {}
|
||||
vcdata.sessionID = state.sessionID
|
||||
vcdata.userID = state.user.id
|
||||
if (done >= 2) {
|
||||
this.#pending.delete(chan.guild.id)
|
||||
resolve(vcdata)
|
||||
}
|
||||
}
|
||||
|
||||
const onVoiceServerUpdate = (data: VoiceServerUpdateData): void => {
|
||||
if (data.guild.id !== chan.guild.id) return
|
||||
vcdata = Object.assign(vcdata ?? {}, data)
|
||||
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
||||
done++
|
||||
if (done >= 2) {
|
||||
this.#pending.delete(chan.guild.id)
|
||||
resolve(vcdata)
|
||||
}
|
||||
}
|
||||
|
||||
this.client.shards
|
||||
.get(chan.guild.shardID)!
|
||||
.updateVoiceState(chan.guild.id, chan.id, options)
|
||||
|
||||
this.on('voiceStateUpdate', onVoiceStateAdd)
|
||||
this.client.on('voiceServerUpdate', onVoiceServerUpdate)
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (done < 2) {
|
||||
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
||||
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
||||
reject(
|
||||
new Error(
|
||||
"Connection timed out - couldn't connect to Voice Channel"
|
||||
)
|
||||
)
|
||||
}
|
||||
}, options?.timeout ?? 1000 * 30)
|
||||
|
||||
this.#pending.set(chan.guild.id, [timer, reject])
|
||||
})
|
||||
}
|
||||
|
||||
async leave(guildOrID: Guild | string): Promise<void> {
|
||||
const id = typeof guildOrID === 'string' ? guildOrID : guildOrID.id
|
||||
const guild = await this.client.guilds.get(id)
|
||||
if (guild === undefined) throw new Error('Guild not cached')
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||
const vcs = await guild.voiceStates.get(this.client.user?.id!)
|
||||
if (vcs === undefined) throw new Error('Not in Voice Channel')
|
||||
|
||||
this.client.shards.get(guild.shardID)!.updateVoiceState(guild, undefined)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
import { Message } from '../structures/message.ts'
|
||||
import { GuildTextChannel } from '../structures/textChannel.ts'
|
||||
import { awaitSync } from '../utils/mixedPromise.ts'
|
||||
import { Client, ClientOptions } from './client.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import type { GuildTextBasedChannel } from '../structures/guildTextChannel.ts'
|
||||
import { Client, ClientOptions } from '../client/mod.ts'
|
||||
import {
|
||||
CategoriesManager,
|
||||
Command,
|
||||
|
@ -10,7 +9,8 @@ import {
|
|||
CommandsManager,
|
||||
parseCommand
|
||||
} from './command.ts'
|
||||
import { Extension, ExtensionsManager } from './extensions.ts'
|
||||
import { parseArgs } from '../utils/command.ts'
|
||||
import { Extension, ExtensionsManager } from './extension.ts'
|
||||
|
||||
type PrefixReturnType = string | string[] | Promise<string | string[]>
|
||||
|
||||
|
@ -44,6 +44,11 @@ export interface CommandClientOptions extends ClientOptions {
|
|||
caseSensitive?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Harmony Client with extended functionality for Message based Commands parsing and handling.
|
||||
*
|
||||
* See SlashClient (`Client#slash`) for more info about Slash Commands.
|
||||
*/
|
||||
export class CommandClient extends Client implements CommandClientOptions {
|
||||
prefix: string | string[]
|
||||
mentionPrefix: boolean
|
||||
|
@ -66,8 +71,6 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
commands: CommandsManager = new CommandsManager(this)
|
||||
categories: CategoriesManager = new CategoriesManager(this)
|
||||
|
||||
_decoratedCommands?: { [name: string]: Command }
|
||||
|
||||
constructor(options: CommandClientOptions) {
|
||||
super(options)
|
||||
this.prefix = options.prefix
|
||||
|
@ -112,11 +115,12 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
this.caseSensitive =
|
||||
options.caseSensitive === undefined ? false : options.caseSensitive
|
||||
|
||||
if (this._decoratedCommands !== undefined) {
|
||||
Object.values(this._decoratedCommands).forEach((entry) => {
|
||||
const self = this as any
|
||||
if (self._decoratedCommands !== undefined) {
|
||||
Object.values(self._decoratedCommands).forEach((entry: any) => {
|
||||
this.commands.add(entry)
|
||||
})
|
||||
this._decoratedCommands = undefined
|
||||
self._decoratedCommands = undefined
|
||||
}
|
||||
|
||||
this.on(
|
||||
|
@ -129,35 +133,29 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
async processMessage(msg: Message): Promise<any> {
|
||||
if (!this.allowBots && msg.author.bot === true) return
|
||||
|
||||
const isUserBlacklisted = await awaitSync(
|
||||
this.isUserBlacklisted(msg.author.id)
|
||||
)
|
||||
if (isUserBlacklisted === true) return
|
||||
const isUserBlacklisted = await this.isUserBlacklisted(msg.author.id)
|
||||
if (isUserBlacklisted) return
|
||||
|
||||
const isChannelBlacklisted = await awaitSync(
|
||||
this.isChannelBlacklisted(msg.channel.id)
|
||||
)
|
||||
if (isChannelBlacklisted === true) return
|
||||
const isChannelBlacklisted = await this.isChannelBlacklisted(msg.channel.id)
|
||||
if (isChannelBlacklisted) return
|
||||
|
||||
if (msg.guild !== undefined) {
|
||||
const isGuildBlacklisted = await awaitSync(
|
||||
this.isGuildBlacklisted(msg.guild.id)
|
||||
)
|
||||
if (isGuildBlacklisted === true) return
|
||||
const isGuildBlacklisted = await this.isGuildBlacklisted(msg.guild.id)
|
||||
if (isGuildBlacklisted) return
|
||||
}
|
||||
|
||||
let prefix: string | string[] = []
|
||||
if (typeof this.prefix === 'string') prefix = [...prefix, this.prefix]
|
||||
else prefix = [...prefix, ...this.prefix]
|
||||
|
||||
const userPrefix = await awaitSync(this.getUserPrefix(msg.author.id))
|
||||
const userPrefix = await this.getUserPrefix(msg.author.id)
|
||||
if (userPrefix !== undefined) {
|
||||
if (typeof userPrefix === 'string') prefix = [...prefix, userPrefix]
|
||||
else prefix = [...prefix, ...userPrefix]
|
||||
}
|
||||
|
||||
if (msg.guild !== undefined) {
|
||||
const guildPrefix = await awaitSync(this.getGuildPrefix(msg.guild.id))
|
||||
const guildPrefix = await this.getGuildPrefix(msg.guild.id)
|
||||
if (guildPrefix !== undefined) {
|
||||
if (typeof guildPrefix === 'string') prefix = [...prefix, guildPrefix]
|
||||
else prefix = [...prefix, ...guildPrefix]
|
||||
|
@ -242,7 +240,7 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
client: this,
|
||||
name: parsed.name,
|
||||
prefix,
|
||||
args: parsed.args,
|
||||
args: parseArgs(command.args, parsed.args),
|
||||
argString: parsed.argString,
|
||||
message: msg,
|
||||
author: msg.author,
|
||||
|
@ -259,7 +257,7 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
: category.ownerOnly) === true &&
|
||||
!this.owners.includes(msg.author.id)
|
||||
)
|
||||
return this.emit('commandOwnerOnly', ctx, command)
|
||||
return this.emit('commandOwnerOnly', ctx)
|
||||
|
||||
// Checks if Command is only for Guild
|
||||
if (
|
||||
|
@ -268,7 +266,7 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
: category.guildOnly) === true &&
|
||||
msg.guild === undefined
|
||||
)
|
||||
return this.emit('commandGuildOnly', ctx, command)
|
||||
return this.emit('commandGuildOnly', ctx)
|
||||
|
||||
// Checks if Command is only for DMs
|
||||
if (
|
||||
|
@ -277,14 +275,14 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
: category.dmOnly) === true &&
|
||||
msg.guild !== undefined
|
||||
)
|
||||
return this.emit('commandDmOnly', ctx, command)
|
||||
return this.emit('commandDmOnly', ctx)
|
||||
|
||||
if (
|
||||
command.nsfw === true &&
|
||||
(msg.guild === undefined ||
|
||||
((msg.channel as unknown) as GuildTextChannel).nsfw !== true)
|
||||
((msg.channel as unknown) as GuildTextBasedChannel).nsfw !== true)
|
||||
)
|
||||
return this.emit('commandNSFW', ctx, command)
|
||||
return this.emit('commandNSFW', ctx)
|
||||
|
||||
const allPermissions =
|
||||
command.permissions !== undefined
|
||||
|
@ -293,7 +291,8 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
|
||||
if (
|
||||
(command.botPermissions !== undefined ||
|
||||
category?.permissions !== undefined) &&
|
||||
category?.botPermissions !== undefined ||
|
||||
allPermissions !== undefined) &&
|
||||
msg.guild !== undefined
|
||||
) {
|
||||
// TODO: Check Overwrites too
|
||||
|
@ -316,18 +315,14 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
}
|
||||
|
||||
if (missing.length !== 0)
|
||||
return this.emit(
|
||||
'commandBotMissingPermissions',
|
||||
ctx,
|
||||
command,
|
||||
missing
|
||||
)
|
||||
return this.emit('commandBotMissingPermissions', ctx, missing)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
(command.userPermissions !== undefined ||
|
||||
category?.userPermissions !== undefined) &&
|
||||
category?.userPermissions !== undefined ||
|
||||
allPermissions !== undefined) &&
|
||||
msg.guild !== undefined
|
||||
) {
|
||||
let permissions =
|
||||
|
@ -349,51 +344,52 @@ export class CommandClient extends Client implements CommandClientOptions {
|
|||
}
|
||||
|
||||
if (missing.length !== 0)
|
||||
return this.emit(
|
||||
'commandUserMissingPermissions',
|
||||
command,
|
||||
missing,
|
||||
ctx
|
||||
)
|
||||
return this.emit('commandUserMissingPermissions', ctx, missing)
|
||||
}
|
||||
}
|
||||
|
||||
if (command.args !== undefined) {
|
||||
if (typeof command.args === 'boolean' && parsed.args.length === 0)
|
||||
return this.emit('commandMissingArgs', ctx, command)
|
||||
return this.emit('commandMissingArgs', ctx)
|
||||
else if (
|
||||
typeof command.args === 'number' &&
|
||||
parsed.args.length < command.args
|
||||
)
|
||||
this.emit('commandMissingArgs', ctx, command)
|
||||
this.emit('commandMissingArgs', ctx)
|
||||
}
|
||||
|
||||
try {
|
||||
this.emit('commandUsed', ctx, command)
|
||||
this.emit('commandUsed', ctx)
|
||||
|
||||
const beforeExecute = await awaitSync(command.beforeExecute(ctx))
|
||||
const beforeExecute = await command.beforeExecute(ctx)
|
||||
if (beforeExecute === false) return
|
||||
|
||||
const result = await awaitSync(command.execute(ctx))
|
||||
command.afterExecute(ctx, result)
|
||||
const result = await command.execute(ctx)
|
||||
await command.afterExecute(ctx, result)
|
||||
} catch (e) {
|
||||
this.emit('commandError', command, ctx, e)
|
||||
try {
|
||||
await command.onError(ctx, e)
|
||||
} catch (e) {
|
||||
this.emit('commandError', ctx, e)
|
||||
}
|
||||
this.emit('commandError', ctx, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Command decorator. Decorates the function with optional metadata as a Command registered upon constructing class.
|
||||
*/
|
||||
export function command(options?: CommandOptions) {
|
||||
return function (target: CommandClient | Extension, name: string) {
|
||||
if (target._decoratedCommands === undefined) target._decoratedCommands = {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const c = target as any
|
||||
if (c._decoratedCommands === undefined) c._decoratedCommands = {}
|
||||
|
||||
const prop = ((target as unknown) as {
|
||||
[name: string]: (ctx: CommandContext) => any
|
||||
})[name]
|
||||
const prop = c[name]
|
||||
|
||||
if (prop instanceof Command) {
|
||||
target._decoratedCommands[prop.name] = prop
|
||||
return
|
||||
}
|
||||
if (typeof prop !== 'function')
|
||||
throw new Error('@command decorator can only be used on class methods')
|
||||
|
||||
const command = new Command()
|
||||
|
||||
|
@ -404,6 +400,6 @@ export function command(options?: CommandOptions) {
|
|||
|
||||
if (target instanceof Extension) command.extension = target
|
||||
|
||||
target._decoratedCommands[command.name] = command
|
||||
c._decoratedCommands[command.name] = command
|
||||
}
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { Guild } from '../structures/guild.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import { TextChannel } from '../structures/textChannel.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import type { TextChannel } from '../structures/textChannel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { CommandClient } from './commandClient.ts'
|
||||
import { Extension } from './extensions.ts'
|
||||
import { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts'
|
||||
|
||||
import type { CommandClient } from './client.ts'
|
||||
import type { Extension } from './extension.ts'
|
||||
import { join, walk } from '../../deps.ts'
|
||||
import type { Args } from '../utils/command.ts'
|
||||
export interface CommandContext {
|
||||
/** The Client object */
|
||||
client: CommandClient
|
||||
|
@ -18,12 +18,12 @@ export interface CommandContext {
|
|||
channel: TextChannel
|
||||
/** Prefix which was used */
|
||||
prefix: string
|
||||
/** Oject of Command which was used */
|
||||
/** Object of Command which was used */
|
||||
command: Command
|
||||
/** Name of Command which was used */
|
||||
name: string
|
||||
/** Array of Arguments used with Command */
|
||||
args: string[]
|
||||
args: Record<string, unknown> | null
|
||||
/** Complete Raw String of Arguments */
|
||||
argString: string
|
||||
/** Guild which the command has called */
|
||||
|
@ -46,7 +46,7 @@ export interface CommandOptions {
|
|||
/** Usage Example of Command, only Arguments (without Prefix and Name) */
|
||||
examples?: string | string[]
|
||||
/** Does the Command take Arguments? Maybe number of required arguments? Or list of arguments? */
|
||||
args?: number | boolean | string[]
|
||||
args?: Args[]
|
||||
/** Permissions(s) required by both User and Bot in order to use Command */
|
||||
permissions?: string | string[]
|
||||
/** Permission(s) required for using Command */
|
||||
|
@ -72,6 +72,8 @@ export interface CommandOptions {
|
|||
}
|
||||
|
||||
export class Command implements CommandOptions {
|
||||
static meta?: CommandOptions
|
||||
|
||||
name: string = ''
|
||||
description?: string
|
||||
category?: string
|
||||
|
@ -79,7 +81,7 @@ export class Command implements CommandOptions {
|
|||
extension?: Extension
|
||||
usage?: string | string[]
|
||||
examples?: string | string[]
|
||||
args?: number | boolean | string[]
|
||||
args?: Args[]
|
||||
permissions?: string | string[]
|
||||
userPermissions?: string | string[]
|
||||
botPermissions?: string | string[]
|
||||
|
@ -92,6 +94,9 @@ export class Command implements CommandOptions {
|
|||
dmOnly?: boolean
|
||||
ownerOnly?: boolean
|
||||
|
||||
/** Method called when the command errors */
|
||||
onError(ctx: CommandContext, error: Error): any {}
|
||||
|
||||
/** Method executed before executing actual command. Returns bool value - whether to continue or not (optional) */
|
||||
beforeExecute(ctx: CommandContext): boolean | Promise<boolean> {
|
||||
return true
|
||||
|
@ -284,13 +289,107 @@ export class CommandBuilder extends Command {
|
|||
}
|
||||
}
|
||||
|
||||
export class CommandsLoader {
|
||||
client: CommandClient
|
||||
#importSeq: { [name: string]: number } = {}
|
||||
|
||||
constructor(client: CommandClient) {
|
||||
this.client = client
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Command from file.
|
||||
*
|
||||
* NOTE: Relative paths resolve from cwd
|
||||
*
|
||||
* @param filePath Path of Command file.
|
||||
* @param exportName Export name. Default is the "default" export.
|
||||
*/
|
||||
async load(
|
||||
filePath: string,
|
||||
exportName: string = 'default',
|
||||
onlyRead?: boolean
|
||||
): Promise<Command> {
|
||||
const stat = await Deno.stat(filePath).catch(() => undefined)
|
||||
if (stat === undefined || stat.isFile !== true)
|
||||
throw new Error(`File not found on path ${filePath}`)
|
||||
|
||||
let seq: number | undefined
|
||||
|
||||
if (this.#importSeq[filePath] !== undefined) seq = this.#importSeq[filePath]
|
||||
const mod = await import(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
'file:///' +
|
||||
join(Deno.cwd(), filePath) +
|
||||
(seq === undefined ? '' : `#${seq}`)
|
||||
)
|
||||
if (this.#importSeq[filePath] === undefined) this.#importSeq[filePath] = 0
|
||||
else this.#importSeq[filePath]++
|
||||
|
||||
const Cmd = mod[exportName]
|
||||
if (Cmd === undefined)
|
||||
throw new Error(`Command not exported as ${exportName} from ${filePath}`)
|
||||
|
||||
let cmd: Command
|
||||
try {
|
||||
if (Cmd instanceof Command) cmd = Cmd
|
||||
else cmd = new Cmd()
|
||||
if (!(cmd instanceof Command)) throw new Error('failed')
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to load Command from ${filePath}`)
|
||||
}
|
||||
|
||||
if (onlyRead !== true) this.client.commands.add(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
/**
|
||||
* Load commands from a Directory.
|
||||
*
|
||||
* NOTE: Relative paths resolve from cwd
|
||||
*
|
||||
* @param path Path of the directory.
|
||||
* @param options Options to configure loading.
|
||||
*/
|
||||
async loadDirectory(
|
||||
path: string,
|
||||
options?: {
|
||||
recursive?: boolean
|
||||
exportName?: string
|
||||
maxDepth?: number
|
||||
exts?: string[]
|
||||
onlyRead?: boolean
|
||||
}
|
||||
): Promise<Command[]> {
|
||||
const commands: Command[] = []
|
||||
|
||||
for await (const entry of walk(path, {
|
||||
maxDepth: options?.maxDepth,
|
||||
exts: options?.exts,
|
||||
includeDirs: false
|
||||
})) {
|
||||
if (entry.isFile !== true) continue
|
||||
const cmd = await this.load(
|
||||
entry.path,
|
||||
options?.exportName,
|
||||
options?.onlyRead
|
||||
)
|
||||
commands.push(cmd)
|
||||
}
|
||||
|
||||
return commands
|
||||
}
|
||||
}
|
||||
|
||||
export class CommandsManager {
|
||||
client: CommandClient
|
||||
list: Collection<string, Command> = new Collection()
|
||||
disabled: Set<string> = new Set()
|
||||
loader: CommandsLoader
|
||||
|
||||
constructor(client: CommandClient) {
|
||||
this.client = client
|
||||
this.loader = new CommandsLoader(client)
|
||||
}
|
||||
|
||||
/** Number of loaded Commands */
|
||||
|
@ -393,12 +492,16 @@ export class CommandsManager {
|
|||
|
||||
/** Add a Command */
|
||||
add(cmd: Command | typeof Command): boolean {
|
||||
// eslint-disable-next-line new-cap
|
||||
if (!(cmd instanceof Command)) cmd = new cmd()
|
||||
if (!(cmd instanceof Command)) {
|
||||
const CmdClass = cmd
|
||||
cmd = new CmdClass()
|
||||
Object.assign(cmd, CmdClass.meta ?? {})
|
||||
}
|
||||
if (this.exists(cmd, cmd.extension?.subPrefix))
|
||||
throw new Error(
|
||||
`Failed to add Command '${cmd.toString()}' with name/alias already exists.`
|
||||
)
|
||||
if (cmd.name === '') throw new Error('Command has no name')
|
||||
this.list.set(
|
||||
`${cmd.name}-${
|
||||
this.list.filter((e) =>
|
||||
|
@ -506,7 +609,7 @@ export const parseCommand = (
|
|||
): ParsedCommand | undefined => {
|
||||
let content = msg.content.slice(prefix.length)
|
||||
if (client.spacesAfterPrefix === true) content = content.trim()
|
||||
const args = parse(content)._.map((e) => e.toString())
|
||||
const args = content.split(' ')
|
||||
|
||||
const name = args.shift()
|
||||
if (name === undefined) return
|
|
@ -1,6 +1,7 @@
|
|||
import { Collection } from '../utils/collection.ts'
|
||||
import { Command } from './command.ts'
|
||||
import { CommandClient } from './commandClient.ts'
|
||||
import { CommandClient } from './client.ts'
|
||||
import type { ClientEvents } from '../gateway/handlers/mod.ts'
|
||||
|
||||
export type ExtensionEventCallback = (ext: Extension, ...args: any[]) => any
|
||||
|
||||
|
@ -67,37 +68,35 @@ export class Extension {
|
|||
description?: string
|
||||
/** Extensions's Commands Manager */
|
||||
commands: ExtensionCommands = new ExtensionCommands(this)
|
||||
/** Sub-Prefix to be used for ALL of Extenion's Commands. */
|
||||
/** Sub-Prefix to be used for ALL of Extension's Commands. */
|
||||
subPrefix?: string
|
||||
/** Events registered by this Extension */
|
||||
events: { [name: string]: (...args: any[]) => {} } = {}
|
||||
|
||||
_decoratedCommands?: { [name: string]: Command }
|
||||
_decoratedEvents?: { [name: string]: (...args: any[]) => any }
|
||||
|
||||
constructor(client: CommandClient) {
|
||||
this.client = client
|
||||
if (this._decoratedCommands !== undefined) {
|
||||
Object.entries(this._decoratedCommands).forEach((entry) => {
|
||||
const self = this as any
|
||||
if (self._decoratedCommands !== undefined) {
|
||||
Object.entries(self._decoratedCommands).forEach((entry: any) => {
|
||||
entry[1].extension = this
|
||||
this.commands.add(entry[1])
|
||||
})
|
||||
this._decoratedCommands = undefined
|
||||
self._decoratedCommands = undefined
|
||||
}
|
||||
|
||||
if (
|
||||
this._decoratedEvents !== undefined &&
|
||||
Object.keys(this._decoratedEvents).length !== 0
|
||||
self._decoratedEvents !== undefined &&
|
||||
Object.keys(self._decoratedEvents).length !== 0
|
||||
) {
|
||||
Object.entries(this._decoratedEvents).forEach((entry) => {
|
||||
this.listen(entry[0], entry[1])
|
||||
Object.entries(self._decoratedEvents).forEach((entry: any) => {
|
||||
this.listen(entry[0] as keyof ClientEvents, entry[1].bind(this))
|
||||
})
|
||||
this._decoratedEvents = undefined
|
||||
self._decoratedEvents = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** Listens for an Event through Extension. */
|
||||
listen(event: string, cb: ExtensionEventCallback): boolean {
|
||||
listen(event: keyof ClientEvents, cb: ExtensionEventCallback): boolean {
|
||||
if (this.events[event] !== undefined) return false
|
||||
else {
|
||||
const fn = (...args: any[]): any => {
|
||||
|
@ -152,7 +151,7 @@ export class ExtensionsManager {
|
|||
if (extension === undefined) return false
|
||||
extension.commands.deleteAll()
|
||||
for (const [k, v] of Object.entries(extension.events)) {
|
||||
this.client.removeListener(k, v)
|
||||
this.client.off(k as keyof ClientEvents, v)
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete extension.events[k]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export * from './client.ts'
|
||||
export * from './command.ts'
|
||||
export * from './extension.ts'
|
|
@ -1,9 +0,0 @@
|
|||
export const DISCORD_API_URL: string = 'https://discord.com/api'
|
||||
|
||||
export const DISCORD_GATEWAY_URL: string = 'wss://gateway.discord.gg'
|
||||
|
||||
export const DISCORD_CDN_URL: string = 'https://cdn.discordapp.com'
|
||||
|
||||
export const DISCORD_API_VERSION: number = 8
|
||||
|
||||
export const DISCORD_VOICE_VERSION: number = 4
|
|
@ -0,0 +1,15 @@
|
|||
import { SlashCommand } from '../../interactions/slashCommand.ts'
|
||||
import { ApplicationCommandPayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const applicationCommandCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: ApplicationCommandPayload
|
||||
) => {
|
||||
const guild =
|
||||
d.guild_id === undefined
|
||||
? undefined
|
||||
: await gateway.client.guilds.get(d.guild_id)
|
||||
const cmd = new SlashCommand(gateway.client.slash.commands, d, guild)
|
||||
gateway.client.emit('slashCommandCreate', cmd)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { SlashCommand } from '../../interactions/slashCommand.ts'
|
||||
import { ApplicationCommandPayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const applicationCommandDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: ApplicationCommandPayload
|
||||
) => {
|
||||
const guild =
|
||||
d.guild_id === undefined
|
||||
? undefined
|
||||
: await gateway.client.guilds.get(d.guild_id)
|
||||
const cmd = new SlashCommand(gateway.client.slash.commands, d, guild)
|
||||
gateway.client.emit('slashCommandDelete', cmd)
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { SlashCommand } from '../../interactions/slashCommand.ts'
|
||||
import { ApplicationCommandPayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const applicationCommandUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: ApplicationCommandPayload
|
||||
) => {
|
||||
const guild =
|
||||
d.guild_id === undefined
|
||||
? undefined
|
||||
: await gateway.client.guilds.get(d.guild_id)
|
||||
const cmd = new SlashCommand(gateway.client.slash.commands, d, guild)
|
||||
gateway.client.emit('slashCommandUpdate', cmd)
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import getChannelByType from '../../utils/getChannelByType.ts'
|
||||
import { ChannelPayload, GuildChannelPayload } from '../../types/channel.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import getChannelByType from '../../utils/channel.ts'
|
||||
import type {
|
||||
ChannelPayload,
|
||||
GuildChannelPayload
|
||||
} from '../../types/channel.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
|
||||
export const channelCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { ChannelPayload } from '../../types/channel.ts'
|
||||
|
||||
export const channelDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -7,7 +7,7 @@ export const channelDelete: GatewayEventHandler = async (
|
|||
) => {
|
||||
const channel = await gateway.client.channels.get(d.id)
|
||||
if (channel !== undefined) {
|
||||
await gateway.client.channels.delete(d.id)
|
||||
await gateway.client.channels._delete(d.id)
|
||||
gateway.client.emit('channelDelete', channel)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { ChannelPinsUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { ChannelPinsUpdatePayload } from '../../types/gateway.ts'
|
||||
|
||||
export const channelPinsUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Channel } from '../../structures/channel.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Channel } from '../../structures/channel.ts'
|
||||
import type { ChannelPayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const channelUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildBanAddPayload } from '../../types/gateway.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildBanRemovePayload } from '../../types/gateway.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
import { GuildChannelPayload } from '../../types/channel.ts'
|
||||
|
@ -27,8 +27,13 @@ export const guildCreate: GatewayEventHandler = async (
|
|||
if (d.voice_states !== undefined)
|
||||
await guild.voiceStates.fromPayload(d.voice_states)
|
||||
|
||||
for (const emojiPayload of d.emojis) {
|
||||
if (emojiPayload.id === null) continue
|
||||
await gateway.client.emojis.set(emojiPayload.id, emojiPayload)
|
||||
}
|
||||
|
||||
if (hasGuild === undefined) {
|
||||
// It wasn't lazy load, so emit event
|
||||
gateway.client.emit('guildCreate', guild)
|
||||
}
|
||||
} else gateway.client.emit('guildLoaded', guild)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const guildDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -13,7 +13,8 @@ export const guildDelete: GatewayEventHandler = async (
|
|||
await guild.channels.flush()
|
||||
await guild.roles.flush()
|
||||
await guild.presences.flush()
|
||||
await gateway.client.guilds.delete(d.id)
|
||||
await guild.emojis.flush()
|
||||
await gateway.client.guilds._delete(d.id)
|
||||
|
||||
gateway.client.emit('guildDelete', guild)
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Emoji } from '../../structures/emoji.ts'
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { EmojiPayload } from '../../types/emoji.ts'
|
||||
import { GuildEmojiUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const guildEmojiUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -17,37 +17,45 @@ export const guildEmojiUpdate: GatewayEventHandler = async (
|
|||
const _updated: EmojiPayload[] = []
|
||||
|
||||
for (const raw of d.emojis) {
|
||||
const has = emojis.get(raw.id)
|
||||
const emojiID = (raw.id !== null ? raw.id : raw.name) as string
|
||||
const has = emojis.get(emojiID)
|
||||
if (has === undefined) {
|
||||
await guild.emojis.set(raw.id, raw)
|
||||
const emoji = (await guild.emojis.get(raw.id)) as Emoji
|
||||
await guild.emojis.set(emojiID, raw)
|
||||
const emoji = (await guild.emojis.get(emojiID)) as Emoji
|
||||
added.push(emoji)
|
||||
} else _updated.push(raw)
|
||||
}
|
||||
|
||||
for (const emoji of emojis.values()) {
|
||||
const find = _updated.find((e) => emoji.id === e.id)
|
||||
const emojiID = (emoji.id !== null ? emoji.id : emoji.name) as string
|
||||
const find = _updated.find((e) => {
|
||||
const eID = e.id !== null ? e.id : e.name
|
||||
return emojiID === eID
|
||||
})
|
||||
if (find === undefined) {
|
||||
await guild.emojis.delete(emoji.id)
|
||||
await guild.emojis.delete(emojiID)
|
||||
deleted.push(emoji)
|
||||
} else {
|
||||
const before = (await guild.emojis.get(find.id)) as Emoji
|
||||
await guild.emojis.set(find.id, find)
|
||||
const after = (await guild.emojis.get(find.id)) as Emoji
|
||||
const foundID = (find.id !== null ? find.id : find.name) as string
|
||||
const before = (await guild.emojis.get(foundID)) as Emoji
|
||||
await guild.emojis.set(foundID, find)
|
||||
const after = (await guild.emojis.get(foundID)) as Emoji
|
||||
updated.push({ before, after })
|
||||
}
|
||||
}
|
||||
|
||||
gateway.client.emit('guildEmojisUpdate', guild)
|
||||
|
||||
for (const emoji of deleted) {
|
||||
gateway.client.emit('guildEmojiDelete', guild, emoji)
|
||||
gateway.client.emit('guildEmojiDelete', emoji)
|
||||
}
|
||||
|
||||
for (const emoji of added) {
|
||||
gateway.client.emit('guildEmojiAdd', guild, emoji)
|
||||
gateway.client.emit('guildEmojiAdd', emoji)
|
||||
}
|
||||
|
||||
for (const emoji of updated) {
|
||||
gateway.client.emit('guildEmojiUpdate', guild, emoji.before, emoji.after)
|
||||
gateway.client.emit('guildEmojiUpdate', emoji.before, emoji.after)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildIntegrationsUpdatePayload } from '../../types/gateway.ts'
|
||||
|
||||
|
@ -6,7 +6,6 @@ export const guildIntegrationsUpdate: GatewayEventHandler = async (
|
|||
gateway: Gateway,
|
||||
d: GuildIntegrationsUpdatePayload
|
||||
) => {
|
||||
console.log(d)
|
||||
const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id)
|
||||
if (guild === undefined) return
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberAddPayload } from '../../types/gateway.ts'
|
||||
import { Member } from '../../structures/member.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { GuildMemberRemovePayload } from '../../types/gateway.ts'
|
||||
|
@ -12,7 +12,7 @@ export const guildMemberRemove: GatewayEventHandler = async (
|
|||
if (guild === undefined) return
|
||||
|
||||
const member = await guild.members.get(d.user.id)
|
||||
await guild.members.delete(d.user.id)
|
||||
await guild.members._delete(d.user.id)
|
||||
|
||||
if (member !== undefined) gateway.client.emit('guildMemberRemove', member)
|
||||
else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberUpdatePayload } from '../../types/gateway.ts'
|
||||
import { MemberPayload } from '../../types/guild.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildMemberChunkPayload } from '../../types/gateway.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleCreatePayload } from '../../types/gateway.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleDeletePayload } from '../../types/gateway.ts'
|
||||
|
||||
|
@ -13,6 +13,7 @@ export const guildRoleDelete: GatewayEventHandler = async (
|
|||
const role = await guild.roles.get(d.role_id)
|
||||
// Shouldn't happen either
|
||||
if (role === undefined) return
|
||||
await guild.roles._delete(d.role_id)
|
||||
|
||||
gateway.client.emit('guildRoleDelete', role)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildRoleUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
|
||||
|
|
|
@ -1,29 +1,173 @@
|
|||
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { Member } from '../../structures/member.ts'
|
||||
import { Interaction } from '../../structures/slash.ts'
|
||||
import { GuildTextChannel } from '../../structures/textChannel.ts'
|
||||
import { InteractionPayload } from '../../types/slash.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import {
|
||||
InteractionApplicationCommandResolved,
|
||||
SlashCommandInteraction
|
||||
} from '../../structures/slash.ts'
|
||||
import {
|
||||
Interaction,
|
||||
InteractionChannel
|
||||
} from '../../structures/interactions.ts'
|
||||
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import {
|
||||
InteractionPayload,
|
||||
InteractionType
|
||||
} from '../../types/interactions.ts'
|
||||
import { UserPayload } from '../../types/user.ts'
|
||||
import { Permissions } from '../../utils/permissions.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
import { RolePayload } from '../../types/role.ts'
|
||||
import {
|
||||
InteractionApplicationCommandData,
|
||||
InteractionChannelPayload
|
||||
} from '../../types/slashCommands.ts'
|
||||
import { Message } from '../../structures/message.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const interactionCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: InteractionPayload
|
||||
) => {
|
||||
const guild = await gateway.client.guilds.get(d.guild_id)
|
||||
if (guild === undefined) return
|
||||
// NOTE(DjDeveloperr): Mason once mentioned that channel_id can be optional in Interaction.
|
||||
// This case can be seen in future proofing Interactions, and one he mentioned was
|
||||
// that bots will be able to add custom context menus. In that case, Interaction will not have it.
|
||||
// Ref: https://github.com/discord/discord-api-docs/pull/2568/files#r569025697
|
||||
if (d.channel_id === undefined) return
|
||||
|
||||
await guild.members.set(d.member.user.id, d.member)
|
||||
const member = ((await guild.members.get(
|
||||
d.member.user.id
|
||||
)) as unknown) as Member
|
||||
const guild =
|
||||
d.guild_id === undefined
|
||||
? undefined
|
||||
: (await gateway.client.guilds.get(d.guild_id)) ??
|
||||
new Guild(gateway.client, { unavailable: true, id: d.guild_id } as any)
|
||||
|
||||
const channel =
|
||||
(await gateway.client.channels.get<GuildTextChannel>(d.channel_id)) ??
|
||||
(await gateway.client.channels.fetch<GuildTextChannel>(d.channel_id))
|
||||
if (d.member !== undefined)
|
||||
await guild?.members.set(d.member.user.id, d.member)
|
||||
const member =
|
||||
d.member !== undefined
|
||||
? (await guild?.members.get(d.member.user.id))! ??
|
||||
new Member(
|
||||
gateway.client,
|
||||
d.member!,
|
||||
new User(gateway.client, d.member.user),
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
guild!,
|
||||
new Permissions(d.member.permissions)
|
||||
)
|
||||
: undefined
|
||||
if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user)
|
||||
const dmUser =
|
||||
d.user !== undefined ? await gateway.client.users.get(d.user.id) : undefined
|
||||
|
||||
const interaction = new Interaction(gateway.client, d, {
|
||||
const user = member !== undefined ? member.user : dmUser
|
||||
if (user === undefined) return
|
||||
|
||||
const channel = await gateway.client.channels.get<GuildTextBasedChannel>(
|
||||
d.channel_id
|
||||
)
|
||||
|
||||
const resolved: InteractionApplicationCommandResolved = {
|
||||
users: {},
|
||||
channels: {},
|
||||
members: {},
|
||||
roles: {}
|
||||
}
|
||||
|
||||
if ((d.data as InteractionApplicationCommandData)?.resolved !== undefined) {
|
||||
for (const [id, data] of Object.entries(
|
||||
(d.data as any)?.resolved.users ?? {}
|
||||
)) {
|
||||
await gateway.client.users.set(id, data as UserPayload)
|
||||
resolved.users[id] = ((await gateway.client.users.get(
|
||||
id
|
||||
)) as unknown) as User
|
||||
if (resolved.members[id] !== undefined)
|
||||
resolved.users[id].member = resolved.members[id]
|
||||
}
|
||||
|
||||
for (const [id, data] of Object.entries(
|
||||
(d.data as InteractionApplicationCommandData)?.resolved?.members ?? {}
|
||||
)) {
|
||||
const roles = await guild?.roles.array()
|
||||
let permissions = new Permissions(Permissions.DEFAULT)
|
||||
if (roles !== undefined) {
|
||||
const mRoles = roles.filter(
|
||||
(r) =>
|
||||
((data as any)?.roles?.includes(r.id) as boolean) ||
|
||||
r.id === guild?.id
|
||||
)
|
||||
permissions = new Permissions(mRoles.map((r) => r.permissions))
|
||||
}
|
||||
;(data as any).user = ((d.data as any).resolved.users?.[
|
||||
id
|
||||
] as unknown) as UserPayload
|
||||
resolved.members[id] = new Member(
|
||||
gateway.client,
|
||||
data as any,
|
||||
resolved.users[id],
|
||||
guild as Guild,
|
||||
permissions
|
||||
)
|
||||
}
|
||||
|
||||
for (const [id, data] of Object.entries(
|
||||
(d.data as InteractionApplicationCommandData).resolved?.roles ?? {}
|
||||
)) {
|
||||
if (guild !== undefined) {
|
||||
await guild.roles.set(id, data as RolePayload)
|
||||
resolved.roles[id] = ((await guild.roles.get(id)) as unknown) as Role
|
||||
} else {
|
||||
resolved.roles[id] = new Role(
|
||||
gateway.client,
|
||||
data as any,
|
||||
(guild as unknown) as Guild
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, data] of Object.entries(
|
||||
(d.data as InteractionApplicationCommandData).resolved?.channels ?? {}
|
||||
)) {
|
||||
resolved.channels[id] = new InteractionChannel(
|
||||
gateway.client,
|
||||
data as InteractionChannelPayload
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let message: Message | undefined
|
||||
if (d.message !== undefined) {
|
||||
const channel = (await gateway.client.channels.get<TextChannel>(
|
||||
d.message.channel_id
|
||||
))!
|
||||
message = new Message(
|
||||
gateway.client,
|
||||
d.message,
|
||||
channel,
|
||||
new User(gateway.client, d.message.author)
|
||||
)
|
||||
}
|
||||
|
||||
let interaction
|
||||
if (d.type === InteractionType.APPLICATION_COMMAND) {
|
||||
interaction = new SlashCommandInteraction(gateway.client, d, {
|
||||
member,
|
||||
guild,
|
||||
channel
|
||||
channel,
|
||||
user,
|
||||
resolved
|
||||
})
|
||||
} else {
|
||||
interaction = new Interaction(gateway.client, d, {
|
||||
member,
|
||||
guild,
|
||||
channel,
|
||||
user,
|
||||
message
|
||||
})
|
||||
}
|
||||
|
||||
gateway.client.emit('interactionCreate', interaction)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { InviteCreatePayload } from '../../types/gateway.ts'
|
||||
import { ChannelPayload } from '../../types/channel.ts'
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { InviteDeletePayload } from '../../types/gateway.ts'
|
||||
import { PartialInvitePayload } from '../../types/invite.ts'
|
||||
|
@ -19,7 +19,6 @@ export const inviteDelete: GatewayEventHandler = async (
|
|||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const cachedGuild = await gateway.client.guilds.get(d.guild_id!)
|
||||
|
||||
// TODO(DjDeveloperr): Make it support self-bots and make Guild not always defined
|
||||
if (cachedInvite === undefined) {
|
||||
const uncachedInvite: PartialInvitePayload = {
|
||||
guild: (cachedGuild as unknown) as Guild,
|
||||
|
@ -28,7 +27,7 @@ export const inviteDelete: GatewayEventHandler = async (
|
|||
}
|
||||
return gateway.client.emit('inviteDeleteUncached', uncachedInvite)
|
||||
} else {
|
||||
await guild.invites.delete(d.code)
|
||||
await guild.invites._delete(d.code)
|
||||
gateway.client.emit('inviteDelete', cachedInvite)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Message } from '../../structures/message.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { MessagePayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { MessagePayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageCreate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessageDeletePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessageDeletePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageDelete: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -15,6 +15,6 @@ export const messageDelete: GatewayEventHandler = async (
|
|||
const message = await channel.messages.get(d.id)
|
||||
if (message === undefined)
|
||||
return gateway.client.emit('messageDeleteUncached', d)
|
||||
await channel.messages.delete(d.id)
|
||||
await channel.messages._delete(d.id)
|
||||
gateway.client.emit('messageDelete', message)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { Message } from '../../structures/message.ts'
|
||||
import { GuildTextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessageDeleteBulkPayload } from '../../types/gateway.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import type { MessageDeleteBulkPayload } from '../../types/gateway.ts'
|
||||
import { Collection } from '../../utils/collection.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageDeleteBulk: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: MessageDeleteBulkPayload
|
||||
) => {
|
||||
let channel = await gateway.client.channels.get<GuildTextChannel>(
|
||||
let channel = await gateway.client.channels.get<GuildTextBasedChannel>(
|
||||
d.channel_id
|
||||
)
|
||||
// Fetch the channel if not cached
|
||||
|
@ -16,7 +16,7 @@ export const messageDeleteBulk: GatewayEventHandler = async (
|
|||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
channel = (await gateway.client.channels.fetch(
|
||||
d.channel_id
|
||||
)) as GuildTextChannel
|
||||
)) as GuildTextBasedChannel
|
||||
|
||||
const messages = new Collection<string, Message>()
|
||||
const uncached = new Set<string>()
|
||||
|
@ -25,7 +25,7 @@ export const messageDeleteBulk: GatewayEventHandler = async (
|
|||
if (message === undefined) uncached.add(id)
|
||||
else {
|
||||
messages.set(id, message)
|
||||
await channel.messages.delete(id)
|
||||
await channel.messages._delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionAddPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import { UserPayload } from '../../types/user.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionAddPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import type { UserPayload } from '../../types/user.ts'
|
||||
|
||||
export const messageReactionAdd: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -29,15 +29,16 @@ export const messageReactionAdd: GatewayEventHandler = async (
|
|||
} else return
|
||||
}
|
||||
|
||||
let reaction = await message.reactions.get(d.emoji.id)
|
||||
const emojiID = (d.emoji.id !== null ? d.emoji.id : d.emoji.name) as string
|
||||
let reaction = await message.reactions.get(emojiID)
|
||||
if (reaction === undefined) {
|
||||
await message.reactions.set(d.emoji.id, {
|
||||
await message.reactions.set(emojiID, {
|
||||
count: 1,
|
||||
emoji: d.emoji,
|
||||
me: d.user_id === gateway.client.user?.id
|
||||
})
|
||||
reaction = ((await message.reactions.get(
|
||||
d.emoji.id
|
||||
emojiID
|
||||
)) as unknown) as MessageReaction
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemovePayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemovePayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemove: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -27,10 +27,11 @@ export const messageReactionRemove: GatewayEventHandler = async (
|
|||
} else return
|
||||
}
|
||||
|
||||
const reaction = await message.reactions.get(d.emoji.id)
|
||||
const emojiID = (d.emoji.id !== null ? d.emoji.id : d.emoji.name) as string
|
||||
const reaction = await message.reactions.get(emojiID)
|
||||
if (reaction === undefined) return
|
||||
|
||||
reaction.users.delete(d.user_id)
|
||||
reaction.users._delete(d.user_id)
|
||||
|
||||
gateway.client.emit('messageReactionRemove', reaction, user)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemoveAllPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemoveAllPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemoveAll: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { MessageReactionRemoveEmojiPayload } from '../../types/gateway.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
|
||||
export const messageReactionRemoveEmoji: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -19,7 +19,8 @@ export const messageReactionRemoveEmoji: GatewayEventHandler = async (
|
|||
} else return
|
||||
}
|
||||
|
||||
const reaction = await message.reactions.get(d.emoji.id)
|
||||
const emojiID = (d.emoji.id !== null ? d.emoji.id : d.emoji.name) as string
|
||||
const reaction = await message.reactions.get(emojiID)
|
||||
if (reaction === undefined) return
|
||||
|
||||
await reaction.users.flush()
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Message } from '../../structures/message.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { MessagePayload } from '../../types/channel.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { MessagePayload } from '../../types/channel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const messageUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
import { GatewayEventHandler } from '../index.ts'
|
||||
import { GatewayEvents, TypingStartGuildData } from '../../types/gateway.ts'
|
||||
import type { GatewayEventHandler } from '../mod.ts'
|
||||
import type {
|
||||
GatewayEvents,
|
||||
MessageDeletePayload,
|
||||
TypingStartGuildData
|
||||
} from '../../types/gateway.ts'
|
||||
import { channelCreate } from './channelCreate.ts'
|
||||
import { channelDelete } from './channelDelete.ts'
|
||||
import { channelUpdate } from './channelUpdate.ts'
|
||||
|
@ -27,17 +31,18 @@ import { webhooksUpdate } from './webhooksUpdate.ts'
|
|||
import { messageDeleteBulk } from './messageDeleteBulk.ts'
|
||||
import { userUpdate } from './userUpdate.ts'
|
||||
import { typingStart } from './typingStart.ts'
|
||||
import { GuildTextChannel, TextChannel } from '../../structures/textChannel.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { User } from '../../structures/user.ts'
|
||||
import { Emoji } from '../../structures/emoji.ts'
|
||||
import { Member } from '../../structures/member.ts'
|
||||
import { Role } from '../../structures/role.ts'
|
||||
import { Message } from '../../structures/message.ts'
|
||||
import { Collection } from '../../utils/collection.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { User } from '../../structures/user.ts'
|
||||
import type { Emoji } from '../../structures/emoji.ts'
|
||||
import type { Member } from '../../structures/member.ts'
|
||||
import type { Role } from '../../structures/role.ts'
|
||||
import type { Message } from '../../structures/message.ts'
|
||||
import type { Collection } from '../../utils/collection.ts'
|
||||
import { voiceServerUpdate } from './voiceServerUpdate.ts'
|
||||
import { voiceStateUpdate } from './voiceStateUpdate.ts'
|
||||
import { VoiceState } from '../../structures/voiceState.ts'
|
||||
import type { VoiceState } from '../../structures/voiceState.ts'
|
||||
import { messageReactionAdd } from './messageReactionAdd.ts'
|
||||
import { messageReactionRemove } from './messageReactionRemove.ts'
|
||||
import { messageReactionRemoveAll } from './messageReactionRemoveAll.ts'
|
||||
|
@ -46,20 +51,32 @@ import { guildMembersChunk } from './guildMembersChunk.ts'
|
|||
import { presenceUpdate } from './presenceUpdate.ts'
|
||||
import { inviteCreate } from './inviteCreate.ts'
|
||||
import { inviteDelete } from './inviteDelete.ts'
|
||||
import { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import { Invite } from '../../structures/invite.ts'
|
||||
import { Presence } from '../../structures/presence.ts'
|
||||
import {
|
||||
import type { MessageReaction } from '../../structures/messageReaction.ts'
|
||||
import type { Invite } from '../../structures/invite.ts'
|
||||
import type { Presence } from '../../structures/presence.ts'
|
||||
import type {
|
||||
EveryChannelTypes,
|
||||
EveryTextChannelTypes
|
||||
} from '../../utils/getChannelByType.ts'
|
||||
} from '../../utils/channel.ts'
|
||||
import { interactionCreate } from './interactionCreate.ts'
|
||||
import { Interaction } from '../../structures/slash.ts'
|
||||
import type { Interaction } from '../../structures/interactions.ts'
|
||||
import type { SlashCommandInteraction } from '../../structures/slash.ts'
|
||||
import type { CommandContext } from '../../commands/command.ts'
|
||||
import type { RequestMethods } from '../../rest/types.ts'
|
||||
import type { PartialInvitePayload } from '../../types/invite.ts'
|
||||
import type { GuildChannels } from '../../types/guild.ts'
|
||||
import { applicationCommandCreate } from './applicationCommandCreate.ts'
|
||||
import { applicationCommandDelete } from './applicationCommandDelete.ts'
|
||||
import { applicationCommandUpdate } from './applicationCommandUpdate.ts'
|
||||
import type { SlashCommand } from '../../interactions/slashCommand.ts'
|
||||
|
||||
export const gatewayHandlers: {
|
||||
[eventCode in GatewayEvents]: GatewayEventHandler | undefined
|
||||
} = {
|
||||
READY: ready,
|
||||
APPLICATION_COMMAND_CREATE: applicationCommandCreate,
|
||||
APPLICATION_COMMAND_DELETE: applicationCommandDelete,
|
||||
APPLICATION_COMMAND_UPDATE: applicationCommandUpdate,
|
||||
RECONNECT: reconnect,
|
||||
RESUMED: resume,
|
||||
CHANNEL_CREATE: channelCreate,
|
||||
|
@ -99,238 +116,313 @@ export const gatewayHandlers: {
|
|||
INTERACTION_CREATE: interactionCreate
|
||||
}
|
||||
|
||||
export interface EventTypes {
|
||||
[name: string]: (...args: any[]) => void
|
||||
}
|
||||
|
||||
export interface VoiceServerUpdateData {
|
||||
token: string
|
||||
endpoint: string
|
||||
guild: Guild
|
||||
}
|
||||
|
||||
export interface ClientEvents extends EventTypes {
|
||||
/** All Client Events */
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type ClientEvents = {
|
||||
/** When Client has successfully connected to Discord */
|
||||
ready: () => void
|
||||
/** When a successful reconnect has been made */
|
||||
reconnect: () => void
|
||||
ready: [shard: number]
|
||||
/** When a reconnect was requested by Discord */
|
||||
reconnect: [shard: number]
|
||||
/** When a successful session resume has been done */
|
||||
resumed: () => void
|
||||
resumed: [shard: number]
|
||||
/**
|
||||
* When a new Channel is created
|
||||
* @param channel New Channel object
|
||||
*/
|
||||
channelCreate: (channel: EveryChannelTypes) => void
|
||||
channelCreate: [channel: EveryChannelTypes]
|
||||
/**
|
||||
* When a Channel was deleted
|
||||
* @param channel Channel object which was deleted
|
||||
*/
|
||||
channelDelete: (channel: EveryChannelTypes) => void
|
||||
channelDelete: [channel: EveryChannelTypes]
|
||||
/**
|
||||
* Channel's Pinned Messages were updated
|
||||
* @param before Channel object before update
|
||||
* @param after Channel object after update
|
||||
*/
|
||||
channelPinsUpdate: (
|
||||
channelPinsUpdate: [
|
||||
before: EveryTextChannelTypes,
|
||||
after: EveryTextChannelTypes
|
||||
) => void
|
||||
]
|
||||
/**
|
||||
* A Channel was updated
|
||||
* @param before Channel object before update
|
||||
* @param after Channel object after update
|
||||
*/
|
||||
channelUpdate: (before: EveryChannelTypes, after: EveryChannelTypes) => void
|
||||
channelUpdate: [before: EveryChannelTypes, after: EveryChannelTypes]
|
||||
/**
|
||||
* A User was banned from a Guild
|
||||
* @param guild The Guild from which User was banned
|
||||
* @param user The User who was banned
|
||||
*/
|
||||
guildBanAdd: (guild: Guild, user: User) => void
|
||||
guildBanAdd: [guild: Guild, user: User]
|
||||
/**
|
||||
* A ban from a User in Guild was elevated
|
||||
* @param guild Guild from which ban was removed
|
||||
* @param user User of which ban was elevated
|
||||
*/
|
||||
guildBanRemove: (guild: Guild, user: User) => void
|
||||
guildBanRemove: [guild: Guild, user: User]
|
||||
/**
|
||||
* Client has joined a new Guild.
|
||||
* @param guild The new Guild object
|
||||
*/
|
||||
guildCreate: (guild: Guild) => void
|
||||
guildCreate: [guild: Guild]
|
||||
/**
|
||||
* A Guild was successfully loaded.
|
||||
* @param guild The Guild object
|
||||
*/
|
||||
guildLoaded: [guild: Guild]
|
||||
/**
|
||||
* A Guild in which Client was either deleted, or bot was kicked
|
||||
* @param guild The Guild object
|
||||
*/
|
||||
guildDelete: (guild: Guild) => void
|
||||
guildDelete: [guild: Guild]
|
||||
/**
|
||||
* A new Emoji was added to Guild
|
||||
* @param guild Guild in which Emoji was added
|
||||
* @param emoji The Emoji which was added
|
||||
*/
|
||||
guildEmojiAdd: (guild: Guild, emoji: Emoji) => void
|
||||
guildEmojiAdd: [emoji: Emoji]
|
||||
/**
|
||||
* An Emoji was deleted from Guild
|
||||
* @param guild Guild from which Emoji was deleted
|
||||
* @param emoji Emoji which was deleted
|
||||
*/
|
||||
guildEmojiDelete: (guild: Guild, emoji: Emoji) => void
|
||||
guildEmojiDelete: [emoji: Emoji]
|
||||
/**
|
||||
* An Emoji in a Guild was updated
|
||||
* @param guild Guild in which Emoji was updated
|
||||
* @param before Emoji object before update
|
||||
* @param after Emoji object after update
|
||||
*/
|
||||
guildEmojiUpdate: (guild: Guild, before: Emoji, after: Emoji) => void
|
||||
guildEmojiUpdate: [before: Emoji, after: Emoji]
|
||||
/**
|
||||
* Guild's Integrations were updated
|
||||
* @param guild The Guild object
|
||||
*/
|
||||
guildIntegrationsUpdate: (guild: Guild) => void
|
||||
guildIntegrationsUpdate: [guild: Guild]
|
||||
/**
|
||||
* Guild's Emojis were updated
|
||||
* @param guild The Guild object
|
||||
*/
|
||||
guildEmojisUpdate: [guild: Guild]
|
||||
/**
|
||||
* A new Member has joined a Guild
|
||||
* @param member The Member object
|
||||
*/
|
||||
guildMemberAdd: (member: Member) => void
|
||||
guildMemberAdd: [member: Member]
|
||||
/**
|
||||
* A Guild Member has either left or was kicked from Guild
|
||||
* @param member The Member object
|
||||
*/
|
||||
guildMemberRemove: (member: Member) => void
|
||||
guildMemberRemove: [member: Member]
|
||||
/**
|
||||
* A Guild Member was updated. Nickname changed, role assigned, etc.
|
||||
* @param before Member object before update
|
||||
* @param after Meber object after update
|
||||
* @param after Member object after update
|
||||
*/
|
||||
guildMemberUpdate: (before: Member, after: Member) => void
|
||||
guildMemberUpdate: [before: Member, after: Member]
|
||||
/**
|
||||
* A new Role was created in Guild
|
||||
* @param role The new Role object
|
||||
*/
|
||||
guildRoleCreate: (role: Role) => void
|
||||
guildRoleCreate: [role: Role]
|
||||
/**
|
||||
* A Role was deleted from the Guild
|
||||
* @param role The Role object
|
||||
*/
|
||||
guildRoleDelete: (role: Role) => void
|
||||
guildRoleDelete: [role: Role]
|
||||
/**
|
||||
* A Role was updated in a Guild
|
||||
* @param before Role object before update
|
||||
* @param after Role object after updated
|
||||
*/
|
||||
guildRoleUpdate: (before: Role, after: Role) => void
|
||||
guildRoleUpdate: [before: Role, after: Role]
|
||||
/**
|
||||
* A Guild has been updated. For example name, icon, etc.
|
||||
* @param before Guild object before update
|
||||
* @param after Guild object after update
|
||||
*/
|
||||
guildUpdate: (before: Guild, after: Guild) => void
|
||||
guildUpdate: [before: Guild, after: Guild]
|
||||
/**
|
||||
* A new Message was created (sent)
|
||||
* @param message The new Message object
|
||||
*/
|
||||
messageCreate: (message: Message) => void
|
||||
messageCreate: [message: Message]
|
||||
/**
|
||||
* A Message was deleted.
|
||||
* @param message The Message object
|
||||
*/
|
||||
messageDelete: (message: Message) => void
|
||||
messageDelete: [message: Message]
|
||||
/**
|
||||
* Messages were bulk deleted in a Guild Text Channel
|
||||
* @param channel Channel in which Messages were deleted
|
||||
* @param messages Collection of Messages deleted
|
||||
* @param uncached Set of Messages deleted's IDs which were not cached
|
||||
*/
|
||||
messageDeleteBulk: (
|
||||
channel: GuildTextChannel,
|
||||
messageDeleteBulk: [
|
||||
channel: GuildTextBasedChannel,
|
||||
messages: Collection<string, Message>,
|
||||
uncached: Set<string>
|
||||
) => void
|
||||
]
|
||||
/**
|
||||
* A Message was updated. For example content, embed, etc.
|
||||
* @param before Message object before update
|
||||
* @param after Message object after update
|
||||
*/
|
||||
messageUpdate: (before: Message, after: Message) => void
|
||||
messageUpdate: [before: Message, after: Message]
|
||||
/**
|
||||
* Reaction was added to a Message
|
||||
* @param reaction Reaction object
|
||||
* @param user User who added the reaction
|
||||
*/
|
||||
messageReactionAdd: (reaction: MessageReaction, user: User) => void
|
||||
messageReactionAdd: [reaction: MessageReaction, user: User]
|
||||
/**
|
||||
* Reaction was removed fro a Message
|
||||
* @param reaction Reaction object
|
||||
* @param user User to who removed the reaction
|
||||
*/
|
||||
messageReactionRemove: (reaction: MessageReaction, user: User) => void
|
||||
messageReactionRemove: [reaction: MessageReaction, user: User]
|
||||
/**
|
||||
* All reactions were removed from a Message
|
||||
* @param message Message from which reactions were removed
|
||||
*/
|
||||
messageReactionRemoveAll: (message: Message) => void
|
||||
messageReactionRemoveAll: [message: Message]
|
||||
/**
|
||||
* All reactions of a single Emoji were removed
|
||||
* @param message The Message object
|
||||
* @param emoji The Emoji object
|
||||
*/
|
||||
messageReactionRemoveEmoji: (message: Message, emoji: Emoji) => void
|
||||
messageReactionRemoveEmoji: [message: Message, emoji: Emoji]
|
||||
/**
|
||||
* A User has started typing in a Text Channel
|
||||
* @param user User who started typing
|
||||
* @param channel Channel which user started typing in
|
||||
* @param at Date when user started typing
|
||||
* @param guild Guild which user started typing in (can be undefined)
|
||||
*/
|
||||
typingStart: (
|
||||
typingStart: [
|
||||
user: User,
|
||||
channel: TextChannel,
|
||||
at: Date,
|
||||
guildData?: TypingStartGuildData
|
||||
) => void
|
||||
guild: TypingStartGuildData | undefined
|
||||
]
|
||||
/**
|
||||
* A new Invite was created
|
||||
* @param invite New Invite object
|
||||
*/
|
||||
inviteCreate: (invite: Invite) => void
|
||||
inviteCreate: [invite: Invite]
|
||||
/**
|
||||
* An Invite was deleted
|
||||
* @param invite Invite object
|
||||
*/
|
||||
inviteDelete: (invite: Invite) => void
|
||||
inviteDelete: [invite: Invite]
|
||||
/**
|
||||
* A User was updated. For example username, avatar, etc.
|
||||
* @param before The User object before update
|
||||
* @param after The User object after update
|
||||
*/
|
||||
userUpdate: (before: User, after: User) => void
|
||||
userUpdate: [before: User, after: User]
|
||||
/**
|
||||
* Client has received credentials for establishing connection to Voice Server
|
||||
* @param data Updated voice server object
|
||||
*/
|
||||
voiceServerUpdate: (data: VoiceServerUpdateData) => void
|
||||
voiceServerUpdate: [data: VoiceServerUpdateData]
|
||||
/**
|
||||
* A User has joined a Voice Channel
|
||||
* @param state Added voice state object
|
||||
*/
|
||||
voiceStateAdd: (state: VoiceState) => void
|
||||
voiceStateAdd: [state: VoiceState]
|
||||
/**
|
||||
* A User has left a Voice Channel
|
||||
* @param state Removed voice state object
|
||||
*/
|
||||
voiceStateRemove: (state: VoiceState) => void
|
||||
voiceStateRemove: [state: VoiceState]
|
||||
/**
|
||||
* Voice State of a User has been updated
|
||||
* @param before Voice State object before update
|
||||
* @param after Voice State object after update
|
||||
*/
|
||||
voiceStateUpdate: (state: VoiceState, after: VoiceState) => void
|
||||
voiceStateUpdate: [before: VoiceState, after: VoiceState]
|
||||
/**
|
||||
* A User's presence has been updated
|
||||
* @param presence New Presence
|
||||
*/
|
||||
presenceUpdate: (presence: Presence) => void
|
||||
presenceUpdate: [presence: Presence]
|
||||
/**
|
||||
* Webhooks of a Channel in a Guild has been updated
|
||||
* @param guild Guild in which Webhooks were updated
|
||||
* @param channel Channel of which Webhooks were updated
|
||||
*/
|
||||
webhooksUpdate: (guild: Guild, channel: GuildTextChannel) => void
|
||||
webhooksUpdate: [guild: Guild, channel: GuildTextBasedChannel]
|
||||
|
||||
/**
|
||||
* An Interaction was created
|
||||
* @param interaction Created interaction object
|
||||
*/
|
||||
interactionCreate: (interaction: Interaction) => void
|
||||
interactionCreate: [interaction: Interaction | SlashCommandInteraction]
|
||||
|
||||
/**
|
||||
* When debug message was made
|
||||
* @param message Debug message
|
||||
*/
|
||||
debug: [message: string]
|
||||
|
||||
/**
|
||||
* Raw event which gives you access to raw events DISPATCH'd from Gateway
|
||||
* @param evt Event name string
|
||||
* @param payload Payload JSON of the event
|
||||
*/
|
||||
raw: [evt: string, payload: any]
|
||||
|
||||
/**
|
||||
* An uncached Message was deleted.
|
||||
* @param payload Message Delete Payload
|
||||
*/
|
||||
messageDeleteUncached: [payload: MessageDeletePayload]
|
||||
|
||||
guildMembersChunk: [
|
||||
guild: Guild,
|
||||
info: {
|
||||
chunkIndex: number
|
||||
chunkCount: number
|
||||
members: string[]
|
||||
presences: string[] | undefined
|
||||
}
|
||||
]
|
||||
guildMembersChunked: [guild: Guild, chunks: number]
|
||||
rateLimit: [
|
||||
data: {
|
||||
method: RequestMethods
|
||||
path: string
|
||||
global: boolean
|
||||
timeout: number
|
||||
limit: number
|
||||
}
|
||||
]
|
||||
inviteDeleteUncached: [invite: PartialInvitePayload]
|
||||
voiceStateRemoveUncached: [data: { guild: Guild; member: Member }]
|
||||
userUpdateUncached: [user: User]
|
||||
webhooksUpdateUncached: [guild: Guild, channelID: string]
|
||||
guildRoleUpdateUncached: [role: Role]
|
||||
guildMemberUpdateUncached: [member: Member]
|
||||
guildMemberRemoveUncached: [member: Member]
|
||||
channelUpdateUncached: [channel: GuildChannels]
|
||||
slashCommandCreate: [cmd: SlashCommand]
|
||||
slashCommandUpdate: [cmd: SlashCommand]
|
||||
slashCommandDelete: [cmd: SlashCommand]
|
||||
commandOwnerOnly: [ctx: CommandContext]
|
||||
commandGuildOnly: [ctx: CommandContext]
|
||||
commandDmOnly: [ctx: CommandContext]
|
||||
commandNSFW: [ctx: CommandContext]
|
||||
commandBotMissingPermissions: [ctx: CommandContext, missing: string[]]
|
||||
commandUserMissingPermissions: [ctx: CommandContext, missing: string[]]
|
||||
commandMissingArgs: [ctx: CommandContext]
|
||||
commandUsed: [ctx: CommandContext]
|
||||
commandError: [ctx: CommandContext, err: Error]
|
||||
gatewayError: [err: ErrorEvent, shards: [number, number]]
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import { PresenceUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { PresenceUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const presenceUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { Ready } from '../../types/gateway.ts'
|
||||
import { GuildPayload } from '../../types/guild.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Ready } from '../../types/gateway.ts'
|
||||
import type { GuildPayload } from '../../types/guild.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const ready: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: Ready
|
||||
) => {
|
||||
gateway.client.upSince = new Date()
|
||||
|
||||
if ('application' in d) {
|
||||
gateway.client.applicationID = d.application.id
|
||||
gateway.client.applicationFlags = d.application.flags
|
||||
}
|
||||
|
||||
await gateway.client.guilds.flush()
|
||||
|
||||
await gateway.client.users.set(d.user.id, d.user)
|
||||
|
@ -19,5 +26,5 @@ export const ready: GatewayEventHandler = async (
|
|||
gateway.client.guilds.set(guild.id, guild)
|
||||
})
|
||||
|
||||
gateway.client.emit('ready')
|
||||
gateway.client.emit('ready', gateway.shards?.[0] ?? 0)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const reconnect: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: any
|
||||
) => {
|
||||
gateway.client.emit('reconnect', gateway.shards?.[0] ?? 0)
|
||||
gateway.reconnect()
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { CLIENT_USER } from '../../types/endpoint.ts'
|
||||
import { Resume } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Resume } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const resume: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
d: Resume
|
||||
) => {
|
||||
gateway.debug(`Session Resumed!`)
|
||||
gateway.client.emit('resume')
|
||||
gateway.client.emit('resumed', gateway.shards?.[0] ?? 0)
|
||||
if (gateway.client.user === undefined)
|
||||
gateway.client.user = new User(
|
||||
gateway.client,
|
||||
await gateway.client.rest.get(CLIENT_USER())
|
||||
)
|
||||
gateway.client.emit('ready')
|
||||
gateway.client.emit('ready', gateway.shards?.[0] ?? 0)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Member } from '../../structures/member.ts'
|
||||
import { TextChannel } from '../../structures/textChannel.ts'
|
||||
import { TypingStartPayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { TextChannel } from '../../structures/textChannel.ts'
|
||||
import type { TypingStartPayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
// TODO: Do we need to add uncached events here?
|
||||
export const typingStart: GatewayEventHandler = async (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { User } from '../../structures/user.ts'
|
||||
import { UserPayload } from '../../types/user.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { User } from '../../structures/user.ts'
|
||||
import type { UserPayload } from '../../types/user.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const userUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { VoiceServerUpdatePayload } from '../../types/gateway.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { VoiceServerUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const voiceServerUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Guild } from '../../structures/guild.ts'
|
||||
import { VoiceState } from '../../structures/voiceState.ts'
|
||||
import { MemberPayload } from '../../types/guild.ts'
|
||||
import { VoiceStatePayload } from '../../types/voice.ts'
|
||||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { VoiceState } from '../../structures/voiceState.ts'
|
||||
import type { MemberPayload } from '../../types/guild.ts'
|
||||
import type { VoiceStatePayload } from '../../types/voice.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
|
||||
export const voiceStateUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -24,7 +24,7 @@ export const voiceStateUpdate: GatewayEventHandler = async (
|
|||
return gateway.client.emit('voiceStateRemoveUncached', { guild, member })
|
||||
}
|
||||
// No longer in the channel, so delete
|
||||
await guild.voiceStates.delete(d.user_id)
|
||||
await guild.voiceStates._delete(d.user_id)
|
||||
gateway.client.emit(
|
||||
'voiceStateRemove',
|
||||
(voiceState as unknown) as VoiceState
|
||||
|
@ -33,17 +33,15 @@ export const voiceStateUpdate: GatewayEventHandler = async (
|
|||
}
|
||||
|
||||
await guild.voiceStates.set(d.user_id, d)
|
||||
const newVoiceState = await guild.voiceStates.get(d.user_id)
|
||||
const newVoiceState = (await guild.voiceStates.get(d.user_id))!
|
||||
|
||||
if (d.user_id === gateway.client.user!.id) {
|
||||
gateway.client.voice.emit('voiceStateUpdate', newVoiceState)
|
||||
}
|
||||
|
||||
if (voiceState === undefined) {
|
||||
gateway.client.emit(
|
||||
'voiceStateAdd',
|
||||
(newVoiceState as unknown) as VoiceState
|
||||
)
|
||||
gateway.client.emit('voiceStateAdd', newVoiceState)
|
||||
} else {
|
||||
gateway.client.emit(
|
||||
'voiceStateUpdate',
|
||||
voiceState,
|
||||
(newVoiceState as unknown) as VoiceState
|
||||
)
|
||||
gateway.client.emit('voiceStateUpdate', voiceState, newVoiceState)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||
import { Guild } from '../../structures/guild.ts'
|
||||
import { WebhooksUpdatePayload } from '../../types/gateway.ts'
|
||||
import { GuildTextChannel } from '../../structures/textChannel.ts'
|
||||
import type { Gateway, GatewayEventHandler } from '../mod.ts'
|
||||
import type { Guild } from '../../structures/guild.ts'
|
||||
import type { WebhooksUpdatePayload } from '../../types/gateway.ts'
|
||||
import type { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||
|
||||
export const webhooksUpdate: GatewayEventHandler = async (
|
||||
gateway: Gateway,
|
||||
|
@ -10,9 +10,9 @@ export const webhooksUpdate: GatewayEventHandler = async (
|
|||
const guild: Guild | undefined = await gateway.client.guilds.get(d.guild_id)
|
||||
if (guild === undefined) return
|
||||
|
||||
const channel: GuildTextChannel | undefined = (await guild.channels.get(
|
||||
const channel: GuildTextBasedChannel | undefined = (await guild.channels.get(
|
||||
d.channel_id
|
||||
)) as GuildTextChannel
|
||||
)) as GuildTextBasedChannel
|
||||
if (channel === undefined)
|
||||
gateway.client.emit('webhooksUpdateUncached', guild, d.channel_id)
|
||||
else gateway.client.emit('webhooksUpdate', guild, channel)
|
||||
|
|
|
@ -1,418 +0,0 @@
|
|||
import { unzlib } from 'https://deno.land/x/denoflate@1.1/mod.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import {
|
||||
DISCORD_GATEWAY_URL,
|
||||
DISCORD_API_VERSION
|
||||
} from '../consts/urlsAndVersions.ts'
|
||||
import { GatewayResponse } from '../types/gatewayResponse.ts'
|
||||
import {
|
||||
GatewayOpcodes,
|
||||
GatewayIntents,
|
||||
GatewayCloseCodes,
|
||||
IdentityPayload,
|
||||
StatusUpdatePayload
|
||||
} from '../types/gateway.ts'
|
||||
import { gatewayHandlers } from './handlers/index.ts'
|
||||
import { GATEWAY_BOT } from '../types/endpoint.ts'
|
||||
import { GatewayCache } from '../managers/gatewayCache.ts'
|
||||
import { delay } from '../utils/delay.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
|
||||
export interface RequestMembersOptions {
|
||||
limit?: number
|
||||
presences?: boolean
|
||||
query?: string
|
||||
users?: string[]
|
||||
}
|
||||
|
||||
export interface VoiceStateOptions {
|
||||
mute?: boolean
|
||||
deaf?: boolean
|
||||
}
|
||||
|
||||
export const RECONNECT_REASON = 'harmony-reconnect'
|
||||
|
||||
/**
|
||||
* Handles Discord gateway connection.
|
||||
*
|
||||
* You should not use this and rather use Client class.
|
||||
*/
|
||||
class Gateway {
|
||||
websocket: WebSocket
|
||||
token: string
|
||||
intents: GatewayIntents[]
|
||||
connected = false
|
||||
initialized = false
|
||||
heartbeatInterval = 0
|
||||
heartbeatIntervalID?: number
|
||||
sequenceID?: number
|
||||
lastPingTimestamp = 0
|
||||
sessionID?: string
|
||||
private heartbeatServerResponded = false
|
||||
client: Client
|
||||
cache: GatewayCache
|
||||
private timedIdentify: number | null = null
|
||||
|
||||
constructor(client: Client, token: string, intents: GatewayIntents[]) {
|
||||
this.token = token
|
||||
this.intents = intents
|
||||
this.client = client
|
||||
this.cache = new GatewayCache(client)
|
||||
this.websocket = new WebSocket(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
|
||||
[]
|
||||
)
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
this.websocket.onopen = this.onopen.bind(this)
|
||||
this.websocket.onmessage = this.onmessage.bind(this)
|
||||
this.websocket.onclose = this.onclose.bind(this)
|
||||
this.websocket.onerror = this.onerror.bind(this)
|
||||
}
|
||||
|
||||
private onopen(): void {
|
||||
this.connected = true
|
||||
this.debug('Connected to Gateway!')
|
||||
}
|
||||
|
||||
private async onmessage(event: MessageEvent): Promise<void> {
|
||||
let data = event.data
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = new Uint8Array(data)
|
||||
}
|
||||
if (data instanceof Uint8Array) {
|
||||
data = unzlib(data)
|
||||
data = new TextDecoder('utf-8').decode(data)
|
||||
}
|
||||
|
||||
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
|
||||
|
||||
switch (op) {
|
||||
case GatewayOpcodes.HELLO:
|
||||
this.heartbeatInterval = d.heartbeat_interval
|
||||
this.debug(
|
||||
`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`
|
||||
)
|
||||
|
||||
this.sendHeartbeat()
|
||||
this.heartbeatIntervalID = setInterval(() => {
|
||||
this.heartbeat()
|
||||
}, this.heartbeatInterval)
|
||||
|
||||
if (!this.initialized) {
|
||||
this.initialized = true
|
||||
await this.sendIdentify(this.client.forceNewSession)
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.sendResume()
|
||||
}
|
||||
break
|
||||
|
||||
case GatewayOpcodes.HEARTBEAT_ACK:
|
||||
this.heartbeatServerResponded = true
|
||||
this.client.ping = Date.now() - this.lastPingTimestamp
|
||||
this.debug(
|
||||
`Received Heartbeat Ack. Ping Recognized: ${this.client.ping}ms`
|
||||
)
|
||||
break
|
||||
|
||||
case GatewayOpcodes.INVALID_SESSION:
|
||||
// Because we know this gonna be bool
|
||||
this.debug(
|
||||
`Invalid Session received! Resumeable? ${d === true ? 'Yes' : 'No'}`
|
||||
)
|
||||
if (d !== true) {
|
||||
this.debug(`Session was invalidated, deleting from cache`)
|
||||
await this.cache.delete('session_id')
|
||||
await this.cache.delete('seq')
|
||||
this.sessionID = undefined
|
||||
this.sequenceID = undefined
|
||||
}
|
||||
this.timedIdentify = setTimeout(async () => {
|
||||
this.timedIdentify = null
|
||||
await this.sendIdentify(!(d as boolean))
|
||||
}, 5000)
|
||||
break
|
||||
|
||||
case GatewayOpcodes.DISPATCH: {
|
||||
this.heartbeatServerResponded = true
|
||||
if (s !== null) {
|
||||
this.sequenceID = s
|
||||
await this.cache.set('seq', s)
|
||||
}
|
||||
if (t !== null && t !== undefined) {
|
||||
this.client.emit('raw', t, d)
|
||||
|
||||
const handler = gatewayHandlers[t]
|
||||
|
||||
if (handler !== undefined) {
|
||||
handler(this, d)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case GatewayOpcodes.RESUME: {
|
||||
// this.token = d.token
|
||||
this.sessionID = d.session_id
|
||||
this.sequenceID = d.seq
|
||||
await this.cache.set('seq', d.seq)
|
||||
await this.cache.set('session_id', this.sessionID)
|
||||
break
|
||||
}
|
||||
case GatewayOpcodes.RECONNECT: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private async onclose(event: CloseEvent): Promise<void> {
|
||||
if (event.reason === RECONNECT_REASON) return
|
||||
this.debug(`Connection Closed with code: ${event.code}`)
|
||||
|
||||
if (event.code === GatewayCloseCodes.UNKNOWN_ERROR) {
|
||||
this.debug('API has encountered Unknown Error. Reconnecting...')
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
} else if (event.code === GatewayCloseCodes.UNKNOWN_OPCODE) {
|
||||
throw new Error("Unknown OP Code was sent. This shouldn't happen!")
|
||||
} else if (event.code === GatewayCloseCodes.DECODE_ERROR) {
|
||||
throw new Error("Invalid Payload was sent. This shouldn't happen!")
|
||||
} else if (event.code === GatewayCloseCodes.NOT_AUTHENTICATED) {
|
||||
throw new Error('Not Authorized: Payload was sent before Identifying.')
|
||||
} else if (event.code === GatewayCloseCodes.AUTHENTICATION_FAILED) {
|
||||
throw new Error('Invalid Token provided!')
|
||||
} else if (event.code === GatewayCloseCodes.INVALID_SEQ) {
|
||||
this.debug('Invalid Seq was sent. Reconnecting.')
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
} else if (event.code === GatewayCloseCodes.RATE_LIMITED) {
|
||||
throw new Error("You're ratelimited. Calm down.")
|
||||
} else if (event.code === GatewayCloseCodes.SESSION_TIMED_OUT) {
|
||||
this.debug('Session Timeout. Reconnecting.')
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect(true)
|
||||
} else if (event.code === GatewayCloseCodes.INVALID_SHARD) {
|
||||
this.debug('Invalid Shard was sent. Reconnecting.')
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
} else if (event.code === GatewayCloseCodes.SHARDING_REQUIRED) {
|
||||
throw new Error("Couldn't connect. Sharding is required!")
|
||||
} else if (event.code === GatewayCloseCodes.INVALID_API_VERSION) {
|
||||
throw new Error("Invalid API Version was used. This shouldn't happen!")
|
||||
} else if (event.code === GatewayCloseCodes.INVALID_INTENTS) {
|
||||
throw new Error('Invalid Intents')
|
||||
} else if (event.code === GatewayCloseCodes.DISALLOWED_INTENTS) {
|
||||
throw new Error("Given Intents aren't allowed")
|
||||
} else {
|
||||
this.debug(
|
||||
'Unknown Close code, probably connection error. Reconnecting in 5s.'
|
||||
)
|
||||
if (this.timedIdentify !== null) {
|
||||
clearTimeout(this.timedIdentify)
|
||||
this.debug('Timed Identify found. Cleared timeout.')
|
||||
}
|
||||
await delay(5000)
|
||||
await this.reconnect(true)
|
||||
}
|
||||
}
|
||||
|
||||
private onerror(event: Event | ErrorEvent): void {
|
||||
const eventError = event as ErrorEvent
|
||||
console.log(eventError)
|
||||
}
|
||||
|
||||
private async sendIdentify(forceNewSession?: boolean): Promise<void> {
|
||||
this.debug('Fetching /gateway/bot...')
|
||||
const info = await this.client.rest.get(GATEWAY_BOT())
|
||||
if (info.session_start_limit.remaining === 0)
|
||||
throw new Error(
|
||||
`Session Limit Reached. Retry After ${info.session_start_limit.reset_after}ms`
|
||||
)
|
||||
this.debug(`Recommended Shards: ${info.shards}`)
|
||||
this.debug('=== Session Limit Info ===')
|
||||
this.debug(
|
||||
`Remaining: ${info.session_start_limit.remaining}/${info.session_start_limit.total}`
|
||||
)
|
||||
this.debug(`Reset After: ${info.session_start_limit.reset_after}ms`)
|
||||
|
||||
if (forceNewSession === undefined || !forceNewSession) {
|
||||
const sessionIDCached = await this.cache.get('session_id')
|
||||
if (sessionIDCached !== undefined) {
|
||||
this.debug(`Found Cached SessionID: ${sessionIDCached}`)
|
||||
this.sessionID = sessionIDCached
|
||||
return await this.sendResume()
|
||||
}
|
||||
}
|
||||
|
||||
const payload: IdentityPayload = {
|
||||
token: this.token,
|
||||
properties: {
|
||||
$os: this.client.clientProperties.os ?? Deno.build.os,
|
||||
$browser: this.client.clientProperties.browser ?? 'harmony',
|
||||
$device: this.client.clientProperties.device ?? 'harmony'
|
||||
},
|
||||
compress: true,
|
||||
shard: [0, 1], // TODO: Make sharding possible
|
||||
intents: this.intents.reduce(
|
||||
(previous, current) => previous | current,
|
||||
0
|
||||
),
|
||||
presence: this.client.presence.create()
|
||||
}
|
||||
|
||||
this.debug('Sending Identify payload...')
|
||||
this.send({
|
||||
op: GatewayOpcodes.IDENTIFY,
|
||||
d: payload
|
||||
})
|
||||
}
|
||||
|
||||
private async sendResume(): Promise<void> {
|
||||
if (this.sessionID === undefined) {
|
||||
this.sessionID = await this.cache.get('session_id')
|
||||
if (this.sessionID === undefined) return await this.sendIdentify()
|
||||
}
|
||||
this.debug(`Preparing to resume with Session: ${this.sessionID}`)
|
||||
if (this.sequenceID === undefined) {
|
||||
const cached = await this.cache.get('seq')
|
||||
if (cached !== undefined)
|
||||
this.sequenceID = typeof cached === 'string' ? parseInt(cached) : cached
|
||||
}
|
||||
const resumePayload = {
|
||||
op: GatewayOpcodes.RESUME,
|
||||
d: {
|
||||
token: this.token,
|
||||
session_id: this.sessionID,
|
||||
seq: this.sequenceID ?? null
|
||||
}
|
||||
}
|
||||
this.debug('Sending Resume payload...')
|
||||
this.send(resumePayload)
|
||||
}
|
||||
|
||||
requestMembers(guild: string, options: RequestMembersOptions = {}): string {
|
||||
if (options.query !== undefined && options.limit === undefined)
|
||||
throw new Error(
|
||||
'Missing limit property when specifying query for Requesting Members!'
|
||||
)
|
||||
const nonce = `${guild}_${new Date().getTime()}`
|
||||
this.send({
|
||||
op: GatewayOpcodes.REQUEST_GUILD_MEMBERS,
|
||||
d: {
|
||||
guild_id: guild,
|
||||
query: options.query,
|
||||
limit: options.limit,
|
||||
presences: options.presences,
|
||||
user_ids: options.users,
|
||||
nonce
|
||||
}
|
||||
})
|
||||
return nonce
|
||||
}
|
||||
|
||||
updateVoiceState(
|
||||
guild: Guild | string,
|
||||
channel?: VoiceChannel | string,
|
||||
voiceOptions: VoiceStateOptions = {}
|
||||
): void {
|
||||
this.send({
|
||||
op: GatewayOpcodes.VOICE_STATE_UPDATE,
|
||||
d: {
|
||||
guild_id: typeof guild === 'string' ? guild : guild.id,
|
||||
channel_id:
|
||||
channel === undefined
|
||||
? null
|
||||
: typeof channel === 'string'
|
||||
? channel
|
||||
: channel?.id,
|
||||
self_mute: voiceOptions.mute === undefined ? false : voiceOptions.mute,
|
||||
self_deaf: voiceOptions.deaf === undefined ? false : voiceOptions.deaf
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
this.client.debug('Gateway', msg)
|
||||
}
|
||||
|
||||
async reconnect(forceNew?: boolean): Promise<void> {
|
||||
clearInterval(this.heartbeatIntervalID)
|
||||
if (forceNew === true) {
|
||||
await this.cache.delete('session_id')
|
||||
await this.cache.delete('seq')
|
||||
}
|
||||
this.close(1000, RECONNECT_REASON)
|
||||
this.initWebsocket()
|
||||
}
|
||||
|
||||
initWebsocket(): void {
|
||||
this.debug('Initializing WebSocket...')
|
||||
this.websocket = new WebSocket(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`${DISCORD_GATEWAY_URL}/?v=${DISCORD_API_VERSION}&encoding=json`,
|
||||
[]
|
||||
)
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
this.websocket.onopen = this.onopen.bind(this)
|
||||
this.websocket.onmessage = this.onmessage.bind(this)
|
||||
this.websocket.onclose = this.onclose.bind(this)
|
||||
this.websocket.onerror = this.onerror.bind(this)
|
||||
}
|
||||
|
||||
close(code: number = 1000, reason?: string): void {
|
||||
return this.websocket.close(code, reason)
|
||||
}
|
||||
|
||||
send(data: GatewayResponse): boolean {
|
||||
if (this.websocket.readyState !== this.websocket.OPEN) return false
|
||||
this.websocket.send(
|
||||
JSON.stringify({
|
||||
op: data.op,
|
||||
d: data.d,
|
||||
s: typeof data.s === 'number' ? data.s : null,
|
||||
t: data.t === undefined ? null : data.t
|
||||
})
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
sendPresence(data: StatusUpdatePayload): void {
|
||||
this.send({
|
||||
op: GatewayOpcodes.PRESENCE_UPDATE,
|
||||
d: data
|
||||
})
|
||||
}
|
||||
|
||||
sendHeartbeat(): void {
|
||||
const payload = {
|
||||
op: GatewayOpcodes.HEARTBEAT,
|
||||
d: this.sequenceID ?? null
|
||||
}
|
||||
|
||||
this.send(payload)
|
||||
this.lastPingTimestamp = Date.now()
|
||||
}
|
||||
|
||||
heartbeat(): void {
|
||||
if (this.heartbeatServerResponded) {
|
||||
this.heartbeatServerResponded = false
|
||||
} else {
|
||||
this.debug('Found dead connection, reconnecting...')
|
||||
clearInterval(this.heartbeatIntervalID)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
return
|
||||
}
|
||||
|
||||
this.sendHeartbeat()
|
||||
}
|
||||
}
|
||||
|
||||
export type GatewayEventHandler = (gateway: Gateway, d: any) => void
|
||||
|
||||
export { Gateway }
|
|
@ -0,0 +1,474 @@
|
|||
import { unzlib } from '../../deps.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { GatewayResponse } from '../types/gatewayResponse.ts'
|
||||
import {
|
||||
GatewayOpcodes,
|
||||
GatewayCloseCodes,
|
||||
IdentityPayload,
|
||||
StatusUpdatePayload,
|
||||
GatewayEvents
|
||||
} from '../types/gateway.ts'
|
||||
import { gatewayHandlers } from './handlers/mod.ts'
|
||||
import { GatewayCache } from '../managers/gatewayCache.ts'
|
||||
import { delay } from '../utils/delay.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { decodeText } from '../utils/encoding.ts'
|
||||
import { Constants } from '../types/constants.ts'
|
||||
|
||||
export interface RequestMembersOptions {
|
||||
limit?: number
|
||||
presences?: boolean
|
||||
query?: string
|
||||
users?: string[]
|
||||
}
|
||||
|
||||
export interface VoiceStateOptions {
|
||||
mute?: boolean
|
||||
deaf?: boolean
|
||||
}
|
||||
|
||||
export const RECONNECT_REASON = 'harmony-reconnect'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type GatewayTypedEvents = {
|
||||
[name in GatewayEvents]: [any]
|
||||
} & {
|
||||
connect: []
|
||||
ping: [number]
|
||||
resume: []
|
||||
reconnectRequired: []
|
||||
close: [number, string]
|
||||
error: [Error, ErrorEvent]
|
||||
sentIdentify: []
|
||||
sentResume: []
|
||||
reconnecting: []
|
||||
init: []
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles Discord Gateway connection.
|
||||
*
|
||||
* You should not use this and rather use Client class.
|
||||
*/
|
||||
export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||
websocket?: WebSocket
|
||||
connected = false
|
||||
initialized = false
|
||||
heartbeatInterval = 0
|
||||
heartbeatIntervalID?: number
|
||||
sequenceID?: number
|
||||
lastPingTimestamp = 0
|
||||
sessionID?: string
|
||||
private heartbeatServerResponded = false
|
||||
client!: Client
|
||||
cache: GatewayCache
|
||||
private timedIdentify: number | null = null
|
||||
shards?: number[]
|
||||
ping: number = 0
|
||||
|
||||
constructor(client: Client, shards?: number[]) {
|
||||
super()
|
||||
Object.defineProperty(this, 'client', { value: client, enumerable: false })
|
||||
this.cache = new GatewayCache(client)
|
||||
this.shards = shards
|
||||
}
|
||||
|
||||
private onopen(): void {
|
||||
this.connected = true
|
||||
this.debug('Connected to Gateway!')
|
||||
this.emit('connect')
|
||||
}
|
||||
|
||||
private async onmessage(event: MessageEvent): Promise<void> {
|
||||
let data = event.data
|
||||
if (data instanceof ArrayBuffer) {
|
||||
data = new Uint8Array(data)
|
||||
}
|
||||
if (data instanceof Uint8Array) {
|
||||
data = unzlib(data)
|
||||
data = decodeText(data)
|
||||
}
|
||||
|
||||
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
|
||||
|
||||
switch (op) {
|
||||
case GatewayOpcodes.HELLO:
|
||||
this.heartbeatInterval = d.heartbeat_interval
|
||||
this.debug(
|
||||
`Received HELLO. Heartbeat Interval: ${this.heartbeatInterval}`
|
||||
)
|
||||
|
||||
this.sendHeartbeat()
|
||||
this.heartbeatIntervalID = setInterval(() => {
|
||||
this.heartbeat()
|
||||
}, this.heartbeatInterval)
|
||||
|
||||
if (!this.initialized) {
|
||||
this.initialized = true
|
||||
this.enqueueIdentify(this.client.forceNewSession)
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.sendResume()
|
||||
}
|
||||
break
|
||||
|
||||
case GatewayOpcodes.HEARTBEAT_ACK:
|
||||
this.heartbeatServerResponded = true
|
||||
this.ping = Date.now() - this.lastPingTimestamp
|
||||
this.emit('ping', this.ping)
|
||||
this.debug(`Received Heartbeat Ack. Ping Recognized: ${this.ping}ms`)
|
||||
break
|
||||
|
||||
case GatewayOpcodes.INVALID_SESSION:
|
||||
// Because we know this gonna be bool
|
||||
this.debug(
|
||||
`Invalid Session received! Resumeable? ${d === true ? 'Yes' : 'No'}`
|
||||
)
|
||||
if (d !== true) {
|
||||
this.debug(`Session was invalidated, deleting from cache`)
|
||||
await this.cache.delete(`session_id_${this.shards?.join('-') ?? '0'}`)
|
||||
await this.cache.delete(`seq_${this.shards?.join('-') ?? '0'}`)
|
||||
this.sessionID = undefined
|
||||
this.sequenceID = undefined
|
||||
}
|
||||
this.timedIdentify = setTimeout(async () => {
|
||||
this.timedIdentify = null
|
||||
this.enqueueIdentify(!(d as boolean))
|
||||
}, 5000)
|
||||
break
|
||||
|
||||
case GatewayOpcodes.DISPATCH: {
|
||||
this.heartbeatServerResponded = true
|
||||
if (s !== null) {
|
||||
this.sequenceID = s
|
||||
await this.cache.set(`seq_${this.shards?.join('-') ?? '0'}`, s)
|
||||
}
|
||||
if (t !== null && t !== undefined) {
|
||||
this.emit(t as any, d)
|
||||
this.client.emit('raw', t, d)
|
||||
|
||||
const handler = gatewayHandlers[t]
|
||||
|
||||
if (handler !== undefined && d !== null) {
|
||||
handler(this, d)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case GatewayOpcodes.RESUME: {
|
||||
// this.token = d.token
|
||||
this.sessionID = d.session_id
|
||||
this.sequenceID = d.seq
|
||||
await this.cache.set(`seq_${this.shards?.join('-') ?? '0'}`, d.seq)
|
||||
await this.cache.set(
|
||||
`session_id_${this.shards?.join('-') ?? '0'}`,
|
||||
this.sessionID
|
||||
)
|
||||
this.emit('resume')
|
||||
break
|
||||
}
|
||||
case GatewayOpcodes.RECONNECT: {
|
||||
this.emit('reconnectRequired')
|
||||
this.debug('Received OpCode RECONNECT')
|
||||
await this.reconnect()
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private async onclose({ reason, code }: CloseEvent): Promise<void> {
|
||||
if (reason === RECONNECT_REASON) return
|
||||
this.emit('close', code, reason)
|
||||
this.debug(`Connection Closed with code: ${code}`)
|
||||
|
||||
switch (code) {
|
||||
case GatewayCloseCodes.UNKNOWN_ERROR:
|
||||
this.debug('API has encountered Unknown Error. Reconnecting...')
|
||||
await this.reconnect()
|
||||
break
|
||||
case GatewayCloseCodes.UNKNOWN_OPCODE:
|
||||
throw new Error(
|
||||
"Invalid OP Code or Payload was sent. This shouldn't happen!"
|
||||
)
|
||||
case GatewayCloseCodes.DECODE_ERROR:
|
||||
throw new Error("Invalid Payload was sent. This shouldn't happen!")
|
||||
case GatewayCloseCodes.NOT_AUTHENTICATED:
|
||||
throw new Error('Not Authorized: Payload was sent before Identifying.')
|
||||
case GatewayCloseCodes.AUTHENTICATION_FAILED:
|
||||
throw new Error('Invalid Token provided!')
|
||||
case GatewayCloseCodes.INVALID_SEQ:
|
||||
this.debug('Invalid Seq was sent. Reconnecting.')
|
||||
await this.reconnect()
|
||||
break
|
||||
case GatewayCloseCodes.RATE_LIMITED:
|
||||
throw new Error("You're ratelimited. Calm down.")
|
||||
case GatewayCloseCodes.SESSION_TIMED_OUT:
|
||||
this.debug('Session Timeout. Reconnecting.')
|
||||
await this.reconnect(true)
|
||||
break
|
||||
case GatewayCloseCodes.INVALID_SHARD:
|
||||
this.debug('Invalid Shard was sent. Reconnecting.')
|
||||
await this.reconnect()
|
||||
break
|
||||
case GatewayCloseCodes.SHARDING_REQUIRED:
|
||||
throw new Error("Couldn't connect. Sharding is required!")
|
||||
case GatewayCloseCodes.INVALID_API_VERSION:
|
||||
throw new Error("Invalid API Version was used. This shouldn't happen!")
|
||||
case GatewayCloseCodes.INVALID_INTENTS:
|
||||
throw new Error('Invalid Intents')
|
||||
case GatewayCloseCodes.DISALLOWED_INTENTS:
|
||||
throw new Error("Given Intents aren't allowed")
|
||||
default:
|
||||
this.debug(
|
||||
'Unknown Close code, probably connection error. Reconnecting in 5s.'
|
||||
)
|
||||
|
||||
if (this.timedIdentify !== null) {
|
||||
clearTimeout(this.timedIdentify)
|
||||
this.debug('Timed Identify found. Cleared timeout.')
|
||||
}
|
||||
|
||||
await delay(5000)
|
||||
await this.reconnect(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private async onerror(event: ErrorEvent): Promise<void> {
|
||||
const error = new Error(
|
||||
Deno.inspect({
|
||||
message: event.message,
|
||||
error: event.error,
|
||||
type: event.type,
|
||||
target: event.target
|
||||
})
|
||||
)
|
||||
error.name = 'ErrorEvent'
|
||||
console.log(error)
|
||||
this.emit('error', error, event)
|
||||
this.client.emit('gatewayError', event, this.shards)
|
||||
}
|
||||
|
||||
private enqueueIdentify(forceNew?: boolean): void {
|
||||
this.client.shards.enqueueIdentify(
|
||||
async () => await this.sendIdentify(forceNew)
|
||||
)
|
||||
}
|
||||
|
||||
private async sendIdentify(forceNewSession?: boolean): Promise<void> {
|
||||
if (typeof this.client.token !== 'string')
|
||||
throw new Error('Token not specified')
|
||||
if (typeof this.client.intents !== 'object')
|
||||
throw new Error('Intents not specified')
|
||||
|
||||
if (forceNewSession === undefined || !forceNewSession) {
|
||||
const sessionIDCached = await this.cache.get(
|
||||
`session_id_${this.shards?.join('-') ?? '0'}`
|
||||
)
|
||||
if (sessionIDCached !== undefined) {
|
||||
this.debug(`Found Cached SessionID: ${sessionIDCached}`)
|
||||
this.sessionID = sessionIDCached
|
||||
return await this.sendResume()
|
||||
}
|
||||
}
|
||||
|
||||
const payload: IdentityPayload = {
|
||||
token: this.client.token,
|
||||
properties: {
|
||||
$os: this.client.clientProperties.os ?? Deno.build.os,
|
||||
$browser: this.client.clientProperties.browser ?? 'harmony',
|
||||
$device: this.client.clientProperties.device ?? 'harmony'
|
||||
},
|
||||
compress: true,
|
||||
shard:
|
||||
this.shards === undefined
|
||||
? [0, 1]
|
||||
: [this.shards[0] ?? 0, this.shards[1] ?? 1],
|
||||
intents: this.client.intents.reduce(
|
||||
(previous, current) => previous | current,
|
||||
0
|
||||
),
|
||||
presence: this.client.presence.create()
|
||||
}
|
||||
|
||||
this.debug('Sending Identify payload...')
|
||||
this.emit('sentIdentify')
|
||||
this.send({
|
||||
op: GatewayOpcodes.IDENTIFY,
|
||||
d: payload
|
||||
})
|
||||
}
|
||||
|
||||
private async sendResume(): Promise<void> {
|
||||
if (typeof this.client.token !== 'string')
|
||||
throw new Error('Token not specified')
|
||||
|
||||
if (this.sessionID === undefined) {
|
||||
this.sessionID = await this.cache.get(
|
||||
`session_id_${this.shards?.join('-') ?? '0'}`
|
||||
)
|
||||
if (this.sessionID === undefined) return this.enqueueIdentify()
|
||||
}
|
||||
this.debug(`Preparing to resume with Session: ${this.sessionID}`)
|
||||
if (this.sequenceID === undefined) {
|
||||
const cached = await this.cache.get(
|
||||
`seq_${this.shards?.join('-') ?? '0'}`
|
||||
)
|
||||
if (cached !== undefined)
|
||||
this.sequenceID = typeof cached === 'string' ? parseInt(cached) : cached
|
||||
}
|
||||
const resumePayload = {
|
||||
op: GatewayOpcodes.RESUME,
|
||||
d: {
|
||||
token: this.client.token,
|
||||
session_id: this.sessionID,
|
||||
seq: this.sequenceID ?? null
|
||||
}
|
||||
}
|
||||
this.emit('sentResume')
|
||||
this.debug('Sending Resume payload...')
|
||||
this.send(resumePayload)
|
||||
}
|
||||
|
||||
requestMembers(guild: string, options: RequestMembersOptions = {}): string {
|
||||
if (options.query !== undefined && options.limit === undefined)
|
||||
throw new Error(
|
||||
'Missing limit property when specifying query for Requesting Members!'
|
||||
)
|
||||
const nonce = `${guild}_${new Date().getTime()}`
|
||||
this.send({
|
||||
op: GatewayOpcodes.REQUEST_GUILD_MEMBERS,
|
||||
d: {
|
||||
guild_id: guild,
|
||||
query: options.query ?? '',
|
||||
limit: options.limit ?? 0,
|
||||
presences: options.presences,
|
||||
user_ids: options.users,
|
||||
nonce
|
||||
}
|
||||
})
|
||||
return nonce
|
||||
}
|
||||
|
||||
updateVoiceState(
|
||||
guild: Guild | string,
|
||||
channel?: VoiceChannel | string,
|
||||
voiceOptions: VoiceStateOptions = {}
|
||||
): void {
|
||||
this.send({
|
||||
op: GatewayOpcodes.VOICE_STATE_UPDATE,
|
||||
d: {
|
||||
guild_id: typeof guild === 'string' ? guild : guild.id,
|
||||
channel_id:
|
||||
channel === undefined
|
||||
? null
|
||||
: typeof channel === 'string'
|
||||
? channel
|
||||
: channel?.id,
|
||||
self_mute:
|
||||
channel === undefined
|
||||
? false
|
||||
: voiceOptions.mute === undefined
|
||||
? false
|
||||
: voiceOptions.mute,
|
||||
self_deaf:
|
||||
channel === undefined
|
||||
? false
|
||||
: voiceOptions.deaf === undefined
|
||||
? false
|
||||
: voiceOptions.deaf
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
this.client.debug('Gateway', msg)
|
||||
}
|
||||
|
||||
async reconnect(forceNew?: boolean): Promise<void> {
|
||||
this.emit('reconnecting')
|
||||
this.debug('Reconnecting... (force new: ' + String(forceNew) + ')')
|
||||
|
||||
clearInterval(this.heartbeatIntervalID)
|
||||
if (forceNew === true) {
|
||||
await this.cache.delete(`session_id_${this.shards?.join('-') ?? '0'}`)
|
||||
await this.cache.delete(`seq_${this.shards?.join('-') ?? '0'}`)
|
||||
}
|
||||
|
||||
this.closeGateway(1000, RECONNECT_REASON)
|
||||
this.initWebsocket()
|
||||
}
|
||||
|
||||
initWebsocket(): void {
|
||||
this.emit('init')
|
||||
this.debug('Initializing WebSocket...')
|
||||
this.websocket = new WebSocket(
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||
`${Constants.DISCORD_GATEWAY_URL}/?v=${Constants.DISCORD_API_VERSION}&encoding=json`,
|
||||
[]
|
||||
)
|
||||
this.websocket.binaryType = 'arraybuffer'
|
||||
this.websocket.onopen = this.onopen.bind(this)
|
||||
this.websocket.onmessage = this.onmessage.bind(this)
|
||||
this.websocket.onclose = this.onclose.bind(this)
|
||||
this.websocket.onerror = this.onerror.bind(this) as any
|
||||
}
|
||||
|
||||
closeGateway(code: number = 1000, reason?: string): void {
|
||||
this.debug(
|
||||
`Closing with code ${code}${
|
||||
reason !== undefined && reason !== '' ? ` and reason ${reason}` : ''
|
||||
}`
|
||||
)
|
||||
return this.websocket?.close(code, reason)
|
||||
}
|
||||
|
||||
send(data: GatewayResponse): boolean {
|
||||
if (this.websocket?.readyState !== this.websocket?.OPEN) return false
|
||||
const packet = JSON.stringify({
|
||||
op: data.op,
|
||||
d: data.d,
|
||||
s: typeof data.s === 'number' ? data.s : null,
|
||||
t: data.t === undefined ? null : data.t
|
||||
})
|
||||
this.websocket?.send(packet)
|
||||
return true
|
||||
}
|
||||
|
||||
sendPresence(data: StatusUpdatePayload): void {
|
||||
this.send({
|
||||
op: GatewayOpcodes.PRESENCE_UPDATE,
|
||||
d: data
|
||||
})
|
||||
}
|
||||
|
||||
sendHeartbeat(): void {
|
||||
const payload = {
|
||||
op: GatewayOpcodes.HEARTBEAT,
|
||||
d: this.sequenceID ?? null
|
||||
}
|
||||
|
||||
this.send(payload)
|
||||
this.lastPingTimestamp = Date.now()
|
||||
}
|
||||
|
||||
heartbeat(): void {
|
||||
if (this.heartbeatServerResponded) {
|
||||
this.heartbeatServerResponded = false
|
||||
} else {
|
||||
this.debug('Found dead connection, reconnecting...')
|
||||
clearInterval(this.heartbeatIntervalID)
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.reconnect()
|
||||
return
|
||||
}
|
||||
|
||||
this.sendHeartbeat()
|
||||
}
|
||||
}
|
||||
|
||||
export type GatewayEventHandler = (gateway: Gateway, d: any) => void
|
|
@ -0,0 +1,3 @@
|
|||
export * from './slashClient.ts'
|
||||
export * from './slashModule.ts'
|
||||
export * from './slashCommand.ts'
|
|
@ -0,0 +1,477 @@
|
|||
import {
|
||||
SlashCommandInteraction,
|
||||
InteractionApplicationCommandResolved
|
||||
} from '../structures/slash.ts'
|
||||
import { Interaction } from '../structures/interactions.ts'
|
||||
import {
|
||||
InteractionPayload,
|
||||
InteractionResponsePayload,
|
||||
InteractionType
|
||||
} from '../types/interactions.ts'
|
||||
import { SlashCommandOptionType } from '../types/slashCommands.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { RESTManager } from '../rest/mod.ts'
|
||||
import { SlashModule } from './slashModule.ts'
|
||||
import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||
import { encodeText, decodeText } from '../utils/encoding.ts'
|
||||
import { SlashCommandsManager } from './slashCommand.ts'
|
||||
|
||||
export type SlashCommandHandlerCallback = (
|
||||
interaction: SlashCommandInteraction
|
||||
) => unknown
|
||||
export interface SlashCommandHandler {
|
||||
name: string
|
||||
guild?: string
|
||||
parent?: string
|
||||
group?: string
|
||||
handler: SlashCommandHandlerCallback
|
||||
}
|
||||
|
||||
/** Options for SlashClient */
|
||||
export interface SlashOptions {
|
||||
id?: string | (() => string)
|
||||
client?: Client
|
||||
enabled?: boolean
|
||||
token?: string
|
||||
rest?: RESTManager
|
||||
publicKey?: string
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||
export type SlashClientEvents = {
|
||||
interaction: [Interaction]
|
||||
interactionError: [Error]
|
||||
ping: []
|
||||
}
|
||||
|
||||
/** Slash Client represents an Interactions Client which can be used without Harmony Client. */
|
||||
export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
||||
id: string | (() => string)
|
||||
client?: Client
|
||||
|
||||
#token?: string
|
||||
|
||||
get token(): string | undefined {
|
||||
return this.#token
|
||||
}
|
||||
|
||||
set token(val: string | undefined) {
|
||||
this.#token = val
|
||||
}
|
||||
|
||||
enabled: boolean = true
|
||||
commands: SlashCommandsManager
|
||||
handlers: SlashCommandHandler[] = []
|
||||
readonly rest!: RESTManager
|
||||
modules: SlashModule[] = []
|
||||
publicKey?: string
|
||||
|
||||
constructor(options: SlashOptions) {
|
||||
super()
|
||||
let id = options.id
|
||||
if (options.token !== undefined) id = atob(options.token?.split('.')[0])
|
||||
if (id === undefined)
|
||||
throw new Error('ID could not be found. Pass at least client or token')
|
||||
this.id = id
|
||||
|
||||
if (options.client !== undefined) {
|
||||
Object.defineProperty(this, 'client', {
|
||||
value: options.client,
|
||||
enumerable: false
|
||||
})
|
||||
}
|
||||
|
||||
this.token = options.token
|
||||
this.publicKey = options.publicKey
|
||||
|
||||
this.enabled = options.enabled ?? true
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const client = this.client as any
|
||||
if (client?._decoratedSlash !== undefined) {
|
||||
client._decoratedSlash.forEach((e: any) => {
|
||||
e.handler = e.handler.bind(this.client)
|
||||
this.handlers.push(e)
|
||||
})
|
||||
}
|
||||
|
||||
const self = this as any
|
||||
if (self._decoratedSlash !== undefined) {
|
||||
self._decoratedSlash.forEach((e: any) => {
|
||||
e.handler = e.handler.bind(this.client)
|
||||
self.handlers.push(e)
|
||||
})
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'rest', {
|
||||
value:
|
||||
options.client === undefined
|
||||
? options.rest === undefined
|
||||
? new RESTManager({
|
||||
token: this.token
|
||||
})
|
||||
: options.rest
|
||||
: options.client.rest,
|
||||
enumerable: false
|
||||
})
|
||||
|
||||
this.client?.on(
|
||||
'interactionCreate',
|
||||
async (interaction) => await this._process(interaction)
|
||||
)
|
||||
|
||||
this.commands = new SlashCommandsManager(this)
|
||||
}
|
||||
|
||||
getID(): string {
|
||||
return typeof this.id === 'string' ? this.id : this.id()
|
||||
}
|
||||
|
||||
/** Adds a new Slash Command Handler */
|
||||
handle(
|
||||
cmd: string | SlashCommandHandler,
|
||||
handler?: SlashCommandHandlerCallback
|
||||
): SlashClient {
|
||||
const handle = {
|
||||
name: typeof cmd === 'string' ? cmd : cmd.name,
|
||||
...(handler !== undefined ? { handler } : {}),
|
||||
...(typeof cmd === 'string' ? {} : cmd)
|
||||
}
|
||||
|
||||
if (handle.handler === undefined)
|
||||
throw new Error('Invalid usage. Handler function not provided')
|
||||
|
||||
if (
|
||||
typeof handle.name === 'string' &&
|
||||
handle.name.includes(' ') &&
|
||||
handle.parent === undefined &&
|
||||
handle.group === undefined
|
||||
) {
|
||||
const parts = handle.name.split(/ +/).filter((e) => e !== '')
|
||||
if (parts.length > 3 || parts.length < 1)
|
||||
throw new Error('Invalid command name')
|
||||
const root = parts.shift() as string
|
||||
const group = parts.length === 2 ? parts.shift() : undefined
|
||||
const sub = parts.shift()
|
||||
|
||||
handle.name = sub ?? root
|
||||
handle.group = group
|
||||
handle.parent = sub === undefined ? undefined : root
|
||||
}
|
||||
|
||||
this.handlers.push(handle as any)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Load a Slash Module */
|
||||
loadModule(module: SlashModule): SlashClient {
|
||||
this.modules.push(module)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Get all Handlers. Including Slash Modules */
|
||||
getHandlers(): SlashCommandHandler[] {
|
||||
let res = this.handlers
|
||||
for (const mod of this.modules) {
|
||||
if (mod === undefined) continue
|
||||
res = [
|
||||
...res,
|
||||
...mod.commands.map((cmd) => {
|
||||
cmd.handler = cmd.handler.bind(mod)
|
||||
return cmd
|
||||
})
|
||||
]
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
/** Get Handler for an Interaction. Supports nested sub commands and sub command groups. */
|
||||
private _getCommand(
|
||||
i: SlashCommandInteraction
|
||||
): SlashCommandHandler | undefined {
|
||||
return this.getHandlers().find((e) => {
|
||||
const hasGroupOrParent = e.group !== undefined || e.parent !== undefined
|
||||
const groupMatched =
|
||||
e.group !== undefined && e.parent !== undefined
|
||||
? i.options
|
||||
.find(
|
||||
(o) =>
|
||||
o.name === e.group &&
|
||||
o.type === SlashCommandOptionType.SUB_COMMAND_GROUP
|
||||
)
|
||||
?.options?.find((o) => o.name === e.name) !== undefined
|
||||
: true
|
||||
const subMatched =
|
||||
e.group === undefined && e.parent !== undefined
|
||||
? i.options.find(
|
||||
(o) =>
|
||||
o.name === e.name &&
|
||||
o.type === SlashCommandOptionType.SUB_COMMAND
|
||||
) !== undefined
|
||||
: true
|
||||
const nameMatched1 = e.name === i.name
|
||||
const parentMatched = hasGroupOrParent ? e.parent === i.name : true
|
||||
const nameMatched = hasGroupOrParent ? parentMatched : nameMatched1
|
||||
|
||||
const matched = groupMatched && subMatched && nameMatched
|
||||
return matched
|
||||
})
|
||||
}
|
||||
|
||||
/** Process an incoming Interaction */
|
||||
private async _process(
|
||||
interaction: Interaction | SlashCommandInteraction
|
||||
): Promise<void> {
|
||||
if (!this.enabled) return
|
||||
|
||||
if (interaction.type !== InteractionType.APPLICATION_COMMAND) return
|
||||
|
||||
const cmd =
|
||||
this._getCommand(interaction as SlashCommandInteraction) ??
|
||||
this.getHandlers().find((e) => e.name === '*')
|
||||
if (cmd?.group !== undefined)
|
||||
(interaction as SlashCommandInteraction).data.options =
|
||||
(interaction as SlashCommandInteraction).data.options[0].options ?? []
|
||||
if (cmd?.parent !== undefined)
|
||||
(interaction as SlashCommandInteraction).data.options =
|
||||
(interaction as SlashCommandInteraction).data.options[0].options ?? []
|
||||
|
||||
if (cmd === undefined) return
|
||||
|
||||
await this.emit('interaction', interaction)
|
||||
try {
|
||||
await cmd.handler(interaction as SlashCommandInteraction)
|
||||
} catch (e) {
|
||||
await this.emit('interactionError', e)
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify HTTP based Interaction */
|
||||
async verifyKey(
|
||||
rawBody: string | Uint8Array,
|
||||
signature: string | Uint8Array,
|
||||
timestamp: string | Uint8Array
|
||||
): Promise<boolean> {
|
||||
if (this.publicKey === undefined)
|
||||
throw new Error('Public Key is not present')
|
||||
|
||||
const fullBody = new Uint8Array([
|
||||
...(typeof timestamp === 'string' ? encodeText(timestamp) : timestamp),
|
||||
...(typeof rawBody === 'string' ? encodeText(rawBody) : rawBody)
|
||||
])
|
||||
|
||||
return edverify(signature, fullBody, this.publicKey).catch(() => false)
|
||||
}
|
||||
|
||||
/** Verify [Deno Std HTTP Server Request](https://deno.land/std/http/server.ts) and return Interaction. **Data present in Interaction returned by this method is very different from actual typings as there is no real `Client` behind the scenes to cache things.** */
|
||||
async verifyServerRequest(req: {
|
||||
headers: Headers
|
||||
method: string
|
||||
body: Deno.Reader | Uint8Array
|
||||
respond: (options: {
|
||||
status?: number
|
||||
headers?: Headers
|
||||
body?: any
|
||||
}) => Promise<void>
|
||||
}): Promise<false | Interaction> {
|
||||
if (req.method.toLowerCase() !== 'post') return false
|
||||
|
||||
const signature = req.headers.get('x-signature-ed25519')
|
||||
const timestamp = req.headers.get('x-signature-timestamp')
|
||||
if (signature === null || timestamp === null) return false
|
||||
|
||||
const rawbody =
|
||||
req.body instanceof Uint8Array ? req.body : await Deno.readAll(req.body)
|
||||
const verify = await this.verifyKey(rawbody, signature, timestamp)
|
||||
if (!verify) return false
|
||||
|
||||
try {
|
||||
const payload: InteractionPayload = JSON.parse(decodeText(rawbody))
|
||||
|
||||
// TODO: Maybe fix all this hackery going on here?
|
||||
let res
|
||||
if (payload.type === InteractionType.APPLICATION_COMMAND) {
|
||||
res = new SlashCommandInteraction(this as any, payload, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
user: new User(this as any, (payload.member?.user ?? payload.user)!),
|
||||
member: payload.member as any,
|
||||
guild: payload.guild_id as any,
|
||||
channel: payload.channel_id as any,
|
||||
resolved: (((payload.data as any)
|
||||
?.resolved as unknown) as InteractionApplicationCommandResolved) ?? {
|
||||
users: {},
|
||||
members: {},
|
||||
roles: {},
|
||||
channels: {}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
res = new Interaction(this as any, payload, {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
user: new User(this as any, (payload.member?.user ?? payload.user)!),
|
||||
member: payload.member as any,
|
||||
guild: payload.guild_id as any,
|
||||
channel: payload.channel_id as any
|
||||
})
|
||||
}
|
||||
res._httpRespond = async (d: InteractionResponsePayload | FormData) =>
|
||||
await req.respond({
|
||||
status: 200,
|
||||
headers: new Headers({
|
||||
'content-type':
|
||||
d instanceof FormData ? 'multipart/form-data' : 'application/json'
|
||||
}),
|
||||
body: d instanceof FormData ? d : JSON.stringify(d)
|
||||
})
|
||||
|
||||
return res
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/** Verify FetchEvent (for Service Worker usage) and return Interaction if valid */
|
||||
async verifyFetchEvent({
|
||||
request: req,
|
||||
respondWith
|
||||
}: {
|
||||
respondWith: CallableFunction
|
||||
request: Request
|
||||
}): Promise<false | Interaction> {
|
||||
if (req.bodyUsed === true) throw new Error('Request Body already used')
|
||||
if (req.body === null) return false
|
||||
const body = (await req.body.getReader().read()).value
|
||||
if (body === undefined) return false
|
||||
|
||||
return await this.verifyServerRequest({
|
||||
headers: req.headers,
|
||||
body,
|
||||
method: req.method,
|
||||
respond: async (options) => {
|
||||
await respondWith(
|
||||
new Response(options.body, {
|
||||
headers: options.headers,
|
||||
status: options.status
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async verifyOpineRequest(req: any): Promise<boolean> {
|
||||
const signature = req.headers.get('x-signature-ed25519')
|
||||
const timestamp = req.headers.get('x-signature-timestamp')
|
||||
const contentLength = req.headers.get('content-length')
|
||||
|
||||
if (signature === null || timestamp === null || contentLength === null)
|
||||
return false
|
||||
|
||||
const body = new Uint8Array(parseInt(contentLength))
|
||||
await req.body.read(body)
|
||||
|
||||
const verified = await this.verifyKey(body, signature, timestamp)
|
||||
if (!verified) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Middleware to verify request in Opine framework. */
|
||||
async verifyOpineMiddleware(
|
||||
req: any,
|
||||
res: any,
|
||||
next: CallableFunction
|
||||
): Promise<any> {
|
||||
const verified = await this.verifyOpineRequest(req)
|
||||
if (!verified) return res.setStatus(401).end()
|
||||
|
||||
await next()
|
||||
return true
|
||||
}
|
||||
|
||||
// TODO: create verifyOakMiddleware too
|
||||
/** Method to verify Request from Oak server "Context". */
|
||||
async verifyOakRequest(ctx: any): Promise<any> {
|
||||
const signature = ctx.request.headers.get('x-signature-ed25519')
|
||||
const timestamp = ctx.request.headers.get('x-signature-timestamp')
|
||||
const contentLength = ctx.request.headers.get('content-length')
|
||||
|
||||
if (
|
||||
signature === null ||
|
||||
timestamp === null ||
|
||||
contentLength === null ||
|
||||
ctx.request.hasBody !== true
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
const body = await ctx.request.body().value
|
||||
|
||||
const verified = await this.verifyKey(body, signature, timestamp)
|
||||
if (!verified) return false
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/** Decorator to create a Slash Command handler */
|
||||
export function slash(name?: string, guild?: string) {
|
||||
return function (client: Client | SlashClient | SlashModule, prop: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const c = client as any
|
||||
if (c._decoratedSlash === undefined) c._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
throw new Error('@slash decorator requires a function')
|
||||
} else
|
||||
c._decoratedSlash.push({
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** Decorator to create a Sub-Slash Command handler */
|
||||
export function subslash(parent: string, name?: string, guild?: string) {
|
||||
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const c = client as any
|
||||
if (c._decoratedSlash === undefined) c._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
throw new Error('@subslash decorator requires a function')
|
||||
} else
|
||||
c._decoratedSlash.push({
|
||||
parent,
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/** Decorator to create a Grouped Slash Command handler */
|
||||
export function groupslash(
|
||||
parent: string,
|
||||
group: string,
|
||||
name?: string,
|
||||
guild?: string
|
||||
) {
|
||||
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const c = client as any
|
||||
if (c._decoratedSlash === undefined) c._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
throw new Error('@groupslash decorator requires a function')
|
||||
} else
|
||||
c._decoratedSlash.push({
|
||||
group,
|
||||
parent,
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,352 @@
|
|||
import { RESTManager } from '../rest/manager.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import {
|
||||
SlashCommandChoice,
|
||||
SlashCommandOption,
|
||||
SlashCommandOptionType,
|
||||
SlashCommandPartial,
|
||||
SlashCommandPayload
|
||||
} from '../types/slashCommands.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import type { SlashClient, SlashCommandHandlerCallback } from './slashClient.ts'
|
||||
|
||||
export class SlashCommand {
|
||||
slash: SlashCommandsManager
|
||||
id: string
|
||||
applicationID: string
|
||||
name: string
|
||||
description: string
|
||||
options: SlashCommandOption[]
|
||||
guild?: Guild
|
||||
_guild?: string
|
||||
|
||||
constructor(
|
||||
manager: SlashCommandsManager,
|
||||
data: SlashCommandPayload,
|
||||
guild?: Guild
|
||||
) {
|
||||
this.slash = manager
|
||||
this.id = data.id
|
||||
this.applicationID = data.application_id
|
||||
this.name = data.name
|
||||
this.description = data.description
|
||||
this.options = data.options ?? []
|
||||
this.guild = guild
|
||||
}
|
||||
|
||||
async delete(): Promise<void> {
|
||||
await this.slash.delete(this.id, this._guild)
|
||||
}
|
||||
|
||||
async edit(data: SlashCommandPartial): Promise<void> {
|
||||
await this.slash.edit(this.id, data, this._guild)
|
||||
}
|
||||
|
||||
/** Create a handler for this Slash Command */
|
||||
handle(
|
||||
func: SlashCommandHandlerCallback,
|
||||
options?: { parent?: string; group?: string }
|
||||
): SlashCommand {
|
||||
this.slash.slash.handle({
|
||||
name: this.name,
|
||||
parent: options?.parent,
|
||||
group: options?.group,
|
||||
guild: this._guild,
|
||||
handler: func
|
||||
})
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateOptions {
|
||||
name: string
|
||||
description?: string
|
||||
options?: Array<SlashCommandOption | SlashOptionCallable>
|
||||
choices?: Array<SlashCommandChoice | string>
|
||||
}
|
||||
|
||||
function createSlashOption(
|
||||
type: SlashCommandOptionType,
|
||||
data: CreateOptions
|
||||
): SlashCommandOption {
|
||||
return {
|
||||
name: data.name,
|
||||
type,
|
||||
description:
|
||||
type === 0 || type === 1
|
||||
? undefined
|
||||
: data.description ?? 'No description.',
|
||||
options: data.options?.map((e) =>
|
||||
typeof e === 'function' ? e(SlashOption) : e
|
||||
),
|
||||
choices:
|
||||
data.choices === undefined
|
||||
? undefined
|
||||
: data.choices.map((e) =>
|
||||
typeof e === 'string' ? { name: e, value: e } : e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
|
||||
export class SlashOption {
|
||||
static string(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.STRING, data)
|
||||
}
|
||||
|
||||
static bool(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.BOOLEAN, data)
|
||||
}
|
||||
|
||||
static subCommand(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.SUB_COMMAND, data)
|
||||
}
|
||||
|
||||
static subCommandGroup(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.SUB_COMMAND_GROUP, data)
|
||||
}
|
||||
|
||||
static role(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.ROLE, data)
|
||||
}
|
||||
|
||||
static channel(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.CHANNEL, data)
|
||||
}
|
||||
|
||||
static user(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.USER, data)
|
||||
}
|
||||
|
||||
static number(data: CreateOptions): SlashCommandOption {
|
||||
return createSlashOption(SlashCommandOptionType.INTEGER, data)
|
||||
}
|
||||
}
|
||||
|
||||
export type SlashOptionCallable = (o: typeof SlashOption) => SlashCommandOption
|
||||
|
||||
export type SlashBuilderOptionsData =
|
||||
| Array<SlashCommandOption | SlashOptionCallable>
|
||||
| {
|
||||
[name: string]:
|
||||
| {
|
||||
description: string
|
||||
type: SlashCommandOptionType
|
||||
options?: SlashCommandOption[]
|
||||
choices?: SlashCommandChoice[]
|
||||
}
|
||||
| SlashOptionCallable
|
||||
}
|
||||
|
||||
function buildOptionsArray(
|
||||
options: SlashBuilderOptionsData
|
||||
): SlashCommandOption[] {
|
||||
return Array.isArray(options)
|
||||
? options.map((op) => (typeof op === 'function' ? op(SlashOption) : op))
|
||||
: Object.entries(options).map((entry) =>
|
||||
typeof entry[1] === 'function'
|
||||
? entry[1](SlashOption)
|
||||
: Object.assign(entry[1], { name: entry[0] })
|
||||
)
|
||||
}
|
||||
|
||||
/** Slash Command Builder */
|
||||
export class SlashBuilder {
|
||||
data: SlashCommandPartial
|
||||
|
||||
constructor(
|
||||
name?: string,
|
||||
description?: string,
|
||||
options?: SlashBuilderOptionsData
|
||||
) {
|
||||
this.data = {
|
||||
name: name ?? '',
|
||||
description: description ?? 'No description.',
|
||||
options: options === undefined ? [] : buildOptionsArray(options)
|
||||
}
|
||||
}
|
||||
|
||||
name(name: string): SlashBuilder {
|
||||
this.data.name = name
|
||||
return this
|
||||
}
|
||||
|
||||
description(desc: string): SlashBuilder {
|
||||
this.data.description = desc
|
||||
return this
|
||||
}
|
||||
|
||||
option(option: SlashOptionCallable | SlashCommandOption): SlashBuilder {
|
||||
if (this.data.options === undefined) this.data.options = []
|
||||
this.data.options.push(
|
||||
typeof option === 'function' ? option(SlashOption) : option
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
options(options: SlashBuilderOptionsData): SlashBuilder {
|
||||
this.data.options = buildOptionsArray(options)
|
||||
return this
|
||||
}
|
||||
|
||||
export(): SlashCommandPartial {
|
||||
if (this.data.name === '')
|
||||
throw new Error('Name was not provided in Slash Builder')
|
||||
return this.data
|
||||
}
|
||||
}
|
||||
|
||||
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
|
||||
export class SlashCommandsManager {
|
||||
readonly slash!: SlashClient
|
||||
readonly rest!: RESTManager
|
||||
|
||||
constructor(client: SlashClient) {
|
||||
Object.defineProperty(this, 'slash', { value: client, enumerable: false })
|
||||
Object.defineProperty(this, 'rest', {
|
||||
enumerable: false,
|
||||
value: client.rest
|
||||
})
|
||||
}
|
||||
|
||||
/** Get all Global Slash Commands */
|
||||
async all(): Promise<Collection<string, SlashCommand>> {
|
||||
const col = new Collection<string, SlashCommand>()
|
||||
|
||||
const res = (await this.rest.api.applications[
|
||||
this.slash.getID()
|
||||
].commands.get()) as SlashCommandPayload[]
|
||||
if (!Array.isArray(res)) return col
|
||||
|
||||
for (const raw of res) {
|
||||
const cmd = new SlashCommand(this, raw)
|
||||
col.set(raw.id, cmd)
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
/** Get a Guild's Slash Commands */
|
||||
async guild(
|
||||
guild: Guild | string
|
||||
): Promise<Collection<string, SlashCommand>> {
|
||||
const col = new Collection<string, SlashCommand>()
|
||||
|
||||
const res = (await this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands.get()) as SlashCommandPayload[]
|
||||
if (!Array.isArray(res)) return col
|
||||
|
||||
const _guild =
|
||||
typeof guild === 'object'
|
||||
? guild
|
||||
: await this.slash.client?.guilds.get(guild)
|
||||
|
||||
for (const raw of res) {
|
||||
const cmd = new SlashCommand(this, raw, _guild)
|
||||
cmd._guild = typeof guild === 'string' ? guild : guild.id
|
||||
col.set(raw.id, cmd)
|
||||
}
|
||||
|
||||
return col
|
||||
}
|
||||
|
||||
/** Create a Slash Command (global or Guild) */
|
||||
async create(
|
||||
data: SlashCommandPartial,
|
||||
guild?: Guild | string
|
||||
): Promise<SlashCommand> {
|
||||
const route =
|
||||
guild === undefined
|
||||
? this.rest.api.applications[this.slash.getID()].commands
|
||||
: this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands
|
||||
|
||||
const payload = await route.post(data)
|
||||
|
||||
const _guild =
|
||||
typeof guild === 'object'
|
||||
? guild
|
||||
: guild === undefined
|
||||
? undefined
|
||||
: await this.slash.client?.guilds.get(guild)
|
||||
|
||||
const cmd = new SlashCommand(this, payload, _guild)
|
||||
cmd._guild =
|
||||
typeof guild === 'string' || guild === undefined ? guild : guild.id
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
/** Edit a Slash Command (global or Guild) */
|
||||
async edit(
|
||||
id: string,
|
||||
data: SlashCommandPartial,
|
||||
guild?: Guild | string
|
||||
): Promise<SlashCommandsManager> {
|
||||
const route =
|
||||
guild === undefined
|
||||
? this.rest.api.applications[this.slash.getID()].commands[id]
|
||||
: this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands[id]
|
||||
|
||||
await route.patch(data)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Delete a Slash Command (global or Guild) */
|
||||
async delete(
|
||||
id: string,
|
||||
guild?: Guild | string
|
||||
): Promise<SlashCommandsManager> {
|
||||
const route =
|
||||
guild === undefined
|
||||
? this.rest.api.applications[this.slash.getID()].commands[id]
|
||||
: this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands[id]
|
||||
|
||||
await route.delete()
|
||||
return this
|
||||
}
|
||||
|
||||
/** Get a Slash Command (global or Guild) */
|
||||
async get(id: string, guild?: Guild | string): Promise<SlashCommand> {
|
||||
const route =
|
||||
guild === undefined
|
||||
? this.rest.api.applications[this.slash.getID()].commands[id]
|
||||
: this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands[id]
|
||||
|
||||
const data = await route.get()
|
||||
|
||||
const _guild =
|
||||
typeof guild === 'object'
|
||||
? guild
|
||||
: guild === undefined
|
||||
? undefined
|
||||
: await this.slash.client?.guilds.get(guild)
|
||||
|
||||
return new SlashCommand(this, data, _guild)
|
||||
}
|
||||
|
||||
/** Bulk Edit Global or Guild Slash Commands */
|
||||
async bulkEdit(
|
||||
cmds: Array<SlashCommandPartial | SlashCommandPayload>,
|
||||
guild?: Guild | string
|
||||
): Promise<SlashCommandsManager> {
|
||||
const route =
|
||||
guild === undefined
|
||||
? this.rest.api.applications[this.slash.getID()].commands
|
||||
: this.rest.api.applications[this.slash.getID()].guilds[
|
||||
typeof guild === 'string' ? guild : guild.id
|
||||
].commands
|
||||
|
||||
await route.put(cmds)
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -1,13 +1,12 @@
|
|||
import { SlashCommandHandler } from './slashClient.ts'
|
||||
import type { SlashCommandHandler } from './slashClient.ts'
|
||||
|
||||
export class SlashModule {
|
||||
name: string = ''
|
||||
commands: SlashCommandHandler[] = []
|
||||
_decoratedSlash?: SlashCommandHandler[]
|
||||
|
||||
constructor() {
|
||||
if (this._decoratedSlash !== undefined) {
|
||||
this.commands = this._decoratedSlash
|
||||
if ((this as any)._decoratedSlash !== undefined) {
|
||||
;(this as any).commands = (this as any)._decoratedSlash
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Base } from '../structures/base.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
/**
|
||||
|
@ -6,15 +7,14 @@ import { Collection } from '../utils/collection.ts'
|
|||
*
|
||||
* You should not be making Managers yourself.
|
||||
*/
|
||||
export class BaseManager<T, T2> {
|
||||
client: Client
|
||||
export class BaseManager<T, T2> extends Base {
|
||||
/** Caches Name or Key used to differentiate caches */
|
||||
cacheName: string
|
||||
/** Which data type does this cache have */
|
||||
DataType: any
|
||||
|
||||
constructor(client: Client, cacheName: string, DataType: any) {
|
||||
this.client = client
|
||||
super(client)
|
||||
this.cacheName = cacheName
|
||||
this.DataType = DataType
|
||||
}
|
||||
|
@ -37,12 +37,12 @@ export class BaseManager<T, T2> {
|
|||
}
|
||||
|
||||
/** Deletes a key from Cache */
|
||||
async delete(key: string): Promise<boolean> {
|
||||
async _delete(key: string): Promise<boolean> {
|
||||
return this.client.cache.delete(this.cacheName, key)
|
||||
}
|
||||
|
||||
/** Gets an Array of values from Cache */
|
||||
async array(): Promise<undefined | T2[]> {
|
||||
async array(): Promise<T2[]> {
|
||||
let arr = await (this.client.cache.array(this.cacheName) as T[])
|
||||
if (arr === undefined) arr = []
|
||||
return arr.map((e) => new this.DataType(this.client, e)) as any
|
||||
|
@ -60,8 +60,35 @@ export class BaseManager<T, T2> {
|
|||
return collection
|
||||
}
|
||||
|
||||
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
|
||||
const arr = (await this.array()) ?? []
|
||||
const { readable, writable } = new TransformStream()
|
||||
const writer = writable.getWriter()
|
||||
arr.forEach((el: unknown) => writer.write(el))
|
||||
writer.close()
|
||||
yield* readable
|
||||
}
|
||||
|
||||
async fetch(...args: unknown[]): Promise<T2 | undefined> {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** Try to get value from cache, if not found then fetch */
|
||||
async resolve(key: string): Promise<T2 | undefined> {
|
||||
const cacheValue = await this.get(key)
|
||||
if (cacheValue !== undefined) return cacheValue
|
||||
else {
|
||||
const fetchValue = await this.fetch(key).catch(() => undefined)
|
||||
if (fetchValue !== undefined) return fetchValue
|
||||
}
|
||||
}
|
||||
|
||||
/** Deletes everything from Cache */
|
||||
flush(): any {
|
||||
return this.client.cache.deleteCache(this.cacheName)
|
||||
}
|
||||
|
||||
[Deno.customInspect](): string {
|
||||
return `Manager(${this.cacheName})`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Base } from '../structures/base.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
/** Child Managers validate data from their parents i.e. from Managers */
|
||||
export class BaseChildManager<T, T2> {
|
||||
client: Client
|
||||
export class BaseChildManager<T, T2> extends Base {
|
||||
/** Parent Manager */
|
||||
parent: BaseManager<T, T2>
|
||||
|
||||
constructor(client: Client, parent: BaseManager<T, T2>) {
|
||||
this.client = client
|
||||
super(client)
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
|
@ -39,4 +39,31 @@ export class BaseChildManager<T, T2> {
|
|||
}
|
||||
return collection
|
||||
}
|
||||
|
||||
async *[Symbol.asyncIterator](): AsyncIterableIterator<T2> {
|
||||
const arr = (await this.array()) ?? []
|
||||
const { readable, writable } = new TransformStream()
|
||||
const writer = writable.getWriter()
|
||||
arr.forEach((el: unknown) => writer.write(el))
|
||||
writer.close()
|
||||
yield* readable
|
||||
}
|
||||
|
||||
async fetch(...args: unknown[]): Promise<T2 | undefined> {
|
||||
return this.parent.fetch(...args)
|
||||
}
|
||||
|
||||
/** Try to get value from cache, if not found then fetch */
|
||||
async resolve(key: string): Promise<T2 | undefined> {
|
||||
const cacheValue = await this.get(key)
|
||||
if (cacheValue !== undefined) return cacheValue
|
||||
else {
|
||||
const fetchValue = await this.fetch(key).catch(() => undefined)
|
||||
if (fetchValue !== undefined) return fetchValue
|
||||
}
|
||||
}
|
||||
|
||||
[Deno.customInspect](): string {
|
||||
return `ChildManager(${this.parent.cacheName})`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Client } from '../client/mod.ts'
|
||||
import { Channel } from '../structures/channel.ts'
|
||||
import { ChannelPayload, GuildChannelPayload } from '../types/channel.ts'
|
||||
import { Embed } from '../structures/embed.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import type { TextChannel } from '../structures/textChannel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import type {
|
||||
ChannelPayload,
|
||||
GuildChannelPayload,
|
||||
MessageOptions
|
||||
} from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import getChannelByType from '../utils/getChannelByType.ts'
|
||||
import getChannelByType from '../utils/channel.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export type AllMessageOptions = MessageOptions | Embed
|
||||
|
||||
export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
|
||||
constructor(client: Client) {
|
||||
super(client, 'channels', Channel)
|
||||
}
|
||||
|
||||
async getUserDM(user: User | string): Promise<string | undefined> {
|
||||
return this.client.cache.get(
|
||||
'user_dms',
|
||||
typeof user === 'string' ? user : user.id
|
||||
)
|
||||
}
|
||||
|
||||
async setUserDM(user: User | string, id: string): Promise<void> {
|
||||
await this.client.cache.set(
|
||||
'user_dms',
|
||||
typeof user === 'string' ? user : user.id,
|
||||
id
|
||||
)
|
||||
}
|
||||
|
||||
// Override get method as Generic
|
||||
async get<T = Channel>(key: string): Promise<T | undefined> {
|
||||
const data = await this._get(key)
|
||||
|
@ -25,10 +50,11 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
|
|||
return res as any
|
||||
}
|
||||
|
||||
async array(): Promise<undefined | Channel[]> {
|
||||
async array(): Promise<Channel[]> {
|
||||
const arr = await (this.client.cache.array(
|
||||
this.cacheName
|
||||
) as ChannelPayload[])
|
||||
if (arr === undefined) return []
|
||||
const result: any[] = []
|
||||
for (const elem of arr) {
|
||||
let guild
|
||||
|
@ -65,4 +91,109 @@ export class ChannelsManager extends BaseManager<ChannelPayload, Channel> {
|
|||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
async sendMessage(
|
||||
channel: string | TextChannel,
|
||||
content?: string | AllMessageOptions,
|
||||
option?: AllMessageOptions
|
||||
): Promise<Message> {
|
||||
const channelID = typeof channel === 'string' ? channel : channel.id
|
||||
|
||||
if (typeof content === 'object') {
|
||||
option = content
|
||||
content = undefined
|
||||
}
|
||||
if (content === undefined && option === undefined) {
|
||||
throw new Error('Either text or option is necessary.')
|
||||
}
|
||||
if (option instanceof Embed) {
|
||||
option = {
|
||||
embed: option
|
||||
}
|
||||
}
|
||||
|
||||
const payload: any = {
|
||||
content: content ?? option?.content,
|
||||
embed: option?.embed,
|
||||
file: option?.file,
|
||||
files: option?.files,
|
||||
tts: option?.tts,
|
||||
allowed_mentions: option?.allowedMentions,
|
||||
message_reference:
|
||||
option?.reply === undefined
|
||||
? undefined
|
||||
: typeof option.reply === 'string'
|
||||
? {
|
||||
message_id: option.reply
|
||||
}
|
||||
: typeof option.reply === 'object'
|
||||
? option.reply instanceof Message
|
||||
? {
|
||||
message_id: option.reply.id,
|
||||
channel_id: option.reply.channel.id,
|
||||
guild_id: option.reply.guild?.id
|
||||
}
|
||||
: option.reply
|
||||
: undefined
|
||||
}
|
||||
|
||||
if (payload.content === undefined && payload.embed === undefined) {
|
||||
payload.content = ''
|
||||
}
|
||||
|
||||
const resp = await this.client.rest.api.channels[channelID].messages.post(
|
||||
payload
|
||||
)
|
||||
const chan =
|
||||
typeof channel === 'string'
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(await this.get<TextChannel>(channel))!
|
||||
: channel
|
||||
const res = new Message(this.client, resp, chan, this.client.user as any)
|
||||
await res.mentions.fromPayload(resp)
|
||||
return res
|
||||
}
|
||||
|
||||
async editMessage(
|
||||
channel: string | TextChannel,
|
||||
message: Message | string,
|
||||
text?: string | MessageOptions,
|
||||
option?: MessageOptions
|
||||
): Promise<Message> {
|
||||
const channelID = typeof channel === 'string' ? channel : channel.id
|
||||
|
||||
if (text === undefined && option === undefined) {
|
||||
throw new Error('Either text or option is necessary.')
|
||||
}
|
||||
|
||||
if (this.client.user === undefined) {
|
||||
throw new Error('Client user has not initialized.')
|
||||
}
|
||||
|
||||
if (typeof text === 'object') {
|
||||
if (typeof option === 'object') Object.assign(option, text)
|
||||
else option = text
|
||||
text = undefined
|
||||
}
|
||||
|
||||
const newMsg = await this.client.rest.api.channels[channelID].messages[
|
||||
typeof message === 'string' ? message : message.id
|
||||
].patch({
|
||||
content: text ?? option?.content,
|
||||
embed: option?.embed !== undefined ? option.embed.toJSON() : undefined,
|
||||
// Cannot upload new files with Message
|
||||
// file: option?.file,
|
||||
tts: option?.tts,
|
||||
allowed_mentions: option?.allowedMentions
|
||||
})
|
||||
|
||||
const chan =
|
||||
typeof channel === 'string'
|
||||
? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
(await this.get<TextChannel>(channel))!
|
||||
: channel
|
||||
const res = new Message(this.client, newMsg, chan, this.client.user)
|
||||
await res.mentions.fromPayload(newMsg)
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import type { EmojiPayload } from '../types/emoji.ts'
|
||||
import { GUILD_EMOJI } from '../types/endpoint.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
|
||||
/**
|
||||
* Cache Manager used for Caching values related to Gateway connection
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import type { Client } from '../client/mod.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import type { VoiceStatePayload } from '../types/voice.ts'
|
||||
import { VoiceState } from '../structures/voiceState.ts'
|
||||
import { GuildVoiceStatesManager } from './guildVoiceStates.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
|
||||
export class GuildChannelVoiceStatesManager extends BaseChildManager<
|
||||
VoiceStatePayload,
|
||||
VoiceState
|
||||
> {
|
||||
channel: VoiceChannel
|
||||
|
||||
constructor(
|
||||
client: Client,
|
||||
parent: GuildVoiceStatesManager,
|
||||
channel: VoiceChannel
|
||||
) {
|
||||
super(client, parent as any)
|
||||
this.channel = channel
|
||||
}
|
||||
|
||||
async get(id: string): Promise<VoiceState | undefined> {
|
||||
const res = await this.parent.get(id)
|
||||
if (res !== undefined && res.channel?.id === this.channel.id) return res
|
||||
else return undefined
|
||||
}
|
||||
|
||||
async array(): Promise<VoiceState[]> {
|
||||
const arr = (await this.parent.array()) as VoiceState[]
|
||||
return arr.filter((c: any) => c.channel?.id === this.channel.id) as any
|
||||
}
|
||||
|
||||
async fromPayload(d: VoiceStatePayload[]): Promise<void> {
|
||||
for (const data of d) {
|
||||
await this.set(data.user_id, data)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,27 +1,33 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Channel } from '../structures/channel.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { CategoryChannel } from '../structures/guildCategoryChannel.ts'
|
||||
import { GuildTextChannel } from '../structures/textChannel.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { CategoryChannel } from '../structures/guildCategoryChannel.ts'
|
||||
import {
|
||||
GuildChannelCategoryPayload,
|
||||
GuildTextChannelPayload,
|
||||
GuildVoiceChannelPayload
|
||||
ChannelTypes,
|
||||
GuildChannelPayload,
|
||||
OverwritePayload
|
||||
} from '../types/channel.ts'
|
||||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import type { GuildChannels, GuildChannelPayloads } from '../types/guild.ts'
|
||||
import { CHANNEL, GUILD_CHANNELS } from '../types/endpoint.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { ChannelsManager } from './channels.ts'
|
||||
import type { ChannelsManager } from './channels.ts'
|
||||
|
||||
export type GuildChannelPayloads =
|
||||
| GuildTextChannelPayload
|
||||
| GuildVoiceChannelPayload
|
||||
| GuildChannelCategoryPayload
|
||||
export type GuildChannel = GuildTextChannel | VoiceChannel | CategoryChannel
|
||||
export interface CreateChannelOptions {
|
||||
name: string
|
||||
type?: ChannelTypes
|
||||
topic?: string
|
||||
bitrate?: number
|
||||
userLimit?: number
|
||||
rateLimitPerUser?: number
|
||||
position?: number
|
||||
permissionOverwrites?: OverwritePayload[]
|
||||
parent?: CategoryChannel | string
|
||||
nsfw?: boolean
|
||||
}
|
||||
|
||||
export class GuildChannelsManager extends BaseChildManager<
|
||||
GuildChannelPayloads,
|
||||
GuildChannel
|
||||
GuildChannels
|
||||
> {
|
||||
guild: Guild
|
||||
|
||||
|
@ -30,17 +36,18 @@ export class GuildChannelsManager extends BaseChildManager<
|
|||
this.guild = guild
|
||||
}
|
||||
|
||||
async get(id: string): Promise<GuildChannel | undefined> {
|
||||
async get(id: string): Promise<GuildChannels | undefined> {
|
||||
const res = await this.parent.get(id)
|
||||
if (res !== undefined && res.guild.id === this.guild.id) return res
|
||||
else return undefined
|
||||
}
|
||||
|
||||
/** Delete a Guild Channel */
|
||||
async delete(id: string): Promise<boolean> {
|
||||
return this.client.rest.delete(CHANNEL(id))
|
||||
}
|
||||
|
||||
async array(): Promise<GuildChannel[]> {
|
||||
async array(): Promise<GuildChannels[]> {
|
||||
const arr = (await this.parent.array()) as Channel[]
|
||||
return arr.filter(
|
||||
(c: any) => c.guild !== undefined && c.guild.id === this.guild.id
|
||||
|
@ -50,8 +57,51 @@ export class GuildChannelsManager extends BaseChildManager<
|
|||
async flush(): Promise<boolean> {
|
||||
const arr = await this.array()
|
||||
for (const elem of arr) {
|
||||
this.parent.delete(elem.id)
|
||||
this.parent._delete(elem.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Create a new Guild Channel */
|
||||
async create(options: CreateChannelOptions): Promise<GuildChannels> {
|
||||
if (options.name === undefined)
|
||||
throw new Error('name is required for GuildChannelsManager#create')
|
||||
const res = ((await this.client.rest.post(GUILD_CHANNELS(this.guild.id), {
|
||||
name: options.name,
|
||||
type: options.type,
|
||||
topic: options.topic,
|
||||
bitrate: options.bitrate,
|
||||
user_limit: options.userLimit,
|
||||
rate_limit_per_user: options.rateLimitPerUser,
|
||||
position: options.position,
|
||||
permission_overwrites: options.permissionOverwrites,
|
||||
parent_id:
|
||||
options.parent === undefined
|
||||
? undefined
|
||||
: typeof options.parent === 'object'
|
||||
? options.parent.id
|
||||
: options.parent,
|
||||
nsfw: options.nsfw
|
||||
})) as unknown) as GuildChannelPayload
|
||||
|
||||
await this.set(res.id, res)
|
||||
const channel = await this.get(res.id)
|
||||
return (channel as unknown) as GuildChannels
|
||||
}
|
||||
|
||||
/** Modify the positions of a set of channel positions for the guild. */
|
||||
async editPositions(
|
||||
...positions: Array<{ id: string | GuildChannels; position: number | null }>
|
||||
): Promise<GuildChannelsManager> {
|
||||
if (positions.length === 0)
|
||||
throw new Error('No channel positions to change specified')
|
||||
|
||||
await this.client.rest.api.guilds[this.guild.id].channels.patch(
|
||||
positions.map((e) => ({
|
||||
id: typeof e.id === 'string' ? e.id : e.id.id,
|
||||
position: e.position ?? null
|
||||
}))
|
||||
)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { EmojiPayload } from '../types/emoji.ts'
|
||||
import type { EmojiPayload } from '../types/emoji.ts'
|
||||
import { CHANNEL, GUILD_EMOJI, GUILD_EMOJIS } from '../types/endpoint.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { EmojisManager } from './emojis.ts'
|
||||
import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts'
|
||||
import type { EmojisManager } from './emojis.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
|
||||
export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> {
|
||||
guild: Guild
|
||||
|
@ -87,7 +87,8 @@ export class GuildEmojisManager extends BaseChildManager<EmojiPayload, Emoji> {
|
|||
async flush(): Promise<boolean> {
|
||||
const arr = await this.array()
|
||||
for (const elem of arr) {
|
||||
this.parent.delete(elem.id)
|
||||
const emojiID = elem.id !== null ? elem.id : elem.name
|
||||
this.parent._delete(emojiID as string)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { VoiceState } from '../structures/voiceState.ts'
|
||||
import { VoiceStatePayload } from '../types/voice.ts'
|
||||
import type { VoiceStatePayload } from '../types/voice.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class GuildVoiceStatesManager extends BaseManager<
|
||||
|
@ -17,19 +17,26 @@ export class GuildVoiceStatesManager extends BaseManager<
|
|||
this.guild = guild
|
||||
}
|
||||
|
||||
/** Get Client's Voice State in the Guild */
|
||||
async me(): Promise<VoiceState | undefined> {
|
||||
const member = await this.guild.me()
|
||||
return await this.get(member.id)
|
||||
}
|
||||
|
||||
/** Get a Voice State by User ID */
|
||||
async get(key: string): Promise<VoiceState | undefined> {
|
||||
const raw = await this._get(key)
|
||||
if (raw === undefined) return
|
||||
|
||||
const guild =
|
||||
raw.guild_id === undefined
|
||||
? undefined
|
||||
? this.guild
|
||||
: await this.client.guilds.get(raw.guild_id)
|
||||
|
||||
return new VoiceState(this.client, raw, {
|
||||
user: ((await this.client.users.get(raw.user_id)) as unknown) as User,
|
||||
channel:
|
||||
raw.channel_id == null
|
||||
raw.channel_id === null
|
||||
? null
|
||||
: (((await this.client.channels.get<VoiceChannel>(
|
||||
raw.channel_id
|
||||
|
@ -40,6 +47,37 @@ export class GuildVoiceStatesManager extends BaseManager<
|
|||
})
|
||||
}
|
||||
|
||||
async array(): Promise<VoiceState[]> {
|
||||
let arr = await (this.client.cache.array(
|
||||
this.cacheName
|
||||
) as VoiceStatePayload[])
|
||||
if (arr === undefined) arr = []
|
||||
|
||||
return await Promise.all(
|
||||
arr.map(async (raw) => {
|
||||
const guild =
|
||||
raw.guild_id === undefined
|
||||
? this.guild
|
||||
: await this.client.guilds.get(raw.guild_id)
|
||||
|
||||
return new VoiceState(this.client, raw, {
|
||||
user: ((await this.client.users.get(raw.user_id)) as unknown) as User,
|
||||
channel:
|
||||
raw.channel_id === null
|
||||
? null
|
||||
: (((await this.client.channels.get<VoiceChannel>(
|
||||
raw.channel_id
|
||||
)) as unknown) as VoiceChannel),
|
||||
guild,
|
||||
member:
|
||||
guild === undefined
|
||||
? undefined
|
||||
: await guild.members.get(raw.user_id)
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async fromPayload(d: VoiceStatePayload[]): Promise<void> {
|
||||
for (const data of d) {
|
||||
await this.set(data.user_id, data)
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { fetchAuto } from '../../deps.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { GUILD } from '../types/endpoint.ts'
|
||||
import { GuildPayload, MemberPayload } from '../types/guild.ts'
|
||||
import type { Template } from '../structures/template.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { GUILD, GUILDS, GUILD_PREVIEW } from '../types/endpoint.ts'
|
||||
import type {
|
||||
GuildPayload,
|
||||
MemberPayload,
|
||||
GuildCreateRolePayload,
|
||||
GuildCreatePayload,
|
||||
GuildCreateChannelPayload,
|
||||
GuildPreview,
|
||||
GuildPreviewPayload,
|
||||
GuildModifyOptions,
|
||||
GuildModifyPayload,
|
||||
GuildCreateOptions
|
||||
} from '../types/guild.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
import { MembersManager } from './members.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
|
||||
export class GuildManager extends BaseManager<GuildPayload, Guild> {
|
||||
constructor(client: Client) {
|
||||
|
@ -15,7 +30,7 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> {
|
|||
this.client.rest
|
||||
.get(GUILD(id))
|
||||
.then(async (data: any) => {
|
||||
this.set(id, data)
|
||||
await this.set(id, data)
|
||||
|
||||
const guild = new Guild(this.client, data)
|
||||
|
||||
|
@ -32,4 +47,207 @@ export class GuildManager extends BaseManager<GuildPayload, Guild> {
|
|||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
/** Create a new guild based on a template. */
|
||||
async createFromTemplate(
|
||||
template: Template | string,
|
||||
name: string,
|
||||
icon?: string
|
||||
): Promise<Guild> {
|
||||
if (icon?.startsWith('http') === true) icon = await fetchAuto(icon)
|
||||
const guild = await this.client.rest.api.guilds.templates[
|
||||
typeof template === 'object' ? template.code : template
|
||||
].post({ name, icon })
|
||||
return new Guild(this.client, guild)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a guild. Returns Guild. Fires guildCreate event.
|
||||
* @param options Options for creating a guild
|
||||
*/
|
||||
async create(options: GuildCreateOptions): Promise<Guild> {
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
if (options.icon !== undefined && !options.icon.startsWith('data:')) {
|
||||
options.icon = await fetchAuto(options.icon)
|
||||
}
|
||||
if (options.roles !== undefined && options.roles[0].name !== '@everyone') {
|
||||
options.roles.unshift({
|
||||
id: Math.floor(Math.random() * 18392375458).toString(),
|
||||
name: '@everyone'
|
||||
})
|
||||
}
|
||||
|
||||
const body: GuildCreatePayload = {
|
||||
name: options.name,
|
||||
region: options.region,
|
||||
icon: options.icon,
|
||||
verification_level: options.verificationLevel,
|
||||
roles: options.roles?.map((obj) => {
|
||||
let result: GuildCreateRolePayload
|
||||
if (obj instanceof Role) {
|
||||
result = {
|
||||
id: obj.id,
|
||||
name: obj.name,
|
||||
color: obj.color,
|
||||
hoist: obj.hoist,
|
||||
position: obj.position,
|
||||
permissions: obj.permissions.bitfield.toString(),
|
||||
managed: obj.managed,
|
||||
mentionable: obj.mentionable
|
||||
}
|
||||
} else {
|
||||
result = obj
|
||||
}
|
||||
|
||||
return result
|
||||
}),
|
||||
channels: options.channels?.map(
|
||||
(obj): GuildCreateChannelPayload => ({
|
||||
id: obj.id,
|
||||
name: obj.name,
|
||||
type: obj.type,
|
||||
parent_id: obj.parentID
|
||||
})
|
||||
),
|
||||
afk_channel_id: options.afkChannelID,
|
||||
afk_timeout: options.afkTimeout,
|
||||
system_channel_id: options.systemChannelID
|
||||
}
|
||||
|
||||
const result: GuildPayload = await this.client.rest.post(GUILDS(), body)
|
||||
const guild = new Guild(this.client, result)
|
||||
|
||||
return guild
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a preview of a guild. Returns GuildPreview.
|
||||
* @param guildID Guild id
|
||||
*/
|
||||
async preview(guildID: string): Promise<GuildPreview> {
|
||||
const resp: GuildPreviewPayload = await this.client.rest.get(
|
||||
GUILD_PREVIEW(guildID)
|
||||
)
|
||||
|
||||
const result: GuildPreview = {
|
||||
id: resp.id,
|
||||
name: resp.name,
|
||||
icon: resp.icon,
|
||||
splash: resp.splash,
|
||||
discoverySplash: resp.discovery_splash,
|
||||
emojis: resp.emojis.map((emoji) => new Emoji(this.client, emoji)),
|
||||
features: resp.features,
|
||||
approximateMemberCount: resp.approximate_member_count,
|
||||
approximatePresenceCount: resp.approximate_presence_count,
|
||||
description: resp.description
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/** Sets a value to Cache */
|
||||
async set(key: string, value: GuildPayload): Promise<any> {
|
||||
value = { ...value }
|
||||
if ('roles' in value) value.roles = []
|
||||
if ('emojis' in value) value.emojis = []
|
||||
if ('members' in value) value.members = []
|
||||
if ('presences' in value) value.presences = []
|
||||
if ('voice_states' in value) value.voice_states = []
|
||||
return this.client.cache.set(this.cacheName, key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Edits a guild. Returns edited guild.
|
||||
* @param guild Guild or guild id
|
||||
* @param options Guild edit options
|
||||
* @param asRaw true for get raw data, false for get guild(defaults to false)
|
||||
*/
|
||||
async edit(
|
||||
guild: Guild | string,
|
||||
options: GuildModifyOptions,
|
||||
asRaw: false
|
||||
): Promise<Guild>
|
||||
async edit(
|
||||
guild: Guild | string,
|
||||
options: GuildModifyOptions,
|
||||
asRaw: true
|
||||
): Promise<GuildPayload>
|
||||
async edit(
|
||||
guild: Guild | string,
|
||||
options: GuildModifyOptions,
|
||||
asRaw: boolean = false
|
||||
): Promise<Guild | GuildPayload> {
|
||||
if (
|
||||
options.icon !== undefined &&
|
||||
options.icon !== null &&
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
!options.icon.startsWith('data:')
|
||||
) {
|
||||
options.icon = await fetchAuto(options.icon)
|
||||
}
|
||||
if (
|
||||
options.splash !== undefined &&
|
||||
options.splash !== null &&
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
!options.splash.startsWith('data:')
|
||||
) {
|
||||
options.splash = await fetchAuto(options.splash)
|
||||
}
|
||||
if (
|
||||
options.banner !== undefined &&
|
||||
options.banner !== null &&
|
||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||
!options.banner.startsWith('data:')
|
||||
) {
|
||||
options.banner = await fetchAuto(options.banner)
|
||||
}
|
||||
if (guild instanceof Guild) {
|
||||
guild = guild.id
|
||||
}
|
||||
|
||||
const body: GuildModifyPayload = {
|
||||
name: options.name,
|
||||
region: options.region,
|
||||
verification_level: options.verificationLevel,
|
||||
default_message_notifications: options.defaultMessageNotifications,
|
||||
explicit_content_filter: options.explicitContentFilter,
|
||||
afk_channel_id: options.afkChannelID,
|
||||
afk_timeout: options.afkTimeout,
|
||||
owner_id: options.ownerID,
|
||||
icon: options.icon,
|
||||
splash: options.splash,
|
||||
banner: options.banner,
|
||||
system_channel_id: options.systemChannelID,
|
||||
rules_channel_id: options.rulesChannelID,
|
||||
public_updates_channel_id: options.publicUpdatesChannelID,
|
||||
preferred_locale: options.preferredLocale
|
||||
}
|
||||
|
||||
const result: GuildPayload = await this.client.rest.patch(
|
||||
GUILD(guild),
|
||||
body
|
||||
)
|
||||
|
||||
if (asRaw) {
|
||||
const guild = new Guild(this.client, result)
|
||||
return guild
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a guild. Returns deleted guild.
|
||||
* @param guild Guild or guild id
|
||||
*/
|
||||
async delete(guild: Guild | string): Promise<Guild | undefined> {
|
||||
if (guild instanceof Guild) {
|
||||
guild = guild.id
|
||||
}
|
||||
|
||||
const oldGuild = await this.get(guild)
|
||||
|
||||
await this.client.rest.delete(GUILD(guild))
|
||||
return oldGuild
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { GuildTextChannel, User } from '../../mod.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Invite } from '../structures/invite.ts'
|
||||
import { GUILD_INVITES } from '../types/endpoint.ts'
|
||||
import { InvitePayload } from '../types/invite.ts'
|
||||
import { CHANNEL_INVITES, GUILD_INVITES, INVITE } from '../types/endpoint.ts'
|
||||
import type { InvitePayload } from '../types/invite.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export enum InviteTargetUserType {
|
||||
STREAM = 1
|
||||
}
|
||||
|
||||
export interface CreateInviteOptions {
|
||||
maxAge?: number
|
||||
maxUses?: number
|
||||
temporary?: boolean
|
||||
unique?: boolean
|
||||
targetUser?: string | User
|
||||
targetUserType?: InviteTargetUserType
|
||||
}
|
||||
|
||||
export class InviteManager extends BaseManager<InvitePayload, Invite> {
|
||||
guild: Guild
|
||||
|
||||
|
@ -19,19 +33,71 @@ export class InviteManager extends BaseManager<InvitePayload, Invite> {
|
|||
return new Invite(this.client, raw)
|
||||
}
|
||||
|
||||
async fetch(id: string): Promise<Invite | undefined> {
|
||||
/** Fetch an Invite */
|
||||
async fetch(id: string, withCounts: boolean = true): Promise<Invite> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
.get(GUILD_INVITES(this.guild.id))
|
||||
.get(`${INVITE(id)}${withCounts ? '?with_counts=true' : ''}`)
|
||||
.then(async (data) => {
|
||||
this.set(id, data as InvitePayload)
|
||||
const newInvite = await this.get(data.code)
|
||||
resolve(newInvite)
|
||||
resolve(newInvite as Invite)
|
||||
})
|
||||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
/** Fetch all Invites of a Guild or a specific Channel */
|
||||
async fetchAll(channel?: string | GuildTextChannel): Promise<Invite[]> {
|
||||
const rawInvites = (await this.client.rest.get(
|
||||
channel === undefined
|
||||
? GUILD_INVITES(this.guild.id)
|
||||
: CHANNEL_INVITES(typeof channel === 'string' ? channel : channel.id)
|
||||
)) as InvitePayload[]
|
||||
|
||||
const res: Invite[] = []
|
||||
|
||||
for (const raw of rawInvites) {
|
||||
await this.set(raw.code, raw)
|
||||
res.push(new Invite(this.client, raw))
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
/** Delete an Invite */
|
||||
async delete(invite: string | Invite): Promise<boolean> {
|
||||
await this.client.rest.delete(
|
||||
INVITE(typeof invite === 'string' ? invite : invite.code)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
/** Create an Invite */
|
||||
async create(
|
||||
channel: string | GuildTextChannel,
|
||||
options?: CreateInviteOptions
|
||||
): Promise<Invite> {
|
||||
const raw = ((await this.client.rest.post(
|
||||
CHANNEL_INVITES(typeof channel === 'string' ? channel : channel.id),
|
||||
{
|
||||
max_age: options?.maxAge,
|
||||
max_uses: options?.maxUses,
|
||||
temporary: options?.temporary,
|
||||
unique: options?.unique,
|
||||
target_user:
|
||||
options?.targetUser === undefined
|
||||
? undefined
|
||||
: typeof options.targetUser === 'string'
|
||||
? options.targetUser
|
||||
: options.targetUser.id,
|
||||
target_user_type: options?.targetUserType
|
||||
}
|
||||
)) as unknown) as InvitePayload
|
||||
|
||||
return new Invite(this.client, raw)
|
||||
}
|
||||
|
||||
async fromPayload(invites: InvitePayload[]): Promise<boolean> {
|
||||
for (const invite of invites) {
|
||||
await this.set(invite.code, invite)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { BaseChildManager } from './baseChild.ts'
|
||||
import { RolePayload } from '../types/role.ts'
|
||||
import type { RolePayload } from '../types/role.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { Member } from '../structures/member.ts'
|
||||
import { RolesManager } from './roles.ts'
|
||||
import { MemberPayload } from '../types/guild.ts'
|
||||
import type { Member } from '../structures/member.ts'
|
||||
import type { RolesManager } from './roles.ts'
|
||||
import type { MemberPayload } from '../types/guild.ts'
|
||||
import { GUILD_MEMBER_ROLE } from '../types/endpoint.ts'
|
||||
|
||||
export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {
|
||||
|
@ -42,7 +42,7 @@ export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {
|
|||
async flush(): Promise<boolean> {
|
||||
const arr = await this.array()
|
||||
for (const elem of arr) {
|
||||
this.parent.delete(elem.id)
|
||||
this.parent._delete(elem.id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {
|
|||
true
|
||||
)
|
||||
|
||||
return res.status === 204
|
||||
return res.response.status === 204
|
||||
}
|
||||
|
||||
async remove(role: string | Role): Promise<boolean> {
|
||||
|
@ -76,6 +76,6 @@ export class MemberRolesManager extends BaseChildManager<RolePayload, Role> {
|
|||
true
|
||||
)
|
||||
|
||||
return res.status === 204
|
||||
return res.response.status === 204
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { User } from '../structures/user.ts'
|
||||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Member } from '../structures/member.ts'
|
||||
import { GUILD_MEMBER } from '../types/endpoint.ts'
|
||||
import { MemberPayload } from '../types/guild.ts'
|
||||
import type { MemberPayload } from '../types/guild.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
import { Permissions } from '../utils/permissions.ts'
|
||||
|
||||
|
@ -37,6 +37,28 @@ export class MembersManager extends BaseManager<MemberPayload, Member> {
|
|||
return res
|
||||
}
|
||||
|
||||
async array(): Promise<Member[]> {
|
||||
let arr = await (this.client.cache.array(this.cacheName) as MemberPayload[])
|
||||
if (arr === undefined) arr = []
|
||||
|
||||
return await Promise.all(
|
||||
arr.map(async (raw) => {
|
||||
const user = new User(this.client, raw.user)
|
||||
const roles = await this.guild.roles.array()
|
||||
let permissions = new Permissions(Permissions.DEFAULT)
|
||||
if (roles !== undefined) {
|
||||
const mRoles = roles.filter(
|
||||
(r) =>
|
||||
(raw.roles.includes(r.id) as boolean) || r.id === this.guild.id
|
||||
)
|
||||
permissions = new Permissions(mRoles.map((r) => r.permissions))
|
||||
}
|
||||
return new Member(this.client, raw, user, this.guild, permissions)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/** Fetch a Guild Member */
|
||||
async fetch(id: string): Promise<Member> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Emoji } from '../structures/emoji.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import type { Message } from '../structures/message.ts'
|
||||
import { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import { Reaction } from '../types/channel.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import type { Reaction } from '../types/channel.ts'
|
||||
import {
|
||||
MESSAGE_REACTION,
|
||||
MESSAGE_REACTIONS,
|
||||
MESSAGE_REACTION_USER
|
||||
} from '../types/endpoint.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class MessageReactionsManager extends BaseManager<
|
||||
|
@ -13,17 +18,28 @@ export class MessageReactionsManager extends BaseManager<
|
|||
message: Message
|
||||
|
||||
constructor(client: Client, message: Message) {
|
||||
super(client, `reactions:${message.id}`, Guild)
|
||||
super(client, `reactions:${message.id}`, MessageReaction)
|
||||
this.message = message
|
||||
}
|
||||
|
||||
async updateRefs(): Promise<void> {
|
||||
const newVal = await this.message.channel.messages.get(this.message.id)
|
||||
if (newVal !== undefined) {
|
||||
this.message = newVal
|
||||
}
|
||||
await this.message.updateRefs()
|
||||
}
|
||||
|
||||
async get(id: string): Promise<MessageReaction | undefined> {
|
||||
const raw = await this._get(id)
|
||||
if (raw === undefined) return
|
||||
|
||||
let emoji = await this.client.emojis.get(raw.emoji.id)
|
||||
const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name
|
||||
|
||||
let emoji = await this.client.emojis.get(emojiID as string)
|
||||
if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji)
|
||||
|
||||
await this.updateRefs()
|
||||
const reaction = new MessageReaction(this.client, raw, this.message, emoji)
|
||||
return reaction
|
||||
}
|
||||
|
@ -37,8 +53,60 @@ export class MessageReactionsManager extends BaseManager<
|
|||
)
|
||||
}
|
||||
|
||||
async array(): Promise<MessageReaction[]> {
|
||||
let arr = await (this.client.cache.array(this.cacheName) as Reaction[])
|
||||
if (arr === undefined) arr = []
|
||||
|
||||
return await Promise.all(
|
||||
arr.map(async (raw) => {
|
||||
const emojiID = raw.emoji.id !== null ? raw.emoji.id : raw.emoji.name
|
||||
let emoji = await this.client.emojis.get(emojiID as string)
|
||||
if (emoji === undefined) emoji = new Emoji(this.client, raw.emoji)
|
||||
|
||||
return new MessageReaction(this.client, raw, this.message, emoji)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async flush(): Promise<any> {
|
||||
await this.client.cache.deleteCache(`reaction_users:${this.message.id}`)
|
||||
return this.client.cache.deleteCache(this.cacheName)
|
||||
}
|
||||
|
||||
/** Remove all Reactions from the Message */
|
||||
async removeAll(): Promise<void> {
|
||||
await this.client.rest.delete(
|
||||
MESSAGE_REACTIONS(this.message.channel.id, this.message.id)
|
||||
)
|
||||
}
|
||||
|
||||
/** Remove a specific Emoji from Reactions */
|
||||
async removeEmoji(emoji: Emoji | string): Promise<MessageReactionsManager> {
|
||||
const val = encodeURIComponent(
|
||||
(typeof emoji === 'object' ? emoji.id ?? emoji.name : emoji) as string
|
||||
)
|
||||
await this.client.rest.delete(
|
||||
MESSAGE_REACTION(this.message.channel.id, this.message.id, val)
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/** Remove a specific Emoji from Reactions */
|
||||
async removeUser(
|
||||
emoji: Emoji | string,
|
||||
user: User | string
|
||||
): Promise<MessageReactionsManager> {
|
||||
const val = encodeURIComponent(
|
||||
(typeof emoji === 'object' ? emoji.id ?? emoji.name : emoji) as string
|
||||
)
|
||||
await this.client.rest.delete(
|
||||
MESSAGE_REACTION_USER(
|
||||
this.message.channel.id,
|
||||
this.message.id,
|
||||
val,
|
||||
typeof user === 'string' ? user : user.id
|
||||
)
|
||||
)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { Message } from '../structures/message.ts'
|
||||
import { TextChannel } from '../structures/textChannel.ts'
|
||||
import type { TextChannel } from '../structures/textChannel.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { MessagePayload } from '../types/channel.ts'
|
||||
import type { MessagePayload } from '../types/channel.ts'
|
||||
import { CHANNEL_MESSAGE } from '../types/endpoint.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
|
@ -44,6 +44,42 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> {
|
|||
)
|
||||
}
|
||||
|
||||
async array(): Promise<Message[]> {
|
||||
let arr = await (this.client.cache.array(
|
||||
this.cacheName
|
||||
) as MessagePayload[])
|
||||
if (arr === undefined) arr = []
|
||||
|
||||
const result: Message[] = []
|
||||
await Promise.all(
|
||||
arr.map(async (raw) => {
|
||||
if (raw.author === undefined) return
|
||||
|
||||
let channel = await this.client.channels.get(raw.channel_id)
|
||||
if (channel === undefined)
|
||||
channel = await this.client.channels.fetch(raw.channel_id)
|
||||
if (channel === undefined) return
|
||||
|
||||
let author = ((await this.client.users.get(
|
||||
raw.author.id
|
||||
)) as unknown) as User
|
||||
|
||||
if (author === undefined) author = new User(this.client, raw.author)
|
||||
|
||||
const res = new Message(
|
||||
this.client,
|
||||
raw,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
channel as TextChannel,
|
||||
author
|
||||
)
|
||||
await res.mentions.fromPayload(raw)
|
||||
result.push(res)
|
||||
})
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
async fetch(id: string): Promise<Message> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
|
@ -57,18 +93,12 @@ export class MessagesManager extends BaseManager<MessagePayload, Message> {
|
|||
if (channel === undefined)
|
||||
channel = await this.client.channels.fetch(this.channel.id)
|
||||
|
||||
const author = new User(this.client, (data as MessagePayload).author)
|
||||
await this.client.users.set(
|
||||
author.id,
|
||||
data.author.id,
|
||||
(data as MessagePayload).author
|
||||
)
|
||||
|
||||
const res = new Message(
|
||||
this.client,
|
||||
data as MessagePayload,
|
||||
channel as TextChannel,
|
||||
author
|
||||
)
|
||||
const res = (await this.get(data.id)) as Message
|
||||
|
||||
await res.mentions.fromPayload(data)
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Presence } from '../structures/presence.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { PresenceUpdatePayload } from '../types/gateway.ts'
|
||||
import type { PresenceUpdatePayload } from '../types/gateway.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class GuildPresencesManager extends BaseManager<
|
||||
|
@ -27,6 +27,25 @@ export class GuildPresencesManager extends BaseManager<
|
|||
return presence
|
||||
}
|
||||
|
||||
async array(): Promise<Presence[]> {
|
||||
let arr = await (this.client.cache.array(
|
||||
this.cacheName
|
||||
) as PresenceUpdatePayload[])
|
||||
if (arr === undefined) arr = []
|
||||
|
||||
const result: Presence[] = []
|
||||
await Promise.all(
|
||||
arr.map(async (raw) => {
|
||||
let user = await this.client.users.get(raw.user.id)
|
||||
if (user === undefined) user = new User(this.client, raw.user)
|
||||
const guild = await this.client.guilds.get(raw.guild_id)
|
||||
if (guild === undefined) return
|
||||
result.push(new Presence(this.client, raw, user, guild))
|
||||
})
|
||||
)
|
||||
return result
|
||||
}
|
||||
|
||||
async fromPayload(
|
||||
data: PresenceUpdatePayload[]
|
||||
): Promise<GuildPresencesManager> {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { MessageReaction } from '../structures/messageReaction.ts'
|
||||
import type { User } from '../structures/user.ts'
|
||||
import { UsersManager } from './users.ts'
|
||||
|
||||
export class ReactionUsersManager extends UsersManager {
|
||||
|
@ -10,4 +11,14 @@ export class ReactionUsersManager extends UsersManager {
|
|||
this.cacheName = `reaction_users:${reaction.message.id}`
|
||||
this.reaction = reaction
|
||||
}
|
||||
|
||||
/** Remove all Users from this Reaction */
|
||||
async removeAll(): Promise<void> {
|
||||
await this.reaction.message.reactions.removeEmoji(this.reaction.emoji)
|
||||
}
|
||||
|
||||
/** Remove a specific User from this Reaction */
|
||||
async remove(user: User | string): Promise<void> {
|
||||
await this.reaction.message.reactions.removeUser(this.reaction.emoji, user)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import { Guild } from '../structures/guild.ts'
|
||||
import { Permissions } from '../../mod.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import type { Guild } from '../structures/guild.ts'
|
||||
import { Role } from '../structures/role.ts'
|
||||
import { GUILD_ROLE } from '../types/endpoint.ts'
|
||||
import { RolePayload } from '../types/role.ts'
|
||||
import { GUILD_ROLE, GUILD_ROLES } from '../types/endpoint.ts'
|
||||
import type { RoleModifyPayload, RolePayload } from '../types/role.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export interface CreateGuildRoleOptions {
|
||||
name?: string
|
||||
permissions?: number | string | Permissions
|
||||
color?: number | string
|
||||
hoist?: boolean
|
||||
mentionable?: boolean
|
||||
}
|
||||
|
||||
export class RolesManager extends BaseManager<RolePayload, Role> {
|
||||
guild: Guild
|
||||
|
||||
|
@ -13,22 +22,108 @@ export class RolesManager extends BaseManager<RolePayload, Role> {
|
|||
this.guild = guild
|
||||
}
|
||||
|
||||
async fetch(id: string): Promise<Role> {
|
||||
/** Fetch All Guild Roles */
|
||||
async fetchAll(): Promise<Role[]> {
|
||||
return await new Promise((resolve, reject) => {
|
||||
this.client.rest
|
||||
.get(GUILD_ROLE(this.guild.id, id))
|
||||
.then((data) => {
|
||||
this.set(id, data as RolePayload)
|
||||
resolve(new Role(this.client, data as RolePayload))
|
||||
this.client.rest.api.guilds[this.guild.id].roles.get
|
||||
.then(async (data: RolePayload[]) => {
|
||||
const roles: Role[] = []
|
||||
for (const raw of data) {
|
||||
await this.set(raw.id, raw)
|
||||
roles.push(new Role(this.client, raw, this.guild))
|
||||
}
|
||||
resolve(roles)
|
||||
})
|
||||
.catch((e) => reject(e))
|
||||
})
|
||||
}
|
||||
|
||||
async get(key: string): Promise<Role | undefined> {
|
||||
const raw = await this._get(key)
|
||||
if (raw === undefined) return
|
||||
return new Role(this.client, raw, this.guild)
|
||||
}
|
||||
|
||||
async array(): Promise<Role[]> {
|
||||
let arr = await (this.client.cache.array(this.cacheName) as RolePayload[])
|
||||
if (arr === undefined) arr = []
|
||||
return arr.map((e) => new Role(this.client, e, this.guild))
|
||||
}
|
||||
|
||||
async fromPayload(roles: RolePayload[]): Promise<boolean> {
|
||||
for (const role of roles) {
|
||||
await this.set(role.id, role)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/** Create a Guild Role */
|
||||
async create(data?: CreateGuildRoleOptions): Promise<Role> {
|
||||
if (typeof data?.color === 'string') {
|
||||
if (data.color.startsWith('#')) data.color = data.color.slice(1)
|
||||
}
|
||||
|
||||
const roleRaw = ((await this.client.rest.post(GUILD_ROLES(this.guild.id), {
|
||||
name: data?.name,
|
||||
permissions:
|
||||
data?.permissions === undefined
|
||||
? undefined
|
||||
: (typeof data.permissions === 'object'
|
||||
? data.permissions.bitfield
|
||||
: data.permissions
|
||||
).toString(),
|
||||
color:
|
||||
data?.color === undefined
|
||||
? undefined
|
||||
: typeof data.color === 'string'
|
||||
? isNaN(parseInt(data.color, 16))
|
||||
? 0
|
||||
: parseInt(data.color, 16)
|
||||
: data.color,
|
||||
hoist: data?.hoist ?? false,
|
||||
mentionable: data?.mentionable ?? false
|
||||
})) as unknown) as RolePayload
|
||||
|
||||
await this.set(roleRaw.id, roleRaw)
|
||||
return ((await this.get(roleRaw.id)) as unknown) as Role
|
||||
}
|
||||
|
||||
/** Delete a Guild Role */
|
||||
async delete(role: Role | string): Promise<Role | undefined> {
|
||||
const oldRole = await this.get(typeof role === 'object' ? role.id : role)
|
||||
|
||||
await this.client.rest.delete(
|
||||
GUILD_ROLE(this.guild.id, typeof role === 'object' ? role.id : role)
|
||||
)
|
||||
|
||||
return oldRole
|
||||
}
|
||||
|
||||
async edit(role: Role | string, options: RoleModifyPayload): Promise<Role> {
|
||||
if (role instanceof Role) {
|
||||
role = role.id
|
||||
}
|
||||
const resp: RolePayload = await this.client.rest.patch(
|
||||
GUILD_ROLE(this.guild.id, role),
|
||||
options
|
||||
)
|
||||
|
||||
return new Role(this.client, resp, this.guild)
|
||||
}
|
||||
|
||||
/** Modify the positions of a set of role positions for the guild. */
|
||||
async editPositions(
|
||||
...positions: Array<{ id: string | Role; position: number | null }>
|
||||
): Promise<RolesManager> {
|
||||
if (positions.length === 0)
|
||||
throw new Error('No role positions to change specified')
|
||||
|
||||
await this.client.rest.api.guilds[this.guild.id].roles.patch(
|
||||
positions.map((e) => ({
|
||||
id: typeof e.id === 'string' ? e.id : e.id.id,
|
||||
position: e.position ?? null
|
||||
}))
|
||||
)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Client } from '../models/client.ts'
|
||||
import type { Client } from '../client/mod.ts'
|
||||
import { User } from '../structures/user.ts'
|
||||
import { USER } from '../types/endpoint.ts'
|
||||
import { UserPayload } from '../types/user.ts'
|
||||
import type { UserPayload } from '../types/user.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
export class UsersManager extends BaseManager<UserPayload, User> {
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
import { User } from '../structures/user.ts'
|
||||
import { GatewayIntents } from '../types/gateway.ts'
|
||||
import { Gateway } from '../gateway/index.ts'
|
||||
import { RESTManager } from './rest.ts'
|
||||
import EventEmitter from 'https://deno.land/std@0.74.0/node/events.ts'
|
||||
import { DefaultCacheAdapter, ICacheAdapter } from './cacheAdapter.ts'
|
||||
import { UsersManager } from '../managers/users.ts'
|
||||
import { GuildManager } from '../managers/guilds.ts'
|
||||
import { ChannelsManager } from '../managers/channels.ts'
|
||||
import { ClientPresence } from '../structures/presence.ts'
|
||||
import { EmojisManager } from '../managers/emojis.ts'
|
||||
import { ActivityGame, ClientActivity } from '../types/presence.ts'
|
||||
import { ClientEvents } from '../gateway/handlers/index.ts'
|
||||
import { Extension } from './extensions.ts'
|
||||
import { SlashClient } from './slashClient.ts'
|
||||
import { Interaction } from '../structures/slash.ts'
|
||||
import { SlashModule } from './slashModule.ts'
|
||||
import type { ShardManager } from './shard.ts'
|
||||
|
||||
/** OS related properties sent with Gateway Identify */
|
||||
export interface ClientProperties {
|
||||
os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string
|
||||
browser?: 'harmony' | string
|
||||
device?: 'harmony' | string
|
||||
}
|
||||
|
||||
/** Some Client Options to modify behaviour */
|
||||
export interface ClientOptions {
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
forceNewSession?: boolean
|
||||
/** Startup presence of client */
|
||||
presence?: ClientPresence | ClientActivity | ActivityGame
|
||||
/** Force all requests to Canary API */
|
||||
canary?: boolean
|
||||
/** Time till which Messages are to be cached, in MS. Default is 3600000 */
|
||||
messageCacheLifetime?: number
|
||||
/** Time till which Message Reactions are to be cached, in MS. Default is 3600000 */
|
||||
reactionCacheLifetime?: number
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions?: boolean
|
||||
/** Client Properties */
|
||||
clientProperties?: ClientProperties
|
||||
/** Enable/Disable Slash Commands Integration (enabled by default) */
|
||||
enableSlash?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Discord Client.
|
||||
*/
|
||||
export class Client extends EventEmitter {
|
||||
/** Gateway object */
|
||||
gateway?: Gateway
|
||||
/** REST Manager - used to make all requests */
|
||||
rest: RESTManager = new RESTManager(this)
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
/** WebSocket ping of Client */
|
||||
ping = 0
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Cache Adapter */
|
||||
cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
forceNewSession?: boolean
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
messageCacheLifetime: number = 3600000
|
||||
/** Time till messages to stay cached, in MS. */
|
||||
reactionCacheLifetime: number = 3600000
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions: boolean = false
|
||||
/** Client Properties */
|
||||
clientProperties: ClientProperties
|
||||
/** Slash-Commands Management client */
|
||||
slash: SlashClient
|
||||
|
||||
users: UsersManager = new UsersManager(this)
|
||||
guilds: GuildManager = new GuildManager(this)
|
||||
channels: ChannelsManager = new ChannelsManager(this)
|
||||
emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Whether the REST Manager will use Canary API or not */
|
||||
canary: boolean = false
|
||||
/** Client's presence. Startup one if set before connecting */
|
||||
presence: ClientPresence = new ClientPresence()
|
||||
_decoratedEvents?: { [name: string]: (...args: any[]) => any }
|
||||
_decoratedSlash?: Array<{
|
||||
name: string
|
||||
guild?: string
|
||||
parent?: string
|
||||
group?: string
|
||||
handler: (interaction: Interaction) => any
|
||||
}>
|
||||
|
||||
_decoratedSlashModules?: SlashModule[]
|
||||
|
||||
private readonly _untypedOn = this.on
|
||||
|
||||
private readonly _untypedEmit = this.emit
|
||||
|
||||
public on = <K extends string>(event: K, listener: ClientEvents[K]): this =>
|
||||
this._untypedOn(event, listener)
|
||||
|
||||
public emit = <K extends string>(
|
||||
event: K,
|
||||
...args: Parameters<ClientEvents[K]>
|
||||
): boolean => this._untypedEmit(event, ...args)
|
||||
|
||||
/** Shard on which this Client is */
|
||||
shard: number = 0
|
||||
/** Shard Manager of this Client if Sharded */
|
||||
shardManager?: ShardManager
|
||||
|
||||
constructor(options: ClientOptions = {}) {
|
||||
super()
|
||||
this.token = options.token
|
||||
this.intents = options.intents
|
||||
this.forceNewSession = options.forceNewSession
|
||||
if (options.cache !== undefined) this.cache = options.cache
|
||||
if (options.presence !== undefined)
|
||||
this.presence =
|
||||
options.presence instanceof ClientPresence
|
||||
? options.presence
|
||||
: new ClientPresence(options.presence)
|
||||
if (options.canary === true) this.canary = true
|
||||
if (options.messageCacheLifetime !== undefined)
|
||||
this.messageCacheLifetime = options.messageCacheLifetime
|
||||
if (options.reactionCacheLifetime !== undefined)
|
||||
this.reactionCacheLifetime = options.reactionCacheLifetime
|
||||
if (options.fetchUncachedReactions === true)
|
||||
this.fetchUncachedReactions = true
|
||||
|
||||
if (
|
||||
this._decoratedEvents !== undefined &&
|
||||
Object.keys(this._decoratedEvents).length !== 0
|
||||
) {
|
||||
Object.entries(this._decoratedEvents).forEach((entry) => {
|
||||
this.on(entry[0], entry[1])
|
||||
})
|
||||
this._decoratedEvents = undefined
|
||||
}
|
||||
|
||||
this.clientProperties =
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties
|
||||
|
||||
this.slash = new SlashClient(this, {
|
||||
enabled: options.enableSlash
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets Cache Adapter
|
||||
*
|
||||
* Should NOT be set after bot is already logged in or using current cache.
|
||||
* Please look into using `cache` option.
|
||||
*/
|
||||
setAdapter(adapter: ICacheAdapter): Client {
|
||||
this.cache = adapter
|
||||
return this
|
||||
}
|
||||
|
||||
/** Changes Presence of Client */
|
||||
setPresence(presence: ClientPresence | ClientActivity | ActivityGame): void {
|
||||
if (presence instanceof ClientPresence) {
|
||||
this.presence = presence
|
||||
} else this.presence = new ClientPresence(presence)
|
||||
this.gateway?.sendPresence(this.presence.create())
|
||||
}
|
||||
|
||||
/** Emits debug event */
|
||||
debug(tag: string, msg: string): void {
|
||||
this.emit('debug', `[${tag}] ${msg}`)
|
||||
}
|
||||
|
||||
// TODO(DjDeveloperr): Implement this
|
||||
// fetchApplication(): Promise<Application>
|
||||
|
||||
/**
|
||||
* This function is used for connecting to discord.
|
||||
* @param token Your token. This is required.
|
||||
* @param intents Gateway intents in array. This is required.
|
||||
*/
|
||||
connect(token?: string, intents?: GatewayIntents[]): void {
|
||||
if (token === undefined && this.token !== undefined) token = this.token
|
||||
else if (this.token === undefined && token !== undefined) {
|
||||
this.token = token
|
||||
} else throw new Error('No Token Provided')
|
||||
if (intents !== undefined && this.intents !== undefined) {
|
||||
this.debug(
|
||||
'client',
|
||||
'Intents were set in both client and connect function. Using the one in the connect function...'
|
||||
)
|
||||
} else if (intents === undefined && this.intents !== undefined) {
|
||||
intents = this.intents
|
||||
} else if (intents !== undefined && this.intents === undefined) {
|
||||
this.intents = intents
|
||||
} else throw new Error('No Gateway Intents were provided')
|
||||
this.gateway = new Gateway(this, token, intents)
|
||||
}
|
||||
}
|
||||
|
||||
export function event(name?: string) {
|
||||
return function (client: Client | Extension, prop: string) {
|
||||
const listener = ((client as unknown) as {
|
||||
[name: string]: (...args: any[]) => any
|
||||
})[prop]
|
||||
if (typeof listener !== 'function')
|
||||
throw new Error('@event decorator requires a function')
|
||||
if (client._decoratedEvents === undefined) client._decoratedEvents = {}
|
||||
client._decoratedEvents[name === undefined ? prop : name] = listener
|
||||
}
|
||||
}
|
||||
|
||||
export function slash(name?: string, guild?: string) {
|
||||
return function (client: Client | SlashModule, prop: string) {
|
||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
client._decoratedSlash.push(item)
|
||||
} else
|
||||
client._decoratedSlash.push({
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function subslash(parent: string, name?: string, guild?: string) {
|
||||
return function (client: Client | SlashModule, prop: string) {
|
||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
item.parent = parent
|
||||
client._decoratedSlash.push(item)
|
||||
} else
|
||||
client._decoratedSlash.push({
|
||||
parent,
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function groupslash(
|
||||
parent: string,
|
||||
group: string,
|
||||
name?: string,
|
||||
guild?: string
|
||||
) {
|
||||
return function (client: Client | SlashModule, prop: string) {
|
||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||
const item = (client as { [name: string]: any })[prop]
|
||||
if (typeof item !== 'function') {
|
||||
item.parent = parent
|
||||
item.group = group
|
||||
client._decoratedSlash.push(item)
|
||||
} else
|
||||
client._decoratedSlash.push({
|
||||
group,
|
||||
parent,
|
||||
name: name ?? prop,
|
||||
guild,
|
||||
handler: item
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function slashModule() {
|
||||
return function (client: Client, prop: string) {
|
||||
if (client._decoratedSlashModules === undefined)
|
||||
client._decoratedSlashModules = []
|
||||
|
||||
const mod = ((client as unknown) as { [key: string]: any })[prop]
|
||||
client._decoratedSlashModules.push(mod)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue