Compare commits

..

92 Commits

Author SHA1 Message Date
github-actions[bot]
790ce1681c chore(main): release 1.2.1 (#148)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-10-03 11:53:45 -05:00
Evo
cbad7380e1 fix(autocomplete): now support multiple autocomplete options (#147) 2022-10-03 11:50:49 -05:00
github-actions[bot]
ad36875be2 chore(main): release 1.2.0 (#145)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-09-28 11:57:10 -05:00
Jacob Nguyen
50288867a5 feat: classmodules@arcs (#143)
* feat: add class based commands

* docs: add deprecation warnings

* feat: add deprecation warnings

* feat: add more deprecation warnings

* feat: add prototype ClassModule abstract class

* feat: add EventModuleClass prototype, change names

* feat: more flexible contract

* feat: EventExecutable

* fix: typo

* feat: made abstract classes because of defaults

* fix: typings

* feat: update Context typings, update to djs v15

* chore: update typescript dependency

* chore: bump version

* chore: update ignore

* chore: prettier

* docs: change readme to be docusaurus compliant
2022-09-28 11:52:25 -05:00
Jacob Nguyen
6b8995d149 feat: update CODEOWNERS 2022-09-13 14:41:57 -05:00
xxDeveloper
82bbddac8d revert: feat of allow constructable modules (#138)
Revert "feat: allow constructable modules (#133)"

This reverts commit 03936eb2ea.
2022-09-13 14:36:15 -05:00
Arcs
03936eb2ea feat: allow constructable modules (#133)
* Update readFile.ts

* Update userDefinedEventsHandling.ts

* Update readyHandler.ts

* fix: ts error

Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-09-13 21:46:06 +03:00
renovate[bot]
c4019f7a08 chore(deps): lock file maintenance (#136)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-12 20:57:34 +03:00
renovate[bot]
992619f8e5 chore(deps): update dependency @typescript-eslint/parser to v5.36.2 (#131)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-09-11 14:24:50 +03:00
renovate[bot]
b995560ec6 chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.36.2 (#130)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-09-11 14:19:40 +03:00
renovate[bot]
f01ef9b86c chore(deps): lock file maintenance (#129)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-11 14:16:19 +03:00
xxDeveloper
702c5750fc docs: Fix the code example (#128) 2022-09-03 14:52:26 +03:00
renovate[bot]
d5d1b4129b chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.36.1 (#126)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-03 11:26:55 +03:00
renovate[bot]
7658d3e3ab chore(deps): update dependency @typescript-eslint/parser to v5.36.1 (#127)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-09-03 11:24:09 +03:00
renovate[bot]
9c1abc6b2e chore(deps): lock file maintenance (#118) 2022-08-29 14:07:45 +03:00
github-actions[bot]
20bc135a7d chore(main): release 1.1.0 (#125)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-28 21:58:11 -05:00
EvolutionX
8a373de880 chore: release 1.1.0
Release-As: 1.1.0
2022-08-29 08:24:54 +05:30
Jacob Nguyen
395549c173 feat: add proper error handling (#115) 2022-08-29 08:09:52 +05:30
Evo
dbc180fa78 chore: duck you typo 2022-08-29 07:57:48 +05:30
github-actions[bot]
ff5c161469 stlye: pretty please (#124)
Co-authored-by: EvolutionX-10 <EvolutionX-10@users.noreply.github.com>
2022-08-29 07:12:33 +05:30
Evo
82637245f3 chore: typo and limits (#121) 2022-08-29 07:10:59 +05:30
Evo
bc3ed918da ci: add a formatting workflow (#116) 2022-08-29 07:01:28 +05:30
renovate[bot]
62bfcb1eb7 chore(deps): update dependency @typescript-eslint/parser to v5.35.1 (#111) 2022-08-28 09:40:48 +03:00
renovate[bot]
fbf54c9d36 chore(deps): update dependency typescript to v4.8.2 (#113) 2022-08-28 09:39:48 +03:00
renovate[bot]
b999918b71 chore(deps): update dependency eslint to v8.23.0 (#112) 2022-08-28 09:39:10 +03:00
renovate[bot]
224ce97fe8 chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.35.1 (#110) 2022-08-28 09:38:01 +03:00
renovate[bot]
f52dd01ca5 chore(deps): lock file maintenance (#109)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-22 22:11:42 +03:00
renovate[bot]
b8841e926d chore(deps): lock file maintenance (#105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 22:35:28 +03:00
renovate[bot]
f0f54cb7f2 chore(deps): update dependency eslint to v8.22.0 (#103)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-08-21 22:34:54 +03:00
renovate[bot]
ebe759f5e0 chore(deps): update dependency @typescript-eslint/parser to v5.33.1 (#102) 2022-08-18 22:16:10 +03:00
renovate[bot]
58d97e08a5 chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.33.1 (#101) 2022-08-18 22:15:18 +03:00
github-actions[bot]
21a438768b chore(main): release 1.0.0 (#82)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2022-08-15 10:52:13 -05:00
Jacob Nguyen
b3ed8da68f feat: add discord.js as a peerDependency instead 2022-08-14 18:35:39 -05:00
Jacob Nguyen
c5bd94131d feat: fix duplicate 2022-08-14 12:54:28 -05:00
Jacob Nguyen
3dec347ef0 feat: remove externallyUsed.ts 2022-08-14 12:53:16 -05:00
Jacob Nguyen
4e9530f4d7 docs(readme): @Murtatrxx work readme improvements (#104)
* docs: 🦋 Refreshed README

* docs: Imrpoved hero image

* docs: Changed the title

* fix: Fixed the title display

* docs: Added copyright to README

* docs: Added more badges

* docs: Updated license badge

* docs: Fixed message grammar

* fix: Fixed the line count

* docs: Shortened the description

* revert: Remove copyright

* docs: Updated the name on README

* docs: Added bun to installations

* ci: version.txt

* ci: simple release-type

* chore: Added .vscode/ dir to ignore file

* docs: Improved readme

* docs: Requested changes

* docs: Updated readme with more details

* feat: update banner

* feat: update readme

* docs: Updated features section’s title

* docs: Requested changes

* fix: Grammar

* feat: delete version.txt

* chore: update release-please

* docs: fixed typo

Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-08-14 12:50:07 -05:00
Jacob Nguyen
0da1b5a4dc feat: update example 2022-08-10 23:44:53 -05:00
renovate[bot]
8a7a671300 chore(deps): lock file maintenance (#100) 2022-08-09 15:06:30 +03:00
renovate[bot]
bf8a5d5a11 chore(deps): update dependency eslint to v8.21.0 (#99) 2022-08-06 23:55:06 +03:00
Jacob Nguyen
74378f0f12 feat: shrink package size, improve dev deps, esm and cjs support (#98)
* chore: fix tsc predicate type checking

* build: add tsup as bundler

* chore: revert text

* chore: fix predicates.ts, update dependencies, bump version

* docs: update example

* build: update dependencies

* fix: crash on collectors (#89)

* fix: crash on collectors

* feat: bump version for bug fix

* fix: crash on collectors pt

* docs: adding some documentation for docasaurus

* docs: add errors.ts comments

* docs: refactor comments

* docs: adding examples

* feat: refresh package-lock.json

* refactor: destructure and clean namespaces

* feat: add regen package.json script

* feat: add tsup, remove tsc, add scripts

* feat: update ts-results import style

* feat: readd typescript because idk if i should

* feat: breakup tsconfigs and add tsup config

* feat: add esm json tsconfig to git

* build: update dependencies and move to ts-result-es

* feat: remove unused function

* feat: update ts-results for esm/cjs interop!

* revert: remove version.txt

* build: goodbye tsc, hello tsup

* build: moving discord.js as dev dependency

* style: requested changes

* feat: add tsc back ( i missed you )

* build: bump version -> 1.0.0

* feat: syncing to main

* style: pretty

* feat: fix tsconfig issues with tsup

* revert: remove ExternallyUsed

* feat: update scripts

* build: update tsup and pkg-lock.json

* feat: refresh package-lock.json

* feat: test
2022-08-06 15:51:19 -05:00
Jacob Nguyen
0559fcca96 chore: forgot to remove console log at prod lol 2022-08-02 14:29:42 -05:00
Jacob Nguyen
70d7bdb8c5 fix: autocomplete in nested form (#97)
Co-authored-by: Evo <85353424+EvolutionX-10@users.noreply.github.com>
2022-08-03 00:21:31 +05:30
renovate[bot]
b6ea3da23f chore(deps): lock file maintenance (#96) 2022-08-02 13:21:38 +03:00
Jacob Nguyen
75a6a04db5 feat: add frontmatter (#95) 2022-08-02 13:21:16 +03:00
renovate[bot]
24fae252a7 chore(deps): update dependency @typescript-eslint/parser to v5.32.0 (#94) 2022-08-02 13:20:28 +03:00
renovate[bot]
d70a2a1d5e chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.32.0 (#93) 2022-08-02 13:19:12 +03:00
Jacob Nguyen
0d6e592614 feat: add feature-request.md (#92) 2022-07-29 13:50:46 +03:00
renovate[bot]
44ee2f4440 chore(deps): lock file maintenance (#91) 2022-07-28 12:59:37 +03:00
renovate[bot]
b560d56346 chore(deps): update actions/dependency-review-action action to v2 (#90)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-24 13:17:25 +03:00
renovate[bot]
344229b65f chore(deps): lock file maintenance (#88)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-24 12:56:12 +03:00
renovate[bot]
ca728f755b chore(deps): update dependency eslint to v8.20.0 (#87)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-24 12:53:56 +03:00
renovate[bot]
d8d6330260 chore(deps): update dependency @typescript-eslint/parser to v5.30.7 (#85)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-24 12:51:19 +03:00
renovate[bot]
c17c49bb17 chore(deps): update dependency @typescript-eslint/eslint-plugin to v5.30.7 (#84)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-24 12:47:38 +03:00
Jacob Nguyen
7da7bff700 fix: crash on collectors pt 2022-07-20 14:36:32 -05:00
Jacob Nguyen
a0587f59d4 fix: crash on collectors (#89)
* fix: crash on collectors

* feat: bump version for bug fix
2022-07-20 14:00:49 -05:00
Jacob Nguyen
d62be87c9a build: update dependencies 2022-07-19 19:48:04 -05:00
Jacob Nguyen
35a200f055 docs: update example 2022-07-18 13:55:32 -05:00
Jacob Nguyen
d96681bfb5 chore: fix predicates.ts, update dependencies, bump version 2022-07-18 12:21:23 -05:00
Jacob Nguyen
d1b034b826 chore: revert text 2022-07-18 12:18:12 -05:00
Jacob Nguyen
2387add445 build: update discord.js to v14.0.1 and bump version 2022-07-17 17:17:38 -05:00
Jacob Nguyen
7bf15a2d5d build: bump version 2022-07-16 15:38:12 -05:00
Jacob Nguyen
07e6dabce1 bug(observableHandling.ts): fix https://github.com/sern-handler/handler/issues/86 2022-07-16 15:37:43 -05:00
Jacob Nguyen
8e3feb81ca build: refresh package-lock.json 2022-07-16 13:23:01 -05:00
Jacob Nguyen
9a16c20dad build: bump to patch version and remove unneeded scripts 2022-07-16 13:20:06 -05:00
Jacob Nguyen
17eb816ec9 refactor(events): use of classes for scalability & maintainability (#83)
Co-authored-by: EvolutionX-10 <evolutionx9777@gmail.com>
2022-07-16 23:38:11 +05:30
Jacob Nguyen
79be5096d3 build: remove cz and standard version 2022-07-15 23:13:22 -05:00
Jacob Nguyen
4fea383519 feat: add version.txt 2022-07-14 17:07:47 -05:00
Jacob Nguyen
ca9ac52fae revert: remove version.txt 2022-07-14 16:56:54 -05:00
Jacob Nguyen
9cb25a585a ci: manual version override 2022-07-14 16:55:17 -05:00
Jacob Nguyen
17033254d4 ci: update release-please.yml 2022-07-14 16:44:01 -05:00
Jacob Nguyen
2f5e0fc772 ci: update release-please 2022-07-14 16:31:39 -05:00
Jacob Nguyen
a35ceb9531 ci: add release-please 2022-07-13 16:26:45 -05:00
renovate[bot]
fbbd79babc chore(deps): update actions/checkout action to v3 (#80)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-07-11 13:10:34 +03:00
renovate[bot]
501745b7ea chore(deps): lock file maintenance (#81)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 13:08:35 +03:00
renovate[bot]
6ea84ac8cd chore(deps): update actions/setup-node action to v3 (#79)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 13:08:09 +03:00
xxDeveloper
76c4333a81 fix: Fixed renovate warning (#77) 2022-07-08 21:34:51 +03:00
xxDeveloper
ac459593a0 chore: Updated code of conduct (#78)
chore: Updated the name on code of conduct
2022-07-06 20:24:31 +03:00
renovate[bot]
c5dac42da0 chore(deps): update github/codeql-action action to v2 (#76)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-05 19:18:22 +03:00
renovate[bot]
ef2200c257 chore(deps): lock file maintenance (#75)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-05 14:42:48 +03:00
renovate[bot]
fcb5f6747c chore(deps): update all non-major dependencies to v5.30.5 (#74)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
2022-07-05 14:39:41 +03:00
renovate[bot]
dd75650a46 chore(deps): pin dependency standard-version to 9.5.0 (#73)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-05 14:36:50 +03:00
xxDeveloper
4872590b28 ci: Added dependency-reviewer (#70) 2022-07-05 12:36:25 +03:00
renovate[bot]
cc127bd040 chore(deps): update all non-major dependencies (#72)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-02 11:27:48 -05:00
Evo
9dd82a3cfb chore: re-add renovate.json (#71) 2022-07-02 01:52:18 -05:00
Evo
9340cf229c refactor(*): use enums for payloadtype & fix type warns (#69)
* refactor(*): use enums and fix type warns

* style: pretty pretty prettier
2022-06-30 00:55:54 -05:00
xxDeveloper
7c97fa9461 chore: Added PR template 🎉 (#67) 2022-06-30 08:45:06 +05:30
xxDeveloper
ef22aca5ff chore: Added issue template for bugs 🎉 (#66)
* chore: 🎉 Added issue template

* chore: Added DiscordJS version

* chore: Added config.yml
2022-06-29 21:06:35 +03:00
Jacob Nguyen
e71b63d261 feat(*)!: improve quality of code, refactorings, QOL intellisense (#64)
* feat: remove unit testing

* feat: make command modules return Awaitable void | unknown

* feat: more clear error for access message or interaction

* feat: make TextCommand and SlashCommand return more specific arg type

* feat: make TextCommand and SlashCommand return more specific arg type

* fix: Remove type reduction to never in onReady.ts

* feat: narrow typings for args Text and SlashCommand x3

* feat: make description optional, default is '...'

* feat: add sern events natively register in sern.init

* feat: remove jest.config.ts

* fix: forget to add SernEvent to Wrapper declaration

* feat: Make name and description defined when making plugins!

* Update README.md

* docs: Highly encourage to use cli

* chore: Improved package.json

* feat: Add .edit method and let .reply accept string

* fix: intellij warnings

* build: update discord.js to latest

* chore(deps): Removed typedoc

Removed typedoc dependency due we won't use it anymore

* fix: Fixed typo at SECURITY.md file

* feat: remove edit context method for future

* feat: remove docs folder

* feat: loading strategy slightly changed, does not throw error on finding no module in file

* feat: change typings of sern emitter

* feat: add generic to readFile.ts buildData, adding different event loading strategies

* feat: add events.ts for more customizable event handling

* refactor: type changes to adjust to event commands

* feat: revamp module types to support event plugins

* feat: rolling out more event loading support

* style: Improved plugin.ts comments

* style: Improved plugin.ts comments

* style: Improved readyEvent.ts comments

* feat: more support for event loading!!

* fix: re add import after rebase

* feat: more event handling progress

* feat: Cleaning up types

* refactor: Cleaning up and adding docs

* feat: Changing types of wrapper for external and sern emitter

* refactor: Adding some type aliases

* feat:  switch to match expression, add docs

* feat: more progress on event handling

* fix: complying to djs v14 build

* feat: Add basic event handling, no plugins checked yet!

* fix: Crash on interactionCreate event

* build(package.json): bump version

* build: bump to 1.0.4-beta

* feat: move new things to top level import

* feat: Separating events from command modules, leads separation of responsibility

* feat: add enum for event modules

* feat: Add more typings for event modules

* feat: Add more plugin definitions

* refactor: add asyncResolveArray.ts to resolve Awaitables easier

* feat: throw error on plugin usage for event listeners

* feat: throw error on plugin usage for event listeners

* feat: change from client -> wrapper

* style: eslint

* style: add comment

* feat: add absPath into commandPlugins!

* build: remove 'tests' exclude

* build: delete test.yml, change .prettierignore

* build: bump version to 1.1.0-beta

* docs(sern.ts): add comments for addExternal

* fix: forgot to add required type property in comment

* fix: load external event emitters correctly

* chore: rebased api update branch (#65)

Co-authored-by: Jacob Nguyen <76754747+jacoobes@users.noreply.github.com>
Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>

Co-authored-by: xxDeveloper <77380166+Murtatrxx@users.noreply.github.com>
Co-authored-by: Evo <85353424+EvolutionX-10@users.noreply.github.com>
2022-06-28 12:38:01 -05:00
Jacob Nguyen
e677ce0839 fix: Change discord server link (#62) 2022-06-20 21:18:56 +03:00
xxDeveloper
371f9d711a chore: Removed @Murtatrxx from CODEOWNERS (#61) 2022-06-19 19:02:02 -05:00
Jacob Nguyen
a5320dddb9 docs: Highly encourage to use cli 2022-06-02 15:29:44 -05:00
Jacob Nguyen
92b83c8d32 Update README.md 2022-06-02 15:28:40 -05:00
51 changed files with 6500 additions and 8245 deletions

View File

@@ -3,9 +3,9 @@
"extends": ["plugin:@typescript-eslint/recommended"],
"parserOptions": { "ecmaVersion": "latest", "sourceType": "script" },
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals" : true }],
"semi": ["error", "always"],
"@typescript-eslint/no-empty-interface": 0
"@typescript-eslint/no-non-null-assertion": "off",
"quotes": [2, "single", { "avoidEscape": true, "allowTemplateLiterals": true }],
"semi": ["error", "always"],
"@typescript-eslint/no-empty-interface": 0
}
}
}

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
src/* @jacoobes
* @jacoobes

33
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,33 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] Write a descriptive title."
labels: bug
assignees: EvolutionX-10, jacoobes, Murtatrxx
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Versioning**
NodeJS version:
DiscordJS version:
SernHandler version:
Channel: (e.g. beta)
**Additional context**
Add any other context about the problem here.

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Sern Community Support
url: https://discord.gg/9w8jzsR48U
about: Please ask and answer questions here.

View File

@@ -0,0 +1,43 @@
---
name: Feature request
about: Ask for things that are not in sern
title: "[Feature] Request a feature"
labels: feature
assignees: EvolutionX-10, jacoobes, Murtatrxx
---
Request a new feature!
---
### Is your proposal related to a problem?
<!--
Provide a clear and concise description of what the problem is.
For example, "I'm always frustrated when..."
-->
(Write your answer here.)
### Describe the solution you'd like
<!--
Provide a clear and concise description of what you want to happen.
-->
(Describe your proposed solution here.)
### Describe alternatives you've considered
<!--
Let us know about other solutions you've tried or researched.
-->
(Write your answer here.)
### Additional context
<!--
Is there anything else you can add about the proposal?
You might want to link to related issues here, if you haven't already.
-->
(Write your answer here.)

View File

@@ -0,0 +1,26 @@
# Description
Please include a summary of the changes and the related issue. Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
# Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules

View File

@@ -24,14 +24,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View File

@@ -0,0 +1,44 @@
name: Continuous Integration
on:
# Trigger the workflow on push or pull request or custom
push:
branches:
main
pull_request_target:
branches:
main
workflow_dispatch:
jobs:
Prettier:
name: Run Prettier
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3
- name: Set up Node.js
uses: actions/setup-node@2fddd8803e2f5c9604345a0b591c3020ee971a93 # tag=v3
with:
node-version: 17
# Prettier must be in `package.json`
- name: Install Node.js dependencies
run: npm i -g prettier && npm i
- name: Run Prettier
run: prettier --write .
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v4
with:
commit-message: "style: pretty please"
branch: prettier
delete-branch: true
branch-suffix: short-commit-hash
title: "style: pretty please"
body: "pretty pretty prettier"
reviewers: EvolutionX-10

20
.github/workflows/dependency-review.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: 'Dependency Review'
on: [pull_request]
permissions:
contents: read
jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
- name: 'Dependency Review'
uses: actions/dependency-review-action@v2

View File

@@ -8,8 +8,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci
@@ -19,8 +19,8 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/

14
.github/workflows/release-please.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: release-please
on:
workflow_dispatch:
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
with:
release-type: node
package-name: release-please-action
bump-patch-for-minor-pre-major: true

3
.gitignore vendored
View File

@@ -82,5 +82,8 @@ dist
# VisualStudio Config file
.vs
# VSCode settings and cache
.vscode
# IntelliJ IDEA Config file
.idea/

View File

@@ -1,5 +1,4 @@
src/
tsconfig.json
docs/
.gitignore
@@ -9,7 +8,6 @@ logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
@@ -106,4 +104,10 @@ CODE_OF_CONDUCT.md
babel.config.js
tests/
tsup.config.js
tsconfig-base.json
tsconfig.cjs.json
tsconfig.esm.json

View File

@@ -1,2 +1,3 @@
.github/
*.md
*.md
dist/

View File

@@ -1,8 +1,8 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 4,
"arrowParens": "avoid"
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 100,
"tabWidth": 4,
"arrowParens": "avoid"
}

129
CHANGELOG.md Normal file
View File

@@ -0,0 +1,129 @@
# Changelog
## [1.2.1](https://github.com/sern-handler/handler/compare/v1.2.0...v1.2.1) (2022-10-03)
### Bug Fixes
* **autocomplete:** now support multiple autocomplete options ([#147](https://github.com/sern-handler/handler/issues/147)) ([cbad738](https://github.com/sern-handler/handler/commit/cbad7380e1993b96c643f365726457f63e4fbd5d))
## [1.2.0](https://github.com/sern-handler/handler/compare/v1.1.0...v1.2.0) (2022-09-28)
### Features
* allow constructable modules ([#133](https://github.com/sern-handler/handler/issues/133)) ([03936eb](https://github.com/sern-handler/handler/commit/03936eb2ea1d1af7cada04d77bb8345d63a5e20f))
* classmodules@arcs ([#143](https://github.com/sern-handler/handler/issues/143)) ([5028886](https://github.com/sern-handler/handler/commit/50288867a5b171511941a1be3877d721694e9f77))
* update CODEOWNERS ([6b8995d](https://github.com/sern-handler/handler/commit/6b8995d149c857558415a6c151a3f575ec373445))
### Reverts
* feat of allow constructable modules ([#138](https://github.com/sern-handler/handler/issues/138)) ([82bbdda](https://github.com/sern-handler/handler/commit/82bbddac8d656b60b3a1fb2471ea03ee5224f5c3))
## [1.1.0](https://github.com/sern-handler/handler/compare/v1.0.0...v1.1.0) (2022-08-29)
### Features
* add proper error handling ([#115](https://github.com/sern-handler/handler/issues/115)) ([395549c](https://github.com/sern-handler/handler/commit/395549c173cb62a18205e451bf2cb5579ba9a6e0))
### Miscellaneous Chores
* release 1.1.0 ([8a373de](https://github.com/sern-handler/handler/commit/8a373de880ff18df85af812adf9f6f6a4f45028d))
## 1.0.0 (2022-08-15)
### ⚠ BREAKING CHANGES
* improve quality of code, refactorings, QOL intellisense (#64)
### Features
* add .prettierignore and ignore README.md ([7ae5ecf](https://github.com/sern-handler/handler/commit/7ae5ecf1a64700d667e85420ae4b2eaf31781d85))
* Add castings for res ([2697e35](https://github.com/sern-handler/handler/commit/2697e35b2e5b754ea9d0d84db3720fb68b3f43db))
* Add DefinetlyDefined type, more todo statements ([c8c0c84](https://github.com/sern-handler/handler/commit/c8c0c841db2423e29d69bbc1a3ab590bfebb5d5b))
* add discord.js as a peerDependency instead ([b3ed8da](https://github.com/sern-handler/handler/commit/b3ed8da68f55b69a7fe1697cd88c552243cc637f))
* add docs/ to npmignore ([f90342d](https://github.com/sern-handler/handler/commit/f90342d6b140241f7a6a95dea71c05bf309a7a52))
* add externallyUsed.ts and support BothCommands again ([fc81bfc](https://github.com/sern-handler/handler/commit/fc81bfc6d75e4722486766715abe7271ad21cd7f))
* add feature-request.md ([#92](https://github.com/sern-handler/handler/issues/92)) ([0d6e592](https://github.com/sern-handler/handler/commit/0d6e592614f0d4eeaaa9ffe5ba245fe002f5b907))
* add frontmatter ([#95](https://github.com/sern-handler/handler/issues/95)) ([75a6a04](https://github.com/sern-handler/handler/commit/75a6a04db56551049387e38979bb7ef21356f303))
* Add messageComponent handler ([d29298c](https://github.com/sern-handler/handler/commit/d29298c17a1d67146bdddb9cf07a16924c55ed3a))
* add version.txt ([4fea383](https://github.com/sern-handler/handler/commit/4fea383519b9905c17c7679587f69b530c08cec8))
* added conventional commits ([741cf13](https://github.com/sern-handler/handler/commit/741cf13fd56ac49ebca6f73ecc3a2209f00e774d))
* Added SECURITY file & formatted some TypeScript code ([779011a](https://github.com/sern-handler/handler/commit/779011a124ab76bbfb19a2a11889bf9255cbd360))
* adding better typings, refactoring ([99e2a99](https://github.com/sern-handler/handler/commit/99e2a997edaac1ba880e56bf782ecd1fa5e96b4c))
* Adding docs to some data structures, moving to default from export files ([0ae541d](https://github.com/sern-handler/handler/commit/0ae541daba4c5d2aa3e612ab4b78fd6a858717ad))
* adding modal and autocomplete support ([77856ce](https://github.com/sern-handler/handler/commit/77856ce5d0d8d1e2e2f5a971269224a4174bc205))
* adding refactoring for repetitive event plugin processing ([475b073](https://github.com/sern-handler/handler/commit/475b0736d573bb8969b2a0eb9180231aa8618a0e))
* Adding sern event listeners, overriding and typing methods ([115d1a4](https://github.com/sern-handler/handler/commit/115d1a49b52eb45d9b68ba015f8f734b902e9a60))
* Adding TextInput map & starting event plugins for message components ([6ac9720](https://github.com/sern-handler/handler/commit/6ac9720260040afb12d232b002c28db99b18e093))
* Aliases optional ([430315a](https://github.com/sern-handler/handler/commit/430315ad02060121e75604aee40c246c71a7e8d2))
* better looking typings for modules ([53bc080](https://github.com/sern-handler/handler/commit/53bc080a290fd5064993aa0d98497d4b239ac8f3))
* broadening EventPlugin default generic type, reformat with prettier ([88dcdee](https://github.com/sern-handler/handler/commit/88dcdee818e42405234ef502087226a8c042c92f))
* CodeQL ([7012da6](https://github.com/sern-handler/handler/commit/7012da60530c2b0b5d8cc97b417a80cd8031f51f))
* delete partition.ts ([f6d584c](https://github.com/sern-handler/handler/commit/f6d584cf99abdb292985f812e64553a37ab51a01))
* Edited event names for more conciseness, finished basic event emitters ([3f64a8a](https://github.com/sern-handler/handler/commit/3f64a8aa0a47a09f822d54f2b3f03bc42faa10f7))
* finished interactionCreate.ts handling? (need test) ([97907b7](https://github.com/sern-handler/handler/commit/97907b746fc94d6e8b65e2fec1cce4b0c3160491))
* finishing autocomplete!! ([d63423c](https://github.com/sern-handler/handler/commit/d63423cfc458cb9ab07b9900a7c4d2f7ea8d71b9))
* finishing optionData for autocomplete changes, adding class for builder ([b08eebf](https://github.com/sern-handler/handler/commit/b08eebf6850acaee3b56bb1c60aec2a026a5144c))
* Finishing up autocomplete, need to test ([d50b801](https://github.com/sern-handler/handler/commit/d50b8013ee343b2be0ed232938e9f5f91c43b493))
* fix duplicate ([c5bd941](https://github.com/sern-handler/handler/commit/c5bd94131dfb20b2c69b7eeb96f3ad89d6de43f4))
* **handler:** adding higher-order-function wrappers to increase readability and shorten code ([0f0b0fb](https://github.com/sern-handler/handler/commit/0f0b0fb61c80654179e2c6d6f69e50c70114201b))
* **handler:** command plugins work?! ([70bd12d](https://github.com/sern-handler/handler/commit/70bd12dd61182f48445c707a9199421b1dba586e))
* **handler:** progress on event plugins ([2f61399](https://github.com/sern-handler/handler/commit/2f61399b5e5ad53ee3165e19cb74dd279b827b99))
* **higherorders.ts:** a new function that acts as a command options builder ([651009c](https://github.com/sern-handler/handler/commit/651009c9ed5e8e04cf44fa4438f94a9e119aa8f8))
* improve quality of code, refactorings, QOL intellisense ([#64](https://github.com/sern-handler/handler/issues/64)) ([e71b63d](https://github.com/sern-handler/handler/commit/e71b63d261d86b17ddc05fbee999f63623d8c6d1))
* Improved TypeScript experince ([dad3042](https://github.com/sern-handler/handler/commit/dad3042a644b0e47d01319f48eefe01632678cc3))
* interactionCreate.ts refactoring ([c4e8e51](https://github.com/sern-handler/handler/commit/c4e8e517b3f4bb6baca902251a0afa22b2548450))
* Making name required in auto cmp interactions ([ac8a2f4](https://github.com/sern-handler/handler/commit/ac8a2f4c86a1c426d32e388a5439a8696db52279))
* move name and description out of OptionsData[] ([93942bd](https://github.com/sern-handler/handler/commit/93942bd0e7d0ac688d20159cab2c70c3285085f4))
* Optional plugins to reduce bloat ([2b81d53](https://github.com/sern-handler/handler/commit/2b81d53503209a738b70d238512c82542d3d88e8))
* **prefix:** make defaultPrefix optional ([f6b88dc](https://github.com/sern-handler/handler/commit/f6b88dcdc80c407e14f4d6ae73eb27e75d195e18))
* **readme.md:** added basic command examples ([63b2d3a](https://github.com/sern-handler/handler/commit/63b2d3a5723ac6e1f0baa0de8b65640cecd7d634))
* remove comments about prev commit ([a220949](https://github.com/sern-handler/handler/commit/a2209494bdd05ca89176aff82f7d3afce0b8de46))
* remove copyright bloat ([48737ef](https://github.com/sern-handler/handler/commit/48737efea3c0fce572238701e72335293333379f))
* remove externallyUsed.ts ([3dec347](https://github.com/sern-handler/handler/commit/3dec347ef0957845601f0eb2acb3f1815d1e9ca1))
* Revamp Docs ([#47](https://github.com/sern-handler/handler/issues/47)) ([163e48f](https://github.com/sern-handler/handler/commit/163e48f3eb38d37500cefc8d0c722c083a3070c7))
* **sern.ts:** adding logging instead of large complaining messages from bot ([00a5fa4](https://github.com/sern-handler/handler/commit/00a5fa43ad9e0b4c7d5ef1f2772a4cb186768837))
* **sern.ts:** beginning to add new basic logger system ([ef9d53e](https://github.com/sern-handler/handler/commit/ef9d53e6b1a9009eab5ce9ff9f8b5542d1d7cf7f))
* **sern.ts:** changed how module is passed around, now has name property at runtime ([40fb723](https://github.com/sern-handler/handler/commit/40fb7231436331c97fa791eab3b8b51636e826f1))
* **sern.ts:** changing default value of args in text based cmd to string[], from string ([1397974](https://github.com/sern-handler/handler/commit/1397974fb6e6d8c1b1e82db8272ef0a57916022c))
* **sern.ts:** changing default value of args in text based cmd to string[], from string ([e0541f7](https://github.com/sern-handler/handler/commit/e0541f777bc3dcb1ec0c0cccf219b9fa66199a2b))
* **sern.ts:** changing text-based command parser fn value to string[] ([b11f999](https://github.com/sern-handler/handler/commit/b11f9996749977a16e516523af7a8e2a9e6521ae))
* **sern.ts:** renaming Module.delegate to Module.execute ([8702876](https://github.com/sern-handler/handler/commit/870287674aa7eccbe1fc890d1cf2cd808982b801))
* should be able to register other nodejs event emitters ([b8cda35](https://github.com/sern-handler/handler/commit/b8cda351e1f549422692c633255ac1d6c7d78a9b))
* shrink package size, improve dev deps, esm and cjs support ([#98](https://github.com/sern-handler/handler/issues/98)) ([74378f0](https://github.com/sern-handler/handler/commit/74378f0f12cf5d16b90ddbc01fb42505e0235c39))
* update example ([0da1b5a](https://github.com/sern-handler/handler/commit/0da1b5a4dc6823807880ade03728b466fe895190))
* Updated Readme design ([369586f](https://github.com/sern-handler/handler/commit/369586f378f807ba9906167b5ada197c2c95e411))
### Bug Fixes
* accidentally imported wildcard from wrong place & namespace ([8782cad](https://github.com/sern-handler/handler/commit/8782cad9cddbb24c03c2bfff96d3377aceb5f542))
* autocomplete in nested form ([#97](https://github.com/sern-handler/handler/issues/97)) ([70d7bdb](https://github.com/sern-handler/handler/commit/70d7bdb8c53a1990addc5c9fd54427f194833b4e))
* Change discord server link ([#62](https://github.com/sern-handler/handler/issues/62)) ([e677ce0](https://github.com/sern-handler/handler/commit/e677ce083966dedc945d236034e2ce4a7a586e05))
* **codeql-analysis.yml:** Fixed autobuild issue on some TS files & deleted more unused comments ([e51c7ff](https://github.com/sern-handler/handler/commit/e51c7ffed038f0519a37f4339406c28546d92c83))
* crash on collectors ([#89](https://github.com/sern-handler/handler/issues/89)) ([a0587f5](https://github.com/sern-handler/handler/commit/a0587f59d43d62642c033e0bb843902f9e6dc0c4))
* crash on collectors pt ([7da7bff](https://github.com/sern-handler/handler/commit/7da7bff700f8e46e72412ca5d379a6edbc14e10a))
* didn't run prettier, now i am ([6c144de](https://github.com/sern-handler/handler/commit/6c144defcacd7732e15292f6c3e5eaea7944bc32))
* Fix return type of sernModule ([cf85a5d](https://github.com/sern-handler/handler/commit/cf85a5db6413e2b8b42143f70964f7a19789e1ff))
* Fixed renovate warning ([#77](https://github.com/sern-handler/handler/issues/77)) ([76c4333](https://github.com/sern-handler/handler/commit/76c4333a817006100f0b99d73bb455e82797d3d9))
* Fixed typo in Security.md ([c35def9](https://github.com/sern-handler/handler/commit/c35def99c93e77a0c932a1b8f1da06cd45fde294))
* **handler.ts:** added the changes of Module<T>.execute to type delegate (now type execute) ([f062a33](https://github.com/sern-handler/handler/commit/f062a338687be4b3ac64c048a63bdcb895282d2d))
* linting issue in markup.ts ([dac665d](https://github.com/sern-handler/handler/commit/dac665d6281a29ec79663adb26a3e5c5243e6ae0))
* Non-exhaustiveness led to commands not registering readyEvent.ts ([b266508](https://github.com/sern-handler/handler/commit/b26650818e2c193c326356359b38412117ea6186))
* prettier changes again ([d5bb992](https://github.com/sern-handler/handler/commit/d5bb9922dfdb14e4f7e95ad5acd470765b7a90c2))
* prettier wants lf line ending ([571a804](https://github.com/sern-handler/handler/commit/571a8044b027afee91466219a841817dd55ef455))
* **sern.ts:** checking ctx instanceof Message always returned false ([7166947](https://github.com/sern-handler/handler/commit/7166947d28f3be1a6e1c44385eb7946731784f58))
* Standard for of does not resolve promises. Switched to for await ([66b9f51](https://github.com/sern-handler/handler/commit/66b9f51fa73cf07a589671d13ca4c65a9c8193a1))
* **utilexports.ts:** forgot to remove the deleted feat of option builder ([1cff46c](https://github.com/sern-handler/handler/commit/1cff46c0ab5959d8e0f0fe89f1e6cd4c6cebff19))
### Reverts
* Move enums to enums.ts ([40a10bf](https://github.com/sern-handler/handler/commit/40a10bf32b9a1c6afbf85bdaeb2a7918c15ac7a8))
* Re-add plugins overload ([b9b5919](https://github.com/sern-handler/handler/commit/b9b59197df7d9bbeac3df68ebe6f7cea1ce50677))
* remove version.txt ([ca9ac52](https://github.com/sern-handler/handler/commit/ca9ac52fae32108b4cb90b201204d5c358c5ef7b))

View File

@@ -1,5 +1,5 @@
# Code of Conduct
All participants of SernHandler are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with SernHandler.
All participants of sern are expected to abide by our Code of Conduct, both online and during in-person events that are hosted and/or associated with sern.
# The Pledge
In the interest of fostering an open and welcoming environment, we pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
@@ -26,7 +26,7 @@ Examples of behaviour that contributes to creating a positive environment includ
• Other conduct which you know could reasonably be considered inappropriate in a professional setting. <br/>
• Enforcement. <br/>
Violations of the Code of Conduct may be reported by sending a message to [discord server](https://discord.com/). All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
Violations of the Code of Conduct may be reported by reaching us on our [discord server](https://discord.com/). All reports will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Further details of specific enforcement policies may be posted separately.
We hold the right and responsibility to remove comments or other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any members for other behaviours that they deem inappropriate, threatening, offensive, or harmful.

View File

@@ -1,13 +1,21 @@
# SernHandler
<div align="center">
<img src="https://raw.githubusercontent.com/sern-handler/.github/main/banner.png" width="900px" />
</div>
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/v/@sern/handler?maxAge=3600" alt="NPM version" /></a>
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/dt/@sern/handler?maxAge=3600" alt="NPM downloads" /></a>
[![License: MIT](https://img.shields.io/badge/License-MIT-blavk.svg)](https://opensource.org/licenses/MIT)
<h1 align="center">Handlers. Redefined.</h1>
<h4 align="center">A customizable, batteries-included, powerful discord.js framework to streamline bot development.</h4>
A customizable, batteries-included, powerful discord.js framework to automate and streamline your bot development.
<div align="center" styles="margin-top: 10px">
<img src="https://img.shields.io/badge/open-source-brightgreen" />
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/v/@sern/handler?maxAge=3600" alt="NPM version" /></a>
<a href="https://www.npmjs.com/package/@sern/handler"><img src="https://img.shields.io/npm/dt/@sern/handler?maxAge=3600" alt="NPM downloads" /></a>
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-brightgreen" alt="License MIT" /></a>
<a href="https://sern-handler.js.org"><img alt="docs.rs" src="https://img.shields.io/docsrs/docs" /></a>
<img alt="Lines of code" src="https://img.shields.io/badge/total%20lines-2k-blue" />
</div>
## Installation
## 📜 Installation
```sh
npm install @sern/handler
@@ -21,13 +29,26 @@ yarn add @sern/handler
pnpm add @sern/handler
```
## Basic Usage
## 👀 Quick Look
* Support for discord.js v14 and all interactions
* Hybrid commands
* lightweight and customizable
* ESM, CommonJS and TypeScript support
* A powerful cli and awesome community-made plugins
## 👶 Basic Usage
#### ` index.js (CommonJS)`
```js
// Import the discord.js Client and GatewayIntentBits
const { Client, GatewayIntentBits } = require('discord.js');
const { Sern } = require('sern-handler');
// Import Sern namespace
const { Sern } = require('@sern/handler');
// Our configuration file
const { defaultPrefix, token } = require('./config.json');
const client = new Client({
@@ -39,9 +60,9 @@ const client = new Client({
});
Sern.init({
client,
defaultPrefix,
commands : 'src/commands',
client,
defaultPrefix,
commands : 'src/commands',
});
client.login(token);
@@ -50,34 +71,35 @@ client.login(token);
#### ` ping.js (CommonJS)`
```js
const { Sern, CommandType } = require('@sern/handler');
const { CommandType, commandModule } = require('@sern/handler');
exports.default = {
description: 'A ping pong command',
type: CommandType.Slash,
execute(ctx) {
ctx.reply('pong!');
}
};
exports.default = commandModule({
name: 'ping',
description: 'A ping pong command',
type: CommandType.Slash,
execute(ctx) {
ctx.reply('pong!');
}
});
```
See our [templates](https://github.com/sern-handler/templates) for TypeScript examples and more
## CLI
## 💻 CLI
It is **highly encouraged** to use the [command line interface](https://github.com/sern-handler/cli) for your project. Don't forget to view it.
## Links
## 🔗 Links
- [Official Documentation](https://sern-handler.js.org)
- [Official Documentation and Guide](https://sern-handler.js.org)
- [Support Server](https://discord.com/invite/mmyCTnYtbF)
## Contribute
## 👋 Contribute
- Read our contribution [guidelines](https://github.com/sern-handler/handler) carefully
- Pull up on [issues](https://github.com/sern-handler/handler/issues) and report bugs
- All kinds of contributions are welcomed.
## Roadmap
## 🚈 Roadmap
You can check our [roadmap](https://github.com/sern-handler/roadmap) to see what's going to be added or patched in the future.

12574
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +1,54 @@
{
"name": "@sern/handler",
"version": "1.1.0-beta",
"description": "A customizable, batteries-included, powerful discord.js framework to automate and streamline bot development.",
"main": "dist/index.js",
"scripts": {
"compile": "tsc",
"watch": "tsc -w",
"lint": "eslint src/**/*.ts",
"format": "eslint src/**/*.ts --fix",
"release": "standard-version && git push --follow-tags",
"commit": "cz"
},
"keywords": [
"sern-handler",
"sern",
"handler",
"sern handler",
"wrapper",
"discord.js",
"framework"
],
"author": "SernDevs",
"license": "MIT",
"dependencies": {
"discord.js": "^14.0.0-dev.1647259751.2297c2b",
"rxjs": "^7.5.5",
"ts-pattern": "^4.0.2",
"ts-results": "^3.3.0"
},
"devDependencies": {
"eslint": "^8.8.0",
"@typescript-eslint/parser": "^5.10.2",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"cz-conventional-changelog": "3.0.1",
"prettier": "2.6.2",
"standard-version": "^9.3.2",
"typescript": "4.5.5"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sern-handler/handler.git"
},
"homepage": "https://sern-handler.js.org",
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
"name": "@sern/handler",
"version": "1.2.1",
"description": "A customizable, batteries-included, powerful discord.js framework to automate and streamline bot development.",
"main": "dist/cjs/index.cjs",
"module": "dist/esm/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/esm/index.mjs",
"require": "./dist/cjs/index.cjs"
}
},
"scripts": {
"watch": "tsup --watch --dts",
"clean-modules": "rimraf node_modules/ && npm install",
"lint": "eslint src/**/*.ts",
"format": "eslint src/**/*.ts --fix",
"build": "tsup && node scripts/mkjson.mjs dist/cjs dist/esm && tsup --dts-only --outDir dist",
"publish": "npm run build && npm publish"
},
"keywords": [
"sern-handler",
"sern",
"handler",
"sern handler",
"wrapper",
"discord.js",
"framework"
],
"author": "SernDevs",
"license": "MIT",
"dependencies": {
"rxjs": "^7.5.6",
"ts-pattern": "^4.0.2",
"ts-results-es": "^3.5.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "5.36.1",
"@typescript-eslint/parser": "5.36.2",
"eslint": "8.22.0",
"prettier": "2.7.1",
"tsup": "^6.1.3",
"typescript": "^4.8.3"
},
"peerDependencies": {
"discord.js": "^14.5.x"
},
"repository": {
"type": "git",
"url": "git+https://github.com/sern-handler/handler.git"
},
"homepage": "https://sern-handler.js.org"
}

14
renovate.json Normal file
View File

@@ -0,0 +1,14 @@
{
"major": {
"dependencyDashboardApproval": true,
"reviewers": ["EvolutionX-10", "jacoobes", "Murtatrxx"]
},
"minor": {
"reviewers": ["jacoobes", "Murtatrxx"]
},
"schedule": ["every weekend"],
"lockFileMaintenance": {
"enabled": true,
"automerge": false
}
}

13
scripts/mkjson.mjs Normal file
View File

@@ -0,0 +1,13 @@
import { writeFile } from 'fs/promises';
import { join } from 'path';
// A quick script to regenerate package.jsons for each cjs and esm after tsup cleans distributions
const locations = process.argv;
locations.shift();
locations.shift();
for (const loc of locations) {
if (loc.endsWith('cjs')) {
await writeFile(join(loc, 'package.json'), JSON.stringify({ type: 'commonjs' }));
} else {
await writeFile(join(loc, 'package.json'), JSON.stringify({ type: 'module' }));
}
}

View File

@@ -0,0 +1,110 @@
import type {
BothCommand,
ButtonCommand,
ContextMenuMsg,
ContextMenuUser,
ModalSubmitCommand,
SelectMenuCommand,
SlashCommand,
} from '../structures/module';
import Context from '../structures/context';
import type { SlashOptions } from '../../types/handler';
import { asyncResolveArray } from '../utilities/asyncResolveArray';
import { controller } from '../sern';
import type {
ButtonInteraction,
ModalSubmitInteraction,
SelectMenuInteraction,
AutocompleteInteraction,
ChatInputCommandInteraction,
Interaction,
UserContextMenuCommandInteraction,
MessageContextMenuCommandInteraction,
} from 'discord.js';
import { isAutocomplete } from '../utilities/predicates';
import { SernError } from '../structures/errors';
import treeSearch from '../utilities/treeSearch';
export function applicationCommandDispatcher(interaction: Interaction) {
if (isAutocomplete(interaction)) {
return dispatchAutocomplete(interaction);
} else {
const ctx = Context.wrap(interaction as ChatInputCommandInteraction);
const args: ['slash', SlashOptions] = ['slash', ctx.interaction.options];
return (mod: BothCommand | SlashCommand) => ({
mod,
execute: () => mod.execute(ctx, args),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([ctx, args], controller)),
),
});
}
}
export function dispatchAutocomplete(interaction: AutocompleteInteraction) {
return (mod: BothCommand | SlashCommand) => {
const selectedOption = treeSearch(interaction, mod.options);
if (selectedOption !== undefined) {
return {
mod,
execute: () => selectedOption.command.execute(interaction),
eventPluginRes: asyncResolveArray(
selectedOption.command.onEvent.map(e => e.execute(interaction, controller)),
),
};
}
throw Error(
SernError.NotSupportedInteraction + ` There is no autocomplete tag for this option`,
);
};
}
export function modalCommandDispatcher(interaction: ModalSubmitInteraction) {
return (mod: ModalSubmitCommand) => ({
mod,
execute: () => mod.execute(interaction),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([interaction], controller)),
),
});
}
export function buttonCommandDispatcher(interaction: ButtonInteraction) {
return (mod: ButtonCommand) => ({
mod,
execute: () => mod.execute(interaction),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([interaction], controller)),
),
});
}
export function selectMenuCommandDispatcher(interaction: SelectMenuInteraction) {
return (mod: SelectMenuCommand) => ({
mod,
execute: () => mod.execute(interaction),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([interaction], controller)),
),
});
}
export function ctxMenuUserDispatcher(interaction: UserContextMenuCommandInteraction) {
return (mod: ContextMenuUser) => ({
mod,
execute: () => mod.execute(interaction),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([interaction], controller)),
),
});
}
export function ctxMenuMsgDispatcher(interaction: MessageContextMenuCommandInteraction) {
return (mod: ContextMenuMsg) => ({
mod,
execute: () => mod.execute(interaction),
eventPluginRes: asyncResolveArray(
mod.onEvent.map(plugs => plugs.execute([interaction], controller)),
),
});
}

View File

@@ -0,0 +1,10 @@
import type Wrapper from '../structures/wrapper';
import { Subject, type Observable } from 'rxjs';
export abstract class EventsHandler<T> {
protected payloadSubject = new Subject<T>();
protected abstract discordEvent: Observable<unknown>;
protected constructor(protected wrapper: Wrapper) {}
protected abstract init(): void;
protected abstract setState(state: T): void;
}

View File

@@ -1,229 +0,0 @@
import type {
CommandInteraction,
Interaction,
MessageComponentInteraction,
ModalSubmitInteraction,
SelectMenuInteraction,
} from 'discord.js';
import { concatMap, fromEvent, map, Observable, of, throwError } from 'rxjs';
import type Wrapper from '../structures/wrapper';
import * as Files from '../utilities/readFile';
import { match } from 'ts-pattern';
import { SernError } from '../structures/errors';
import Context from '../structures/context';
import { controller } from '../sern';
import type { Module } from '../structures/module';
import {
isApplicationCommand,
isAutocomplete,
isButton,
isChatInputCommand,
isMessageComponent,
isMessageCtxMenuCmd,
isModalSubmit,
isSelectMenu,
isUserContextMenuCmd,
} from '../utilities/predicates';
import { filterCorrectModule } from './observableHandling';
import { CommandType } from '../structures/enums';
import type { AutocompleteInteraction } from 'discord.js';
import { asyncResolveArray } from '../utilities/asyncResolveArray';
function applicationCommandHandler(mod: Module | undefined, interaction: CommandInteraction) {
const mod$ = <T extends CommandType>(cmdTy: T) => of(mod).pipe(filterCorrectModule(cmdTy));
return (
match(interaction)
.when(isChatInputCommand, i => {
const ctx = Context.wrap(i);
return mod$(CommandType.Slash).pipe(
concatMap(m => {
return of(
m.onEvent.map(e => e.execute([ctx, ['slash', i.options]], controller)),
).pipe(
map(res => ({
mod,
res,
execute() {
return m.execute(ctx, ['slash', i.options]);
},
})),
);
}),
);
})
//Todo: refactor so that we dont have to have two separate branches. They're near identical!!
//Only thing that differs is type of interaction
.when(isMessageCtxMenuCmd, ctx => {
return mod$(CommandType.MenuMsg).pipe(
concatMap(m => {
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
map(res => ({
mod,
res,
execute() {
return m.execute(ctx);
},
})),
);
}),
);
})
.when(isUserContextMenuCmd, ctx => {
return mod$(CommandType.MenuUser).pipe(
concatMap(m => {
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
map(res => ({
mod,
res,
execute() {
return m.execute(ctx);
},
})),
);
}),
);
})
.run()
);
}
function messageComponentInteractionHandler(
mod: Module | undefined,
interaction: MessageComponentInteraction,
) {
const mod$ = <T extends CommandType>(ty: T) => of(mod).pipe(filterCorrectModule(ty));
//Todo: refactor so that we dont have to have two separate branches. They're near identical!!
//Only thing that differs is type of interaction
return match(interaction)
.when(isButton, ctx => {
return mod$(CommandType.Button).pipe(
concatMap(m => {
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
map(res => ({
mod,
res,
execute() {
return m.execute(ctx);
},
})),
);
}),
);
})
.when(isSelectMenu, (ctx: SelectMenuInteraction) => {
return mod$(CommandType.MenuSelect).pipe(
concatMap(m => {
return of(m.onEvent.map(e => e.execute([ctx], controller))).pipe(
map(res => ({
mod,
res,
execute() {
return m.execute(ctx);
},
})),
);
}),
);
})
.otherwise(() => throwError(() => SernError.NotSupportedInteraction));
}
function modalHandler(modul: Module | undefined, ctx: ModalSubmitInteraction) {
return of(modul).pipe(
filterCorrectModule(CommandType.Modal),
concatMap(mod => {
return of(mod.onEvent.map(e => e.execute([ctx], controller))).pipe(
map(res => ({
mod,
res,
execute() {
return mod.execute(ctx);
},
})),
);
}),
);
}
function autoCmpHandler(mod: Module | undefined, interaction: AutocompleteInteraction) {
return of(mod).pipe(
filterCorrectModule(CommandType.Slash),
concatMap(mod => {
const choice = interaction.options.getFocused(true);
const selectedOption = mod.options?.find(o => o.autocomplete && o.name === choice.name);
if (selectedOption !== undefined && selectedOption.autocomplete) {
return of(
selectedOption.command.onEvent.map(e => e.execute(interaction, controller)),
).pipe(
map(res => ({
mod,
res,
execute() {
return selectedOption.command.execute(interaction);
},
})),
);
}
return throwError(
() =>
SernError.NotSupportedInteraction +
` There is probably no autocomplete tag for this option`,
);
}),
);
}
export function onInteractionCreate(wrapper: Wrapper) {
const { client } = wrapper;
const interactionEvent$ = <Observable<Interaction>>fromEvent(client, 'interactionCreate');
interactionEvent$
.pipe(
/*processing plugins*/
concatMap(interaction => {
if (isApplicationCommand(interaction)) {
const modul =
Files.ApplicationCommands[interaction.commandType].get(
interaction.commandName,
) ?? Files.BothCommands.get(interaction.commandName);
return applicationCommandHandler(modul, interaction);
}
if (isMessageComponent(interaction)) {
const modul = Files.MessageCompCommands[interaction.componentType].get(
interaction.customId,
);
return messageComponentInteractionHandler(modul, interaction);
}
if (isModalSubmit(interaction)) {
const modul = Files.ModalSubmitCommands.get(interaction.customId);
return modalHandler(modul, interaction);
}
if (isAutocomplete(interaction)) {
const modul =
Files.ApplicationCommands['1'].get(interaction.commandName) ??
Files.BothCommands.get(interaction.commandName);
return autoCmpHandler(modul, interaction);
}
return throwError(() => SernError.NotSupportedInteraction);
}),
)
.subscribe({
async next({ mod, res: eventPluginRes, execute }) {
const ePlugArr = await asyncResolveArray(eventPluginRes);
if (ePlugArr.every(e => e.ok)) {
await execute();
wrapper.sernEmitter?.emit('module.activate', { type: 'success', module: mod! });
} else {
wrapper.sernEmitter?.emit('module.activate', {
type: 'failure',
module: mod!,
reason: SernError.PluginFailure,
});
}
},
error(err) {
wrapper.sernEmitter?.emit('error', err);
},
});
}

View File

@@ -0,0 +1,123 @@
import type { Interaction } from 'discord.js';
import { catchError, concatMap, from, fromEvent, map, Observable } from 'rxjs';
import type Wrapper from '../structures/wrapper';
import { EventsHandler } from './eventsHandler';
import {
isApplicationCommand,
isAutocomplete,
isMessageComponent,
isModalSubmit,
} from '../utilities/predicates';
import * as Files from '../utilities/readFile';
import type { CommandModule } from '../structures/module';
import { SernError } from '../structures/errors';
import { CommandType, PayloadType } from '../structures/enums';
import { match, P } from 'ts-pattern';
import {
applicationCommandDispatcher,
buttonCommandDispatcher,
ctxMenuMsgDispatcher,
ctxMenuUserDispatcher,
modalCommandDispatcher,
selectMenuCommandDispatcher,
} from './dispatchers';
import type {
ButtonInteraction,
ModalSubmitInteraction,
SelectMenuInteraction,
UserContextMenuCommandInteraction,
MessageContextMenuCommandInteraction,
} from 'discord.js';
import { executeModule } from './observableHandling';
export default class InteractionHandler extends EventsHandler<{
event: Interaction;
mod: CommandModule;
}> {
protected override discordEvent: Observable<Interaction>;
constructor(protected wrapper: Wrapper) {
super(wrapper);
this.discordEvent = <Observable<Interaction>>fromEvent(wrapper.client, 'interactionCreate');
this.init();
this.payloadSubject
.pipe(
map(this.processModules),
concatMap(({ mod, execute, eventPluginRes }) => {
//resolve all the Results from event plugins
return from(eventPluginRes).pipe(map(res => ({ mod, res, execute })));
}),
concatMap(payload => executeModule(wrapper, payload)),
catchError((err, caught) => {
wrapper.sernEmitter?.emit('error', err);
return caught;
}),
)
.subscribe();
}
override init() {
this.discordEvent.subscribe({
next: event => {
if (isMessageComponent(event)) {
const mod = Files.MessageCompCommands[event.componentType].get(event.customId);
this.setState({ event, mod });
} else if (isApplicationCommand(event) || isAutocomplete(event)) {
const mod =
Files.ApplicationCommands[event.commandType].get(event.commandName) ??
Files.BothCommands.get(event.commandName);
this.setState({ event, mod });
} else if (isModalSubmit(event)) {
/**
* maybe move modal submits into message component object maps?
*/
const mod = Files.ModalSubmitCommands.get(event.customId);
this.setState({ event, mod });
} else {
throw Error('This interaction is not supported yet');
}
},
error: reason => {
this.wrapper.sernEmitter?.emit('error', { type: PayloadType.Failure, reason });
},
});
}
protected setState(state: { event: Interaction; mod: CommandModule | undefined }): void {
if (state.mod === undefined) {
this.wrapper?.sernEmitter?.emit('warning', 'Found no module for this interaction');
} else {
//if statement above checks already, safe cast
this.payloadSubject.next(state as { event: Interaction; mod: CommandModule });
}
}
protected processModules({ mod, event }: { event: Interaction; mod: CommandModule }) {
return match(mod)
.with(
{ type: P.union(CommandType.Slash, CommandType.Both) },
applicationCommandDispatcher(event),
)
.with(
{ type: CommandType.Modal },
modalCommandDispatcher(event as ModalSubmitInteraction),
)
.with({ type: CommandType.Button }, buttonCommandDispatcher(event as ButtonInteraction))
.with(
{ type: CommandType.MenuSelect },
selectMenuCommandDispatcher(event as SelectMenuInteraction),
)
.with(
{ type: CommandType.MenuUser },
ctxMenuUserDispatcher(event as UserContextMenuCommandInteraction),
)
.with(
{ type: CommandType.MenuMsg },
ctxMenuMsgDispatcher(event as MessageContextMenuCommandInteraction),
)
.otherwise(() => {
throw Error(SernError.MismatchModule);
});
}
}

View File

@@ -1,71 +0,0 @@
import type { Message } from 'discord.js';
import { concatMap, from, fromEvent, map, Observable, of } from 'rxjs';
import { controller } from '../sern';
import Context from '../structures/context';
import type Wrapper from '../structures/wrapper';
import { fmt } from '../utilities/messageHelpers';
import * as Files from '../utilities/readFile';
import { filterCorrectModule, ignoreNonBot } from './observableHandling';
import { CommandType } from '../structures/enums';
import { SernError } from '../structures/errors';
import { asyncResolveArray } from '../utilities/asyncResolveArray';
export const onMessageCreate = (wrapper: Wrapper) => {
const { client, defaultPrefix } = wrapper;
if (defaultPrefix === undefined) return;
const messageEvent$ = <Observable<Message>>fromEvent(client, 'messageCreate');
const processMessage$ = messageEvent$.pipe(
ignoreNonBot(defaultPrefix),
map(message => {
const [prefix, ...rest] = fmt(message, defaultPrefix);
return {
ctx: Context.wrap(message),
args: <['text', string[]]>['text', rest],
mod:
Files.TextCommands.text.get(prefix) ??
Files.BothCommands.get(prefix) ??
Files.TextCommands.aliases.get(prefix),
};
}),
);
const ensureModuleType$ = processMessage$.pipe(
concatMap(payload =>
of(payload.mod).pipe(
filterCorrectModule(CommandType.Text),
map(mod => ({ ...payload, mod })),
),
),
);
const processEventPlugins$ = ensureModuleType$.pipe(
concatMap(({ ctx, args, mod }) => {
const res = asyncResolveArray(
mod.onEvent.map(ePlug => {
return ePlug.execute([ctx, args], controller);
}),
);
return from(res).pipe(map(res => ({ mod, ctx, args, res })));
}),
);
processEventPlugins$.subscribe({
next({ mod, ctx, args, res }) {
if (res.every(pl => pl.ok)) {
Promise.resolve(mod.execute(ctx, args)).then(() => {
wrapper.sernEmitter?.emit('module.activate', { type: 'success', module: mod! });
});
} else {
wrapper.sernEmitter?.emit('module.activate', {
type: 'failure',
module: mod!,
reason: SernError.PluginFailure,
});
}
},
error(e) {
wrapper.sernEmitter?.emit('error', e);
},
});
};

View File

@@ -0,0 +1,81 @@
import { EventsHandler } from './eventsHandler';
import { catchError, concatMap, from, fromEvent, map, Observable, of, switchMap } from 'rxjs';
import type Wrapper from '../structures/wrapper';
import type { Message } from 'discord.js';
import { executeModule, ignoreNonBot, isOneOfCorrectModules } from './observableHandling';
import { fmt } from '../utilities/messageHelpers';
import Context from '../structures/context';
import * as Files from '../utilities/readFile';
import type { TextCommand } from '../structures/module';
import { CommandType, PayloadType } from '../structures/enums';
import { asyncResolveArray } from '../utilities/asyncResolveArray';
import { controller } from '../sern';
export default class MessageHandler extends EventsHandler<{
ctx: Context;
args: ['text', string[]];
mod: TextCommand;
}> {
protected discordEvent: Observable<Message>;
public constructor(wrapper: Wrapper) {
super(wrapper);
this.discordEvent = <Observable<Message>>fromEvent(wrapper.client, 'messageCreate');
this.init();
this.payloadSubject
.pipe(
switchMap(({ mod, ctx, args }) => {
const res = asyncResolveArray(
mod.onEvent.map(ePlug => {
return ePlug.execute([ctx, args], controller);
}),
);
const execute = () => {
return mod.execute(ctx, args);
};
//resolves the promise and re-emits it back into source
return from(res).pipe(map(res => ({ mod, execute, res })));
}),
concatMap(payload => executeModule(wrapper, payload)),
catchError((err, caught) => {
wrapper.sernEmitter?.emit('error', err);
return caught;
}),
)
.subscribe();
}
protected init(): void {
if (this.wrapper.defaultPrefix === undefined) return; //for now, just ignore if prefix doesn't exist
const { defaultPrefix } = this.wrapper;
this.discordEvent
.pipe(
ignoreNonBot(this.wrapper.defaultPrefix),
map(message => {
const [prefix, ...rest] = fmt(message, defaultPrefix);
return {
ctx: Context.wrap(message),
args: <['text', string[]]>['text', rest],
mod:
Files.TextCommands.text.get(prefix) ??
Files.BothCommands.get(prefix) ??
Files.TextCommands.aliases.get(prefix),
};
}),
concatMap(element =>
of(element.mod).pipe(
isOneOfCorrectModules(CommandType.Text),
map(mod => ({ ...element, mod })),
),
),
)
.subscribe({
next: value => this.setState(value),
error: reason =>
this.wrapper.sernEmitter?.emit('error', { type: PayloadType.Failure, reason }),
});
}
protected setState(state: { ctx: Context; args: ['text', string[]]; mod: TextCommand }) {
this.payloadSubject.next(state);
}
}

View File

@@ -1,28 +1,11 @@
import type { Message } from 'discord.js';
import { Observable, throwError } from 'rxjs';
import { concatMap, from, Observable, of, tap, throwError } from 'rxjs';
import { SernError } from '../structures/errors';
import type { Module, CommandModuleDefs } from '../structures/module';
import { correctModuleType } from '../utilities/predicates';
import type { Result } from 'ts-results';
export function filterCorrectModule<T extends keyof CommandModuleDefs>(cmdType: T) {
return (src: Observable<Module | undefined>) =>
new Observable<CommandModuleDefs[T]>(subscriber => {
return src.subscribe({
next(mod) {
if (mod === undefined) {
return throwError(() => SernError.UndefinedModule);
}
if (correctModuleType(mod, cmdType)) {
subscriber.next(mod!);
} else {
return throwError(() => SernError.MismatchModule);
}
},
error: e => subscriber.error(e),
complete: () => subscriber.complete(),
});
});
}
import type { Module, CommandModuleDefs, CommandModule } from '../structures/module';
import { Result } from 'ts-results-es';
import type { CommandType } from '../structures/enums';
import type Wrapper from '../structures/wrapper';
import { PayloadType } from '../structures/enums';
export function ignoreNonBot(prefix: string) {
return (src: Observable<Message>) =>
@@ -64,3 +47,66 @@ export function errTap<T extends Module>(cb: (err: SernError) => void) {
});
});
}
//POG
export function isOneOfCorrectModules<T extends readonly CommandType[]>(...inputs: [...T]) {
return (src: Observable<CommandModule | undefined>) => {
return new Observable<CommandModuleDefs[T[number]]>(subscriber => {
return src.subscribe({
next(mod) {
if (mod === undefined) {
return throwError(() => SernError.UndefinedModule);
}
if (inputs.some(type => (mod.type & type) !== 0)) {
subscriber.next(mod as CommandModuleDefs[T[number]]);
} else {
return throwError(() => SernError.MismatchModule);
}
},
error: e => subscriber.error(e),
complete: () => subscriber.complete(),
});
});
};
}
export function executeModule(
wrapper: Wrapper,
payload: {
mod: CommandModule;
execute: () => unknown;
res: Result<void, void>[];
},
) {
if (payload.res.every(el => el.ok)) {
const executeFn = Result.wrapAsync<unknown, Error | string>(() =>
Promise.resolve(payload.execute()),
);
return from(executeFn).pipe(
concatMap(res => {
if (res.err) {
return throwError(() => ({
type: PayloadType.Failure,
reason: res.val,
module: payload.mod,
}));
}
return of(res.val).pipe(
tap(() =>
wrapper.sernEmitter?.emit('module.activate', {
type: PayloadType.Success,
module: payload.mod,
}),
),
);
}),
);
} else {
wrapper.sernEmitter?.emit('module.activate', {
type: PayloadType.Failure,
module: payload.mod,
reason: SernError.PluginFailure,
});
return of(undefined);
}
}

View File

@@ -1,125 +0,0 @@
import { concat, concatMap, from, fromEvent, map, Observable, of, skip, take } from 'rxjs';
import { basename } from 'path';
import * as Files from '../utilities/readFile';
import type Wrapper from '../structures/wrapper';
import type { Result } from 'ts-results';
import { Err, Ok } from 'ts-results';
import type { Awaitable } from 'discord.js';
import { ApplicationCommandType, ComponentType } from 'discord.js';
import type { CommandModule } from '../structures/module';
import { match } from 'ts-pattern';
import { SernError } from '../structures/errors';
import type { DefinedCommandModule } from '../../types/handler';
import { CommandType, PluginType } from '../structures/enums';
import { errTap } from './observableHandling';
import { processCommandPlugins } from './userDefinedEventsHandling';
export function onReady(wrapper: Wrapper) {
const { client, commands } = wrapper;
const ready$ = fromEvent(client, 'ready').pipe(take(1), skip(1));
// Using sernModule function already checks if module is not EventModule
const processCommandFiles$ = Files.buildData<CommandModule>(commands).pipe(
errTap(reason => {
wrapper.sernEmitter?.emit('module.register', {
type: 'failure',
module: undefined,
reason,
});
}),
map(({ mod, absPath }) => {
return {
absPath,
mod: <DefinedCommandModule>{
name: mod?.name ?? Files.fmtFileName(basename(absPath)),
description: mod?.description ?? '...',
...mod,
},
};
}),
);
const processPlugins$ = processCommandFiles$.pipe(
concatMap(payload => {
const cmdPluginRes = processCommandPlugins(wrapper, payload);
return of({ mod: payload.mod, cmdPluginRes });
}),
);
(
concat(ready$, processPlugins$) as Observable<{
mod: DefinedCommandModule;
cmdPluginRes: {
execute: Awaitable<Result<void, void>>;
type: PluginType.Command;
name: string;
description: string;
}[];
}>
)
.pipe(
concatMap(pl => {
return from(
//refactor, this allocates too many objects
Promise.all(
pl.cmdPluginRes.map(async e => ({ ...e, execute: await e.execute })),
),
).pipe(map(res => ({ ...pl, cmdPluginsRes: res })));
}),
)
.subscribe(({ mod, cmdPluginsRes }) => {
const loadedPluginsCorrectly = cmdPluginsRes.every(({ execute }) => execute.ok);
if (loadedPluginsCorrectly) {
const res = registerModule(mod);
if (res.err) {
throw Error(SernError.NonValidModuleType);
}
wrapper.sernEmitter?.emit('module.register', { type: 'success', module: mod });
} else {
wrapper.sernEmitter?.emit('module.register', {
type: 'failure',
module: mod,
reason: SernError.PluginFailure,
});
}
});
}
function registerModule(mod: DefinedCommandModule): Result<void, void> {
const name = mod.name;
return match<DefinedCommandModule>(mod)
.with({ type: CommandType.Text }, mod => {
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
Files.TextCommands.text.set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Slash }, mod => {
Files.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Both }, mod => {
Files.BothCommands.set(name, mod);
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
return Ok.EMPTY;
})
.with({ type: CommandType.MenuUser }, mod => {
Files.ApplicationCommands[ApplicationCommandType.User].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.MenuMsg }, mod => {
Files.ApplicationCommands[ApplicationCommandType.Message].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Button }, mod => {
Files.ApplicationCommands[ComponentType.Button].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.MenuSelect }, mod => {
Files.MessageCompCommands[ComponentType.SelectMenu].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Modal }, mod => {
Files.ModalSubmitCommands.set(name, mod);
return Ok.EMPTY;
})
.otherwise(() => Err.EMPTY);
}

View File

@@ -0,0 +1,159 @@
import { EventsHandler } from './eventsHandler';
import type Wrapper from '../structures/wrapper';
import { concatMap, fromEvent, Observable, map, take, of, from, toArray, switchMap } from 'rxjs';
import type { CommandModule } from '../structures/module';
import * as Files from '../utilities/readFile';
import { errTap } from './observableHandling';
import type { DefinedCommandModule } from '../../types/handler';
import { basename } from 'path';
import { CommandType, PayloadType, PluginType } from '../structures/enums';
import { processCommandPlugins } from './userDefinedEventsHandling';
import type { Awaitable } from 'discord.js';
import { SernError } from '../structures/errors';
import { match } from 'ts-pattern';
import { type Result, Err, Ok } from 'ts-results-es';
import { ApplicationCommandType, ComponentType } from 'discord.js';
export default class ReadyHandler extends EventsHandler<{
mod: DefinedCommandModule;
absPath: string;
}> {
protected discordEvent!: Observable<{ mod: CommandModule; absPath: string }>;
constructor(wrapper: Wrapper) {
super(wrapper);
const ready$ = fromEvent(this.wrapper.client, 'ready').pipe(take(1));
this.discordEvent = ready$.pipe(
concatMap(() =>
Files.buildData<CommandModule>(this.wrapper.commands).pipe(
errTap(reason =>
wrapper.sernEmitter?.emit('module.register', {
type: PayloadType.Failure,
module: undefined,
reason,
}),
),
),
),
);
this.init();
this.payloadSubject
.pipe(
concatMap(payload => this.processPlugins(payload)),
concatMap(payload => this.resolvePlugins(payload)),
)
.subscribe(payload => {
const allPluginsSuccessful = payload.pluginRes.every(({ execute }) => execute.ok);
if (allPluginsSuccessful) {
const res = registerModule(payload.mod);
if (res.err) {
throw Error(SernError.InvalidModuleType);
}
wrapper.sernEmitter?.emit('module.register', {
type: PayloadType.Success,
module: payload.mod,
});
} else {
wrapper.sernEmitter?.emit('module.register', {
type: PayloadType.Failure,
module: payload.mod,
reason: SernError.PluginFailure,
});
}
});
}
private static intoDefinedModule({ absPath, mod }: { absPath: string; mod: CommandModule }): {
absPath: string;
mod: DefinedCommandModule;
} {
return {
absPath,
mod: {
name: mod?.name ?? Files.fmtFileName(basename(absPath)),
description: mod?.description ?? '...',
...mod,
},
};
}
private resolvePlugins({
mod,
cmdPluginRes,
}: {
mod: DefinedCommandModule;
cmdPluginRes: {
name: string;
description: string;
execute: Awaitable<Result<void, void>>;
type: PluginType.Command;
}[];
}) {
if (mod.plugins.length === 0) {
return of({ mod, pluginRes: [] });
}
// modules with no event plugins are ignored in the previous
return from(cmdPluginRes).pipe(
switchMap(pl =>
from(pl.execute).pipe(
map(execute => ({ ...pl, execute })),
toArray(),
),
),
map(pluginRes => ({ mod, pluginRes })),
);
}
private processPlugins(payload: { mod: DefinedCommandModule; absPath: string }) {
const cmdPluginRes = processCommandPlugins(this.wrapper, payload);
return of({ mod: payload.mod, cmdPluginRes });
}
protected init() {
this.discordEvent.pipe(map(ReadyHandler.intoDefinedModule)).subscribe({
next: value => this.setState(value),
complete: () => this.payloadSubject.unsubscribe(),
});
}
protected setState(state: { absPath: string; mod: DefinedCommandModule }): void {
this.payloadSubject.next(state);
}
}
function registerModule(mod: DefinedCommandModule): Result<void, void> {
const name = mod.name;
return match<DefinedCommandModule>(mod)
.with({ type: CommandType.Text }, mod => {
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
Files.TextCommands.text.set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Slash }, mod => {
Files.ApplicationCommands[ApplicationCommandType.ChatInput].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Both }, mod => {
Files.BothCommands.set(name, mod);
mod.alias?.forEach(a => Files.TextCommands.aliases.set(a, mod));
return Ok.EMPTY;
})
.with({ type: CommandType.MenuUser }, mod => {
Files.ApplicationCommands[ApplicationCommandType.User].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.MenuMsg }, mod => {
Files.ApplicationCommands[ApplicationCommandType.Message].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Button }, mod => {
Files.MessageCompCommands[ComponentType.Button].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.MenuSelect }, mod => {
Files.MessageCompCommands[ComponentType.SelectMenu].set(name, mod);
return Ok.EMPTY;
})
.with({ type: CommandType.Modal }, mod => {
Files.ModalSubmitCommands.set(name, mod);
return Ok.EMPTY;
})
.otherwise(() => Err.EMPTY);
}

View File

@@ -2,8 +2,14 @@ import { from, fromEvent, map } from 'rxjs';
import * as Files from '../utilities/readFile';
import { buildData, ExternalEventEmitters } from '../utilities/readFile';
import { controller } from '../sern';
import type { DefinedCommandModule, DefinedEventModule, SpreadParams } from '../../types/handler';
import type {
DefinedCommandModule,
DefinedEventModule,
EventInput,
SpreadParams,
} from '../../types/handler';
import type { EventModule } from '../structures/module';
import { PayloadType } from '../structures/enums';
import type Wrapper from '../structures/wrapper';
import { basename } from 'path';
import { match } from 'ts-pattern';
@@ -27,13 +33,7 @@ export function processCommandPlugins<T extends DefinedCommandModule>(
}));
}
export function processEvents(
wrapper: Wrapper,
events:
| string
| { mod: EventModule; absPath: string }[]
| (() => { mod: EventModule; absPath: string }[]),
) {
export function processEvents(wrapper: Wrapper, events: EventInput) {
const eventStream$ = eventObservable$(wrapper, events);
const normalize$ = eventStream$.pipe(
map(({ mod, absPath }) => {
@@ -58,13 +58,7 @@ export function processEvents(
});
}
function eventObservable$(
{ sernEmitter }: Wrapper,
events:
| string
| { mod: EventModule; absPath: string }[]
| (() => { mod: EventModule; absPath: string }[]),
) {
function eventObservable$({ sernEmitter }: Wrapper, events: EventInput) {
return match(events)
.when(Array.isArray, (arr: { mod: EventModule; absPath: string }[]) => {
return from(arr);
@@ -75,7 +69,7 @@ function eventObservable$(
return buildData<EventModule>(eventsDir).pipe(
errTap(reason =>
sernEmitter?.emit('module.register', {
type: 'failure',
type: PayloadType.Failure,
module: undefined,
reason,
}),

View File

@@ -7,14 +7,14 @@
* The goal of plugins is to organize commands and
* provide extensions to repetitive patterns
* examples include refreshing modules,
* categorizing commands, cooldowns, permissions, etc.
* categorizing commands, cool-downs, permissions, etc.
* Plugins are reminiscent of middleware in express.
*/
import type { AutocompleteInteraction, Awaitable, Client, ClientEvents } from 'discord.js';
import type { Err, Ok, Result } from 'ts-results';
import type { CommandType, DefinitelyDefined, Override, SernEventsMapping } from '../..';
import { EventType, PluginType } from '../..';
import type { Result, Ok, Err } from 'ts-results-es';
import type { CommandType, DefinitelyDefined, Override, SernEventsMapping } from '../../index';
import { EventType, PluginType } from '../../index';
import type { BaseModule, CommandModuleDefs, EventModuleDefs } from '../structures/module';
import type { EventEmitter } from 'events';
import type {
@@ -171,8 +171,6 @@ export type EventModulePlugin<T extends EventType> =
| EventModuleCommandPluginDefs[T];
export type CommandModulePlugin<T extends CommandType> = EventPlugin<T> | CommandPlugin<T>;
//TODO: I WANT BETTER TYPINGS AHHHHHHHHHHHHHHH
// Maybe add overlaods
/**
* User inputs this type. Sern processes behind the scenes for better usage

View File

@@ -1,13 +1,15 @@
import type Wrapper from './structures/wrapper';
import { onReady } from './events/readyEvent';
import { onMessageCreate } from './events/messageEvent';
import { onInteractionCreate } from './events/interactionCreate';
import { Err, Ok } from 'ts-results';
import { Err, Ok } from 'ts-results-es';
import { ExternalEventEmitters } from './utilities/readFile';
import type { EventEmitter } from 'events';
import { processEvents } from './events/userDefinedEventsHandling';
import type { CommandModule, EventModule } from './structures/module';
import { EventType, PluginType } from './structures/enums';
import type {
CommandModule,
CommandModuleDefs,
EventModule,
EventModuleDefs,
} from './structures/module';
import { CommandType, EventType, PluginType } from './structures/enums';
import type {
CommandPlugin,
EventModuleCommandPluginDefs,
@@ -17,43 +19,55 @@ import type {
InputEventModule,
} from './plugins/plugin';
import { SernError } from './structures/errors';
import InteractionHandler from './events/interactionHandler';
import ReadyHandler from './events/readyHandler';
import MessageHandler from './events/messageHandler';
/**
*
* @param wrapper options to pass into sern.
* Function to start the handler up.
* @param wrapper Options to pass into sern.
* Function to start the handler up
* @example
* ```ts title="src/index.ts"
* Sern.init({
* client,
* defaultPrefix: '!',
* commands: 'dist/commands',
* })
* ```
*/
export function init(wrapper: Wrapper) {
const { events } = wrapper;
if (events !== undefined) {
processEvents(wrapper, events);
}
onReady(wrapper);
onMessageCreate(wrapper);
onInteractionCreate(wrapper);
new ReadyHandler(wrapper);
new MessageHandler(wrapper);
new InteractionHandler(wrapper);
}
/**
*
* @deprecated - use Sern#makeDependencies instead
* @param emitter Any external event emitter.
* The object will be stored in a map, and then fetched by the name of the instance's class provided.
* The object will be stored in a map, and then fetched by the name of the instance's class.
* As there are infinite possibilities to adding external event emitters,
* Most types aren't provided and are as narrow as possibly can.
* @example
* ```ts title="src/index.ts"
* //Add this before initiating Sern!
* Sern.addExternal(new Level())
* ```
* Sern.addExternal(new Level())
* @example
* ```ts title="events/level.ts"
* export default eventModule({
* emitter: 'Level',
* type : EventType.External,
* name: 'error',
* execute(args) {
* console.log(args)
* }
* })
* ```
* ```
* // events/level.ts
* export default eventModule({
* emitter: 'Level',
* type : EventType.External,
* name: 'error',
* execute(args) {
* console.log(args)
* }
* })
*
*/
export function addExternal<T extends EventEmitter>(emitter: T) {
if (ExternalEventEmitters.has(emitter.constructor.name)) {
@@ -62,11 +76,18 @@ export function addExternal<T extends EventEmitter>(emitter: T) {
ExternalEventEmitters.set(emitter.constructor.name, emitter);
}
/**
* The object passed into every plugin to control a command's behavior
*/
export const controller = {
next: () => Ok.EMPTY,
stop: () => Err.EMPTY,
};
/**
* The wrapper function to define command modules for sern
* @param mod
*/
export function commandModule(mod: InputCommandModule): CommandModule {
const onEvent: EventPlugin[] = [];
const plugins: CommandPlugin[] = [];
@@ -84,6 +105,10 @@ export function commandModule(mod: InputCommandModule): CommandModule {
plugins,
} as CommandModule;
}
/**
* The wrapper function to define event modules for sern
* @param mod
*/
export function eventModule(mod: InputEventModule): EventModule {
const onEvent: EventModuleEventPluginDefs[EventType][] = [];
const plugins: EventModuleCommandPluginDefs[EventType][] = [];
@@ -99,3 +124,17 @@ export function eventModule(mod: InputEventModule): EventModule {
plugins,
} as EventModule;
}
export abstract class CommandExecutable<Type extends CommandType> {
abstract type: Type;
plugins: CommandPlugin<Type>[] = [];
onEvent: EventPlugin<Type>[] = [];
abstract execute: CommandModuleDefs[Type]['execute'];
}
export abstract class EventExecutable<Type extends EventType> {
abstract type: Type;
plugins: EventModuleCommandPluginDefs[Type][] = [];
onEvent: EventModuleEventPluginDefs[Type][] = [];
abstract execute: EventModuleDefs[Type]['execute'];
}

View File

@@ -6,14 +6,13 @@ import type {
GuildMember,
InteractionReplyOptions,
Message,
ReplyMessageOptions,
Snowflake,
TextBasedChannel,
MessageReplyOptions,
User,
} from 'discord.js';
import { None, Option, Some } from 'ts-results';
import type { Nullish } from '../../types/handler';
import { ExternallyUsed } from '../utilities/externallyUsed';
import { type Option, None, Some } from 'ts-results-es';
import type { Nullish, ReplyOptions } from '../../types/handler';
import { SernError } from './errors';
function firstSome<T>(...args: Option<T>[]): Nullish<T> {
@@ -42,7 +41,6 @@ export default class Context {
* CommandType.Slash or the event fired in a Both command was
* ChatInputCommandInteraction
*/
@ExternallyUsed
public get message() {
return this.oMsg.expect(SernError.MismatchEvent);
}
@@ -51,12 +49,10 @@ export default class Context {
* CommandType.Text or the event fired in a Both command was
* Message
*/
@ExternallyUsed
public get interaction() {
return this.oInterac.expect(SernError.MismatchEvent);
}
@ExternallyUsed
public get id(): Snowflake {
return firstSome(
this.oInterac.map(i => i.id),
@@ -64,7 +60,6 @@ export default class Context {
)!;
}
@ExternallyUsed
public get channel(): Nullish<TextBasedChannel> {
return firstSome(
this.oMsg.map(m => m.channel),
@@ -72,7 +67,6 @@ export default class Context {
);
}
@ExternallyUsed
public get user(): User {
return firstSome(
this.oMsg.map(m => m.author),
@@ -80,7 +74,6 @@ export default class Context {
)!;
}
@ExternallyUsed
public get createdTimestamp(): number {
return firstSome(
this.oMsg.map(m => m.createdTimestamp),
@@ -88,7 +81,6 @@ export default class Context {
)!;
}
@ExternallyUsed
public get guild(): Guild {
return firstSome(
this.oMsg.map(m => m.guild),
@@ -96,7 +88,6 @@ export default class Context {
)!;
}
@ExternallyUsed
public get guildId(): Snowflake {
return firstSome(
this.oMsg.map(m => m.guildId),
@@ -107,7 +98,6 @@ export default class Context {
/*
* interactions can return APIGuildMember if the guild it is emitted from is not cached
*/
@ExternallyUsed
public get member(): Nullish<GuildMember | APIGuildMember> {
return firstSome(
this.oMsg.map(m => m.member),
@@ -115,7 +105,6 @@ export default class Context {
);
}
@ExternallyUsed
public get client(): Client {
return firstSome(
this.oMsg.map(m => m.client),
@@ -123,7 +112,6 @@ export default class Context {
)!;
}
@ExternallyUsed
public get inGuild(): boolean {
return firstSome(
this.oMsg.map(m => m.inGuild()),
@@ -138,15 +126,11 @@ export default class Context {
return new Context(Some(wrappable), None);
}
@ExternallyUsed
public isEmpty() {
return this.oMsg.none && this.oInterac.none;
}
//Make queueable
@ExternallyUsed
public reply(
content: string | Omit<InteractionReplyOptions, 'fetchReply'> | ReplyMessageOptions,
) {
public reply(content: ReplyOptions) {
return firstSome(
this.oInterac.map(i => {
return i
@@ -154,7 +138,7 @@ export default class Context {
.then(() => i.fetchReply());
}),
this.oMsg.map(m => {
return m.reply(content as string | ReplyMessageOptions);
return m.reply(content as string | MessageReplyOptions);
}),
)!;
}

View File

@@ -1,26 +1,119 @@
/**
* @enum { number };
* @enum { number }
* @example
* ```ts
* export default commandModule({
* // highlight-next-line
* type : CommandType.Text,
* name : 'a text command'
* execute(message) {
* console.log(message.content)
* }
* })
* ```
*/
enum CommandType {
export enum CommandType {
/**
* The CommandType for text commands
*/
Text = 0b00000000001,
/**
* The CommandType for slash commands
*/
Slash = 0b00000000010,
/**
* The CommandType for hybrid commands, text and slash
*/
Both = 0b0000011,
/**
* The CommandType for UserContextMenuInteraction commands
*/
MenuUser = 0b00000000100,
/**
* The CommandType for MessageContextMenuInteraction commands
*/
MenuMsg = 0b0000001000,
/**
* The CommandType for ButtonInteraction commands
*/
Button = 0b00000010000,
/**
* The CommandType for SelectMenuInteraction commands
*/
MenuSelect = 0b00000100000,
/**
* The CommandType for ModalSubmitInteraction commands
*/
Modal = 0b00001000000,
}
enum EventType {
/**
* @enum { number }
* @example
* ```ts
* export default eventModule({
* //highlight-next-line
* type : EventType.Discord,
* name : 'guildMemberAdd'
* execute(member : GuildMember) {
* console.log(member)
* }
* })
* ```
*/
export enum EventType {
/**
* The EventType for handling discord events
*/
Discord = 0b01,
/**
* The EventType for handling sern events
*/
Sern = 0b10,
/**
* The EventType for handling external events.
* Could be for example, `process` events, database events
*/
External = 0b11,
}
enum PluginType {
/**
* @enum { number }
* @example
* ```ts
* export default function myPlugin() : EventPlugin<CommandType.Text> {
* //highlight-next-line
* type : PluginType.Event,
* execute([ctx, args], controller) {
* return controller.next();
* }
* }
* ```
*/
export enum PluginType {
/**
* The PluginType for CommandPlugins
*/
Command = 0b01,
/**
* The PluginType for EventPlugins
*/
Event = 0b10,
}
export { CommandType, PluginType, EventType };
/**
* @enum { string }
*/
export enum PayloadType {
/**
* The PayloadType for a SernEmitter success event
*/
Success = 'success',
/**
* The PayloadType for a SernEmitter failure event
*/
Failure = 'failure',
/**
* The PayloadType for a SernEmitter warning event
*/
Warning = 'warning',
}

View File

@@ -1,9 +1,34 @@
/**
* @enum { string }
*/
export enum SernError {
NonValidModuleType = 'Detected an unknown module type',
UndefinedModule = `A module could not be detected at`,
/**
* Throws when registering an invalid module.
* This means it is undefined or an invalid command type was provided
*/
InvalidModuleType = 'Detected an unknown module type',
/**
* Attempted to lookup module in command module store. Nothing was found!
*/
UndefinedModule = `A module could not be detected`,
/**
* Attempted to lookup module in command module store. Nothing was found!
*/
MismatchModule = `A module type mismatched with event emitted!`,
/**
* Unsupported interaction at this moment.
*/
NotSupportedInteraction = `This interaction is not supported.`,
/**
* One plugin called `controller.stop()` (end command execution / loading)
*/
PluginFailure = `A plugin failed to call controller.next()`,
/**
* A crash that occurs when accessing an invalid property of Context
*/
MismatchEvent = `You cannot use message when an interaction fired or vice versa`,
/**
* Unsupported feature attempted to access at this time
*/
NotSupportedYet = `This feature is not supported yet`,
}

View File

@@ -11,6 +11,9 @@ import type {
import type { Awaitable, ClientEvents } from 'discord.js';
import type { EventType } from './enums';
/*
* Mapped type to generate all sern event modules
*/
export type SernEventCommand<T extends keyof SernEventsMapping = keyof SernEventsMapping> =
Override<
BaseModule,
@@ -22,6 +25,9 @@ export type SernEventCommand<T extends keyof SernEventsMapping = keyof SernEvent
execute(...args: SernEventsMapping[T]): Awaitable<void | unknown>;
}
>;
/*
* Mapped type to generate all discord event modules
*/
export type DiscordEventCommand<T extends keyof ClientEvents = keyof ClientEvents> = Override<
BaseModule,
{
@@ -32,7 +38,9 @@ export type DiscordEventCommand<T extends keyof ClientEvents = keyof ClientEvent
execute(...args: ClientEvents[T]): Awaitable<void | unknown>;
}
>;
/*
* Type for any event emitter that can be handled by sern
*/
export type ExternalEventCommand = Override<
BaseModule,
{

View File

@@ -30,7 +30,6 @@ export interface BaseModule {
execute: (ctx: Context, args: Args) => Awaitable<void | unknown>;
}
//possible refactoring types into interfaces and not types
export type TextCommand = Override<
BaseModule,
{
@@ -118,7 +117,6 @@ export type ModalSubmitCommand = Override<
// Autocomplete commands are a little different
// They can't have command plugins as they are
// in conjunction with chat input commands
// TODO: possibly in future, allow Autocmp commands in separate files?
export type AutocompleteCommand = Override<
BaseModule,
{
@@ -178,7 +176,7 @@ export type SernAutocompleteData = Override<
>;
/**
* Type that just uses SernAutocompleteData and not regular autocomplete
* Type that replaces autocomplete with {@link SernAutocompleteData}
*/
export type BaseOptions =
| ApplicationCommandChoicesData

View File

@@ -3,22 +3,21 @@ import type SernEmitter from '../sernEmitter';
import type { EventModule } from './module';
/**
* An object to be passed into Sern.Handler constructor.
* An object to be passed into Sern#init() function.
* @typedef {object} Wrapper
* @property {readonly Client} client
* @prop { readonly SernEmitter } sernEmitter
* @property {readonly string} defaultPrefix
* @property {readonly string} commands
* @prop { readonly DiscordEvent[] } events
*/
interface Wrapper {
//@deprecated - Use Sern#makeDependencies instead
readonly client: Client;
//@deprecated - Use Sern#makeDependencies instead
readonly sernEmitter?: SernEmitter;
readonly defaultPrefix?: string;
readonly commands: string;
readonly events?:
| string
//@deprecated - array and function options will be removed
| { mod: EventModule; absPath: string }[]
//@deprecated - array and function options will be removed
| (() => { mod: EventModule; absPath: string }[]);
}

View File

@@ -1,18 +0,0 @@
/**
* This function denotes usage of decorated method is external
* Also, makes method appear 'used' in IDEs
* @param _target
* @param _propertyKey
* @param _descriptor
* @constructor
*/
export function ExternallyUsed(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_target: unknown,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_propertyKey: string,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_descriptor: PropertyDescriptor,
) {
return void 0;
}

View File

@@ -1,19 +1,14 @@
import type { CommandModuleDefs, EventModule, Module } from '../structures/module';
import type {
Awaitable,
ButtonInteraction,
ChatInputCommandInteraction,
CommandInteraction,
MessageComponentInteraction,
MessageContextMenuCommandInteraction,
SelectMenuInteraction,
UserContextMenuCommandInteraction,
} from 'discord.js';
import {
AutocompleteInteraction,
Interaction,
InteractionType,
ModalSubmitInteraction,
type ModalSubmitInteraction,
type ButtonInteraction,
type SelectMenuInteraction,
type ChatInputCommandInteraction,
type UserContextMenuCommandInteraction,
type MessageContextMenuCommandInteraction,
} from 'discord.js';
import type {
DiscordEventCommand,
@@ -31,31 +26,12 @@ export function correctModuleType<T extends keyof CommandModuleDefs>(
return plug !== undefined && (plug.type & type) !== 0;
}
export function isChatInputCommand(i: CommandInteraction): i is ChatInputCommandInteraction {
return i.isChatInputCommand();
}
export function isButton(i: MessageComponentInteraction): i is ButtonInteraction {
return i.isButton();
}
export function isSelectMenu(i: MessageComponentInteraction): i is SelectMenuInteraction {
return i.isSelectMenu();
}
export function isMessageCtxMenuCmd(
i: CommandInteraction,
): i is MessageContextMenuCommandInteraction {
return i.isMessageContextMenuCommand();
}
export function isUserContextMenuCmd(
i: CommandInteraction,
): i is UserContextMenuCommandInteraction {
return i.isUserContextMenuCommand();
}
export function isApplicationCommand(interaction: Interaction): interaction is CommandInteraction {
export function isApplicationCommand(
interaction: Interaction,
): interaction is
| ChatInputCommandInteraction
| UserContextMenuCommandInteraction
| MessageContextMenuCommandInteraction {
return interaction.type === InteractionType.ApplicationCommand;
}
@@ -65,18 +41,12 @@ export function isModalSubmit(interaction: Interaction): interaction is ModalSub
export function isAutocomplete(interaction: Interaction): interaction is AutocompleteInteraction {
return interaction.type === InteractionType.ApplicationCommandAutocomplete;
}
export function isMessageComponent(
interaction: Interaction,
): interaction is MessageComponentInteraction {
): interaction is ButtonInteraction | SelectMenuInteraction {
return interaction.type === InteractionType.MessageComponent;
}
export function isPromise<T>(promiseLike: Awaitable<T>): promiseLike is PromiseLike<T> {
const keys = new Set(Object.keys(promiseLike));
return keys.has('then') && keys.has('catch');
}
export function isDiscordEvent(el: EventModule): el is DiscordEventCommand {
return el.type === EventType.Discord;
}

View File

@@ -1,31 +1,30 @@
import { ApplicationCommandType, ComponentType } from 'discord.js';
import { readdirSync, statSync } from 'fs';
import { join } from 'path';
import { from, Observable } from 'rxjs';
import type { Module } from '../structures/module';
import { type Observable, from, concatAll } from 'rxjs';
import type { CommandModule } from '../structures/module';
import { SernError } from '../structures/errors';
import type { Result } from 'ts-results';
import { Err, Ok } from 'ts-results';
import { type Result, Err, Ok } from 'ts-results-es';
import type { EventEmitter } from 'events';
//Maybe move this? this probably doesnt belong in utlities/
export const BothCommands = new Map<string, Module>();
export const BothCommands = new Map<string, CommandModule>();
export const ApplicationCommands = {
[ApplicationCommandType.User]: new Map<string, Module>(),
[ApplicationCommandType.Message]: new Map<string, Module>(),
[ApplicationCommandType.ChatInput]: new Map<string, Module>(),
} as { [K in ApplicationCommandType]: Map<string, Module> };
[ApplicationCommandType.User]: new Map<string, CommandModule>(),
[ApplicationCommandType.Message]: new Map<string, CommandModule>(),
[ApplicationCommandType.ChatInput]: new Map<string, CommandModule>(),
} as { [K in ApplicationCommandType]: Map<string, CommandModule> };
export const MessageCompCommands = {
[ComponentType.Button]: new Map<string, Module>(),
[ComponentType.SelectMenu]: new Map<string, Module>(),
[ComponentType.TextInput]: new Map<string, Module>(),
[ComponentType.Button]: new Map<string, CommandModule>(),
[ComponentType.SelectMenu]: new Map<string, CommandModule>(),
[ComponentType.TextInput]: new Map<string, CommandModule>(),
};
export const TextCommands = {
text: new Map<string, Module>(),
aliases: new Map<string, Module>(),
text: new Map<string, CommandModule>(),
aliases: new Map<string, CommandModule>(),
};
export const ModalSubmitCommands = new Map<string, Module>();
export const ModalSubmitCommands = new Map<string, CommandModule>();
/**
* keeps all external emitters stored here
*/
@@ -63,15 +62,27 @@ export function buildData<T>(commandDir: string): Observable<
SernError
>
> {
const commands = getCommands(commandDir);
return from(
getCommands(commandDir).map(absPath => {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const mod = <T | undefined>require(absPath).default;
if (mod !== undefined) {
Promise.all(
commands.map(async absPath => {
let mod: T | undefined;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
mod = require(absPath).default;
} catch {
mod = (await import(`file:///` + absPath)).default;
}
if (mod === undefined) {
return Err(SernError.UndefinedModule);
}
try {
mod = new (mod as unknown as new () => T)();
} catch {}
return Ok({ mod, absPath });
} else return Err(SernError.UndefinedModule);
}),
);
}),
),
).pipe(concatAll());
}
export function getCommands(dir: string): string[] {

View File

@@ -0,0 +1,42 @@
import type { SernAutocompleteData, SernOptionsData } from '../structures/module';
import { ApplicationCommandOptionType, AutocompleteInteraction } from 'discord.js';
export default function treeSearch(
iAutocomplete: AutocompleteInteraction,
options: SernOptionsData[] | undefined,
): SernAutocompleteData {
if (options === undefined) return undefined;
const _options = options.slice(); // required to prevent direct mutation of options
let autocompleteData: SernAutocompleteData;
while (_options.length > 0) {
const cur = _options.pop()!;
switch (cur.type) {
case ApplicationCommandOptionType.Subcommand:
{
for (const option of cur.options ?? []) {
_options.push(option);
}
}
break;
case ApplicationCommandOptionType.SubcommandGroup:
{
for (const command of cur.options ?? []) {
_options.push(command);
}
}
break;
default:
{
if (cur.autocomplete) {
const choice = iAutocomplete.options.getFocused(true);
if (cur.name === choice.name && cur.autocomplete) {
autocompleteData = cur;
}
}
}
break;
}
}
return autocompleteData;
}

View File

@@ -1,5 +1,5 @@
import SernEmitter from './handler/sernEmitter';
export { eventModule, commandModule } from './handler/sern';
export { eventModule, commandModule, EventExecutable, CommandExecutable } from './handler/sern';
export * as Sern from './handler/sern';
export * from './types/handler';
export * from './handler/structures/structxports';

View File

@@ -1,5 +1,7 @@
import type { CommandInteractionOptionResolver } from 'discord.js';
import type { CommandModule, EventModule, Module } from '../handler/structures/module';
import type { PayloadType } from '../handler/structures/enums';
import type { InteractionReplyOptions, MessageReplyOptions } from 'discord.js';
export type Nullish<T> = T | undefined | null;
// Thanks to @kelsny
@@ -20,17 +22,16 @@ export type DefinitelyDefined<T, K extends keyof T = keyof T> = {
: Required<T>[L];
} & T;
type Reconstruct<T> = T extends Omit<infer O, infer _> ? O & Reconstruct<O> : T;
type IsOptional<T> = {
[K in keyof T]-?: T[K] extends Required<T>[K] ? false : true;
};
export type EventInput =
| string
| { mod: EventModule; absPath: string }[]
| (() => { mod: EventModule; absPath: string }[]);
/**
* Turns a function with a union of array of args into a single union
* [ T , V , B ] | [ A ] => T | V | B | A
*/
export type SpreadParams<T extends (...args: any) => unknown> = (
export type SpreadParams<T extends (...args: never) => unknown> = (
args: Parameters<T>[number],
) => unknown;
@@ -42,10 +43,16 @@ export type DefinedModule = DefinitelyDefined<Module, 'name' | 'description'>;
export type DefinedCommandModule = DefinitelyDefined<CommandModule, 'name' | 'description'>;
export type DefinedEventModule = DefinitelyDefined<EventModule, 'name' | 'description'>;
export type Payload =
| { type: 'success'; module: Module }
| { type: 'failure'; module: Module | undefined; reason: string | Error };
| { type: PayloadType.Success; module: Module }
| { type: PayloadType.Failure; module?: Module; reason: string | Error };
export type SernEventsMapping = {
['module.register']: [Payload];
['module.activate']: [Payload];
['error']: [Error | string];
['error']: [Payload];
['warning']: [string];
};
export type ReplyOptions =
| string
| Omit<InteractionReplyOptions, 'fetchReply'>
| MessageReplyOptions;

17
tsconfig-base.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"noImplicitAny": true,
"strictNullChecks": true,
"importsNotUsedAsValues": "error",
"moduleResolution": "node",
"skipLibCheck": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true
},
"exclude": ["node_modules", "dist"],
"include": ["src"]
}

8
tsconfig-cjs.json Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "dist/cjs",
"target": "esnext"
}
}

8
tsconfig-esm.json Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": "./tsconfig-base.json",
"compilerOptions": {
"module": "esnext",
"outDir": "dist/esm",
"target": "esnext"
}
}

View File

@@ -1,21 +0,0 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"resolveJsonModule": true,
"target": "esnext",
"module": "commonjs",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"noImplicitAny": true,
"strictNullChecks": true,
"importsNotUsedAsValues": "error",
"moduleResolution": "node",
"skipLibCheck": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
},
"exclude": ["node_modules", "dist"],
"include": ["src"],
}

38
tsup.config.js Normal file
View File

@@ -0,0 +1,38 @@
import { defineConfig } from 'tsup';
export default defineConfig([
{
entry: ['src/index.ts'],
format: 'esm',
sourcemap: false,
target: 'node16',
tsconfig: './tsconfig-esm.json',
outDir: './dist/esm',
platform: 'node',
external: ['discord.js'],
clean: true,
treeshake: true,
outExtension() {
return {
js: '.mjs',
};
},
},
{
entry: ['src/index.ts'],
format: 'cjs',
splitting: false,
sourcemap: false,
external: ['discord.js'],
clean: true,
target: 'node16',
tsconfig: './tsconfig-cjs.json',
outDir: './dist/cjs',
platform: 'node',
outExtension() {
return {
js: '.cjs',
};
},
},
]);