Compare commits

...

199 Commits

Author SHA1 Message Date
iCrawl
6712de9752 chore(collection): release @discordjs/collection@0.8.0 2022-07-17 20:59:54 +02:00
iCrawl
28cd293f14 chore: update changelog 2022-07-17 19:42:10 +02:00
iCrawl
3f5690afe1 chore(builders): bump dev version 2022-07-17 19:31:32 +02:00
iCrawl
015ab69956 chore(builders): release @discordjs/builders@0.16.0 2022-07-17 19:28:12 +02:00
iCrawl
caecc574f0 chore: deps 2022-07-17 19:20:40 +02:00
BattleEye
3bf30b1e6d fix(PermissionOverwriteManager): mutates user (#8283) 2022-07-17 18:59:10 +02:00
DD
103a3584c9 refactor(rest): add content-type(s) to uploads (#8290) 2022-07-17 18:55:25 +02:00
Almeida
bf65b37d1a types: remove MemberMention (#8292) 2022-07-17 18:54:44 +02:00
Parbez
7e6dbaaed9 types: remove expect error (#8242) 2022-07-17 18:53:40 +02:00
CallMe AsYouFeel
1a6ddbbe7b fix(VoiceReceiver): parsePacket correctly (#8277) 2022-07-17 18:52:35 +02:00
Tiemen
32f9056b15 fix: slash command name regex (#8265) 2022-07-17 18:52:18 +02:00
Jiralite
3648f6d567 fix(GuildChannelManager): Access resolveId correctly (#8296) 2022-07-17 18:51:25 +02:00
Almeida
bddb6a461c chore: ignore index files in coverage (#8293)
Co-authored-by: ckohen <chaikohen@gmail.com>
2022-07-17 18:51:03 +02:00
Jiralite
fe34f48efb chore: Remove --cache (#8279) 2022-07-13 16:58:40 +02:00
Jeroen Claassens
30a8d3231f chore: update Git Cliff TOMLs (#8276) 2022-07-13 09:49:17 +02:00
Suneet Tipirneni
1ed605eaa4 feat(website): add extends clauses, enum members and automatic -types links (#8270)
* feat(website): add extends clauses, enum members and automatic -types links

* chore: remove vscode settings

* refactor: remove util file
2022-07-12 22:42:32 +02:00
muchnameless
787654816d fix(GuildChannelManager): edit lockPermissions (#8269) 2022-07-12 22:34:14 +02:00
Eejit
f0b68d5736 refactor: make GuildAuditLogsEntry.action return an AuditLogEvent (#8256) 2022-07-09 19:55:24 +02:00
Jiralite
75256153a9 types(GuildMemberManager): Fix placement for fetchMe() (#8258) 2022-07-09 19:43:02 +02:00
Suneet Tipirneni
33ae7df000 feat(website): add detailed property and method documentation (#8252)
Co-authored-by: Noel <buechler.noel@outlook.com>
2022-07-08 22:03:18 +02:00
Tyler Resch
feb3bdda0a types: convert Events to an enum (#8246) 2022-07-08 16:02:48 +02:00
Suneet Tipirneni
e78c9c9ee9 feat(website): show package members in a sidebar (#8245)
* feat(website): show package members in a sidebar

* fix: put response instead of loader

* Apply suggestions from code review

Co-authored-by: Noel <buechler.noel@outlook.com>

* chore: make requested changes

* refactor: make only package list scrollable

* feat: make sidebar mobile responsive

* fix: breakpoints for sidebar

Co-authored-by: Noel <buechler.noel@outlook.com>
2022-07-07 22:09:19 +02:00
MateoDeveloper
43f62bb667 docs(ApplicationCommand): add min_length and max_length to ApplicationCommandOptionData (#8239) 2022-07-07 20:46:06 +02:00
Parbez
96c8d21f95 feat(builder): add max min length in string option (#8214) 2022-07-07 20:45:32 +02:00
Anton Istomin
10ba0080cc fix(recorder-example): bump dependencies (#8123) 2022-07-06 22:25:10 +02:00
iCrawl
0b979b04f2 chore: add blocked label 2022-07-06 21:17:07 +02:00
Parbez
a4d1862982 refactor(builder): remove unsafe*Builders (#8074) 2022-07-06 20:42:51 +02:00
Suneet Tipirneni
34531c45e3 feat(website): add support for type parameter documentation (#8237) 2022-07-06 17:37:33 +02:00
Parbez
8198da5cd0 types(builder): remove casting (#8241) 2022-07-06 12:28:00 +02:00
iCrawl
ba10637529 chore: deps 2022-07-06 10:43:06 +02:00
iCrawl
68c3d8743e refactor(createApiModel): reusable function for api model creation 2022-07-05 17:00:44 +02:00
Almeida
a51f7215ec test(collection): improve coverage (#8222) 2022-07-05 16:10:42 +02:00
Jiralite
c271e05223 fix(SpeakingMap): Allow docgen to detect event name (#8236) 2022-07-05 16:10:23 +02:00
Almeida
d2e74003d5 chore: remove docgen test stuff (#8231) 2022-07-05 14:13:11 +02:00
ckohen
fd1c24036f fix(codecov): use cobertura (#8235) 2022-07-05 12:32:05 +02:00
ckohen
eb9ad46d4f fix(coverage): upload lcov instead of clover (#8234) 2022-07-05 10:52:38 +02:00
Almeida
5bd6b28b3e fix(Collection): make error messages consistent (#8224) 2022-07-04 17:40:33 +02:00
ckohen
f6db285c07 docs: add codecov coverage badge to readmes (#8226)
* docs: add codecov coverage badge to readmes

* docs: fix tab-space consistency
2022-07-04 14:00:27 +02:00
Almeida
68ade870f8 chore: upgrade vitest and add it as dep on each workspace (#8223) 2022-07-04 11:51:48 +02:00
Jiralite
c7a205f7b9 types(GuildMemberManager): Non-void return of edit() (#8186) 2022-07-03 18:04:29 +02:00
Almeida
c5750d59f5 refactor: make ShardEvents the events of Shard (#8185) 2022-07-03 18:04:19 +02:00
Suneet Tipirneni
31d5930464 fix(SelectMenuBuilder): properly accept SelectMenuOptionBuilders (#8174) 2022-07-03 18:04:08 +02:00
Jiralite
cdd9214212 fix: Remove global flag on regular expressions (#8177) 2022-07-03 15:36:53 +02:00
A. Román
fa010b5162 fix(MessagePayload): guard against repliedUser property (#8211) 2022-07-03 15:36:32 +02:00
A. Román
6b20645740 refactor(Util): make single replace call in cleanContent (#8210)
Co-authored-by: Almeida <almeidx@pm.me>
2022-07-03 15:36:20 +02:00
Almeida
50d55bd6b8 fix(ApplicationCommandManager): explicitly allow passing builders to methods (#8209) 2022-07-03 15:35:59 +02:00
Almeida
2d9dfa3c6e fix(TextInputBuilder): parse custom_id, label, and style (#8216) 2022-07-03 15:35:19 +02:00
Tyler Resch
ab238a9046 docs(MessageInteraction#commandName): updated description (#8212)
Co-authored-by: A. Román <kyradiscord@gmail.com>
2022-07-03 15:34:47 +02:00
Parbez
94ee60d3d4 feat(applicationCommand): add max min length in string option (#8215) 2022-07-03 15:34:04 +02:00
ckohen
f10f4cdcd8 feat: codecov (#8219) 2022-07-03 15:33:18 +02:00
Suneet Tipirneni
d95197cc78 feat: add website documentation early mvp (#8183)
Co-authored-by: iCrawl <buechler.noel@outlook.com>
2022-07-01 20:54:15 +02:00
Jiralite
e0c8282490 docs: Add missing @extends (#8205) 2022-07-01 17:48:11 +02:00
tnfAngel
741b3c8e27 docs: Remove Music bot in voice examples (#8203) 2022-07-01 10:54:11 +02:00
iCrawl
819a1fdf7d ci: check for main package before moving api-extractor docs 2022-06-30 16:17:52 +02:00
iCrawl
ce9afbb8e4 ci: fix building before generating docs 2022-06-30 15:56:07 +02:00
iCrawl
b2776c22d4 ci: api-extractor support for docs 2022-06-30 15:46:14 +02:00
DD
525bf031a5 chore: revert pre-commit hook to not change state (#8200) 2022-06-30 11:47:27 +02:00
CarelessInternet
27d8deb471 types: add missing shard types (#8180) 2022-06-30 00:37:55 +02:00
Xaliks
11b1739319 fix(GuildMemberRemove): remove member's presence (#8181) 2022-06-30 00:37:47 +02:00
Jiralite
b83e0c0caf types: Implement GuildChannelEditOptions (#8184) 2022-06-30 00:37:38 +02:00
Almeida
8421f9203b types(Status): add missing members (#8179) 2022-06-30 00:37:31 +02:00
A. Román
cb3dca4ae0 refactor(ApplicationCommandManager): use makeURLSearchParams (#8196) 2022-06-30 00:36:47 +02:00
MateoDeveloper
002d6a5aed feat(BaseInteraction): add support for app_permissions (#8194)
Co-authored-by: Almeida <almeidx@pm.me>
2022-06-30 00:36:32 +02:00
DD
c4653f97b1 feat(util): parseWebhookURL (#8166)
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
2022-06-25 11:02:11 +02:00
Suneet Tipirneni
e24970e3c3 refactor: Use Base prefix for channel and interaction base classes (#8099) 2022-06-24 10:42:50 +02:00
Tyler Resch
65dc8d677e types(GuildScheduledEvent#scheduledStartAt): should be nullish (#8111) 2022-06-24 10:42:39 +02:00
Almeida
0ffbef506a fix: edit() data can be partial and defaultMemberPermissions can be null (#8163) 2022-06-24 10:42:20 +02:00
iCrawl
5d3dd55a26 chore(changelog): add v13 changelog 2022-06-23 18:28:32 +02:00
Skick
70b42bb64a types(voice): bring back typed events (#8109) 2022-06-23 12:39:36 +02:00
Jan
af04992ed3 docs(Constants): fix SweeperKeys type (#8157) 2022-06-23 12:37:12 +02:00
ckohen
bbdb5d980b revert: refactor: move eslint.tsconfig.json to root (#8159) 2022-06-23 12:36:42 +02:00
Suneet Tipirneni
7279f9c31b types: fix modal builder constructor data type (#8143) 2022-06-22 20:37:46 +02:00
Jiralite
5e5853a4e8 docs(Channels): internally document channel creation (#8154) 2022-06-22 20:37:24 +02:00
Jiralite
cd17aad720 refactor(Constants): Remove leftover code (#8156)
* refactor(Constants): tidy up file

* docs(Constants): add type definition
2022-06-22 20:36:58 +02:00
Jiralite
ee36d60dc6 docs: Update threads to use ThreadAutoArchiveDuration (#8153) 2022-06-22 20:34:41 +02:00
SpaceEEC
c34c02ab8d fix(WebSocketShard): keep an error handler on connections (#8150) 2022-06-22 20:34:11 +02:00
Almeida
31f658247f fix(DJSError): error code validation (#8149) 2022-06-22 20:33:47 +02:00
Jiralite
a3799f9ebb types: Use ThreadAutoArchiveDuration from discord-api-types (#8145) 2022-06-22 11:23:39 +02:00
Jiralite
a061233510 docs(APITypes): Remove duplicate type definition (#8144) 2022-06-22 11:23:17 +02:00
Jiralite
203bc4a2cf docs: Document missing type definitions (#8130)
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
2022-06-22 11:23:03 +02:00
Parbez
65d1879c0a chore: move deps to root and some miscellaneous changes (#8129) 2022-06-21 15:15:38 +02:00
Parbez
c6f285b7b0 refactor: remove @sindresorhus/is as it's now esm only (#8133) 2022-06-21 14:41:33 +02:00
pat
2eeaad6f27 fix(vcs): nsfw property (#8132) 2022-06-21 14:35:42 +02:00
pat
f1ac17c961 docs(InteractionResponse): fix return (#8141) 2022-06-21 14:35:33 +02:00
Jiralite
db2b0333d9 fix(WebSocketManager): Correct error name (#8138) 2022-06-21 14:35:17 +02:00
DD
94f7ca9474 chore: update proxy-container README (#8122)
Co-authored-by: Almeida <almeidx@pm.me>
2022-06-20 14:47:19 +02:00
DD
e68effa822 refactor: errors (#8068)
Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
Co-authored-by: A. Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
2022-06-20 14:46:35 +02:00
iCrawl
358c3f4a46 chore: upgrade deps 2022-06-19 13:19:24 +02:00
DD
268a9b4be5 fix: vitest recursion (#8121) 2022-06-19 13:18:36 +02:00
KinectTheUnknown
95e6d6ede0 types(Shard#reconnecting): fix event name (#8118) 2022-06-19 12:24:04 +02:00
DD
17867f9154 fix(proxy-container): proper deps (#8120) 2022-06-19 12:17:47 +02:00
Jiralite
3a77ce0b18 docs(PermissionsBitField): Fix @name of bitfield (#8115)
docs(PermissionsBitField): `Permission` -> `PermissionsBitField`
2022-06-18 18:42:47 +02:00
Jiralite
23e183a9ac fix(WebSocketShard): Disconnected casing (#8117) 2022-06-18 18:42:30 +02:00
Parbez
b3c3a94d5d ci: don't run publish workflow on forks (#8116) 2022-06-18 18:42:09 +02:00
DD
8610700c87 fix(ci): log into dockerhub properly (#8114) 2022-06-18 13:59:05 +02:00
Jiralite
db663a55c2 docs: TextBasedChannel -> TextBasedChannels typos (#8110) 2022-06-18 13:02:41 +02:00
DD
0673ea377a fix(proxy-container): add @discordjs/rest to deps (#8113) 2022-06-18 13:02:12 +02:00
DD
2681929e42 feat: proxy container (#8000) 2022-06-17 23:29:50 +02:00
Jiralite
0a138dab95 docs: Remove numbers from enums (#8098) 2022-06-17 23:28:30 +02:00
Jiralite
415513696c docs(GuildAuditLogs): Fix and reimplement type definitions (#8108) 2022-06-17 23:27:54 +02:00
Almeida
90a98fee16 refactor(ClientOptions): remove $ prefix from ws.properties keys (#8094) 2022-06-17 23:26:42 +02:00
advaith
386c41f24f docs(WebSocketOptions): add version to docs and typings (#8050) 2022-06-17 23:26:08 +02:00
pat
d54bf5d286 fix(webhooks): revert webhook caching (and returning Message) (#8038) 2022-06-17 23:25:46 +02:00
Jiralite
e5ec1c4dbc refactor: Use GuildFeature enum (#8101) 2022-06-17 23:25:29 +02:00
Jiralite
ad9ab2b177 chore: Add MessageContent to issue form (#8106) 2022-06-17 23:24:26 +02:00
Rodry
4df491ce85 types(ApplicationCommand): fix typo in setDMPermission (#8097) 2022-06-16 09:59:46 +02:00
Jiralite
0a44b05db8 fix(ApplicationCommand): Remove autocomplete check at the top level and correctly check for dmPermission (#8100) 2022-06-16 09:58:54 +02:00
Almeida
b4e28a8ff6 types: fix setType() parameter and ChannelData.type (#8089) 2022-06-13 23:43:10 +02:00
Jiralite
9c0f190de1 docs(BaseGuildTextChannel): Update setType()'s parameter type (#8088) 2022-06-13 22:09:00 +02:00
BaumianerNiklas
093117d938 chore: remove -types import in readme example (#8085) 2022-06-13 21:42:28 +02:00
Suneet Tipirneni
e53d162198 refactor(util): make utility functions top level (#8052)
* refactor(util): make functions top level

* types: make channel typeguards more strict

* chore: make requested changes
2022-06-13 20:04:53 +02:00
Almeida
51eadf3737 docs: update outdated examples (#8081) 2022-06-13 18:18:15 +02:00
Jiralite
552ec72542 docs(ThreadMemberManager): Require member in FetchThreadMemberOptions (#8079) 2022-06-13 18:17:44 +02:00
Ben
ac7bf692bf docs(AutocompleteInteraction): change useless log in responds example (#8077) 2022-06-13 18:16:54 +02:00
n1ck_pro
9964454c29 types: fix ApplicationCommandPermissionsUpdate event typings (#8071) 2022-06-13 18:16:07 +02:00
Rodry
476b7d519c types(AutocompleteOption): fix and improve types (#8069) 2022-06-13 18:15:31 +02:00
Jiralite
10a6c4287d feat(AutocompleteInteraction): Add commandGuildId (#8086) 2022-06-13 18:14:01 +02:00
Jiralite
a2eebf6c66 docs: Description and missing @typedef fixes (#8087) 2022-06-13 18:12:08 +02:00
CodeGoat
96053babe1 fix(ApplicationCommand): fix default member permissions assignment (#8067) 2022-06-11 11:05:43 +02:00
ckohen
f527dea36e fix(scripts): read directory and rerun (#8065) 2022-06-11 11:04:53 +02:00
ckohen
c7391db11b refactor(ApplicationCommand): permissions v2 (#7857)
Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
2022-06-10 21:34:17 +02:00
Suneet Tipirneni
c5176be14b feat(guild): add support for setting MFA level (#8024) 2022-06-10 21:26:11 +02:00
Parbez
fbe67e1025 fix: select menu options to accept both rest and array (#8032)
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
2022-06-10 21:22:04 +02:00
Parbez
3bb9c0e5c3 fix: check for function type (#8064) 2022-06-10 21:21:45 +02:00
DD
9c8b3102ce refactor(*): include name/reason/etc fields into options/data params (#8026) 2022-06-10 21:20:59 +02:00
Jiralite
2392a6f5de types(ThreadMemberManager): Fix return type of fetching members with no arguments (#8060)
* types(ThreadMemberManager): non-optional first overload for fetching

* test: organisation
2022-06-10 21:20:00 +02:00
Parbez
c0f079d232 typings: remove isAutocomplete typeguard from typings (#8063) 2022-06-10 21:19:44 +02:00
Jiralite
d8077c6839 fix(CommandInteractionOptionResolver): Handle autocompletion interactions (#8058) 2022-06-10 21:19:27 +02:00
iCrawl
d4b41dd081 feat(docgen): proper event parsing for typescript 2022-06-10 16:22:11 +02:00
pat
0415300243 feat(vcs): add missing property and methods (#8002) 2022-06-10 13:42:53 +02:00
Jeroen Claassens
caee94897f build: fix git cliff config files (#8057) 2022-06-10 13:37:24 +02:00
iCrawl
b3346f4b9b feat(docgen): update typedoc 2022-06-10 12:59:46 +02:00
iCrawl
2791c86cdf chore: remove unneeded tsc build 2022-06-09 21:27:06 +02:00
Almeida
c8f1690896 refactor(collection): remove default property (#8055) 2022-06-09 20:53:23 +02:00
Jiralite
da9107c007 refactor(ThreadMemberManager): Consistent thread member fetching (#8021) 2022-06-09 20:52:56 +02:00
Suneet Tipirneni
f57d6768ad refactor(interactions): remove redundant interaction typeguards (#8027) 2022-06-09 20:52:44 +02:00
iCrawl
d7b8357dcb fix(docgen): strip dots from return types 2022-06-09 20:47:51 +02:00
A. Román
16810f3e41 refactor(collection): remove default export (#8053) 2022-06-09 16:53:01 +02:00
A. Román
598f61b992 fix(scripts): add quotes around blob arguments (#8054) 2022-06-09 16:52:18 +02:00
iCrawl
50401453e7 fix(docgen): shorten output for path info 2022-06-09 16:14:37 +02:00
Almeida
94bdcaca62 docs: ignore docs of unexported functions (#8051) 2022-06-09 13:58:42 +02:00
iCrawl
eea139b346 feat(docgen): support for ignore tag 2022-06-09 13:54:35 +02:00
iCrawl
50822f5254 fix(docgen): parsing constructor 2022-06-09 11:37:22 +02:00
muchnameless
f2b267c079 fix(Message): force fetching (#8047) 2022-06-09 10:35:52 +02:00
advaith
b2eea1c900 docs(ClientOptions): fix closeTimeout default (#8049) 2022-06-09 10:35:27 +02:00
iCrawl
3ae2633c3f feat(website): add some styling 2022-06-08 23:40:03 +02:00
iCrawl
3937b402c0 fix(website): don't build concurrently 2022-06-08 22:26:39 +02:00
iCrawl
256c4f955c feat(website): unocss 2022-06-08 21:45:16 +02:00
iCrawl
33cdcdbb7a fix: vercel deployment 2022-06-08 20:09:23 +02:00
iCrawl
bc466a5997 fix(website): deployment 2022-06-08 19:36:09 +02:00
Noel
127931d1df feat: website (#8043) 2022-06-08 19:13:31 +02:00
iCrawl
5259639c2c fix: doc generation for return types 2022-06-08 19:12:42 +02:00
iCrawl
5de9b80814 fix: returns for typescript parsing 2022-06-08 18:41:22 +02:00
iCrawl
bc4fbcef2e fix(serializer): properly serialize data 2022-06-08 18:11:25 +02:00
iCrawl
3279b40912 feat(docgen): typescript support 2022-06-08 17:26:54 +02:00
Synbulat Biishev
1afae909d7 fix(Attachment): do not destructure data (#8041) 2022-06-08 13:30:06 +02:00
iCrawl
65cb36166f fix(VarType): parsing type names 2022-06-08 13:27:41 +02:00
Superchupu
ecc6600df2 refactor(docgen): use the node: protocol (#8034)
Co-authored-by: Synbulat Biishev <syjalo.dev@gmail.com>
2022-06-08 00:17:48 +02:00
iCrawl
314d76e907 fix(docgen): fix up method return types 2022-06-07 15:19:42 +02:00
Parbez
769ea0bfe7 refactor: move all the config files to root (#8033) 2022-06-07 12:35:19 +02:00
Noel
8b979c0245 feat: docgen package (#8029) 2022-06-07 10:51:33 +02:00
Noel
5475767c2c build: use cliff-jumper for releases (#8030) 2022-06-07 00:59:21 +02:00
Jiralite
86d8fbc023 fix(DirectoryChannel): Type name and handle url (#8023) 2022-06-06 15:47:13 +02:00
iCrawl
fba9710fc9 chore(builders): dev version 2022-06-06 14:45:46 +02:00
iCrawl
5ca3974301 chore(release): version 2022-06-06 14:44:25 +02:00
iCrawl
3202c91c5a chore: properly adapt changelog script 2022-06-06 14:43:15 +02:00
Jiralite
f3f34f07b3 docs(DirectoryChannel): Extend Channel (#8022) 2022-06-06 14:24:35 +02:00
Parbez
f8ed71bfca fix: readd isThread type guard (#8019) 2022-06-06 14:24:03 +02:00
Voxelli
e1176faa27 feat: backport handle zombie connection (#7626)
* feat: backport zombie connection fixes

* fix: enums

* fix: prettier

* feat: add zombie connection event to shard events

* Apply suggestions from code review

Co-authored-by: muchnameless <12682826+muchnameless@users.noreply.github.com>

* fix: prettier

* fix: handleZombieConnection

* feat: backport new logic of handling zombie connection

Co-authored-by: muchnameless <12682826+muchnameless@users.noreply.github.com>
2022-06-06 12:21:35 +02:00
Caleb Lam
aa59a409b3 feat(CommandInteraction): add 'commandGuildId' (#8018) 2022-06-06 10:07:37 +02:00
Rodry
7fa698d23e types(AttachmentBuilder): fix data type (#8016)
* types(AttachmentBuilder): fix data type

* types(MessageOptions): add AttachmentPayload
2022-06-06 10:07:15 +02:00
markisha64
b9df37a89f refact: Sticker's tags property (#8010) 2022-06-05 23:29:31 +02:00
Rodry
ad75be9a9c feat: allow builders to accept rest params and arrays (#7874)
Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
Co-authored-by: Khafra <42794878+KhafraDev@users.noreply.github.com>
2022-06-05 23:29:16 +02:00
Suneet Tipirneni
70c733bb9a refactor(channel): remove redundant channel type guards (#8012) 2022-06-05 23:29:05 +02:00
Rodry
a3287782b5 feat(MessageReaction): add react method (#7810) 2022-06-05 23:28:44 +02:00
Suneet Tipirneni
0ccc243c8f types(modal): fix showModal() typings (#8014) 2022-06-05 23:28:28 +02:00
Almeida
0a7953e463 docs(Attachment): remove constructor doc (#8009) 2022-06-05 23:28:18 +02:00
Parbez
6aa623240e types: fix some attachment related typings (#8013) 2022-06-05 22:37:56 +02:00
n1ck_pro
6266b0c1e3 types(AttachmentBuilder): remove name parameter from constructor (#8008) 2022-06-05 22:37:01 +02:00
Suneet Tipirneni
9720e55534 refactor: always return Message instances in interactions (#7917)
Co-authored-by: Almeida <almeidx@pm.me>
2022-06-05 19:00:35 +02:00
Parbez
ad36c0be77 fix: add static method from in builders (#7990) 2022-06-05 18:33:19 +02:00
SpaceEEC
5987dbe5cf docs(VoiceChannel): annotate that it is implementing TextBasedChannel (#8007) 2022-06-05 18:30:14 +02:00
Rodry
5244fe3c1c feat(Collector): add ignore event (#7644)
Co-authored-by: Parbez <imranbarbhuiya.fsd@gmail.com>
2022-06-05 18:29:13 +02:00
Synbulat Biishev
349766dd69 feat(GuildMemberManager): add GuildMemberManager#fetchMe() (#7526) 2022-06-05 18:29:00 +02:00
markisha64
7a1095b66b fix: typings (#7965)
Co-authored-by: Almeida <almeidx@pm.me>
2022-06-05 09:36:59 +02:00
iCrawl
10009a43ee ci: remove cache-dir from test 2022-06-05 01:14:39 +02:00
iCrawl
8d8e6c03de feat: use vitest instead of jest for more speed 2022-06-05 01:07:10 +02:00
Suneet Tipirneni
dfadcbc2fd refactor(attachment): don't return attachment builders from API (#7852)
Co-authored-by: Almeida <almeidx@pm.me>
Co-authored-by: A. Román <kyradiscord@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
2022-06-04 22:33:13 +02:00
n1ck_pro
546d48655f docs: add missing discord-api-types external types (#8001) 2022-06-04 22:27:01 +02:00
iCrawl
f8739bd9c0 fix(voice): re-add accidental removal of postbuild script 2022-06-04 19:15:28 +02:00
iCrawl
f2ae1f9348 feat: add scripts package for locally used scripts 2022-06-04 19:07:50 +02:00
iCrawl
3401fd4eb6 chore: remove leftover files from pre-monorepo era 2022-06-04 18:41:37 +02:00
iCrawl
fdbd229832 ci: equality check on tag and package name 2022-06-04 17:57:07 +02:00
iCrawl
9e8e2411c1 ci: check for the correct prop to be defined 2022-06-04 17:51:31 +02:00
iCrawl
c02ced9a22 ci: fix if checks for branch/tag when using workflow dispatch 2022-06-04 17:37:00 +02:00
iCrawl
8c5a7f80ef ci: fix inputs for workflow dispatches 2022-06-04 17:15:08 +02:00
iCrawl
14018b0118 ci: allow workflow dispatches 2022-06-04 17:13:40 +02:00
iCrawl
8095723604 chore: only build cjs for actions 2022-06-04 17:08:26 +02:00
iCrawl
e17bb54c85 chore: update dev versions 2022-06-04 17:01:47 +02:00
495 changed files with 24210 additions and 8400 deletions

38
.dockerignore Normal file
View File

@@ -0,0 +1,38 @@
# Packages
node_modules/
# Log files
logs/
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Env
.env
# Dist
dist/
# Miscellaneous
.tmp/
.vscode/*
!.vscode/extensions.json
!.vscode/settings.json
.idea/
.DS_Store
.turbo
tsconfig.tsbuildinfo
# yarn
.pnp.*
.yarn/*
!.yarn/cache
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

11
.eslintrc.json Normal file
View File

@@ -0,0 +1,11 @@
{
"root": true,
"extends": "marine/prettier/node",
"parserOptions": {
"project": "./tsconfig.eslint.json"
},
"ignorePatterns": ["**/dist/*"],
"env": {
"jest": true
}
}

View File

@@ -16,6 +16,7 @@ body:
- collection
- rest
- proxy
- proxy-container
- voice
validations:
required: true
@@ -127,6 +128,7 @@ body:
- DirectMessages
- DirectMessageReactions
- DirectMessageTyping
- MessageContent
- GuildScheduledEvents
multiple: true
validations:

View File

@@ -18,6 +18,7 @@ body:
- collection
- rest
- proxy
- proxy-container
- voice
validations:
required: true

12
.github/labeler.yml vendored
View File

@@ -14,10 +14,18 @@ chore:
- packages/discord.js/*
- packages/discord.js/**/*
'packages:docgen':
- packages/docgen/*
- packages/docgen/**/*
'packages:proxy':
- packages/proxy/*
- packages/proxy/**/*
'packages:proxy-container':
- packages/proxy-container/*
- packages/proxy-container/**/*
'packages:rest':
- packages/rest/*
- packages/rest/**/*
@@ -26,6 +34,10 @@ chore:
- packages/voice/*
- packages/voice/**/*
'packages:website':
- packages/website/*
- packages/website/**/*
'packages:ws':
- packages/ws/*
- packages/ws/**/*

8
.github/labels.yml vendored
View File

@@ -4,6 +4,8 @@
color: '5663e9'
- name: 'backlog'
color: '7ef7ef'
- name: 'blocked'
color: 'fc1423'
- name: 'bug'
color: 'd73a4a'
- name: 'caching'
@@ -50,14 +52,20 @@
color: 'fbca04'
- name: 'packages:discord.js'
color: 'fbca04'
- name: 'packages:docgen'
color: 'fbca04'
- name: 'packages:proxy'
color: 'fbca04'
- name: 'packages:proxy-container'
color: 'fbca04'
- name: 'packages:rest'
color: 'fbca04'
- name: 'packages:voice'
color: 'fbca04'
- name: 'packages:ws'
color: 'fbca04'
- name: 'packages:website'
color: 'fbca04'
- name: 'performance'
color: '80c042'
- name: 'permissions'

View File

@@ -10,10 +10,10 @@ jobs:
if: github.repository_owner == 'discordjs'
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install node.js v16
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'

View File

@@ -7,6 +7,11 @@ on:
- '!docs'
tags:
- '**'
workflow_dispatch:
inputs:
ref:
description: 'The branch, tag or SHA to checkout'
required: true
jobs:
build:
name: Build documentation
@@ -18,10 +23,12 @@ jobs:
SHA: ${{ steps.env.outputs.SHA }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: ${{ github.event.inputs.ref || '' }}
- name: Install node.js v16
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
@@ -29,7 +36,7 @@ jobs:
- name: Turbo cache
id: turbo-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
@@ -39,15 +46,24 @@ jobs:
- name: Install dependencies
run: yarn --immutable
- name: Build dependencies
run: yarn build --cache-dir=".turbo"
- name: Build docs
run: yarn docs --cache-dir=".turbo"
- name: Upload artifacts
uses: actions/upload-artifact@v2
- name: Upload docgen artifacts
uses: actions/upload-artifact@v3
with:
name: docs
name: docgen
path: packages/*/docs/docs.json
- name: Upload api-extractor artifacts
uses: actions/upload-artifact@v3
with:
name: api-extractor
path: packages/*/docs/docs.api.json
- name: Set outputs for upload job
id: env
run: |
@@ -65,15 +81,15 @@ jobs:
package: ['builders', 'collection', 'discord.js', 'proxy', 'rest', 'voice']
runs-on: ubuntu-latest
env:
BRANCH_NAME: ${{ needs.build.outputs.BRANCH_NAME }}
BRANCH_NAME: ${{ github.event.inputs.ref || needs.build.outputs.BRANCH_NAME }}
BRANCH_OR_TAG: ${{ needs.build.outputs.BRANCH_OR_TAG }}
SHA: ${{ needs.build.outputs.SHA }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install node.js v16
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
@@ -81,7 +97,7 @@ jobs:
- name: Turbo cache
id: turbo-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
@@ -94,42 +110,54 @@ jobs:
- name: Build actions
run: yarn build --cache-dir=".turbo"
- name: Download artifacts
uses: actions/download-artifact@v2
- name: Download docgen artifacts
uses: actions/download-artifact@v3
with:
name: docs
name: docgen
path: docs
- name: Download api-extractor artifacts
uses: actions/download-artifact@v3
with:
name: api-extractor
path: docs
- name: Checkout docs repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: 'discordjs/docs'
token: ${{ secrets.DJS_DOCS }}
path: 'out'
- name: Extract package and semver from tag
if: env.BRANCH_OR_TAG == 'tag'
if: ${{ github.event.inputs.ref || env.BRANCH_OR_TAG == 'tag' }}
id: extract-tag
uses: ./packages/actions/src/formatTag
with:
tag: ${{ env.BRANCH_NAME }}
- name: Move docs to correct directory
if: env.BRANCH_OR_TAG == 'tag'
if: ${{ (github.event.inputs.ref || env.BRANCH_OR_TAG == 'tag') && matrix.package == steps.extract-tag.outputs.package }}
env:
PACKAGE: ${{ steps.extract-tag.outputs.package }}
SEMVER: ${{ steps.extract-tag.outputs.semver }}
run: |
mkdir -p out/${PACKAGE}
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${SEMVER}.json
if [[ $PACKAGE != "discord.js" ]]; then
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${SEMVER}.api.json
fi
- name: Move docs to correct directory
if: env.BRANCH_OR_TAG == 'branch'
if: ${{ !github.event.inputs.ref && env.BRANCH_OR_TAG == 'branch' }}
env:
PACKAGE: ${{ matrix.package }}
run: |
mkdir -p out/${PACKAGE}
mv docs/${PACKAGE}/docs/docs.json out/${PACKAGE}/${BRANCH_NAME}.json
if [[ $PACKAGE != "discord.js" ]]; then
mv docs/${PACKAGE}/docs/docs.api.json out/${PACKAGE}/${BRANCH_NAME}.api.json
fi
- name: Commit and push
run: |

View File

@@ -14,9 +14,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Label sync
uses: crazy-max/ghaction-github-labeler@v3
uses: crazy-max/ghaction-github-labeler@v4
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Automatically label PR
uses: actions/labeler@v3
uses: actions/labeler@v4
with:
repo-token: '${{ secrets.GITHUB_TOKEN }}'
sync-labels: true

View File

@@ -0,0 +1,26 @@
name: Publish dev Docker Images
on:
workflow_dispatch:
schedule:
- cron: '0 */12 * * *'
jobs:
docker:
name: Docker
runs-on: ubuntu-latest
if: github.repository_owner == 'discordjs'
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build the image
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
- name: Push image to DockerHub
run: docker push discordjs/proxy:latest

View File

@@ -26,10 +26,10 @@ jobs:
if: github.repository_owner == 'discordjs'
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install node.js v16
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
@@ -38,7 +38,7 @@ jobs:
- name: Turbo cache
id: turbo-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}

26
.github/workflows/publish-docker.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Publish Docker Images
on:
workflow_dispatch:
jobs:
docker:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
run: echo ${{ secrets.DOCKER_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
- name: Build the image
run: docker build -t discordjs/proxy:latest -f packages/proxy-container/Dockerfile .
- name: Tag the image with major
run: docker tag discordjs/proxy discordjs/proxy:$(cut -d '.' -f1 <<< $(jq --raw-output '.version' packages/proxy-container/package.json))
- name: Push image to DockerHub
run: docker push --all-tags discordjs/proxy

View File

@@ -6,10 +6,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install node.js v16
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
@@ -17,7 +17,7 @@ jobs:
- name: Turbo cache
id: turbo-cache
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: .turbo
key: turbo-${{ github.job }}-${{ github.ref_name }}-${{ github.sha }}
@@ -31,7 +31,11 @@ jobs:
run: yarn lint --cache-dir=".turbo"
- name: Tests
run: yarn test --cache-dir=".turbo"
run: yarn test
- name: Upload Coverage
uses: ./packages/actions/src/uploadCoverage
if: github.repository_owner == 'discordjs'
- name: Build
run: yarn build --cache-dir=".turbo"

5
.gitignore vendored
View File

@@ -26,6 +26,7 @@ dist/
.DS_Store
.turbo
tsconfig.tsbuildinfo
coverage/
# yarn
.pnp.*
@@ -35,3 +36,7 @@ tsconfig.tsbuildinfo
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Cache
.prettiercache
.eslintcache

View File

@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn format
yarn build && yarn lint && yarn test

View File

@@ -7,6 +7,7 @@
"codezombiech.gitignore",
"eamodio.gitlens",
"christian-kohler.npm-intellisense",
"christian-kohler.path-intellisense"
"christian-kohler.path-intellisense",
"antfu.unocss"
]
}

View File

@@ -6,7 +6,5 @@
"source.fixAll": true,
"source.organizeImports": false
},
"files.exclude": {
"**/node_modules": true
}
"unocss.root": "./packages/website"
}

View File

@@ -9,6 +9,7 @@
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/v/discord.js.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/discord.js"><img src="https://img.shields.io/npm/dt/discord.js.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Tests status" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2" alt="Code coverage" /></a>
</p>
</div>
@@ -45,16 +46,16 @@ pnpm add discord.js
Install all required dependencies:
```sh-session
npm install discord.js @discordjs/rest discord-api-types
yarn add discord.js @discordjs/rest discord-api-types
pnpm add discord.js @discordjs/rest discord-api-types
npm install discord.js @discordjs/rest
yarn add discord.js @discordjs/rest
pnpm add discord.js @discordjs/rest
```
Register a slash command against the Discord API:
```js
const { REST } = require('@discordjs/rest');
const { Routes } = require('discord-api-types/v10');
const { Routes } = require('discord.js');
const commands = [
{
@@ -89,7 +90,7 @@ client.on('ready', () => {
});
client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply('Pong!');
@@ -101,7 +102,7 @@ client.login('token');
## Links
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/website))
- [Documentation](https://discord.js.org/#/docs)
- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide))
See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v13.html), including updated and removed items in the library.

26
codecov.yml Normal file
View File

@@ -0,0 +1,26 @@
codecov:
notify:
after_n_builds: 6
strict_yaml_branch: main
coverage:
range: '50...90'
status:
project:
default:
target: auto
threshold: 5%
informational: true
patch: off
flag_management:
default_rules:
statuses:
- type: project
target: auto
threshold: 2%
informational: true
comment:
require_changes: true
after_n_builds: 6

View File

@@ -38,13 +38,23 @@
},
"homepage": "https://discord.js.org",
"devDependencies": {
"@commitlint/cli": "^17.0.2",
"@commitlint/config-angular": "^17.0.0",
"@commitlint/cli": "^17.0.3",
"@commitlint/config-angular": "^17.0.3",
"@favware/cliff-jumper": "^1.8.5",
"@favware/npm-deprecate": "^1.0.4",
"@typescript-eslint/eslint-plugin": "^5.30.6",
"@typescript-eslint/parser": "^5.30.6",
"conventional-changelog-cli": "^2.2.2",
"eslint": "^8.20.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-import-resolver-typescript": "^3.2.7",
"eslint-plugin-import": "^2.26.0",
"husky": "^8.0.1",
"prettier": "^2.6.2",
"turbo": "^1.2.16"
"is-ci": "^3.0.1",
"prettier": "^2.7.1",
"turbo": "^1.3.1",
"typescript": "^4.7.4"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -1,12 +1,3 @@
{
"root": true,
"extends": "marine/prettier/node",
"parserOptions": {
"project": "./tsconfig.eslint.json",
"extraFileExtensions": [".mjs"]
},
"ignorePatterns": ["**/dist/*"],
"env": {
"jest": true
}
"extends": "../../.eslintrc.json"
}

View File

@@ -6,3 +6,4 @@ docs/**/*
!docs/index.yml
!docs/README.md
coverage/
tsup.config.*.mjs

View File

@@ -0,0 +1 @@
module.exports = require('../../.prettierrc.json');

View File

@@ -175,8 +175,7 @@
END OF TERMS AND CONDITIONS
Copyright 2021 Noel Buechler
Copyright 2021 Vlad Frangu
Copyright 2022 Noel Buechler
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -6,45 +6,23 @@
<br />
<p>
<a href="https://discord.gg/djs"><img src="https://img.shields.io/discord/222078108977594368?color=5865F2&logo=discord&logoColor=white" alt="Discord server" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/v/@discordjs/builders.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/dt/@discordjs/builders.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://codecov.io/gh/discordjs/builders"><img src="https://codecov.io/gh/discordjs/builders/branch/main/graph/badge.svg" alt="Code coverage" /></a>
</p>
</div>
## Installation
**Node.js 16.9.0 or newer is required.**
```sh-session
npm install @discordjs/builders
yarn add @discordjs/builders
pnpm add @discordjs/builders
```
## Examples
Here are some examples for the builders and utilities you can find in this package:
- [Slash Command Builders](https://github.com/discordjs/discord.js/blob/main/packages/builders/docs/examples/Slash%20Command%20Builders.md)
## Links
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
- [Documentation](https://discord.js.org/#/docs/builders)
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/website))
- [Documentation](https://discord.js.org/#/docs)
- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide))
See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v13.html), including updated and removed items in the library.
- [discord.js Discord server](https://discord.gg/djs)
- [Discord API Discord server](https://discord.gg/discord-api)
- [GitHub](https://github.com/discordjs/discord.js/tree/main/packages/builders)
- [npm](https://www.npmjs.com/package/@discordjs/builders)
- [GitHub](https://github.com/discordjs/discord.js/tree/main/packages/scripts)
- [Related libraries](https://discord.com/developers/docs/topics/community-resources#libraries)
## Contributing
Before creating an issue, please ensure that it hasn't already been reported/suggested, and double-check the
[documentation](https://discord.js.org/#/docs/builders).
See [the contribution guide](https://github.com/discordjs/discord.js/blob/main/.github/CONTRIBUTING.md) if you'd like to submit a PR.
## Help

View File

@@ -1,3 +1,4 @@
import { describe, test, expect } from 'vitest';
import { formatTag } from '../src';
describe('Format Tag', () => {

View File

@@ -1,18 +0,0 @@
/**
* @type {import('@babel/core').TransformOptions}
*/
module.exports = {
parserOpts: { strictMode: true },
sourceMaps: 'inline',
presets: [
[
'@babel/preset-env',
{
targets: { node: 'current' },
modules: 'commonjs',
},
],
'@babel/preset-typescript',
],
plugins: ['babel-plugin-transform-typescript-metadata', ['@babel/plugin-proposal-decorators', { legacy: true }]],
};

View File

@@ -1,10 +0,0 @@
coverage:
status:
project:
default:
target: 70%
threshold: 5%
patch:
default:
target: 70%
threshold: 5%

View File

@@ -1,19 +0,0 @@
/**
* @type {import('@jest/types').Config.InitialOptions}
*/
module.exports = {
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'clover'],
coverageThreshold: {
global: {
branches: 70,
lines: 70,
statements: 70,
},
},
coveragePathIgnorePatterns: ['src/index.ts', 'src/formatTag/index.ts'],
};

View File

@@ -4,8 +4,8 @@
"description": "A set of actions that we use for our workflows",
"private": true,
"scripts": {
"test": "vitest run",
"build": "tsup",
"test": "jest --pass-with-no-tests",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix"
},
@@ -44,28 +44,17 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@actions/core": "^1.8.2",
"@actions/core": "^1.9.0",
"tslib": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
"@types/node": "^16.11.45",
"c8": "^7.11.3",
"eslint": "^8.20.0",
"prettier": "^2.7.1",
"tsup": "^6.1.3",
"typescript": "^4.7.4",
"vitest": "^0.18.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -11,4 +11,4 @@ outputs:
description: 'The semver string that was extracted from this tag'
runs:
using: node16
main: ../../dist/formatTag/index.mjs
main: ../../dist/formatTag/index.js

View File

@@ -4,7 +4,7 @@ import { formatTag } from './formatTag';
const tag = getInput('tag', { required: true });
const parsed = formatTag(tag);
if (parsed?.groups) {
if (parsed) {
setOutput('package', parsed.package);
setOutput('semver', parsed.semver);
}

View File

@@ -0,0 +1,52 @@
name: 'Upload Coverage'
description: 'Uploads code coverage reports to codecov with separate flags for separate packages'
runs:
using: 'composite'
steps:
- name: Upload Builders Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/builders/coverage/cobertura-coverage.xml
flags: builders
- name: Upload Collection Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/collection/coverage/cobertura-coverage.xml
flags: collection
- name: Upload Discord.js Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/discord.js/coverage/cobertura-coverage.xml
flags: discord.js
- name: Upload Proxy Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/proxy/coverage/cobertura-coverage.xml
flags: proxy
- name: Upload Rest Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/rest/coverage/cobertura-coverage.xml
flags: rest
- name: Upload Voice Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/voice/coverage/cobertura-coverage.xml
flags: voice
- name: Upload Website Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/website/coverage/cobertura-coverage.xml
flags: website
- name: Upload Utilities Coverage
uses: codecov/codecov-action@v3
with:
files: ./packages/actions/coverage/cobertura-coverage.xml, ./packages/scripts/coverage/cobertura-coverage.xml
flags: utilities

View File

@@ -1,13 +1,9 @@
import { defineConfig } from 'tsup';
import { createTsupConfig } from '../../tsup.config';
export default defineConfig({
clean: true,
dts: true,
entryPoints: ['src/index.ts', 'src/formatTag/index.ts'],
format: ['esm'],
minify: true,
export default createTsupConfig({
entry: ['src/index.ts', 'src/formatTag/index.ts'],
format: ['cjs'],
skipNodeModulesBundle: false,
noExternal: ['@actions/core'],
sourcemap: true,
target: 'es2021',
minify: true,
});

View File

@@ -0,0 +1,5 @@
{
"name": "builders",
"org": "discordjs",
"packagePath": "packages/builders"
}

View File

@@ -1,12 +1,3 @@
{
"root": true,
"extends": "marine/prettier/node",
"parserOptions": {
"project": "./tsconfig.eslint.json",
"extraFileExtensions": [".mjs"]
},
"ignorePatterns": ["**/dist/*"],
"env": {
"jest": true
}
"extends": "../../.eslintrc.json"
}

View File

@@ -19,7 +19,7 @@ dist/
typings/
docs/**/*
!docs/index.yml
!docs/index.json
!docs/README.md
!docs/examples/
!docs/examples/*.md

View File

@@ -6,3 +6,4 @@ docs/**/*
!docs/index.yml
!docs/README.md
coverage/
tsup.config.*.mjs

View File

@@ -0,0 +1 @@
module.exports = require('../../.prettierrc.json');

View File

@@ -1,8 +0,0 @@
{
"printWidth": 120,
"useTabs": true,
"singleQuote": true,
"quoteProps": "as-needed",
"trailingComma": "all",
"endOfLine": "lf"
}

View File

@@ -1,3 +0,0 @@
{
"releaseCommitMessageFormat": "chore(Release): publish"
}

View File

@@ -2,6 +2,53 @@
All notable changes to this project will be documented in this file.
# [@discordjs/builders@0.16.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.15.0...@discordjs/builders@0.16.0) - (2022-07-17)
## Bug Fixes
- Slash command name regex (#8265) ([32f9056](https://github.com/discordjs/discord.js/commit/32f9056b15edede3bab07de96afb4b56d3a9ecca))
- **TextInputBuilder:** Parse `custom_id`, `label`, and `style` (#8216) ([2d9dfa3](https://github.com/discordjs/discord.js/commit/2d9dfa3c6ea4bb972da2f7e088d148b798c866d9))
## Documentation
- Add codecov coverage badge to readmes (#8226) ([f6db285](https://github.com/discordjs/discord.js/commit/f6db285c073898a749fe4591cbd4463d1896daf5))
## Features
- **builder:** Add max min length in string option (#8214) ([96c8d21](https://github.com/discordjs/discord.js/commit/96c8d21f95eb366c46ae23505ba9054f44821b25))
- Codecov (#8219) ([f10f4cd](https://github.com/discordjs/discord.js/commit/f10f4cdcd88ca6be7ec735ed3a415ba13da83db0))
- **docgen:** Update typedoc ([b3346f4](https://github.com/discordjs/discord.js/commit/b3346f4b9b3d4f96443506643d4631dc1c6d7b21))
- Website (#8043) ([127931d](https://github.com/discordjs/discord.js/commit/127931d1df7a2a5c27923c2f2151dbf3824e50cc))
- **docgen:** Typescript support ([3279b40](https://github.com/discordjs/discord.js/commit/3279b40912e6aa61507bedb7db15a2b8668de44b))
- Docgen package (#8029) ([8b979c0](https://github.com/discordjs/discord.js/commit/8b979c0245c42fd824d8e98745ee869f5360fc86))
## Refactor
- **builder:** Remove `unsafe*Builder`s (#8074) ([a4d1862](https://github.com/discordjs/discord.js/commit/a4d18629828234f43f03d1bd4851d4b727c6903b))
- Remove @sindresorhus/is as it's now esm only (#8133) ([c6f285b](https://github.com/discordjs/discord.js/commit/c6f285b7b089b004776fbeb444fe973a68d158d8))
- Move all the config files to root (#8033) ([769ea0b](https://github.com/discordjs/discord.js/commit/769ea0bfe78c4f1d413c6b397c604ffe91e39c6a))
## Typings
- Remove expect error (#8242) ([7e6dbaa](https://github.com/discordjs/discord.js/commit/7e6dbaaed900c07d1a04e23bbbf9cd0d1b0501c5))
- **builder:** Remove casting (#8241) ([8198da5](https://github.com/discordjs/discord.js/commit/8198da5cd0898e06954615a2287853321e7ebbd4))
# [@discordjs/builders@0.15.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.14.0...@discordjs/builders@0.15.0) - (2022-06-06)
## Features
- Allow builders to accept rest params and arrays (#7874) ([ad75be9](https://github.com/discordjs/discord.js/commit/ad75be9a9cf90c8624495df99b75177e6c24022f))
- Use vitest instead of jest for more speed ([8d8e6c0](https://github.com/discordjs/discord.js/commit/8d8e6c03decd7352a2aa180f6e5bc1a13602539b))
- Add scripts package for locally used scripts ([f2ae1f9](https://github.com/discordjs/discord.js/commit/f2ae1f9348bfd893332a9060f71a8a5f272a1b8b))
# [@discordjs/builders@0.15.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.14.0...@discordjs/builders@0.15.0) - (2022-06-05)
## Features
- Allow builders to accept rest params and arrays (#7874) ([ad75be9](https://github.com/discordjs/discord.js/commit/ad75be9a9cf90c8624495df99b75177e6c24022f))
- Use vitest instead of jest for more speed ([8d8e6c0](https://github.com/discordjs/discord.js/commit/8d8e6c03decd7352a2aa180f6e5bc1a13602539b))
- Add scripts package for locally used scripts ([f2ae1f9](https://github.com/discordjs/discord.js/commit/f2ae1f9348bfd893332a9060f71a8a5f272a1b8b))
# [@discordjs/builders@0.14.0](https://github.com/discordjs/discord.js/compare/@discordjs/builders@0.13.0...@discordjs/builders@0.14.0) - (2022-06-04)
## Bug Fixes

View File

@@ -9,7 +9,7 @@
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/v/@discordjs/builders.svg?maxAge=3600" alt="npm version" /></a>
<a href="https://www.npmjs.com/package/@discordjs/builders"><img src="https://img.shields.io/npm/dt/@discordjs/builders.svg?maxAge=3600" alt="npm downloads" /></a>
<a href="https://github.com/discordjs/discord.js/actions"><img src="https://github.com/discordjs/discord.js/actions/workflows/test.yml/badge.svg" alt="Build status" /></a>
<a href="https://codecov.io/gh/discordjs/builders"><img src="https://codecov.io/gh/discordjs/builders/branch/main/graph/badge.svg" alt="Code coverage" /></a>
<a href="https://codecov.io/gh/discordjs/discord.js" ><img src="https://codecov.io/gh/discordjs/discord.js/branch/main/graph/badge.svg?precision=2&flag=builders" alt="Code coverage" /></a>
</p>
</div>
@@ -31,7 +31,7 @@ Here are some examples for the builders and utilities you can find in this packa
## Links
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/website))
- [Website](https://discord.js.org/) ([source](https://github.com/discordjs/discord.js/tree/main/packages/website))
- [Documentation](https://discord.js.org/#/docs/builders)
- [Guide](https://discordjs.guide/) ([source](https://github.com/discordjs/guide))
See also the [Update Guide](https://discordjs.guide/additional-info/changes-in-v13.html), including updated and removed items in the library.

View File

@@ -1,4 +1,5 @@
import { APIActionRowComponent, APIMessageActionRowComponent, ButtonStyle, ComponentType } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
ActionRowBuilder,
ButtonBuilder,
@@ -44,6 +45,8 @@ const rowWithSelectMenuData: APIActionRowComponent<APIMessageActionRowComponent>
describe('Action Row Components', () => {
describe('Assertion Tests', () => {
test('GIVEN valid components THEN do not throw', () => {
expect(() => new ActionRowBuilder().addComponents(new ButtonBuilder())).not.toThrowError();
expect(() => new ActionRowBuilder().setComponents(new ButtonBuilder())).not.toThrowError();
expect(() => new ActionRowBuilder().addComponents([new ButtonBuilder()])).not.toThrowError();
expect(() => new ActionRowBuilder().setComponents([new ButtonBuilder()])).not.toThrowError();
});
@@ -82,6 +85,7 @@ describe('Action Row Components', () => {
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
});
test('GIVEN valid builder options THEN valid JSON output is given', () => {
const rowWithButtonData: APIActionRowComponent<APIMessageActionRowComponent> = {
type: ComponentType.ActionRow,
@@ -122,17 +126,24 @@ describe('Action Row Components', () => {
expect(new ActionRowBuilder().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
expect(() => createComponentBuilder({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
});
test('GIVEN valid builder options THEN valid JSON output is given', () => {
test('GIVEN valid builder options THEN valid JSON output is given 2', () => {
const button = new ButtonBuilder().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
const selectMenu = new SelectMenuBuilder()
.setCustomId('1234')
.setMaxValues(10)
.setMinValues(12)
.setOptions(
new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
)
.setOptions([
new SelectMenuOptionBuilder().setLabel('one').setValue('one'),
new SelectMenuOptionBuilder().setLabel('two').setValue('two'),
]);
expect(new ActionRowBuilder().addComponents(button).toJSON()).toEqual(rowWithButtonData);
expect(new ActionRowBuilder().addComponents(selectMenu).toJSON()).toEqual(rowWithSelectMenuData);
expect(new ActionRowBuilder().addComponents([button]).toJSON()).toEqual(rowWithButtonData);
expect(new ActionRowBuilder().addComponents([selectMenu]).toJSON()).toEqual(rowWithSelectMenuData);
});

View File

@@ -4,6 +4,7 @@ import {
ButtonStyle,
ComponentType,
} from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { buttonLabelValidator, buttonStyleValidator } from '../../src/components/Assertions';
import { ButtonBuilder } from '../../src/components/button/Button';
@@ -124,7 +125,7 @@ describe('Button Components', () => {
expect(
buttonComponent()
.setCustomId(interactionData.custom_id)
.setLabel(interactionData.label)
.setLabel(interactionData.label!)
.setStyle(interactionData.style)
.setDisabled(interactionData.disabled)
.toJSON(),
@@ -140,7 +141,7 @@ describe('Button Components', () => {
expect(new ButtonBuilder(linkData).toJSON()).toEqual(linkData);
expect(buttonComponent().setLabel(linkData.label).setDisabled(true).setURL(linkData.url));
expect(buttonComponent().setLabel(linkData.label!).setDisabled(true).setURL(linkData.url));
});
});
});

View File

@@ -1,4 +1,5 @@
import { APISelectMenuComponent, APISelectMenuOption, ComponentType } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { SelectMenuBuilder, SelectMenuOptionBuilder } from '../../src/index';
const selectMenu = () => new SelectMenuBuilder();
@@ -43,12 +44,15 @@ describe('Select Menu Components', () => {
.setDefault(true)
.setEmoji({ name: 'test' })
.setDescription('description');
expect(() => selectMenu().addOptions(option)).not.toThrowError();
expect(() => selectMenu().setOptions(option)).not.toThrowError();
expect(() => selectMenu().setOptions({ label: 'test', value: 'test' })).not.toThrowError();
expect(() => selectMenu().addOptions([option])).not.toThrowError();
expect(() => selectMenu().setOptions([option])).not.toThrowError();
expect(() => selectMenu().setOptions([{ label: 'test', value: 'test' }])).not.toThrowError();
expect(() =>
selectMenu().addOptions([
{
selectMenu()
.addOptions({
label: 'test',
value: 'test',
emoji: {
@@ -56,14 +60,31 @@ describe('Select Menu Components', () => {
name: 'test',
animated: true,
},
},
]),
})
.addOptions([
{
label: 'test',
value: 'test',
emoji: {
id: '123',
name: 'test',
animated: true,
},
},
]),
).not.toThrowError();
const options = new Array<APISelectMenuOption>(25).fill({ label: 'test', value: 'test' });
expect(() => selectMenu().addOptions(...options)).not.toThrowError();
expect(() => selectMenu().setOptions(...options)).not.toThrowError();
expect(() => selectMenu().addOptions(options)).not.toThrowError();
expect(() => selectMenu().setOptions(options)).not.toThrowError();
expect(() =>
selectMenu()
.addOptions({ label: 'test', value: 'test' })
.addOptions(...new Array<APISelectMenuOption>(24).fill({ label: 'test', value: 'test' })),
).not.toThrowError();
expect(() =>
selectMenu()
.addOptions([{ label: 'test', value: 'test' }])
@@ -79,6 +100,17 @@ describe('Select Menu Components', () => {
expect(() => selectMenu().setDisabled(0)).toThrowError();
expect(() => selectMenu().setPlaceholder(longStr)).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ label: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ label: longStr, value: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ value: longStr, label: 'test' })).toThrowError();
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', description: longStr })).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ label: 'test', value: 'test', default: 100 })).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ value: 'test' })).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions({ default: true })).toThrowError();
// @ts-expect-error
expect(() => selectMenu().addOptions([{ label: 'test' }])).toThrowError();
expect(() => selectMenu().addOptions([{ label: longStr, value: 'test' }])).toThrowError();
expect(() => selectMenu().addOptions([{ value: longStr, label: 'test' }])).toThrowError();
@@ -91,8 +123,14 @@ describe('Select Menu Components', () => {
expect(() => selectMenu().addOptions([{ default: true }])).toThrowError();
const tooManyOptions = new Array<APISelectMenuOption>(26).fill({ label: 'test', value: 'test' });
expect(() => selectMenu().setOptions(...tooManyOptions)).toThrowError();
expect(() => selectMenu().setOptions(tooManyOptions)).toThrowError();
expect(() =>
selectMenu()
.addOptions({ label: 'test', value: 'test' })
.addOptions(...tooManyOptions),
).toThrowError();
expect(() =>
selectMenu()
.addOptions([{ label: 'test', value: 'test' }])
@@ -112,6 +150,11 @@ describe('Select Menu Components', () => {
});
test('GIVEN valid JSON input THEN valid JSON history is correct', () => {
expect(
new SelectMenuBuilder(selectMenuDataWithoutOptions)
.addOptions(new SelectMenuOptionBuilder(selectMenuOptionData))
.toJSON(),
).toEqual(selectMenuData);
expect(
new SelectMenuBuilder(selectMenuDataWithoutOptions)
.addOptions([new SelectMenuOptionBuilder(selectMenuOptionData)])

View File

@@ -1,4 +1,5 @@
import { APITextInputComponent, ComponentType, TextInputStyle } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
labelValidator,
maxLengthValidator,
@@ -45,7 +46,7 @@ describe('Text Input Components', () => {
expect(() => maxLengthValidator.parse(10)).not.toThrowError();
});
test('GIVEN invalid min length THEN validator does throw', () => {
test('GIVEN invalid min length THEN validator does throw 2', () => {
expect(() => maxLengthValidator.parse(4001)).toThrowError();
});
@@ -61,7 +62,7 @@ describe('Text Input Components', () => {
expect(() => placeholderValidator.parse('foobar')).not.toThrowError();
});
test('GIVEN invalid value THEN validator does throw', () => {
test('GIVEN invalid value THEN validator does throw 2', () => {
expect(() => placeholderValidator.parse(superLongStr)).toThrowError();
});
@@ -81,6 +82,12 @@ describe('Text Input Components', () => {
.setStyle(TextInputStyle.Paragraph)
.toJSON();
}).not.toThrowError();
expect(() => {
// Issue #8107
// @ts-expect-error: shapeshift maps the enum key to the value when parsing
textInputComponent().setCustomId('Custom').setLabel('Guess').setStyle('Short').toJSON();
}).not.toThrowError();
});
});
@@ -114,10 +121,10 @@ describe('Text Input Components', () => {
textInputComponent()
.setCustomId(textInputData.custom_id)
.setLabel(textInputData.label)
.setPlaceholder(textInputData.placeholder)
.setMaxLength(textInputData.max_length)
.setMinLength(textInputData.min_length)
.setValue(textInputData.value)
.setPlaceholder(textInputData.placeholder!)
.setMaxLength(textInputData.max_length!)
.setMinLength(textInputData.min_length!)
.setValue(textInputData.value!)
.setRequired(textInputData.required)
.setStyle(textInputData.style)
.toJSON(),

View File

@@ -1,4 +1,5 @@
import { PermissionFlagsBits } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import { ContextMenuCommandAssertions, ContextMenuCommandBuilder } from '../../src/index';
const getBuilder = () => new ContextMenuCommandBuilder();

View File

@@ -10,6 +10,7 @@ import {
ApplicationCommandOptionType,
ChannelType,
} from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
SlashCommandBooleanOption,
SlashCommandChannelOption,
@@ -166,11 +167,13 @@ describe('Application Command toJSON() results', () => {
});
test('GIVEN a string option THEN calling toJSON should return a valid JSON', () => {
expect(getStringOption().toJSON()).toEqual<APIApplicationCommandStringOption>({
expect(getStringOption().setMinLength(1).setMaxLength(10).toJSON()).toEqual<APIApplicationCommandStringOption>({
name: 'owo',
description: 'Testing 123',
type: ApplicationCommandOptionType.String,
required: true,
max_length: 10,
min_length: 1,
});
expect(getStringOption().setAutocomplete(true).setChoices().toJSON()).toEqual<APIApplicationCommandStringOption>({

View File

@@ -1,4 +1,5 @@
import { APIApplicationCommandOptionChoice, ChannelType, PermissionFlagsBits } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
SlashCommandAssertions,
SlashCommandBooleanOption,
@@ -41,6 +42,8 @@ describe('Slash Commands', () => {
describe('Assertions tests', () => {
test('GIVEN valid name THEN does not throw error', () => {
expect(() => SlashCommandAssertions.validateName('ping')).not.toThrowError();
expect(() => SlashCommandAssertions.validateName('hello-world_command')).not.toThrowError();
expect(() => SlashCommandAssertions.validateName('aˇ㐆1٢〣²अก')).not.toThrowError();
});
test('GIVEN invalid name THEN throw error', () => {
@@ -50,7 +53,10 @@ describe('Slash Commands', () => {
expect(() => SlashCommandAssertions.validateName('')).toThrowError();
// Invalid characters used
expect(() => SlashCommandAssertions.validateName('ABC')).toThrowError();
expect(() => SlashCommandAssertions.validateName('ABC123$%^&')).toThrowError();
expect(() => SlashCommandAssertions.validateName('help ping')).toThrowError();
expect(() => SlashCommandAssertions.validateName('🦦')).toThrowError();
// Too long of a name
expect(() =>
@@ -313,8 +319,10 @@ describe('Slash Commands', () => {
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
expect(() => getBuilder().addBooleanOption(true)).toThrowError();
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
expect(() => getBuilder().addBooleanOption(null)).toThrowError();
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error
expect(() => getBuilder().addBooleanOption(undefined)).toThrowError();
// @ts-expect-error Checking if not providing anything, or an invalid return type causes an error

View File

@@ -1,4 +1,5 @@
import { APIModalInteractionResponseCallbackData, ComponentType, TextInputStyle } from 'discord-api-types/v10';
import { describe, test, expect } from 'vitest';
import {
ActionRowBuilder,
ButtonBuilder,
@@ -48,7 +49,11 @@ describe('Modals', () => {
test('GIVEN valid fields THEN builder does not throw', () => {
expect(() =>
modal().setTitle('test').setCustomId('foobar').setComponents([new ActionRowBuilder()]),
modal()
.setTitle('test')
.setCustomId('foobar')
.setComponents(new ActionRowBuilder())
.addComponents([new ActionRowBuilder()]),
).not.toThrowError();
});
@@ -74,6 +79,17 @@ describe('Modals', () => {
},
],
},
{
type: ComponentType.ActionRow,
components: [
{
type: ComponentType.TextInput,
label: 'label',
style: TextInputStyle.Paragraph,
custom_id: 'custom id',
},
],
},
],
};
@@ -83,10 +99,15 @@ describe('Modals', () => {
modal()
.setTitle(modalData.title)
.setCustomId('custom id')
.setComponents([
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents([
.setComponents(
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
]),
),
)
.addComponents([
new ActionRowBuilder<ModalActionRowComponentBuilder>().addComponents(
new TextInputBuilder().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
),
])
.toJSON(),
).toEqual(modalData);

View File

@@ -1,3 +1,4 @@
import { describe, test, expect } from 'vitest';
import { EmbedBuilder, embedLength } from '../../src';
const alpha = 'abcdefghijklmnopqrstuvwxyz';
@@ -322,28 +323,29 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#addFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
embed.addFields({ name: 'foo', value: 'bar' });
embed.addFields([{ name: 'foo', value: 'bar' }]);
expect(embed.toJSON()).toStrictEqual({
fields: [{ name: 'foo', value: 'bar' }],
fields: [
{ name: 'foo', value: 'bar' },
{ name: 'foo', value: 'bar' },
],
});
});
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
embed.addFields([
{ name: 'foo', value: 'bar' },
{ name: 'foo', value: 'baz' },
]);
embed.addFields({ name: 'foo', value: 'bar' }, { name: 'foo', value: 'baz' });
expect(embed.spliceFields(0, 1).toJSON()).toStrictEqual({
fields: [{ name: 'foo', value: 'baz' }],
});
});
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data', () => {
test('GIVEN an embed using Embed#spliceFields THEN returns valid toJSON data 2', () => {
const embed = new EmbedBuilder();
embed.addFields(Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
expect(() =>
embed.spliceFields(0, 3, ...Array.from({ length: 5 }, () => ({ name: 'foo', value: 'bar' }))),
@@ -352,7 +354,7 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#spliceFields that adds additional fields resulting in fields > 25 THEN throws error', () => {
const embed = new EmbedBuilder();
embed.addFields(Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
embed.addFields(...Array.from({ length: 23 }, () => ({ name: 'foo', value: 'bar' })));
expect(() =>
embed.spliceFields(0, 3, ...Array.from({ length: 8 }, () => ({ name: 'foo', value: 'bar' }))),
@@ -362,6 +364,9 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#setFields THEN returns valid toJSON data', () => {
const embed = new EmbedBuilder();
expect(() =>
embed.setFields(...Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
).not.toThrowError();
expect(() =>
embed.setFields(Array.from({ length: 25 }, () => ({ name: 'foo', value: 'bar' }))),
).not.toThrowError();
@@ -370,38 +375,43 @@ describe('Embed', () => {
test('GIVEN an embed using Embed#setFields that sets more than 25 fields THEN throws error', () => {
const embed = new EmbedBuilder();
expect(() =>
embed.setFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
).toThrowError();
expect(() => embed.setFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
});
describe('GIVEN invalid field amount THEN throws error', () => {
test('', () => {
test('1', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields(Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' })))).toThrowError();
expect(() =>
embed.addFields(...Array.from({ length: 26 }, () => ({ name: 'foo', value: 'bar' }))),
).toThrowError();
});
});
describe('GIVEN invalid field name THEN throws error', () => {
test('', () => {
test('2', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields([{ name: '', value: 'bar' }])).toThrowError();
expect(() => embed.addFields({ name: '', value: 'bar' })).toThrowError();
});
});
describe('GIVEN invalid field name length THEN throws error', () => {
test('', () => {
test('3', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields([{ name: 'a'.repeat(257), value: 'bar' }])).toThrowError();
expect(() => embed.addFields({ name: 'a'.repeat(257), value: 'bar' })).toThrowError();
});
});
describe('GIVEN invalid field value length THEN throws error', () => {
test('', () => {
test('4', () => {
const embed = new EmbedBuilder();
expect(() => embed.addFields([{ name: '', value: 'a'.repeat(1025) }])).toThrowError();
expect(() => embed.addFields({ name: '', value: 'a'.repeat(1025) })).toThrowError();
});
});
});

View File

@@ -1,3 +1,5 @@
import { URL } from 'node:url';
import { describe, test, expect, vitest } from 'vitest';
import {
blockQuote,
bold,
@@ -150,12 +152,12 @@ describe('Message formatters', () => {
describe('time', () => {
test('GIVEN no arguments THEN returns "<t:${bigint}>"', () => {
jest.useFakeTimers('modern');
jest.setSystemTime(1566424897579);
vitest.useFakeTimers();
vitest.setSystemTime(1566424897579);
expect<`<t:${bigint}>`>(time()).toEqual('<t:1566424897>');
jest.useRealTimers();
vitest.useRealTimers();
});
test('GIVEN a date THEN returns "<t:${bigint}>"', () => {

View File

@@ -0,0 +1,376 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
/**
* Optionally specifies another JSON config file that this file extends from. This provides a way for
* standard settings to be shared across multiple projects.
*
* If the path starts with "./" or "../", the path is resolved relative to the folder of the file that contains
* the "extends" field. Otherwise, the first path segment is interpreted as an NPM package name, and will be
* resolved using NodeJS require().
*
* SUPPORTED TOKENS: none
* DEFAULT VALUE: ""
*/
// "extends": "./shared/api-extractor-base.json"
// "extends": "my-package/include/api-extractor-base.json"
/**
* Determines the "<projectFolder>" token that can be used with other config file settings. The project folder
* typically contains the tsconfig.json and package.json config files, but the path is user-defined.
*
* The path is resolved relative to the folder of the config file that contains the setting.
*
* The default value for "projectFolder" is the token "<lookup>", which means the folder is determined by traversing
* parent folders, starting from the folder containing api-extractor.json, and stopping at the first folder
* that contains a tsconfig.json file. If a tsconfig.json file cannot be found in this way, then an error
* will be reported.
*
* SUPPORTED TOKENS: <lookup>
* DEFAULT VALUE: "<lookup>"
*/
// "projectFolder": "..",
/**
* (REQUIRED) Specifies the .d.ts file to be used as the starting point for analysis. API Extractor
* analyzes the symbols exported by this module.
*
* The file extension must be ".d.ts" and not ".ts".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
*/
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts",
/**
* A list of NPM package names whose exports should be treated as part of this package.
*
* For example, suppose that Webpack is used to generate a distributed bundle for the project "library1",
* and another NPM package "library2" is embedded in this bundle. Some types from library2 may become part
* of the exported API for library1, but by default API Extractor would generate a .d.ts rollup that explicitly
* imports library2. To avoid this, we can specify:
*
* "bundledPackages": [ "library2" ],
*
* This would direct API Extractor to embed those types directly in the .d.ts rollup, as if they had been
* local files for library1.
*/
"bundledPackages": [],
/**
* Determines how the TypeScript compiler engine will be invoked by API Extractor.
*/
"compiler": {
/**
* Specifies the path to the tsconfig.json file to be used by API Extractor when analyzing the project.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* Note: This setting will be ignored if "overrideTsconfig" is used.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/tsconfig.json"
*/
// "tsconfigFilePath": "<projectFolder>/tsconfig.json",
/**
* Provides a compiler configuration that will be used instead of reading the tsconfig.json file from disk.
* The object must conform to the TypeScript tsconfig schema:
*
* http://json.schemastore.org/tsconfig
*
* If omitted, then the tsconfig.json file will be read from the "projectFolder".
*
* DEFAULT VALUE: no overrideTsconfig section
*/
// "overrideTsconfig": {
// . . .
// }
/**
* This option causes the compiler to be invoked with the --skipLibCheck option. This option is not recommended
* and may cause API Extractor to produce incomplete or incorrect declarations, but it may be required when
* dependencies contain declarations that are incompatible with the TypeScript engine that API Extractor uses
* for its analysis. Where possible, the underlying issue should be fixed rather than relying on skipLibCheck.
*
* DEFAULT VALUE: false
*/
// "skipLibCheck": true,
},
/**
* Configures how the API report file (*.api.md) will be generated.
*/
"apiReport": {
/**
* (REQUIRED) Whether to generate an API report.
*/
"enabled": false
/**
* The filename for the API report files. It will be combined with "reportFolder" or "reportTempFolder" to produce
* a full file path.
*
* The file extension should be ".api.md", and the string should not contain a path separator such as "\" or "/".
*
* SUPPORTED TOKENS: <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<unscopedPackageName>.api.md"
*/
// "reportFileName": "<unscopedPackageName>.api.md",
/**
* Specifies the folder where the API report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* The API report file is normally tracked by Git. Changes to it can be used to trigger a branch policy,
* e.g. for an API review.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/"
*/
// "reportFolder": "<projectFolder>/temp/",
/**
* Specifies the folder where the temporary report file is written. The file name portion is determined by
* the "reportFileName" setting.
*
* After the temporary file is written to disk, it is compared with the file in the "reportFolder".
* If they are different, a production build will fail.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/"
*/
// "reportTempFolder": "<projectFolder>/temp/"
},
/**
* Configures how the doc model file (*.api.json) will be generated.
*/
"docModel": {
/**
* (REQUIRED) Whether to generate a doc model file.
*/
"enabled": true,
/**
* The output path for the doc model file. The file extension should be ".api.json".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/temp/<unscopedPackageName>.api.json"
*/
"apiJsonFilePath": "<projectFolder>/docs/docs.api.json"
},
/**
* Configures how the .d.ts rollup file will be generated.
*/
"dtsRollup": {
/**
* (REQUIRED) Whether to generate the .d.ts rollup file.
*/
"enabled": false
/**
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.
* This file will include all declarations that are exported by the main entry point.
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<projectFolder>/dist/<unscopedPackageName>.d.ts"
*/
// "untrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for an "alpha" release.
* This file will include only declarations that are marked as "@public", "@beta", or "@alpha".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "alphaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-alpha.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "beta" release.
* This file will include only declarations that are marked as "@public" or "@beta".
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "betaTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-beta.d.ts",
/**
* Specifies the output path for a .d.ts rollup file to be generated with trimming for a "public" release.
* This file will include only declarations that are marked as "@public".
*
* If the path is an empty string, then this file will not be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: ""
*/
// "publicTrimmedFilePath": "<projectFolder>/dist/<unscopedPackageName>-public.d.ts",
/**
* When a declaration is trimmed, by default it will be replaced by a code comment such as
* "Excluded from this release type: exampleMember". Set "omitTrimmingComments" to true to remove the
* declaration completely.
*
* DEFAULT VALUE: false
*/
// "omitTrimmingComments": true
},
/**
* Configures how the tsdoc-metadata.json file will be generated.
*/
"tsdocMetadata": {
/**
* Whether to generate the tsdoc-metadata.json file.
*
* DEFAULT VALUE: true
*/
// "enabled": true,
/**
* Specifies where the TSDoc metadata file should be written.
*
* The path is resolved relative to the folder of the config file that contains the setting; to change this,
* prepend a folder token such as "<projectFolder>".
*
* The default value is "<lookup>", which causes the path to be automatically inferred from the "tsdocMetadata",
* "typings" or "main" fields of the project's package.json. If none of these fields are set, the lookup
* falls back to "tsdoc-metadata.json" in the package folder.
*
* SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
* DEFAULT VALUE: "<lookup>"
*/
// "tsdocMetadataFilePath": "<projectFolder>/dist/tsdoc-metadata.json"
},
/**
* Specifies what type of newlines API Extractor should use when writing output files. By default, the output files
* will be written with Windows-style newlines. To use POSIX-style newlines, specify "lf" instead.
* To use the OS's default newline kind, specify "os".
*
* DEFAULT VALUE: "crlf"
*/
"newlineKind": "lf",
/**
* Configures how API Extractor reports error and warning messages produced during analysis.
*
* There are three sources of messages: compiler messages, API Extractor messages, and TSDoc messages.
*/
"messages": {
/**
* Configures handling of diagnostic messages reported by the TypeScript compiler engine while analyzing
* the input .d.ts files.
*
* TypeScript message identifiers start with "TS" followed by an integer. For example: "TS2551"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"compilerMessageReporting": {
/**
* Configures the default routing for messages that don't match an explicit rule in this table.
*/
"default": {
/**
* Specifies whether the message should be written to the the tool's output log. Note that
* the "addToApiReportFile" property may supersede this option.
*
* Possible values: "error", "warning", "none"
*
* Errors cause the build to fail and return a nonzero exit code. Warnings cause a production build fail
* and return a nonzero exit code. For a non-production build (e.g. when "api-extractor run" includes
* the "--local" option), the warning is displayed but the build will not fail.
*
* DEFAULT VALUE: "warning"
*/
"logLevel": "warning"
/**
* When addToApiReportFile is true: If API Extractor is configured to write an API report file (.api.md),
* then the message will be written inside that file; otherwise, the message is instead logged according to
* the "logLevel" option.
*
* DEFAULT VALUE: false
*/
// "addToApiReportFile": false
}
// "TS2551": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by API Extractor during its analysis.
*
* API Extractor message identifiers start with "ae-". For example: "ae-extra-release-tag"
*
* DEFAULT VALUE: See api-extractor-defaults.json for the complete table of extractorMessageReporting mappings
*/
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "ae-extra-release-tag": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
},
/**
* Configures handling of messages reported by the TSDoc parser when analyzing code comments.
*
* TSDoc message identifiers start with "tsdoc-". For example: "tsdoc-link-tag-unescaped-text"
*
* DEFAULT VALUE: A single "default" entry with logLevel=warning.
*/
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
// "addToApiReportFile": false
}
// "tsdoc-link-tag-unescaped-text": {
// "logLevel": "warning",
// "addToApiReportFile": true
// },
//
// . . .
}
}
}

View File

@@ -1,18 +0,0 @@
/**
* @type {import('@babel/core').TransformOptions}
*/
module.exports = {
parserOpts: { strictMode: true },
sourceMaps: 'inline',
presets: [
[
'@babel/preset-env',
{
targets: { node: 'current' },
modules: 'commonjs',
},
],
'@babel/preset-typescript',
],
plugins: ['babel-plugin-transform-typescript-metadata', ['@babel/plugin-proposal-decorators', { legacy: true }]],
};

View File

@@ -10,7 +10,7 @@ body = """
{% if previous %}\
{% if previous.version %}\
(https://github.com/discordjs/discord.js/compare/{{ previous.version }}...{{ version }})\
{% else %}
{% else %}\
(https://github.com/discordjs/discord.js/tree/{{ version }})\
{% endif %}\
{% endif %} \
@@ -26,9 +26,8 @@ body = """
{% endif %}\
{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/discordjs/discord.js/commit/{{ commit.id }}))\
{% if commit.breaking %}\
\n\n {% raw %} {% endraw %} ### Breaking Changes:\n \
{% for breakingChange in commit.footers %}\
{% raw %} {% endraw %} - {{ breakingChange }}\n\
\n{% raw %} {% endraw %}- **{{ breakingChange.token }}{{ breakingChange.separator }}** {{ breakingChange.value }}\
{% endfor %}\
{% endif %}\
{% endfor %}

View File

@@ -1,10 +0,0 @@
coverage:
status:
project:
default:
target: 70%
threshold: 5%
patch:
default:
target: 70%
threshold: 5%

View File

@@ -0,0 +1 @@
[{ "name": "General", "files": [{ "name": "Welcome", "id": "welcome", "path": "../../README.md" }] }]

View File

@@ -1,5 +0,0 @@
- name: General
files:
- name: Welcome
id: welcome
path: ../../README.md

View File

@@ -1,19 +0,0 @@
/**
* @type {import('@jest/types').Config.InitialOptions}
*/
module.exports = {
testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
testEnvironment: 'node',
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts'],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'clover'],
coverageThreshold: {
global: {
branches: 70,
lines: 70,
statements: 70,
},
},
coveragePathIgnorePatterns: ['src/index.ts'],
};

View File

@@ -1,15 +1,16 @@
{
"name": "@discordjs/builders",
"version": "0.14.0",
"version": "0.17.0-dev",
"description": "A set of builders that you can use when creating your bot",
"scripts": {
"test": "vitest run",
"build": "tsup",
"test": "jest --pass-with-no-tests",
"lint": "prettier --check . && eslint src __tests__ --ext mjs,js,ts",
"format": "prettier --write . && eslint src __tests__ --ext mjs,js,ts --fix",
"docs": "typedoc --json docs/typedoc-out.json src/index.ts && node scripts/docs.mjs",
"prepublishOnly": "yarn build && yarn lint && yarn test",
"changelog": "git cliff --prepend ./CHANGELOG.md -l -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'"
"docs": "docgen -i src/index.ts -c docs/index.json -o docs/docs.json --typescript && api-extractor run --local",
"prepack": "yarn build && yarn lint",
"changelog": "git cliff --prepend ./CHANGELOG.md -u -c ./cliff.toml -r ../../ --include-path 'packages/builders/*'",
"release": "cliff-jumper"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
@@ -52,33 +53,24 @@
},
"homepage": "https://discord.js.org",
"dependencies": {
"@sapphire/shapeshift": "^3.1.0",
"@sindresorhus/is": "^4.6.0",
"discord-api-types": "^0.33.3",
"@sapphire/shapeshift": "^3.5.1",
"discord-api-types": "^0.36.2",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.1",
"tslib": "^2.4.0"
},
"devDependencies": {
"@babel/core": "^7.18.2",
"@babel/plugin-proposal-decorators": "^7.18.2",
"@babel/preset-env": "^7.18.2",
"@babel/preset-typescript": "^7.17.12",
"@discordjs/ts-docgen": "^0.4.1",
"@types/jest": "^28.1.0",
"@types/node": "^16.11.38",
"@typescript-eslint/eslint-plugin": "^5.27.0",
"@typescript-eslint/parser": "^5.27.0",
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"eslint": "^8.17.0",
"eslint-config-marine": "^9.4.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"jest": "^28.1.0",
"prettier": "^2.6.2",
"tsup": "^6.0.1",
"typedoc": "^0.22.17",
"typescript": "^4.7.3"
"@discordjs/docgen": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@favware/cliff-jumper": "^1.8.5",
"@microsoft/api-extractor": "^7.28.4",
"@types/node": "^16.11.45",
"c8": "^7.11.3",
"eslint": "^8.20.0",
"prettier": "^2.7.1",
"tsup": "^6.1.3",
"typescript": "^4.7.4",
"vitest": "^0.18.1"
},
"engines": {
"node": ">=16.9.0"

View File

@@ -1,7 +0,0 @@
import { runGenerator } from '@discordjs/ts-docgen';
runGenerator({
existingOutput: 'docs/typedoc-out.json',
custom: 'docs/index.yml',
output: 'docs/docs.json',
});

View File

@@ -7,7 +7,10 @@ import {
} from 'discord-api-types/v10';
import { ComponentBuilder } from './Component';
import { createComponentBuilder } from './Components';
import type { ButtonBuilder, SelectMenuBuilder, TextInputBuilder } from '..';
import type { ButtonBuilder } from './button/Button';
import type { SelectMenuBuilder } from './selectMenu/SelectMenu';
import type { TextInputBuilder } from './textInput/TextInput';
import { normalizeArray, type RestOrArray } from '../util/normalizeArray';
export type MessageComponentBuilder =
| MessageActionRowComponentBuilder
@@ -35,20 +38,21 @@ export class ActionRowBuilder<T extends AnyComponentBuilder> extends ComponentBu
/**
* Adds components to this action row.
* @param components The components to add to this action row.
* @returns
*
* @param components - The components to add to this action row.
*/
public addComponents(components: T[]) {
this.components.push(...components);
public addComponents(...components: RestOrArray<T>) {
this.components.push(...normalizeArray(components));
return this;
}
/**
* Sets the components in this action row
* @param components The components to set this row to
*
* @param components - The components to set this row to
*/
public setComponents(components: T[]) {
this.components.splice(0, this.components.length, ...components);
public setComponents(...components: RestOrArray<T>) {
this.components.splice(0, this.components.length, ...normalizeArray(components));
return this;
}

View File

@@ -1,55 +1,78 @@
import { s } from '@sapphire/shapeshift';
import { APIMessageComponentEmoji, ButtonStyle } from 'discord-api-types/v10';
import type { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
import { UnsafeSelectMenuOptionBuilder } from './selectMenu/UnsafeSelectMenuOption';
import { SelectMenuOptionBuilder } from './selectMenu/SelectMenuOption';
import { isValidationEnabled } from '../util/validation';
export const customIdValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const customIdValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(100)
.setValidationEnabled(isValidationEnabled);
export const emojiValidator = s.object({
id: s.string,
name: s.string,
animated: s.boolean,
}).partial.strict;
export const emojiValidator = s
.object({
id: s.string,
name: s.string,
animated: s.boolean,
})
.partial.strict.setValidationEnabled(isValidationEnabled);
export const disabledValidator = s.boolean;
export const buttonLabelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(80);
export const buttonLabelValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(80)
.setValidationEnabled(isValidationEnabled);
export const buttonStyleValidator = s.nativeEnum(ButtonStyle);
export const placeholderValidator = s.string.lengthLessThanOrEqual(150);
export const minMaxValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
export const placeholderValidator = s.string.lengthLessThanOrEqual(150).setValidationEnabled(isValidationEnabled);
export const minMaxValidator = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(25)
.setValidationEnabled(isValidationEnabled);
export const labelValueDescriptionValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const optionValidator = s.union(
s.object({
label: labelValueDescriptionValidator,
value: labelValueDescriptionValidator,
description: labelValueDescriptionValidator.optional,
emoji: emojiValidator.optional,
default: s.boolean.optional,
}),
s.instance(UnsafeSelectMenuOptionBuilder),
);
export const optionsValidator = optionValidator.array.lengthGreaterThanOrEqual(0);
export const optionsLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(25);
export const labelValueDescriptionValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(100)
.setValidationEnabled(isValidationEnabled);
export const optionValidator = s
.union(
s.object({
label: labelValueDescriptionValidator,
value: labelValueDescriptionValidator,
description: labelValueDescriptionValidator.optional,
emoji: emojiValidator.optional,
default: s.boolean.optional,
}),
s.instance(SelectMenuOptionBuilder),
)
.setValidationEnabled(isValidationEnabled);
export const optionsValidator = optionValidator.array
.lengthGreaterThanOrEqual(0)
.setValidationEnabled(isValidationEnabled);
export const optionsLengthValidator = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(25)
.setValidationEnabled(isValidationEnabled);
export function validateRequiredSelectMenuParameters(options: SelectMenuOptionBuilder[], customId?: string) {
customIdValidator.parse(customId);
optionsValidator.parse(options);
}
export const labelValueValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
export const defaultValidator = s.boolean;
export function validateRequiredSelectMenuOptionParameters(label?: string, value?: string) {
labelValueValidator.parse(label);
labelValueValidator.parse(value);
labelValueDescriptionValidator.parse(label);
labelValueDescriptionValidator.parse(value);
}
export const urlValidator = s.string.url({
allowedProtocols: ['http:', 'https:', 'discord:'],
});
export const urlValidator = s.string
.url({
allowedProtocols: ['http:', 'https:', 'discord:'],
})
.setValidationEnabled(isValidationEnabled);
export function validateRequiredButtonParameters(
style?: ButtonStyle,

View File

@@ -1,6 +1,14 @@
import { APIMessageComponent, APIModalComponent, ComponentType } from 'discord-api-types/v10';
import type { AnyComponentBuilder, MessageComponentBuilder, ModalComponentBuilder } from './ActionRow';
import { ActionRowBuilder, ButtonBuilder, ComponentBuilder, SelectMenuBuilder, TextInputBuilder } from '../index';
import {
ActionRowBuilder,
type AnyComponentBuilder,
type MessageComponentBuilder,
type ModalComponentBuilder,
} from './ActionRow';
import { ComponentBuilder } from './Component';
import { ButtonBuilder } from './button/Button';
import { SelectMenuBuilder } from './selectMenu/SelectMenu';
import { TextInputBuilder } from './textInput/TextInput';
export interface MappedComponentTypes {
[ComponentType.ActionRow]: ActionRowBuilder<AnyComponentBuilder>;
@@ -11,7 +19,8 @@ export interface MappedComponentTypes {
/**
* Factory for creating components from API data
* @param data The api data to transform to a component class
*
* @param data - The api data to transform to a component class
*/
export function createComponentBuilder<T extends keyof MappedComponentTypes>(
data: (APIMessageComponent | APIModalComponent) & { type: T },

View File

@@ -1,11 +1,11 @@
import type {
import {
ComponentType,
ButtonStyle,
APIMessageComponentEmoji,
APIButtonComponent,
APIButtonComponentWithCustomId,
APIButtonComponentWithURL,
type APIMessageComponentEmoji,
type APIButtonComponent,
type APIButtonComponentWithURL,
type APIButtonComponentWithCustomId,
} from 'discord-api-types/v10';
import { UnsafeButtonBuilder } from './UnsafeButton';
import {
buttonLabelValidator,
buttonStyleValidator,
@@ -15,36 +15,77 @@ import {
urlValidator,
validateRequiredButtonParameters,
} from '../Assertions';
import { ComponentBuilder } from '../Component';
/**
* Represents a validated button component
* Represents a button component
*/
export class ButtonBuilder extends UnsafeButtonBuilder {
public override setStyle(style: ButtonStyle) {
return super.setStyle(buttonStyleValidator.parse(style));
export class ButtonBuilder extends ComponentBuilder<APIButtonComponent> {
public constructor(data?: Partial<APIButtonComponent>) {
super({ type: ComponentType.Button, ...data });
}
public override setURL(url: string) {
return super.setURL(urlValidator.parse(url));
/**
* Sets the style of this button
*
* @param style - The style of the button
*/
public setStyle(style: ButtonStyle) {
this.data.style = buttonStyleValidator.parse(style);
return this;
}
public override setCustomId(customId: string) {
return super.setCustomId(customIdValidator.parse(customId));
/**
* Sets the URL for this button
*
* @param url - The URL to open when this button is clicked
*/
public setURL(url: string) {
(this.data as APIButtonComponentWithURL).url = urlValidator.parse(url);
return this;
}
public override setEmoji(emoji: APIMessageComponentEmoji) {
return super.setEmoji(emojiValidator.parse(emoji));
/**
* Sets the custom id for this button
*
* @param customId - The custom id to use for this button
*/
public setCustomId(customId: string) {
(this.data as APIButtonComponentWithCustomId).custom_id = customIdValidator.parse(customId);
return this;
}
public override setDisabled(disabled = true) {
return super.setDisabled(disabledValidator.parse(disabled));
/**
* Sets the emoji to display on this button
*
* @param emoji - The emoji to display on this button
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
this.data.emoji = emojiValidator.parse(emoji);
return this;
}
public override setLabel(label: string) {
return super.setLabel(buttonLabelValidator.parse(label));
/**
* Sets whether this button is disabled
*
* @param disabled - Whether to disable this button
*/
public setDisabled(disabled = true) {
this.data.disabled = disabledValidator.parse(disabled);
return this;
}
public override toJSON(): APIButtonComponent {
/**
* Sets the label for this button
*
* @param label - The label to display on this button
*/
public setLabel(label: string) {
this.data.label = buttonLabelValidator.parse(label);
return this;
}
public toJSON(): APIButtonComponent {
validateRequiredButtonParameters(
this.data.style,
this.data.label,
@@ -52,6 +93,9 @@ export class ButtonBuilder extends UnsafeButtonBuilder {
(this.data as APIButtonComponentWithCustomId).custom_id,
(this.data as APIButtonComponentWithURL).url,
);
return super.toJSON();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APIButtonComponent;
}
}

View File

@@ -1,79 +0,0 @@
import {
ComponentType,
ButtonStyle,
type APIMessageComponentEmoji,
type APIButtonComponent,
type APIButtonComponentWithURL,
type APIButtonComponentWithCustomId,
} from 'discord-api-types/v10';
import { ComponentBuilder } from '../Component';
/**
* Represents a non-validated button component
*/
export class UnsafeButtonBuilder extends ComponentBuilder<APIButtonComponent> {
public constructor(data?: Partial<APIButtonComponent>) {
super({ type: ComponentType.Button, ...data });
}
/**
* Sets the style of this button
* @param style The style of the button
*/
public setStyle(style: ButtonStyle) {
this.data.style = style;
return this;
}
/**
* Sets the URL for this button
* @param url The URL to open when this button is clicked
*/
public setURL(url: string) {
(this.data as APIButtonComponentWithURL).url = url;
return this;
}
/**
* Sets the custom Id for this button
* @param customId The custom id to use for this button
*/
public setCustomId(customId: string) {
(this.data as APIButtonComponentWithCustomId).custom_id = customId;
return this;
}
/**
* Sets the emoji to display on this button
* @param emoji The emoji to display on this button
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
this.data.emoji = emoji;
return this;
}
/**
* Sets whether this button is disable or not
* @param disabled Whether or not to disable this button or not
*/
public setDisabled(disabled = true) {
this.data.disabled = disabled;
return this;
}
/**
* Sets the label for this button
* @param label The label to display on this button
*/
public setLabel(label: string) {
this.data.label = label;
return this;
}
public toJSON(): APIButtonComponent {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APIButtonComponent;
}
}

View File

@@ -1,6 +1,6 @@
import type { APISelectMenuComponent, APISelectMenuOption } from 'discord-api-types/v10';
import { UnsafeSelectMenuBuilder } from './UnsafeSelectMenu';
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
import { APISelectMenuOption, ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
import { SelectMenuOptionBuilder } from './SelectMenuOption';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
import {
customIdValidator,
disabledValidator,
@@ -10,59 +10,118 @@ import {
placeholderValidator,
validateRequiredSelectMenuParameters,
} from '../Assertions';
import { ComponentBuilder } from '../Component';
/**
* Represents a validated select menu component
* Represents a select menu component
*/
export class SelectMenuBuilder extends UnsafeSelectMenuBuilder {
public override setPlaceholder(placeholder: string) {
return super.setPlaceholder(placeholderValidator.parse(placeholder));
export class SelectMenuBuilder extends ComponentBuilder<APISelectMenuComponent> {
/**
* The options within this select menu
*/
public readonly options: SelectMenuOptionBuilder[];
public constructor(data?: Partial<APISelectMenuComponent>) {
const { options, ...initData } = data ?? {};
super({ type: ComponentType.SelectMenu, ...initData });
this.options = options?.map((o) => new SelectMenuOptionBuilder(o)) ?? [];
}
public override setMinValues(minValues: number) {
return super.setMinValues(minMaxValidator.parse(minValues));
/**
* Sets the placeholder for this select menu
*
* @param placeholder - The placeholder to use for this select menu
*/
public setPlaceholder(placeholder: string) {
this.data.placeholder = placeholderValidator.parse(placeholder);
return this;
}
public override setMaxValues(maxValues: number) {
return super.setMaxValues(minMaxValidator.parse(maxValues));
/**
* Sets the minimum values that must be selected in the select menu
*
* @param minValues - The minimum values that must be selected
*/
public setMinValues(minValues: number) {
this.data.min_values = minMaxValidator.parse(minValues);
return this;
}
public override setCustomId(customId: string) {
return super.setCustomId(customIdValidator.parse(customId));
/**
* Sets the maximum values that must be selected in the select menu
*
* @param maxValues - The maximum values that must be selected
*/
public setMaxValues(maxValues: number) {
this.data.max_values = minMaxValidator.parse(maxValues);
return this;
}
public override setDisabled(disabled = true) {
return super.setDisabled(disabledValidator.parse(disabled));
/**
* Sets the custom id for this select menu
*
* @param customId - The custom id to use for this select menu
*/
public setCustomId(customId: string) {
this.data.custom_id = customIdValidator.parse(customId);
return this;
}
public override addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
/**
* Sets whether this select menu is disabled
*
* @param disabled - Whether this select menu is disabled
*/
public setDisabled(disabled = true) {
this.data.disabled = disabledValidator.parse(disabled);
return this;
}
/**
* Adds options to this select menu
*
* @param options - The options to add to this select menu
* @returns
*/
public addOptions(...options: RestOrArray<SelectMenuOptionBuilder | APISelectMenuOption>) {
options = normalizeArray(options);
optionsLengthValidator.parse(this.options.length + options.length);
this.options.push(
...options.map((option) =>
option instanceof UnsafeSelectMenuOptionBuilder
option instanceof SelectMenuOptionBuilder
? option
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
: new SelectMenuOptionBuilder(optionValidator.parse<APISelectMenuOption>(option)),
),
);
return this;
}
public override setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
/**
* Sets the options on this select menu
*
* @param options - The options to set on this select menu
*/
public setOptions(...options: RestOrArray<SelectMenuOptionBuilder | APISelectMenuOption>) {
options = normalizeArray(options);
optionsLengthValidator.parse(options.length);
this.options.splice(
0,
this.options.length,
...options.map((option) =>
option instanceof UnsafeSelectMenuOptionBuilder
option instanceof SelectMenuOptionBuilder
? option
: new UnsafeSelectMenuOptionBuilder(optionValidator.parse(option) as APISelectMenuOption),
: new SelectMenuOptionBuilder(optionValidator.parse<APISelectMenuOption>(option)),
),
);
return this;
}
public override toJSON(): APISelectMenuComponent {
public toJSON(): APISelectMenuComponent {
validateRequiredSelectMenuParameters(this.options, this.data.custom_id);
return super.toJSON();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
options: this.options.map((o) => o.toJSON()),
} as APISelectMenuComponent;
}
}

View File

@@ -1,30 +1,73 @@
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
import {
defaultValidator,
emojiValidator,
labelValueValidator,
labelValueDescriptionValidator,
validateRequiredSelectMenuOptionParameters,
} from '../Assertions';
/**
* Represents a validated option within a select menu component
* Represents a option within a select menu component
*/
export class SelectMenuOptionBuilder extends UnsafeSelectMenuOptionBuilder {
public override setDescription(description: string) {
return super.setDescription(labelValueValidator.parse(description));
export class SelectMenuOptionBuilder {
public constructor(public data: Partial<APISelectMenuOption> = {}) {}
/**
* Sets the label of this option
*
* @param label - The label to show on this option
*/
public setLabel(label: string) {
this.data.label = labelValueDescriptionValidator.parse(label);
return this;
}
public override setDefault(isDefault = true) {
return super.setDefault(defaultValidator.parse(isDefault));
/**
* Sets the value of this option
*
* @param value - The value of this option
*/
public setValue(value: string) {
this.data.value = labelValueDescriptionValidator.parse(value);
return this;
}
public override setEmoji(emoji: APIMessageComponentEmoji) {
return super.setEmoji(emojiValidator.parse(emoji));
/**
* Sets the description of this option
*
* @param description - The description of this option
*/
public setDescription(description: string) {
this.data.description = labelValueDescriptionValidator.parse(description);
return this;
}
public override toJSON(): APISelectMenuOption {
/**
* Sets whether this option is selected by default
*
* @param isDefault - Whether this option is selected by default
*/
public setDefault(isDefault = true) {
this.data.default = defaultValidator.parse(isDefault);
return this;
}
/**
* Sets the emoji to display on this option
*
* @param emoji - The emoji to display on this option
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
this.data.emoji = emojiValidator.parse(emoji);
return this;
}
public toJSON(): APISelectMenuOption {
validateRequiredSelectMenuOptionParameters(this.data.label, this.data.value);
return super.toJSON();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APISelectMenuOption;
}
}

View File

@@ -1,101 +0,0 @@
import { APISelectMenuOption, ComponentType, type APISelectMenuComponent } from 'discord-api-types/v10';
import { UnsafeSelectMenuOptionBuilder } from './UnsafeSelectMenuOption';
import { ComponentBuilder } from '../Component';
/**
* Represents a non-validated select menu component
*/
export class UnsafeSelectMenuBuilder extends ComponentBuilder<APISelectMenuComponent> {
/**
* The options within this select menu
*/
public readonly options: UnsafeSelectMenuOptionBuilder[];
public constructor(data?: Partial<APISelectMenuComponent>) {
const { options, ...initData } = data ?? {};
super({ type: ComponentType.SelectMenu, ...initData });
this.options = options?.map((o) => new UnsafeSelectMenuOptionBuilder(o)) ?? [];
}
/**
* Sets the placeholder for this select menu
* @param placeholder The placeholder to use for this select menu
*/
public setPlaceholder(placeholder: string) {
this.data.placeholder = placeholder;
return this;
}
/**
* Sets the minimum values that must be selected in the select menu
* @param minValues The minimum values that must be selected
*/
public setMinValues(minValues: number) {
this.data.min_values = minValues;
return this;
}
/**
* Sets the maximum values that must be selected in the select menu
* @param minValues The maximum values that must be selected
*/
public setMaxValues(maxValues: number) {
this.data.max_values = maxValues;
return this;
}
/**
* Sets the custom Id for this select menu
* @param customId The custom id to use for this select menu
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;
return this;
}
/**
* Sets whether or not this select menu is disabled
* @param disabled Whether or not this select menu is disabled
*/
public setDisabled(disabled = true) {
this.data.disabled = disabled;
return this;
}
/**
* Adds options to this select menu
* @param options The options to add to this select menu
* @returns
*/
public addOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
this.options.push(
...options.map((option) =>
option instanceof UnsafeSelectMenuOptionBuilder ? option : new UnsafeSelectMenuOptionBuilder(option),
),
);
return this;
}
/**
* Sets the options on this select menu
* @param options The options to set on this select menu
*/
public setOptions(options: (UnsafeSelectMenuOptionBuilder | APISelectMenuOption)[]) {
this.options.splice(
0,
this.options.length,
...options.map((option) =>
option instanceof UnsafeSelectMenuOptionBuilder ? option : new UnsafeSelectMenuOptionBuilder(option),
),
);
return this;
}
public toJSON(): APISelectMenuComponent {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
options: this.options.map((o) => o.toJSON()),
} as APISelectMenuComponent;
}
}

View File

@@ -1,60 +0,0 @@
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
/**
* Represents a non-validated option within a select menu component
*/
export class UnsafeSelectMenuOptionBuilder {
public constructor(public data: Partial<APISelectMenuOption> = {}) {}
/**
* Sets the label of this option
* @param label The label to show on this option
*/
public setLabel(label: string) {
this.data.label = label;
return this;
}
/**
* Sets the value of this option
* @param value The value of this option
*/
public setValue(value: string) {
this.data.value = value;
return this;
}
/**
* Sets the description of this option.
* @param description The description of this option
*/
public setDescription(description: string) {
this.data.description = description;
return this;
}
/**
* Sets whether this option is selected by default
* @param isDefault Whether this option is selected by default
*/
public setDefault(isDefault = true) {
this.data.default = isDefault;
return this;
}
/**
* Sets the emoji to display on this option
* @param emoji The emoji to display on this option
*/
public setEmoji(emoji: APIMessageComponentEmoji) {
this.data.emoji = emoji;
return this;
}
public toJSON(): APISelectMenuOption {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APISelectMenuOption;
}
}

View File

@@ -1,14 +1,24 @@
import { s } from '@sapphire/shapeshift';
import { TextInputStyle } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation';
import { customIdValidator } from '../Assertions';
export const textInputStyleValidator = s.nativeEnum(TextInputStyle);
export const minLengthValidator = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(4000);
export const maxLengthValidator = s.number.int.greaterThanOrEqual(1).lessThanOrEqual(4000);
export const minLengthValidator = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(4000)
.setValidationEnabled(isValidationEnabled);
export const maxLengthValidator = s.number.int
.greaterThanOrEqual(1)
.lessThanOrEqual(4000)
.setValidationEnabled(isValidationEnabled);
export const requiredValidator = s.boolean;
export const valueValidator = s.string.lengthLessThanOrEqual(4000);
export const placeholderValidator = s.string.lengthLessThanOrEqual(100);
export const labelValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
export const valueValidator = s.string.lengthLessThanOrEqual(4000).setValidationEnabled(isValidationEnabled);
export const placeholderValidator = s.string.lengthLessThanOrEqual(100).setValidationEnabled(isValidationEnabled);
export const labelValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(45)
.setValidationEnabled(isValidationEnabled);
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
customIdValidator.parse(customId);

View File

@@ -1,4 +1,5 @@
import type { APITextInputComponent } from 'discord-api-types/v10';
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
import isEqual from 'fast-deep-equal';
import {
maxLengthValidator,
minLengthValidator,
@@ -6,32 +7,111 @@ import {
requiredValidator,
valueValidator,
validateRequiredParameters,
labelValidator,
textInputStyleValidator,
} from './Assertions';
import { UnsafeTextInputBuilder } from './UnsafeTextInput';
import { isJSONEncodable, type JSONEncodable } from '../../util/jsonEncodable';
import { customIdValidator } from '../Assertions';
import { ComponentBuilder } from '../Component';
export class TextInputBuilder extends UnsafeTextInputBuilder {
public override setMinLength(minLength: number) {
return super.setMinLength(minLengthValidator.parse(minLength));
export class TextInputBuilder extends ComponentBuilder<APITextInputComponent> {
public constructor(data?: APITextInputComponent & { type?: ComponentType.TextInput }) {
super({ type: ComponentType.TextInput, ...data });
}
public override setMaxLength(maxLength: number) {
return super.setMaxLength(maxLengthValidator.parse(maxLength));
/**
* Sets the custom id for this text input
*
* @param customId - The custom id of this text input
*/
public setCustomId(customId: string) {
this.data.custom_id = customIdValidator.parse(customId);
return this;
}
public override setRequired(required = true) {
return super.setRequired(requiredValidator.parse(required));
/**
* Sets the label for this text input
*
* @param label - The label for this text input
*/
public setLabel(label: string) {
this.data.label = labelValidator.parse(label);
return this;
}
public override setValue(value: string) {
return super.setValue(valueValidator.parse(value));
/**
* Sets the style for this text input
*
* @param style - The style for this text input
*/
public setStyle(style: TextInputStyle) {
this.data.style = textInputStyleValidator.parse(style);
return this;
}
public override setPlaceholder(placeholder: string) {
return super.setPlaceholder(placeholderValidator.parse(placeholder));
/**
* Sets the minimum length of text for this text input
*
* @param minLength - The minimum length of text for this text input
*/
public setMinLength(minLength: number) {
this.data.min_length = minLengthValidator.parse(minLength);
return this;
}
public override toJSON(): APITextInputComponent {
/**
* Sets the maximum length of text for this text input
*
* @param maxLength - The maximum length of text for this text input
*/
public setMaxLength(maxLength: number) {
this.data.max_length = maxLengthValidator.parse(maxLength);
return this;
}
/**
* Sets the placeholder of this text input
*
* @param placeholder - The placeholder of this text input
*/
public setPlaceholder(placeholder: string) {
this.data.placeholder = placeholderValidator.parse(placeholder);
return this;
}
/**
* Sets the value of this text input
*
* @param value - The value for this text input
*/
public setValue(value: string) {
this.data.value = valueValidator.parse(value);
return this;
}
/**
* Sets whether this text input is required
*
* @param required - Whether this text input is required
*/
public setRequired(required = true) {
this.data.required = requiredValidator.parse(required);
return this;
}
public toJSON(): APITextInputComponent {
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
return super.toJSON();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APITextInputComponent;
}
public equals(other: JSONEncodable<APITextInputComponent> | APITextInputComponent): boolean {
if (isJSONEncodable(other)) {
return isEqual(other.toJSON(), this.data);
}
return isEqual(other, this.data);
}
}

View File

@@ -1,96 +0,0 @@
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
import isEqual from 'fast-deep-equal';
import { ComponentBuilder } from '../../index';
export class UnsafeTextInputBuilder extends ComponentBuilder<APITextInputComponent> {
public constructor(data?: APITextInputComponent & { type?: ComponentType.TextInput }) {
super({ type: ComponentType.TextInput, ...data });
}
/**
* Sets the custom id for this text input
* @param customId The custom id of this text input
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;
return this;
}
/**
* Sets the label for this text input
* @param label The label for this text input
*/
public setLabel(label: string) {
this.data.label = label;
return this;
}
/**
* Sets the style for this text input
* @param style The style for this text input
*/
public setStyle(style: TextInputStyle) {
this.data.style = style;
return this;
}
/**
* Sets the minimum length of text for this text input
* @param minLength The minimum length of text for this text input
*/
public setMinLength(minLength: number) {
this.data.min_length = minLength;
return this;
}
/**
* Sets the maximum length of text for this text input
* @param maxLength The maximum length of text for this text input
*/
public setMaxLength(maxLength: number) {
this.data.max_length = maxLength;
return this;
}
/**
* Sets the placeholder of this text input
* @param placeholder The placeholder of this text input
*/
public setPlaceholder(placeholder: string) {
this.data.placeholder = placeholder;
return this;
}
/**
* Sets the value of this text input
* @param value The value for this text input
*/
public setValue(value: string) {
this.data.value = value;
return this;
}
/**
* Sets whether this text input is required or not
* @param required Whether this text input is required or not
*/
public setRequired(required = true) {
this.data.required = required;
return this;
}
public toJSON(): APITextInputComponent {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
} as APITextInputComponent;
}
public equals(other: UnsafeTextInputBuilder | APITextInputComponent): boolean {
if (other instanceof UnsafeTextInputBuilder) {
return isEqual(other.data, this.data);
}
return isEqual(other, this.data);
}
}

View File

@@ -1,7 +1,6 @@
export * as EmbedAssertions from './messages/embed/Assertions';
export * from './messages/embed/Embed';
export * from './messages/formatters';
export * from './messages/embed/UnsafeEmbed';
export * as ComponentAssertions from './components/Assertions';
export * from './components/ActionRow';
@@ -10,15 +9,10 @@ export * from './components/Component';
export * from './components/Components';
export * from './components/textInput/TextInput';
export * as TextInputAssertions from './components/textInput/Assertions';
export * from './components/textInput/UnsafeTextInput';
export * from './interactions/modals/UnsafeModal';
export * from './interactions/modals/Modal';
export * as ModalAssertions from './interactions/modals/Assertions';
export * from './components/selectMenu/SelectMenu';
export * from './components/selectMenu/SelectMenuOption';
export * from './components/button/UnsafeButton';
export * from './components/selectMenu/UnsafeSelectMenu';
export * from './components/selectMenu/UnsafeSelectMenuOption';
export * as SlashCommandAssertions from './interactions/slashCommands/Assertions';
export * from './interactions/slashCommands/SlashCommandBuilder';
@@ -45,3 +39,5 @@ export * from './interactions/contextMenuCommands/ContextMenuCommandBuilder';
export * from './util/jsonEncodable';
export * from './util/equatable';
export * from './util/componentUtil';
export * from './util/normalizeArray';
export * from './util/validation';

View File

@@ -1,14 +1,16 @@
import { s } from '@sapphire/shapeshift';
import { ApplicationCommandType } from 'discord-api-types/v10';
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder';
import { isValidationEnabled } from '../../util/validation';
const namePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/^( *[\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+ *)+$/u);
const typePredicate = s.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message));
.regex(/^( *[\p{L}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+ *)+$/u)
.setValidationEnabled(isValidationEnabled);
const typePredicate = s
.union(s.literal(ApplicationCommandType.User), s.literal(ApplicationCommandType.Message))
.setValidationEnabled(isValidationEnabled);
const booleanPredicate = s.boolean;
export function validateDefaultPermission(value: unknown): asserts value is boolean {

View File

@@ -53,7 +53,7 @@ export class ContextMenuCommandBuilder {
/**
* Sets the name
*
* @param name The name
* @param name - The name
*/
public setName(name: string) {
// Assert the name matches the conditions
@@ -67,7 +67,7 @@ export class ContextMenuCommandBuilder {
/**
* Sets the type
*
* @param type The type
* @param type - The type
*/
public setType(type: ContextMenuCommandType) {
// Assert the type is valid
@@ -83,7 +83,7 @@ export class ContextMenuCommandBuilder {
*
* **Note**: If set to `false`, you will have to later `PUT` the permissions for this command.
*
* @param value Whether or not to enable this command by default
* @param value - Whether or not to enable this command by default
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
* @deprecated Use `setDefaultMemberPermissions` or `setDMPermission` instead.
@@ -102,7 +102,7 @@ export class ContextMenuCommandBuilder {
*
* **Note:** You can set this to `'0'` to disable the command by default.
*
* @param permissions The permissions bit field to set
* @param permissions - The permissions bit field to set
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
@@ -119,7 +119,7 @@ export class ContextMenuCommandBuilder {
* Sets if the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*
* @param enabled If the command should be enabled in DMs
* @param enabled - If the command should be enabled in DMs
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
@@ -135,8 +135,8 @@ export class ContextMenuCommandBuilder {
/**
* Sets a name localization
*
* @param locale The locale to set a description for
* @param localizedName The localized description for the given locale
* @param locale - The locale to set a description for
* @param localizedName - The localized description for the given locale
*/
public setNameLocalization(locale: LocaleString, localizedName: string | null) {
if (!this.name_localizations) {
@@ -159,7 +159,7 @@ export class ContextMenuCommandBuilder {
/**
* Sets the name localizations
*
* @param localizedNames The dictionary of localized descriptions to set
* @param localizedNames - The dictionary of localized descriptions to set
*/
public setNameLocalizations(localizedNames: LocalizationMap | null) {
if (localizedNames === null) {

View File

@@ -1,9 +1,16 @@
import { s } from '@sapphire/shapeshift';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../..';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow';
import { customIdValidator } from '../../components/Assertions';
import { isValidationEnabled } from '../../util/validation';
export const titleValidator = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(45);
export const componentsValidator = s.instance(ActionRowBuilder).array.lengthGreaterThanOrEqual(1);
export const titleValidator = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(45)
.setValidationEnabled(isValidationEnabled);
export const componentsValidator = s
.instance(ActionRowBuilder)
.array.lengthGreaterThanOrEqual(1)
.setValidationEnabled(isValidationEnabled);
export function validateRequiredParameters(
customId?: string,

View File

@@ -1,19 +1,81 @@
import type { APIModalInteractionResponseCallbackData } from 'discord-api-types/v10';
import type {
APIActionRowComponent,
APIModalActionRowComponent,
APIModalInteractionResponseCallbackData,
} from 'discord-api-types/v10';
import { titleValidator, validateRequiredParameters } from './Assertions';
import { UnsafeModalBuilder } from './UnsafeModal';
import { ActionRowBuilder, type ModalActionRowComponentBuilder } from '../../components/ActionRow';
import { customIdValidator } from '../../components/Assertions';
import { createComponentBuilder } from '../../components/Components';
import type { JSONEncodable } from '../../util/jsonEncodable';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
export class ModalBuilder extends UnsafeModalBuilder {
public override setCustomId(customId: string): this {
return super.setCustomId(customIdValidator.parse(customId));
export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
public readonly data: Partial<APIModalInteractionResponseCallbackData>;
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
this.data = { ...data };
this.components = (components?.map((c) => createComponentBuilder(c)) ??
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
}
public override setTitle(title: string) {
return super.setTitle(titleValidator.parse(title));
/**
* Sets the title of the modal
*
* @param title - The title of the modal
*/
public setTitle(title: string) {
this.data.title = titleValidator.parse(title);
return this;
}
public override toJSON(): APIModalInteractionResponseCallbackData {
/**
* Sets the custom id of the modal
*
* @param customId - The custom id of this modal
*/
public setCustomId(customId: string) {
this.data.custom_id = customIdValidator.parse(customId);
return this;
}
/**
* Adds components to this modal
*
* @param components - The components to add to this modal
*/
public addComponents(
...components: RestOrArray<
ActionRowBuilder<ModalActionRowComponentBuilder> | APIActionRowComponent<APIModalActionRowComponent>
>
) {
this.components.push(
...normalizeArray(components).map((component) =>
component instanceof ActionRowBuilder
? component
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
),
);
return this;
}
/**
* Sets the components in this modal
*
* @param components - The components to set this modal to
*/
public setComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>) {
this.components.splice(0, this.components.length, ...normalizeArray(components));
return this;
}
public toJSON(): APIModalInteractionResponseCallbackData {
validateRequiredParameters(this.data.custom_id, this.data.title, this.components);
return super.toJSON();
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
components: this.components.map((component) => component.toJSON()),
} as APIModalInteractionResponseCallbackData;
}
}

View File

@@ -1,72 +0,0 @@
import type {
APIActionRowComponent,
APIModalActionRowComponent,
APIModalInteractionResponseCallbackData,
} from 'discord-api-types/v10';
import { ActionRowBuilder, createComponentBuilder, JSONEncodable, ModalActionRowComponentBuilder } from '../../index';
export class UnsafeModalBuilder implements JSONEncodable<APIModalInteractionResponseCallbackData> {
public readonly data: Partial<APIModalInteractionResponseCallbackData>;
public readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[] = [];
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
this.data = { ...data };
this.components = (components?.map((c) => createComponentBuilder(c)) ??
[]) as ActionRowBuilder<ModalActionRowComponentBuilder>[];
}
/**
* Sets the title of the modal
* @param title The title of the modal
*/
public setTitle(title: string) {
this.data.title = title;
return this;
}
/**
* Sets the custom id of the modal
* @param customId The custom id of this modal
*/
public setCustomId(customId: string) {
this.data.custom_id = customId;
return this;
}
/**
* Adds components to this modal
* @param components The components to add to this modal
*/
public addComponents(
components: (
| ActionRowBuilder<ModalActionRowComponentBuilder>
| APIActionRowComponent<APIModalActionRowComponent>
)[],
) {
this.components.push(
...components.map((component) =>
component instanceof ActionRowBuilder
? component
: new ActionRowBuilder<ModalActionRowComponentBuilder>(component),
),
);
return this;
}
/**
* Sets the components in this modal
* @param components The components to set this modal to
*/
public setComponents(components: ActionRowBuilder<ModalActionRowComponentBuilder>[]) {
this.components.splice(0, this.components.length, ...components);
return this;
}
public toJSON(): APIModalInteractionResponseCallbackData {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
return {
...this.data,
components: this.components.map((component) => component.toJSON()),
} as APIModalInteractionResponseCallbackData;
}
}

View File

@@ -1,27 +1,31 @@
import { s } from '@sapphire/shapeshift';
import is from '@sindresorhus/is';
import { type APIApplicationCommandOptionChoice, Locale, LocalizationMap } from 'discord-api-types/v10';
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
import type { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands';
import type { ApplicationCommandOptionBase } from './mixins/ApplicationCommandOptionBase';
import { isValidationEnabled } from '../../util/validation';
const namePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(32)
.regex(/^[\P{Lu}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u);
.regex(/^[\p{Ll}\p{Lm}\p{Lo}\p{N}\p{sc=Devanagari}\p{sc=Thai}_-]+$/u)
.setValidationEnabled(isValidationEnabled);
export function validateName(name: unknown): asserts name is string {
namePredicate.parse(name);
}
const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(100);
const descriptionPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(100)
.setValidationEnabled(isValidationEnabled);
const localePredicate = s.nativeEnum(Locale);
export function validateDescription(description: unknown): asserts description is string {
descriptionPredicate.parse(description);
}
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25);
const maxArrayLengthPredicate = s.unknown.array.lengthLessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateLocale(locale: unknown) {
return localePredicate.parse(locale);
}
@@ -55,7 +59,7 @@ export function validateRequired(required: unknown): asserts required is boolean
booleanPredicate.parse(required);
}
const choicesLengthPredicate = s.number.lessThanOrEqual(25);
const choicesLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateChoicesLength(amountAdding: number, choices?: APIApplicationCommandOptionChoice[]): void {
choicesLengthPredicate.parse((choices?.length ?? 0) + amountAdding);
@@ -64,33 +68,12 @@ export function validateChoicesLength(amountAdding: number, choices?: APIApplica
export function assertReturnOfBuilder<
T extends ApplicationCommandOptionBase | SlashCommandSubcommandBuilder | SlashCommandSubcommandGroupBuilder,
>(input: unknown, ExpectedInstanceOf: new () => T): asserts input is T {
const instanceName = ExpectedInstanceOf.name;
if (is.nullOrUndefined(input)) {
throw new TypeError(
`Expected to receive a ${instanceName} builder, got ${input === null ? 'null' : 'undefined'} instead.`,
);
}
if (is.primitive(input)) {
throw new TypeError(`Expected to receive a ${instanceName} builder, got a primitive (${typeof input}) instead.`);
}
if (!(input instanceof ExpectedInstanceOf)) {
const casted = input as Record<PropertyKey, unknown>;
const constructorName = is.function_(input) ? input.name : casted.constructor.name;
const stringTag = Reflect.get(casted, Symbol.toStringTag) as string | undefined;
const fullResultName = stringTag ? `${constructorName} [${stringTag}]` : constructorName;
throw new TypeError(`Expected to receive a ${instanceName} builder, got ${fullResultName} instead.`);
}
s.instance(ExpectedInstanceOf).parse(input);
}
export const localizationMapPredicate = s.object<LocalizationMap>(
Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])),
).strict.nullish;
export const localizationMapPredicate = s
.object<LocalizationMap>(Object.fromEntries(Object.values(Locale).map((locale) => [locale, s.string.nullish])))
.strict.nullish.setValidationEnabled(isValidationEnabled);
export function validateLocalizationMap(value: unknown): asserts value is LocalizationMap {
localizationMapPredicate.parse(value);

View File

@@ -86,7 +86,7 @@ export class SlashCommandBuilder {
*
* **Note**: If set to `false`, you will have to later `PUT` the permissions for this command.
*
* @param value Whether or not to enable this command by default
* @param value - Whether or not to enable this command by default
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
* @deprecated Use `setDefaultMemberPermissions` or `setDMPermission` instead.
@@ -105,7 +105,7 @@ export class SlashCommandBuilder {
*
* **Note:** You can set this to `'0'` to disable the command by default.
*
* @param permissions The permissions bit field to set
* @param permissions - The permissions bit field to set
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
@@ -122,7 +122,7 @@ export class SlashCommandBuilder {
* Sets if the command is available in DMs with the application, only for globally-scoped commands.
* By default, commands are visible.
*
* @param enabled If the command should be enabled in DMs
* @param enabled - If the command should be enabled in DMs
*
* @see https://discord.com/developers/docs/interactions/application-commands#permissions
*/
@@ -138,7 +138,7 @@ export class SlashCommandBuilder {
/**
* Adds a new subcommand group to this command
*
* @param input A function that returns a subcommand group builder, or an already built builder
* @param input - A function that returns a subcommand group builder, or an already built builder
*/
public addSubcommandGroup(
input:
@@ -164,7 +164,7 @@ export class SlashCommandBuilder {
/**
* Adds a new subcommand to this command
*
* @param input A function that returns a subcommand builder, or an already built builder
* @param input - A function that returns a subcommand builder, or an already built builder
*/
public addSubcommand(
input:

View File

@@ -35,7 +35,7 @@ export class SlashCommandSubcommandGroupBuilder implements ToAPIApplicationComma
/**
* Adds a new subcommand to this group
*
* @param input A function that returns a subcommand builder, or an already built builder
* @param input - A function that returns a subcommand builder, or an already built builder
*/
public addSubcommand(
input:

View File

@@ -4,13 +4,15 @@ export abstract class ApplicationCommandNumericOptionMinMaxValueMixin {
/**
* Sets the maximum number value of this option
* @param max The maximum value this option can be
*
* @param max - The maximum value this option can be
*/
public abstract setMaxValue(max: number): this;
/**
* Sets the minimum number value of this option
* @param min The minimum value this option can be
*
* @param min - The minimum value this option can be
*/
public abstract setMinValue(min: number): this;
}

View File

@@ -10,7 +10,7 @@ export abstract class ApplicationCommandOptionBase extends SharedNameAndDescript
/**
* Marks the option as required
*
* @param required If this option should be required
* @param required - If this option should be required
*/
public setRequired(required: boolean) {
// Assert that you actually passed a boolean

View File

@@ -23,7 +23,7 @@ export class ApplicationCommandOptionChannelTypesMixin {
/**
* Adds channel types to this option
*
* @param channelTypes The channel types to add
* @param channelTypes - The channel types to add
*/
public addChannelTypes(...channelTypes: ApplicationCommandOptionAllowedChannelTypes[]) {
if (this.channel_types === undefined) {

View File

@@ -21,7 +21,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
/**
* Adds multiple choices for this option
*
* @param choices The choices to add
* @param choices - The choices to add
*/
public addChoices(...choices: APIApplicationCommandOptionChoice<T>[]): this {
if (choices.length > 0 && this.autocomplete) {
@@ -65,7 +65,7 @@ export class ApplicationCommandOptionWithChoicesAndAutocompleteMixin<T extends s
/**
* Marks the option as autocompletable
* @param autocomplete If this option should be autocompletable
* @param autocomplete - If this option should be autocompletable
*/
public setAutocomplete(autocomplete: boolean): this {
// Assert that you actually passed a boolean

View File

@@ -10,7 +10,7 @@ export class SharedNameAndDescription {
/**
* Sets the name
*
* @param name The name
* @param name - The name
*/
public setName(name: string): this {
// Assert the name matches the conditions
@@ -24,7 +24,7 @@ export class SharedNameAndDescription {
/**
* Sets the description
*
* @param description The description
* @param description - The description
*/
public setDescription(description: string) {
// Assert the description matches the conditions
@@ -38,8 +38,8 @@ export class SharedNameAndDescription {
/**
* Sets a name localization
*
* @param locale The locale to set a description for
* @param localizedName The localized description for the given locale
* @param locale - The locale to set a description for
* @param localizedName - The localized description for the given locale
*/
public setNameLocalization(locale: LocaleString, localizedName: string | null) {
if (!this.name_localizations) {
@@ -62,7 +62,7 @@ export class SharedNameAndDescription {
/**
* Sets the name localizations
*
* @param localizedNames The dictionary of localized descriptions to set
* @param localizedNames - The dictionary of localized descriptions to set
*/
public setNameLocalizations(localizedNames: LocalizationMap | null) {
if (localizedNames === null) {
@@ -81,8 +81,8 @@ export class SharedNameAndDescription {
/**
* Sets a description localization
*
* @param locale The locale to set a description for
* @param localizedDescription The localized description for the given locale
* @param locale - The locale to set a description for
* @param localizedDescription - The localized description for the given locale
*/
public setDescriptionLocalization(locale: LocaleString, localizedDescription: string | null) {
if (!this.description_localizations) {
@@ -105,7 +105,7 @@ export class SharedNameAndDescription {
/**
* Sets the description localizations
*
* @param localizedDescriptions The dictionary of localized descriptions to set
* @param localizedDescriptions - The dictionary of localized descriptions to set
*/
public setDescriptionLocalizations(localizedDescriptions: LocalizationMap | null) {
if (localizedDescriptions === null) {

View File

@@ -17,7 +17,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a boolean option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addBooleanOption(
input: SlashCommandBooleanOption | ((builder: SlashCommandBooleanOption) => SlashCommandBooleanOption),
@@ -28,7 +28,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a user option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addUserOption(input: SlashCommandUserOption | ((builder: SlashCommandUserOption) => SlashCommandUserOption)) {
return this._sharedAddOptionMethod(input, SlashCommandUserOption);
@@ -37,7 +37,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a channel option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addChannelOption(
input: SlashCommandChannelOption | ((builder: SlashCommandChannelOption) => SlashCommandChannelOption),
@@ -48,7 +48,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a role option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addRoleOption(input: SlashCommandRoleOption | ((builder: SlashCommandRoleOption) => SlashCommandRoleOption)) {
return this._sharedAddOptionMethod(input, SlashCommandRoleOption);
@@ -57,7 +57,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds an attachment option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addAttachmentOption(
input: SlashCommandAttachmentOption | ((builder: SlashCommandAttachmentOption) => SlashCommandAttachmentOption),
@@ -68,7 +68,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a mentionable option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addMentionableOption(
input: SlashCommandMentionableOption | ((builder: SlashCommandMentionableOption) => SlashCommandMentionableOption),
@@ -79,7 +79,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a string option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addStringOption(
input:
@@ -99,7 +99,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds an integer option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addIntegerOption(
input:
@@ -119,7 +119,7 @@ export class SharedSlashCommandOptions<ShouldOmitSubcommandFunctions = true> {
/**
* Adds a number option
*
* @param input A function that returns an option builder, or an already built builder
* @param input - A function that returns an option builder, or an already built builder
*/
public addNumberOption(
input:

View File

@@ -1,11 +1,43 @@
import { s } from '@sapphire/shapeshift';
import { APIApplicationCommandStringOption, ApplicationCommandOptionType } from 'discord-api-types/v10';
import { mix } from 'ts-mixer';
import { ApplicationCommandOptionBase } from '../mixins/ApplicationCommandOptionBase';
import { ApplicationCommandOptionWithChoicesAndAutocompleteMixin } from '../mixins/ApplicationCommandOptionWithChoicesAndAutocompleteMixin';
const minLengthValidator = s.number.greaterThanOrEqual(0).lessThanOrEqual(6000);
const maxLengthValidator = s.number.greaterThanOrEqual(1).lessThanOrEqual(6000);
@mix(ApplicationCommandOptionWithChoicesAndAutocompleteMixin)
export class SlashCommandStringOption extends ApplicationCommandOptionBase {
public readonly type = ApplicationCommandOptionType.String as const;
public readonly max_length?: number;
public readonly min_length?: number;
/**
* Sets the maximum length of this string option.
*
* @param max - The maximum length this option can be
*/
public setMaxLength(max: number): this {
maxLengthValidator.parse(max);
Reflect.set(this, 'max_length', max);
return this;
}
/**
* Sets the minimum length of this string option.
*
* @param min - The minimum length this option can be
*/
public setMinLength(min: number): this {
minLengthValidator.parse(min);
Reflect.set(this, 'min_length', min);
return this;
}
public toJSON(): APIApplicationCommandStringOption {
this.runRequiredValidations();

View File

@@ -1,57 +1,84 @@
import { s } from '@sapphire/shapeshift';
import type { APIEmbedField } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation';
export const fieldNamePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(256);
export const fieldNamePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(256)
.setValidationEnabled(isValidationEnabled);
export const fieldValuePredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(1024);
export const fieldValuePredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(1024)
.setValidationEnabled(isValidationEnabled);
export const fieldInlinePredicate = s.boolean.optional;
export const embedFieldPredicate = s.object({
name: fieldNamePredicate,
value: fieldValuePredicate,
inline: fieldInlinePredicate,
});
export const embedFieldPredicate = s
.object({
name: fieldNamePredicate,
value: fieldValuePredicate,
inline: fieldInlinePredicate,
})
.setValidationEnabled(isValidationEnabled);
export const embedFieldsArrayPredicate = embedFieldPredicate.array;
export const embedFieldsArrayPredicate = embedFieldPredicate.array.setValidationEnabled(isValidationEnabled);
export const fieldLengthPredicate = s.number.lessThanOrEqual(25);
export const fieldLengthPredicate = s.number.lessThanOrEqual(25).setValidationEnabled(isValidationEnabled);
export function validateFieldLength(amountAdding: number, fields?: APIEmbedField[]): void {
fieldLengthPredicate.parse((fields?.length ?? 0) + amountAdding);
}
export const authorNamePredicate = fieldNamePredicate.nullable;
export const authorNamePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);
export const imageURLPredicate = s.string.url({
allowedProtocols: ['http:', 'https:', 'attachment:'],
}).nullish;
export const imageURLPredicate = s.string
.url({
allowedProtocols: ['http:', 'https:', 'attachment:'],
})
.nullish.setValidationEnabled(isValidationEnabled);
export const urlPredicate = s.string.url({
allowedProtocols: ['http:', 'https:'],
}).nullish;
export const urlPredicate = s.string
.url({
allowedProtocols: ['http:', 'https:'],
})
.nullish.setValidationEnabled(isValidationEnabled);
export const embedAuthorPredicate = s.object({
name: authorNamePredicate,
iconURL: imageURLPredicate,
url: urlPredicate,
});
export const embedAuthorPredicate = s
.object({
name: authorNamePredicate,
iconURL: imageURLPredicate,
url: urlPredicate,
})
.setValidationEnabled(isValidationEnabled);
export const RGBPredicate = s.number.int.greaterThanOrEqual(0).lessThanOrEqual(255);
export const RGBPredicate = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(255)
.setValidationEnabled(isValidationEnabled);
export const colorPredicate = s.number.int
.greaterThanOrEqual(0)
.lessThanOrEqual(0xffffff)
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate])).nullable;
.or(s.tuple([RGBPredicate, RGBPredicate, RGBPredicate]))
.nullable.setValidationEnabled(isValidationEnabled);
export const descriptionPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(4096).nullable;
export const descriptionPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(4096)
.nullable.setValidationEnabled(isValidationEnabled);
export const footerTextPredicate = s.string.lengthGreaterThanOrEqual(1).lengthLessThanOrEqual(2048).nullable;
export const footerTextPredicate = s.string
.lengthGreaterThanOrEqual(1)
.lengthLessThanOrEqual(2048)
.nullable.setValidationEnabled(isValidationEnabled);
export const embedFooterPredicate = s.object({
text: footerTextPredicate,
iconURL: imageURLPredicate,
});
export const embedFooterPredicate = s
.object({
text: footerTextPredicate,
iconURL: imageURLPredicate,
})
.setValidationEnabled(isValidationEnabled);
export const timestampPredicate = s.union(s.number, s.date).nullable;
export const timestampPredicate = s.union(s.number, s.date).nullable.setValidationEnabled(isValidationEnabled);
export const titlePredicate = fieldNamePredicate.nullable;
export const titlePredicate = fieldNamePredicate.nullable.setValidationEnabled(isValidationEnabled);

View File

@@ -1,4 +1,4 @@
import type { APIEmbedField } from 'discord-api-types/v10';
import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter, APIEmbedImage } from 'discord-api-types/v10';
import {
colorPredicate,
descriptionPredicate,
@@ -11,82 +11,228 @@ import {
urlPredicate,
validateFieldLength,
} from './Assertions';
import { EmbedAuthorOptions, EmbedFooterOptions, RGBTuple, UnsafeEmbedBuilder } from './UnsafeEmbed';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray';
export type RGBTuple = [red: number, green: number, blue: number];
export interface IconData {
/**
* The URL of the icon
*/
iconURL?: string;
/**
* The proxy URL of the icon
*/
proxyIconURL?: string;
}
export type EmbedAuthorData = Omit<APIEmbedAuthor, 'icon_url' | 'proxy_icon_url'> & IconData;
export type EmbedAuthorOptions = Omit<EmbedAuthorData, 'proxyIconURL'>;
export type EmbedFooterData = Omit<APIEmbedFooter, 'icon_url' | 'proxy_icon_url'> & IconData;
export type EmbedFooterOptions = Omit<EmbedFooterData, 'proxyIconURL'>;
export interface EmbedImageData extends Omit<APIEmbedImage, 'proxy_url'> {
/**
* The proxy URL for the image
*/
proxyURL?: string;
}
/**
* Represents a validated embed in a message (image/video preview, rich embed, etc.)
* Represents a embed in a message (image/video preview, rich embed, etc.)
*/
export class EmbedBuilder extends UnsafeEmbedBuilder {
public override addFields(fields: APIEmbedField[]): this {
export class EmbedBuilder {
public readonly data: APIEmbed;
public constructor(data: APIEmbed = {}) {
this.data = { ...data };
if (data.timestamp) this.data.timestamp = new Date(data.timestamp).toISOString();
}
/**
* Adds fields to the embed (max 25)
*
* @param fields The fields to add
*/
public addFields(...fields: RestOrArray<APIEmbedField>): this {
fields = normalizeArray(fields);
// Ensure adding these fields won't exceed the 25 field limit
validateFieldLength(fields.length, this.data.fields);
// Data assertions
return super.addFields(embedFieldsArrayPredicate.parse(fields));
embedFieldsArrayPredicate.parse(fields);
if (this.data.fields) this.data.fields.push(...fields);
else this.data.fields = fields;
return this;
}
public override spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
/**
* Removes, replaces, or inserts fields in the embed (max 25)
*
* @param index The index to start at
* @param deleteCount The number of fields to remove
* @param fields The replacing field objects
*/
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
// Ensure adding these fields won't exceed the 25 field limit
validateFieldLength(fields.length - deleteCount, this.data.fields);
// Data assertions
return super.spliceFields(index, deleteCount, ...embedFieldsArrayPredicate.parse(fields));
embedFieldsArrayPredicate.parse(fields);
if (this.data.fields) this.data.fields.splice(index, deleteCount, ...fields);
else this.data.fields = fields;
return this;
}
public override setAuthor(options: EmbedAuthorOptions | null): this {
/**
* Sets the embed's fields (max 25).
* @param fields The fields to set
*/
public setFields(...fields: RestOrArray<APIEmbedField>) {
this.spliceFields(0, this.data.fields?.length ?? 0, ...normalizeArray(fields));
return this;
}
/**
* Sets the author of this embed
*
* @param options The options for the author
*/
public setAuthor(options: EmbedAuthorOptions | null): this {
if (options === null) {
return super.setAuthor(null);
this.data.author = undefined;
return this;
}
// Data assertions
embedAuthorPredicate.parse(options);
return super.setAuthor(options);
this.data.author = { name: options.name, url: options.url, icon_url: options.iconURL };
return this;
}
public override setColor(color: number | RGBTuple | null): this {
/**
* Sets the color of this embed
*
* @param color The color of the embed
*/
public setColor(color: number | RGBTuple | null): this {
// Data assertions
return super.setColor(colorPredicate.parse(color));
colorPredicate.parse(color);
if (Array.isArray(color)) {
const [red, green, blue] = color;
this.data.color = (red << 16) + (green << 8) + blue;
return this;
}
this.data.color = color ?? undefined;
return this;
}
public override setDescription(description: string | null): this {
/**
* Sets the description of this embed
*
* @param description The description
*/
public setDescription(description: string | null): this {
// Data assertions
return super.setDescription(descriptionPredicate.parse(description));
descriptionPredicate.parse(description);
this.data.description = description ?? undefined;
return this;
}
public override setFooter(options: EmbedFooterOptions | null): this {
/**
* Sets the footer of this embed
*
* @param options The options for the footer
*/
public setFooter(options: EmbedFooterOptions | null): this {
if (options === null) {
return super.setFooter(null);
this.data.footer = undefined;
return this;
}
// Data assertions
embedFooterPredicate.parse(options);
return super.setFooter(options);
this.data.footer = { text: options.text, icon_url: options.iconURL };
return this;
}
public override setImage(url: string | null): this {
/**
* Sets the image of this embed
*
* @param url The URL of the image
*/
public setImage(url: string | null): this {
// Data assertions
return super.setImage(imageURLPredicate.parse(url)!);
imageURLPredicate.parse(url);
this.data.image = url ? { url } : undefined;
return this;
}
public override setThumbnail(url: string | null): this {
/**
* Sets the thumbnail of this embed
*
* @param url The URL of the thumbnail
*/
public setThumbnail(url: string | null): this {
// Data assertions
return super.setThumbnail(imageURLPredicate.parse(url)!);
imageURLPredicate.parse(url);
this.data.thumbnail = url ? { url } : undefined;
return this;
}
public override setTimestamp(timestamp: number | Date | null = Date.now()): this {
/**
* Sets the timestamp of this embed
*
* @param timestamp The timestamp or date
*/
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
// Data assertions
return super.setTimestamp(timestampPredicate.parse(timestamp));
timestampPredicate.parse(timestamp);
this.data.timestamp = timestamp ? new Date(timestamp).toISOString() : undefined;
return this;
}
public override setTitle(title: string | null): this {
/**
* Sets the title of this embed
*
* @param title The title
*/
public setTitle(title: string | null): this {
// Data assertions
return super.setTitle(titlePredicate.parse(title));
titlePredicate.parse(title);
this.data.title = title ?? undefined;
return this;
}
public override setURL(url: string | null): this {
/**
* Sets the URL of this embed
*
* @param url The URL
*/
public setURL(url: string | null): this {
// Data assertions
return super.setURL(urlPredicate.parse(url)!);
urlPredicate.parse(url);
this.data.url = url ?? undefined;
return this;
}
/**
* Transforms the embed to a plain object
*/
public toJSON(): APIEmbed {
return { ...this.data };
}
}

View File

@@ -1,186 +0,0 @@
import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter, APIEmbedImage } from 'discord-api-types/v10';
export type RGBTuple = [red: number, green: number, blue: number];
export interface IconData {
/**
* The URL of the icon
*/
iconURL?: string;
/**
* The proxy URL of the icon
*/
proxyIconURL?: string;
}
export type EmbedAuthorData = Omit<APIEmbedAuthor, 'icon_url' | 'proxy_icon_url'> & IconData;
export type EmbedAuthorOptions = Omit<EmbedAuthorData, 'proxyIconURL'>;
export type EmbedFooterData = Omit<APIEmbedFooter, 'icon_url' | 'proxy_icon_url'> & IconData;
export type EmbedFooterOptions = Omit<EmbedFooterData, 'proxyIconURL'>;
export interface EmbedImageData extends Omit<APIEmbedImage, 'proxy_url'> {
/**
* The proxy URL for the image
*/
proxyURL?: string;
}
/**
* Represents a non-validated embed in a message (image/video preview, rich embed, etc.)
*/
export class UnsafeEmbedBuilder {
public readonly data: APIEmbed;
public constructor(data: APIEmbed = {}) {
this.data = { ...data };
if (data.timestamp) this.data.timestamp = new Date(data.timestamp).toISOString();
}
/**
* Adds fields to the embed (max 25)
*
* @param fields The fields to add
*/
public addFields(fields: APIEmbedField[]): this {
if (this.data.fields) this.data.fields.push(...fields);
else this.data.fields = fields;
return this;
}
/**
* Removes, replaces, or inserts fields in the embed (max 25)
*
* @param index The index to start at
* @param deleteCount The number of fields to remove
* @param fields The replacing field objects
*/
public spliceFields(index: number, deleteCount: number, ...fields: APIEmbedField[]): this {
if (this.data.fields) this.data.fields.splice(index, deleteCount, ...fields);
else this.data.fields = fields;
return this;
}
/**
* Sets the embed's fields (max 25).
* @param fields The fields to set
*/
public setFields(fields: APIEmbedField[]) {
this.spliceFields(0, this.data.fields?.length ?? 0, ...fields);
return this;
}
/**
* Sets the author of this embed
*
* @param options The options for the author
*/
public setAuthor(options: EmbedAuthorOptions | null): this {
if (options === null) {
this.data.author = undefined;
return this;
}
this.data.author = { name: options.name, url: options.url, icon_url: options.iconURL };
return this;
}
/**
* Sets the color of this embed
*
* @param color The color of the embed
*/
public setColor(color: number | RGBTuple | null): this {
if (Array.isArray(color)) {
const [red, green, blue] = color;
this.data.color = (red << 16) + (green << 8) + blue;
return this;
}
this.data.color = color ?? undefined;
return this;
}
/**
* Sets the description of this embed
*
* @param description The description
*/
public setDescription(description: string | null): this {
this.data.description = description ?? undefined;
return this;
}
/**
* Sets the footer of this embed
*
* @param options The options for the footer
*/
public setFooter(options: EmbedFooterOptions | null): this {
if (options === null) {
this.data.footer = undefined;
return this;
}
this.data.footer = { text: options.text, icon_url: options.iconURL };
return this;
}
/**
* Sets the image of this embed
*
* @param url The URL of the image
*/
public setImage(url: string | null): this {
this.data.image = url ? { url } : undefined;
return this;
}
/**
* Sets the thumbnail of this embed
*
* @param url The URL of the thumbnail
*/
public setThumbnail(url: string | null): this {
this.data.thumbnail = url ? { url } : undefined;
return this;
}
/**
* Sets the timestamp of this embed
*
* @param timestamp The timestamp or date
*/
public setTimestamp(timestamp: number | Date | null = Date.now()): this {
this.data.timestamp = timestamp ? new Date(timestamp).toISOString() : undefined;
return this;
}
/**
* Sets the title of this embed
*
* @param title The title
*/
public setTitle(title: string | null): this {
this.data.title = title ?? undefined;
return this;
}
/**
* Sets the URL of this embed
*
* @param url The URL
*/
public setURL(url: string | null): this {
this.data.url = url ?? undefined;
return this;
}
/**
* Transforms the embed to a plain object
*/
public toJSON(): APIEmbed {
return { ...this.data };
}
}

View File

@@ -4,15 +4,15 @@ import type { Snowflake } from 'discord-api-types/globals';
/**
* Wraps the content inside a codeblock with no language
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function codeBlock<C extends string>(content: C): `\`\`\`\n${C}\`\`\``;
/**
* Wraps the content inside a codeblock with the specified language
*
* @param language The language for the codeblock
* @param content The content to wrap
* @param language - The language for the codeblock
* @param content - The content to wrap
*/
export function codeBlock<L extends string, C extends string>(language: L, content: C): `\`\`\`${L}\n${C}\`\`\``;
export function codeBlock(language: string, content?: string): string {
@@ -22,7 +22,7 @@ export function codeBlock(language: string, content?: string): string {
/**
* Wraps the content inside \`backticks\`, which formats it as inline code
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function inlineCode<C extends string>(content: C): `\`${C}\`` {
return `\`${content}\``;
@@ -31,7 +31,7 @@ export function inlineCode<C extends string>(content: C): `\`${C}\`` {
/**
* Formats the content into italic text
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function italic<C extends string>(content: C): `_${C}_` {
return `_${content}_`;
@@ -40,7 +40,7 @@ export function italic<C extends string>(content: C): `_${C}_` {
/**
* Formats the content into bold text
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function bold<C extends string>(content: C): `**${C}**` {
return `**${content}**`;
@@ -49,7 +49,7 @@ export function bold<C extends string>(content: C): `**${C}**` {
/**
* Formats the content into underscored text
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function underscore<C extends string>(content: C): `__${C}__` {
return `__${content}__`;
@@ -58,7 +58,7 @@ export function underscore<C extends string>(content: C): `__${C}__` {
/**
* Formats the content into strike-through text
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function strikethrough<C extends string>(content: C): `~~${C}~~` {
return `~~${content}~~`;
@@ -67,7 +67,7 @@ export function strikethrough<C extends string>(content: C): `~~${C}~~` {
/**
* Formats the content into a quote. This needs to be at the start of the line for Discord to format it
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function quote<C extends string>(content: C): `> ${C}` {
return `> ${content}`;
@@ -76,7 +76,7 @@ export function quote<C extends string>(content: C): `> ${C}` {
/**
* Formats the content into a block quote. This needs to be at the start of the line for Discord to format it
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function blockQuote<C extends string>(content: C): `>>> ${C}` {
return `>>> ${content}`;
@@ -85,14 +85,14 @@ export function blockQuote<C extends string>(content: C): `>>> ${C}` {
/**
* Wraps the URL into `<>`, which stops it from embedding
*
* @param url The URL to wrap
* @param url - The URL to wrap
*/
export function hideLinkEmbed<C extends string>(url: C): `<${C}>`;
/**
* Wraps the URL into `<>`, which stops it from embedding
*
* @param url The URL to wrap
* @param url - The URL to wrap
*/
export function hideLinkEmbed(url: URL): `<${string}>`;
export function hideLinkEmbed(url: string | URL) {
@@ -103,25 +103,25 @@ export function hideLinkEmbed(url: string | URL) {
/**
* Formats the content and the URL into a masked URL
*
* @param content The content to display
* @param url The URL the content links to
* @param content - The content to display
* @param url - The URL the content links to
*/
export function hyperlink<C extends string>(content: C, url: URL): `[${C}](${string})`;
/**
* Formats the content and the URL into a masked URL
*
* @param content The content to display
* @param url The URL the content links to
* @param content - The content to display
* @param url - The URL the content links to
*/
export function hyperlink<C extends string, U extends string>(content: C, url: U): `[${C}](${U})`;
/**
* Formats the content and the URL into a masked URL
*
* @param content The content to display
* @param url The URL the content links to
* @param title The title shown when hovering on the masked link
* @param content - The content to display
* @param url - The URL the content links to
* @param title - The title shown when hovering on the masked link
*/
export function hyperlink<C extends string, T extends string>(
content: C,
@@ -132,9 +132,9 @@ export function hyperlink<C extends string, T extends string>(
/**
* Formats the content and the URL into a masked URL
*
* @param content The content to display
* @param url The URL the content links to
* @param title The title shown when hovering on the masked link
* @param content - The content to display
* @param url - The URL the content links to
* @param title - The title shown when hovering on the masked link
*/
export function hyperlink<C extends string, U extends string, T extends string>(
content: C,
@@ -149,7 +149,7 @@ export function hyperlink(content: string, url: string | URL, title?: string) {
/**
* Wraps the content inside spoiler (hidden text)
*
* @param content The content to wrap
* @param content - The content to wrap
*/
export function spoiler<C extends string>(content: C): `||${C}||` {
return `||${content}||`;
@@ -158,7 +158,7 @@ export function spoiler<C extends string>(content: C): `||${C}||` {
/**
* Formats a user ID into a user mention
*
* @param userId The user ID to format
* @param userId - The user ID to format
*/
export function userMention<C extends Snowflake>(userId: C): `<@${C}>` {
return `<@${userId}>`;
@@ -167,7 +167,7 @@ export function userMention<C extends Snowflake>(userId: C): `<@${C}>` {
/**
* Formats a channel ID into a channel mention
*
* @param channelId The channel ID to format
* @param channelId - The channel ID to format
*/
export function channelMention<C extends Snowflake>(channelId: C): `<#${C}>` {
return `<#${channelId}>`;
@@ -176,7 +176,7 @@ export function channelMention<C extends Snowflake>(channelId: C): `<#${C}>` {
/**
* Formats a role ID into a role mention
*
* @param roleId The role ID to format
* @param roleId - The role ID to format
*/
export function roleMention<C extends Snowflake>(roleId: C): `<@&${C}>` {
return `<@&${roleId}>`;
@@ -185,23 +185,23 @@ export function roleMention<C extends Snowflake>(roleId: C): `<@&${C}>` {
/**
* Formats an emoji ID into a fully qualified emoji identifier
*
* @param emojiId The emoji ID to format
* @param emojiId - The emoji ID to format
*/
export function formatEmoji<C extends Snowflake>(emojiId: C, animated?: false): `<:_:${C}>`;
/**
* Formats an emoji ID into a fully qualified emoji identifier
*
* @param emojiId The emoji ID to format
* @param animated Whether the emoji is animated or not. Defaults to `false`
* @param emojiId - The emoji ID to format
* @param animated - Whether the emoji is animated or not. Defaults to `false`
*/
export function formatEmoji<C extends Snowflake>(emojiId: C, animated?: true): `<a:_:${C}>`;
/**
* Formats an emoji ID into a fully qualified emoji identifier
*
* @param emojiId The emoji ID to format
* @param animated Whether the emoji is animated or not. Defaults to `false`
* @param emojiId - The emoji ID to format
* @param animated - Whether the emoji is animated or not. Defaults to `false`
*/
export function formatEmoji<C extends Snowflake>(emojiId: C, animated = false): `<a:_:${C}>` | `<:_:${C}>` {
return `<${animated ? 'a' : ''}:_:${emojiId}>`;
@@ -210,30 +210,30 @@ export function formatEmoji<C extends Snowflake>(emojiId: C, animated = false):
/**
* Formats a date into a short date-time string
*
* @param date The date to format, defaults to the current time
* @param date - The date to format, defaults to the current time
*/
export function time(date?: Date): `<t:${bigint}>`;
/**
* Formats a date given a format style
*
* @param date The date to format
* @param style The style to use
* @param date - The date to format
* @param style - The style to use
*/
export function time<S extends TimestampStylesString>(date: Date, style: S): `<t:${bigint}:${S}>`;
/**
* Formats the given timestamp into a short date-time string
*
* @param seconds The time to format, represents an UNIX timestamp in seconds
* @param seconds - The time to format, represents an UNIX timestamp in seconds
*/
export function time<C extends number>(seconds: C): `<t:${C}>`;
/**
* Formats the given timestamp into a short date-time string
*
* @param seconds The time to format, represents an UNIX timestamp in seconds
* @param style The style to use
* @param seconds - The time to format, represents an UNIX timestamp in seconds
* @param style - The style to use
*/
export function time<C extends number, S extends TimestampStylesString>(seconds: C, style: S): `<t:${C}:${S}>`;
export function time(timeOrSeconds?: number | Date, style?: TimestampStylesString): string {

View File

@@ -7,7 +7,7 @@ export interface Equatable<T> {
/**
* Indicates if an object is equatable or not.
* @param maybeEquatable The object to check against
* @param maybeEquatable - The object to check against
*/
export function isEquatable(maybeEquatable: unknown): maybeEquatable is Equatable<unknown> {
return maybeEquatable !== null && typeof maybeEquatable === 'object' && 'equals' in maybeEquatable;

View File

@@ -7,7 +7,7 @@ export interface JSONEncodable<T> {
/**
* Indicates if an object is encodable or not.
* @param maybeEncodable The object to check against
* @param maybeEncodable - The object to check against
*/
export function isJSONEncodable(maybeEncodable: unknown): maybeEncodable is JSONEncodable<unknown> {
return maybeEncodable !== null && typeof maybeEncodable === 'object' && 'toJSON' in maybeEncodable;

View File

@@ -0,0 +1,6 @@
export function normalizeArray<T>(arr: RestOrArray<T>): T[] {
if (Array.isArray(arr[0])) return arr[0];
return arr as T[];
}
export type RestOrArray<T> = T[] | [T[]];

View File

@@ -0,0 +1,5 @@
let validate = true;
export const enableValidators = () => (validate = true);
export const disableValidators = () => (validate = false);
export const isValidationEnabled = () => validate;

View File

@@ -1,4 +1,7 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"exactOptionalPropertyTypes": false
},
"include": ["src/**/*.ts"]
}

Some files were not shown because too many files have changed in this diff Show More